Files
doormile_react/src/components/optimization/OptimizationSection.tsx
2026-06-02 23:59:51 +05:30

885 lines
34 KiB
TypeScript

"use client";
import React, { useEffect, useRef, useState } from "react";
import dynamic from "next/dynamic";
import { motion, AnimatePresence, useMotionValue, useTransform } from "framer-motion";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import Lenis from "lenis";
import {
COLORS,
PHASE_LABELS,
PhaseKey,
phaseFromProgress,
rgba,
} from "./constants";
import MetricsPanel from "./MetricsPanel";
// 3D scene is client-only and code-split so it never blocks first paint.
const OptimizationCanvas = dynamic(() => import("./OptimizationCanvas"), {
ssr: false,
});
const PHASE_ORDER: PhaseKey[] = [
"chaos",
"scan",
"dissolve",
"optimize",
"reorganize",
"metrics",
];
const WORKFLOW_STEPS = [
{ label: "Analyze", icon: "🔍", activateAt: 0 },
{ label: "Optimize", icon: "⚡", activateAt: 2 },
{ label: "Assign", icon: "🚛", activateAt: 3 },
{ label: "Execute", icon: "📡", activateAt: 4 },
{ label: "Monitor", icon: "📊", activateAt: 5 },
];
export default function OptimizationSection() {
const containerRef = useRef<HTMLDivElement>(null);
const progressRef = useRef(0);
const scroll = useMotionValue(0);
const [phase, setPhase] = useState<PhaseKey>("chaos");
const [pinState, setPinState] = useState<"before" | "pinned" | "after">("before");
const [mountScene, setMountScene] = useState(false);
const [sceneActive, setSceneActive] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [reduced, setReduced] = useState(false);
const [orders, setOrders] = useState(59);
const [accuracy, setAccuracy] = useState(98.7);
const [activeVehicles, setActiveVehicles] = useState(5);
const [carbon, setCarbon] = useState(-12.0);
const [routeHealth, setRouteHealth] = useState(99.4);
// Interval timers for high-fidelity live dashboard fluctuations
useEffect(() => {
const ordersInterval = setInterval(() => {
setOrders((prev) => prev + (Math.random() > 0.4 ? 1 : 0));
}, 4500);
const accuracyInterval = setInterval(() => {
setAccuracy((prev) => {
const delta = (Math.random() - 0.5) * 0.15;
const next = prev + delta;
return parseFloat(Math.min(99.1, Math.max(98.4, next)).toFixed(2));
});
}, 2800);
const vehicleInterval = setInterval(() => {
setActiveVehicles((prev) => (prev === 5 ? (Math.random() > 0.5 ? 4 : 5) : (Math.random() > 0.3 ? 5 : 4)));
}, 3500);
const carbonInterval = setInterval(() => {
setCarbon((prev) => {
const delta = (Math.random() - 0.5) * 0.2;
const next = prev + delta;
return parseFloat(Math.min(-11.5, Math.max(-12.8, next)).toFixed(1));
});
}, 3200);
const healthInterval = setInterval(() => {
setRouteHealth((prev) => {
const delta = (Math.random() - 0.5) * 0.12;
const next = prev + delta;
return parseFloat(Math.min(99.9, Math.max(98.8, next)).toFixed(2));
});
}, 2500);
return () => {
clearInterval(ordersInterval);
clearInterval(accuracyInterval);
clearInterval(vehicleInterval);
clearInterval(carbonInterval);
clearInterval(healthInterval);
};
}, []);
// Environment detection (client only).
useEffect(() => {
const mqMobile = window.matchMedia("(max-width: 767px)");
const mqReduce = window.matchMedia("(prefers-reduced-motion: reduce)");
const sync = () => {
setIsMobile(mqMobile.matches);
setReduced(mqReduce.matches);
};
sync();
mqMobile.addEventListener("change", sync);
mqReduce.addEventListener("change", sync);
return () => {
mqMobile.removeEventListener("change", sync);
mqReduce.removeEventListener("change", sync);
};
}, []);
// Mount the WebGL scene when the section approaches the viewport, and pause
// its render loop entirely once it scrolls off-screen (battery / 60fps).
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const mountIo = new IntersectionObserver(
(entries) => {
if (entries.some((e) => e.isIntersecting)) {
setMountScene(true);
// Activate immediately on mount so the render loop starts even on a
// direct scroll-jump (the activeIo below only fires on later changes).
setSceneActive(true);
mountIo.disconnect();
}
},
{ rootMargin: "120% 0px" },
);
const activeIo = new IntersectionObserver(
(entries) => setSceneActive(entries.some((e) => e.isIntersecting)),
{ rootMargin: "10% 0px" },
);
mountIo.observe(el);
activeIo.observe(el);
return () => {
mountIo.disconnect();
activeIo.disconnect();
};
}, []);
// Drive the shared scroll progress with GSAP ScrollTrigger.
// We pin via our own `position: fixed` (toggled by `pinState`) rather than
// GSAP's pin or CSS `position: sticky`. CSS sticky is broken by an ancestor
// (.body-container) using `overflow: hidden`, and GSAP's pin offsets the
// element by the fixed site-header height. A self-managed fixed element
// pins to the viewport top deterministically and ignores ancestor overflow.
useEffect(() => {
const el = containerRef.current;
if (!el) return;
gsap.registerPlugin(ScrollTrigger);
// Smooth scroll (Lenis), driven by a SINGLE rAF source — GSAP's ticker.
// Previously lenis.raf() was called from both a manual requestAnimationFrame
// loop AND gsap.ticker, double-stepping the integrator every frame, which is
// what made scrolling stutter. One source keeps Lenis + ScrollTrigger locked.
const lenis = new Lenis({
duration: 1.05,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
orientation: "vertical",
gestureOrientation: "vertical",
smoothWheel: true,
});
lenis.on("scroll", ScrollTrigger.update);
const tickerCb = (time: number) => lenis.raf(time * 1000); // ticker is in seconds, Lenis wants ms
gsap.ticker.add(tickerCb);
gsap.ticker.lagSmoothing(0);
let lastPhase: PhaseKey = "chaos";
let lastPin: "before" | "pinned" | "after" = "before";
const st = ScrollTrigger.create({
trigger: el,
start: "top top",
end: "bottom bottom",
scrub: 0.4,
invalidateOnRefresh: true,
onUpdate: (self) => {
const p = self.progress;
progressRef.current = p;
scroll.set(p);
const next = phaseFromProgress(p);
if (next !== lastPhase) {
lastPhase = next;
setPhase(next);
}
const ns = p <= 0.0002 ? "before" : p >= 0.9998 ? "after" : "pinned";
if (ns !== lastPin) {
lastPin = ns;
setPinState(ns);
}
},
});
const refresh = setTimeout(() => ScrollTrigger.refresh(), 300);
return () => {
clearTimeout(refresh);
st.kill();
gsap.ticker.remove(tickerCb);
lenis.destroy();
};
}, [scroll]);
// Overlay reactions to scroll (no React re-render — direct DOM updates).
const leftOpacity = useTransform(scroll, [0.3, 0.55], [1, 0.32]);
const leftBlur = useTransform(scroll, [0.3, 0.55], [0, 3]);
const leftFilter = useTransform(leftBlur, (b) => `blur(${b}px)`);
const rightOpacity = useTransform(scroll, [0.42, 0.66], [0.36, 1]);
const scanWidth = useTransform(scroll, [0, 1], ["0%", "100%"]);
const scanLineY = useTransform(scroll, [0.2, 0.42], ["8%", "92%"]);
const scanLineOpacity = useTransform(scroll, [0.18, 0.22, 0.42, 0.46], [0, 1, 1, 0]);
const dividerOpacity = useTransform(scroll, [0.45, 0.6], [0.15, 0.75]);
const phaseIndex = PHASE_ORDER.indexOf(phase);
return (
<section
ref={containerRef}
className={`dm-opt is-${pinState}`}
aria-label="AI Logistics Optimization"
>
<div className="dm-opt-sticky">
<div className="dm-opt-card">
{/* Static backdrop (also the canvas loading state) */}
<div className="dm-opt-backdrop" aria-hidden />
{/* 3D scene */}
{mountScene && (
<div className="dm-opt-canvas">
<OptimizationCanvas
progress={progressRef}
reduced={reduced}
isMobile={isMobile}
active={sceneActive}
/>
</div>
)}
{/* Depth vignette */}
<div className="dm-opt-vignette" aria-hidden />
{/* Floating AI Status Badge replacing giant abstract sphere centerpiece */}
<AnimatePresence mode="wait">
{phase !== "chaos" && (
<motion.div
key={phase}
className="dm-opt-floating-badge"
initial={{ scale: 0.85, opacity: 0 }}
animate={{
scale: 1,
opacity: 1,
}}
exit={{
scale: 0.85,
opacity: 0,
}}
transition={{ duration: 0.28, ease: "easeInOut" }}
style={{
position: "absolute",
top: "40%",
left: "50%",
x: "-50%",
y: "-50%",
zIndex: 10,
pointerEvents: "none",
}}
>
<div className="dm-opt-floating-badge__inner">
<span className={`dm-opt-floating-badge__dot is-${phase}`} />
<span className="dm-opt-floating-badge__text">
{PHASE_LABELS[phase]}
</span>
</div>
</motion.div>
)}
</AnimatePresence>
{/* Center scan divider */}
<motion.div
className="dm-opt-divider"
style={{ opacity: dividerOpacity }}
aria-hidden
/>
{/* AI scan sweep line */}
<motion.div
className="dm-opt-scanline"
style={{ top: scanLineY, opacity: scanLineOpacity }}
aria-hidden
/>
{/* UI overlay */}
<div className="dm-opt-ui">
{/* Header */}
<header className="dm-opt-head">
<motion.div
className="dm-opt-eyebrow"
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<span className="dm-opt-dot" /> Doormile AI Control Tower
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 22 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.05 }}
>
AI Logistics Optimization Engine
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 22 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.12 }}
>
Watch Doormile&apos;s AI engine transform chaotic logistics into precision-optimized delivery networks reducing distance, fleet size, delays, and cost in real time.
</motion.p>
{/* Workflow step indicators */}
<motion.div
className="dm-opt-steps"
initial={{ opacity: 0, y: 14 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.18 }}
>
{WORKFLOW_STEPS.map((step, i) => {
const isActive = phaseIndex >= step.activateAt;
const isCurrent = phaseIndex === step.activateAt;
return (
<React.Fragment key={step.label}>
{i > 0 && <span className={`dm-opt-steps__line ${isActive ? 'is-active' : ''}`} />}
<span className={`dm-opt-steps__pill ${isActive ? 'is-active' : ''} ${isCurrent ? 'is-current' : ''}`}>
<span className="dm-opt-steps__icon">{step.icon}</span>
<span className="dm-opt-steps__text">{step.label}</span>
</span>
</React.Fragment>
);
})}
</motion.div>
{/* Scan progress bar */}
<div className="dm-opt-progress">
<div className="dm-opt-progress__track">
<motion.div className="dm-opt-progress__fill" style={{ width: scanWidth }} />
</div>
</div>
</header>
{/* Side comparison panels */}
<div className="dm-opt-compare">
<motion.aside
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>
</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>
</motion.aside>
</div>
{/* KPI metrics */}
<div className="dm-opt-foot">
<MetricsPanel scroll={scroll} />
{/* Bottom insight bar */}
<motion.div
className="dm-opt-insight"
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<span className="dm-opt-insight__dot" />
<span className="dm-opt-insight__text">Live Analytics: <strong>{orders} Orders</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">AI Accuracy: <strong>{accuracy}%</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Fleet: <strong>{activeVehicles}/5 EV Active</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Route Health: <strong>{routeHealth}%</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Carbon: <strong>{carbon}%</strong></span>
</motion.div>
</div>
</div>
</div>
</div>
<style>{styles}</style>
</section>
);
}
const styles = `
/* ===== OUTER SECTION: Transparent so the card floats ===== */
.dm-opt {
position: relative;
height: 230vh;
background: transparent;
margin-bottom: 0;
}
.dm-opt-sticky {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
overflow: hidden;
background: transparent;
}
.dm-opt.is-pinned .dm-opt-sticky { position: fixed; top: 0; left: 0; }
.dm-opt.is-after .dm-opt-sticky { position: absolute; top: auto; bottom: 0; }
/* ===== FLOATING CARD — the only colored surface ===== */
.dm-opt-card {
position: absolute !important;
top: 110px !important;
left: 40px !important;
right: 40px !important;
bottom: 0 !important;
/* flat bottom + flush to container so the Performance card butts directly
against it, reading as one continuous container (home-page technique) */
border-radius: 42px 42px 0 0 !important;
overflow: hidden !important;
// background: linear-gradient(165deg, #06101f 0%, #020617 35%, #040d1c 70%, #030a18 100%) !important;
// border: 0px solid ${rgba("#ffffff", 0.08)} !important;
border-bottom: none !important;
// box-shadow:
// 0 0 0 1px ${rgba(COLORS.cyan, 0.04)},
// 0 4px 30px -4px rgba(0, 0, 0, 0.7),
// 0 20px 80px -20px rgba(0, 0, 0, 0.6),
// 0 0 120px -30px ${rgba(COLORS.cyan, 0.08)},
// inset 0 1px 0 ${rgba("#ffffff", 0.06)},
// inset 0 -1px 0 ${rgba("#ffffff", 0.02)} !important;
// box-sizing: border-box !important;
}
/* Animated subtle grid pattern */
.dm-opt-card::before {
content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
opacity: 0.035;
background-image:
linear-gradient(${rgba(COLORS.cyan, 0.5)} 1px, transparent 1px),
linear-gradient(90deg, ${rgba(COLORS.cyan, 0.5)} 1px, transparent 1px);
background-size: 60px 60px;
animation: dmOptGridDrift 25s linear infinite;
}
@keyframes dmOptGridDrift {
0% { background-position: 0 0; }
100% { background-position: 60px 60px; }
}
/* Radial center glow behind 3D scene */
.dm-opt-card::after {
content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
background:
radial-gradient(ellipse 50% 45% at 50% 48%, ${rgba(COLORS.cyan, 0.08)} 0%, transparent 70%),
radial-gradient(ellipse 60% 40% at 50% 90%, ${rgba(COLORS.green, 0.05)} 0%, transparent 60%),
radial-gradient(ellipse 80% 50% at 50% 10%, ${rgba(COLORS.cyan, 0.04)} 0%, transparent 50%);
}
@media (max-width: 1024px) {
.dm-opt-card {
top: 96px !important;
left: 20px !important;
right: 20px !important;
bottom: 0 !important;
border-radius: 42px 42px 0 0 !important;
}
}
@media (max-width: 767px) {
.dm-opt-card {
top: 86px !important;
left: 10px !important;
right: 10px !important;
bottom: 0 !important;
border-radius: 28px 28px 0 0 !important;
}
}
/* ===== INNER LAYERS ===== */
.dm-opt-backdrop {
position: absolute;
inset: 0; z-index: 0;
background:
radial-gradient(100% 70% at 50% 6%, ${rgba(COLORS.cyan, 0.06)} 0%, transparent 55%),
radial-gradient(80% 60% at 50% 100%, ${rgba(COLORS.green, 0.05)} 0%, transparent 55%);
}
.dm-opt-canvas { position: absolute; inset: 0; z-index: 1; }
.dm-opt-canvas canvas { display: block; }
.dm-opt-vignette {
position: absolute; inset: 0; z-index: 2; pointer-events: none;
background:
radial-gradient(110% 90% at 50% 50%, transparent 48%, ${rgba("#020617", 0.88)} 100%),
linear-gradient(180deg, ${rgba("#020617", 0.6)} 0%, transparent 20%, transparent 65%, ${rgba("#020617", 0.92)} 100%);
}
.dm-opt-divider {
position: absolute; left: 50%; top: 14%; bottom: 28%;
width: 1px; z-index: 3; pointer-events: none; transform: translateX(-0.5px);
background: linear-gradient(180deg, transparent, ${rgba(COLORS.cyan, 0.6)}, transparent);
box-shadow: 0 0 16px ${rgba(COLORS.cyan, 0.4)};
}
.dm-opt-scanline {
position: absolute; left: 6%; right: 6%; height: 2px; z-index: 3; pointer-events: none;
background: linear-gradient(90deg, transparent, ${COLORS.cyan}, transparent);
box-shadow: 0 0 22px ${rgba(COLORS.cyan, 0.8)};
}
/* ===== FLOATING AI STATUS BADGE ===== */
.dm-opt-floating-badge {
pointer-events: none;
}
.dm-opt-floating-badge__inner {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 8px 18px;
border-radius: 999px;
background: ${rgba(COLORS.ink, 0.85)};
border: 1.5px solid ${rgba(COLORS.cyan, 0.3)};
box-shadow:
0 10px 30px -5px rgba(0, 0, 0, 0.65),
0 0 24px -2px ${rgba(COLORS.cyan, 0.18)},
inset 0 1px 0 rgba(255, 255, 255, 0.08);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
transition: border-color 0.4s ease, box-shadow 0.4s ease;
}
.dm-opt-floating-badge__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: ${COLORS.cyan};
box-shadow: 0 0 10px ${COLORS.cyan};
animation: dmOptPulse 1.4s ease-in-out infinite;
transition: background 0.4s ease, box-shadow 0.4s ease;
}
.dm-opt-floating-badge__dot.is-scan {
background: ${COLORS.cyan};
box-shadow: 0 0 10px ${COLORS.cyan};
}
.dm-opt-floating-badge__dot.is-dissolve,
.dm-opt-floating-badge__dot.is-optimize {
background: ${COLORS.amber};
box-shadow: 0 0 10px ${COLORS.amber};
}
.dm-opt-floating-badge__dot.is-reorganize {
background: #C084FC;
box-shadow: 0 0 10px #C084FC;
}
.dm-opt-floating-badge__dot.is-metrics {
background: ${COLORS.green};
box-shadow: 0 0 10px ${COLORS.green};
}
.dm-opt-floating-badge__text {
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: #F8FAFC;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
/* ===== UI OVERLAY ===== */
.dm-opt-ui { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
.dm-opt-ui h2, .dm-opt-ui h3, .dm-opt-metric__value, .dm-opt-eyebrow, .dm-opt-phase {
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
}
/* ===== HEADER — compact, no dead space ===== */
.dm-opt-head {
position: absolute; top: clamp(18px, 3vh, 36px); left: 50%;
transform: translateX(-50%); width: min(640px, 90vw); text-align: center;
}
.dm-opt-eyebrow {
display: inline-flex; align-items: center; gap: 7px;
font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
color: ${COLORS.cyan}; padding: 5px 14px; border-radius: 999px;
background: ${rgba(COLORS.cyan, 0.06)}; border: 1px solid ${rgba(COLORS.cyan, 0.25)};
backdrop-filter: blur(8px);
}
.dm-opt-dot { width: 6px; height: 6px; border-radius: 50%; background: ${COLORS.cyan}; box-shadow: 0 0 10px ${COLORS.cyan}; }
.dm-opt .dm-opt-head h2 {
margin: 8px 0 4px !important; padding: 0 !important; color: #F8FAFC !important;
font-weight: 700 !important; text-transform: none !important;
font-size: clamp(22px, 2.4vw, 36px) !important; line-height: 1.1 !important;
letter-spacing: -0.015em !important;
}
.dm-opt .dm-opt-head p {
margin: 0 auto !important; padding: 0 !important; color: ${COLORS.textDim} !important;
max-width: 440px; font-size: clamp(11px, 1vw, 13px) !important; line-height: 1.45 !important;
}
/* ===== WORKFLOW STEPS ===== */
.dm-opt-steps {
display: flex; align-items: center; justify-content: center; gap: 0;
margin-top: 12px; flex-wrap: wrap;
}
.dm-opt-steps__pill {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 999px; font-weight: 600;
letter-spacing: 0.04em; text-transform: uppercase;
color: ${COLORS.slate};
background: ${rgba(COLORS.ink, 0.5)};
border: 1px solid ${rgba(COLORS.slate, 0.2)};
backdrop-filter: blur(6px);
transition: all 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
.dm-opt-steps__pill.is-active {
color: #E2E8F0;
background: ${rgba(COLORS.cyan, 0.10)};
border-color: ${rgba(COLORS.cyan, 0.35)};
box-shadow: 0 0 18px -6px ${rgba(COLORS.cyan, 0.5)};
}
.dm-opt-steps__pill.is-current {
color: ${COLORS.cyan};
border-color: ${rgba(COLORS.cyan, 0.6)};
box-shadow: 0 0 24px -4px ${rgba(COLORS.cyan, 0.6)};
}
.dm-opt-steps__icon { font-size: 11px; }
.dm-opt-steps__text { font-size: 9.5px; }
.dm-opt-steps__line {
display: block; width: 16px; height: 1px; margin: 0 2px;
background: ${rgba(COLORS.slate, 0.3)};
transition: background 0.45s ease;
}
.dm-opt-steps__line.is-active {
background: linear-gradient(90deg, ${COLORS.cyan}, ${COLORS.green});
box-shadow: 0 0 6px ${rgba(COLORS.cyan, 0.5)};
}
/* ===== PROGRESS BAR ===== */
.dm-opt-progress { margin-top: 10px; }
.dm-opt-progress__track {
height: 2px; border-radius: 999px; overflow: hidden;
background: ${rgba(COLORS.cyan, 0.10)}; max-width: 420px; margin: 0 auto;
}
.dm-opt-progress__fill {
height: 100%; border-radius: 999px;
background: linear-gradient(90deg, ${COLORS.cyan}, ${COLORS.green});
box-shadow: 0 0 12px ${rgba(COLORS.cyan, 0.6)};
}
.dm-opt-status {
display: inline-flex; align-items: center; gap: 8px; margin-top: 8px;
padding: 4px 11px; border-radius: 999px;
background: ${rgba(COLORS.ink, 0.55)}; border: 1px solid ${rgba(COLORS.cyan, 0.18)};
backdrop-filter: blur(8px);
}
.dm-opt-status__dot {
width: 5px; height: 5px; border-radius: 50%; background: ${COLORS.cyan};
box-shadow: 0 0 8px ${COLORS.cyan}; animation: dmOptPulse 1.4s ease-in-out infinite;
}
.dm-opt-status__label {
font-size: 9.5px; letter-spacing: 0.14em; text-transform: uppercase; color: #E2E8F0; font-weight: 600;
}
.dm-opt-status__step { font-size: 9.5px; color: ${COLORS.cyan}; font-weight: 600; }
/* ===== COMPARE PANELS — tighter, stronger ===== */
.dm-opt-compare {
position: absolute; top: 50%; left: 0; right: 0; transform: translateY(-50%);
display: flex; justify-content: space-between; align-items: center;
gap: 12px; padding: 0 clamp(12px, 2.5vw, 36px);
}
.dm-opt-panel {
pointer-events: auto; width: clamp(230px, 26vw, 340px);
padding: 18px 20px; border-radius: 20px;
background: ${rgba(COLORS.ink, 0.72)};
border: 1px solid ${rgba(COLORS.slate, 0.22)};
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
}
.dm-opt-panel--bad {
border-color: ${rgba(COLORS.red, 0.45)};
box-shadow:
0 0 30px -8px ${rgba(COLORS.red, 0.35)},
0 20px 50px -20px ${rgba(COLORS.red, 0.25)},
inset 0 1px 0 ${rgba(COLORS.red, 0.08)};
}
.dm-opt-panel--good {
border-color: ${rgba(COLORS.green, 0.45)};
box-shadow:
0 0 30px -8px ${rgba(COLORS.green, 0.35)},
0 20px 50px -20px ${rgba(COLORS.green, 0.25)},
inset 0 1px 0 ${rgba(COLORS.green, 0.08)};
}
.dm-opt .dm-opt-panel h3 {
margin: 10px 0 12px !important; padding: 0 !important; color: #F1F5F9 !important;
font-size: clamp(15px, 1.4vw, 19px) !important; font-weight: 600 !important;
line-height: 1.15 !important; text-transform: none !important; letter-spacing: -0.01em !important;
}
.dm-opt .dm-opt-panel ul { list-style: none !important; margin: 0 !important; padding: 0 !important; display: grid; gap: 7px; }
.dm-opt .dm-opt-panel li {
position: relative; padding-left: 0 !important; margin: 0 !important;
color: ${COLORS.textDim} !important; font-size: 12.5px !important; line-height: 1.35 !important;
display: flex; align-items: center; gap: 8px;
}
.dm-opt .dm-opt-panel li::marker { content: "" !important; }
/* Remove old dot pseudo — now using ✖/✔ markers */
.dm-opt .dm-opt-panel li::before { content: none !important; display: none !important; }
/* ✖/✔ markers */
.dm-opt-marker {
display: inline-flex; align-items: center; justify-content: center;
width: 18px; height: 18px; border-radius: 6px; flex-shrink: 0;
font-size: 10px; font-weight: 700; line-height: 1;
}
.dm-opt-marker--x {
background: ${rgba(COLORS.red, 0.15)};
color: ${COLORS.red};
border: 1px solid ${rgba(COLORS.red, 0.35)};
box-shadow: 0 0 8px ${rgba(COLORS.red, 0.3)};
}
.dm-opt-marker--ok {
background: ${rgba(COLORS.green, 0.15)};
color: ${COLORS.green};
border: 1px solid ${rgba(COLORS.green, 0.35)};
box-shadow: 0 0 8px ${rgba(COLORS.green, 0.3)};
}
.dm-opt-panel__badge {
display: inline-flex; align-items: center; gap: 7px;
font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 700;
color: ${COLORS.red}; padding: 5px 10px; border-radius: 999px;
background: ${rgba(COLORS.red, 0.1)}; border: 1px solid ${rgba(COLORS.red, 0.35)};
}
.dm-opt-panel__badge--good { color: ${COLORS.green}; background: ${rgba(COLORS.green, 0.1)}; border-color: ${rgba(COLORS.green, 0.35)}; }
.dm-opt-pulse { width: 6px; height: 6px; border-radius: 50%; animation: dmOptPulse 1.4s ease-in-out infinite; }
.dm-opt-pulse--red { background: ${COLORS.red}; box-shadow: 0 0 10px ${COLORS.red}; }
.dm-opt-pulse--green { background: ${COLORS.green}; box-shadow: 0 0 10px ${COLORS.green}; }
@keyframes dmOptPulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.5; } }
/* ===== METRICS ===== */
.dm-opt-foot {
position: absolute; left: 0; right: 0; bottom: clamp(12px, 2.5vh, 28px);
padding: 0 clamp(12px, 3vw, 36px);
}
.dm-opt-metrics {
pointer-events: auto; display: grid; grid-template-columns: repeat(5, 1fr);
gap: 10px; max-width: 1100px; margin: 0 auto;
}
.dm-opt-metric {
position: relative; padding: 14px 14px 12px; border-radius: 16px;
background: ${rgba(COLORS.ink, 0.72)};
border: 1px solid ${rgba(COLORS.slate, 0.25)};
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
overflow: hidden;
opacity: 0; transform: translateY(22px);
animation: dmOptCardIn 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
box-shadow: 0 8px 32px -8px rgba(0, 0, 0, 0.5), inset 0 1px 0 ${rgba("#ffffff", 0.04)};
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.4s ease, box-shadow 0.4s ease, background 0.4s ease !important;
cursor: pointer;
}
.dm-opt-metric:hover {
transform: translateY(-5px) scale(1.028) !important;
background: ${rgba(COLORS.ink, 0.9)} !important;
border-color: ${rgba(COLORS.cyan, 0.52)} !important;
box-shadow:
0 18px 48px -8px ${rgba(COLORS.cyan, 0.25)},
0 4px 12px -2px ${rgba(COLORS.cyan, 0.12)},
inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
}
.dm-opt-metric:hover .dm-opt-metric__sparkline {
opacity: 0.72 !important;
}
.dm-opt-metric__sparkline {
transition: opacity 0.4s ease;
}
@keyframes dmOptCardIn { to { opacity: 1; transform: translateY(0); } }
.dm-opt-metric__top { display: flex; align-items: center; justify-content: space-between; }
.dm-opt-metric__label {
font-size: 10px; letter-spacing: 0.04em; color: ${COLORS.textDim}; text-transform: uppercase;
}
.dm-opt-metric__arrow { font-size: 11px; }
.dm-opt-metric__value { margin-top: 6px; font-size: clamp(20px, 2.8vw, 32px); font-weight: 700; line-height: 1; }
.dm-opt-metric__bar { margin-top: 10px; height: 3px; border-radius: 999px; background: ${rgba(COLORS.slate, 0.2)}; overflow: hidden; }
.dm-opt-metric__fill { height: 100%; border-radius: 999px; transform-origin: left center; }
/* ===== BOTTOM INSIGHT BAR ===== */
.dm-opt-insight {
display: flex; align-items: center; justify-content: center; gap: 8px;
max-width: 760px; margin: 10px auto 0;
padding: 7px 18px; border-radius: 999px;
background: ${rgba(COLORS.ink, 0.6)};
border: 1px solid ${rgba(COLORS.cyan, 0.12)};
backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
pointer-events: auto;
}
.dm-opt-insight__dot {
width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
background: ${COLORS.green};
box-shadow: 0 0 12px ${COLORS.green};
animation: dmOptPulse 2s ease-in-out infinite;
}
.dm-opt-insight__text {
font-size: 10.5px; color: ${COLORS.textDim}; line-height: 1.3;
letter-spacing: 0.03em; font-weight: 500;
}
.dm-opt-insight__text strong {
color: #E2E8F0; font-weight: 700;
}
.dm-opt-insight__sep {
width: 1px; height: 12px; flex-shrink: 0;
background: ${rgba(COLORS.slate, 0.35)};
}
/* ===== RESPONSIVE ===== */
@media (max-width: 1024px) {
.dm-opt-panel { width: clamp(190px, 30vw, 280px); padding: 14px 16px; }
.dm-opt-panel li { font-size: 11.5px; }
.dm-opt-marker { width: 16px; height: 16px; font-size: 9px; }
}
@media (max-width: 767px) {
.dm-opt { height: 200vh; }
.dm-opt-card {
top: 8px !important; left: 8px !important; right: 8px !important; bottom: 8px !important;
border-radius: 24px !important;
}
.dm-opt-compare {
top: auto; bottom: 148px; transform: none;
flex-direction: row; align-items: stretch; gap: 6px; padding: 0 10px;
}
.dm-opt-panel { width: 50%; padding: 10px 11px; border-radius: 14px; }
.dm-opt-panel ul { gap: 4px; }
.dm-opt-panel li { font-size: 10px; }
.dm-opt-panel li::before { display: none !important; }
.dm-opt-marker { width: 14px; height: 14px; font-size: 8px; border-radius: 4px; }
.dm-opt-panel h3 { margin: 6px 0 5px; font-size: 14px; }
.dm-opt-panel__badge { font-size: 8px; padding: 3px 7px; }
.dm-opt-metrics { grid-template-columns: repeat(5, 1fr); gap: 4px; }
.dm-opt-metric { padding: 7px 5px; border-radius: 10px; }
.dm-opt-metric__label { font-size: 7.5px; letter-spacing: 0; }
.dm-opt-metric__value { font-size: 14px; }
.dm-opt-metric__bar { margin-top: 6px; }
.dm-opt-head h2 { font-size: 22px; margin: 6px 0 4px; }
.dm-opt-head p { font-size: 10.5px; }
.dm-opt-phases { display: none; }
.dm-opt-steps { gap: 0; }
.dm-opt-steps__pill { padding: 3px 6px; }
.dm-opt-steps__icon { font-size: 9px; }
.dm-opt-steps__text { font-size: 7.5px; }
.dm-opt-steps__line { width: 6px; }
.dm-opt-insight { padding: 5px 10px; gap: 5px; }
.dm-opt-insight__text { font-size: 8.5px; }
.dm-opt-insight__sep { height: 10px; }
}
@media (prefers-reduced-motion: reduce) {
.dm-opt-pulse { animation: none; }
.dm-opt-metric { animation: none; opacity: 1; transform: none; }
.dm-opt-insight__dot { animation: none; }
.dm-opt-card::before { animation: none; }
}
`;