update miletruth page and remove unwanted files
This commit is contained in:
516
src/components/strategy/StrategySection.tsx
Normal file
516
src/components/strategy/StrategySection.tsx
Normal file
@@ -0,0 +1,516 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { motion, useMotionValue, useTransform, type MotionValue } from "framer-motion";
|
||||
import gsap from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
import { STAGES, N } from "./theme";
|
||||
|
||||
const StrategyCanvas = dynamic(() => import("./StrategyCanvas"), { ssr: false });
|
||||
|
||||
/** Center of each stage's scroll window (0…1). */
|
||||
const CENTER = (i: number) => i / (N - 1);
|
||||
|
||||
/** Persistent top rail: the 5 stages, current one highlighted. */
|
||||
function StageRail({ active }: { active: number }) {
|
||||
return (
|
||||
<div className="dm-st-rail" aria-hidden>
|
||||
{STAGES.map((s, i) => {
|
||||
const state = i < active ? "done" : i === active ? "current" : "todo";
|
||||
return (
|
||||
<React.Fragment key={s.n}>
|
||||
{i > 0 && <span className={`dm-st-rail__line is-${i <= active ? "on" : "off"}`} />}
|
||||
<div className={`dm-st-rail__step is-${state}`} style={{ ["--c" as string]: s.theme }}>
|
||||
<span className="dm-st-rail__num">{i < active ? "✓" : s.n}</span>
|
||||
<span className="dm-st-rail__title">{s.kicker}</span>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** A cross-fading glass content card pinned to one side, themed per stage. */
|
||||
function StageCard({
|
||||
i,
|
||||
scroll,
|
||||
side,
|
||||
children,
|
||||
}: {
|
||||
i: number;
|
||||
scroll: MotionValue<number>;
|
||||
side: "left" | "right";
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const c = CENTER(i);
|
||||
const opacity = useTransform(scroll, [c - 0.14, c - 0.06, c + 0.06, c + 0.14], [0, 1, 1, 0]);
|
||||
const y = useTransform(scroll, [c - 0.14, c - 0.05], [34, 0]);
|
||||
const s = STAGES[i];
|
||||
return (
|
||||
<motion.div
|
||||
className={`dm-st-card-story is-${side}`}
|
||||
style={{ opacity, y, ["--c" as string]: s.theme }}
|
||||
>
|
||||
<div className="dm-st-card-story__head">
|
||||
<span className="dm-st-pillar__num">{s.n}</span>
|
||||
<span className="dm-st-pillar__kicker">{s.kicker}</span>
|
||||
</div>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Strategy" — a premium, Apple-keynote-style 3D scroll-storytelling section.
|
||||
* A single GSAP ScrollTrigger maps scroll to a normalized progress that drives
|
||||
* the R3F camera through five floating glass stages while DOM glass cards
|
||||
* cross-fade in lockstep. Pins via a self-managed fixed element (the site's
|
||||
* fixed header + an ancestor `overflow:hidden` break CSS sticky / GSAP pin).
|
||||
*/
|
||||
export default function StrategySection({ connected = false }: { connected?: boolean } = {}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const progressRef = useRef(0);
|
||||
const scroll = useMotionValue(0);
|
||||
|
||||
const [pinState, setPinState] = useState<"before" | "pinned" | "after">("before");
|
||||
const [active, setActive] = useState(0);
|
||||
const [mountScene, setMountScene] = useState(false);
|
||||
const [sceneActive, setSceneActive] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [reduced, setReduced] = useState(false);
|
||||
|
||||
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); };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
const mountIo = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries.some((e) => e.isIntersecting)) {
|
||||
setMountScene(true);
|
||||
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(); };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
let lastPin: "before" | "pinned" | "after" = "before";
|
||||
let lastActive = 0;
|
||||
const st = ScrollTrigger.create({
|
||||
trigger: el,
|
||||
start: "top top",
|
||||
end: "bottom bottom",
|
||||
scrub: 0.5,
|
||||
invalidateOnRefresh: true,
|
||||
onUpdate: (self) => {
|
||||
const p = self.progress;
|
||||
progressRef.current = p;
|
||||
scroll.set(p);
|
||||
const ns = p <= 0.0002 ? "before" : p >= 0.9998 ? "after" : "pinned";
|
||||
if (ns !== lastPin) { lastPin = ns; setPinState(ns); }
|
||||
const na = Math.round(p * (N - 1));
|
||||
if (na !== lastActive) { lastActive = na; setActive(na); }
|
||||
},
|
||||
});
|
||||
const refresh = setTimeout(() => ScrollTrigger.refresh(), 300);
|
||||
return () => { clearTimeout(refresh); st.kill(); };
|
||||
}, [scroll]);
|
||||
|
||||
// Intro hint fades out as the journey begins.
|
||||
const introOpacity = useTransform(scroll, [0, 0.03, 0.06], [1, 1, 0]);
|
||||
// Persistent header fades in after the intro.
|
||||
const headerOpacity = useTransform(scroll, [0.02, 0.07], [0, 1]);
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={containerRef}
|
||||
className={`dm-st is-${pinState}${connected ? " is-connected" : ""}`}
|
||||
aria-label="Strategy — Happier Riders. Higher Fulfillment."
|
||||
>
|
||||
<div className="dm-st-sticky">
|
||||
<div className="dm-st-card">
|
||||
{mountScene && (
|
||||
<div className="dm-st-canvas">
|
||||
<StrategyCanvas progress={progressRef} reduced={reduced} isMobile={isMobile} active={sceneActive} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="dm-st-ui">
|
||||
{/* Persistent header */}
|
||||
<motion.div className="dm-st-top" style={{ opacity: headerOpacity }}>
|
||||
<div className="dm-st-eyebrow"><span className="dm-st-dot" /> MileTruth Strategy Engine</div>
|
||||
<StageRail active={active} />
|
||||
</motion.div>
|
||||
|
||||
{/* Intro hint */}
|
||||
<motion.div className="dm-st-scrollhint" style={{ opacity: introOpacity }}>
|
||||
<span>Scroll to follow the strategy</span>
|
||||
<span className="dm-st-arrow">↓</span>
|
||||
</motion.div>
|
||||
|
||||
{/* STAGE 01 — INPUT (green): orders + riders enter the system */}
|
||||
<StageCard i={0} scroll={scroll} side="left">
|
||||
<h3 className="dm-st-pillar__title">Orders & riders enter the system</h3>
|
||||
<div className="dm-st-grid2">
|
||||
<div className="dm-st-tile">
|
||||
<span className="dm-st-tile__ico">📄</span>
|
||||
<span className="dm-st-tile__num">59</span>
|
||||
<span className="dm-st-tile__lbl">Orders</span>
|
||||
<span className="dm-st-tile__sub">Order intake</span>
|
||||
</div>
|
||||
<div className="dm-st-tile">
|
||||
<span className="dm-st-tile__ico">🧑✈️</span>
|
||||
<span className="dm-st-tile__num">4</span>
|
||||
<span className="dm-st-tile__lbl">Total Riders</span>
|
||||
<span className="dm-st-tile__sub">Available fleet</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dm-st-file">
|
||||
<span className="dm-st-file__ico">⬆️</span>
|
||||
<span className="dm-st-file__name">orders.csv</span>
|
||||
<span className="dm-st-file__meta">59 rows · uploaded</span>
|
||||
<span className="dm-st-file__ok">✓</span>
|
||||
</div>
|
||||
<ul className="dm-st-riders">
|
||||
{[
|
||||
{ a: "A", name: "Rider A", v: "EV Bike", cap: "12 orders" },
|
||||
{ a: "B", name: "Rider B", v: "Auto", cap: "18 orders" },
|
||||
{ a: "C", name: "Rider C", v: "Cargo Truck", cap: "20 orders" },
|
||||
{ a: "D", name: "Rider D", v: "EV Van", cap: "9 orders" },
|
||||
].map((r) => (
|
||||
<li key={r.a} className="dm-st-rider">
|
||||
<span className="dm-st-rider__av">{r.a}<i /></span>
|
||||
<span className="dm-st-rider__name">{r.name}</span>
|
||||
<span className="dm-st-rider__veh">{r.v}</span>
|
||||
<span className="dm-st-rider__cap">{r.cap}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</StageCard>
|
||||
|
||||
{/* STAGE 02 — PARALLEL EXECUTION (purple): 6 strategies at once */}
|
||||
<StageCard i={1} scroll={scroll} side="right">
|
||||
<h3 className="dm-st-pillar__title">Six strategies, evaluated in parallel</h3>
|
||||
<div className="dm-st-engines">
|
||||
<div className="dm-st-engine">
|
||||
<div className="dm-st-engine__head"><span className="dm-st-engine__name">Legacy Engine</span><span className="dm-st-engine__tag">Baseline</span></div>
|
||||
<div className="dm-st-pills">
|
||||
{["Proximity", "Balanced", "Fuel Saver"].map((p) => <span key={p} className="dm-st-pill">{p}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="dm-st-engine is-unified">
|
||||
<div className="dm-st-engine__head"><span className="dm-st-engine__name">Unified Engine</span><span className="dm-st-engine__tag dm-st-engine__tag--u">MileTruth</span></div>
|
||||
<div className="dm-st-pills">
|
||||
{["EV Aware", "Multi Trip", "Time Aware"].map((p) => <span key={p} className="dm-st-pill dm-st-pill--u">{p}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="dm-st-foot"><span className="dm-st-livedot" /> All 6 strategies run at the same time</p>
|
||||
</StageCard>
|
||||
|
||||
{/* STAGE 03 — SMART OPTIMIZATION (blue): validation pipeline */}
|
||||
<StageCard i={2} scroll={scroll} side="left">
|
||||
<h3 className="dm-st-pillar__title">Routes validated & optimized</h3>
|
||||
<div className="dm-st-pipe">
|
||||
{[
|
||||
{ ico: "⚙️", name: "VRP Optimizer", desc: "Google OR-Tools solver" },
|
||||
{ ico: "🔋", name: "Battery Simulation", desc: "EV range & charging feasibility" },
|
||||
{ ico: "⏱️", name: "SLA Validator", desc: "ETA vs promised window" },
|
||||
].map((m, j) => (
|
||||
<React.Fragment key={m.name}>
|
||||
{j > 0 && <span className="dm-st-pipe__arrow"><i /></span>}
|
||||
<div className="dm-st-pipe__node">
|
||||
<span className="dm-st-pipe__ico">{m.ico}</span>
|
||||
<span className="dm-st-pipe__name">{m.name}</span>
|
||||
<span className="dm-st-pipe__desc">{m.desc}</span>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</StageCard>
|
||||
|
||||
{/* STAGE 04 — PERFORMANCE GRADING (orange): every strategy scored */}
|
||||
<StageCard i={3} scroll={scroll} side="right">
|
||||
<h3 className="dm-st-pillar__title">Every strategy is scored</h3>
|
||||
<div className="dm-st-stars"><span className="dm-st-stars__on">★★★★</span><span className="dm-st-stars__off">★</span><span className="dm-st-stars__txt">4.5 / 5 grade</span></div>
|
||||
<ul className="dm-st-metrics">
|
||||
{[
|
||||
{ name: "Fulfillment Rate", score: "88%", w: 88, status: "Good", desc: "Orders delivered vs total" },
|
||||
{ name: "SLA Compliance", score: "95%", w: 95, status: "Pass", desc: "On-time within window" },
|
||||
{ name: "Efficiency Score", score: "92", w: 92, status: "Strong", desc: "Distance & fleet usage" },
|
||||
{ name: "Battery & Route", score: "OK", w: 100, status: "Feasible", desc: "EV range respected" },
|
||||
].map((m) => (
|
||||
<li key={m.name} className="dm-st-metric">
|
||||
<span className="dm-st-metric__name">{m.name}</span>
|
||||
<span className="dm-st-metric__bar"><i style={{ width: `${m.w}%` }} /></span>
|
||||
<span className="dm-st-metric__score">{m.score}</span>
|
||||
<span className="dm-st-metric__status">✓ {m.status}</span>
|
||||
<span className="dm-st-metric__desc">{m.desc}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</StageCard>
|
||||
|
||||
{/* STAGE 05 — STRATEGY COMPARISON (red, hero): the winner */}
|
||||
<StageCard i={4} scroll={scroll} side="right">
|
||||
<div className="dm-st-winner">
|
||||
<span className="dm-st-winner__eyebrow">Best strategy recommendation</span>
|
||||
<div className="dm-st-winner__name"><span className="dm-st-trophy">🏆</span> EV Aware</div>
|
||||
<span className="dm-st-winner__grade">High Performance Grade</span>
|
||||
</div>
|
||||
<div className="dm-st-hero">
|
||||
<div className="dm-st-hero__ring">
|
||||
<span className="dm-st-hero__pct">88%</span>
|
||||
<span className="dm-st-hero__sub">Score</span>
|
||||
</div>
|
||||
<ul className="dm-st-wins">
|
||||
<li><strong>52/59</strong> Orders Fulfilled</li>
|
||||
<li><strong>88%</strong> Performance Score</li>
|
||||
<li><strong>3</strong> SLA Violations</li>
|
||||
<li><strong>A</strong> Performance Grade</li>
|
||||
</ul>
|
||||
</div>
|
||||
</StageCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>{styles}</style>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = `
|
||||
.dm-st { position: relative; height: 620vh; background: transparent; }
|
||||
.dm-st-sticky { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; overflow: hidden; }
|
||||
.dm-st.is-pinned .dm-st-sticky { position: fixed; top: 0; left: 0; }
|
||||
.dm-st.is-after .dm-st-sticky { position: absolute; top: auto; bottom: 0; }
|
||||
|
||||
.dm-st-card {
|
||||
position: absolute !important; inset: 16px !important;
|
||||
border-radius: 28px !important; overflow: hidden !important;
|
||||
background: radial-gradient(120% 100% at 50% 0%, #ffffff 0%, #eef1f6 60%, #e6eaf2 100%) !important;
|
||||
border: 1px solid rgba(15,23,42,0.08) !important;
|
||||
box-shadow: 0 30px 90px -34px rgba(15,23,42,0.4) !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
@media (max-width: 767px) { .dm-st-card { inset: 10px !important; border-radius: 20px !important; } }
|
||||
|
||||
/* Connected mode (inside Workflow 3): flatten the card's bottom so the Strategy
|
||||
content card below butts directly against it — same seam as Workflow 1 & 2. */
|
||||
.dm-st.is-connected .dm-st-card {
|
||||
top: 16px !important; left: 16px !important; right: 16px !important; bottom: 0 !important;
|
||||
border-radius: 28px 28px 0 0 !important; border-bottom: none !important;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.dm-st.is-connected .dm-st-card {
|
||||
top: 10px !important; left: 10px !important; right: 10px !important; bottom: 0 !important;
|
||||
border-radius: 20px 20px 0 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dm-st-canvas { position: absolute; inset: 0; z-index: 1; }
|
||||
.dm-st-canvas canvas { display: block; }
|
||||
|
||||
.dm-st-ui { position: absolute; inset: 0; z-index: 4; pointer-events: none;
|
||||
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif; color: #0f172a; }
|
||||
|
||||
/* ---- Persistent header: title + 5-stage rail ---- */
|
||||
.dm-st-top { position: absolute; top: clamp(96px, 13vh, 128px); left: 0; right: 0; z-index: 5;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 0 16px; }
|
||||
.dm-st-eyebrow { display: inline-flex; align-items: center; gap: 8px; font-size: 11px; line-height: 1.35;
|
||||
letter-spacing: 0.28em; text-transform: uppercase; color: #475569; padding: 9px 18px; border-radius: 999px;
|
||||
background: rgba(255,255,255,0.72); border: 1px solid rgba(15,23,42,0.08); backdrop-filter: blur(10px); white-space: nowrap; }
|
||||
.dm-st-dot { width: 6px; height: 6px; border-radius: 50%; background: #6366f1; box-shadow: 0 0 10px #6366f1; }
|
||||
|
||||
.dm-st-rail { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; max-width: 980px; }
|
||||
.dm-st-rail__step { display: inline-flex; align-items: center; gap: 7px; padding: 5px 11px; border-radius: 999px;
|
||||
background: rgba(255,255,255,0.7); border: 1px solid rgba(15,23,42,0.08); backdrop-filter: blur(8px);
|
||||
transition: all 0.45s cubic-bezier(0.22,1,0.36,1); }
|
||||
.dm-st-rail__num { width: 18px; height: 18px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
|
||||
font-size: 10px; font-weight: 800; color: #64748b; background: rgba(15,23,42,0.06); }
|
||||
.dm-st-rail__title { font-size: 11px; font-weight: 600; letter-spacing: 0.02em; color: #64748b; white-space: nowrap; }
|
||||
.dm-st-rail__step.is-current { background: color-mix(in srgb, var(--c) 16%, white); border-color: var(--c);
|
||||
box-shadow: 0 0 22px -6px var(--c); }
|
||||
.dm-st-rail__step.is-current .dm-st-rail__num { background: var(--c); color: #fff; }
|
||||
.dm-st-rail__step.is-current .dm-st-rail__title { color: #0f172a; }
|
||||
.dm-st-rail__step.is-done .dm-st-rail__num { background: #22C55E; color: #fff; }
|
||||
.dm-st-rail__step.is-done .dm-st-rail__title { color: #334155; }
|
||||
.dm-st-rail__line { width: 14px; height: 1px; background: rgba(15,23,42,0.14); margin: 0 3px; transition: background 0.45s ease; }
|
||||
.dm-st-rail__line.is-on { background: var(--c, #22C55E); }
|
||||
|
||||
.dm-st-scrollhint { position: absolute; bottom: clamp(26px, 6vh, 60px); left: 50%; transform: translateX(-50%);
|
||||
display: flex; flex-direction: column; align-items: center; gap: 8px; font-size: 12px; letter-spacing: 0.12em;
|
||||
color: #64748b; text-transform: uppercase; text-align: center; }
|
||||
.dm-st-arrow { font-size: 18px; animation: dmStBob 1.8s ease-in-out infinite; }
|
||||
@keyframes dmStBob { 0%,100% { transform: translateY(0); opacity: 0.5; } 50% { transform: translateY(6px); opacity: 1; } }
|
||||
|
||||
/* ---- Per-stage glass content card ---- */
|
||||
.dm-st-card-story { position: absolute; bottom: clamp(24px, 6vh, 64px); width: min(484px, 88vw);
|
||||
pointer-events: auto; will-change: opacity, transform; padding: 20px 22px; border-radius: 20px;
|
||||
background: rgba(255,255,255,0.94); border: 1px solid rgba(15,23,42,0.08);
|
||||
/* backdrop blur removed — card cross-fades/translates per scroll-stage; blur was the
|
||||
heaviest per-frame cost on this section. Near-opaque white keeps the glass look. */
|
||||
border-top: 3px solid var(--c);
|
||||
box-shadow: 0 28px 70px -34px rgba(15,23,42,0.5); }
|
||||
.dm-st-card-story.is-left { left: clamp(18px, 5vw, 72px); }
|
||||
.dm-st-card-story.is-right { right: clamp(18px, 5vw, 72px); }
|
||||
.dm-st-card-story__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||||
.dm-st-pillar__num { font-size: 12px; font-weight: 800; letter-spacing: 0.1em; color: #fff;
|
||||
background: var(--c); border-radius: 7px; padding: 3px 8px; }
|
||||
.dm-st-pillar__kicker { font-size: clamp(11px, 1.1vw, 13px); font-weight: 700; letter-spacing: 0.16em;
|
||||
text-transform: uppercase; color: var(--c); }
|
||||
.dm-st .dm-st-pillar__title { margin: 0 0 14px !important; padding: 0 !important; color: #0f172a !important;
|
||||
font-weight: 700 !important; text-transform: none !important; letter-spacing: -0.015em !important;
|
||||
font-size: clamp(18px, 2vw, 26px) !important; line-height: 1.16 !important; }
|
||||
.dm-st .dm-st-pillar__title--hero { font-size: clamp(22px, 2.6vw, 34px) !important;
|
||||
background: linear-gradient(90deg, #C01227, #E2354A) !important; -webkit-background-clip: text !important;
|
||||
background-clip: text !important; -webkit-text-fill-color: transparent !important; }
|
||||
.dm-st-foot { margin: 12px 0 0; font-size: clamp(12px, 1.1vw, 13.5px); line-height: 1.5; color: #475569;
|
||||
display: flex; align-items: center; gap: 8px; }
|
||||
.dm-st-livedot { width: 8px; height: 8px; border-radius: 50%; background: var(--c); box-shadow: 0 0 0 0 var(--c);
|
||||
animation: dmStPulse 1.8s ease-out infinite; }
|
||||
@keyframes dmStPulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--c) 55%, transparent); } 100% { box-shadow: 0 0 0 9px transparent; } }
|
||||
|
||||
/* In-scene 3D labels (drei <Html>) — crisp glass chips floating in the WebGL scene */
|
||||
.dm-st3d-file, .dm-st3d-count, .dm-st3d-ai, .dm-st3d-chip {
|
||||
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
|
||||
pointer-events: none; user-select: none; white-space: nowrap; transition: opacity 0.2s linear; }
|
||||
.dm-st3d-file, .dm-st3d-ai {
|
||||
display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; color: #0f172a;
|
||||
background: rgba(255,255,255,0.88); border: 1px solid rgba(34,197,94,0.4); border-radius: 999px;
|
||||
padding: 6px 13px; box-shadow: 0 8px 22px -12px rgba(34,197,94,0.7); backdrop-filter: blur(8px); }
|
||||
.dm-st3d-count { font-size: 15px; font-weight: 800; color: #0f172a; background: rgba(255,255,255,0.9);
|
||||
border: 1px solid rgba(34,197,94,0.45); border-radius: 12px; padding: 6px 14px;
|
||||
box-shadow: 0 10px 26px -12px rgba(34,197,94,0.8); backdrop-filter: blur(8px); }
|
||||
.dm-st3d-count span { color: #16a34a; font-size: 19px; }
|
||||
.dm-st3d-chip { display: inline-flex; align-items: center; gap: 8px; background: rgba(255,255,255,0.92);
|
||||
border: 1px solid rgba(34,197,94,0.4); border-radius: 12px; padding: 6px 11px;
|
||||
box-shadow: 0 10px 26px -14px rgba(15,23,42,0.7); backdrop-filter: blur(8px); }
|
||||
.dm-st3d-chip__ico { font-size: 17px; }
|
||||
.dm-st3d-chip__txt { display: flex; flex-direction: column; line-height: 1.15; }
|
||||
.dm-st3d-chip__txt b { font-size: 12.5px; font-weight: 800; color: #0f172a; }
|
||||
.dm-st3d-chip__txt { font-size: 10.5px; color: #475569; }
|
||||
|
||||
/* STAGE 01 — Input */
|
||||
.dm-st-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
|
||||
.dm-st-tile { position: relative; background: color-mix(in srgb, var(--c) 7%, white); border: 1px solid color-mix(in srgb, var(--c) 22%, white);
|
||||
border-radius: 14px; padding: 12px 14px; display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 10px; align-items: center; }
|
||||
.dm-st-tile__ico { grid-row: 1 / 3; font-size: 24px; }
|
||||
.dm-st-tile__num { font-size: 26px; font-weight: 800; color: #0f172a; line-height: 1; }
|
||||
.dm-st-tile__lbl { font-size: 12px; font-weight: 700; color: #334155; }
|
||||
.dm-st-tile__sub { grid-column: 1 / 3; margin-top: 4px; font-size: 10.5px; letter-spacing: 0.04em; text-transform: uppercase; color: #64748b; }
|
||||
.dm-st-file { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; padding: 9px 12px; border-radius: 12px;
|
||||
background: rgba(15,23,42,0.04); border: 1px dashed rgba(15,23,42,0.18); }
|
||||
.dm-st-file__ico { font-size: 15px; }
|
||||
.dm-st-file__name { font-size: 13px; font-weight: 700; color: #0f172a; }
|
||||
.dm-st-file__meta { font-size: 11.5px; color: #64748b; }
|
||||
.dm-st-file__ok { margin-left: auto; font-size: 12px; font-weight: 800; color: #22C55E; }
|
||||
.dm-st-riders { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
|
||||
.dm-st-rider { display: grid; grid-template-columns: 26px 1fr auto auto; align-items: center; gap: 9px;
|
||||
padding: 6px 8px; border-radius: 10px; background: rgba(15,23,42,0.03); }
|
||||
.dm-st-rider__av { position: relative; width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
|
||||
font-size: 11px; font-weight: 800; color: #fff; background: linear-gradient(135deg, var(--c), color-mix(in srgb, var(--c) 60%, #1e293b)); }
|
||||
.dm-st-rider__av i { position: absolute; right: -1px; bottom: -1px; width: 8px; height: 8px; border-radius: 50%; background: #22C55E; border: 2px solid #fff; }
|
||||
.dm-st-rider__name { font-size: 12.5px; font-weight: 700; color: #0f172a; }
|
||||
.dm-st-rider__veh { font-size: 11px; font-weight: 600; color: #475569; padding: 2px 8px; border-radius: 999px; background: rgba(15,23,42,0.06); }
|
||||
.dm-st-rider__cap { font-size: 11px; color: #64748b; white-space: nowrap; }
|
||||
|
||||
/* STAGE 02 — Parallel execution */
|
||||
.dm-st-engines { display: grid; gap: 10px; }
|
||||
.dm-st-engine { padding: 11px 12px; border-radius: 14px; background: rgba(15,23,42,0.03); border: 1px solid rgba(15,23,42,0.07); }
|
||||
.dm-st-engine.is-unified { background: color-mix(in srgb, var(--c) 8%, white); border-color: color-mix(in srgb, var(--c) 28%, white); }
|
||||
.dm-st-engine__head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.dm-st-engine__name { font-size: 13px; font-weight: 800; color: #0f172a; }
|
||||
.dm-st-engine__tag { font-size: 9.5px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #64748b;
|
||||
background: rgba(15,23,42,0.07); padding: 2px 7px; border-radius: 6px; }
|
||||
.dm-st-engine__tag--u { color: #fff; background: var(--c); }
|
||||
.dm-st-pills { display: flex; flex-wrap: wrap; gap: 7px; }
|
||||
.dm-st-pill { font-size: 12px; font-weight: 700; color: #334155; padding: 6px 12px; border-radius: 999px;
|
||||
background: rgba(255,255,255,0.9); border: 1px solid rgba(15,23,42,0.12); }
|
||||
.dm-st-pill--u { color: #0f172a; background: color-mix(in srgb, var(--c) 14%, white); border-color: color-mix(in srgb, var(--c) 40%, white); }
|
||||
|
||||
/* STAGE 03 — Optimization pipeline */
|
||||
.dm-st-pipe { display: grid; gap: 0; }
|
||||
.dm-st-pipe__node { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 11px; align-items: center;
|
||||
padding: 11px 14px; border-radius: 14px; background: rgba(255,255,255,0.92);
|
||||
border: 1px solid color-mix(in srgb, var(--c) 30%, white); box-shadow: 0 8px 22px -14px var(--c); }
|
||||
.dm-st-pipe__ico { grid-row: 1 / 3; font-size: 22px; }
|
||||
.dm-st-pipe__name { font-size: 13.5px; font-weight: 800; color: #0f172a; }
|
||||
.dm-st-pipe__desc { font-size: 11.5px; color: #64748b; }
|
||||
.dm-st-pipe__arrow { display: flex; align-items: center; justify-content: center; height: 22px; }
|
||||
.dm-st-pipe__arrow i { position: relative; width: 2px; height: 22px; background: linear-gradient(180deg, color-mix(in srgb, var(--c) 50%, transparent), var(--c)); overflow: visible; }
|
||||
.dm-st-pipe__arrow i::after { content: ""; position: absolute; left: 50%; top: -4px; width: 6px; height: 6px; border-radius: 50%;
|
||||
background: var(--c); box-shadow: 0 0 8px var(--c); transform: translateX(-50%); animation: dmStFlow 1.4s linear infinite; }
|
||||
@keyframes dmStFlow { 0% { top: -4px; opacity: 0; } 20% { opacity: 1; } 100% { top: 22px; opacity: 0; } }
|
||||
|
||||
/* STAGE 04 — Performance grading */
|
||||
.dm-st-stars { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; }
|
||||
.dm-st-stars__on { color: var(--c); letter-spacing: 2px; font-size: 16px; }
|
||||
.dm-st-stars__off { color: rgba(15,23,42,0.18); font-size: 16px; }
|
||||
.dm-st-stars__txt { font-size: 12px; font-weight: 700; color: #475569; margin-left: 4px; }
|
||||
.dm-st-metrics { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
|
||||
.dm-st-metric { display: grid; grid-template-columns: 1fr 70px auto; grid-template-rows: auto auto; column-gap: 10px; row-gap: 3px; align-items: center; }
|
||||
.dm-st-metric__name { font-size: 12.5px; font-weight: 700; color: #0f172a; }
|
||||
.dm-st-metric__bar { grid-column: 1 / 2; grid-row: 2; height: 6px; border-radius: 999px; background: rgba(15,23,42,0.08); overflow: hidden; }
|
||||
.dm-st-metric__bar i { display: block; height: 100%; border-radius: 999px; background: var(--c); }
|
||||
.dm-st-metric__score { grid-column: 2; grid-row: 1 / 3; font-size: 17px; font-weight: 800; color: #0f172a; text-align: right; }
|
||||
.dm-st-metric__status { grid-column: 3; grid-row: 1 / 3; font-size: 11px; font-weight: 800; color: #16a34a;
|
||||
background: rgba(34,197,94,0.12); border: 1px solid rgba(34,197,94,0.3); padding: 3px 8px; border-radius: 999px; white-space: nowrap; }
|
||||
.dm-st-metric__desc { grid-column: 1 / 2; grid-row: 1; font-size: 10.5px; color: #64748b; align-self: end; display: none; }
|
||||
|
||||
/* STAGE 05 — Strategy comparison (hero) */
|
||||
.dm-st-winner { margin-bottom: 14px; }
|
||||
.dm-st-winner__eyebrow { display: block; font-size: 10.5px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: #64748b; }
|
||||
.dm-st-winner__name { display: flex; align-items: center; gap: 10px; margin: 4px 0; font-size: clamp(24px, 2.8vw, 34px); font-weight: 800; color: #0f172a; letter-spacing: -0.02em; }
|
||||
.dm-st-winner__grade { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: 0.04em; color: #fff;
|
||||
background: linear-gradient(90deg, #C01227, #E2354A); padding: 4px 11px; border-radius: 999px; }
|
||||
.dm-st-trophy { font-size: 32px; filter: drop-shadow(0 8px 18px rgba(192,18,39,0.4)); animation: dmStFloat 3s ease-in-out infinite; }
|
||||
@keyframes dmStFloat { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
|
||||
.dm-st-hero { display: flex; align-items: center; gap: 20px; }
|
||||
.dm-st-hero__ring { position: relative; flex-shrink: 0; width: 96px; height: 96px; border-radius: 50%; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; background: conic-gradient(#C01227 88%, rgba(15,23,42,0.1) 0); }
|
||||
.dm-st-hero__ring::after { content: ""; position: absolute; inset: 9px; border-radius: 50%; background: #fff; }
|
||||
.dm-st-hero__pct { position: relative; z-index: 1; font-size: 24px; font-weight: 800; color: #C01227; }
|
||||
.dm-st-hero__sub { position: relative; z-index: 1; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: #64748b; }
|
||||
.dm-st-wins { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
|
||||
.dm-st-wins li { font-size: 13px; color: #334155; display: flex; align-items: baseline; gap: 8px; }
|
||||
.dm-st-wins strong { color: #C01227; font-weight: 800; min-width: 48px; }
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.dm-st-rail__title { display: none; }
|
||||
.dm-st-rail__step { padding: 5px 7px; }
|
||||
.dm-st-rail__line { width: 9px; }
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.dm-st { height: 560vh; }
|
||||
.dm-st-card-story { left: 0 !important; right: 0 !important; margin: 0 auto; width: calc(100% - 28px);
|
||||
bottom: clamp(18px, 4vh, 40px); padding: 15px 16px; }
|
||||
.dm-st-rider__cap { display: none; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dm-st-arrow, .dm-st-trophy { animation: none !important; }
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user