update miletruth page and remove unwanted files
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import { Canvas, useFrame } from "@react-three/fiber";
|
||||
import { EffectComposer, Bloom, Vignette } from "@react-three/postprocessing";
|
||||
import { EffectComposer, Bloom } from "@react-three/postprocessing";
|
||||
import { KernelSize } from "postprocessing";
|
||||
import * as THREE from "three";
|
||||
import { C, WAYPOINTS } from "./theme";
|
||||
@@ -85,7 +85,7 @@ function LogisticsBrainCanvas({ progress, reduced = false, isMobile = false, act
|
||||
return (
|
||||
<Canvas
|
||||
flat
|
||||
dpr={[1, isMobile || reduced ? 1.3 : 1.7]}
|
||||
dpr={[1, isMobile || reduced ? 1.25 : 1.5]}
|
||||
camera={{ position: WAYPOINTS[0].pos, fov: 52, near: 0.1, far: 200 }}
|
||||
gl={{ antialias: !isMobile, powerPreference: "high-performance", alpha: false }}
|
||||
frameloop={active ? "always" : "never"}
|
||||
@@ -115,7 +115,6 @@ function LogisticsBrainCanvas({ progress, reduced = false, isMobile = false, act
|
||||
radius={isMobile ? 0.65 : 0.82}
|
||||
kernelSize={KernelSize.MEDIUM}
|
||||
/>
|
||||
<Vignette eskil={false} offset={0.22} darkness={0.62} />
|
||||
</EffectComposer>
|
||||
)}
|
||||
</Canvas>
|
||||
|
||||
@@ -77,7 +77,7 @@ function StoryCard({
|
||||
* progress value that drives the R3F scene, the camera spline and this overlay
|
||||
* in lockstep, so the whole thing reads as one continuous shot.
|
||||
*/
|
||||
export default function LogisticsBrainSection() {
|
||||
export default function LogisticsBrainSection({ connected = false }: { connected?: boolean } = {}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const progressRef = useRef(0);
|
||||
const scroll = useMotionValue(0);
|
||||
@@ -110,7 +110,7 @@ export default function LogisticsBrainSection() {
|
||||
mountIo.disconnect();
|
||||
}
|
||||
},
|
||||
{ rootMargin: "120% 0px" },
|
||||
{ rootMargin: "70% 0px" },
|
||||
);
|
||||
const activeIo = new IntersectionObserver(
|
||||
(entries) => setSceneActive(entries.some((e) => e.isIntersecting)),
|
||||
@@ -164,12 +164,6 @@ export default function LogisticsBrainSection() {
|
||||
const p5o = useTransform(scroll, [0.75, 0.78, 0.855, 0.875], [0, 1, 1, 0]);
|
||||
const p5y = useTransform(scroll, [0.75, 0.79], [26, 0]);
|
||||
|
||||
// Readability scrim behind the lower-left story pillars. The bright street-level
|
||||
// city (esp. the EV beat) leaves the text with no contrast, so we darken the
|
||||
// bottom-left corner across all pillar beats and fade it out for the intro hint
|
||||
// and the centered finale.
|
||||
const scrimOpacity = useTransform(scroll, [0.08, 0.13, 0.84, P.finale], [0, 1, 1, 0]);
|
||||
|
||||
const finaleOpacity = useTransform(scroll, [P.finale - 0.02, P.finale + 0.04], [0, 1]);
|
||||
const finaleY = useTransform(scroll, [P.finale - 0.02, P.finale + 0.06], [40, 0]);
|
||||
const taglineOpacity = useTransform(scroll, [P.finale + 0.04, P.finale + 0.1], [0, 1]);
|
||||
@@ -178,7 +172,7 @@ export default function LogisticsBrainSection() {
|
||||
const cost = useTransform(scroll, [P.finale, 0.97], [0, 18]);
|
||||
|
||||
return (
|
||||
<section ref={containerRef} className={`dm-lb is-${pinState}`} aria-label="Logistics Brain — one intelligent system">
|
||||
<section ref={containerRef} className={`dm-lb is-${pinState}${connected ? " is-connected" : ""}`} aria-label="Logistics Brain — one intelligent system">
|
||||
<div className="dm-lb-sticky">
|
||||
<div className="dm-lb-card">
|
||||
{mountScene && (
|
||||
@@ -186,9 +180,6 @@ export default function LogisticsBrainSection() {
|
||||
<LogisticsBrainCanvas progress={progressRef} reduced={reduced} isMobile={isMobile} active={sceneActive} />
|
||||
</div>
|
||||
)}
|
||||
<div className="dm-lb-vignette" aria-hidden />
|
||||
<motion.div className="dm-lb-scrim" style={{ opacity: scrimOpacity }} aria-hidden />
|
||||
|
||||
<div className="dm-lb-ui">
|
||||
{/* Persistent header: what this is + where we are in the workflow */}
|
||||
<motion.div className="dm-lb-top" style={{ opacity: headerOpacity }}>
|
||||
@@ -280,10 +271,6 @@ export default function LogisticsBrainSection() {
|
||||
<span className="dm-lb-logo__mark" />
|
||||
MileTruth
|
||||
</motion.div>
|
||||
<motion.p className="dm-lb-tagline" style={{ opacity: taglineOpacity }}>
|
||||
This isn't just software.<br />
|
||||
<strong>This is your logistics brain.</strong>
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -309,32 +296,36 @@ const styles = `
|
||||
}
|
||||
@media (max-width: 767px) { .dm-lb-card { inset: 10px !important; border-radius: 20px !important; } }
|
||||
|
||||
/* Connected mode (inside Workflow 2): flatten the card's bottom edge and flush it to
|
||||
the section's bottom so the Innovation card below butts directly against it, reading
|
||||
as one continuous container — mirrors the Optimisation → Performance seam in Workflow 1. */
|
||||
.dm-lb.is-connected .dm-lb-card {
|
||||
top: 16px !important; left: 16px !important; right: 16px !important; bottom: 0 !important;
|
||||
border-radius: 28px 28px 0 0 !important; border-bottom: none !important;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.dm-lb.is-connected .dm-lb-card {
|
||||
top: 10px !important; left: 10px !important; right: 10px !important; bottom: 0 !important;
|
||||
border-radius: 20px 20px 0 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dm-lb-canvas { position: absolute; inset: 0; z-index: 1; }
|
||||
.dm-lb-canvas canvas { display: block; }
|
||||
.dm-lb-vignette { position: absolute; inset: 0; z-index: 2; pointer-events: none;
|
||||
background: radial-gradient(130% 110% at 50% 45%, transparent 58%, rgba(3,4,10,0.9) 100%),
|
||||
linear-gradient(180deg, rgba(3,4,10,0.55) 0%, transparent 18%, transparent 70%, rgba(3,4,10,0.92) 100%); }
|
||||
|
||||
/* Lower-left readability scrim — keeps the story pillars legible over the bright
|
||||
street-level skyline. Anchored to the bottom-left corner where the pillars sit. */
|
||||
.dm-lb-scrim { position: absolute; inset: 0; z-index: 3; pointer-events: none;
|
||||
background:
|
||||
linear-gradient(to top right, rgba(3,4,10,0.94) 0%, rgba(3,4,10,0.74) 20%, rgba(3,4,10,0.34) 40%, transparent 60%),
|
||||
linear-gradient(0deg, rgba(3,4,10,0.6) 0%, transparent 38%); }
|
||||
@media (max-width: 767px) {
|
||||
.dm-lb-scrim { background: linear-gradient(0deg, rgba(3,4,10,0.92) 0%, rgba(3,4,10,0.55) 28%, transparent 52%); }
|
||||
}
|
||||
|
||||
.dm-lb-ui { position: absolute; inset: 0; z-index: 4; pointer-events: none;
|
||||
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif; color: #eaf2ff; }
|
||||
|
||||
/* ---- Persistent header: title + 6-step engine rail ---- */
|
||||
.dm-lb-top { position: absolute; top: clamp(16px, 3.5vh, 34px); left: 0; right: 0;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 0 16px; }
|
||||
/* ---- Persistent header: title + 6-step engine rail ----
|
||||
Offset the bar below the site's fixed navbar (~104px desktop / ~100px mobile once
|
||||
.dm-header-scrolled is active). The section pins full-viewport, so without this the
|
||||
eyebrow badge would sit under the navbar (z-index 10000) and get clipped. */
|
||||
.dm-lb-top { position: absolute; top: clamp(96px, 13vh, 128px); left: 0; right: 0; z-index: 5;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 0 16px; overflow: visible; }
|
||||
.dm-lb-eyebrow {
|
||||
display: inline-flex; align-items: center; gap: 8px; font-size: 11px; letter-spacing: 0.28em; text-transform: uppercase;
|
||||
color: #F2667A; padding: 6px 16px; border-radius: 999px; background: rgba(192,18,39,0.10);
|
||||
border: 1px solid rgba(226,53,66,0.32); backdrop-filter: blur(8px); white-space: nowrap; }
|
||||
display: inline-flex; align-items: center; gap: 8px; font-size: 11px; line-height: 1.35; letter-spacing: 0.28em; text-transform: uppercase;
|
||||
color: #F2667A; padding: 9px 18px; border-radius: 999px; background: rgba(192,18,39,0.10);
|
||||
border: 1px solid rgba(226,53,66,0.32); backdrop-filter: blur(8px); white-space: nowrap; overflow: visible; }
|
||||
.dm-lb-dot { width: 6px; height: 6px; border-radius: 50%; background: #E2354A; box-shadow: 0 0 10px #E2354A; }
|
||||
|
||||
.dm-lb-rail { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; max-width: 940px; }
|
||||
@@ -362,8 +353,9 @@ const styles = `
|
||||
.dm-lb-card-story { position: absolute; left: clamp(18px, 4vw, 56px); bottom: clamp(26px, 7vh, 64px);
|
||||
width: min(440px, 84vw); pointer-events: auto; will-change: opacity, transform;
|
||||
padding: 18px 20px; border-radius: 18px;
|
||||
background: rgba(14,8,10,0.6); border: 1px solid rgba(226,53,66,0.22);
|
||||
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
||||
background: rgba(14,8,10,0.9); border: 1px solid rgba(226,53,66,0.22);
|
||||
/* backdrop blur removed — this card cross-fades/translates on scroll, so the blur
|
||||
was recomputed every frame; a near-opaque fill keeps the look at no per-frame cost. */
|
||||
box-shadow: 0 24px 64px -30px rgba(0,0,0,0.92); }
|
||||
.dm-lb-card-story__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||||
.dm-lb-pillar__num { font-size: 12px; font-weight: 700; letter-spacing: 0.1em; color: #ffffff;
|
||||
@@ -418,8 +410,8 @@ const styles = `
|
||||
.dm-lb-finale { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 0 20px; }
|
||||
.dm-lb-kpis { display: flex; gap: clamp(14px, 2.4vw, 28px); margin-bottom: clamp(28px, 6vh, 56px); flex-wrap: wrap; justify-content: center; }
|
||||
.dm-lb-kpi { display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: clamp(150px, 18vw, 210px);
|
||||
padding: 22px 26px; border-radius: 18px; background: rgba(16,9,11,0.6); border: 1px solid rgba(226,53,66,0.28);
|
||||
backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); box-shadow: 0 24px 60px -28px rgba(0,0,0,0.9); }
|
||||
padding: 22px 26px; border-radius: 18px; background: rgba(16,9,11,0.9); border: 1px solid rgba(226,53,66,0.28);
|
||||
box-shadow: 0 24px 60px -28px rgba(0,0,0,0.9); }
|
||||
.dm-lb-kpi--green { border-color: rgba(34,197,94,0.4); }
|
||||
.dm-lb-kpi__num { font-size: clamp(38px, 5.5vw, 72px); font-weight: 800; line-height: 1; letter-spacing: -0.03em;
|
||||
color: #fff; text-shadow: 0 0 32px rgba(226,53,66,0.55), 0 0 12px rgba(192,18,39,0.5); }
|
||||
@@ -431,11 +423,6 @@ const styles = `
|
||||
.dm-lb-logo__mark { width: clamp(20px, 2.4vw, 30px); height: clamp(20px, 2.4vw, 30px); border-radius: 8px;
|
||||
background: conic-gradient(from 140deg, #E2354A, #C01227, #8A0E1F, #C8102E, #E2354A);
|
||||
box-shadow: 0 0 28px rgba(192,18,39,0.75); }
|
||||
.dm-lb-tagline { margin: 0; font-size: clamp(15px, 1.9vw, 24px); line-height: 1.4; font-weight: 400;
|
||||
color: rgba(240,224,226,0.78); letter-spacing: 0.02em; }
|
||||
.dm-lb-tagline strong { display: inline-block; margin-top: 4px; font-weight: 700; color: #fff;
|
||||
background: linear-gradient(90deg, #E2354A, #C01227, #C8102E); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
|
||||
text-transform: uppercase; letter-spacing: 0.06em; }
|
||||
|
||||
/* Hide the step titles on narrower screens so the rail stays a single tidy row of numbers. */
|
||||
@media (max-width: 1000px) {
|
||||
|
||||
48
src/components/logisticsbrain/math.test.ts
Normal file
48
src/components/logisticsbrain/math.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { clamp01, lerp, smoothstep, damp, seeded } from "./math";
|
||||
|
||||
// The Logistics Brain scene intentionally keeps its own copy of these helpers
|
||||
// (see the note in math.ts about Turbopack cross-folder const-arrow imports),
|
||||
// so they are covered independently from optimization/math.
|
||||
|
||||
describe("logisticsbrain/math — clamp01() & lerp()", () => {
|
||||
it("clamps to [0,1]", () => {
|
||||
expect(clamp01(-1)).toBe(0);
|
||||
expect(clamp01(0.5)).toBe(0.5);
|
||||
expect(clamp01(2)).toBe(1);
|
||||
});
|
||||
|
||||
it("interpolates linearly", () => {
|
||||
expect(lerp(0, 10, 0.5)).toBe(5);
|
||||
expect(lerp(-4, 4, 0.5)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("logisticsbrain/math — smoothstep()", () => {
|
||||
it("hits edges and midpoint", () => {
|
||||
expect(smoothstep(0, 1, 0)).toBe(0);
|
||||
expect(smoothstep(0, 1, 1)).toBe(1);
|
||||
expect(smoothstep(0, 1, 0.5)).toBeCloseTo(0.5, 10);
|
||||
});
|
||||
|
||||
it("guards equal edges (no NaN)", () => {
|
||||
expect(Number.isNaN(smoothstep(2, 2, 2))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("logisticsbrain/math — damp()", () => {
|
||||
it("is a no-op at dt=0 and converges with large lambda*dt", () => {
|
||||
expect(damp(5, 20, 4, 0)).toBe(5);
|
||||
expect(damp(0, 100, 50, 1)).toBeCloseTo(100, 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("logisticsbrain/math — seeded()", () => {
|
||||
it("is deterministic and bounded to [0,1)", () => {
|
||||
expect(seeded(42)).toBe(seeded(42));
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const v = seeded(i);
|
||||
expect(v).toBeGreaterThanOrEqual(0);
|
||||
expect(v).toBeLessThan(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user