update how it works card update
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function ContactsHero() {
|
||||
return (
|
||||
@@ -82,9 +81,9 @@ export default function ContactsHero() {
|
||||
-webkit-backdrop-filter: none !important;
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
max-width: 820px !important;
|
||||
width: 90% !important;
|
||||
padding: 0 24px !important;
|
||||
max-width: 1500px !important;
|
||||
width: 92% !important;
|
||||
box-shadow: none !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
@@ -95,60 +94,53 @@ export default function ContactsHero() {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Spaced kicker */
|
||||
.contacts-hero-kicker {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
gap: 12px !important;
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
.contacts-hero-kicker-line {
|
||||
display: block !important;
|
||||
width: 24px !important;
|
||||
height: 1.5px !important;
|
||||
background: #C01227 !important;
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.contacts-hero-kicker-text {
|
||||
font-size: 13px !important;
|
||||
font-weight: 850 !important;
|
||||
letter-spacing: 4px !important;
|
||||
color: #C01227 !important;
|
||||
text-transform: uppercase !important;
|
||||
font-family: var(--font-manrope), "Manrope", sans-serif !important;
|
||||
}
|
||||
|
||||
/* Bold modern typography */
|
||||
/* Hero headline — large, light, reference-matched display type.
|
||||
Size scales with the viewport so the line-to-container width ratio
|
||||
stays constant; the cap keeps the longest line inside the 1500px
|
||||
container (with nowrap on desktop) so it can never overflow/clip. */
|
||||
.contacts-hero-title {
|
||||
font-size: clamp(34px, 5.2vw, 62px) !important;
|
||||
font-weight: 850 !important;
|
||||
line-height: 1.15 !important;
|
||||
font-size: clamp(34px, 5.9vw, 98px) !important;
|
||||
font-weight: 300 !important;
|
||||
line-height: 0.95 !important;
|
||||
color: #ffffff !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: -1.8px !important;
|
||||
margin: 0 0 20px 0 !important;
|
||||
letter-spacing: -0.02em !important;
|
||||
margin: 0 0 28px 0 !important;
|
||||
font-family: var(--font-manrope), "Manrope", sans-serif !important;
|
||||
}
|
||||
|
||||
.contacts-hero-title-line {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Keep each line intact on desktop — never split SYSTEM or PROMISE/KEPT */
|
||||
@media (min-width: 1024px) {
|
||||
.contacts-hero-title-line {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
}
|
||||
|
||||
.contacts-hero-title-highlight {
|
||||
background: linear-gradient(135deg, #ffffff 40%, #c01227 100%) !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
color: #c01227 !important;
|
||||
}
|
||||
|
||||
/* Description text */
|
||||
.contacts-hero-desc {
|
||||
font-size: clamp(15px, 1.22vw, 18px) !important;
|
||||
font-size: clamp(15px, 1.3vw, 19px) !important;
|
||||
line-height: 1.6 !important;
|
||||
color: rgba(255, 255, 255, 0.75) !important;
|
||||
max-width: 600px !important;
|
||||
margin: 0 auto 36px auto !important;
|
||||
color: rgba(255, 255, 255, 0.82) !important;
|
||||
max-width: 640px !important;
|
||||
margin: 0 auto 24px auto !important;
|
||||
font-weight: 500 !important;
|
||||
font-family: var(--font-manrope), "Manrope", sans-serif !important;
|
||||
}
|
||||
|
||||
.contacts-hero-desc-trademark {
|
||||
color: #ffffff !important;
|
||||
font-weight: 700 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
/* Breadcrumb capsule */
|
||||
.contacts-hero-breadcrumbs {
|
||||
display: inline-flex !important;
|
||||
@@ -201,6 +193,9 @@ export default function ContactsHero() {
|
||||
padding: 0 16px !important;
|
||||
width: 95% !important;
|
||||
}
|
||||
.contacts-hero-title {
|
||||
letter-spacing: -1px !important;
|
||||
}
|
||||
}
|
||||
`}} />
|
||||
<div className="custom-standard-hero-container">
|
||||
@@ -212,18 +207,16 @@ export default function ContactsHero() {
|
||||
<div className="contacts-hero-glow-blue"></div>
|
||||
|
||||
<div className="contacts-hero-glass-card">
|
||||
<div className="contacts-hero-kicker">
|
||||
<span className="contacts-hero-kicker-line"></span>
|
||||
<span className="contacts-hero-kicker-text">24/7 support & sales</span>
|
||||
<span className="contacts-hero-kicker-line"></span>
|
||||
</div>
|
||||
|
||||
<h1 className="contacts-hero-title">
|
||||
Get In <span className="contacts-hero-title-highlight">Touch</span>
|
||||
<span className="contacts-hero-title-line">One Connected System.</span>
|
||||
<span className="contacts-hero-title-line">
|
||||
One Promise <span className="contacts-hero-title-highlight">Kept.</span>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
|
||||
<p className="contacts-hero-desc">
|
||||
Have questions about our smart delivery network, pricing plans, or partner ecosystem? Let's build the future of logistics together.
|
||||
Stop managing separate logistics providers. Doormile unifies First Mile, Mid Mile and Last Mile into one intelligent delivery ecosystem powered by{" "}
|
||||
<span className="contacts-hero-desc-trademark">MileTruth™ AI</span>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ export default function HowItWorksHero() {
|
||||
__html: `
|
||||
.howits-hero-custom-bg.elementor-repeater-item-3264830,
|
||||
.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/home2-banner-1.jpg') !important;
|
||||
background-position: center !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: cover !important;
|
||||
|
||||
@@ -60,10 +60,32 @@ function pointInPoly(x: number, y: number, poly: number[][]) {
|
||||
return inside;
|
||||
}
|
||||
|
||||
export default function IndustryWorldMap() {
|
||||
/** Parse a #rrggbb hex into an [r,g,b] triple. Falls back to the section red. */
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
||||
if (!m) return [239, 68, 68];
|
||||
const int = parseInt(m[1], 16);
|
||||
return [(int >> 16) & 255, (int >> 8) & 255, int & 255];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param accent Network accent colour (#rrggbb) for the hub nodes, pulse
|
||||
* rings, travelling packets and dashed routes. The dotted continent
|
||||
* silhouette stays neutral grey. Defaults to the section red so the Women
|
||||
* Empowerment usage is unchanged; the MileTruth workflows pass their own
|
||||
* accent (WF1 teal/cyan · WF2 crimson/red).
|
||||
*/
|
||||
export default function IndustryWorldMap({
|
||||
accent = "#ef4444",
|
||||
}: {
|
||||
accent?: string;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const [ar, ag, ab] = hexToRgb(accent);
|
||||
const rgba = (a: number) => `rgba(${ar},${ag},${ab},${a})`;
|
||||
const solid = `rgb(${ar},${ag},${ab})`;
|
||||
const canvas = canvasRef.current;
|
||||
const parent = canvas?.parentElement;
|
||||
if (!canvas || !parent) return;
|
||||
@@ -135,7 +157,7 @@ export default function IndustryWorldMap() {
|
||||
ctx.save();
|
||||
ctx.setLineDash([4, 7]);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "rgba(239,68,68,0.13)";
|
||||
ctx.strokeStyle = rgba(0.13);
|
||||
for (const [a, b] of ROUTES) {
|
||||
const c = ctrl(cs[a], cs[b]);
|
||||
ctx.beginPath();
|
||||
@@ -156,8 +178,8 @@ export default function IndustryWorldMap() {
|
||||
const tt = Math.max(0, t - 0.04);
|
||||
const pt = bezier(cs[a], c, cs[b], tt);
|
||||
const grad = ctx.createLinearGradient(pt.x, pt.y, p.x, p.y);
|
||||
grad.addColorStop(0, "rgba(239,68,68,0)");
|
||||
grad.addColorStop(1, "rgba(239,68,68,0.5)");
|
||||
grad.addColorStop(0, rgba(0));
|
||||
grad.addColorStop(1, rgba(0.5));
|
||||
ctx.strokeStyle = grad;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
@@ -165,9 +187,9 @@ export default function IndustryWorldMap() {
|
||||
ctx.lineTo(p.x, p.y);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.shadowColor = "#ef4444";
|
||||
ctx.shadowColor = solid;
|
||||
ctx.shadowBlur = 12;
|
||||
ctx.fillStyle = "#ef4444";
|
||||
ctx.fillStyle = solid;
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, 2.6, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
@@ -183,13 +205,13 @@ export default function IndustryWorldMap() {
|
||||
const radius = 3 + phase * 24;
|
||||
const alpha = (1 - phase) * 0.45;
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = `rgba(239,68,68,${alpha})`;
|
||||
ctx.strokeStyle = rgba(alpha);
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.arc(c.x, c.y, radius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.fillStyle = "#ef4444";
|
||||
ctx.shadowColor = "#ef4444";
|
||||
ctx.fillStyle = solid;
|
||||
ctx.shadowColor = solid;
|
||||
ctx.shadowBlur = 8;
|
||||
ctx.beginPath();
|
||||
ctx.arc(c.x, c.y, 2.6, 0, Math.PI * 2);
|
||||
@@ -221,7 +243,7 @@ export default function IndustryWorldMap() {
|
||||
cancelAnimationFrame(raf);
|
||||
ro.disconnect();
|
||||
};
|
||||
}, []);
|
||||
}, [accent]);
|
||||
|
||||
return <canvas ref={canvasRef} className="ind__map" aria-hidden="true" />;
|
||||
}
|
||||
|
||||
@@ -1,66 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import EVSection, { EVStat, EVBadge, EVSlide, EVCardsTheme } from "./EVSection";
|
||||
import EVSection, { EVStat, EVBadge, EVFeature, EVCardsTheme } from "./EVSection";
|
||||
import WorkflowScene from "./WorkflowScene";
|
||||
|
||||
/* Cyan / electric-blue — matches the Optimization Engine scene palette. */
|
||||
/* Cyan / Teal — matches the Optimization Engine scene palette. */
|
||||
const THEME: EVCardsTheme = {
|
||||
accent: "#00E5FF",
|
||||
accent2: "#3B82F6",
|
||||
glow: "rgba(0,229,255,0.22)",
|
||||
accent2: "#14B8A6",
|
||||
glow: "rgba(0,229,255,0.18)",
|
||||
};
|
||||
|
||||
/**
|
||||
* Workflow 1 — Performance (hybrid split-screen).
|
||||
*
|
||||
* Keeps the premium EVSection chrome (banner → floating card → dark section →
|
||||
* stat bar) but converts the body into a split layout:
|
||||
* • Left — the PRODUCTION Optimization Engine Three.js scene (the same
|
||||
* OptimizationCanvas used by OptimizationSection: depot, trucks,
|
||||
* route optimization, shaders, particles). One instance, mounted
|
||||
* compactly instead of as a multi-viewport pinned scroll.
|
||||
* • Right — lightweight auto-rotating cards (4s / 600ms fade+slide).
|
||||
*
|
||||
* This preserves the 3D storytelling while dramatically cutting page height.
|
||||
* • Left — the PRODUCTION Optimization Engine Three.js scene (depot, trucks,
|
||||
* route optimization, shaders, particles).
|
||||
* • Right — a COMPACT DASHBOARD: a quick KPI row over short feature cards,
|
||||
* sitting on the animated network backdrop (cyan/teal). Surfaces the
|
||||
* key metrics fast, keeps copy short and reads well on mobile.
|
||||
*/
|
||||
const SLIDES: EVSlide[] = [
|
||||
const METRICS: EVStat[] = [
|
||||
{ value: 42, suffix: "%", label: "Distance Saved" },
|
||||
{ value: 28, suffix: "%", label: "Faster Routes" },
|
||||
{ value: 31, suffix: "%", label: "Lower Cost" },
|
||||
{ value: 99.9, decimals: 1, suffix: "%", label: "On-Time" },
|
||||
];
|
||||
|
||||
const ico = (path: React.ReactNode) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
{path}
|
||||
</svg>
|
||||
);
|
||||
|
||||
const FEATURES: EVFeature[] = [
|
||||
{
|
||||
status: "Optimization Running",
|
||||
icon: ico(<polygon points="3 11 22 2 13 21 11 13 3 11" />),
|
||||
title: "Route Optimization",
|
||||
value: 42,
|
||||
suffix: "%",
|
||||
metricLabel: "Distance Saved",
|
||||
kpis: ["Route optimization active", "37% fewer vehicles required", "SLA compliance 99.9%"],
|
||||
desc: "AI selects the most efficient delivery paths across every zone, cutting unnecessary travel and fuel and battery consumption.",
|
||||
desc: "AI selects the most efficient path across every zone.",
|
||||
},
|
||||
{
|
||||
status: "Fleet Balancing",
|
||||
icon: ico(
|
||||
<>
|
||||
<polyline points="22 17 13.5 8.5 8.5 13.5 2 7" />
|
||||
<polyline points="16 17 22 17 22 11" />
|
||||
</>,
|
||||
),
|
||||
title: "Distance Reduction",
|
||||
value: 37,
|
||||
suffix: "%",
|
||||
metricLabel: "Fewer Vehicles",
|
||||
kpis: ["Load balancing engaged", "Same volume, leaner fleet", "Lower maintenance & staffing"],
|
||||
desc: "Intelligent load balancing fulfils the same order volume with a leaner, better-utilised fleet — fewer miles, fewer vehicles.",
|
||||
desc: "Same volume delivered with a leaner, better-used fleet.",
|
||||
},
|
||||
{
|
||||
status: "Dispatch Active",
|
||||
icon: ico(
|
||||
<>
|
||||
<path d="M12 14l4-4" />
|
||||
<path d="M3.34 19a10 10 0 1 1 17.32 0" />
|
||||
</>,
|
||||
),
|
||||
title: "Fleet Efficiency",
|
||||
value: 31,
|
||||
suffix: "%",
|
||||
metricLabel: "Lower Operating Cost",
|
||||
kpis: ["Higher fleet utilisation", "Predictable operations", "Reduced fuel & overhead"],
|
||||
desc: "Smart grouping and dispatch keep operations smooth and predictable while reducing maintenance and staffing cost.",
|
||||
desc: "Higher utilisation and lower operating cost.",
|
||||
},
|
||||
{
|
||||
status: "SLA Safe",
|
||||
icon: ico(
|
||||
<>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
<polyline points="9 12 11 14 15 10" />
|
||||
</>,
|
||||
),
|
||||
title: "SLA Performance",
|
||||
value: 99.9,
|
||||
decimals: 1,
|
||||
suffix: "%",
|
||||
metricLabel: "On-Time Delivery",
|
||||
kpis: ["Real-time route correction", "Consistent delivery windows", "100% order fulfilment"],
|
||||
desc: "Real-time routing keeps deliveries on time across all zones, sustaining high customer satisfaction and SLA performance.",
|
||||
desc: "Real-time correction keeps deliveries on time.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -69,31 +83,24 @@ const BADGES: EVBadge[] = [
|
||||
{ value: "-37%", label: "FEWER VEHICLES" },
|
||||
];
|
||||
|
||||
const STATS: EVStat[] = [
|
||||
{ value: 42, suffix: "%", label: "Distance Saved" },
|
||||
{ value: 28, suffix: "%", label: "Faster Routes" },
|
||||
{ value: 31, suffix: "%", label: "Lower Cost" },
|
||||
{ value: 99.9, decimals: 1, suffix: "%", label: "On-Time" },
|
||||
];
|
||||
|
||||
export default function Workflow1() {
|
||||
return (
|
||||
<EVSection
|
||||
ariaLabel="Workflow 1 — Performance"
|
||||
gapTop
|
||||
gapBottom
|
||||
bannerImage="/images/home3-slide-1.jpg"
|
||||
bannerImage="/images/mile-1.png"
|
||||
cardTitle="OPTIMIZE EVERY MILE"
|
||||
cardSubtitle="Cut travel distance, reduce operating cost, and improve fleet productivity across every route."
|
||||
eyebrow="/ Performance /"
|
||||
titleLead="SMARTER ROUTES. "
|
||||
titleAccent="LOWER COSTS."
|
||||
mediaSlot={<WorkflowScene variant="optimization" ariaLabel="Live route optimization engine" />}
|
||||
slides={SLIDES}
|
||||
metrics={METRICS}
|
||||
features={FEATURES}
|
||||
cardsHeading="Performance Insight"
|
||||
cardsTheme={THEME}
|
||||
badges={BADGES}
|
||||
stats={STATS}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,63 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import EVSection, { EVStat, EVBadge, EVSlide, EVCardsTheme } from "./EVSection";
|
||||
import EVSection, { EVStat, EVBadge, EVFeature, EVCardsTheme } from "./EVSection";
|
||||
import WorkflowScene from "./WorkflowScene";
|
||||
|
||||
/* Red / crimson / orange — matches the Routing Engine (logistics brain) scene. */
|
||||
/* Red / Crimson — matches the Routing Engine (logistics brain) scene. */
|
||||
const THEME: EVCardsTheme = {
|
||||
accent: "#E2354A",
|
||||
accent2: "#F59E0B",
|
||||
glow: "rgba(226,53,74,0.24)",
|
||||
accent2: "#C01227",
|
||||
glow: "rgba(226,53,74,0.2)",
|
||||
};
|
||||
|
||||
/**
|
||||
* Workflow 2 — Innovation (hybrid split-screen).
|
||||
*
|
||||
* Keeps the premium EVSection chrome but converts the body into a split layout:
|
||||
* • Left — the PRODUCTION Routing Engine Three.js scene (the same
|
||||
* LogisticsBrainCanvas used by LogisticsBrainSection: city nodes,
|
||||
* buildings, multi-route generation, constraint evaluation,
|
||||
* network/brain animation). One instance, mounted compactly.
|
||||
* • Right — lightweight auto-rotating cards (4s / 600ms fade+slide).
|
||||
*
|
||||
* Preserves the 3D storytelling while dramatically cutting page height.
|
||||
* • Left — the PRODUCTION Routing Engine Three.js scene (city nodes,
|
||||
* buildings, multi-route generation, constraint evaluation).
|
||||
* • Right — a COMPACT DASHBOARD: a quick KPI row over short feature cards,
|
||||
* sitting on the animated network backdrop (red/crimson). Surfaces
|
||||
* the key metrics fast and keeps the section tight on mobile.
|
||||
*/
|
||||
const SLIDES: EVSlide[] = [
|
||||
const METRICS: EVStat[] = [
|
||||
{ value: 45, suffix: "ms", label: "Inference" },
|
||||
{ value: 12, suffix: "+", label: "Strategies" },
|
||||
{ value: 99.9, decimals: 1, suffix: "%", label: "SLA Met" },
|
||||
{ value: 24, suffix: "/7", label: "Adaptive" },
|
||||
];
|
||||
|
||||
const ico = (path: React.ReactNode) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
{path}
|
||||
</svg>
|
||||
);
|
||||
|
||||
const FEATURES: EVFeature[] = [
|
||||
{
|
||||
status: "Generating Routes",
|
||||
icon: ico(
|
||||
<>
|
||||
<polygon points="12 2 2 7 12 12 22 7 12 2" />
|
||||
<polyline points="2 17 12 22 22 17" />
|
||||
<polyline points="2 12 12 17 22 12" />
|
||||
</>,
|
||||
),
|
||||
title: "Generate Routes",
|
||||
value: 6,
|
||||
suffix: " plans",
|
||||
metricLabel: "Route Plans Generated",
|
||||
kpis: ["Parallel strategies explored", "59 orders in scope", "Real-time combinations"],
|
||||
desc: "The Parallel Universe Engine evaluates many routing strategies at once for every dispatch window, exploring route combinations in real time.",
|
||||
desc: "Many strategies explored per dispatch window.",
|
||||
},
|
||||
{
|
||||
status: "Constraints Passed",
|
||||
icon: ico(
|
||||
<>
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
||||
<polyline points="22 4 12 14.01 9 11.01" />
|
||||
</>,
|
||||
),
|
||||
title: "Check Constraints",
|
||||
value: 5,
|
||||
metricLabel: "Constraints Evaluated",
|
||||
kpis: ["Battery aware", "Capacity & distance checked", "Powered by Google OR-Tools"],
|
||||
desc: "Battery, distance, capacity and time are first-class inputs — battery-aware simulation solves the EV routing challenge.",
|
||||
desc: "Battery, capacity, distance and time validated.",
|
||||
},
|
||||
{
|
||||
status: "Scoring Routes",
|
||||
icon: ico(
|
||||
<>
|
||||
<line x1="6" y1="20" x2="6" y2="14" />
|
||||
<line x1="12" y1="20" x2="12" y2="4" />
|
||||
<line x1="18" y1="20" x2="18" y2="10" />
|
||||
</>,
|
||||
),
|
||||
title: "Score & Compare",
|
||||
value: 12,
|
||||
suffix: "+",
|
||||
metricLabel: "Strategies Compared",
|
||||
kpis: ["Ranked by total cost", "SLA protected", "Real-time ETA validation"],
|
||||
desc: "Every plan is benchmarked in parallel and ranked by total cost, with sub-45ms inference at production scale.",
|
||||
desc: "Plans ranked by total cost in parallel.",
|
||||
},
|
||||
{
|
||||
status: "Delivery Ready",
|
||||
icon: ico(<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />),
|
||||
title: "Select Best Plan",
|
||||
value: 45,
|
||||
suffix: "ms",
|
||||
metricLabel: "Decision Latency",
|
||||
kpis: ["Late plans rejected", "Best plan locked in", "Dispatched to the fleet"],
|
||||
desc: "Late plans are rejected automatically and the highest-performing, SLA-first plan is locked in and dispatched.",
|
||||
desc: "SLA-first plan locked in and dispatched.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -66,13 +85,6 @@ const BADGES: EVBadge[] = [
|
||||
{ value: "100%", label: "SLA-FIRST" },
|
||||
];
|
||||
|
||||
const STATS: EVStat[] = [
|
||||
{ value: 45, suffix: "ms", label: "Inference" },
|
||||
{ value: 12, suffix: "+", label: "Strategies" },
|
||||
{ value: 99.9, decimals: 1, suffix: "%", label: "SLA Met" },
|
||||
{ value: 24, suffix: "/7", label: "Adaptive" },
|
||||
];
|
||||
|
||||
export default function Workflow2() {
|
||||
return (
|
||||
<EVSection
|
||||
@@ -84,12 +96,17 @@ export default function Workflow2() {
|
||||
eyebrow="/ Innovation /"
|
||||
titleLead="MANY STRATEGIES. "
|
||||
titleAccent="ONE BEST PLAN."
|
||||
mediaSlot={<WorkflowScene variant="logistics" ariaLabel="Live multi-route logistics brain" />}
|
||||
slides={SLIDES}
|
||||
mediaSlot={
|
||||
<WorkflowScene
|
||||
variant="logistics"
|
||||
ariaLabel="Live multi-route logistics brain"
|
||||
/>
|
||||
}
|
||||
metrics={METRICS}
|
||||
features={FEATURES}
|
||||
cardsHeading="AI Decision Engine"
|
||||
cardsTheme={THEME}
|
||||
badges={BADGES}
|
||||
stats={STATS}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,282 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import React from "react";
|
||||
import StrategySection from "../strategy/StrategySection";
|
||||
|
||||
/**
|
||||
* Workflow 3 — the "Happier Riders. Higher Fulfillment." 3D scroll-storytelling
|
||||
* experience (StrategySection).
|
||||
*
|
||||
* The old bottom "STRATEGY" explanation card (heading + description + pagination
|
||||
* + chevron graphic) has been removed: the workflow narrative is already covered
|
||||
* by the previous sections, so that card only repeated it and lengthened the
|
||||
* page. StrategySection now renders standalone (non-connected) with its own
|
||||
* rounded card. The wrapper only cancels the global `section` padding so the
|
||||
* tall pinned section doesn't gain empty bands.
|
||||
*/
|
||||
export default function Workflow3() {
|
||||
const [activeSlide, setActiveSlide] = useState(0);
|
||||
const [paused, setPaused] = useState(false);
|
||||
const [inView, setInView] = useState(false);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const slides = [
|
||||
{
|
||||
title: "STRATEGY",
|
||||
text: "Our grading engine continuously evaluates fulfillment performance, SLA compliance, and route efficiency before every dispatch. By comparing legacy routing methods with unified optimization, the system ensures smarter and more reliable delivery planning. This helps businesses maintain operational consistency while improving overall delivery performance."
|
||||
},
|
||||
{
|
||||
title: "STRATEGY",
|
||||
text: "Every EV route is pre-validated against real battery capacity and charging feasibility before a rider leaves the hub. This reduces the risk of delivery interruptions, charging failures, or delayed orders during operations. The platform ensures reliable route execution while maximizing EV fleet efficiency and rider confidence."
|
||||
},
|
||||
{
|
||||
title: "STRATEGY",
|
||||
text: "The system provides actionable fleet insights and optimized workload distribution to improve both rider experience and operational productivity. Balanced route allocation helps reduce rider fatigue, improve retention, and maintain consistent delivery quality across zones. Managers gain better visibility into fleet performance, enabling faster and more informed decision-making."
|
||||
}
|
||||
];
|
||||
|
||||
// Always begin on slide 1 (01/03) on mount. Scrolling away and back does NOT reset
|
||||
// (the component stays mounted) — only a fresh page load / route change back to
|
||||
// MileTruth re-mounts and restarts at slide 1.
|
||||
useEffect(() => {
|
||||
setActiveSlide(0);
|
||||
}, []);
|
||||
|
||||
// Autoplay is gated on visibility: it starts only once the slider card scrolls into
|
||||
// view (not on page load) and stops when it leaves — without touching activeSlide,
|
||||
// so returning to the section resumes from wherever it was, never snapping to slide 1.
|
||||
useEffect(() => {
|
||||
const el = cardRef.current;
|
||||
if (!el) return;
|
||||
const io = new IntersectionObserver(
|
||||
([entry]) => setInView(entry.isIntersecting),
|
||||
{ threshold: 0.35 }
|
||||
);
|
||||
io.observe(el);
|
||||
return () => io.disconnect();
|
||||
}, []);
|
||||
|
||||
// Auto-advance every 10s, looping — but only while the card is in view and the user
|
||||
// isn't hovering it. Keyed on activeSlide so a manual jump restarts the 10s dwell.
|
||||
useEffect(() => {
|
||||
if (!inView || paused) return;
|
||||
const id = setTimeout(() => {
|
||||
setActiveSlide((prev) => (prev + 1) % slides.length);
|
||||
}, 10000);
|
||||
return () => clearTimeout(id);
|
||||
}, [activeSlide, inView, paused, slides.length]);
|
||||
|
||||
return (
|
||||
<section className="dm-wf3" aria-label="Workflow 3 — Happier Riders. Higher Fulfillment. & Strategy">
|
||||
<section
|
||||
className="dm-wf3"
|
||||
aria-label="Workflow 3 — Happier Riders. Higher Fulfillment."
|
||||
>
|
||||
<StrategySection />
|
||||
|
||||
{/* ── Top sub-section: the full "Happier Riders. Higher Fulfillment."
|
||||
3D scroll-storytelling experience ── */}
|
||||
<StrategySection connected />
|
||||
|
||||
{/* ── Bottom sub-section: Strategy content, flush + pulled up to butt against
|
||||
the 3D card's flat bottom so the whole workflow reads as one container —
|
||||
the same connected structure used in Workflow 1 & 2 ── */}
|
||||
<div className="dm-wf3-card" ref={cardRef} onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
|
||||
{/* Left Column: Overlapping Chevron Graphic */}
|
||||
<div className="dm-workflow-left">
|
||||
<svg viewBox="0 0 320 280" fill="none" xmlns="http://www.w3.org/2000/svg" className="dm-workflow-svg">
|
||||
<path
|
||||
d="M 30,20 C 22,20 16,26 16,34 L 78,85 C 81,88 81,92 78,95 L 16,146 C 16,154 22,160 30,160 L 130,160 C 138,160 145,154 148,146 L 204,95 C 207,92 207,88 204,85 L 148,34 C 145,26 138,20 130,20 Z"
|
||||
stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" opacity="0.25"
|
||||
/>
|
||||
<path
|
||||
d="M 110,100 C 102,100 96,106 96,114 L 158,165 C 161,168 161,172 158,175 L 96,226 C 96,234 102,240 110,240 L 210,240 C 218,240 225,234 228,226 L 284,175 C 287,172 287,168 284,165 L 228,114 C 225,106 218,100 210,100 Z"
|
||||
stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" opacity="0.85"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Quotes & Text Content */}
|
||||
<div className="dm-workflow-right">
|
||||
<svg width="32" height="24" viewBox="0 0 32 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="dm-workflow-quote">
|
||||
<rect x="2" y="2" width="9" height="20" rx="1.5" transform="skewX(-12)" fill="#C01227" />
|
||||
<rect x="16" y="2" width="9" height="20" rx="1.5" transform="skewX(-12)" fill="#C01227" />
|
||||
</svg>
|
||||
|
||||
<h3 className="dm-workflow-title">{slides[activeSlide].title}</h3>
|
||||
|
||||
<div className="dm-workflow-text-container">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.p
|
||||
key={activeSlide}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -12 }}
|
||||
transition={{ duration: 0.7, ease: "easeInOut" }}
|
||||
className="dm-workflow-text"
|
||||
>
|
||||
{slides[activeSlide].text}
|
||||
</motion.p>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<div className="dm-workflow-nav">
|
||||
<span className="dm-workflow-counter">0{activeSlide + 1}/03</span>
|
||||
<div className="dm-workflow-bars">
|
||||
{slides.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
className={`dm-workflow-bar ${index === activeSlide ? "is-active" : ""}`}
|
||||
onClick={() => setActiveSlide(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style dangerouslySetInnerHTML={{ __html: styles }} />
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
.dm-wf3 { position: relative; margin: 0 auto; }
|
||||
/* Cancel the global "section { padding: 6rem 0 }": both this wrapper
|
||||
and the nested .dm-st are sections, so that padding would stack into
|
||||
large empty bands around the pinned 3D experience. */
|
||||
.dm-wf3, .dm-wf3 .dm-st { padding-top: 0; padding-bottom: 0; }
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = `
|
||||
/* ============================================================
|
||||
Workflow 3 = ONE container:
|
||||
├─ Happier Riders. Higher Fulfillment. (full StrategySection — 3D)
|
||||
└─ Strategy (content card, flush, pulled up)
|
||||
The Strategy card aligns to the 3D card's 20px side insets, butts against
|
||||
its flat bottom and rounds the bottom corners, so the two read as a single
|
||||
continuous container — same technique as Workflow 1 & 2.
|
||||
============================================================ */
|
||||
.dm-wf3 {
|
||||
position: relative;
|
||||
margin: 0 auto 0;
|
||||
}
|
||||
|
||||
/* Cancel the global "section { padding: 6rem 0 }" (consolidated into /public/css/site.css): both
|
||||
this wrapper and the nested .dm-st are sections, so that 96px top+bottom stacked
|
||||
into large empty bands above / between the workflows. These are full-bleed pinned
|
||||
experiences whose cards butt together via their own insets — no section padding. */
|
||||
.dm-wf3, .dm-wf3 .dm-st { padding-top: 0; padding-bottom: 0; }
|
||||
|
||||
.dm-wf3-card {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin: 0 20px 0;
|
||||
background: #181818;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-top: none;
|
||||
border-radius: 0 0 28px 28px;
|
||||
/* No shadow: this card is flush under the strategy 3D card and merges with it as one
|
||||
continuous container — a shadow here would re-introduce a dark band at the seam. */
|
||||
box-shadow: none;
|
||||
padding: 36px 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 40px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dm-workflow-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 440px;
|
||||
}
|
||||
.dm-workflow-svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
filter: drop-shadow(0 8px 24px rgba(0,0,0,0.3));
|
||||
}
|
||||
|
||||
.dm-workflow-right {
|
||||
flex: 1.2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
.dm-workflow-quote { margin-bottom: 5px; }
|
||||
|
||||
.dm-workflow-title {
|
||||
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
|
||||
font-size: 38px;
|
||||
font-weight: 700;
|
||||
color: #F8FAFC !important;
|
||||
letter-spacing: -0.015em;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.dm-workflow-text-container { min-height: 150px; width: 100%; }
|
||||
.dm-workflow-text {
|
||||
font-family: var(--font-manrope), system-ui, sans-serif;
|
||||
font-size: 21px;
|
||||
line-height: 1.75;
|
||||
letter-spacing: 0.01em;
|
||||
color: #A3A3A3;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.dm-workflow-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
align-self: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.dm-workflow-counter {
|
||||
font-family: var(--font-space-grotesk), sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #737373;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.dm-workflow-bars { display: flex; gap: 8px; }
|
||||
.dm-workflow-bar {
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.dm-workflow-bar.is-active { background: #C01227; }
|
||||
.dm-workflow-bar:hover { background: rgba(255, 255, 255, 0.35); }
|
||||
.dm-workflow-bar.is-active:hover { background: #C01227; }
|
||||
|
||||
/* ── Responsive — keep insets/radius aligned to the 3D card ── */
|
||||
@media (max-width: 1024px) {
|
||||
.dm-wf3-card { padding: 44px 44px; gap: 44px; }
|
||||
.dm-workflow-title { font-size: 32px; }
|
||||
.dm-workflow-text { font-size: 19px; }
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
/* Mobile: compact card so it never exceeds ~500px (was ~850px from the full
|
||||
desktop chevron + long paragraph). Smaller chevron, tighter spacing and a
|
||||
line-clamped paragraph keep the workflow state readable without a long scroll. */
|
||||
.dm-wf3-card {
|
||||
/* Bottom gap separates this last workflow card from the contact section below. */
|
||||
margin: 0 10px 16px;
|
||||
border-radius: 0 0 20px 20px;
|
||||
padding: 26px 22px;
|
||||
gap: 16px;
|
||||
flex-direction: column;
|
||||
}
|
||||
.dm-workflow-left { max-width: 128px; }
|
||||
.dm-workflow-right { width: 100%; gap: 12px; }
|
||||
.dm-workflow-quote { margin-bottom: 2px; }
|
||||
.dm-workflow-title { font-size: 22px; }
|
||||
.dm-workflow-text-container { min-height: auto; }
|
||||
.dm-workflow-text {
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 5;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dm-workflow-nav { margin-top: 4px; }
|
||||
}
|
||||
@media (max-width: 390px) {
|
||||
.dm-workflow-left { max-width: 108px; }
|
||||
.dm-workflow-title { font-size: 20px; }
|
||||
.dm-workflow-text { font-size: 14px; -webkit-line-clamp: 4; }
|
||||
}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user