fix the mobile responsive
This commit is contained in:
@@ -527,6 +527,28 @@ export default function Header() {
|
|||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive logo adjustment on mobile/tablet */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
#masthead .elementor-element.elementor-element-846e53d .hfe-site-logo .hfe-site-logo-container img {
|
||||||
|
width: 150px !important;
|
||||||
|
height: auto !important;
|
||||||
|
margin-left: 20px !important;
|
||||||
|
}
|
||||||
|
#masthead .header-menu-container {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
#masthead .menu-trigger {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
#masthead .elementor-element.elementor-element-846e53d .hfe-site-logo .hfe-site-logo-container img {
|
||||||
|
width: 130px !important;
|
||||||
|
margin-left: 10px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* The theme reveals the mobile slide-in menu via Elementor's
|
/* The theme reveals the mobile slide-in menu via Elementor's
|
||||||
body[data-elementor-device-mode="mobile"] rules, which are set by
|
body[data-elementor-device-mode="mobile"] rules, which are set by
|
||||||
Elementor's frontend JS — that JS isn't shipped in this Next port,
|
Elementor's frontend JS — that JS isn't shipped in this Next port,
|
||||||
|
|||||||
@@ -24,23 +24,7 @@ function Counter({ mv }: { mv: MotionValue<number> }) {
|
|||||||
return <span ref={ref}>{Math.round(mv.get())}</span>;
|
return <span ref={ref}>{Math.round(mv.get())}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True only while a card's own opacity window is open (with a tiny buffer).
|
|
||||||
* Lets us keep future/past story cards out of the DOM — and off the compositor
|
|
||||||
* (each has `will-change`) — until their beat is actually on screen, so no
|
|
||||||
* workflow state is rendered before activation. Visually identical, since a
|
|
||||||
* card outside its window is opacity:0 anyway. */
|
|
||||||
function useInWindow(mv: MotionValue<number>, threshold = 0.01): boolean {
|
|
||||||
// `mv` is an external mutable store (a MotionValue). useTransform `.set()`s its
|
|
||||||
// output synchronously while the PARENT renders, so a plain `.on("change") -> setState`
|
|
||||||
// updates this component during the parent's render (React warns). useSyncExternalStore
|
|
||||||
// is built for exactly this: it reads a snapshot and reconciles store-changes-during-
|
|
||||||
// render safely. The snapshot is a primitive boolean, so it never re-renders needlessly.
|
|
||||||
return useSyncExternalStore(
|
|
||||||
(onStoreChange) => mv.on("change", onStoreChange),
|
|
||||||
() => mv.get() > threshold,
|
|
||||||
() => mv.get() > threshold,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Active step index from scroll progress (−1 before the engine starts). */
|
/** Active step index from scroll progress (−1 before the engine starts). */
|
||||||
function stepFromProgress(p: number): number {
|
function stepFromProgress(p: number): number {
|
||||||
@@ -71,6 +55,8 @@ function StepRail({ active }: { active: number }) {
|
|||||||
|
|
||||||
/** One cross-fading workflow card pinned to the lower-left. */
|
/** One cross-fading workflow card pinned to the lower-left. */
|
||||||
function StoryCard({
|
function StoryCard({
|
||||||
|
step,
|
||||||
|
index,
|
||||||
opacity,
|
opacity,
|
||||||
y,
|
y,
|
||||||
num,
|
num,
|
||||||
@@ -78,6 +64,8 @@ function StoryCard({
|
|||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
|
step: number;
|
||||||
|
index: number;
|
||||||
opacity: MotionValue<number>;
|
opacity: MotionValue<number>;
|
||||||
y: MotionValue<number>;
|
y: MotionValue<number>;
|
||||||
num: string;
|
num: string;
|
||||||
@@ -85,8 +73,8 @@ function StoryCard({
|
|||||||
title: string;
|
title: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
// Don't mount this beat's card until its cross-fade window opens.
|
// Don't mount this beat's card until its step is active.
|
||||||
if (!useInWindow(opacity)) return null;
|
if (step !== index) return null;
|
||||||
return (
|
return (
|
||||||
<motion.div className="dm-lb-card-story" style={{ opacity, y }}>
|
<motion.div className="dm-lb-card-story" style={{ opacity, y }}>
|
||||||
<div className="dm-lb-card-story__head">
|
<div className="dm-lb-card-story__head">
|
||||||
@@ -230,7 +218,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* STEP 01 — Generate Routes */}
|
{/* STEP 01 — Generate Routes */}
|
||||||
<StoryCard opacity={p1o} y={p1y} num="01" kicker="Generate Routes" title="We create many delivery plans at once">
|
<StoryCard step={step} index={0} opacity={p1o} y={p1y} num="01" kicker="Generate Routes" title="We create many delivery plans at once">
|
||||||
<div className="dm-lb-chips">
|
<div className="dm-lb-chips">
|
||||||
{STRATEGIES.map((s) => (
|
{STRATEGIES.map((s) => (
|
||||||
<span key={s} className="dm-lb-chip">{s}</span>
|
<span key={s} className="dm-lb-chip">{s}</span>
|
||||||
@@ -240,7 +228,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
|||||||
</StoryCard>
|
</StoryCard>
|
||||||
|
|
||||||
{/* STEP 02 — Check Constraints (the EV paradox) */}
|
{/* STEP 02 — Check Constraints (the EV paradox) */}
|
||||||
<StoryCard opacity={p2o} y={p2y} num="02" kicker="Check Constraints" title="Every plan must respect real-world limits">
|
<StoryCard step={step} index={1} opacity={p2o} y={p2y} num="02" kicker="Check Constraints" title="Every plan must respect real-world limits">
|
||||||
<ul className="dm-lb-constraints">
|
<ul className="dm-lb-constraints">
|
||||||
{CONSTRAINT_LIST.map((c) => (
|
{CONSTRAINT_LIST.map((c) => (
|
||||||
<li key={c.label}>
|
<li key={c.label}>
|
||||||
@@ -254,7 +242,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
|||||||
</StoryCard>
|
</StoryCard>
|
||||||
|
|
||||||
{/* STEP 03 — Score & Compare (the leaderboard) */}
|
{/* STEP 03 — Score & Compare (the leaderboard) */}
|
||||||
<StoryCard opacity={p3o} y={p3y} num="03" kicker="Score & Compare" title="Each plan is scored by total delivery cost">
|
<StoryCard step={step} index={2} opacity={p3o} y={p3y} num="03" kicker="Score & Compare" title="Each plan is scored by total delivery cost">
|
||||||
<ul className="dm-lb-board">
|
<ul className="dm-lb-board">
|
||||||
{STRATEGY_SCORES.map((s) => (
|
{STRATEGY_SCORES.map((s) => (
|
||||||
<li key={s.name} className={s.win ? "is-win" : ""}>
|
<li key={s.name} className={s.win ? "is-win" : ""}>
|
||||||
@@ -267,7 +255,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
|||||||
</StoryCard>
|
</StoryCard>
|
||||||
|
|
||||||
{/* STEP 04 — Guarantee On-Time */}
|
{/* STEP 04 — Guarantee On-Time */}
|
||||||
<StoryCard opacity={p4o} y={p4y} num="04" kicker="Guarantee On-Time" title="Any plan even 1 minute late is rejected">
|
<StoryCard step={step} index={3} opacity={p4o} y={p4y} num="04" kicker="Guarantee On-Time" title="Any plan even 1 minute late is rejected">
|
||||||
<div className="dm-lb-sla">
|
<div className="dm-lb-sla">
|
||||||
<span className="dm-lb-sla__badge">⏱️ On-time only</span>
|
<span className="dm-lb-sla__badge">⏱️ On-time only</span>
|
||||||
<span className="dm-lb-sla__x">✕ Late plan → dropped</span>
|
<span className="dm-lb-sla__x">✕ Late plan → dropped</span>
|
||||||
@@ -276,7 +264,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
|||||||
</StoryCard>
|
</StoryCard>
|
||||||
|
|
||||||
{/* STEP 05 — Pick & Dispatch */}
|
{/* STEP 05 — Pick & Dispatch */}
|
||||||
<StoryCard opacity={p5o} y={p5y} num="05" kicker="Pick & Dispatch" title="The winning plan is sent to the fleet">
|
<StoryCard step={step} index={4} opacity={p5o} y={p5y} num="05" kicker="Pick & Dispatch" title="The winning plan is sent to the fleet">
|
||||||
<div className="dm-lb-winner">✓ Multi-Trip selected — lowest cost, zero delays</div>
|
<div className="dm-lb-winner">✓ Multi-Trip selected — lowest cost, zero delays</div>
|
||||||
<div className="dm-lb-chips">
|
<div className="dm-lb-chips">
|
||||||
<span className="dm-lb-chip">EV Bikes</span>
|
<span className="dm-lb-chip">EV Bikes</span>
|
||||||
|
|||||||
@@ -8,6 +8,78 @@ import { ScrollReveal, Magnetic } from "@/animations/Reveal";
|
|||||||
export default function ConnectedLogistics() {
|
export default function ConnectedLogistics() {
|
||||||
return (
|
return (
|
||||||
<div className="elementor-element elementor-element-89a0ca1 e-con-full e-flex cut-corner-no sticky-container-off e-con e-parent" data-id="89a0ca1" data-element_type="container" data-e-type="container">
|
<div className="elementor-element elementor-element-89a0ca1 e-con-full e-flex cut-corner-no sticky-container-off e-con e-parent" data-id="89a0ca1" data-element_type="container" data-e-type="container">
|
||||||
|
<style dangerouslySetInnerHTML={{ __html: `
|
||||||
|
/* Ensure the inner content wrapper is responsive and never overflows */
|
||||||
|
.elementor-element-fdb2e58 {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 650px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make heading font size fluid and responsive */
|
||||||
|
.elementor-element-7500280 .logico-title {
|
||||||
|
font-size: clamp(26px, 3.5vw, 54px) !important;
|
||||||
|
line-height: 1.25em !important;
|
||||||
|
word-wrap: break-word !important;
|
||||||
|
overflow-wrap: break-word !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure paragraphs are responsive on all screen widths */
|
||||||
|
.elementor-element-165dfa5 {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: min(619px, 100%) !important;
|
||||||
|
}
|
||||||
|
.elementor-element-3888a1e {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: min(526px, 100%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop/Laptop (min-width: 1025px) column width and flex rules */
|
||||||
|
@media (min-width: 1025px) {
|
||||||
|
.elementor-element-9ffed33 {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
align-items: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elementor-element-96343ba,
|
||||||
|
.elementor-element-71c3e1d {
|
||||||
|
width: 50% !important;
|
||||||
|
max-width: 50% !important;
|
||||||
|
flex: 1 1 50% !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
/* Force columns to stack vertically on mobile/tablet */
|
||||||
|
.elementor-element-9ffed33 {
|
||||||
|
flex-direction: column !important;
|
||||||
|
align-items: stretch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force both children to be full width */
|
||||||
|
.elementor-element-96343ba,
|
||||||
|
.elementor-element-71c3e1d {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show image at the bottom or top depending on order */
|
||||||
|
.elementor-element-96343ba {
|
||||||
|
margin-top: 30px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override desktop fixed widths on mobile/tablet */
|
||||||
|
.elementor-element-165dfa5,
|
||||||
|
.elementor-element-3888a1e {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}} />
|
||||||
<div className="elementor-element elementor-element-9ffed33 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="9ffed33" data-element_type="container" data-e-type="container" data-settings="{"background_background":"classic"}">
|
<div className="elementor-element elementor-element-9ffed33 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="9ffed33" data-element_type="container" data-e-type="container" data-settings="{"background_background":"classic"}">
|
||||||
|
|
||||||
{/* Image side */}
|
{/* Image side */}
|
||||||
|
|||||||
@@ -52,13 +52,84 @@ export default function IndexHero() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<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 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">
|
||||||
|
<style dangerouslySetInnerHTML={{ __html: `
|
||||||
|
/* Fluid responsive font size override for hero headings */
|
||||||
|
.logico-content-slider-widget .content-slider-item-heading {
|
||||||
|
font-size: clamp(30px, 5.5vw, 80px) !important;
|
||||||
|
word-wrap: break-word !important;
|
||||||
|
overflow-wrap: break-word !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent horizontal overflow on slider and stage containers */
|
||||||
|
.logico-content-slider-widget,
|
||||||
|
.content-slider-wrapper,
|
||||||
|
.content-slider-container,
|
||||||
|
.content-slider,
|
||||||
|
.owl-stage-outer,
|
||||||
|
.owl-stage,
|
||||||
|
.owl-item,
|
||||||
|
.slider-item,
|
||||||
|
.slide-content,
|
||||||
|
.slide-content-inner {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force word wrapping and responsive spacing for heading and text */
|
||||||
|
.logico-content-slider-widget .content-slider-item-heading,
|
||||||
|
.logico-content-slider-widget .content-slider-item-heading span,
|
||||||
|
.logico-content-slider-widget .content-slider-item-heading .heading-content {
|
||||||
|
white-space: normal !important;
|
||||||
|
word-wrap: break-word !important;
|
||||||
|
overflow-wrap: break-word !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logico-content-slider-widget .content-slider-item-heading {
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logico-content-slider-widget .text-content {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: min(680px, 100%) !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive slider heights */
|
||||||
|
.logico-content-slider-widget .owl-stage-outer {
|
||||||
|
height: 800px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 840px) {
|
||||||
|
.logico-content-slider-widget .owl-stage-outer {
|
||||||
|
height: 600px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.logico-content-slider-widget .owl-stage-outer {
|
||||||
|
height: 520px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 660px) {
|
||||||
|
.logico-content-slider-widget .content-slider-item-heading {
|
||||||
|
font-size: clamp(20px, 7vw, 32px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}} />
|
||||||
<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-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" ref={containerRef}>
|
<div className="content-slider owl-carousel owl-theme nav-view-vertical nav-h-position-right nav-v-position-bottom owl-loaded owl-drag" ref={containerRef}>
|
||||||
<div className="owl-stage-outer" style={{ position: "relative", overflow: "hidden", height: "800px" }}>
|
<div className="owl-stage-outer" style={{ position: "relative", overflow: "hidden" }}>
|
||||||
<div className="owl-stage" style={{ position: "relative", width: "100%", height: "100%" }}>
|
<div className="owl-stage" style={{ position: "relative", width: "100%", height: "100%" }}>
|
||||||
|
|
||||||
{/* Slide 1 */}
|
{/* Slide 1 */}
|
||||||
|
|||||||
@@ -118,12 +118,14 @@ export default function Miles3() {
|
|||||||
}
|
}
|
||||||
.elementor-element-4add972 .elementor-icon svg {
|
.elementor-element-4add972 .elementor-icon svg {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
.elementor-element-74687fb > .elementor-widget-container { padding: 22px 0 0 0; }
|
.elementor-element-74687fb > .elementor-widget-container { padding: 22px 0 0 0; }
|
||||||
.elementor-element-74687fb .elementor-icon svg { height: 139px; }
|
.elementor-element-74687fb .elementor-icon svg { max-height: 139px; }
|
||||||
.elementor-element-fd9c57e .elementor-icon svg { height: 158px; }
|
.elementor-element-fd9c57e .elementor-icon svg { max-height: 158px; }
|
||||||
.elementor-element-fbb1628 > .elementor-widget-container { padding: 25px 0 0 0; }
|
.elementor-element-fbb1628 > .elementor-widget-container { padding: 25px 0 0 0; }
|
||||||
.elementor-element-fbb1628 .elementor-icon svg { height: 128px; }
|
.elementor-element-fbb1628 .elementor-icon svg { max-height: 128px; }
|
||||||
|
|
||||||
/* Card titles (First / Mid / Last Mile) */
|
/* Card titles (First / Mid / Last Mile) */
|
||||||
.elementor-element-d310968 > .elementor-widget-container,
|
.elementor-element-d310968 > .elementor-widget-container,
|
||||||
|
|||||||
@@ -12,23 +12,6 @@ const StrategyCanvas = dynamic(() => import("./StrategyCanvas"), { ssr: false })
|
|||||||
/** Center of each stage's scroll window (0…1). */
|
/** Center of each stage's scroll window (0…1). */
|
||||||
const CENTER = (i: number) => i / (N - 1);
|
const CENTER = (i: number) => i / (N - 1);
|
||||||
|
|
||||||
/** True only while a card's own opacity window is open (tiny buffer). Keeps
|
|
||||||
* not-yet-reached stage cards out of the DOM / off the compositor until their
|
|
||||||
* stage is on screen — no future workflow state is rendered before activation.
|
|
||||||
* Visually identical: a card outside its window is opacity:0 regardless. */
|
|
||||||
function useInWindow(mv: MotionValue<number>, threshold = 0.01): boolean {
|
|
||||||
// `mv` is an external mutable store (a MotionValue). useTransform `.set()`s its
|
|
||||||
// output synchronously while the PARENT renders, so a plain `.on("change") -> setState`
|
|
||||||
// updates this component during the parent's render (React warns). useSyncExternalStore
|
|
||||||
// is built for exactly this: it reads a snapshot and reconciles store-changes-during-
|
|
||||||
// render safely. The snapshot is a primitive boolean, so it never re-renders needlessly.
|
|
||||||
return useSyncExternalStore(
|
|
||||||
(onStoreChange) => mv.on("change", onStoreChange),
|
|
||||||
() => mv.get() > threshold,
|
|
||||||
() => mv.get() > threshold,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Persistent top rail: the 5 stages, current one highlighted. */
|
/** Persistent top rail: the 5 stages, current one highlighted. */
|
||||||
function StageRail({ active }: { active: number }) {
|
function StageRail({ active }: { active: number }) {
|
||||||
return (
|
return (
|
||||||
@@ -54,11 +37,13 @@ function StageCard({
|
|||||||
i,
|
i,
|
||||||
scroll,
|
scroll,
|
||||||
side,
|
side,
|
||||||
|
active,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
i: number;
|
i: number;
|
||||||
scroll: MotionValue<number>;
|
scroll: MotionValue<number>;
|
||||||
side: "left" | "right";
|
side: "left" | "right";
|
||||||
|
active: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const c = CENTER(i);
|
const c = CENTER(i);
|
||||||
@@ -67,8 +52,8 @@ function StageCard({
|
|||||||
const opacity = useTransform(scroll, [c - 0.1, c - 0.05, c + 0.05, c + 0.1], [0, 1, 1, 0]);
|
const opacity = useTransform(scroll, [c - 0.1, c - 0.05, c + 0.05, c + 0.1], [0, 1, 1, 0]);
|
||||||
const y = useTransform(scroll, [c - 0.1, c - 0.05], [34, 0]);
|
const y = useTransform(scroll, [c - 0.1, c - 0.05], [34, 0]);
|
||||||
const s = STAGES[i];
|
const s = STAGES[i];
|
||||||
// Only mount the card while its stage's cross-fade window is open.
|
// Only mount the card while its stage is active.
|
||||||
if (!useInWindow(opacity)) return null;
|
if (active !== i) return null;
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className={`dm-st-card-story is-${side}`}
|
className={`dm-st-card-story is-${side}`}
|
||||||
@@ -202,7 +187,7 @@ export default function StrategySection({ connected = false }: { connected?: boo
|
|||||||
detail. Each: short title + one lead line + a couple of key chips. */}
|
detail. Each: short title + one lead line + a couple of key chips. */}
|
||||||
|
|
||||||
{/* STAGE 01 — INPUT (green) */}
|
{/* STAGE 01 — INPUT (green) */}
|
||||||
<StageCard i={0} scroll={scroll} side="left">
|
<StageCard i={0} scroll={scroll} side="left" active={active}>
|
||||||
<h3 className="dm-st-pillar__title">Orders & riders enter the system</h3>
|
<h3 className="dm-st-pillar__title">Orders & riders enter the system</h3>
|
||||||
<p className="dm-st-anchor__lead">Orders are uploaded and matched against the available fleet, ready for assignment.</p>
|
<p className="dm-st-anchor__lead">Orders are uploaded and matched against the available fleet, ready for assignment.</p>
|
||||||
<div className="dm-st-anchor__chips">
|
<div className="dm-st-anchor__chips">
|
||||||
@@ -213,7 +198,7 @@ export default function StrategySection({ connected = false }: { connected?: boo
|
|||||||
</StageCard>
|
</StageCard>
|
||||||
|
|
||||||
{/* STAGE 02 — PARALLEL EXECUTION (purple) */}
|
{/* STAGE 02 — PARALLEL EXECUTION (purple) */}
|
||||||
<StageCard i={1} scroll={scroll} side="right">
|
<StageCard i={1} scroll={scroll} side="right" active={active}>
|
||||||
<h3 className="dm-st-pillar__title">Six strategies, evaluated in parallel</h3>
|
<h3 className="dm-st-pillar__title">Six strategies, evaluated in parallel</h3>
|
||||||
<p className="dm-st-anchor__lead">The AI runs every routing strategy at the same time — legacy baselines and MileTruth's unified engine.</p>
|
<p className="dm-st-anchor__lead">The AI runs every routing strategy at the same time — legacy baselines and MileTruth's unified engine.</p>
|
||||||
<div className="dm-st-anchor__chips">
|
<div className="dm-st-anchor__chips">
|
||||||
@@ -224,7 +209,7 @@ export default function StrategySection({ connected = false }: { connected?: boo
|
|||||||
</StageCard>
|
</StageCard>
|
||||||
|
|
||||||
{/* STAGE 03 — SMART OPTIMIZATION (blue) */}
|
{/* STAGE 03 — SMART OPTIMIZATION (blue) */}
|
||||||
<StageCard i={2} scroll={scroll} side="left">
|
<StageCard i={2} scroll={scroll} side="left" active={active}>
|
||||||
<h3 className="dm-st-pillar__title">Routes optimized & validated</h3>
|
<h3 className="dm-st-pillar__title">Routes optimized & validated</h3>
|
||||||
<p className="dm-st-anchor__lead">Every route is solved for distance, then checked against battery range and delivery SLAs.</p>
|
<p className="dm-st-anchor__lead">Every route is solved for distance, then checked against battery range and delivery SLAs.</p>
|
||||||
<div className="dm-st-anchor__chips">
|
<div className="dm-st-anchor__chips">
|
||||||
@@ -235,7 +220,7 @@ export default function StrategySection({ connected = false }: { connected?: boo
|
|||||||
</StageCard>
|
</StageCard>
|
||||||
|
|
||||||
{/* STAGE 04 — PERFORMANCE GRADING (orange) */}
|
{/* STAGE 04 — PERFORMANCE GRADING (orange) */}
|
||||||
<StageCard i={3} scroll={scroll} side="right">
|
<StageCard i={3} scroll={scroll} side="right" active={active}>
|
||||||
<h3 className="dm-st-pillar__title">Every strategy is scored</h3>
|
<h3 className="dm-st-pillar__title">Every strategy is scored</h3>
|
||||||
<p className="dm-st-anchor__lead">Each strategy is graded live on fulfillment, SLA compliance, efficiency and battery feasibility.</p>
|
<p className="dm-st-anchor__lead">Each strategy is graded live on fulfillment, SLA compliance, efficiency and battery feasibility.</p>
|
||||||
<div className="dm-st-anchor__chips">
|
<div className="dm-st-anchor__chips">
|
||||||
@@ -246,7 +231,7 @@ export default function StrategySection({ connected = false }: { connected?: boo
|
|||||||
</StageCard>
|
</StageCard>
|
||||||
|
|
||||||
{/* STAGE 05 — STRATEGY COMPARISON (red, hero) */}
|
{/* STAGE 05 — STRATEGY COMPARISON (red, hero) */}
|
||||||
<StageCard i={4} scroll={scroll} side="right">
|
<StageCard i={4} scroll={scroll} side="right" active={active}>
|
||||||
<h3 className="dm-st-pillar__title">Happier riders. Higher fulfillment.</h3>
|
<h3 className="dm-st-pillar__title">Happier riders. Higher fulfillment.</h3>
|
||||||
<p className="dm-st-anchor__lead">EV Aware wins — the best fulfillment with feasible, battery-safe routes for every rider.</p>
|
<p className="dm-st-anchor__lead">EV Aware wins — the best fulfillment with feasible, battery-safe routes for every rider.</p>
|
||||||
<div className="dm-st-anchor__chips">
|
<div className="dm-st-anchor__chips">
|
||||||
|
|||||||
Reference in New Issue
Block a user