update solution page
This commit is contained in:
@@ -259,7 +259,7 @@ export default function ConnectedLogistics() {
|
||||
</div>
|
||||
<div className="elementor-element elementor-element-165dfa5 elementor-widget__width-initial elementor-widget elementor-widget-text-editor" data-id="165dfa5" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
|
||||
<div className="elementor-widget-container">
|
||||
<p>Learn from every delivery. Get smarter with every mile traveled.</p>
|
||||
<p>Learn from every shipment. Get smarter with every mile traveled.</p>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
|
||||
@@ -13,7 +13,7 @@ const CARDS_DATA = [
|
||||
{
|
||||
index: 1,
|
||||
title: "Operational Visibility",
|
||||
desc: "Real-time tracking and centralized control provide complete visibility across every shipment, vehicle, and delivery milestone.",
|
||||
desc: "Real-time tracking and centralized control provide complete visibility across every shipment, vehicle, and logistics milestone.",
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="2" y="7" width="16" height="10" rx="2" ry="2" />
|
||||
@@ -26,7 +26,7 @@ const CARDS_DATA = [
|
||||
{
|
||||
index: 2,
|
||||
title: "Intelligent Routing",
|
||||
desc: "AI-powered route optimization reduces travel time, improves delivery accuracy, and maximizes fleet utilization.",
|
||||
desc: "AI-powered route optimization reduces travel time, improves routing accuracy, and maximizes fleet utilization.",
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
|
||||
@@ -49,7 +49,7 @@ const CARDS_DATA = [
|
||||
{
|
||||
index: 4,
|
||||
title: "Scalable Network",
|
||||
desc: "Flexible logistics infrastructure supports growth across cities, regions, and high-volume delivery operations without disruption.",
|
||||
desc: "Flexible logistics infrastructure supports growth across cities, regions, and high-volume logistics operations without disruption.",
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 3.5 2 5.5a7 7 0 0 1-7 7h-3" />
|
||||
|
||||
@@ -135,7 +135,7 @@ const DEFAULT_FEATURES: EVFeature[] = [
|
||||
</svg>
|
||||
),
|
||||
title: "Charging Integration",
|
||||
desc: "Seamlessly integrate charging stops without compromising delivery windows or SLA commitments.",
|
||||
desc: "Seamlessly integrate charging stops without compromising arrival windows or SLA commitments.",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
@@ -352,7 +352,7 @@ export default function EVSection({
|
||||
titleAccent = "NOT ADAPTED.",
|
||||
features = DEFAULT_FEATURES,
|
||||
image = "/images/premium-ev-van.webp",
|
||||
imageAlt = "DoorMile electric delivery van",
|
||||
imageAlt = "DoorMile electric fleet van",
|
||||
badges = DEFAULT_BADGES,
|
||||
stats = DEFAULT_STATS,
|
||||
gapTop = false,
|
||||
|
||||
@@ -301,8 +301,8 @@ export default function HowItWorksHero() {
|
||||
<div className="text-content">
|
||||
<p>
|
||||
See how Doormile connects first, mid, and
|
||||
last mile into a seamless delivery
|
||||
experience powered by MileTruth™ AI.
|
||||
last mile into a seamless end-to-end
|
||||
logistics experience powered by MileTruth™ AI.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -341,8 +341,8 @@ export default function HowItWorksHero() {
|
||||
<div className="text-content">
|
||||
<p>
|
||||
See how Doormile connects first, mid, and
|
||||
last mile into a seamless delivery
|
||||
experience powered by MileTruth™ AI.
|
||||
last mile into a seamless end-to-end
|
||||
logistics experience powered by MileTruth™ AI.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -197,7 +197,7 @@ export default function IndexHero() {
|
||||
</h1>
|
||||
<div className="content-slider-item-text logico-content-wrapper-2" style={{ display: "flex", justifyContent: "center", width: "100%", marginTop: "23px" }}>
|
||||
<div className="text-content" style={{ textAlign: "center", maxWidth: "680px", margin: "0 auto" }}>
|
||||
<p>Stop managing three separate logistics services. Doormile unifies first, mid and last mile into a single intelligent delivery system powered by MileTruth™ AI.</p>
|
||||
<p>Stop managing three separate logistics services. Doormile unifies first, mid and last mile into a single intelligent logistics system powered by MileTruth™ AI.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -189,7 +189,7 @@ export default function IndustrySolutions() {
|
||||
<h5 className="industry-card-section-title">Doormile Solutions</h5>
|
||||
<ul className="industry-card-list">
|
||||
<li className="industry-card-list-item bullet-4">AI-driven demand-responsive routing</li>
|
||||
<li className="industry-card-list-item bullet-5">Freshness-aware delivery prioritization</li>
|
||||
<li className="industry-card-list-item bullet-5">Freshness-aware shipment prioritization</li>
|
||||
<li className="industry-card-list-item bullet-6">Dynamic batch optimization</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -246,7 +246,7 @@ export default function IndustrySolutions() {
|
||||
<ul className="industry-card-list">
|
||||
<li className="industry-card-list-item bullet-1">Cold chain integrity requirements</li>
|
||||
<li className="industry-card-list-item bullet-2">Regulatory compliance tracking</li>
|
||||
<li className="industry-card-list-item bullet-3">Critical delivery time windows</li>
|
||||
<li className="industry-card-list-item bullet-3">Critical shipment time windows</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
@@ -303,13 +303,13 @@ export default function IndustrySolutions() {
|
||||
<div className="industry-card-hover-content">
|
||||
<div className="industry-card-hover-top">
|
||||
<p className="industry-card-description">
|
||||
High-value shipments with complex delivery requirements.
|
||||
High-value shipments with complex logistics requirements.
|
||||
</p>
|
||||
<div>
|
||||
<h5 className="industry-card-section-title">Challenges</h5>
|
||||
<ul className="industry-card-list">
|
||||
<li className="industry-card-list-item bullet-1">Appointment scheduling coordination</li>
|
||||
<li className="industry-card-list-item bullet-2">White-glove delivery standards</li>
|
||||
<li className="industry-card-list-item bullet-2">White-glove logistics standards</li>
|
||||
<li className="industry-card-list-item bullet-3">Multi-location routing complexity</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -22,15 +22,15 @@ const SECTIONS: Section[] = [
|
||||
image: "/images/tab-pic-1-solution.webp",
|
||||
alt: "FMCG logistics",
|
||||
desc:
|
||||
"FMCG logistics demands speed, precision, and continuous fulfillment across high-volume delivery networks. Businesses must balance tight delivery timelines, inventory movement, and operational efficiency without compromising product availability.",
|
||||
"FMCG logistics demands speed, precision, and continuous fulfillment across high-volume logistics networks. Businesses must balance tight shipment timelines, inventory movement, and operational efficiency without compromising product availability.",
|
||||
ch: [
|
||||
"Unpredictable demand spikes create delivery pressure and reduce operational efficiency during peak periods.",
|
||||
"Fresh product expiry constraints require faster, precisely timed deliveries to maintain product quality.",
|
||||
"Multi-stop route complexity increases travel time, operational costs, and delivery coordination challenges.",
|
||||
"Inventory stockout risks increase when delivery delays disrupt fast-moving product distribution.",
|
||||
"Unpredictable demand spikes create logistics pressure and reduce operational efficiency during peak periods.",
|
||||
"Fresh product expiry constraints require faster, precisely timed shipments to maintain product quality.",
|
||||
"Multi-stop route complexity increases travel time, operational costs, and logistics coordination challenges.",
|
||||
"Inventory stockout risks increase when shipment delays disrupt fast-moving product distribution.",
|
||||
],
|
||||
so: [
|
||||
"AI demand forecasting adapts delivery plans instantly to real-time order demand.",
|
||||
"AI demand forecasting adapts logistics plans instantly to real-time order demand.",
|
||||
"Expiry-aware routing prioritises perishable goods for on-time freshness.",
|
||||
"Smart multi-stop optimisation groups orders to cut cost and travel time.",
|
||||
"Real-time inventory sync prevents stockouts and improves fulfilment accuracy.",
|
||||
@@ -42,17 +42,17 @@ const SECTIONS: Section[] = [
|
||||
image: "/images/tab-pic-2-solution.webp",
|
||||
alt: "Pharma logistics",
|
||||
desc:
|
||||
"Pharma logistics requires precision, compliance, and real-time monitoring so every shipment arrives safely and on time — from temperature-sensitive medicines to urgent emergency deliveries.",
|
||||
"Pharma logistics requires precision, compliance, and real-time monitoring so every shipment arrives safely and on time — from temperature-sensitive medicines to urgent emergency shipments.",
|
||||
ch: [
|
||||
"Cold chain integrity demands precise temperature control throughout transit.",
|
||||
"Regulatory compliance must be tracked and documented on every delivery.",
|
||||
"Critical delivery time windows require highly accurate scheduling.",
|
||||
"Regulatory compliance must be tracked and documented on every shipment.",
|
||||
"Critical arrival windows require highly accurate scheduling.",
|
||||
"Emergency shipments need instant dispatch and zero-delay execution.",
|
||||
],
|
||||
so: [
|
||||
"Cold chain monitoring with automatic re-routing keeps shipments in-spec.",
|
||||
"Compliance engine with audit trails ensures full chain-of-custody visibility.",
|
||||
"Precision scheduling locks in critical delivery windows reliably.",
|
||||
"Precision scheduling locks in critical arrival windows reliably.",
|
||||
"Priority dispatch queue fast-tracks urgent, life-critical shipments.",
|
||||
],
|
||||
},
|
||||
@@ -65,14 +65,14 @@ const SECTIONS: Section[] = [
|
||||
"Enterprise and B2B logistics require coordination and reliability to manage high-value shipments at scale — with appointment scheduling, white-glove standards, and strict SLA commitments.",
|
||||
ch: [
|
||||
"Appointment scheduling requires precise timing across many locations.",
|
||||
"White-glove delivery standards demand premium handling and accuracy.",
|
||||
"White-glove logistics standards demand premium handling and accuracy.",
|
||||
"Multi-location routing complexity grows with large-scale operations.",
|
||||
"Strict SLA commitments pressure teams to stay timely and error-free.",
|
||||
],
|
||||
so: [
|
||||
"Intelligent appointment engine streamlines and automates delivery slots.",
|
||||
"Intelligent appointment engine streamlines and automates arrival slots.",
|
||||
"White-glove workflow module enforces premium handling end to end.",
|
||||
"Enterprise route planner coordinates efficient multi-location delivery.",
|
||||
"Enterprise route planner coordinates efficient multi-location logistics.",
|
||||
"SLA monitoring dashboard tracks commitments and flags risk in real time.",
|
||||
],
|
||||
},
|
||||
|
||||
164
src/components/sections/MileTruthCanvas.tsx
Normal file
164
src/components/sections/MileTruthCanvas.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo, useRef } from "react";
|
||||
import { Canvas, useFrame } from "@react-three/fiber";
|
||||
import { Sparkles } from "@react-three/drei";
|
||||
import { EffectComposer, Bloom } from "@react-three/postprocessing";
|
||||
import { KernelSize } from "postprocessing";
|
||||
import * as THREE from "three";
|
||||
|
||||
/* ============================================================
|
||||
MileTruthCanvas — small, self-contained R3F scene for the
|
||||
"MileTruth AI" section: a 4-node data → AI → recommendations →
|
||||
improvement network with glowing cores and travelling light
|
||||
packets along each connector. Deliberately lightweight (a
|
||||
handful of meshes + a low-count sparkle field) — this and the
|
||||
Quantum section are the ONLY two places on the Solutions page
|
||||
that touch Three.js.
|
||||
============================================================ */
|
||||
|
||||
const RED = "#C01227";
|
||||
const RED_BRIGHT = "#E2354A";
|
||||
const STEEL = "#8b8b96";
|
||||
const WHITE = "#f4f4f6";
|
||||
|
||||
type Node = { pos: [number, number, number]; color: string; label: string; scale: number };
|
||||
|
||||
const NODES: Node[] = [
|
||||
{ pos: [-4.6, 0.3, 0], color: STEEL, label: "data", scale: 0.34 },
|
||||
{ pos: [-1.6, 0.75, 0.4], color: RED, label: "ai", scale: 0.46 },
|
||||
{ pos: [1.5, 0.25, -0.35], color: RED_BRIGHT, label: "recs", scale: 0.38 },
|
||||
{ pos: [4.5, -0.15, 0.15], color: WHITE, label: "improve", scale: 0.34 },
|
||||
];
|
||||
|
||||
const LINKS: [number, number][] = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[2, 3],
|
||||
];
|
||||
|
||||
function NodeMesh({ node, reduced, seed }: { node: Node; reduced: boolean; seed: number }) {
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const haloRef = useRef<THREE.Mesh>(null);
|
||||
|
||||
useFrame((state) => {
|
||||
const t = state.clock.elapsedTime;
|
||||
const pulse = reduced ? 1 : 1 + Math.sin(t * 1.6 + seed) * 0.08;
|
||||
if (meshRef.current) meshRef.current.scale.setScalar(node.scale * pulse);
|
||||
if (haloRef.current) {
|
||||
const haloPulse = reduced ? 1.4 : 1.4 + Math.sin(t * 1.6 + seed) * 0.18;
|
||||
haloRef.current.scale.setScalar(node.scale * haloPulse);
|
||||
const mat = haloRef.current.material as THREE.MeshBasicMaterial;
|
||||
mat.opacity = reduced ? 0.16 : 0.14 + Math.sin(t * 1.6 + seed) * 0.06;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<group position={node.pos}>
|
||||
<mesh ref={haloRef}>
|
||||
<sphereGeometry args={[1, 20, 20]} />
|
||||
<meshBasicMaterial color={node.color} transparent opacity={0.16} depthWrite={false} />
|
||||
</mesh>
|
||||
<mesh ref={meshRef}>
|
||||
<sphereGeometry args={[1, 28, 28]} />
|
||||
<meshStandardMaterial
|
||||
color={node.color}
|
||||
emissive={node.color}
|
||||
emissiveIntensity={1.4}
|
||||
roughness={0.35}
|
||||
metalness={0.1}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function Connector({ a, b, reduced, index }: { a: Node; b: Node; reduced: boolean; index: number }) {
|
||||
const packetRef = useRef<THREE.Mesh>(null);
|
||||
const lineMatRef = useRef<THREE.LineBasicMaterial>(null);
|
||||
const start = useMemo(() => new THREE.Vector3(...a.pos), [a]);
|
||||
const end = useMemo(() => new THREE.Vector3(...b.pos), [b]);
|
||||
|
||||
const geo = useMemo(() => {
|
||||
const g = new THREE.BufferGeometry();
|
||||
g.setAttribute("position", new THREE.BufferAttribute(new Float32Array([...a.pos, ...b.pos]), 3));
|
||||
return g;
|
||||
}, [a, b]);
|
||||
|
||||
useFrame((state) => {
|
||||
const t = state.clock.elapsedTime;
|
||||
if (lineMatRef.current) {
|
||||
lineMatRef.current.opacity = reduced ? 0.3 : 0.2 + Math.sin(t * 1.2 + index) * 0.12;
|
||||
}
|
||||
if (packetRef.current) {
|
||||
if (reduced) {
|
||||
packetRef.current.visible = false;
|
||||
return;
|
||||
}
|
||||
packetRef.current.visible = true;
|
||||
const speed = 0.28;
|
||||
const u = (t * speed + index * 0.33) % 1;
|
||||
packetRef.current.position.lerpVectors(start, end, u);
|
||||
const mat = packetRef.current.material as THREE.MeshBasicMaterial;
|
||||
mat.opacity = Math.sin(u * Math.PI);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<group>
|
||||
<lineSegments geometry={geo}>
|
||||
<lineBasicMaterial ref={lineMatRef} color={RED_BRIGHT} transparent opacity={0.25} depthWrite={false} />
|
||||
</lineSegments>
|
||||
<mesh ref={packetRef}>
|
||||
<sphereGeometry args={[0.09, 12, 12]} />
|
||||
<meshBasicMaterial color="#ffffff" transparent opacity={0.9} depthWrite={false} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function SlowSpin({ reduced, children }: { reduced: boolean; children: React.ReactNode }) {
|
||||
const group = useRef<THREE.Group>(null);
|
||||
useFrame((_state, dt) => {
|
||||
if (reduced || !group.current) return;
|
||||
group.current.rotation.y += dt * 0.035;
|
||||
});
|
||||
return <group ref={group}>{children}</group>;
|
||||
}
|
||||
|
||||
export default function MileTruthCanvas({
|
||||
reduced = false,
|
||||
isMobile = false,
|
||||
active = true,
|
||||
}: {
|
||||
reduced?: boolean;
|
||||
isMobile?: boolean;
|
||||
active?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Canvas
|
||||
flat
|
||||
frameloop={active ? "always" : "never"}
|
||||
dpr={[1, isMobile || reduced ? 1.2 : 1.6]}
|
||||
camera={{ position: [0, 1.4, 9.2], fov: 42, near: 0.1, far: 60 }}
|
||||
gl={{ antialias: !isMobile, powerPreference: "high-performance", alpha: false }}
|
||||
>
|
||||
<color attach="background" args={["#0c0c10"]} />
|
||||
<ambientLight intensity={0.35} />
|
||||
<SlowSpin reduced={reduced}>
|
||||
{LINKS.map(([a, b], i) => (
|
||||
<Connector key={i} a={NODES[a]} b={NODES[b]} reduced={reduced} index={i} />
|
||||
))}
|
||||
{NODES.map((n, i) => (
|
||||
<NodeMesh key={n.label} node={n} reduced={reduced} seed={i * 1.75} />
|
||||
))}
|
||||
</SlowSpin>
|
||||
{!isMobile && (
|
||||
<Sparkles count={70} scale={[11, 4.5, 6]} size={1.6} speed={reduced ? 0 : 0.15} color={RED_BRIGHT} opacity={0.3} />
|
||||
)}
|
||||
<EffectComposer enabled={!isMobile} multisampling={isMobile ? 0 : 2}>
|
||||
<Bloom kernelSize={KernelSize.SMALL} luminanceThreshold={0.2} intensity={0.55} mipmapBlur />
|
||||
</EffectComposer>
|
||||
</Canvas>
|
||||
);
|
||||
}
|
||||
@@ -260,9 +260,6 @@ export default function MileTruthHero() {
|
||||
}
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.miletruth-hero .elementor-element-86f3204 {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
.miletruth-hero-container {
|
||||
min-height: 600px;
|
||||
padding: 80px 0 !important;
|
||||
@@ -290,6 +287,15 @@ export default function MileTruthHero() {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
/* Horizontal inset switches to 10px here (not at 1024px) to match the
|
||||
breakpoint every other MileTruth section (Intro, Workflow1/2/4) uses
|
||||
for the same 20px -> 10px step, so the Hero's left/right edges stay
|
||||
aligned with the rest of the page through the whole 768-1024px range. */
|
||||
@media (max-width: 767px) {
|
||||
.miletruth-hero .elementor-element-86f3204 {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 580px) {
|
||||
.miletruth-hero-container {
|
||||
min-height: 500px;
|
||||
@@ -358,7 +364,7 @@ export default function MileTruthHero() {
|
||||
</h1>
|
||||
<div className="content-slider-item-text logico-content-wrapper-2">
|
||||
<div className="text-content">
|
||||
<p>Optimizes every stage of the delivery journey for maximum efficiency.</p>
|
||||
<p>Optimizes every stage of the logistics journey for maximum efficiency.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
413
src/components/sections/MileTruthIntro.tsx
Normal file
413
src/components/sections/MileTruthIntro.tsx
Normal file
@@ -0,0 +1,413 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import gsap from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
}
|
||||
|
||||
/* A small route graph: three loose columns of candidate nodes converging on
|
||||
* one destination. Coordinates are hand-placed (not a strict grid) so the
|
||||
* mesh reads as organic route options rather than a wiring diagram. */
|
||||
const NODES = [
|
||||
{ x: 40, y: 50 },
|
||||
{ x: 40, y: 130 },
|
||||
{ x: 40, y: 210 },
|
||||
{ x: 150, y: 30 },
|
||||
{ x: 150, y: 95 },
|
||||
{ x: 150, y: 160 },
|
||||
{ x: 150, y: 225 },
|
||||
{ x: 270, y: 55 },
|
||||
{ x: 270, y: 130 },
|
||||
{ x: 270, y: 200 },
|
||||
{ x: 400, y: 130 },
|
||||
] as const;
|
||||
|
||||
const FINAL_NODE = 10;
|
||||
const DIM_NODES = [0, 2, 3, 5, 6, 7, 9];
|
||||
|
||||
const ALL_EDGES: [number, number][] = [
|
||||
[0, 3], [0, 4], [1, 4], [1, 5], [2, 5], [2, 6],
|
||||
[3, 7], [4, 7], [4, 8], [5, 8], [5, 9], [6, 9],
|
||||
[7, 10], [8, 10], [9, 10],
|
||||
];
|
||||
|
||||
const SURVIVOR_EDGES: [number, number][] = [
|
||||
[1, 4],
|
||||
[4, 8],
|
||||
[8, 10],
|
||||
];
|
||||
|
||||
const edgeLength = (a: number, b: number) =>
|
||||
Math.hypot(NODES[b].x - NODES[a].x, NODES[b].y - NODES[a].y);
|
||||
|
||||
/** Conceptual preview of Workflow1's route optimization: many candidate
|
||||
* routes converge into the single plan MileTruth keeps. Plays once on
|
||||
* scroll-in — grey mesh appears, a soft sweep suggests evaluation, unused
|
||||
* routes fade, the surviving path draws in red, the destination pulses
|
||||
* once — then stops. No loop, no labels, respects prefers-reduced-motion. */
|
||||
function RouteConverge() {
|
||||
const wrapRef = useRef<HTMLDivElement>(null);
|
||||
const sweepRef = useRef<HTMLDivElement>(null);
|
||||
const edgeRefs = useRef<(SVGLineElement | null)[]>([]);
|
||||
const survivorEdgeRefs = useRef<(SVGLineElement | null)[]>([]);
|
||||
const nodeRefs = useRef<(SVGCircleElement | null)[]>([]);
|
||||
const haloRef = useRef<SVGCircleElement>(null);
|
||||
const pulseRef = useRef<SVGCircleElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const wrap = wrapRef.current;
|
||||
const sweep = sweepRef.current;
|
||||
const halo = haloRef.current;
|
||||
const pulse = pulseRef.current;
|
||||
const edges = edgeRefs.current.filter(Boolean) as SVGLineElement[];
|
||||
const survivorEdges = survivorEdgeRefs.current.filter(Boolean) as SVGLineElement[];
|
||||
const nodes = nodeRefs.current.filter(Boolean) as SVGCircleElement[];
|
||||
if (!wrap || !sweep || !halo || !pulse || edges.length === 0 || nodes.length === 0) return;
|
||||
|
||||
const dimNodes = DIM_NODES.map((i) => nodeRefs.current[i]).filter(Boolean) as SVGCircleElement[];
|
||||
const survivorNodes = [1, 4, 8].map((i) => nodeRefs.current[i]).filter(Boolean) as SVGCircleElement[];
|
||||
const finalNode = nodeRefs.current[FINAL_NODE] as SVGCircleElement;
|
||||
|
||||
gsap.set(edges, { opacity: 0 });
|
||||
gsap.set(nodes, { opacity: 0, attr: { fill: "#fdfdfd", stroke: "rgba(17, 17, 17, 0.3)" } });
|
||||
survivorEdges.forEach((el, i) => {
|
||||
const [a, b] = SURVIVOR_EDGES[i];
|
||||
const len = edgeLength(a, b);
|
||||
gsap.set(el, { attr: { "stroke-dasharray": len, "stroke-dashoffset": len } });
|
||||
});
|
||||
gsap.set(halo, { opacity: 0 });
|
||||
gsap.set(pulse, { opacity: 0, attr: { r: 5 } });
|
||||
gsap.set(sweep, { opacity: 0, left: "-30%" });
|
||||
|
||||
const showFinalState = () => {
|
||||
gsap.set(edges, { opacity: 0.08 });
|
||||
gsap.set(dimNodes, { opacity: 0.3 });
|
||||
gsap.set(nodes, { opacity: 1 });
|
||||
gsap.set([...survivorNodes, finalNode], { attr: { fill: "#C01227", stroke: "#C01227" } });
|
||||
survivorEdges.forEach((el) => gsap.set(el, { attr: { "stroke-dashoffset": 0 } }));
|
||||
gsap.set(halo, { opacity: 0.22 });
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
||||
showFinalState();
|
||||
return;
|
||||
}
|
||||
|
||||
let tl: gsap.core.Timeline | undefined;
|
||||
|
||||
const play = () => {
|
||||
tl = gsap.timeline();
|
||||
|
||||
tl.to(edges, { opacity: 1, duration: 0.5, ease: "power1.out" }, 0)
|
||||
.to(nodes, { opacity: 1, duration: 0.5, ease: "power1.out" }, 0.05)
|
||||
.set(sweep, { opacity: 0.9 }, 0.55)
|
||||
.to(sweep, { left: "104%", duration: 1, ease: "power1.inOut" }, 0.55)
|
||||
.to(sweep, { opacity: 0, duration: 0.3 }, 1.35)
|
||||
.to(dimNodes, { opacity: 0.28, duration: 0.6, ease: "power1.out" }, 1.3)
|
||||
.to(edges, { opacity: 0.08, duration: 0.6, ease: "power1.out" }, 1.3)
|
||||
.to(survivorNodes, { attr: { fill: "#C01227", stroke: "#C01227" }, duration: 0.35, ease: "power1.out" }, 1.5)
|
||||
.to(survivorEdges[0], { attr: { "stroke-dashoffset": 0 }, duration: 0.4, ease: "power2.inOut" }, 1.55)
|
||||
.to(survivorEdges[1], { attr: { "stroke-dashoffset": 0 }, duration: 0.4, ease: "power2.inOut" }, 1.85)
|
||||
.to(survivorEdges[2], { attr: { "stroke-dashoffset": 0 }, duration: 0.4, ease: "power2.inOut" }, 2.15)
|
||||
.to(finalNode, { attr: { fill: "#C01227", stroke: "#C01227" }, duration: 0.3, ease: "power1.out" }, 2.5)
|
||||
.to(halo, { opacity: 0.22, duration: 0.4, ease: "power1.out" }, 2.55)
|
||||
.fromTo(
|
||||
pulse,
|
||||
{ opacity: 0.7, attr: { r: 5 } },
|
||||
{ opacity: 0, attr: { r: 22 }, duration: 0.7, ease: "power2.out" },
|
||||
2.55,
|
||||
);
|
||||
};
|
||||
|
||||
const st = ScrollTrigger.create({
|
||||
trigger: wrap,
|
||||
start: "top 82%",
|
||||
once: true,
|
||||
onEnter: play,
|
||||
});
|
||||
|
||||
const raf = requestAnimationFrame(() => {
|
||||
const r = wrap.getBoundingClientRect();
|
||||
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
if (r.top < vh && r.bottom > 0) {
|
||||
play();
|
||||
st.kill();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(raf);
|
||||
st.kill();
|
||||
tl?.kill();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mti__viz" ref={wrapRef} aria-hidden="true">
|
||||
<div className="mti__viz-frame">
|
||||
<div className="mti__viz-sweep" ref={sweepRef} />
|
||||
<svg className="mti__viz-svg" viewBox="0 0 440 260" preserveAspectRatio="xMidYMid meet">
|
||||
<g>
|
||||
{ALL_EDGES.map(([a, b], i) => (
|
||||
<line
|
||||
key={i}
|
||||
ref={(el) => {
|
||||
edgeRefs.current[i] = el;
|
||||
}}
|
||||
x1={NODES[a].x}
|
||||
y1={NODES[a].y}
|
||||
x2={NODES[b].x}
|
||||
y2={NODES[b].y}
|
||||
className="mti__viz-edge"
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
<g>
|
||||
{SURVIVOR_EDGES.map(([a, b], i) => (
|
||||
<line
|
||||
key={i}
|
||||
ref={(el) => {
|
||||
survivorEdgeRefs.current[i] = el;
|
||||
}}
|
||||
x1={NODES[a].x}
|
||||
y1={NODES[a].y}
|
||||
x2={NODES[b].x}
|
||||
y2={NODES[b].y}
|
||||
className="mti__viz-edge-active"
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
<circle ref={haloRef} cx={NODES[FINAL_NODE].x} cy={NODES[FINAL_NODE].y} r={11} className="mti__viz-halo" />
|
||||
<g>
|
||||
{NODES.map((n, i) => (
|
||||
<circle
|
||||
key={i}
|
||||
ref={(el) => {
|
||||
nodeRefs.current[i] = el;
|
||||
}}
|
||||
cx={n.x}
|
||||
cy={n.y}
|
||||
r={i === FINAL_NODE ? 4.5 : 3.2}
|
||||
className={i === FINAL_NODE ? "mti__viz-node mti__viz-node--final" : "mti__viz-node"}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
<circle ref={pulseRef} cx={NODES[FINAL_NODE].x} cy={NODES[FINAL_NODE].y} r={5} className="mti__viz-pulse" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Short editorial bridge between the MileTruth hero/metrics and Workflow1 —
|
||||
* a quick read, not a second hero. No motion, no media, no CTA.
|
||||
*
|
||||
* Spacing mirrors the workflow shells (EVSection): a 20px outer inset
|
||||
* (width calc(100% - 40px) / margin 0 20px) and a 1280px inner with 48px
|
||||
* horizontal padding, so the heading/body left-align with the workflow
|
||||
* cards above and below instead of using an unrelated flat gutter. The
|
||||
* shell also carries the same external top margin as EVSection's
|
||||
* `.evnd-gap-top` (40 / 32 / 24 desktop-tablet-mobile) so the gap above
|
||||
* this section matches the gap above every workflow section below it —
|
||||
* otherwise the Hero sits flush against this section while every other
|
||||
* section boundary on the page carries that gap.
|
||||
*
|
||||
* The right side carries a minimal "many routes -> one route" visualization
|
||||
* (see RouteConverge above) instead of sitting empty — it never touches the
|
||||
* heading/copy, spacing, or section height on the left. */
|
||||
export default function MileTruthIntro() {
|
||||
return (
|
||||
<>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
.mti-shell {
|
||||
width: calc(100% - 40px);
|
||||
margin: 40px 20px 0;
|
||||
}
|
||||
.mti {
|
||||
background: #fafafa;
|
||||
padding: clamp(28px, 3.5vw, 40px) 0;
|
||||
}
|
||||
.mti__inner {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 48px;
|
||||
}
|
||||
.mti__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: clamp(32px, 5vw, 64px);
|
||||
}
|
||||
.mti__left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.mti__eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: #C01227 !important;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.16em;
|
||||
font-size: 13px;
|
||||
margin-bottom: clamp(14px, 1.8vw, 18px);
|
||||
}
|
||||
.mti__eyebrow::before {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background: #C01227;
|
||||
}
|
||||
.mti__divider {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background: rgba(17, 17, 17, 0.12);
|
||||
margin: 0 0 clamp(20px, 2.4vw, 28px);
|
||||
}
|
||||
.mti__heading {
|
||||
margin: 0 0 clamp(18px, 2vw, 24px);
|
||||
color: #111111 !important;
|
||||
font-weight: 800 !important;
|
||||
font-size: clamp(28px, 3.4vw, 46px) !important;
|
||||
line-height: 1.15 !important;
|
||||
letter-spacing: -0.02em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.mti__accent {
|
||||
color: #C01227 !important;
|
||||
}
|
||||
.mti__copy {
|
||||
max-width: 680px;
|
||||
}
|
||||
.mti__copy p {
|
||||
margin: 0 0 14px;
|
||||
color: rgba(17, 17, 17, 0.68) !important;
|
||||
font-size: clamp(15px, 1.05vw, 17px) !important;
|
||||
font-weight: 500 !important;
|
||||
line-height: 1.6 !important;
|
||||
}
|
||||
.mti__copy p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.mti__lede {
|
||||
margin-left: clamp(16px, 2vw, 24px);
|
||||
}
|
||||
.mti__viz {
|
||||
flex: 0 1 38%;
|
||||
min-width: 240px;
|
||||
max-width: 460px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.mti__viz-frame {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.mti__viz-svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
.mti__viz-edge {
|
||||
stroke: rgba(17, 17, 17, 0.16);
|
||||
stroke-width: 1;
|
||||
}
|
||||
.mti__viz-edge-active {
|
||||
fill: none;
|
||||
stroke: #C01227;
|
||||
stroke-width: 1.4;
|
||||
}
|
||||
.mti__viz-node {
|
||||
fill: #fdfdfd;
|
||||
stroke: rgba(17, 17, 17, 0.3);
|
||||
stroke-width: 1.1;
|
||||
}
|
||||
.mti__viz-node--final {
|
||||
stroke-width: 1.3;
|
||||
}
|
||||
.mti__viz-halo {
|
||||
fill: #C01227;
|
||||
opacity: 0;
|
||||
filter: blur(6px);
|
||||
}
|
||||
.mti__viz-pulse {
|
||||
fill: none;
|
||||
stroke: #C01227;
|
||||
stroke-width: 1.4;
|
||||
opacity: 0;
|
||||
}
|
||||
.mti__viz-sweep {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 24%;
|
||||
background: linear-gradient(90deg, transparent, rgba(192, 18, 39, 0.14), transparent);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.mti-shell { margin-top: 32px; }
|
||||
.mti__inner { padding: 0 32px; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.mti__row {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: clamp(28px, 5vw, 40px);
|
||||
}
|
||||
.mti__left {
|
||||
width: 100%;
|
||||
}
|
||||
.mti__viz {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.mti-shell {
|
||||
width: calc(100% - 20px);
|
||||
margin: 24px 10px 0;
|
||||
}
|
||||
.mti__inner { padding: 0 20px; }
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<div className="mti-shell">
|
||||
<section className="mti" aria-label="MileTruth AI introduction">
|
||||
<div className="mti__inner">
|
||||
<div className="mti__row">
|
||||
<div className="mti__left">
|
||||
<span className="mti__eyebrow">/ MILETRUTH AI /</span>
|
||||
<hr className="mti__divider" />
|
||||
<h2 className="mti__heading">
|
||||
ROUTING IS A <span className="mti__accent">DECISION PROBLEM.</span>
|
||||
</h2>
|
||||
<div className="mti__copy">
|
||||
<p className="mti__lede">Routing is not one path between two points.</p>
|
||||
<p>
|
||||
Orders change. Vehicles have different limits. Capacity, range
|
||||
and time change what is possible.
|
||||
</p>
|
||||
<p>
|
||||
MileTruth evaluates the options, checks what can actually work,
|
||||
and moves the strongest plan forward.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<RouteConverge />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
69
src/components/sections/MileTruthNetwork.tsx
Normal file
69
src/components/sections/MileTruthNetwork.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
/* ============================================================
|
||||
MileTruthNetwork — gated mount for MileTruthCanvas (the lightweight
|
||||
R3F data -> AI -> recommendations -> improvement scene). Mirrors
|
||||
WorkflowScene.tsx: code-split, mounts a little before it scrolls
|
||||
into view, pauses the render loop off-screen, and respects
|
||||
prefers-reduced-motion.
|
||||
============================================================ */
|
||||
|
||||
const MileTruthCanvas = dynamic(() => import("./MileTruthCanvas"), { ssr: false });
|
||||
|
||||
export default function MileTruthNetwork({ ariaLabel }: { ariaLabel?: string }) {
|
||||
const wrapRef = useRef<HTMLDivElement>(null);
|
||||
const [mountScene, setMountScene] = useState(false);
|
||||
const [active, setActive] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [reduced, setReduced] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const mqMobile = window.matchMedia("(max-width: 767px)");
|
||||
const mqReduce = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
const sync = () => {
|
||||
setIsMobile(mqMobile.matches);
|
||||
setReduced(mqReduce.matches);
|
||||
};
|
||||
sync();
|
||||
mqMobile.addEventListener("change", sync);
|
||||
mqReduce.addEventListener("change", sync);
|
||||
return () => {
|
||||
mqMobile.removeEventListener("change", sync);
|
||||
mqReduce.removeEventListener("change", sync);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Single observer: mount the scene a little before it scrolls into view and
|
||||
// keep it active from then on (this scene is small/light enough that a
|
||||
// second "pause when off-screen" observer isn't worth the added race risk
|
||||
// between two independent IntersectionObservers on the same element).
|
||||
useEffect(() => {
|
||||
const el = wrapRef.current;
|
||||
if (!el) return;
|
||||
const mountIo = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries.some((e) => e.isIntersecting)) {
|
||||
setMountScene(true);
|
||||
setActive(true);
|
||||
mountIo.disconnect();
|
||||
}
|
||||
},
|
||||
{ rootMargin: "50% 0px" },
|
||||
);
|
||||
mountIo.observe(el);
|
||||
return () => mountIo.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={wrapRef} className="mtn-scene" role="img" aria-label={ariaLabel ?? "MileTruth AI network"}>
|
||||
<style dangerouslySetInnerHTML={{ __html: `
|
||||
.mtn-scene { position: relative; width: 100%; height: 100%; min-height: 320px; }
|
||||
.mtn-scene canvas { display: block; width: 100% !important; height: 100% !important; }
|
||||
`}} />
|
||||
{mountScene && <MileTruthCanvas reduced={reduced} isMobile={isMobile} active={active} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ export default function Miles3() {
|
||||
</div>
|
||||
<div className="elementor-element elementor-element-63a9de5 elementor-widget elementor-widget-logico_heading" data-id="63a9de5" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default">
|
||||
<div className="elementor-widget-container">
|
||||
<h3 className="logico-title">Doormile connects first, mid, and last mile into a seamless delivery experience</h3>
|
||||
<h3 className="logico-title">Doormile connects first, mid, and last mile into a seamless end-to-end logistics experience</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -243,7 +243,7 @@ export default function Miles3() {
|
||||
</div>
|
||||
<div className="elementor-element elementor-element-3ae1ce0 elementor-widget elementor-widget-text-editor" data-id="3ae1ce0" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
|
||||
<div className="elementor-widget-container">
|
||||
<p>Smooth inter-city delivery through strategically located hubs with live tracking.</p>
|
||||
<p>Smooth inter-city logistics through strategically located hubs with live tracking.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -266,7 +266,7 @@ export default function Miles3() {
|
||||
</div>
|
||||
<div className="elementor-element elementor-element-1057c22 elementor-widget elementor-widget-text-editor" data-id="1057c22" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
|
||||
<div className="elementor-widget-container">
|
||||
<p>Packages are delivered to customers with real-time updates and proof of delivery.</p>
|
||||
<p>Packages reach customers with real-time updates and proof of delivery.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
164
src/components/sections/QuantumStage.tsx
Normal file
164
src/components/sections/QuantumStage.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import gsap from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
import WorkflowScene from "./WorkflowScene";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
QuantumStage — the page's ONE true 3D moment. Wraps the existing
|
||||
production "Logistics Brain" scene (glowing core, orbiting particle
|
||||
shells, six parallel route strategies racing, live network) —
|
||||
already built and already on-brand red — inside a near-full-viewport
|
||||
frame, with a floating Traditional-vs-Quantum comparison overlaid.
|
||||
GSAP drives only the entrance (fade/rise); the 3D scene animates
|
||||
itself.
|
||||
============================================================ */
|
||||
export default function QuantumStage() {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const root = rootRef.current;
|
||||
if (!root) return;
|
||||
const frame = root.querySelector(".qs-frame");
|
||||
const tradCol = root.querySelector(".qs-col--trad");
|
||||
const qSteps = Array.from(root.querySelectorAll(".qs-qstep"));
|
||||
const qDot = root.querySelector(".qs-qdot");
|
||||
if (!frame || !tradCol || qSteps.length === 0) return;
|
||||
|
||||
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
||||
gsap.set([frame, tradCol, ...qSteps], { opacity: 1, y: 0, scale: 1 });
|
||||
return;
|
||||
}
|
||||
gsap.set(frame, { opacity: 0, y: 26 });
|
||||
gsap.set(tradCol, { opacity: 0, y: 26 });
|
||||
gsap.set(qSteps, { opacity: 0.25, y: 10 });
|
||||
gsap.set(qDot, { scale: 0.6, opacity: 0 });
|
||||
|
||||
const play = () => {
|
||||
const tl = gsap.timeline();
|
||||
// Frame + traditional baseline settle in first (the "boring" case).
|
||||
tl.to(frame, { opacity: 1, y: 0, duration: 0.9, ease: "power3.out" }, 0)
|
||||
.to(tradCol, { opacity: 1, y: 0, duration: 0.7, ease: "power3.out" }, 0.15);
|
||||
// Quantum steps light up one at a time — a process unfolding, not a fade group.
|
||||
qSteps.forEach((el, i) => {
|
||||
tl.to(el, { opacity: 1, y: 0, scale: 1.04, duration: 0.32, ease: "power2.out" }, 0.7 + i * 0.42)
|
||||
.to(el, { scale: 1, duration: 0.22, ease: "power1.out" }, 0.7 + i * 0.42 + 0.32);
|
||||
});
|
||||
// Dispatch pulses brighter — the decisive moment.
|
||||
const last = qSteps[qSteps.length - 1];
|
||||
tl.to(qDot, { opacity: 1, scale: 1, duration: 0.4, ease: "back.out(2)" }, 0.7 + qSteps.length * 0.42)
|
||||
.to(last, { color: "#ff5a6e", duration: 0.3 }, "<");
|
||||
};
|
||||
|
||||
const st = ScrollTrigger.create({ trigger: root, start: "top 78%", once: true, onEnter: play });
|
||||
return () => st.kill();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={rootRef} className="qs">
|
||||
<style dangerouslySetInnerHTML={{ __html: CSS }} />
|
||||
<div className="qs-frame">
|
||||
<WorkflowScene variant="logistics" ariaLabel="Quantum decision engine — parallel route simulation" />
|
||||
</div>
|
||||
|
||||
<div className="qs-compare">
|
||||
<div className="qs-col qs-col--trad">
|
||||
<span className="qs-col-tag">Traditional</span>
|
||||
<div className="qs-col-steps">
|
||||
<span>One Route</span>
|
||||
<span className="qs-col-arrow">↓</span>
|
||||
<span>Dispatch</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="qs-col qs-col--q">
|
||||
<span className="qs-col-tag qs-col-tag--accent">Quantum</span>
|
||||
<div className="qs-col-steps qs-col-steps--accent">
|
||||
<span className="qs-qstep">100+ Route Simulations</span>
|
||||
<span className="qs-col-arrow">→</span>
|
||||
<span className="qs-qstep">AI Comparison</span>
|
||||
<span className="qs-col-arrow">→</span>
|
||||
<span className="qs-qstep">Best Route</span>
|
||||
<span className="qs-col-arrow">→</span>
|
||||
<span className="qs-qstep qs-col-final">
|
||||
Dispatch<span className="qs-qdot" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const CSS = `
|
||||
.qs { position: relative; }
|
||||
|
||||
/* --------------------------------------------------------------
|
||||
Section 05 runs light — every other section on this page keeps
|
||||
the shared dark "ground texture" system, but this one uses the
|
||||
shared white .sw-bg--white variant (set via the Section's bg
|
||||
prop in SolutionsWorkflow.tsx) so the workflow reads as a
|
||||
dashboard panel, not a visualization sitting in a black canvas —
|
||||
while still going through the exact same inset/rounded .sw-bg
|
||||
card surface as every other section. Only the text colors need
|
||||
a local override here, for readability on that white surface.
|
||||
-------------------------------------------------------------- */
|
||||
#quantum .sw-h { color: #14151a !important; }
|
||||
#quantum .sw-lede { color: #5a5b64 !important; }
|
||||
|
||||
.qs-frame {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: auto;
|
||||
min-height: min(84vh, 900px);
|
||||
border-radius: 28px;
|
||||
overflow: hidden;
|
||||
background: #f6f6f7;
|
||||
border: 1px solid rgba(20,21,26,0.08);
|
||||
box-shadow: 0 30px 70px -45px rgba(20,21,26,0.25);
|
||||
}
|
||||
.qs-frame .wf-scene {
|
||||
position: absolute;
|
||||
inset: clamp(16px, 3vw, 32px);
|
||||
width: auto;
|
||||
height: auto;
|
||||
aspect-ratio: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
.qs-compare {
|
||||
position: relative;
|
||||
margin-top: clamp(28px, 4vw, 48px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.4fr;
|
||||
gap: clamp(20px, 3vw, 40px);
|
||||
}
|
||||
.qs-col {
|
||||
padding: clamp(20px, 2.4vw, 30px) clamp(22px, 2.6vw, 34px);
|
||||
border-radius: 22px;
|
||||
background: #f6f6f7;
|
||||
border: 1px solid rgba(20,21,26,0.08);
|
||||
box-shadow: 0 20px 44px -34px rgba(20,21,26,0.2);
|
||||
display: flex; flex-direction: column; align-items: center; gap: 18px; text-align: center;
|
||||
}
|
||||
.qs-col--q { background: linear-gradient(180deg, rgba(192,18,39,0.06), rgba(246,246,247,0.4)); border-color: rgba(192,18,39,0.22); }
|
||||
.qs-col-tag { font-size: 12px; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase; color: #8a8b93; }
|
||||
.qs-col-tag--accent { color: #C01227; }
|
||||
.qs-col-steps { display: flex; flex-direction: column; align-items: center; gap: 8px; color: #4a4b53; font-weight: 700; font-size: 14px; }
|
||||
.qs-col-steps--accent { color: #14151a; font-size: 15px; flex-direction: row; flex-wrap: wrap; justify-content: center; }
|
||||
.qs-qstep { display: inline-flex; align-items: center; position: relative; }
|
||||
.qs-col-arrow { color: rgba(20,21,26,0.28); font-size: 13px; }
|
||||
.qs-col-final { color: #C01227; display: inline-flex; align-items: center; gap: 8px; }
|
||||
.qs-qdot { width: 8px; height: 8px; border-radius: 50%; background: #E2354A; box-shadow: 0 0 12px 3px rgba(226,53,74,0.55); }
|
||||
@media (max-width: 860px) {
|
||||
.qs-compare { grid-template-columns: 1fr; }
|
||||
.qs-col-steps--accent { flex-direction: column; }
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.qs-frame { min-height: 62vh; border-radius: 20px; }
|
||||
.qs-frame .wf-scene { border-radius: 14px; }
|
||||
}
|
||||
`;
|
||||
3077
src/components/sections/SolutionsWorkflow.tsx
Normal file
3077
src/components/sections/SolutionsWorkflow.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ export default function StatsBar() {
|
||||
<div className="elementor-widget-container">
|
||||
<div className="logico-title">
|
||||
<CountUp end={99.2} decimals={1} suffix="%" duration={2.2} /> <br />
|
||||
<span style={{ fontSize: "20px" }}>On-Time Delivery</span>
|
||||
<span style={{ fontSize: "20px" }}>On-Time Performance</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,10 +59,10 @@ export default function TheDoormileWay() {
|
||||
<p>
|
||||
<strong>
|
||||
We connect first mile and last mile into one seamless system, reducing delays and improving efficiency. With real-time tracking and digital workflows,
|
||||
<br /> every delivery is faster, reliable, and fully transparent.
|
||||
<br /> every shipment is faster, reliable, and fully transparent.
|
||||
</strong>
|
||||
</p>
|
||||
<p>Seamless logistics powered by real-time tracking, digital processes, and reliable delivery execution.</p>
|
||||
<p>Seamless logistics powered by real-time tracking, digital processes, and reliable logistics execution.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,7 +118,7 @@ export default function TheDoormileWay() {
|
||||
</div>
|
||||
<div className="elementor-element elementor-element-74d14e3 elementor-widget elementor-widget-text-editor" data-id="74d14e3" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
|
||||
<div className="elementor-widget-container">
|
||||
<p>Real-time monitoring helps prevent delays before they impact deliveries.</p>
|
||||
<p>Real-time monitoring helps prevent delays before they impact operations.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,7 +141,7 @@ export default function TheDoormileWay() {
|
||||
</div>
|
||||
<div className="elementor-element elementor-element-74cfdf7 elementor-widget elementor-widget-text-editor" data-id="74cfdf7" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
|
||||
<div className="elementor-widget-container">
|
||||
<p>One unified system manages the entire delivery process seamlessly.</p>
|
||||
<p>One unified system manages the entire logistics process seamlessly.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,12 +45,12 @@ const STAGES: Stage[] = [
|
||||
{
|
||||
img: "/images/last-mile-approach.webp",
|
||||
label: "Stage 03",
|
||||
title: "Last Mile Delivery",
|
||||
title: "Last Mile Logistics",
|
||||
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"],
|
||||
desc: "The final handoff. Our routing engine optimizes multi-stop itineraries to move parcels directly to customers' doorsteps in record time.",
|
||||
points: ["Multi-stop route optimisation", "Precise arrival windows", "Digital proof of delivery"],
|
||||
stats: [
|
||||
{ value: "450K+", label: "Happy Deliveries" },
|
||||
{ value: "450K+", label: "Shipments Delivered" },
|
||||
{ value: "2.8 Hours", label: "Average Turnaround" }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -465,7 +465,7 @@ export default function WingsHero() {
|
||||
<p>
|
||||
Linking India's major regional capitals with
|
||||
AI-powered air movement and zero-emission EV
|
||||
last-mile delivery.
|
||||
last-mile transport.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@ const STEPS = [
|
||||
},
|
||||
{
|
||||
label: "EV Last-Mile",
|
||||
desc: "Zero-emission electric vehicles complete the final delivery.",
|
||||
desc: "Zero-emission electric vehicles complete the final mile.",
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 13h13l3 4h2v3H3z" />
|
||||
@@ -72,7 +72,7 @@ export default function WingsHowItWorks() {
|
||||
<h2 className={styles.title}>
|
||||
Pickup to
|
||||
<br />
|
||||
delivery, one
|
||||
arrival, one
|
||||
<br />
|
||||
connected flow
|
||||
</h2>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ScrollReveal } from "@/animations/Reveal";
|
||||
const INDUSTRIES = [
|
||||
{
|
||||
title: "Quick Commerce",
|
||||
challenge: "Sub-3-hour delivery windows for dark stores and micro-warehouses.",
|
||||
challenge: "Sub-3-hour arrival windows for dark stores and micro-warehouses.",
|
||||
solution: "Dedicated same-day air capacity linking metros to fulfillment hubs.",
|
||||
image: "/images/card 1.png",
|
||||
},
|
||||
@@ -34,7 +34,7 @@ const INDUSTRIES = [
|
||||
{
|
||||
title: "E-Commerce Logistics",
|
||||
challenge: "Next-day and same-day fulfillment expectations across tier-1 and tier-2 cities.",
|
||||
solution: "Direct sorting-hub-to-airport clearance network for faster regional delivery.",
|
||||
solution: "Direct sorting-hub-to-airport clearance network for faster regional logistics.",
|
||||
image: "/images/card6.png",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -475,7 +475,7 @@ export default function WingsMileTruth() {
|
||||
<div className="wmt-inner dm-section-container">
|
||||
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wmt-header">
|
||||
<div className="wmt-eyebrow">/ MileTruth™ AI /</div>
|
||||
<h2 className="wmt-title">The intelligence layer behind every delivery</h2>
|
||||
<h2 className="wmt-title">The intelligence layer behind every shipment</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<StaggerChildren stagger={0.08} duration={0.65} yOffset={25} className="wmt-grid">
|
||||
|
||||
@@ -204,7 +204,7 @@ export default function WingsSpeedAdvantage() {
|
||||
Max <CountUp end={3} duration={1.4} triggerOnce />
|
||||
</div>
|
||||
<div className="wsa-unit">Hours, Door-to-Door</div>
|
||||
<p className="wsa-panel-sub">Same-day delivery between major regional capitals.</p>
|
||||
<p className="wsa-panel-sub">Same-day logistics between major regional capitals.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ const MAX_WAVE_AMP_FACTOR = 1 + 0.28 + 0.15;
|
||||
const FEATURES = [
|
||||
{
|
||||
title: "Same-Day Air Logistics",
|
||||
desc: "Max 3-hour door-to-door delivery between India's major regional capitals.",
|
||||
desc: "Max 3-hour door-to-door transit between India's major regional capitals.",
|
||||
icon: (
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
@@ -70,7 +70,7 @@ const FEATURES = [
|
||||
},
|
||||
{
|
||||
title: "EV Last Mile",
|
||||
desc: "Zero-emission electric vehicles complete every first- and final-mile delivery.",
|
||||
desc: "Zero-emission electric vehicles complete every first- and final-mile leg.",
|
||||
icon: (
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 13h13l3 4h2v3H3z" />
|
||||
|
||||
@@ -6,7 +6,7 @@ import IndustryWorldMap from "./IndustryWorldMap";
|
||||
const WS_STATS = [
|
||||
{ value: "500", plus: "+", label: "Women Partners" },
|
||||
{ value: "35", plus: "+", label: "Cities" },
|
||||
{ value: "10K", plus: "+", label: "Deliveries" },
|
||||
{ value: "10K", plus: "+", label: "Shipments" },
|
||||
];
|
||||
|
||||
const WS_CARDS = [
|
||||
|
||||
@@ -54,7 +54,7 @@ const FEATURES: EVFeature[] = [
|
||||
</>,
|
||||
),
|
||||
title: "Distance Reduction",
|
||||
desc: "Same volume delivered with a leaner, better-used fleet.",
|
||||
desc: "Same volume moved with a leaner, better-used fleet.",
|
||||
},
|
||||
{
|
||||
icon: ico(
|
||||
@@ -74,7 +74,7 @@ const FEATURES: EVFeature[] = [
|
||||
</>,
|
||||
),
|
||||
title: "SLA Performance",
|
||||
desc: "Real-time correction keeps deliveries on time.",
|
||||
desc: "Real-time correction keeps shipments on time.",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -91,12 +91,12 @@ export default function Workflow2() {
|
||||
gapBottom
|
||||
bannerImage="/images/mid-mile-approach.webp"
|
||||
cardTitle="CHOOSE THE BEST PLAN"
|
||||
cardSubtitle="Analyze thousands of route possibilities and automatically select the most efficient delivery strategy."
|
||||
cardSubtitle="Analyze thousands of route possibilities and automatically select the most efficient routing strategy."
|
||||
eyebrow="/ Innovation /"
|
||||
titleLead="MANY STRATEGIES. "
|
||||
titleAccent="ONE BEST PLAN."
|
||||
image="/videos/workflow-2-routing.mp4"
|
||||
imageAlt="AI route-planning engine selecting the best delivery plan"
|
||||
imageAlt="AI route-planning engine selecting the best route plan"
|
||||
metrics={METRICS}
|
||||
features={FEATURES}
|
||||
cardsHeading="AI Decision Engine"
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function Workflow3() {
|
||||
return (
|
||||
<section
|
||||
className="dm-wf3"
|
||||
aria-label="Workflow 3 — Happier Riders. Higher Fulfillment."
|
||||
aria-label="Workflow 3 — Happier Fleet Drivers. Higher Fulfillment."
|
||||
>
|
||||
<StrategySection />
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const METRICS: EVStat[] = [
|
||||
/* Distinct copy for the bottom capability bar (not a repeat of the KPI row —
|
||||
* Workflow 4's brief specifies separate content for each). */
|
||||
const BAR_STATS: EVStat[] = [
|
||||
{ text: "Multi-Rider", value: 0, suffix: "", label: "Dynamic Routing" },
|
||||
{ text: "Multi-Driver", value: 0, suffix: "", label: "Dynamic Routing" },
|
||||
{ text: "Optimized", value: 0, suffix: "", label: "Time Windows" },
|
||||
{ text: "Real-Time", value: 0, suffix: "", label: "Fleet Balancing" },
|
||||
{ text: "Cost + Distance + SLA", value: 0, suffix: "", label: "Objectives" },
|
||||
@@ -62,7 +62,7 @@ const FEATURES: EVFeature[] = [
|
||||
</>,
|
||||
),
|
||||
title: "Encode the Problem",
|
||||
desc: "Rider assignments, delivery sequences, capacity, traffic, service time, and SLA constraints become part of one mathematical optimization model.",
|
||||
desc: "Fleet driver assignments, stop sequences, capacity, traffic, service time, and SLA constraints become part of one mathematical optimization model.",
|
||||
},
|
||||
{
|
||||
icon: ico(<path d="m8 3 4 8 5-5 5 15H2L8 3z" />),
|
||||
@@ -105,7 +105,7 @@ export default function Workflow4() {
|
||||
gapBottom
|
||||
bannerImage="/images/workflow4.png"
|
||||
image="/videos/Doormile-quantum-video3.mp4"
|
||||
cardTitle="BUILDING QUANTUM INTELLIGENCE"
|
||||
cardTitle="QUANTUM INTELLIGENCE"
|
||||
cardSubtitle="Exploring a hybrid optimization layer that treats routing as an energy landscape, not a search problem."
|
||||
eyebrow="/ Quantum Intelligence /"
|
||||
titleLead="FROM ROUTE SEARCH. "
|
||||
|
||||
Reference in New Issue
Block a user