fix the mobile responsive
This commit is contained in:
@@ -24,23 +24,7 @@ function Counter({ mv }: { mv: MotionValue<number> }) {
|
||||
return <span ref={ref}>{Math.round(mv.get())}</span>;
|
||||
}
|
||||
|
||||
/** True only while a card's own opacity window is open (with a tiny buffer).
|
||||
* Lets us keep future/past story cards out of the DOM — and off the compositor
|
||||
* (each has `will-change`) — until their beat is actually on screen, so no
|
||||
* workflow state is rendered before activation. Visually identical, since a
|
||||
* card outside its window is opacity:0 anyway. */
|
||||
function useInWindow(mv: MotionValue<number>, threshold = 0.01): boolean {
|
||||
// `mv` is an external mutable store (a MotionValue). useTransform `.set()`s its
|
||||
// output synchronously while the PARENT renders, so a plain `.on("change") -> setState`
|
||||
// updates this component during the parent's render (React warns). useSyncExternalStore
|
||||
// is built for exactly this: it reads a snapshot and reconciles store-changes-during-
|
||||
// render safely. The snapshot is a primitive boolean, so it never re-renders needlessly.
|
||||
return useSyncExternalStore(
|
||||
(onStoreChange) => mv.on("change", onStoreChange),
|
||||
() => mv.get() > threshold,
|
||||
() => mv.get() > threshold,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/** Active step index from scroll progress (−1 before the engine starts). */
|
||||
function stepFromProgress(p: number): number {
|
||||
@@ -71,6 +55,8 @@ function StepRail({ active }: { active: number }) {
|
||||
|
||||
/** One cross-fading workflow card pinned to the lower-left. */
|
||||
function StoryCard({
|
||||
step,
|
||||
index,
|
||||
opacity,
|
||||
y,
|
||||
num,
|
||||
@@ -78,6 +64,8 @@ function StoryCard({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
step: number;
|
||||
index: number;
|
||||
opacity: MotionValue<number>;
|
||||
y: MotionValue<number>;
|
||||
num: string;
|
||||
@@ -85,8 +73,8 @@ function StoryCard({
|
||||
title: string;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
// Don't mount this beat's card until its cross-fade window opens.
|
||||
if (!useInWindow(opacity)) return null;
|
||||
// Don't mount this beat's card until its step is active.
|
||||
if (step !== index) return null;
|
||||
return (
|
||||
<motion.div className="dm-lb-card-story" style={{ opacity, y }}>
|
||||
<div className="dm-lb-card-story__head">
|
||||
@@ -230,7 +218,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
||||
</motion.div>
|
||||
|
||||
{/* STEP 01 — Generate Routes */}
|
||||
<StoryCard opacity={p1o} y={p1y} num="01" kicker="Generate Routes" title="We create many delivery plans at once">
|
||||
<StoryCard step={step} index={0} opacity={p1o} y={p1y} num="01" kicker="Generate Routes" title="We create many delivery plans at once">
|
||||
<div className="dm-lb-chips">
|
||||
{STRATEGIES.map((s) => (
|
||||
<span key={s} className="dm-lb-chip">{s}</span>
|
||||
@@ -240,7 +228,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
||||
</StoryCard>
|
||||
|
||||
{/* STEP 02 — Check Constraints (the EV paradox) */}
|
||||
<StoryCard opacity={p2o} y={p2y} num="02" kicker="Check Constraints" title="Every plan must respect real-world limits">
|
||||
<StoryCard step={step} index={1} opacity={p2o} y={p2y} num="02" kicker="Check Constraints" title="Every plan must respect real-world limits">
|
||||
<ul className="dm-lb-constraints">
|
||||
{CONSTRAINT_LIST.map((c) => (
|
||||
<li key={c.label}>
|
||||
@@ -254,7 +242,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
||||
</StoryCard>
|
||||
|
||||
{/* STEP 03 — Score & Compare (the leaderboard) */}
|
||||
<StoryCard opacity={p3o} y={p3y} num="03" kicker="Score & Compare" title="Each plan is scored by total delivery cost">
|
||||
<StoryCard step={step} index={2} opacity={p3o} y={p3y} num="03" kicker="Score & Compare" title="Each plan is scored by total delivery cost">
|
||||
<ul className="dm-lb-board">
|
||||
{STRATEGY_SCORES.map((s) => (
|
||||
<li key={s.name} className={s.win ? "is-win" : ""}>
|
||||
@@ -267,7 +255,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
||||
</StoryCard>
|
||||
|
||||
{/* STEP 04 — Guarantee On-Time */}
|
||||
<StoryCard opacity={p4o} y={p4y} num="04" kicker="Guarantee On-Time" title="Any plan even 1 minute late is rejected">
|
||||
<StoryCard step={step} index={3} opacity={p4o} y={p4y} num="04" kicker="Guarantee On-Time" title="Any plan even 1 minute late is rejected">
|
||||
<div className="dm-lb-sla">
|
||||
<span className="dm-lb-sla__badge">⏱️ On-time only</span>
|
||||
<span className="dm-lb-sla__x">✕ Late plan → dropped</span>
|
||||
@@ -276,7 +264,7 @@ export default function LogisticsBrainSection({ connected = false }: { connected
|
||||
</StoryCard>
|
||||
|
||||
{/* STEP 05 — Pick & Dispatch */}
|
||||
<StoryCard opacity={p5o} y={p5y} num="05" kicker="Pick & Dispatch" title="The winning plan is sent to the fleet">
|
||||
<StoryCard step={step} index={4} opacity={p5o} y={p5y} num="05" kicker="Pick & Dispatch" title="The winning plan is sent to the fleet">
|
||||
<div className="dm-lb-winner">✓ Multi-Trip selected — lowest cost, zero delays</div>
|
||||
<div className="dm-lb-chips">
|
||||
<span className="dm-lb-chip">EV Bikes</span>
|
||||
|
||||
Reference in New Issue
Block a user