update screen fix

This commit is contained in:
2026-06-05 17:40:56 +05:30
parent 2f23f16634
commit 91841ba3f4
12 changed files with 438 additions and 93 deletions

View File

@@ -109,6 +109,46 @@ const LiveInsightBar = React.memo(function LiveInsightBar() {
);
});
/** Inner content of the "Without Optimization" panel — shared by the desktop
* (scroll-reactive motion.aside) and mobile (static aside) layouts. */
function WithoutPanelBody() {
return (
<>
<div className="dm-opt-panel__badge">
<span className="dm-opt-pulse dm-opt-pulse--red" /> System: Congested
</div>
<h3>Without Optimization</h3>
<ul>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> Chaotic overlapping routes</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> Duplicate &amp; idle trips</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> 8 vehicles required</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> 23 delivery delays</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> +18% cost overrun</li>
</ul>
</>
);
}
/** Inner content of the "With Doormile AI" panel — shared by both layouts. */
function WithPanelBody() {
return (
<>
<div className="dm-opt-panel__badge dm-opt-panel__badge--good">
<span className="dm-opt-pulse dm-opt-pulse--green" /> System: Optimized
</div>
<h3>With Doormile AI</h3>
<ul>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Optimized route clusters</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Intelligent vehicle assignment</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Multi-trip &amp; EV planning</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Zero delivery delays</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> 18% cost saved</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Carbon footprint reduced</li>
</ul>
</>
);
}
export default function OptimizationSection() {
const containerRef = useRef<HTMLDivElement>(null);
const progressRef = useRef(0);
@@ -119,21 +159,30 @@ export default function OptimizationSection() {
const [mountScene, setMountScene] = useState(false);
const [sceneActive, setSceneActive] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [isTablet, setIsTablet] = useState(false);
const [reduced, setReduced] = useState(false);
// Final-state metrics value for the mobile stack: a constant MotionValue so
// MetricsPanel renders the optimized numbers without scroll-driven counting.
const staticFinal = useMotionValue(1);
// Environment detection (client only).
useEffect(() => {
const mqMobile = window.matchMedia("(max-width: 767px)");
const mqTablet = window.matchMedia("(min-width: 768px) and (max-width: 1024px)");
const mqReduce = window.matchMedia("(prefers-reduced-motion: reduce)");
const sync = () => {
setIsMobile(mqMobile.matches);
setIsTablet(mqTablet.matches);
setReduced(mqReduce.matches);
};
sync();
mqMobile.addEventListener("change", sync);
mqTablet.addEventListener("change", sync);
mqReduce.addEventListener("change", sync);
return () => {
mqMobile.removeEventListener("change", sync);
mqTablet.removeEventListener("change", sync);
mqReduce.removeEventListener("change", sync);
};
}, []);
@@ -176,6 +225,10 @@ export default function OptimizationSection() {
useEffect(() => {
const el = containerRef.current;
if (!el) return;
// Mobile renders a non-pinned vertical stack (see the `isMobile` branch in
// render): no ScrollTrigger pin/scrub at all. Bail before creating one so
// pinState stays "before" and the section keeps its natural auto height.
if (isMobile) return;
gsap.registerPlugin(ScrollTrigger);
// NOTE: global Lenis (src/animations/SmoothScroll.tsx) is active on this
@@ -213,7 +266,29 @@ export default function OptimizationSection() {
clearTimeout(refresh);
st.kill();
};
}, [scroll]);
}, [scroll, isMobile]);
// Mobile ambient loop: with no scroll scrub, gently oscillate the shared
// progress inside the *optimized* band so the hologram stays "alive" and
// coherent with the final metrics. Held static under reduced-motion.
useEffect(() => {
if (!isMobile) return;
if (reduced) {
progressRef.current = 0.85;
return;
}
if (!sceneActive) return;
let raf = 0;
let start = 0;
const tick = (ts: number) => {
if (!start) start = ts;
const t = (ts - start) / 1000;
progressRef.current = 0.76 + Math.sin(t * 0.5) * 0.16; // ~0.60 → 0.92
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [isMobile, reduced, sceneActive]);
// Overlay reactions to scroll (no React re-render — direct DOM updates).
const leftOpacity = useTransform(scroll, [0.3, 0.55], [1, 0.32]);
@@ -231,9 +306,59 @@ export default function OptimizationSection() {
return (
<section
ref={containerRef}
className={`dm-opt is-${pinState}`}
className={`dm-opt is-${pinState}${isMobile ? " dm-opt--mobile" : ""}`}
aria-label="AI Logistics Optimization"
>
{/* ===== MOBILE: non-pinned vertical stack ===== */}
{isMobile && (
<div className="dm-opt-mobile">
<header className="dm-opt-mhead">
<div className="dm-opt-eyebrow">
<span className="dm-opt-dot" /> Doormile AI Control Tower
</div>
<h2>AI Logistics Optimization Engine</h2>
<p>
Watch Doormile&apos;s AI engine transform chaotic logistics into precision-optimized delivery networks reducing distance, fleet size, delays, and cost.
</p>
</header>
{/* 1. Without Optimization */}
<aside className="dm-opt-panel dm-opt-panel--bad dm-opt-mpanel">
<WithoutPanelBody />
</aside>
{/* 2. 3D Visualization (ambient) */}
<div className="dm-opt-mobile__scene">
{mountScene && (
<div className="dm-opt-canvas">
<OptimizationCanvas
progress={progressRef}
reduced={reduced}
isMobile
active={sceneActive}
/>
</div>
)}
<span className="dm-opt-mobile__scene-tag">
<span className="dm-opt-dot" /> Live AI optimization
</span>
</div>
{/* 3. With Doormile AI */}
<aside className="dm-opt-panel dm-opt-panel--good dm-opt-mpanel">
<WithPanelBody />
</aside>
{/* 4. Metrics — final optimized values, 2-col grid */}
<div className="dm-opt-mfoot">
<MetricsPanel scroll={staticFinal} />
<LiveInsightBar />
</div>
</div>
)}
{/* ===== DESKTOP / TABLET: pinned scroll experience ===== */}
{!isMobile && (
<div className="dm-opt-sticky">
<div className="dm-opt-card">
{/* Static backdrop (also the canvas loading state) */}
@@ -246,6 +371,7 @@ export default function OptimizationSection() {
progress={progressRef}
reduced={reduced}
isMobile={isMobile}
isTablet={isTablet}
// Only run the render loop while the section is actually pinned
// (filling the viewport). At a workflow seam two sections can both
// satisfy their activeIo margin; without the pin gate their two
@@ -375,35 +501,14 @@ export default function OptimizationSection() {
className="dm-opt-panel dm-opt-panel--bad"
style={{ opacity: leftOpacity, filter: leftFilter }}
>
<div className="dm-opt-panel__badge">
<span className="dm-opt-pulse dm-opt-pulse--red" /> System: Congested
</div>
<h3>Without Optimization</h3>
<ul>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> Chaotic overlapping routes</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> Duplicate &amp; idle trips</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> 8 vehicles required</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> 23 delivery delays</li>
<li><span className="dm-opt-marker dm-opt-marker--x"></span> +18% cost overrun</li>
</ul>
<WithoutPanelBody />
</motion.aside>
<motion.aside
className="dm-opt-panel dm-opt-panel--good"
style={{ opacity: rightOpacity }}
>
<div className="dm-opt-panel__badge dm-opt-panel__badge--good">
<span className="dm-opt-pulse dm-opt-pulse--green" /> System: Optimized
</div>
<h3>With Doormile AI</h3>
<ul>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Optimized route clusters</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Intelligent vehicle assignment</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Multi-trip &amp; EV planning</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Zero delivery delays</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> 18% cost saved</li>
<li><span className="dm-opt-marker dm-opt-marker--ok"></span> Carbon footprint reduced</li>
</ul>
<WithPanelBody />
</motion.aside>
</div>
@@ -417,6 +522,7 @@ export default function OptimizationSection() {
</div>
</div>
</div>
)}
<style>{styles}</style>
</section>
@@ -893,6 +999,98 @@ const styles = `
.dm-opt-insight__text { font-size: 8.5px; }
.dm-opt-insight__sep { height: 10px; }
}
/* ===== MOBILE STACKED LAYOUT (<=767px) — non-pinned vertical flow =====
Rendered by the isMobile branch as a normal-flow .dm-opt-mobile container
(header → Without → 3D → With → metrics). These rules come after the block
above so they win for the elements that actually exist on mobile. */
@media (max-width: 767px) {
/* Un-pin: natural height, no fixed sticky, no 200/230vh scroll runway.
(.dm-opt--mobile out-specifies the base .dm-opt height rules.) */
.dm-opt.dm-opt--mobile { height: auto; }
.dm-opt-mobile {
position: relative;
margin: 0 10px;
padding: 24px 13px 22px;
display: flex;
flex-direction: column;
gap: 14px;
box-sizing: border-box;
background: linear-gradient(180deg, #06101f 0%, #020617 55%, #030a18 100%);
border: 1px solid rgba(255, 255, 255, 0.05);
border-bottom: none;
/* Flat bottom + flush so the Performance card (.dm-wf1-card) butts directly
against it as one continuous container (matches Workflow1 ≤767 styles). */
border-radius: 20px 20px 0 0;
overflow: hidden;
}
/* Header */
.dm-opt-mhead { text-align: center; padding: 0 4px; }
.dm-opt-mhead .dm-opt-eyebrow { font-size: 10px; }
.dm-opt-mhead h2 {
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
margin: 10px 0 6px !important; padding: 0 !important; color: #F8FAFC !important;
font-weight: 700 !important; text-transform: none !important;
font-size: clamp(20px, 6.2vw, 26px) !important; line-height: 1.15 !important;
letter-spacing: -0.015em !important;
}
.dm-opt-mhead p {
margin: 0 auto !important; padding: 0 !important; color: ${COLORS.textDim} !important;
max-width: 40ch; font-size: 12.5px !important; line-height: 1.5 !important;
}
/* Comparison panels → full-width static cards, fully visible, readable.
Trim the heavy glow (box-shadow) but keep the colored border for identity. */
.dm-opt-mobile .dm-opt-panel {
width: 100%; box-sizing: border-box;
opacity: 1 !important; filter: none !important;
padding: 15px 16px; border-radius: 16px; box-shadow: none;
}
.dm-opt-mobile .dm-opt-panel h3 {
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
font-size: 16px !important; margin: 9px 0 10px !important;
}
.dm-opt-mobile .dm-opt-panel ul { gap: 8px; }
.dm-opt-mobile .dm-opt-panel li { font-size: 12.5px !important; line-height: 1.35 !important; }
.dm-opt-mobile .dm-opt-marker { width: 18px; height: 18px; font-size: 10px; border-radius: 6px; }
.dm-opt-mobile .dm-opt-panel__badge { font-size: 9.5px; padding: 4px 9px; }
/* 3D visualization block — contained, ~40vh, premium but not dominant. */
.dm-opt-mobile__scene {
position: relative; width: 100%; height: 40vh; min-height: 260px; max-height: 360px;
border-radius: 16px; overflow: hidden;
background: radial-gradient(120% 90% at 50% 30%, ${rgba(COLORS.cyan, 0.06)} 0%, ${COLORS.bg} 70%);
border: 1px solid ${rgba(COLORS.cyan, 0.14)};
}
.dm-opt-mobile__scene .dm-opt-canvas { position: absolute; inset: 0; z-index: 0; }
.dm-opt-mobile__scene-tag {
position: absolute; left: 10px; top: 10px; z-index: 1;
display: inline-flex; align-items: center; gap: 6px;
font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700;
color: #E2E8F0; padding: 4px 9px; border-radius: 999px;
background: ${rgba(COLORS.ink, 0.72)}; border: 1px solid ${rgba(COLORS.cyan, 0.22)};
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
/* Metrics → 2-col grid (5th card spans full width), final optimized values,
readable sizing so nothing truncates. */
.dm-opt-mfoot { padding: 0; }
.dm-opt-mobile .dm-opt-metrics {
grid-template-columns: repeat(2, 1fr); gap: 8px; max-width: none;
}
.dm-opt-mobile .dm-opt-metric { padding: 12px 13px 11px; border-radius: 12px; }
.dm-opt-mobile .dm-opt-metric:last-child { grid-column: 1 / -1; }
.dm-opt-mobile .dm-opt-metric__label { font-size: 10px; letter-spacing: 0.02em; }
.dm-opt-mobile .dm-opt-metric__value { font-size: clamp(20px, 6.5vw, 26px); }
/* Insight bar wraps instead of overflowing. */
.dm-opt-mobile .dm-opt-insight {
flex-wrap: wrap; max-width: none; gap: 6px 10px; padding: 8px 14px; margin-top: 4px;
}
.dm-opt-mobile .dm-opt-insight__text { font-size: 10px; }
}
@media (prefers-reduced-motion: reduce) {
.dm-opt-pulse { animation: none; }
.dm-opt-metric { animation: none; opacity: 1; transform: none; }