Files
doormile_react/src/components/logisticsbrain/theme.ts
2026-06-02 23:59:51 +05:30

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] },
];