fix padding gap

This commit is contained in:
2026-06-05 13:58:14 +05:30
parent 3a16bf9267
commit 7fb97a9ca6
50 changed files with 19296 additions and 142142 deletions

View File

@@ -5,7 +5,7 @@ import dynamic from "next/dynamic";
import { motion, useMotionValue, useTransform, type MotionValue } from "framer-motion";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { P, STRATEGIES, ENGINE_STEPS, CONSTRAINT_LIST, STRATEGY_SCORES } from "./theme";
import { P, STRATEGIES, WINNER_INDEX, ENGINE_STEPS, CONSTRAINT_LIST, STRATEGY_SCORES } from "./theme";
const LogisticsBrainCanvas = dynamic(() => import("./LogisticsBrainCanvas"), { ssr: false });
@@ -53,12 +53,21 @@ function StepRail({ active }: { active: number }) {
);
}
/** One cross-fading workflow card pinned to the lower-left. */
/**
* One workflow card that travels through the story. The outer anchor pins it to
* its stage position (Left / Center / Right / Center-Hero); the inner motion card
* slides + scales into that anchor in lockstep with scroll, so as the camera moves
* through the stages the card visibly moves with the narrative instead of sitting
* fixed in one corner.
*/
function StoryCard({
step,
index,
pos,
opacity,
y,
x,
scale,
num,
kicker,
title,
@@ -66,8 +75,11 @@ function StoryCard({
}: {
step: number;
index: number;
pos: "left" | "center" | "right" | "hero";
opacity: MotionValue<number>;
y: MotionValue<number>;
x: MotionValue<number>;
scale: MotionValue<number>;
num: string;
kicker: string;
title: string;
@@ -76,14 +88,16 @@ function StoryCard({
// 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">
<span className="dm-lb-pillar__num">{num}</span>
<span className="dm-lb-pillar__kicker">{kicker}</span>
</div>
<h3 className="dm-lb-pillar__title">{title}</h3>
{children}
</motion.div>
<div className={`dm-lb-card-anchor is-${pos}`}>
<motion.div className="dm-lb-card-story" style={{ opacity, y, x, scale }}>
<div className="dm-lb-card-story__head">
<span className="dm-lb-pillar__num">{num}</span>
<span className="dm-lb-pillar__kicker">{kicker}</span>
</div>
<h3 className="dm-lb-pillar__title">{title}</h3>
{children}
</motion.div>
</div>
);
}
@@ -182,6 +196,22 @@ export default function LogisticsBrainSection({ connected = false }: { connected
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]);
// Horizontal slide + scale per beat — same windows as the opacity above, so the
// card glides between its stage anchors (Left → Center → Right → Left → Center-Hero)
// in lockstep with the camera. Each card enters from the direction of the previous
// stage and drifts toward the next as it leaves, reading as one continuous travel.
const p1x = useTransform(scroll, [0.135, 0.165, 0.255, 0.275], [-52, 0, 0, 52]);
const p1s = useTransform(scroll, [0.135, 0.165, 0.255, 0.275], [0.965, 1, 1, 0.965]);
const p2x = useTransform(scroll, [0.29, 0.32, 0.415, 0.435], [-52, 0, 0, 52]);
const p2s = useTransform(scroll, [0.29, 0.32, 0.415, 0.435], [0.965, 1, 1, 0.965]);
const p3x = useTransform(scroll, [0.45, 0.48, 0.575, 0.595], [-52, 0, 0, -52]);
const p3s = useTransform(scroll, [0.45, 0.48, 0.575, 0.595], [0.965, 1, 1, 0.965]);
const p4x = useTransform(scroll, [0.61, 0.64, 0.715, 0.735], [52, 0, 0, 52]);
const p4s = useTransform(scroll, [0.61, 0.64, 0.715, 0.735], [0.965, 1, 1, 0.965]);
// Hero (final selection): scales up a touch and holds center as it settles.
const p5x = useTransform(scroll, [0.75, 0.78, 0.855, 0.875], [-52, 0, 0, 0]);
const p5s = useTransform(scroll, [0.75, 0.78, 0.855, 0.875], [0.97, 1.05, 1.05, 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]);
@@ -217,18 +247,18 @@ export default function LogisticsBrainSection({ connected = false }: { connected
<span className="dm-lb-arrow"></span>
</motion.div>
{/* STEP 01 — Generate Routes */}
<StoryCard step={step} index={0} opacity={p1o} y={p1y} num="01" kicker="Generate Routes" title="We create many delivery plans at once">
{/* STEP 01 — Generate Routes (card anchored LEFT) */}
<StoryCard step={step} index={0} pos="left" opacity={p1o} y={p1y} x={p1x} scale={p1s} 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>
{STRATEGIES.map((s, i) => (
<span key={s} className={`dm-lb-chip${i === WINNER_INDEX ? " dm-lb-chip--active" : ""}`}>{s}</span>
))}
</div>
<p className="dm-lb-pillar__foot">6 different ways to deliver all 59 orders generated in milliseconds.</p>
</StoryCard>
{/* STEP 02 — Check Constraints (the EV paradox) */}
<StoryCard step={step} index={1} opacity={p2o} y={p2y} num="02" kicker="Check Constraints" title="Every plan must respect real-world limits">
{/* STEP 02 — Check Constraints (card anchored CENTER) */}
<StoryCard step={step} index={1} pos="center" opacity={p2o} y={p2y} x={p2x} scale={p2s} 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}>
@@ -241,8 +271,8 @@ export default function LogisticsBrainSection({ connected = false }: { connected
<p className="dm-lb-pillar__stat"><strong>59/59</strong> delivered <em>vs 34/59 when battery limits are ignored</em></p>
</StoryCard>
{/* STEP 03 — Score & Compare (the leaderboard) */}
<StoryCard step={step} index={2} opacity={p3o} y={p3y} num="03" kicker="Score &amp; Compare" title="Each plan is scored by total delivery cost">
{/* STEP 03 — Score & Compare (card anchored RIGHT) */}
<StoryCard step={step} index={2} pos="right" opacity={p3o} y={p3y} x={p3x} scale={p3s} num="03" kicker="Score &amp; 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" : ""}>
@@ -254,8 +284,8 @@ export default function LogisticsBrainSection({ connected = false }: { connected
</ul>
</StoryCard>
{/* STEP 04 — Guarantee On-Time */}
<StoryCard step={step} index={3} opacity={p4o} y={p4y} num="04" kicker="Guarantee On-Time" title="Any plan even 1 minute late is rejected">
{/* STEP 04 — Guarantee On-Time (card anchored LEFT) */}
<StoryCard step={step} index={3} pos="left" opacity={p4o} y={p4y} x={p4x} scale={p4s} 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>
@@ -263,8 +293,8 @@ export default function LogisticsBrainSection({ connected = false }: { connected
<p className="dm-lb-pillar__foot">We only keep plans that hit every promised delivery window.</p>
</StoryCard>
{/* STEP 05 — Pick & Dispatch */}
<StoryCard step={step} index={4} opacity={p5o} y={p5y} num="05" kicker="Pick &amp; Dispatch" title="The winning plan is sent to the fleet">
{/* STEP 05 — Pick & Dispatch (card anchored CENTER, hero) */}
<StoryCard step={step} index={4} pos="hero" opacity={p5o} y={p5y} x={p5x} scale={p5s} num="05" kicker="Pick &amp; 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>
@@ -381,62 +411,76 @@ const styles = `
.dm-lb-arrow { font-size: 18px; animation: dmLbBob 1.8s ease-in-out infinite; }
@keyframes dmLbBob { 0%,100% { transform: translateY(0); opacity: 0.5; } 50% { transform: translateY(6px); opacity: 1; } }
/* ---- Lower-left workflow card (glass panel, cross-fades per step) ---- */
.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;
/* ---- Story card: a premium light-glass panel that TRAVELS between stage
anchors. The anchor pins the stage position; the inner card slides/scales into
it (Left → Center → Right → Left → Center-Hero) in lockstep with scroll. ---- */
.dm-lb-card-anchor { position: absolute; bottom: clamp(26px, 7vh, 64px); z-index: 6; pointer-events: none; }
.dm-lb-card-anchor.is-left { left: clamp(18px, 4vw, 56px); }
.dm-lb-card-anchor.is-right { right: clamp(18px, 4vw, 56px); }
.dm-lb-card-anchor.is-center,
.dm-lb-card-anchor.is-hero { left: 50%; transform: translateX(-50%); }
/* Hero (final selection) sits a little higher + centred so it reads as the payoff. */
.dm-lb-card-anchor.is-hero { bottom: clamp(40px, 9vh, 92px); }
.dm-lb-card-story { position: relative; width: min(440px, 84vw); pointer-events: auto;
will-change: opacity, transform; transform-origin: bottom center;
padding: 18px 20px; border-radius: 18px;
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); }
/* Premium light glass — clean SaaS surface, brand red used only as a top accent. */
background: rgba(255,255,255,0.94);
border: 1px solid rgba(15,23,42,0.08); border-top: 3px solid #C01227;
box-shadow: 0 28px 70px -34px rgba(15,23,42,0.45); }
.dm-lb-card-anchor.is-hero .dm-lb-card-story { width: min(480px, 88vw);
box-shadow: 0 38px 92px -34px rgba(192,18,39,0.4); }
.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;
background: linear-gradient(135deg, #E2354A, #C01227); border-radius: 7px; padding: 3px 8px; }
.dm-lb-pillar__kicker { font-size: clamp(11px, 1.1vw, 13px); font-weight: 700; letter-spacing: 0.18em;
text-transform: uppercase; color: #F2667A; }
.dm-lb .dm-lb-pillar__title { margin: 0 0 12px !important; padding: 0 !important; color: #fbf5f6 !important;
text-transform: uppercase; color: #C01227; }
.dm-lb .dm-lb-pillar__title { margin: 0 0 12px !important; padding: 0 !important; color: #0f172a !important;
font-weight: 700 !important; text-transform: none !important; letter-spacing: -0.015em !important;
font-size: clamp(17px, 1.9vw, 24px) !important; line-height: 1.18 !important;
text-shadow: 0 0 30px rgba(192,18,39,0.3) !important; }
.dm-lb-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.dm-lb-chip { font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em; color: #f1dadd;
padding: 4px 11px; border-radius: 999px; background: rgba(192,18,39,0.12);
border: 1px solid rgba(226,53,66,0.30); white-space: nowrap; }
.dm-lb-pillar__foot { margin: 0; font-size: clamp(12px, 1.1vw, 13.5px); line-height: 1.45; color: rgba(236,224,226,0.72); }
.dm-lb-pillar__stat { margin: 6px 0 0; font-size: clamp(12.5px, 1.2vw, 15px); color: rgba(236,224,226,0.78); }
.dm-lb-pillar__stat strong { color: #4ade80; font-weight: 800; font-size: 1.25em; text-shadow: 0 0 20px rgba(34,197,94,0.5); }
.dm-lb-pillar__stat em { font-style: normal; color: rgba(230,218,220,0.55); }
font-size: clamp(17px, 1.9vw, 24px) !important; line-height: 1.18 !important; }
.dm-lb-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 10px; }
/* Strategy pills — white pills, soft border + light shadow, brand-red active state. */
.dm-lb-chip { font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em; color: #334155;
padding: 5px 12px; border-radius: 999px; background: #ffffff;
border: 1px solid rgba(15,23,42,0.1); box-shadow: 0 1px 2px rgba(15,23,42,0.06); white-space: nowrap; }
.dm-lb-chip--active { color: #ffffff; background: linear-gradient(135deg, #E2354A, #C01227);
border-color: transparent; box-shadow: 0 6px 16px -6px rgba(192,18,39,0.5); }
.dm-lb-pillar__foot { margin: 0; font-size: clamp(12px, 1.1vw, 13.5px); line-height: 1.45; color: #475569; }
.dm-lb-pillar__stat { margin: 6px 0 0; font-size: clamp(12.5px, 1.2vw, 15px); color: #475569; }
.dm-lb-pillar__stat strong { color: #16a34a; font-weight: 800; font-size: 1.25em; }
.dm-lb-pillar__stat em { font-style: normal; color: #94a3b8; }
/* Constraints checklist (step 02) */
.dm-lb-constraints { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 7px; }
.dm-lb-constraints li { display: flex; align-items: center; gap: 9px; }
.dm-lb-constraints__icon { font-size: 14px; width: 20px; text-align: center; }
.dm-lb-constraints__label { font-size: 13px; font-weight: 700; color: #fbeff0; min-width: 84px; }
.dm-lb-constraints__note { font-size: 12px; color: rgba(232,222,224,0.6); }
.dm-lb-constraints__label { font-size: 13px; font-weight: 700; color: #0f172a; min-width: 84px; }
.dm-lb-constraints__note { font-size: 12px; color: #64748b; }
/* Scored leaderboard (step 03) */
.dm-lb-board { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.dm-lb-board li { display: grid; grid-template-columns: 104px 1fr 26px; align-items: center; gap: 9px; }
.dm-lb-board__name { font-size: 11.5px; font-weight: 600; color: rgba(234,226,228,0.68); display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.dm-lb-board li.is-win .dm-lb-board__name { color: #fff; font-weight: 800; }
.dm-lb-board__name { font-size: 11.5px; font-weight: 600; color: #64748b; display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.dm-lb-board li.is-win .dm-lb-board__name { color: #0f172a; font-weight: 800; }
.dm-lb-board__tag { font-size: 8px; font-weight: 800; letter-spacing: 0.08em; color: #fff;
background: linear-gradient(135deg,#E2354A,#C01227); padding: 2px 5px; border-radius: 5px; }
.dm-lb-board__track { height: 7px; border-radius: 999px; background: rgba(255,255,255,0.08); overflow: hidden; }
.dm-lb-board__fill { display: block; height: 100%; border-radius: 999px; background: rgba(150,150,165,0.5); }
.dm-lb-board li.is-win .dm-lb-board__fill { background: linear-gradient(90deg,#E2354A,#C01227); box-shadow: 0 0 12px rgba(226,53,66,0.6); }
.dm-lb-board__score { font-size: 12px; font-weight: 700; color: rgba(234,226,228,0.68); text-align: right; }
.dm-lb-board li.is-win .dm-lb-board__score { color: #fff; }
.dm-lb-board__track { height: 7px; border-radius: 999px; background: rgba(15,23,42,0.08); overflow: hidden; }
.dm-lb-board__fill { display: block; height: 100%; border-radius: 999px; background: rgba(100,116,139,0.45); }
.dm-lb-board li.is-win .dm-lb-board__fill { background: linear-gradient(90deg,#E2354A,#C01227); box-shadow: 0 0 12px rgba(226,53,66,0.4); }
.dm-lb-board__score { font-size: 12px; font-weight: 700; color: #64748b; text-align: right; }
.dm-lb-board li.is-win .dm-lb-board__score { color: #0f172a; }
/* SLA badges (step 04) */
.dm-lb-sla { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.dm-lb-sla__badge { font-size: 12px; font-weight: 700; color: #86efac; background: rgba(34,197,94,0.1);
border: 1px solid rgba(34,197,94,0.32); padding: 6px 12px; border-radius: 999px; }
.dm-lb-sla__x { font-size: 12px; font-weight: 700; color: #fca5a5; background: rgba(239,68,68,0.1);
border: 1px solid rgba(239,68,68,0.32); padding: 6px 12px; border-radius: 999px; }
.dm-lb-sla__badge { font-size: 12px; font-weight: 700; color: #15803d; background: rgba(34,197,94,0.1);
border: 1px solid rgba(34,197,94,0.3); padding: 6px 12px; border-radius: 999px; }
.dm-lb-sla__x { font-size: 12px; font-weight: 700; color: #b91c1c; background: rgba(239,68,68,0.08);
border: 1px solid rgba(239,68,68,0.28); padding: 6px 12px; border-radius: 999px; }
/* Winner banner (step 05) */
.dm-lb-winner { font-size: 13.5px; font-weight: 700; color: #fff; margin-bottom: 10px; padding: 9px 13px; border-radius: 12px;
background: linear-gradient(135deg, rgba(192,18,39,0.24), rgba(34,197,94,0.16)); border: 1px solid rgba(226,53,66,0.4); }
.dm-lb-winner { font-size: 13.5px; font-weight: 700; color: #0f172a; margin-bottom: 10px; padding: 9px 13px; border-radius: 12px;
background: linear-gradient(135deg, rgba(192,18,39,0.08), rgba(34,197,94,0.08)); border: 1px solid rgba(226,53,66,0.32); }
/* ---- Finale: KPI cards ---- */
.dm-lb-finale { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 0 20px; }
@@ -466,7 +510,15 @@ const styles = `
.dm-lb { height: 400vh; }
.dm-lb-kpis { gap: 12px; }
.dm-lb-kpi { min-width: 96px; padding: 14px 14px; }
.dm-lb-card-story { left: 0; right: 0; margin: 0 auto; width: calc(100% - 28px); bottom: clamp(20px, 5vh, 44px); padding: 14px 16px; }
/* On phones every stage collapses to one centred, full-width position — the
horizontal travel only reads on wider screens. */
.dm-lb-card-anchor,
.dm-lb-card-anchor.is-left,
.dm-lb-card-anchor.is-right,
.dm-lb-card-anchor.is-center,
.dm-lb-card-anchor.is-hero { left: 50%; right: auto; transform: translateX(-50%); bottom: clamp(20px, 5vh, 44px); }
.dm-lb-card-story,
.dm-lb-card-anchor.is-hero .dm-lb-card-story { width: calc(100vw - 28px); padding: 14px 16px; }
.dm-lb-board li { grid-template-columns: 88px 1fr 24px; }
.dm-lb-constraints__note { display: none; }
}