"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 { 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 }, ]; /** * Bottom "Live Analytics" ticker. Self-contained: it owns its five fluctuation * timers and the state they mutate, so a tick re-renders only this ~10-node * subtree instead of the entire (canvas + overlay) OptimizationSection — which * previously re-reconciled on every timer fire, competing with scroll updates. */ const LiveInsightBar = React.memo(function LiveInsightBar() { 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); useEffect(() => { // Pause every ticker while the tab is hidden — no point re-rendering offscreen. const guard = (fn: () => void) => () => { if (!document.hidden) fn(); }; const ordersInterval = setInterval(guard(() => { setOrders((prev) => prev + (Math.random() > 0.4 ? 1 : 0)); }), 4500); const accuracyInterval = setInterval(guard(() => { setAccuracy((prev) => { const next = prev + (Math.random() - 0.5) * 0.15; return parseFloat(Math.min(99.1, Math.max(98.4, next)).toFixed(2)); }); }), 2800); const vehicleInterval = setInterval(guard(() => { setActiveVehicles((prev) => (prev === 5 ? (Math.random() > 0.5 ? 4 : 5) : (Math.random() > 0.3 ? 5 : 4))); }), 3500); const carbonInterval = setInterval(guard(() => { setCarbon((prev) => { const next = prev + (Math.random() - 0.5) * 0.2; return parseFloat(Math.min(-11.5, Math.max(-12.8, next)).toFixed(1)); }); }), 3200); const healthInterval = setInterval(guard(() => { setRouteHealth((prev) => { const next = prev + (Math.random() - 0.5) * 0.12; 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); }; }, []); return ( Live Analytics: {orders} Orders AI Accuracy: {accuracy}% Fleet: {activeVehicles}/5 EV Active Route Health: {routeHealth}% Carbon: {carbon}% ); }); /** Inner content of the "Without Optimization" panel — shared by the desktop * (scroll-reactive motion.aside) and mobile (static aside) layouts. */ function WithoutPanelBody() { return ( <>
System: Congested

Without Optimization

); } /** Inner content of the "With Doormile AI" panel — shared by both layouts. */ function WithPanelBody() { return ( <>
System: Optimized

With Doormile AI

); } export default function OptimizationSection() { const containerRef = useRef(null); const progressRef = useRef(0); const scroll = useMotionValue(0); const [phase, setPhase] = useState("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 [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); }; }, []); // 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: "70% 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; // 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 // route. Because the pin is a self-managed `position: fixed` driven by // `self.progress` (not a GSAP pin-spacer) and Lenis scrolls the real // document hooked to ScrollTrigger.update, the scrub + fixed pin work // unchanged under smooth scroll. 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(), 120); return () => { clearTimeout(refresh); st.kill(); }; }, [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]); 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]); // GPU transform (scaleX) instead of animating width, which forces layout each frame. const scanScaleX = useTransform(scroll, [0, 1], [0, 1]); 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 (
{/* ===== MOBILE: non-pinned vertical stack ===== */} {isMobile && (
Doormile AI Control Tower

AI Logistics Optimization Engine

Watch Doormile's AI engine transform chaotic logistics into precision-optimized delivery networks — reducing distance, fleet size, delays, and cost.

{/* 1. Without Optimization */} {/* 2. 3D Visualization (ambient) */}
{mountScene && (
)} Live AI optimization
{/* 3. With Doormile AI */} {/* 4. Metrics — final optimized values, 2-col grid */}
)} {/* ===== DESKTOP / TABLET: pinned scroll experience ===== */} {!isMobile && (
{/* Static backdrop (also the canvas loading state) */}
{/* 3D scene */} {mountScene && (
)} {/* Depth vignette */}
{/* Floating AI Status Badge replacing giant abstract sphere centerpiece */} {phase !== "chaos" && (
{PHASE_LABELS[phase]}
)}
{/* Center scan divider */} {/* AI scan sweep line */} {/* UI overlay */}
{/* Header */}
Doormile AI Control Tower AI Logistics Optimization Engine Watch Doormile's AI engine transform chaotic logistics into precision-optimized delivery networks — reducing distance, fleet size, delays, and cost in real time. {/* Workflow step indicators */} {WORKFLOW_STEPS.map((step, i) => { const isActive = phaseIndex >= step.activateAt; const isCurrent = phaseIndex === step.activateAt; return ( {i > 0 && } {step.icon} {step.label} ); })} {/* Scan progress bar */}
{/* Side comparison panels */}
{/* KPI metrics */}
{/* Bottom insight bar — isolated so its live tickers don't re-render the whole section (and never fight the scroll handler). */}
)}
); } 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; /* Promote the pinned layer to its own GPU compositing layer so scroll-driven pin/scrub updates never trigger main-thread paints of the rest of the page. */ will-change: transform; transform: translateZ(0); backface-visibility: hidden; } .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; /* 20px top inset to match the Workflow 2 / 3 connected cards (.dm-lb-card / .dm-st-card), so the gap above Workflow 1 (between the Control Tower stats bar and this card) is the same small, consistent seam used at every other workflow transition — not the old oversized 96px band. The header below is offset on its own to clear the fixed navbar (see .dm-opt-head). */ top: 20px !important; left: 20px !important; right: 20px !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: 28px 28px 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 { /* Expanded one tile beyond the (clipped) card so we can drift it via a GPU transform instead of animating background-position (which repaints each frame). */ content: ""; position: absolute; inset: -60px; 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; will-change: transform; } @keyframes dmOptGridDrift { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(60px, 60px, 0); } } /* 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: 20px !important; left: 20px !important; right: 20px !important; bottom: 0 !important; border-radius: 28px 28px 0 0 !important; } } @media (max-width: 767px) { .dm-opt-card { top: 10px !important; left: 10px !important; right: 10px !important; bottom: 0 !important; border-radius: 20px 20px 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 { /* Offset below the site's fixed navbar (~104px desktop / ~100px mobile when .dm-header-scrolled is active). The card now sits at top:20px (was 96px), so the navbar clearance lives here on the header itself — mirroring how the Workflow 2 / 3 sections offset their inner header while the card stays at 20px. This keeps the eyebrow/title clear of the navbar (z-index 10000) without re-introducing the oversized top gap above Workflow 1. */ position: absolute; top: clamp(96px, 12vh, 116px); 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%; width: 100%; transform-origin: left center; 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.92)}; border: 1px solid ${rgba(COLORS.slate, 0.22)}; /* backdrop blur removed — panel opacity is scroll-driven, so the blur was recomputed every scroll frame; a near-opaque fill reads the same and is free. */ } .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.92)}; border: 1px solid ${rgba(COLORS.slate, 0.25)}; /* backdrop blur removed — 5 metric cards animate on scroll; per-frame blur recompute was a hotspot. */ 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; } } /* ===== 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; /* Top gap separates this card from the hero stats bar above; sides stay 10px and bottom stays flush so the Performance card butts against it below. */ margin: 16px 10px 0; 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; } .dm-opt-insight__dot { animation: none; } .dm-opt-card::before { animation: none; } } `;