diff --git a/doorven.svg b/doorven.svg new file mode 100644 index 0000000..62361a8 --- /dev/null +++ b/doorven.svg @@ -0,0 +1 @@ +Doormile \ No newline at end of file diff --git a/public/css/site.css b/public/css/site.css index 86af439..2dc412e 100644 --- a/public/css/site.css +++ b/public/css/site.css @@ -16678,6 +16678,20 @@ body:not(.rtl) .elementor-6585 .elementor-element.elementor-element-8899bdf { } } +/* Footer's own breakpoint above switches horizontal padding at 1020px, but + every other section's shared container (.sw-in, and the Hero's matching + padding) switches at 1024px — leaving a 1021-1024px viewport gap where + the footer sits 10px further in than everything above it (e.g. iPad + portrait, 1024px wide). This closes that gap without touching the + 1020px block above, which also carries unrelated vertical-rhythm rules + for other footer elements. */ +@media (max-width: 1024px) and (min-width: 1021px) { + .elementor-6585 .elementor-element.elementor-element-3cd920c { + --padding-left: 10px; + --padding-right: 10px; + } +} + @media(max-width: 840px) { .elementor-6585 .elementor-element.elementor-element-2631b42 { --flex-direction:row; diff --git a/public/doorven.svg b/public/doorven.svg new file mode 100644 index 0000000..62361a8 --- /dev/null +++ b/public/doorven.svg @@ -0,0 +1 @@ +Doormile \ No newline at end of file diff --git a/public/images/doormile-mark.png b/public/images/doormile-mark.png new file mode 100644 index 0000000..14b027d Binary files /dev/null and b/public/images/doormile-mark.png differ diff --git a/public/live-track.svg b/public/live-track.svg new file mode 100644 index 0000000..c3f7a33 --- /dev/null +++ b/public/live-track.svg @@ -0,0 +1 @@ +doormile \ No newline at end of file diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx index 54c329d..ab93f85 100644 --- a/src/app/blog/page.tsx +++ b/src/app/blog/page.tsx @@ -4,7 +4,7 @@ import BlogGrid from "@/components/sections/BlogGrid"; export const metadata = { title: "Blog – Doormile", - description: "Practical notes on delivery planning, EV fleet operations, route optimisation, charging, and last-mile performance from the Doormile team.", + description: "Practical notes on logistics planning, EV fleet operations, route intelligence, charging, and last-mile performance from the Doormile team.", }; export default function BlogPage() { diff --git a/src/app/doormile-wings/page.tsx b/src/app/doormile-wings/page.tsx index 11f3502..e681de3 100644 --- a/src/app/doormile-wings/page.tsx +++ b/src/app/doormile-wings/page.tsx @@ -11,13 +11,13 @@ import WingsRoadmap from "@/components/sections/WingsRoadmap"; export const metadata = { title: "Doormile Wings – Redefining Domestic Air Logistics Through AI", - description: "Doormile Wings links India's major regional capitals with AI-powered, same-day air logistics and zero-emission EV last-mile delivery — where quick commerce meets quick logistics.", + description: "Doormile Wings links India's major regional capitals with AI-powered, same-day air logistics and zero-emission EV last-mile transport — where quick commerce meets quick logistics.", alternates: { canonical: "https://doormile.com/doormile-wings", }, openGraph: { title: "Doormile Wings – Redefining Domestic Air Logistics Through AI", - description: "Same-day air logistics linking India's major regional capitals, powered by MileTruth™ AI and zero-emission EV last-mile delivery.", + description: "Same-day air logistics linking India's major regional capitals, powered by MileTruth™ AI and zero-emission EV last-mile transport.", url: "https://doormile.com/doormile-wings", siteName: "Doormile", type: "website", @@ -33,7 +33,7 @@ export const metadata = { twitter: { card: "summary_large_image", title: "Doormile Wings – Redefining Domestic Air Logistics Through AI", - description: "Same-day air logistics linking India's major regional capitals, powered by MileTruth™ AI and zero-emission EV last-mile delivery.", + description: "Same-day air logistics linking India's major regional capitals, powered by MileTruth™ AI and zero-emission EV last-mile transport.", images: ["https://doormile.com/images/wings-logo.png"], }, }; diff --git a/src/app/how-it-works/page.tsx b/src/app/how-it-works/page.tsx index 39c479d..6a477ea 100644 --- a/src/app/how-it-works/page.tsx +++ b/src/app/how-it-works/page.tsx @@ -4,7 +4,7 @@ import Experience3DLoader from "@/modules/how-it-works-3d/Experience3DLoader"; export const metadata = { title: "How It Works – Doormile", - description: "See how Doormile connects first, mid, and last mile into a seamless delivery experience powered by MileTruth™ AI.", + description: "See how Doormile connects first, mid, and last mile into a seamless end-to-end logistics experience powered by MileTruth™ AI.", }; export default function HowItWorksPage() { diff --git a/src/app/miletruth/page.tsx b/src/app/miletruth/page.tsx index 870f4ce..7cbb781 100644 --- a/src/app/miletruth/page.tsx +++ b/src/app/miletruth/page.tsx @@ -1,5 +1,6 @@ import React from "react"; import MileTruthHero from "../../components/sections/MileTruthHero"; +import MileTruthIntro from "../../components/sections/MileTruthIntro"; import Workflow1 from "../../components/sections/Workflow1"; import Workflow2 from "../../components/sections/Workflow2"; import Workflow4 from "../../components/sections/Workflow4"; @@ -7,7 +8,7 @@ import Workflow3Lazy from "../../components/sections/Workflow3Lazy"; export const metadata = { title: "MileTruth – Doormile", - description: "Optimizes every stage of the delivery journey for maximum efficiency. Explore the only AI built exclusively for logistics.", + description: "Optimizes every stage of the logistics journey for maximum efficiency. Explore the only AI built exclusively for logistics.", }; export default function MileTruthPage() { @@ -17,6 +18,7 @@ export default function MileTruthPage() {
+ diff --git a/src/app/solutions/page.tsx b/src/app/solutions/page.tsx index e6ca5ee..7443256 100644 --- a/src/app/solutions/page.tsx +++ b/src/app/solutions/page.tsx @@ -1,10 +1,10 @@ import React from "react"; import SolutionsHero from "@/components/sections/SolutionsHero"; -import IndustryStack from "@/components/sections/IndustryStack"; +import SolutionsWorkflow from "@/components/sections/SolutionsWorkflow"; export const metadata = { title: "Solutions – Doormile", - description: "Discover how Doormile's connected logistics platform serves diverse industries (FMCG, Pharma, and Enterprise) with tailored solutions.", + description: "See how Doormile's AI-powered Logistics Intelligence Platform works end-to-end — from pickup and AI vehicle selection to Quantum routing, live shipment tracking, door-to-door movement, MileTruth AI and real-time operations monitoring.", }; export default function SolutionsPage() { @@ -55,7 +55,7 @@ export default function SolutionsPage() {
- +
diff --git a/src/components/blog/BlogSidebar.tsx b/src/components/blog/BlogSidebar.tsx index b7ac0ba..8fbd4ea 100644 --- a/src/components/blog/BlogSidebar.tsx +++ b/src/components/blog/BlogSidebar.tsx @@ -72,7 +72,7 @@ export default function BlogSidebar({ current }: { current?: BlogPost }) { {/* CTA Card */}
-

Planning delivery routes?

+

Planning logistics routes?

Talk to us about reducing wasted distance, missed windows, and avoidable vehicle time. diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index ab4123b..8ad69e7 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -126,7 +126,7 @@ export default function Footer() {

-

Connecting businesses with fast, secure, smart deliveries.

+

Connecting businesses with fast, secure, intelligent logistics.

@@ -360,7 +360,7 @@ export default function Footer() {

- Delivered on time with no hassle. + Shipped on time with no hassle.

diff --git a/src/components/logisticsbrain/LogisticsBrainSection.tsx b/src/components/logisticsbrain/LogisticsBrainSection.tsx index c787d83..10b331e 100644 --- a/src/components/logisticsbrain/LogisticsBrainSection.tsx +++ b/src/components/logisticsbrain/LogisticsBrainSection.tsx @@ -243,18 +243,18 @@ export default function LogisticsBrainSection({ connected = false }: { connected - Scroll to see how every delivery is planned + Scroll to see how every shipment is planned {/* STEP 01 — Generate Routes (card anchored LEFT) */} - +
{STRATEGIES.map((s, i) => ( {s} ))}
-

6 different ways to deliver all 59 orders — generated in milliseconds.

+

6 different ways to move all 59 orders — generated in milliseconds.

{/* STEP 02 — Check Constraints (card anchored CENTER) */} @@ -268,11 +268,11 @@ export default function LogisticsBrainSection({ connected = false }: { connected ))} -

59/59 delivered vs 34/59 when battery limits are ignored

+

59/59 fulfilled vs 34/59 when battery limits are ignored

{/* STEP 03 — Score & Compare (card anchored RIGHT) */} - +
    {STRATEGY_SCORES.map((s) => (
  • @@ -290,7 +290,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected ⏱️ On-time only ✕ Late plan → dropped
-

We only keep plans that hit every promised delivery window.

+

We only keep plans that hit every promised arrival window.

{/* STEP 05 — Pick & Dispatch (card anchored CENTER, hero) */} @@ -308,7 +308,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
/59 - Orders Delivered + Orders Fulfilled
0 diff --git a/src/components/logisticsbrain/theme.ts b/src/components/logisticsbrain/theme.ts index 8d27a69..c503cb0 100644 --- a/src/components/logisticsbrain/theme.ts +++ b/src/components/logisticsbrain/theme.ts @@ -74,7 +74,7 @@ export const CONSTRAINT_LIST = [ { icon: "🔋", label: "Battery", note: "EV range & recharge stops" }, { icon: "📍", label: "Distance", note: "Total km per route" }, { icon: "📦", label: "Capacity", note: "Orders each vehicle can carry" }, - { icon: "⏱️", label: "Time / SLA", note: "Promised delivery windows" }, + { icon: "⏱️", label: "Time / SLA", note: "Promised arrival windows" }, ] as const; /** @@ -119,7 +119,7 @@ export type PhaseKey = keyof typeof P; * scroll-progress threshold where each step becomes the active one. */ export const ENGINE_STEPS = [ - { n: "01", at: P.routes, title: "Generate Routes", caption: "Many delivery plans created at once" }, + { n: "01", at: P.routes, title: "Generate Routes", caption: "Many route plans created at once" }, { n: "02", at: P.ev, title: "Check Constraints", caption: "Battery, distance, capacity & time" }, { n: "03", at: P.network, title: "Score & Compare", caption: "Every plan ranked by total cost" }, { n: "04", at: P.sla, title: "Guarantee On-Time", caption: "Late plans rejected automatically" }, @@ -129,9 +129,9 @@ export const ENGINE_STEPS = [ export const PHASE_CAPTIONS: Record = { birth: "Initializing the logistics brain", - routes: "Simulating delivery strategies", + routes: "Simulating routing strategies", ev: "Recalculating around EV constraints", - network: "Mapping the live delivery network", + network: "Mapping the live logistics network", sla: "Enforcing on-time SLA reliability", ecosystem: "Autonomous fleet in motion", finale: "", diff --git a/src/components/optimization/OptimizationSection.tsx b/src/components/optimization/OptimizationSection.tsx index f4724e9..8c80975 100644 --- a/src/components/optimization/OptimizationSection.tsx +++ b/src/components/optimization/OptimizationSection.tsx @@ -122,7 +122,7 @@ function WithoutPanelBody() {
  • Chaotic overlapping routes
  • Duplicate & idle trips
  • 8 vehicles required
  • -
  • 23 delivery delays
  • +
  • 23 shipment delays
  • +18% cost overrun
  • @@ -141,7 +141,7 @@ function WithPanelBody() {
  • Optimized route clusters
  • Intelligent vehicle assignment
  • Multi-trip & EV planning
  • -
  • Zero delivery delays
  • +
  • Zero shipment delays
  • 18% cost saved
  • Carbon footprint reduced
  • @@ -318,7 +318,7 @@ export default function OptimizationSection() {

    AI Logistics Optimization Engine

    - Watch Doormile's AI engine transform chaotic logistics into precision-optimized delivery networks — reducing distance, fleet size, delays, and cost. + Watch Doormile's AI engine transform chaotic operations into precision-optimized logistics networks — reducing distance, fleet size, delays, and cost.

    @@ -461,7 +461,7 @@ export default function OptimizationSection() { viewport={{ once: true }} transition={{ duration: 0.7, delay: 0.12 }} > - Watch Doormile's AI engine transform chaotic logistics into precision-optimized delivery networks — reducing distance, fleet size, delays, and cost in real time. + Watch Doormile's AI engine transform chaotic operations into precision-optimized logistics networks — reducing distance, fleet size, delays, and cost in real time. {/* Workflow step indicators */} diff --git a/src/components/sections/ConnectedLogistics.tsx b/src/components/sections/ConnectedLogistics.tsx index c1ec901..b5116ba 100644 --- a/src/components/sections/ConnectedLogistics.tsx +++ b/src/components/sections/ConnectedLogistics.tsx @@ -259,7 +259,7 @@ export default function ConnectedLogistics() {
    -

    Learn from every delivery. Get smarter with every mile traveled.

    +

    Learn from every shipment. Get smarter with every mile traveled.

    diff --git a/src/components/sections/EVLogisticSection.tsx b/src/components/sections/EVLogisticSection.tsx index 82dbb63..05968f1 100644 --- a/src/components/sections/EVLogisticSection.tsx +++ b/src/components/sections/EVLogisticSection.tsx @@ -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: ( @@ -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: ( @@ -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: ( diff --git a/src/components/sections/EVSection.tsx b/src/components/sections/EVSection.tsx index 19c0f5a..38f1743 100644 --- a/src/components/sections/EVSection.tsx +++ b/src/components/sections/EVSection.tsx @@ -135,7 +135,7 @@ const DEFAULT_FEATURES: EVFeature[] = [ ), 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, diff --git a/src/components/sections/HowItWorksHero.tsx b/src/components/sections/HowItWorksHero.tsx index ccc454a..282b5af 100644 --- a/src/components/sections/HowItWorksHero.tsx +++ b/src/components/sections/HowItWorksHero.tsx @@ -301,8 +301,8 @@ export default function HowItWorksHero() {

    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.

    @@ -341,8 +341,8 @@ export default function HowItWorksHero() {

    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.

    diff --git a/src/components/sections/IndexHero.tsx b/src/components/sections/IndexHero.tsx index 753bcec..158a26a 100644 --- a/src/components/sections/IndexHero.tsx +++ b/src/components/sections/IndexHero.tsx @@ -197,7 +197,7 @@ export default function IndexHero() {
    -

    Stop managing three separate logistics services. Doormile unifies first, mid and last mile into a single intelligent delivery system powered by MileTruth™ AI.

    +

    Stop managing three separate logistics services. Doormile unifies first, mid and last mile into a single intelligent logistics system powered by MileTruth™ AI.

    diff --git a/src/components/sections/IndustrySolutions.tsx b/src/components/sections/IndustrySolutions.tsx index 400a659..0228d89 100644 --- a/src/components/sections/IndustrySolutions.tsx +++ b/src/components/sections/IndustrySolutions.tsx @@ -189,7 +189,7 @@ export default function IndustrySolutions() {
    Doormile Solutions
    @@ -246,7 +246,7 @@ export default function IndustrySolutions() {
    @@ -303,13 +303,13 @@ export default function IndustrySolutions() {

    - High-value shipments with complex delivery requirements. + High-value shipments with complex logistics requirements.

    Challenges
    • Appointment scheduling coordination
    • -
    • White-glove delivery standards
    • +
    • White-glove logistics standards
    • Multi-location routing complexity
    diff --git a/src/components/sections/IndustryStack.tsx b/src/components/sections/IndustryStack.tsx index bc90e8d..d54e318 100644 --- a/src/components/sections/IndustryStack.tsx +++ b/src/components/sections/IndustryStack.tsx @@ -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.", ], }, diff --git a/src/components/sections/MileTruthCanvas.tsx b/src/components/sections/MileTruthCanvas.tsx new file mode 100644 index 0000000..f758c11 --- /dev/null +++ b/src/components/sections/MileTruthCanvas.tsx @@ -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(null); + const haloRef = useRef(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 ( + + + + + + + + + + + ); +} + +function Connector({ a, b, reduced, index }: { a: Node; b: Node; reduced: boolean; index: number }) { + const packetRef = useRef(null); + const lineMatRef = useRef(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 ( + + + + + + + + + + ); +} + +function SlowSpin({ reduced, children }: { reduced: boolean; children: React.ReactNode }) { + const group = useRef(null); + useFrame((_state, dt) => { + if (reduced || !group.current) return; + group.current.rotation.y += dt * 0.035; + }); + return {children}; +} + +export default function MileTruthCanvas({ + reduced = false, + isMobile = false, + active = true, +}: { + reduced?: boolean; + isMobile?: boolean; + active?: boolean; +}) { + return ( + + + + + {LINKS.map(([a, b], i) => ( + + ))} + {NODES.map((n, i) => ( + + ))} + + {!isMobile && ( + + )} + + + + + ); +} diff --git a/src/components/sections/MileTruthHero.tsx b/src/components/sections/MileTruthHero.tsx index 93ca678..2455148 100644 --- a/src/components/sections/MileTruthHero.tsx +++ b/src/components/sections/MileTruthHero.tsx @@ -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() {
    -

    Optimizes every stage of the delivery journey for maximum efficiency.

    +

    Optimizes every stage of the logistics journey for maximum efficiency.

    diff --git a/src/components/sections/MileTruthIntro.tsx b/src/components/sections/MileTruthIntro.tsx new file mode 100644 index 0000000..5ec3198 --- /dev/null +++ b/src/components/sections/MileTruthIntro.tsx @@ -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(null); + const sweepRef = useRef(null); + const edgeRefs = useRef<(SVGLineElement | null)[]>([]); + const survivorEdgeRefs = useRef<(SVGLineElement | null)[]>([]); + const nodeRefs = useRef<(SVGCircleElement | null)[]>([]); + const haloRef = useRef(null); + const pulseRef = useRef(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 ( +