diff --git a/public/css/site.css b/public/css/site.css index 2dc412e..e28f1e6 100644 --- a/public/css/site.css +++ b/public/css/site.css @@ -14856,7 +14856,7 @@ img:is([sizes=auto i]) { } .elementor-element.elementor-element-7da6646:not(.elementor-motion-effects-element-type-background) { - background-image: url("/images/bg-header-5.webp"); + background-image: url("/images/ev.webp"); background-position: center center; background-repeat: no-repeat; background-size: cover; diff --git a/public/images/bg-header-5.png b/public/images/bg-header-5.png new file mode 100644 index 0000000..d7ab42a Binary files /dev/null and b/public/images/bg-header-5.png differ diff --git a/public/images/bg-header-5.webp b/public/images/bg-header-5.webp index f26f55d..e69de29 100644 Binary files a/public/images/bg-header-5.webp and b/public/images/bg-header-5.webp differ diff --git a/public/images/ev fleet1.png b/public/images/ev fleet1.png new file mode 100644 index 0000000..48f4b80 Binary files /dev/null and b/public/images/ev fleet1.png differ diff --git a/src/app/page.tsx b/src/app/page.tsx index 31f4e22..49ed614 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -27,7 +27,7 @@ export default function Home() { - + diff --git a/src/app/solutions/page.tsx b/src/app/solutions/page.tsx index 39c8b3b..4438d8d 100644 --- a/src/app/solutions/page.tsx +++ b/src/app/solutions/page.tsx @@ -1,6 +1,8 @@ import React from "react"; import SolutionsHero, { type HeroSlide } from "@/components/sections/SolutionsHero"; import Miles3 from "@/components/sections/Miles3"; +import PlatformCapabilities from "@/components/sections/PlatformCapabilities"; +import DeliveredCounter from "@/components/sections/DeliveredCounter"; import EVSection, { type EVFeature } from "@/components/sections/EVSection"; import { SolutionsSectionsShell, @@ -96,7 +98,17 @@ export default function SolutionsPage() { so their Elementor markup is unaffected. */} - + {/* Platform Capabilities carousel is passed as children so it + renders INSIDE the Miles3 dark card, directly under the + first/mid/last mile grid — one continuous "What We Offer" + section, not a second one. Scoped to #pcap-root, so it does + not inherit anything from the shell's `.sol-*` stylesheet. */} + + {/* Big count-up stat over the dotted world map, then the + capabilities carousel — both inside the same dark card. */} + + + diff --git a/src/components/sections/AiCoreAnimation.tsx b/src/components/sections/AiCoreAnimation.tsx new file mode 100644 index 0000000..0bf4ab7 --- /dev/null +++ b/src/components/sections/AiCoreAnimation.tsx @@ -0,0 +1,368 @@ +"use client"; + +import React, { useState, useEffect } from "react"; + +interface AiCoreAnimationProps { + variant?: "compact" | "full"; + className?: string; +} + +export default function AiCoreAnimation({ + variant = "full", + className = "", +}: AiCoreAnimationProps) { + const isCompact = variant === "compact"; + + // Cycle phase for organic ecosystem simulation (0 to 10) + const [phase, setPhase] = useState(0); + + useEffect(() => { + const timer = setInterval(() => { + setPhase((prev) => (prev + 1) % 11); + }, 1250); // ~13.7s total continuous ecosystem cycle + + return () => clearInterval(timer); + }, []); + + if (isCompact) { + return ( +
+ + + + + + + +
+ ); + } + + // Ecosystem phase indicators + const isTrafficActive = phase >= 2 && phase <= 4; + const isNetworkReacting = phase >= 3 && phase <= 8; + const isBypassIlluminated = phase >= 4 && phase <= 8; + const isEtaImproved = phase >= 6; + const isDelivered = phase >= 8; + const isSlaProtected = phase >= 9; + + return ( +
+ + + + + {/* Micro Dot Grid Pattern for Clean Off-White Background */} + + + + + {/* Emerald Green Route Gradient */} + + + + + + + {/* Red Congestion Route Gradient */} + + + + + + {/* Apple Product Artwork Soft Shadow */} + + + + + + {/* ============================================================ + 1. OFF-WHITE CANVAS & AMBIENT TELEMETRY PARTICLES + ============================================================ */} + + + {/* Ambient Telemetry Particles Drifting Across Network */} + + + + + + + + + + + + {/* ============================================================ + 2. ABSTRACT CITY LOGISTICS NETWORK TOPOLOGY + ============================================================ */} + + {/* THIN GREY ARTERIAL HIGHWAYS & STREETS */} + + + + + {/* CONNECTING VERTICAL AVENUES */} + + + + + {/* DIAGONAL BYPASS CONNECTORS & RING EXPRESSWAY ARCS */} + + + + + {/* LIVE DATA SIGNALS FLOWING ACROSS CITY NETWORK */} + + + + + {/* ============================================================ + 3. DYNAMIC ROAD STATES (Congestion Red vs Emerald Bypass) + ============================================================ */} + + {/* CONGESTED ROAD SEGMENT (200, 220 -> 480, 220) */} + + + {/* ORGANIC TRAFFIC ALERT INDICATOR AT CONGESTION ZONE */} + {isTrafficActive && ( + + + + + )} + + {/* SUBTLE ILLUMINATED EMERALD GREEN BYPASS ROAD (200, 220 -> 340, 100 -> 480, 220) */} + {isBypassIlluminated && ( + + + {/* Ambient Green Glow Trail */} + + + )} + + {/* ============================================================ + 4. MINIMAL LOGISTICS FACILITIES (Warehouses, Hubs, Destinations) + ============================================================ */} + + {/* Warehouse 1 (North Dispatch at 80, 100) */} + + + + + + {/* Warehouse 2 (Main Dispatch at 80, 220) */} + + + + + + {/* Distribution Hub 1 (200, 220) */} + + + {/* Distribution Hub 2 (340, 100) */} + + + {/* Distribution Hub 3 (480, 220) */} + + + {/* Destination 1 (North Target at 600, 100) */} + + + {/* Destination 2 (Main Target at 600, 220) */} + + + + + + {/* Destination 3 (South Target at 600, 340) */} + + + {/* ============================================================ + 5. MULTIPLE DELIVERY VEHICLES MOVING SIMULTANEOUSLY + ============================================================ */} + + {/* VEHICLE 1 (PRIMARY FLEET UNIT - REROUTES UPON CONGESTION) */} + {(() => { + let vx = 80, vy = 220; + if (phase === 0) { vx = 80; vy = 220; } + else if (phase === 1) { vx = 140; vy = 220; } + else if (phase === 2) { vx = 200; vy = 220; } // Encounters traffic at Hub 1 (200, 220) + else if (phase === 3) { vx = 210; vy = 210; } // Network reacts + else if (phase === 4) { vx = 270; vy = 160; } // Smoothly curves onto Green Bypass Road + else if (phase === 5) { vx = 340; vy = 100; } // At Hub 2 Apex (340, 100) + else if (phase === 6) { vx = 410; vy = 160; } // Sweeping down to Hub 3 (480, 220) + else if (phase === 7) { vx = 480; vy = 220; } // At Hub 3 + else if (phase >= 8) { vx = 600; vy = 220; } // Delivered at Destination 2 + + return ( + + + + + + ); + })()} + + {/* VEHICLE 2 (NORTH ARTERIAL VAN - MOVES UNINTERRUPTED) */} + + + + + + {/* VEHICLE 3 (SOUTH COURIER VAN - MOVES UNINTERRUPTED) */} + + + + + + {/* VEHICLE 4 (CROSS-TOWN RELAY VAN) */} + + + + + {/* VEHICLE 5 (RING EXPRESSWAY SHUTTLE) */} + + + + + {/* ============================================================ + 6. APPLE-STYLE MINIMAL BADGES (ETA & SLA CONFIRMATION) + ============================================================ */} + + {/* Live ETA Badge (Top Right) */} + + + + + {isEtaImproved ? "41 min" : isTrafficActive ? "58 min" : "45 min"} + + {isEtaImproved && ( + + · Saved 17m + + )} + + + {/* SLA Protected Pill (Appears at Step 9 Post-Reroute) */} + {isSlaProtected && ( + + + + + ✓ SLA Protected (99.2%) + + + )} + +
+ ); +} diff --git a/src/components/sections/DeliveredCounter.tsx b/src/components/sections/DeliveredCounter.tsx new file mode 100644 index 0000000..46690d0 --- /dev/null +++ b/src/components/sections/DeliveredCounter.tsx @@ -0,0 +1,143 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; + +/* ============================================================ + DELIVERED COUNTER — Solutions page + + The big outlined stat that, in the Logico reference, sits between the + mile cards and the "what we do" carousel: a large count-up number over a + dotted world map, with a rotated caption alongside it. + + Rebuilt with Doormile branding — the site's red accent instead of teal, + the project's own dotted-map asset (public/images/bg-map.png), and a + logistics stat. It renders INSIDE the Miles3 dark card (as a sibling of + the carousel), so it reads as part of the same section. Transparent + surface; everything scoped to #dcount-root. + ============================================================ */ + +const TARGET = 24_836_517; +const DURATION = 2200; + +/** de-DE groups thousands with ".", matching the reference's 358.895.941. */ +function format(n: number): string { + return Math.round(n).toLocaleString("de-DE"); +} + +export default function DeliveredCounter() { + const rootRef = useRef(null); + const [val, setVal] = useState(0); + + useEffect(() => { + const el = rootRef.current; + if (!el) return; + const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + let raf = 0; + let fired = false; + + const io = new IntersectionObserver( + (entries) => { + const e = entries[0]; + if (!e.isIntersecting || fired) return; + fired = true; + io.disconnect(); + if (reduce) { setVal(TARGET); return; } + const start = performance.now(); + const tick = (now: number) => { + const p = Math.min(1, (now - start) / DURATION); + const eased = 1 - Math.pow(1 - p, 3); // easeOutCubic + setVal(TARGET * eased); + if (p < 1) raf = requestAnimationFrame(tick); + }; + raf = requestAnimationFrame(tick); + }, + { threshold: 0.35 } + ); + + io.observe(el); + return () => { io.disconnect(); cancelAnimationFrame(raf); }; + }, []); + + return ( + <> + + + + + + + + + ); + } + + return ( +
+ + + + + + + + + + + + + {/* Glass: the card is white, so depth comes from a barely-there + vertical wash rather than blur — anything stronger reads as dirt. */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* ============ CARD ============ */} + + + + + + + {/* ============ HEADER ============ */} + + + + MILETRUTH™ AI DECISION ENGINE + + + + + + + + + {PHASES[phase]} + {" · "} + {signals} live signals + + + + + {/* ============ COLUMN LABELS ============ */} + {[ + { x: 91, t: "LIVE INPUTS" }, + { x: 318, t: "NEURAL CORE" }, + { x: 555, t: "PROTECTED OUTCOME" }, + ].map((c) => ( + + {c.t} + + ))} + + {/* ============================================================ + LAYER 1 — LIVE INPUTS + ============================================================ */} + {INPUTS.map((inp, i) => { + const top = 96 + i * 37; + return ( + + + + + + + {inp.icon} + + + {inp.label} + + + + + ); + })} + + {/* Feeds converging on the core's input layer. Two chips per input + node, which is why the column reads as convergence. */} + {INPUTS.map((inp, i) => { + const cy = 96 + i * 37 + 13; + const ny = NET[0][Math.floor(i / 2)]; + const d = `M 160 ${cy} C 190 ${cy}, 202 ${ny}, ${NET_X[0] - 5} ${ny}`; + return ( + + {/* The wire is always drawn; only the signal on it moves. Without + the base line the dashes read as debris floating in the gap. */} + + + + ); + })} + + {/* ============================================================ + LAYER 2 — THE DECISION ENGINE + ============================================================ */} + + + + + {/* Synapses. The full mesh is drawn faint; only the path the engine + actually took this cycle is lit and carries signal. */} + {NET.slice(0, -1).map((layer, l) => + layer.map((y1, a) => + NET[l + 1].map((y2, b) => { + const live = LIVE_SET.has(liveKey(l, a, b)); + return ( + + ); + }) + ) + )} + {LIVE_PATH.map(([l, a, b]) => ( + + ))} + + {NET.map((layer, l) => + layer.map((y, i) => { + const on = ACTIVE_NODES.has(`${l}-${i}`); + return ( + + {on && ( + <> + {/* Lit, not neon — a soft halo at half opacity rather than + a bloom that turns the node into a lens flare. */} + + + + )} + + + ); + }) + )} + + {/* Decision leaving the core for the outcome column. */} + {[ + { d: "M 406 168 C 428 168, 432 138, 449 132" }, + { d: "M 406 232 C 428 232, 432 252, 449 258" }, + ].map((c, i) => ( + + + + + ))} + + {/* ============================================================ + LAYER 3 — THE PROTECTED OUTCOME + ============================================================ */} + + + {/* Rejected route in grey. It has to WANDER, not shadow the red one — + two near-identical curves read as a rendering artefact. */} + + + + + + + + {!still && ( + + + + + + + + )} + + {/* Legend below the panel. Labels sat on top of the curves before, + which made both unreadable. */} + + + + Original 58m + + + + MileTruth 41m + + + + {OUTCOMES.map((o, i) => { + const cy = 212 + i * 28; + return ( + + + + + + {o} + + + ); + })} + + {/* ============ KPI STRIP ============ */} + + + + + + {[ + { + x: 54, + v: "41 min", + l: "OPTIMISED ETA", + ic: ( + <> + + + + ), + }, + { + x: 275, + v: "17 min", + l: "TIME SAVED", + ic: , + }, + { + x: 496, + v: "99.2%", + l: "SLA PROTECTED", + ic: ( + <> + + + + ), + }, + ].map((k) => ( + + + + {k.ic} + + + {k.v} + + + {k.l} + + + ))} + + +
+ ); +} diff --git a/src/components/sections/SolutionsSections.tsx b/src/components/sections/SolutionsSections.tsx index 74bc3da..3b9b850 100644 --- a/src/components/sections/SolutionsSections.tsx +++ b/src/components/sections/SolutionsSections.tsx @@ -13,6 +13,8 @@ import { } from "framer-motion"; import { ScrollReveal, StaggerChildren, Magnetic } from "@/animations/Reveal"; import IndustryWorldMap from "./IndustryWorldMap"; +import NeuralField from "./NeuralField"; +import SolutionsMileTruthAnimation from "./SolutionsMileTruthAnimation"; /* ============================================================ DoorMile Solutions — Human-Centric Business Storytelling (v7) @@ -100,7 +102,7 @@ const IcClockSm =
{children}
); @@ -378,22 +393,27 @@ function FloatCard({ ); } -/** Thin marquee strip used as a transition out of a section's final visual — - * the Home MarqueeTicker treatment, scoped and rendered INSIDE a section. */ +/** Marquee ticker strip matching the Logico elementor marquee style */ function SolTicker({ items, tone }: { items: string[]; tone?: "light" }) { return (