/** * Shared design tokens, scroll-phase thresholds and the cinematic camera path * for the "Logistics Brain" scroll-storytelling section. * * The whole experience is driven by ONE normalized scroll progress value * (0 → 1) shared between GSAP ScrollTrigger, the R3F render loop and the * Framer-Motion HTML overlay — so every layer stays perfectly in sync and the * journey reads as one continuous shot with no scene cuts. */ /** * Doormile brand palette: near-black + brand red, with green reserved for the * "delivered / optimized" success state. * * NOTE ON TOKEN NAMES: the keys below (cyan/blue/violet/…) are the original * cool-palette slot names, kept ONLY so the many `C.cyan`-style references in * the scene files don't all have to change. Their *values* are now brand reds * and neutral steels — read the inline comment, not the key name. New code * should prefer the semantic aliases (red / redBright / redSoft / steel). */ export const C = { bg: "#08080c", // near-black scene background red: "#C01227", // PRIMARY brand red cyan: "#E2354A", // redBright — main glowing red accent (roads, rings, brain, scooter) blue: "#7E1420", // crimson — dim deep-red structural glow (grid, links, soft glows) sky: "#F2667A", // redSoft — light coral highlight points magenta: "#C8102E", // secondary brand red violet: "#3c3c46", // steel — neutral grey structure (losing routes, secondary shell) purple: "#6a6a76", // steelLight — lighter neutral grey steel: "#43434d", // losing-route grey gray: "#7c7c86", // losing-route grey (lighter) green: "#22C55E", // success / delivered (unchanged) amber: "#F59E0B", // transitional (battery mid-charge) (unchanged) white: "#FFFFFF", } as const; /** * Palette for the six competing route simulations. Index 0 (Multi-Trip) is the * winner and gets the brand red so it stands out; the five losing strategies * are neutral greys that recede, then fade out entirely. */ export const ROUTE_COLORS = [ C.red, // 0 — the eventual winner (brand red) C.steel, C.gray, C.steel, C.gray, C.steel, ] as const; /** * The six strategies the "Parallel Universe Engine" benchmarks (index-aligned * to ROUTE_COLORS). Index 0 = Multi-Trip is the winner — it's also what solves * the EV paradox (59/59 vs 34/59), tying the routes beat to the EV beat. */ export const STRATEGIES = [ "Multi-Trip", "Proximity", "Balanced", "Fuel Saver", "EV-Aware", "Time-Aware", ] as const; export const WINNER_INDEX = 0; /** The constraints the VRP solver optimizes together (Mathematical Precision). */ export const CONSTRAINTS = ["Capacity", "Distance", "Battery", "Traffic", "SLA"] as const; /** Plain-language constraints shown in the EV / constraints beat. */ 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" }, ] as const; /** * Scored leaderboard for the "Score & Compare" beat. Higher = better total * delivery cost/outcome. Multi-Trip wins; EV-Aware (the naive baseline) scores * worst — it's the one that only delivered 34/59 in the EV paradox beat. */ export const STRATEGY_SCORES: { name: string; score: number; win?: boolean }[] = [ { name: "Multi-Trip", score: 98, win: true }, { name: "Time-Aware", score: 90 }, { name: "Balanced", score: 84 }, { name: "Proximity", score: 76 }, { name: "Fuel Saver", score: 68 }, { name: "EV-Aware", score: 58 }, ]; /** Layout anchors (world units). */ export const BRAIN_Y = 3.0; // depot beacon hovers low over the map centre export const CITY_RADIUS = 19; /** * Scroll-phase thresholds (normalized 0 → 1). These are *story beats*, not hard * cuts — every element cross-fades across a window around its beat so the scene * morphs continuously. */ export const P = { birth: 0.0, // glowing brain + city form, energy roads grow routes: 0.13, // six route simulations race through the city ev: 0.28, // EV battery drains → recalc → charging station → green route network: 0.44, // camera rises to eagle-eye; full live network sla: 0.6, // holographic SLA clock; delayed routes dissolve ecosystem: 0.74, // pull back; warehouses + fleet come alive finale: 0.88, // fly up; stats + logo + tagline reveal } as const; export type PhaseKey = keyof typeof P; /** * The six steps of the routing engine, in plain language — the spine of the * on-screen story. The persistent step rail and the per-beat content cards both * read from this so a non-technical viewer can follow the workflow. `at` is the * 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: "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" }, { n: "05", at: P.ecosystem, title: "Pick & Dispatch", caption: "Best plan sent to the fleet" }, { n: "06", at: P.finale, title: "Delivered", caption: "Real business results" }, ] as const; export const PHASE_CAPTIONS: Record = { birth: "Initializing the logistics brain", routes: "Simulating delivery strategies", ev: "Recalculating around EV constraints", network: "Mapping the live delivery network", sla: "Enforcing on-time SLA reliability", ecosystem: "Autonomous fleet in motion", finale: "", }; export function captionFor(p: number): string { if (p >= P.finale) return PHASE_CAPTIONS.finale; if (p >= P.ecosystem) return PHASE_CAPTIONS.ecosystem; if (p >= P.sla) return PHASE_CAPTIONS.sla; if (p >= P.network) return PHASE_CAPTIONS.network; if (p >= P.ev) return PHASE_CAPTIONS.ev; if (p >= P.routes) return PHASE_CAPTIONS.routes; return PHASE_CAPTIONS.birth; } /** * Camera waypoints sampled by scroll progress. Each has an eye position and a * look-at target. The rig lerps between adjacent waypoints (smoothstep eased) * and then exponentially damps toward the result — giving a smooth spline-like * dolly with no jerk on fast scroll. */ export type Waypoint = { at: number; pos: [number, number, number]; look: [number, number, number]; }; /** * NEAR-STATIC MAP VIEW. The camera holds a consistent elevated 3/4 "control * room" angle on the depot/map the whole way through — only small, slow shifts * between beats (≈70% less travel than a cinematic fly-through). The story is * told by the OBJECTS on the map (nodes, candidate routes, scores, vehicles), * not by camera moves. Keep these positions clustered; large deltas reintroduce * the "city fly-through" feel we're removing. */ export const WAYPOINTS: Waypoint[] = [ { at: 0.0, pos: [0, 20, 27], look: [0, 1.4, 0] }, // establish the map { at: 0.13, pos: [-2.5, 20, 26.5], look: [-0.5, 1.2, 0] }, // routes generate { at: 0.28, pos: [3, 18.5, 25.5], look: [0.6, 0.8, -0.8] }, // constraints — ease toward street { at: 0.44, pos: [0, 23, 24.5], look: [0, 1.0, 0] }, // score — lift slightly to read labels { at: 0.6, pos: [2.5, 21, 25.5], look: [0, 1.2, 0] }, // SLA — small lateral drift { at: 0.74, pos: [-2, 20, 26.5], look: [0, 1.2, 0] }, // dispatch { at: 0.88, pos: [0, 22, 25.5], look: [0, 1.8, 0] }, // results { at: 1.0, pos: [0, 21.5, 25.5], look: [0, 2.0, 0] }, ];