283 lines
11 KiB
TypeScript
283 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useRef } from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import StrategySection from "../strategy/StrategySection";
|
|
|
|
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">
|
|
|
|
{/* ── 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 }} />
|
|
</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; }
|
|
}
|
|
`;
|