885 lines
34 KiB
TypeScript
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'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 & 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 & 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; }
|
|
}
|
|
`;
|