180 lines
7.6 KiB
TypeScript
180 lines
7.6 KiB
TypeScript
/**
|
|
* 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<PhaseKey, string> = {
|
|
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] },
|
|
];
|