update screen fix
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import { Canvas, useFrame } from "@react-three/fiber";
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import { EffectComposer, Bloom } from "@react-three/postprocessing";
|
||||
import { KernelSize } from "postprocessing";
|
||||
import { COLORS } from "./constants";
|
||||
@@ -15,40 +15,78 @@ type Props = {
|
||||
progress: React.RefObject<number>;
|
||||
reduced?: boolean;
|
||||
isMobile?: boolean;
|
||||
isTablet?: boolean;
|
||||
/** Pause the render loop when the section is scrolled off-screen. */
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Device-specific camera framing. The mobile/tablet scene renders into a much
|
||||
* smaller, near-square block than the desktop full-screen card, so the camera
|
||||
* is pulled back, raised, and widened (higher fov) to keep every node in frame
|
||||
* without clipping. `lerp(start, end, progress)` eases from the chaotic wide
|
||||
* view to the settled framing as the narrative progresses.
|
||||
*/
|
||||
type CameraFraming = {
|
||||
radiusStart: number;
|
||||
radiusEnd: number;
|
||||
heightStart: number;
|
||||
heightEnd: number;
|
||||
lookAtY: number;
|
||||
fov: number;
|
||||
};
|
||||
|
||||
const FRAMING: Record<"desktop" | "tablet" | "mobile", CameraFraming> = {
|
||||
desktop: { radiusStart: 17, radiusEnd: 13, heightStart: 9, heightEnd: 6.5, lookAtY: 2.4, fov: 50 },
|
||||
tablet: { radiusStart: 19, radiusEnd: 15, heightStart: 9.5, heightEnd: 7, lookAtY: 2.6, fov: 54 },
|
||||
mobile: { radiusStart: 22, radiusEnd: 18, heightStart: 11, heightEnd: 8, lookAtY: 3, fov: 62 },
|
||||
};
|
||||
|
||||
/** Slow cinematic camera move from a high chaotic view to a settled framing. */
|
||||
function CameraRig({ progress }: { progress: React.RefObject<number> }) {
|
||||
function CameraRig({ progress, framing }: { progress: React.RefObject<number>; framing: CameraFraming }) {
|
||||
const eased = useRef(0);
|
||||
const camera = useThree((s) => s.camera);
|
||||
|
||||
// Re-frame on device change (orientation / breakpoint crossing): the fov is a
|
||||
// construction-time prop on <Canvas>, so update it imperatively here.
|
||||
useEffect(() => {
|
||||
if ("fov" in camera) {
|
||||
(camera as THREE_PerspectiveCamera).fov = framing.fov;
|
||||
(camera as THREE_PerspectiveCamera).updateProjectionMatrix();
|
||||
}
|
||||
}, [camera, framing.fov]);
|
||||
|
||||
useFrame((state, dt) => {
|
||||
const p = progress.current ?? 0;
|
||||
eased.current = damp(eased.current, p, 1.5, dt);
|
||||
const e = eased.current;
|
||||
const t = state.clock.elapsedTime;
|
||||
|
||||
const radius = lerp(17, 13, e);
|
||||
const radius = lerp(framing.radiusStart, framing.radiusEnd, e);
|
||||
const angle = lerp(-0.5, 0.45, e) + t * 0.02;
|
||||
const height = lerp(9, 6.5, e) + Math.sin(t * 0.4) * 0.3;
|
||||
const height = lerp(framing.heightStart, framing.heightEnd, e) + Math.sin(t * 0.4) * 0.3;
|
||||
|
||||
const cam = state.camera;
|
||||
cam.position.x = Math.sin(angle) * radius;
|
||||
cam.position.z = Math.cos(angle) * radius;
|
||||
cam.position.y = height;
|
||||
cam.lookAt(0, 2.4, 0);
|
||||
cam.lookAt(0, framing.lookAtY, 0);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
function OptimizationCanvas({ progress, reduced = false, isMobile = false, active = true }: Props) {
|
||||
// Minimal structural type so we can set fov without importing three's types here.
|
||||
type THREE_PerspectiveCamera = { fov: number; updateProjectionMatrix: () => void };
|
||||
|
||||
function OptimizationCanvas({ progress, reduced = false, isMobile = false, isTablet = false, active = true }: Props) {
|
||||
const cityCount = isMobile ? 48 : 90;
|
||||
const framing = isMobile ? FRAMING.mobile : isTablet ? FRAMING.tablet : FRAMING.desktop;
|
||||
|
||||
return (
|
||||
<Canvas
|
||||
flat
|
||||
dpr={[1, isMobile || reduced ? 1.25 : 1.5]}
|
||||
camera={{ position: [0, 9, 19], fov: 50, near: 0.1, far: 120 }}
|
||||
camera={{ position: [0, framing.heightStart, framing.radiusStart], fov: framing.fov, near: 0.1, far: 120 }}
|
||||
gl={{ antialias: !isMobile, powerPreference: "high-performance", alpha: false }}
|
||||
frameloop={active ? "always" : "never"}
|
||||
>
|
||||
@@ -56,7 +94,7 @@ function OptimizationCanvas({ progress, reduced = false, isMobile = false, activ
|
||||
<fog attach="fog" args={[COLORS.bg, 18, 52]} />
|
||||
<ambientLight intensity={0.6} />
|
||||
|
||||
<CameraRig progress={progress} />
|
||||
<CameraRig progress={progress} framing={framing} />
|
||||
<HologramCity progress={progress} count={cityCount} reduced={reduced} />
|
||||
<RouteSystem progress={progress} reduced={reduced} isMobile={isMobile} />
|
||||
<VehicleFleet progress={progress} reduced={reduced} />
|
||||
@@ -68,10 +106,10 @@ function OptimizationCanvas({ progress, reduced = false, isMobile = false, activ
|
||||
<EffectComposer multisampling={isMobile ? 0 : 2}>
|
||||
<Bloom
|
||||
mipmapBlur
|
||||
intensity={isMobile ? 0.7 : 1.0}
|
||||
intensity={isMobile ? 0.5 : 1.0}
|
||||
luminanceThreshold={0.15}
|
||||
luminanceSmoothing={0.04}
|
||||
radius={isMobile ? 0.6 : 0.75}
|
||||
radius={isMobile ? 0.55 : 0.75}
|
||||
kernelSize={KernelSize.MEDIUM}
|
||||
/>
|
||||
</EffectComposer>
|
||||
|
||||
@@ -109,6 +109,46 @@ const LiveInsightBar = React.memo(function LiveInsightBar() {
|
||||
);
|
||||
});
|
||||
|
||||
/** Inner content of the "Without Optimization" panel — shared by the desktop
|
||||
* (scroll-reactive motion.aside) and mobile (static aside) layouts. */
|
||||
function WithoutPanelBody() {
|
||||
return (
|
||||
<>
|
||||
<div className="dm-opt-panel__badge">
|
||||
<span className="dm-opt-pulse dm-opt-pulse--red" /> System: Congested
|
||||
</div>
|
||||
<h3>Without Optimization</h3>
|
||||
<ul>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> Chaotic overlapping routes</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> Duplicate & idle trips</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> 8 vehicles required</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> 23 delivery delays</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> +18% cost overrun</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** Inner content of the "With Doormile AI" panel — shared by both layouts. */
|
||||
function WithPanelBody() {
|
||||
return (
|
||||
<>
|
||||
<div className="dm-opt-panel__badge dm-opt-panel__badge--good">
|
||||
<span className="dm-opt-pulse dm-opt-pulse--green" /> System: Optimized
|
||||
</div>
|
||||
<h3>With Doormile AI</h3>
|
||||
<ul>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Optimized route clusters</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Intelligent vehicle assignment</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Multi-trip & EV planning</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Zero delivery delays</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> 18% cost saved</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Carbon footprint reduced</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function OptimizationSection() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const progressRef = useRef(0);
|
||||
@@ -119,21 +159,30 @@ export default function OptimizationSection() {
|
||||
const [mountScene, setMountScene] = useState(false);
|
||||
const [sceneActive, setSceneActive] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [isTablet, setIsTablet] = useState(false);
|
||||
const [reduced, setReduced] = useState(false);
|
||||
|
||||
// Final-state metrics value for the mobile stack: a constant MotionValue so
|
||||
// MetricsPanel renders the optimized numbers without scroll-driven counting.
|
||||
const staticFinal = useMotionValue(1);
|
||||
|
||||
// Environment detection (client only).
|
||||
useEffect(() => {
|
||||
const mqMobile = window.matchMedia("(max-width: 767px)");
|
||||
const mqTablet = window.matchMedia("(min-width: 768px) and (max-width: 1024px)");
|
||||
const mqReduce = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
const sync = () => {
|
||||
setIsMobile(mqMobile.matches);
|
||||
setIsTablet(mqTablet.matches);
|
||||
setReduced(mqReduce.matches);
|
||||
};
|
||||
sync();
|
||||
mqMobile.addEventListener("change", sync);
|
||||
mqTablet.addEventListener("change", sync);
|
||||
mqReduce.addEventListener("change", sync);
|
||||
return () => {
|
||||
mqMobile.removeEventListener("change", sync);
|
||||
mqTablet.removeEventListener("change", sync);
|
||||
mqReduce.removeEventListener("change", sync);
|
||||
};
|
||||
}, []);
|
||||
@@ -176,6 +225,10 @@ export default function OptimizationSection() {
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
// Mobile renders a non-pinned vertical stack (see the `isMobile` branch in
|
||||
// render): no ScrollTrigger pin/scrub at all. Bail before creating one so
|
||||
// pinState stays "before" and the section keeps its natural auto height.
|
||||
if (isMobile) return;
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
// NOTE: global Lenis (src/animations/SmoothScroll.tsx) is active on this
|
||||
@@ -213,7 +266,29 @@ export default function OptimizationSection() {
|
||||
clearTimeout(refresh);
|
||||
st.kill();
|
||||
};
|
||||
}, [scroll]);
|
||||
}, [scroll, isMobile]);
|
||||
|
||||
// Mobile ambient loop: with no scroll scrub, gently oscillate the shared
|
||||
// progress inside the *optimized* band so the hologram stays "alive" and
|
||||
// coherent with the final metrics. Held static under reduced-motion.
|
||||
useEffect(() => {
|
||||
if (!isMobile) return;
|
||||
if (reduced) {
|
||||
progressRef.current = 0.85;
|
||||
return;
|
||||
}
|
||||
if (!sceneActive) return;
|
||||
let raf = 0;
|
||||
let start = 0;
|
||||
const tick = (ts: number) => {
|
||||
if (!start) start = ts;
|
||||
const t = (ts - start) / 1000;
|
||||
progressRef.current = 0.76 + Math.sin(t * 0.5) * 0.16; // ~0.60 → 0.92
|
||||
raf = requestAnimationFrame(tick);
|
||||
};
|
||||
raf = requestAnimationFrame(tick);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [isMobile, reduced, sceneActive]);
|
||||
|
||||
// Overlay reactions to scroll (no React re-render — direct DOM updates).
|
||||
const leftOpacity = useTransform(scroll, [0.3, 0.55], [1, 0.32]);
|
||||
@@ -231,9 +306,59 @@ export default function OptimizationSection() {
|
||||
return (
|
||||
<section
|
||||
ref={containerRef}
|
||||
className={`dm-opt is-${pinState}`}
|
||||
className={`dm-opt is-${pinState}${isMobile ? " dm-opt--mobile" : ""}`}
|
||||
aria-label="AI Logistics Optimization"
|
||||
>
|
||||
{/* ===== MOBILE: non-pinned vertical stack ===== */}
|
||||
{isMobile && (
|
||||
<div className="dm-opt-mobile">
|
||||
<header className="dm-opt-mhead">
|
||||
<div className="dm-opt-eyebrow">
|
||||
<span className="dm-opt-dot" /> Doormile AI Control Tower
|
||||
</div>
|
||||
<h2>AI Logistics Optimization Engine</h2>
|
||||
<p>
|
||||
Watch Doormile's AI engine transform chaotic logistics into precision-optimized delivery networks — reducing distance, fleet size, delays, and cost.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* 1. Without Optimization */}
|
||||
<aside className="dm-opt-panel dm-opt-panel--bad dm-opt-mpanel">
|
||||
<WithoutPanelBody />
|
||||
</aside>
|
||||
|
||||
{/* 2. 3D Visualization (ambient) */}
|
||||
<div className="dm-opt-mobile__scene">
|
||||
{mountScene && (
|
||||
<div className="dm-opt-canvas">
|
||||
<OptimizationCanvas
|
||||
progress={progressRef}
|
||||
reduced={reduced}
|
||||
isMobile
|
||||
active={sceneActive}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<span className="dm-opt-mobile__scene-tag">
|
||||
<span className="dm-opt-dot" /> Live AI optimization
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 3. With Doormile AI */}
|
||||
<aside className="dm-opt-panel dm-opt-panel--good dm-opt-mpanel">
|
||||
<WithPanelBody />
|
||||
</aside>
|
||||
|
||||
{/* 4. Metrics — final optimized values, 2-col grid */}
|
||||
<div className="dm-opt-mfoot">
|
||||
<MetricsPanel scroll={staticFinal} />
|
||||
<LiveInsightBar />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ===== DESKTOP / TABLET: pinned scroll experience ===== */}
|
||||
{!isMobile && (
|
||||
<div className="dm-opt-sticky">
|
||||
<div className="dm-opt-card">
|
||||
{/* Static backdrop (also the canvas loading state) */}
|
||||
@@ -246,6 +371,7 @@ export default function OptimizationSection() {
|
||||
progress={progressRef}
|
||||
reduced={reduced}
|
||||
isMobile={isMobile}
|
||||
isTablet={isTablet}
|
||||
// Only run the render loop while the section is actually pinned
|
||||
// (filling the viewport). At a workflow seam two sections can both
|
||||
// satisfy their activeIo margin; without the pin gate their two
|
||||
@@ -375,35 +501,14 @@ export default function OptimizationSection() {
|
||||
className="dm-opt-panel dm-opt-panel--bad"
|
||||
style={{ opacity: leftOpacity, filter: leftFilter }}
|
||||
>
|
||||
<div className="dm-opt-panel__badge">
|
||||
<span className="dm-opt-pulse dm-opt-pulse--red" /> System: Congested
|
||||
</div>
|
||||
<h3>Without Optimization</h3>
|
||||
<ul>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> Chaotic overlapping routes</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> Duplicate & idle trips</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> 8 vehicles required</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> 23 delivery delays</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--x">✖</span> +18% cost overrun</li>
|
||||
</ul>
|
||||
<WithoutPanelBody />
|
||||
</motion.aside>
|
||||
|
||||
<motion.aside
|
||||
className="dm-opt-panel dm-opt-panel--good"
|
||||
style={{ opacity: rightOpacity }}
|
||||
>
|
||||
<div className="dm-opt-panel__badge dm-opt-panel__badge--good">
|
||||
<span className="dm-opt-pulse dm-opt-pulse--green" /> System: Optimized
|
||||
</div>
|
||||
<h3>With Doormile AI</h3>
|
||||
<ul>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Optimized route clusters</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Intelligent vehicle assignment</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Multi-trip & EV planning</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Zero delivery delays</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> 18% cost saved</li>
|
||||
<li><span className="dm-opt-marker dm-opt-marker--ok">✔</span> Carbon footprint reduced</li>
|
||||
</ul>
|
||||
<WithPanelBody />
|
||||
</motion.aside>
|
||||
</div>
|
||||
|
||||
@@ -417,6 +522,7 @@ export default function OptimizationSection() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style>{styles}</style>
|
||||
</section>
|
||||
@@ -893,6 +999,98 @@ const styles = `
|
||||
.dm-opt-insight__text { font-size: 8.5px; }
|
||||
.dm-opt-insight__sep { height: 10px; }
|
||||
}
|
||||
|
||||
/* ===== MOBILE STACKED LAYOUT (<=767px) — non-pinned vertical flow =====
|
||||
Rendered by the isMobile branch as a normal-flow .dm-opt-mobile container
|
||||
(header → Without → 3D → With → metrics). These rules come after the block
|
||||
above so they win for the elements that actually exist on mobile. */
|
||||
@media (max-width: 767px) {
|
||||
/* Un-pin: natural height, no fixed sticky, no 200/230vh scroll runway.
|
||||
(.dm-opt--mobile out-specifies the base .dm-opt height rules.) */
|
||||
.dm-opt.dm-opt--mobile { height: auto; }
|
||||
|
||||
.dm-opt-mobile {
|
||||
position: relative;
|
||||
margin: 0 10px;
|
||||
padding: 24px 13px 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #06101f 0%, #020617 55%, #030a18 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-bottom: none;
|
||||
/* Flat bottom + flush so the Performance card (.dm-wf1-card) butts directly
|
||||
against it as one continuous container (matches Workflow1 ≤767 styles). */
|
||||
border-radius: 20px 20px 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.dm-opt-mhead { text-align: center; padding: 0 4px; }
|
||||
.dm-opt-mhead .dm-opt-eyebrow { font-size: 10px; }
|
||||
.dm-opt-mhead h2 {
|
||||
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
|
||||
margin: 10px 0 6px !important; padding: 0 !important; color: #F8FAFC !important;
|
||||
font-weight: 700 !important; text-transform: none !important;
|
||||
font-size: clamp(20px, 6.2vw, 26px) !important; line-height: 1.15 !important;
|
||||
letter-spacing: -0.015em !important;
|
||||
}
|
||||
.dm-opt-mhead p {
|
||||
margin: 0 auto !important; padding: 0 !important; color: ${COLORS.textDim} !important;
|
||||
max-width: 40ch; font-size: 12.5px !important; line-height: 1.5 !important;
|
||||
}
|
||||
|
||||
/* Comparison panels → full-width static cards, fully visible, readable.
|
||||
Trim the heavy glow (box-shadow) but keep the colored border for identity. */
|
||||
.dm-opt-mobile .dm-opt-panel {
|
||||
width: 100%; box-sizing: border-box;
|
||||
opacity: 1 !important; filter: none !important;
|
||||
padding: 15px 16px; border-radius: 16px; box-shadow: none;
|
||||
}
|
||||
.dm-opt-mobile .dm-opt-panel h3 {
|
||||
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
|
||||
font-size: 16px !important; margin: 9px 0 10px !important;
|
||||
}
|
||||
.dm-opt-mobile .dm-opt-panel ul { gap: 8px; }
|
||||
.dm-opt-mobile .dm-opt-panel li { font-size: 12.5px !important; line-height: 1.35 !important; }
|
||||
.dm-opt-mobile .dm-opt-marker { width: 18px; height: 18px; font-size: 10px; border-radius: 6px; }
|
||||
.dm-opt-mobile .dm-opt-panel__badge { font-size: 9.5px; padding: 4px 9px; }
|
||||
|
||||
/* 3D visualization block — contained, ~40vh, premium but not dominant. */
|
||||
.dm-opt-mobile__scene {
|
||||
position: relative; width: 100%; height: 40vh; min-height: 260px; max-height: 360px;
|
||||
border-radius: 16px; overflow: hidden;
|
||||
background: radial-gradient(120% 90% at 50% 30%, ${rgba(COLORS.cyan, 0.06)} 0%, ${COLORS.bg} 70%);
|
||||
border: 1px solid ${rgba(COLORS.cyan, 0.14)};
|
||||
}
|
||||
.dm-opt-mobile__scene .dm-opt-canvas { position: absolute; inset: 0; z-index: 0; }
|
||||
.dm-opt-mobile__scene-tag {
|
||||
position: absolute; left: 10px; top: 10px; z-index: 1;
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700;
|
||||
color: #E2E8F0; padding: 4px 9px; border-radius: 999px;
|
||||
background: ${rgba(COLORS.ink, 0.72)}; border: 1px solid ${rgba(COLORS.cyan, 0.22)};
|
||||
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* Metrics → 2-col grid (5th card spans full width), final optimized values,
|
||||
readable sizing so nothing truncates. */
|
||||
.dm-opt-mfoot { padding: 0; }
|
||||
.dm-opt-mobile .dm-opt-metrics {
|
||||
grid-template-columns: repeat(2, 1fr); gap: 8px; max-width: none;
|
||||
}
|
||||
.dm-opt-mobile .dm-opt-metric { padding: 12px 13px 11px; border-radius: 12px; }
|
||||
.dm-opt-mobile .dm-opt-metric:last-child { grid-column: 1 / -1; }
|
||||
.dm-opt-mobile .dm-opt-metric__label { font-size: 10px; letter-spacing: 0.02em; }
|
||||
.dm-opt-mobile .dm-opt-metric__value { font-size: clamp(20px, 6.5vw, 26px); }
|
||||
|
||||
/* Insight bar wraps instead of overflowing. */
|
||||
.dm-opt-mobile .dm-opt-insight {
|
||||
flex-wrap: wrap; max-width: none; gap: 6px 10px; padding: 8px 14px; margin-top: 4px;
|
||||
}
|
||||
.dm-opt-mobile .dm-opt-insight__text { font-size: 10px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dm-opt-pulse { animation: none; }
|
||||
.dm-opt-metric { animation: none; opacity: 1; transform: none; }
|
||||
|
||||
Reference in New Issue
Block a user