Files
doormile_react/src/components/sections/EVSection.tsx
2026-08-03 14:56:30 +05:30

1604 lines
61 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useEffect, useRef, useState } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import IndustryWorldMap from "./IndustryWorldMap";
if (typeof window !== "undefined") {
gsap.registerPlugin(ScrollTrigger);
}
/* ============================================================
EV Logistics section — the proven Elementor architecture:
Container (bbc6760)
├── Hero Image Container (7da6646) — parallax background banner
├── Floating Card Container (8b5d6e6)— number / title / subtitle
└── Content Section (.evnd) — eyebrow, title, body, stat bar
This component is content-driven via props. With no props it renders the
original homepage "EV Logistics" content verbatim (feature list + stat bar).
The MileTruth workflows (Performance, Innovation) reuse the EXACT same chrome
(banner → floating card → dark section) but render a COMPACT DASHBOARD in the
right column instead of the homepage feature list: a small KPI row over a set
of compact feature cards, sitting on the shared animated network backdrop.
The dashboard surfaces metrics fast, keeps descriptions short, cuts the
section height and reads far better on mobile than the old tall insight card.
============================================================ */
export interface EVFeature {
icon: React.ReactNode;
title: string;
desc: string;
}
export interface EVBadge {
value: string;
label: string;
}
export interface EVStat {
value: number;
decimals?: number;
suffix: string;
label: string;
/** Optional static text override — when set, renders this text instead of
* an animated CountUp (for non-numeric KPI concepts, e.g. "QUBO"). */
text?: string;
}
/** Accent theming for the dashboard column so it matches the scene on the left
* (Workflow 1 → cyan/teal · Workflow 2 → red/crimson). */
export interface EVCardsTheme {
/** Primary accent (metrics, icons, active rail, glow). */
accent: string;
/** Secondary accent (gradients, network lines, highlights). */
accent2: string;
/** Soft glow colour (rgba string). */
glow: string;
}
interface EVSectionProps {
/** Hero parallax banner background image. */
bannerImage?: string;
/** Floating card (8b5d6e6 → 3b61435). */
cardNumber?: string;
cardTitle?: string;
cardSubtitle?: string;
/** Content section (.evnd). */
eyebrow?: string;
titleLead?: string;
titleAccent?: string;
features?: EVFeature[];
image?: string;
imageAlt?: string;
badges?: EVBadge[];
stats?: EVStat[];
/** External spacing (used to keep the MileTruth workflow rhythm consistent). */
gapTop?: boolean;
gapBottom?: boolean;
ariaLabel?: string;
/** When provided, replaces the left hero <img> with custom media (e.g. a live
* Three.js scene). Badges still overlay on top. */
mediaSlot?: React.ReactNode;
/** When provided, the right column renders the compact dashboard (KPI row +
* compact feature cards on the animated network backdrop) instead of the
* static homepage feature list. The bottom stat bar is dropped in this mode
* to avoid repeating the metrics. */
metrics?: EVStat[];
/** Optional distinct copy for the bottom `.evnd__bar` strip in dashboard
* mode. Defaults to reusing `metrics` (WF1/WF2 behaviour) when omitted. */
barStats?: EVStat[];
/** Heading above the dashboard, e.g. "PERFORMANCE INSIGHT". */
cardsHeading?: string;
/** Accent theme for the dashboard column (defaults to the section red). */
cardsTheme?: EVCardsTheme;
/** Dashboard system label, top-right of the header row. Defaults to "AI ENGINE". */
sysLabel?: string;
/** Extra class appended to `.evnd__imgwrap`, for workflow-specific media
* sizing overrides (e.g. video object-fit) defined in the caller's own
* scoped <style> tag rather than here. */
mediaClassName?: string;
}
/* ---- Default content = original homepage EV Logistics section ---- */
const DEFAULT_FEATURES: EVFeature[] = [
{
icon: (
<svg
className="evnd-icon"
viewBox="0 0 24 24"
fill="none"
stroke="#f59e0b"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
),
title: "Battery-Aware Routing",
desc: "Battery level, health, and degradation are first-class inputs to route optimization — not afterthoughts.",
},
{
icon: (
<svg
className="evnd-icon"
viewBox="0 0 24 24"
fill="none"
stroke="#94a3b8"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 10h-1.28A6 6 0 0 0 12 5V3M12 5V3M6 10h1.28A6 6 0 0 0 12 5M12 18v2M12 18v2M8 10v6a4 4 0 0 0 8 0v-6" />
</svg>
),
title: "Charging Integration",
desc: "Seamlessly integrate charging stops without compromising arrival windows or SLA commitments.",
},
{
icon: (
<svg
className="evnd-icon"
viewBox="0 0 24 24"
fill="none"
stroke="#ef4444"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m8 3 4 8 5-5 5 15H2L8 3z" />
</svg>
),
title: "Energy-Optimized Paths",
desc: "Factor in elevation, speed limits, payload weight, and live weather for maximum range efficiency.",
},
{
icon: (
<svg
className="evnd-icon"
viewBox="0 0 24 24"
fill="none"
stroke="#ef4444"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
</svg>
),
title: "Predictable Operations",
desc: "EVs become predictable assets, not operational risks. Full visibility from depot to doorstep.",
},
];
const DEFAULT_BADGES: EVBadge[] = [
{ value: "100%", label: "ELECTRIC FLEET" },
{ value: "-40%", label: "COST / MILE" },
];
const DEFAULT_STATS: EVStat[] = [
{ value: 99.9, decimals: 1, suffix: "%", label: "SLA Compliance" },
{ value: 42, suffix: "%", label: "Distance Saved" },
{ value: 37, suffix: "%", label: "Fewer Vehicles" },
{ value: 45, suffix: "ms", label: "Dispatch Latency" },
];
/** Count-up that fires once when scrolled ~20% into view (ease-out cubic). */
function CountUp({
value,
decimals = 0,
suffix = "",
duration = 1700,
className,
}: {
value: number;
decimals?: number;
suffix?: string;
duration?: number;
className?: string;
}) {
const [n, setN] = useState(0);
const ref = useRef<HTMLElement>(null);
const done = useRef(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const reduced = window.matchMedia?.(
"(prefers-reduced-motion: reduce)",
).matches;
if (reduced) {
requestAnimationFrame(() => setN(value));
return;
}
const io = new IntersectionObserver(
(entries) => {
for (const e of entries) {
if (e.isIntersecting && !done.current) {
done.current = true;
const start = performance.now();
const ease = (t: number) => 1 - Math.pow(1 - t, 3);
const tick = (now: number) => {
const p = Math.min(1, (now - start) / duration);
setN(value * ease(p));
if (p < 1) requestAnimationFrame(tick);
else setN(value);
};
requestAnimationFrame(tick);
io.disconnect();
}
}
},
{ threshold: 0.2 },
);
io.observe(el);
return () => io.disconnect();
}, [value, duration]);
return (
<b ref={ref as React.RefObject<HTMLElement>} className={className}>
{n.toFixed(decimals)}
{suffix}
</b>
);
}
/**
* Shared global-network backdrop for the MileTruth workflows — the EXACT same
* implementation the Women Empowerment section uses: the dotted world-map
* silhouette with animated hub nodes, double pulse rings, travelling glowing
* packets and dashed connection routes (IndustryWorldMap, Canvas 2D). The grey
* continent silhouette is unchanged; the network is tinted to the workflow
* accent (WF1 teal/cyan · WF2 crimson/red). It is a full-bleed, pointer-events
* free layer held to a subtle 510% opacity so it sits strictly behind the KPI
* cards, feature cards and insight content. Motion / reduced-motion behaviour is
* inherited verbatim from IndustryWorldMap.
*/
function WorkflowMapBackdrop({ accent }: { accent: string }) {
return (
<div className="evnd__map" aria-hidden="true">
<IndustryWorldMap accent={accent} />
</div>
);
}
/** Compact dashboard panel (right column of the MileTruth workflows): a small
* KPI row over a set of compact feature cards, on the animated network backdrop.
* Themed per-workflow via CSS vars (--ca accent · --ca2 secondary · --cg glow). */
function DashboardPanel({
metrics,
features,
heading,
theme,
sysLabel = "AI ENGINE",
}: {
metrics: EVStat[];
features: EVFeature[];
heading?: string;
theme?: EVCardsTheme;
sysLabel?: string;
}) {
const themeVars = theme
? ({
["--ca"]: theme.accent,
["--ca2"]: theme.accent2,
["--cg"]: theme.glow,
} as React.CSSProperties)
: undefined;
return (
<div className="evnd__dash" style={themeVars}>
{/* The animated network backdrop now lives at the .evnd section level
(shared behind the whole workflow, mirroring the Women Empowerment
section's full-bleed map) — not boxed inside this panel. The panel's
translucent surface lets that shared network show through faintly so
the dashboard still reads as part of the same visual environment. */}
{heading && (
<div className="evnd__dash-head">
<span className="evnd__dash-live">
<span className="evnd__dash-livedot" />
{heading}
</span>
<span className="evnd__dash-sys">{sysLabel}</span>
</div>
)}
{/* Compact KPI row */}
<div className="evnd__dash-kpis">
{metrics.map((m) => (
<div className="evnd__kpi" key={m.label}>
{m.text ? (
<b className="evnd__kpi-val evnd__kpi-val--text">{m.text}</b>
) : (
<CountUp
value={m.value}
decimals={m.decimals}
suffix={m.suffix}
className="evnd__kpi-val"
/>
)}
<span className="evnd__kpi-label">{m.label}</span>
</div>
))}
</div>
{/* Compact feature cards (short descriptions only) */}
<div className="evnd__dash-feats">
{features.map((f) => (
<div className="evnd__dfeat" key={f.title}>
<span className="evnd__dfeat-ico" aria-hidden="true">
{f.icon}
</span>
<div className="evnd__dfeat-body">
<span className="evnd__dfeat-title">{f.title}</span>
<p className="evnd__dfeat-desc">{f.desc}</p>
</div>
</div>
))}
</div>
</div>
);
}
/**
* LazyVideo
* ---------------------------------------------------------------------------
* The workflow videos sit thousands of pixels below the fold (e.g. /miletruth
* renders one at document y≈9978), but a plain `<video autoPlay muted loop>`
* makes Chrome buffer the ENTIRE file at page load even while it is off-screen
* and still paused — and `preload="none"` does not stop it, because the browser
* keeps buffering to satisfy the pending autoplay. Measured on /miletruth that
* was 80.7 MB of mp4 pulled before the user scrolled at all (the 40 MB
* Doormile-quantum-video3.mp4 fetched twice over restarted byte ranges).
* On localhost that is free; on a deployed site it saturates the connection and
* the media pipeline exactly while the user is scrolling.
*
* Attaching the source only once the element nears the viewport defers all of
* it. Playback then starts exactly as before — `.evnd__video` is sized purely
* by CSS (width/height 100%), so an empty <video> occupies the identical box
* and nothing shifts.
*/
function LazyVideo({ src, ariaLabel }: { src: string; ariaLabel: string }) {
const ref = useRef<HTMLVideoElement>(null);
const [shouldLoad, setShouldLoad] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el || shouldLoad) return;
const io = new IntersectionObserver(
(entries) => {
if (entries.some((e) => e.isIntersecting)) {
setShouldLoad(true);
io.disconnect();
}
},
// ~1.3 viewports of lead time: enough for the fetch to be well under way
// before the section arrives, without paying for it at page load.
{ rootMargin: "1200px 0px" },
);
io.observe(el);
return () => io.disconnect();
}, [shouldLoad]);
useEffect(() => {
if (shouldLoad) ref.current?.load();
}, [shouldLoad]);
return (
<video
ref={ref}
className="evnd__video"
autoPlay
muted
loop
playsInline
preload="none"
aria-label={ariaLabel}
src={shouldLoad ? src : undefined}
/>
);
}
export default function EVSection({
bannerImage = "/images/ev.webp",
cardNumber = "",
cardTitle = "EV Logistics",
cardSubtitle = "Cleaner miles, lower costs",
eyebrow = "/ EV-Native Design /",
titleLead = "BUILT FOR ELECTRIC. ",
titleAccent = "NOT ADAPTED.",
features = DEFAULT_FEATURES,
image = "/images/ev.webp",
imageAlt = "DoorMile electric fleet van",
badges = DEFAULT_BADGES,
stats = DEFAULT_STATS,
gapTop = false,
gapBottom = false,
ariaLabel,
mediaSlot,
metrics,
barStats,
cardsHeading,
cardsTheme,
sysLabel,
mediaClassName,
}: EVSectionProps) {
const bannerRef = useRef<HTMLDivElement>(null);
const useDashboard = !!metrics && metrics.length > 0;
const bottomBarItems = useDashboard ? barStats : stats;
const isVideo = image.endsWith(".mp4");
/* In dashboard mode the workflow's accent theme is applied at the .evnd
section level so the shared animated network backdrop (below) is tinted to
match the workflow — cyan/teal for WF1, red/crimson for WF2. CSS vars
inherit down to the dashboard panel too. */
const sectionThemeVars =
useDashboard && cardsTheme
? ({
["--ca"]: cardsTheme.accent,
["--ca2"]: cardsTheme.accent2,
["--cg"]: cardsTheme.glow,
} as React.CSSProperties)
: undefined;
useEffect(() => {
// Banner Scroll-Triggered Parallax (Replicating background_image_parallax from theme.js exactly)
const banner = bannerRef.current;
if (!banner) return;
const parallaxTrigger = ScrollTrigger.create({
trigger: banner,
start: "top bottom",
end: "bottom top",
scrub: true,
onUpdate: () => {
if (window.innerWidth >= 1021) {
const scrollTop = window.scrollY;
const rect = banner.getBoundingClientRect();
const offsetTop = rect.top + scrollTop;
const from_top = scrollTop - offsetTop;
const yOffset = 0.3 * from_top;
gsap.set(banner, { backgroundPosition: `center ${yOffset}px` });
} else {
gsap.set(banner, { backgroundPosition: "" });
}
},
});
return () => parallaxTrigger?.kill();
}, []);
const outerClass =
"elementor-element elementor-element-bbc6760 e-con-full e-flex cut-corner-no sticky-container-off e-con e-parent" +
(useDashboard ? " evnd-workflow-shell" : "") +
(gapTop ? " evnd-gap-top" : "") +
(gapBottom ? " evnd-gap-bottom" : "");
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
/* ============================================================
EV-Native Design — redesigned section
bg #080808 · red #ef4444 · Manrope
============================================================ */
.evnd, .evnd * { font-family: "Manrope", Sans-serif !important; }
/* External gap helpers — keep stacked workflow sections on one rhythm
(Desktop 40 · Tablet 32 · Mobile 24). Single-sided per boundary. */
.evnd-gap-top { margin-top: 40px !important; }
.evnd-gap-bottom { margin-bottom: 40px !important; }
.evnd {
position: relative;
isolation: isolate;
overflow: hidden;
background: #080808;
/* flat top so it connects seamlessly to the banner above; rounded
bottom only, and no top margin so there is no white gap */
border-radius: 0 0 clamp(16px, 2vw, 28px) clamp(16px, 2vw, 28px);
margin: 0 0 clamp(28px, 5vw, 64px);
padding: 64px 48px clamp(48px, 6vw, 80px);
}
/* ---- Tighten the space below the KPI bar (WF1/WF2 only) ----
The base section reserves a tall padding-bottom (up to 80px) plus a
margin-bottom (up to 64px) that renders as a #0d0d0d band inside the
rounded container — together ~144px of empty space after the KPI bar.
The workflows end at the bar, so trim both: the bar now sits close to
the section's bottom edge, and the outer .evnd-gap-bottom (40px) still
provides clean separation to the next section. Scoped to .evnd--cards
(specificity beats the base + responsive .evnd rules at every
breakpoint) so the homepage EV section keeps its original spacing. */
.evnd.evnd--cards {
padding-bottom: clamp(28px, 3vw, 40px);
margin-bottom: clamp(8px, 1vw, 14px);
}
/* ---- Tighten the banner → black-section transition (≥768px) ----
From 768px up the floating title card overlaps with a FIXED budget
(margin-top:-298px into the banner, -70px into this section). The
workflow cards carry longer subtitles than the homepage card, so they
run taller than that budget and the overflow shows as a dark #0d0d0d
spacer between the hero image and this black section. Pull the section
up under the banner to swallow that strip — the card (z-index 2) stays
on top, so the overlapping card design is preserved. The flat top tucks
cleanly behind the banner. Not applied ≤767px, where the card sits
inline (no overlap) and no spacer exists — so mobile is unchanged. */
@media (min-width: 768px) {
.evnd { margin-top: -60px; }
}
/* ---- Floating card sizing: content-driven, not container-driven ----
The exported Elementor card reserves a lot of vertical space that the
short title + subtitle don't fill: the red text block (3b61435) carried
64px/73px top/bottom padding and the empty white footer strip (5aea22e)
a 70px min-height. Trim both so the card height tracks the text, while
keeping the two-tone red/white design, the 25px corner radius, and the
typography untouched. These rules live in the (body) inline <style> so
they override site.css at equal specificity; values tighten on mobile.
Applies to every workflow card (shared element IDs) for consistency. */
.elementor-element.elementor-element-3b61435 {
--padding-top: 46px;
--padding-bottom: 38px;
}
/* Fix missing font-size for card title that caused it to inherit 80px h2 */
.elementor-element.elementor-element-239afbb .logico-title {
font-size: clamp(28px, 3.5vw, 40px) !important;
}
.elementor-element.elementor-element-5aea22e {
--min-height: 38px;
--padding-top: 14px;
--padding-bottom: 14px;
}
@media (max-width: 1020px) {
.elementor-element.elementor-element-3b61435 {
--padding-top: 34px;
--padding-bottom: 28px;
}
.elementor-element.elementor-element-5aea22e {
--min-height: 32px;
--padding-top: 12px;
--padding-bottom: 12px;
}
}
@media (max-width: 660px) {
/* Mobile: trim more aggressively so there is no large empty area
below the paragraph. */
.elementor-element.elementor-element-3b61435 {
--padding-top: 26px;
--padding-bottom: 22px;
}
.elementor-element.elementor-element-5aea22e {
--min-height: 24px;
--padding-top: 10px;
--padding-bottom: 10px;
}
}
/* subtle diagonal light band for depth (matches reference) */
.evnd::before {
content: '';
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background: linear-gradient(120deg, transparent 28%, rgba(255,255,255,0.015) 50%, transparent 72%);
}
.evnd__inner { position: relative; z-index: 1; max-width: 1280px; margin: 0 auto; }
/* ---- MAIN GRID ---- */
.evnd__grid {
display: grid;
grid-template-columns: 1.15fr 1fr;
gap: clamp(32px, 4vw, 56px);
align-items: center;
}
/* Split-screen workflow layout: ~60/40 so the compact dashboard column
carries enough weight to balance the Three.js scene. align-items:
stretch lets the dashboard panel match the scene height. */
.evnd__grid--cards {
grid-template-columns: 1.5fr 1fr;
align-items: stretch;
}
.evnd__grid--cards .evnd__left { justify-content: center; }
.evnd__grid--cards .evnd__left,
.evnd__grid--cards .evnd__media { width: 100%; }
/* ---- Desktop height alignment: scene and dashboard share one row height.
A min-height floor on the scene column sets the baseline row height;
both columns stretch to the row via grid align-items: stretch. The
dashboard uses min-height:100% and its feature list grows (flex:1) to
fill any extra height so the panel always matches the scene. */
@media (min-width: 992px) {
.evnd__grid--cards .evnd__left {
min-height: clamp(560px, 40vw, 620px);
}
.evnd__grid--cards .evnd__media,
.evnd__grid--cards .evnd__imgwrap--media,
.evnd__grid--cards .wf-scene { height: 100%; }
.evnd__grid--cards .wf-scene { aspect-ratio: auto; }
.evnd__grid--cards .evnd__dash { min-height: 100%; }
/* Static-image media (Workflow 2): cap the image height with a fixed
max-height so it sits at roughly the dashboard's row height instead
of towering hundreds of px below it. width:auto keeps the aspect
ratio (no stretch, no crop) and the frame hugs the image
(fit-content), so it stays centred with no side bands. The cap is
kept below the dashboard height so the image never inflates the row.
Scoped to the non-scene wrapper; desktop only, mobile untouched. */
.evnd__grid--cards .evnd__imgwrap:not(.evnd__imgwrap--media):not(.evnd__imgwrap--video) {
width: fit-content;
max-width: 100%;
margin-inline: auto;
}
.evnd__grid--cards .evnd__imgwrap:not(.evnd__imgwrap--media):not(.evnd__imgwrap--video) .evnd__img {
width: auto;
height: auto;
max-height: clamp(520px, 46vw, 670px);
max-width: 100%;
}
}
@media (min-width: 1200px) {
.evnd__grid--cards .evnd__imgwrap--video {
width: 100%;
height: 100%;
min-height: 100%;
}
.evnd__grid--cards .evnd__imgwrap--video .evnd__video {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.evnd__left {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.evnd__right {
display: flex;
flex-direction: column;
}
.evnd__eyebrow {
display: inline-flex;
align-items: center;
gap: 12px;
color: #ef4444 !important;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 13px;
margin-bottom: 20px;
}
.evnd__eyebrow::before {
content: '';
width: 16px;
height: 2px;
background: #ef4444;
}
/* ---- Heading → content gap ----
Deliberately set here (on the wrapper div) rather than as
margin-bottom on .evnd__title (the <h2>): a global site.css rule
(.logico-front-end h2:not([class*="logico-title-h"]):last-child)
outweighs .evnd__title's own specificity and zeroes any bottom
margin placed on the heading itself, since the h2 is always the
last child of .evnd__heading. Setting it here sidesteps that
collision and gives every workflow section + Fleet Strategy one
shared, consistent gap. */
.evnd__heading {
margin-bottom: 40px;
}
@media (max-width: 991px) and (min-width: 768px) {
.evnd__heading {
margin-bottom: 34px;
}
}
.evnd__title {
color: #fff !important;
font-weight: 800 !important;
font-size: clamp(32px, 3.8vw, 48px) !important;
line-height: 1.15 !important;
letter-spacing: -0.01em;
margin: 0;
}
@media (min-width: 768px) {
.evnd__title {
white-space: nowrap;
}
}
.evnd__title .accent {
color: #ef4444 !important;
}
.evnd__media {
position: relative;
width: 100%;
}
.evnd__glow {
position: absolute;
left: 50%; bottom: -4%;
width: 80%; height: 80px;
transform: translateX(-50%);
background: radial-gradient(50% 50% at 50% 50%, rgba(239,68,68,0.3), transparent 72%);
filter: blur(35px);
z-index: 0;
animation: evndGlow 4s ease-in-out infinite;
}
.evnd__imgwrap {
position: relative;
z-index: 1;
overflow: hidden;
border-radius: 16px;
border: 1px solid rgba(255,255,255,0.06);
box-shadow: 0 30px 60px -25px rgba(0,0,0,0.85);
}
.evnd__img {
display: block;
width: 100%;
height: auto;
object-fit: cover;
transition: transform 0.8s cubic-bezier(0.25, 1, 0.5, 1);
}
.evnd__video {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.evnd__imgwrap:hover .evnd__img {
transform: scale(1.03);
}
/* Badge overlay styling */
.evnd__badge {
position: absolute;
z-index: 2;
display: flex;
flex-direction: column;
gap: 2px;
padding: 12px 16px;
background: rgba(13,13,13,0.72);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 8px;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.evnd__badge b {
color: #ef4444 !important;
font-weight: 800;
font-size: 24px;
line-height: 1;
}
.evnd__badge span {
color: rgba(255,255,255,0.7) !important;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.evnd__badge--tl { top: 20px; left: 20px; }
.evnd__badge--br { bottom: 20px; right: 20px; }
/* ---- Homepage feature cards (default, no-dashboard mode) ---- */
.evnd__features {
display: flex;
flex-direction: column;
gap: 16px;
height: 100%;
justify-content: space-between;
}
.evnd-feature {
position: relative;
display: grid;
grid-template-columns: 48px minmax(0, 1fr) auto;
gap: 20px;
align-items: start;
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 16px;
padding: 24px;
overflow: hidden;
transition: background-color 0.4s ease, border-color 0.4s ease, transform 0.4s cubic-bezier(.25,1,.5,1);
}
.evnd-feature__body { min-width: 0; }
.evnd-feature::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: #ef4444;
transform: scaleY(0);
transform-origin: bottom;
transition: transform 0.4s ease;
}
.evnd-feature:hover {
background: rgba(239,68,68,0.03);
border-color: rgba(239,68,68,0.2);
transform: translateY(-2px);
}
.evnd-feature:hover::before { transform: scaleY(1); }
.evnd-feature__icon-container {
width: 48px; height: 48px;
display: flex; align-items: center; justify-content: center;
background: rgba(239, 68, 68, 0.14);
border: 1px solid rgba(239, 68, 68, 0.35);
border-radius: 12px;
color: #ef4444 !important;
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.evnd-feature:hover .evnd-feature__icon-container {
background: rgba(239, 68, 68, 0.25);
border-color: rgba(239, 68, 68, 0.6);
color: #ff5555 !important;
}
.evnd-icon {
width: 22px;
height: 22px;
display: block;
color: #ef4444 !important;
stroke: #ef4444 !important;
}
.evnd-icon path,
.evnd-icon circle,
.evnd-icon polygon,
.evnd-icon rect {
stroke: #ef4444 !important;
}
.evnd-feature__title {
color: #fff !important;
font-weight: 700;
font-size: 15px !important;
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 4px 0 8px;
transition: color 0.3s ease;
}
.evnd-feature:hover .evnd-feature__title { color: #ef4444 !important; }
.evnd-feature__desc {
color: rgba(255,255,255,0.65) !important;
font-weight: 400 !important;
font-size: 16px !important;
line-height: 1.6 !important;
margin: 0;
}
.evnd-feature__arrow {
color: rgba(255,255,255,0.25);
font-size: 16px;
align-self: flex-start;
margin-top: 4px;
transition: color 0.3s ease, transform 0.3s ease;
}
.evnd-feature:hover .evnd-feature__arrow { color: #ef4444; transform: translate(3px, -3px); }
/* When the left media is a live 3D scene, let it define its own height
(via aspect-ratio) — drop the image-only hover zoom container quirks. */
.evnd__imgwrap--media { box-shadow: 0 30px 60px -25px rgba(0,0,0,0.85); }
/* ============================================================
COMPACT DASHBOARD (MileTruth workflow right column).
A small KPI row over compact feature cards, on the animated network
backdrop. Themed per-workflow via CSS vars (--ca accent · --ca2
secondary · --cg glow). Falls back to the section red.
============================================================ */
.evnd__dash {
--ca: #ef4444;
--ca2: #f59e0b;
--cg: rgba(239,68,68,0.22);
position: relative;
display: flex;
flex-direction: column;
gap: 18px;
width: 100%;
/* panel surface so the column reads as a real dashboard, not empty space */
background:
radial-gradient(120% 80% at 100% 0%, color-mix(in srgb, var(--ca) 9%, transparent) 0%, transparent 55%),
linear-gradient(180deg, rgba(255,255,255,0.025) 0%, rgba(255,255,255,0.01) 100%);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 20px;
padding: 24px;
overflow: hidden;
isolation: isolate;
}
.evnd__dash-head,
.evnd__dash-kpis,
.evnd__dash-feats { position: relative; z-index: 1; }
/* dashboard header row */
.evnd__dash-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.evnd__dash-live {
display: inline-flex;
align-items: center;
gap: 9px;
color: var(--ca) !important;
font-weight: 800;
font-size: 12px;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.evnd__dash-livedot {
width: 8px; height: 8px;
border-radius: 50%;
background: var(--ca);
box-shadow: 0 0 0 0 var(--cg);
animation: evndPulse 1.8s ease-out infinite;
}
@keyframes evndPulse {
0% { box-shadow: 0 0 0 0 var(--cg); opacity: 1; }
70% { box-shadow: 0 0 0 8px transparent; opacity: 0.7; }
100% { box-shadow: 0 0 0 0 transparent; opacity: 1; }
}
.evnd__dash-sys {
color: rgba(255,255,255,0.4) !important;
font-weight: 700;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
}
/* ---- Compact KPI row ---- */
.evnd__dash-kpis {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.evnd__kpi {
display: flex;
flex-direction: column;
gap: 5px;
padding: 14px 16px;
background: rgba(255,255,255,0.025);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 14px;
}
.evnd__kpi-val {
color: #fff !important;
font-weight: 800;
font-size: clamp(26px, 3vw, 34px);
line-height: 1;
letter-spacing: -0.02em;
background: linear-gradient(120deg, #fff 0%, var(--ca) 135%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.evnd__kpi-label {
color: rgba(255,255,255,0.6) !important;
font-weight: 600;
font-size: 12.5px;
letter-spacing: 0.01em;
}
/* Non-numeric KPI concepts (EVStat.text) — same gradient treatment as
the counted values, sized down so short codes/words don't inherit
the numeric clamp() meant for 2-3 digit figures. */
.evnd__kpi-val--text {
font-size: clamp(18px, 1.6vw, 22px) !important;
letter-spacing: 0.02em;
}
/* ============================================================
Large-screen layout balance (WF1/WF2 dashboard only, ≥1440px).
Root cause of the empty space is an oversized dashboard column, not
the KPI cards: give the Three.js scene more of the row (≈66/34, up to
68/32 on ultra-wide) and pin the dashboard panel to a compact fixed
max-width, centred in its column — so the KPI cards and feature rows
stop stretching and read as content-sized. Card heights, typography,
card styling, animations and the scene itself are untouched; only the
column proportions, panel width and KPI content alignment change.
(.evnd__inner caps at 1280px, so one ratio step covers all 1440+
widths; the 1600/1920 steps tighten the split further on very wide
displays.) Everything is scoped to .evnd__grid--cards and ≥1440px, so
the homepage EV section and tablet/laptop/mobile layouts are unchanged.
============================================================ */
@media (min-width: 1440px) {
/* Let the content area grow past the 1280px base cap so the scene
can get wider on large screens (scoped to WF1/WF2 — the homepage
EV section keeps its 1280px inner). Viewport/padding still limit
this at smaller widths; it mainly pays off at ~1600px+. */
.evnd__inner--cards { max-width: 1560px; }
/* Dashboard pinned to a fixed 600px track; the scene (1fr) absorbs
ALL the extra width, so growing the inner widens the scene only. */
.evnd__grid--cards { grid-template-columns: 1fr 600px; }
.evnd__grid--cards .evnd__dash {
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
/* Centre the KPI value/label so the cards read balanced rather than
left-hugging (height/type/styling unchanged). */
.evnd__grid--cards .evnd__kpi {
align-items: center;
text-align: center;
}
}
@media (min-width: 1920px) {
/* Ultra-wide: give the scene even more room (dashboard stays 600px). */
.evnd__inner--cards { max-width: 1720px; }
}
/* ---- Static-image workflow (Workflow 2) layout, 1440px+ ----
The asset is PORTRAIT, so it can't fill the wide scene column without
cropping. To show the WHOLE image (no missing content) it's hugged to
its own width via the ≥992 rules (fit-content frame + height cap) and
centred next to the 600px dashboard (same width as Workflow 1) with the
pair centred in a tightened inner — full image, no crop, no dead space.
It therefore sits a little narrower than the dashboard; filling the
column edge-to-edge would need a landscape source image. */
@media (min-width: 1440px) {
.evnd__inner--cards.evnd__inner--img { max-width: 1200px; }
.evnd__grid--cards.evnd__grid--img {
grid-template-columns: auto 600px;
justify-content: center;
gap: clamp(40px, 3vw, 56px);
}
}
/* ---- Compact feature cards ---- */
.evnd__dash-feats {
display: flex;
flex-direction: column;
gap: 12px;
flex: 1 0 auto;
}
.evnd__dfeat {
display: grid;
grid-template-columns: 38px minmax(0, 1fr);
gap: 14px;
align-items: center;
padding: 13px 16px;
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 14px;
transition: background-color 0.35s ease, border-color 0.35s ease, transform 0.35s cubic-bezier(.25,1,.5,1);
}
.evnd__dfeat:hover {
background: color-mix(in srgb, var(--ca) 5%, transparent);
border-color: color-mix(in srgb, var(--ca) 28%, transparent);
transform: translateY(-2px);
}
.evnd__dfeat-ico {
width: 38px; height: 38px;
display: flex; align-items: center; justify-content: center;
color: var(--ca);
background: color-mix(in srgb, var(--ca) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--ca) 30%, transparent);
border-radius: 10px;
}
.evnd__dfeat-ico svg { width: 19px; height: 19px; display: block; }
.evnd__dfeat-body { min-width: 0; }
.evnd__dfeat-title {
display: block;
color: #fff !important;
font-weight: 700;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 3px;
}
.evnd__dfeat-desc {
color: rgba(255,255,255,0.6) !important;
font-weight: 400 !important;
font-size: 16px !important;
line-height: 1.55 !important;
margin: 0 !important;
}
/* ============================================================
Shared global-network backdrop (WF1/WF2) — the dotted world-map
silhouette + animated hub network reused VERBATIM from the Women
Empowerment section (IndustryWorldMap canvas), tinted to the workflow
accent. Full-bleed, behind the .evnd__inner content (z 0 vs z 1),
pointer-events free, held to a subtle 510% opacity so the KPI cards,
feature cards and insight content stay the primary focus.
============================================================ */
.evnd__map {
position: absolute;
inset: 0;
z-index: 0;
/* Matches the Women Empowerment section's .ws-map treatment (flat
opacity, no mask — same implementation). On this darker #080808
background the grey dots are already very low contrast, so this
sits subtle while keeping the world-map silhouette readable. */
opacity: 0.5;
pointer-events: none;
overflow: hidden;
}
.evnd__map canvas { display: block; width: 100%; height: 100%; }
/* ---- BOTTOM BAR (homepage / default mode) ---- */
.evnd__bar {
display: grid;
grid-template-columns: repeat(4, 1fr);
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px;
overflow: hidden;
margin-top: 60px;
padding: 38px 0;
}
.evnd__bar-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
text-align: center;
padding: 16px 20px;
box-sizing: border-box;
}
.evnd__bar-item:not(:last-child)::after {
content: '';
position: absolute;
right: 0;
top: 15%;
height: 70%;
width: 1px;
background: rgba(255, 255, 255, 0.08);
}
.evnd__bar-label {
color: #fff !important;
font-size: 13.5px;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: none;
opacity: 0.9;
display: flex;
align-items: flex-end;
justify-content: center;
min-height: 36px;
text-align: center;
line-height: 1.25;
width: 100%;
}
.evnd__bar-val {
color: #ef4444 !important;
font-weight: 800;
font-size: clamp(30px, 3.8vw, 52px);
line-height: 1.15;
display: flex;
align-items: flex-start;
justify-content: center;
text-align: center;
margin-top: 8px;
width: 100%;
}
/* Non-numeric bar values (EVStat.text) — the bar's numeric clamp()
(up to 56px) is too large for words/phrases; size down and let
longer phrases (e.g. "Cost + Distance + SLA") wrap cleanly. */
.evnd__bar-val--text {
font-size: clamp(15px, 2vw, 22px);
line-height: 1.25;
white-space: normal;
word-break: break-word;
overflow-wrap: break-word;
}
@keyframes evndGlow { 0%,100% { opacity: 0.75; } 50% { opacity: 1; } }
/* ---- Responsive ---- */
@media (max-width: 1024px) {
.evnd-gap-top { margin-top: 32px !important; }
.evnd-gap-bottom { margin-bottom: 32px !important; }
}
@media (max-width: 991px) {
.evnd { padding: 48px 32px 56px; }
.evnd__grid { grid-template-columns: 1fr; gap: 40px; }
.evnd__features { gap: 14px; }
/* Stacked: KPI row can spread to 4-up since it now has full width. */
.evnd__dash-kpis { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 767px) {
.elementor.elementor-61 .elementor-element.elementor-element-bbc6760,
.elementor-element.elementor-element-bbc6760.evnd-workflow-shell {
width: calc(100% - 20px) !important;
margin-left: 10px !important;
margin-right: 10px !important;
padding-left: 0px !important;
padding-right: 0px !important;
box-sizing: border-box !important;
}
.evnd-gap-top { margin-top: 24px !important; }
.evnd-gap-bottom { margin-bottom: 24px !important; }
.evnd { padding-inline: 0; }
.evnd__heading {
padding-left: 26px;
padding-right: 26px;
box-sizing: border-box;
margin-bottom: 28px;
display: flow-root;
}
.evnd__inner {
padding-left: 4px;
padding-right: 4px;
box-sizing: border-box;
}
.evnd__dash {
padding-left: 12px !important;
padding-right: 12px !important;
}
.evnd__bar {
grid-template-columns: repeat(2, 1fr);
gap: 0;
width: calc(100% - 8px);
max-width: calc(100% - 8px);
margin: 28px auto 0;
padding: 12px 0;
box-sizing: border-box;
}
.evnd.evnd--cards {
padding-bottom: 20px;
margin-bottom: 0;
}
.evnd__bar-item {
padding: 16px 12px;
border-right: 1px solid rgba(255, 255, 255, 0.08);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.evnd__bar-item::after { display: none !important; }
.evnd__bar-item:nth-child(even) { border-right: none; }
.evnd__bar-item:nth-child(n+3) { border-bottom: none; }
.evnd__bar-label { min-height: 32px; font-size: 12.5px; }
.evnd__bar-val--text { font-size: clamp(14px, 3.8vw, 18px); }
/* ---- Scene overlay cleanup (mobile) ----
Keep only the primary top-left KPI badge; the second (bottom-right)
badge covers the route visualization, so it is removed. The
remaining badge is scaled down (~0.72) so it never dominates. */
.evnd__badge--br { display: none !important; }
.evnd__badge--tl { transform: scale(0.72); transform-origin: top left; }
/* ---- Horizontal alignment (WF1 / WF2 only) ----
minmax(0,1fr) + min-width:0 let the scene/panel columns shrink to
the container so the scene, panel and content share one symmetric
width and centre perfectly. Scoped to .evnd__grid--cards so the
homepage EV section is untouched. */
.evnd__grid--cards { grid-template-columns: minmax(0, 1fr); }
.evnd__grid--cards .evnd__left,
.evnd__grid--cards .evnd__right,
.evnd__grid--cards .evnd__media,
.evnd__grid--cards .evnd__dash {
min-width: 0;
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
}
/* Keep the KPI row a tidy 2x2 on phones. */
.evnd__dash-kpis { grid-template-columns: repeat(2, 1fr); }
/* Workflow feature cards: distribute content like the Women stats
rows so the icon balances the right side instead of everything
clustering at the left edge. */
.evnd-workflow-shell .evnd__dash-feats { gap: 10px; }
.evnd-workflow-shell .evnd__dfeat {
grid-template-columns: minmax(0, 1fr) 36px;
gap: 12px;
padding: 12px 14px;
}
.evnd-workflow-shell .evnd__dfeat-ico {
grid-column: 2;
grid-row: 1;
justify-self: end;
width: 36px;
height: 36px;
}
.evnd-workflow-shell .evnd__dfeat-body {
grid-column: 1;
grid-row: 1;
}
/* ---- Mobile-only overrides for Hero Image & EV Logistics Card ---- */
.elementor-element.elementor-element-7da6646 {
width: calc(100% - 16px) !important;
margin-left: 8px !important;
margin-right: 8px !important;
border-radius: 18px !important;
margin-top: 15px !important;
margin-bottom: -50px !important;
box-sizing: border-box !important;
}
.elementor-element.elementor-element-8b5d6e6 {
padding-left: 8px !important;
padding-right: 8px !important;
margin-left: 0px !important;
margin-right: 0px !important;
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
}
.elementor-element.elementor-element-1f766ea {
padding-left: 0px !important;
padding-right: 0px !important;
margin-left: auto !important;
margin-right: auto !important;
width: 100% !important;
max-width: 100% !important;
}
}
@media (max-width: 480px) {
.evnd { padding: 40px 16px 48px; }
.evnd { padding-inline: 0; }
.evnd__bar { grid-template-columns: repeat(2, 1fr); gap: 0; padding: 8px 0; }
.evnd__bar-item { padding: 14px 10px; }
.evnd__bar-val { font-size: clamp(22px, 7vw, 30px); }
.evnd__bar-label { font-size: 12px; min-height: 32px; }
/* feature cards (homepage): tighter, no overflow, more room for text */
.evnd-feature {
grid-template-columns: 40px minmax(0, 1fr);
gap: 14px;
padding: 18px 16px;
}
.evnd-feature__icon-container { width: 40px; height: 40px; border-radius: 10px; }
.evnd-icon { width: 20px; height: 20px; }
.evnd-feature__title { font-size: 14px !important; letter-spacing: 0.03em; margin: 2px 0 6px; padding-right: 22px; }
.evnd-feature__desc { font-size: 13px !important; line-height: 1.55 !important; }
.evnd-feature__arrow { position: absolute; top: 16px; right: 16px; margin: 0; }
/* ---- Compact dashboard (phones) ---- */
.evnd__dash { padding-top: 18px !important; padding-bottom: 18px !important; gap: 14px; }
.evnd__dash-sys { display: none; }
.evnd__dash-kpis { gap: 10px; }
.evnd__kpi { padding: 12px 14px; gap: 4px; }
.evnd__kpi-val { font-size: clamp(24px, 8vw, 30px); }
.evnd__kpi-label { font-size: 12px; }
.evnd-workflow-shell .evnd__dfeat { padding: 11px 12px; gap: 10px; grid-template-columns: minmax(0, 1fr) 34px; }
.evnd__dfeat-ico { width: 34px; height: 34px; border-radius: 9px; }
.evnd__dfeat-ico svg { width: 17px; height: 17px; }
.evnd__dfeat-title { font-size: 13.5px; }
.evnd__dfeat-desc { font-size: 12.5px !important; }
}
`,
}}
/>
<div
className={outerClass}
data-id="bbc6760"
data-element_type="container"
data-e-type="container"
aria-label={ariaLabel}
style={{
backgroundColor: "#0d0d0d",
width: "calc(100% - 40px)",
marginLeft: "20px",
marginRight: "20px",
borderRadius: "25px",
overflow: "hidden",
}}
>
{/* Background Banner with Native GSAP Background Scroll Parallax */}
<div
ref={bannerRef}
className="elementor-element elementor-element-7da6646 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child"
data-id="7da6646"
data-element_type="container"
data-e-type="container"
data-settings='{"background_background":"classic"}'
style={{
backgroundPosition: "center 0px",
backgroundImage: `url("${encodeURI(bannerImage)}")`,
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
position: "relative",
zIndex: 2,
borderRadius: "25px 25px 0 0",
overflow: "hidden",
}}
></div>
<div
className="elementor-element elementor-element-8b5d6e6 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child"
data-id="8b5d6e6"
data-element_type="container"
data-e-type="container"
>
<div
className="elementor-element elementor-element-1f766ea e-con-full e-flex cut-corner-no sticky-container-off e-con e-child"
data-id="1f766ea"
data-element_type="container"
data-e-type="container"
>
<div
className="elementor-element elementor-element-3b61435 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child"
data-id="3b61435"
data-element_type="container"
data-e-type="container"
data-settings='{"background_background":"classic"}'
>
{cardNumber && (
<div
className="elementor-element elementor-element-c364d1c elementor-widget elementor-widget-text-editor"
data-id="c364d1c"
data-element_type="widget"
data-e-type="widget"
data-widget_type="text-editor.default"
>
<div className="elementor-widget-container">
<p>{cardNumber}</p>
</div>
</div>
)}
<div
className="elementor-element elementor-element-239afbb elementor-widget elementor-widget-logico_heading"
data-id="239afbb"
data-element_type="widget"
data-e-type="widget"
data-widget_type="logico_heading.default"
>
<div className="elementor-widget-container">
<h2 className="logico-title">{cardTitle}</h2>
</div>
</div>
<div
className="elementor-element elementor-element-0d307dd elementor-widget elementor-widget-text-editor"
data-id="0d307dd"
data-element_type="widget"
data-e-type="widget"
data-widget_type="text-editor.default"
>
<div className="elementor-widget-container">
<p>{cardSubtitle}</p>
</div>
</div>
</div>
<div
className="elementor-element elementor-element-5aea22e e-con-full e-flex cut-corner-no sticky-container-off e-con e-child"
data-id="5aea22e"
data-element_type="container"
data-e-type="container"
data-settings='{"background_background":"classic"}'
></div>
</div>
</div>
{/* ===== EV-Native Design (redesigned) ===== */}
<section
className={`evnd${useDashboard ? " evnd--cards" : ""}`}
aria-label={ariaLabel ?? "EV-Native Design"}
style={sectionThemeVars}
>
{/* Shared global-network backdrop — full-bleed behind the whole
workflow, the SAME dotted world-map asset the Women Empowerment
section uses (silhouette · animated hub nodes · pulse rings ·
travelling packets · connection routes), tinted to the workflow
accent. Dashboard mode only; subtle opacity keeps the cards the
primary focus. */}
{useDashboard && cardsTheme && (
<WorkflowMapBackdrop accent={cardsTheme.accent} />
)}
<div
className={`evnd__inner${useDashboard ? " evnd__inner--cards" : ""}${
useDashboard && !mediaSlot && !isVideo ? " evnd__inner--img" : ""
}`}
>
<div className="evnd__heading">
<span className="evnd__eyebrow">{eyebrow}</span>
<h2 className="evnd__title">
{titleLead}
<span className="accent">{titleAccent}</span>
</h2>
</div>
{/* MAIN GRID */}
<div
className={`evnd__grid${useDashboard ? " evnd__grid--cards" : ""}${
useDashboard && !mediaSlot && !isVideo ? " evnd__grid--img" : ""
}`}
>
{/* Left column */}
<div className="evnd__left">
<div className="evnd__media">
<div className="evnd__glow" />
<div
className={`evnd__imgwrap${mediaSlot ? " evnd__imgwrap--media" : ""}${isVideo ? " evnd__imgwrap--video" : ""}${mediaClassName ? ` ${mediaClassName}` : ""}`}
>
{mediaSlot ? (
mediaSlot
) : isVideo ? (
<LazyVideo src={image} ariaLabel={imageAlt} />
) : (
/* Same reason as LazyVideo: these stills are multi-MB raw
PNGs (e.g. /solutions' "ev section1.png" is 2.6 MB at
document y≈6793) and were being fetched and decoded
eagerly while far below the fold. */
/* eslint-disable-next-line @next/next/no-img-element */
<img
className="evnd__img"
src={image}
alt={imageAlt}
decoding="async"
loading="lazy"
/>
)}
{badges[0] && (
<div className="evnd__badge evnd__badge--tl">
<b>{badges[0].value}</b>
<span>{badges[0].label}</span>
</div>
)}
{badges[1] && (
<div className="evnd__badge evnd__badge--br">
<b>{badges[1].value}</b>
<span>{badges[1].label}</span>
</div>
)}
</div>
</div>
</div>
{/* Right column */}
<div className="evnd__right">
{useDashboard ? (
<DashboardPanel
metrics={metrics!}
features={features}
heading={cardsHeading}
theme={cardsTheme}
sysLabel={sysLabel}
/>
) : (
<div className="evnd__features">
{features.map((f) => (
<div className="evnd-feature" key={f.title}>
<div
className="evnd-feature__icon-container"
aria-hidden="true"
>
{f.icon}
</div>
<div className="evnd-feature__body">
<div className="evnd-feature__title">{f.title}</div>
<p className="evnd-feature__desc">{f.desc}</p>
</div>
<span
className="evnd-feature__arrow"
aria-hidden="true"
>
</span>
</div>
))}
</div>
)}
</div>
</div>
{bottomBarItems && (
<div className="evnd__bar">
{bottomBarItems.map((s) => (
<div className="evnd__bar-item" key={s.label}>
<span className="evnd__bar-label">{s.label}</span>
{s.text ? (
<b className="evnd__bar-val evnd__bar-val--text">{s.text}</b>
) : (
<CountUp
value={s.value}
decimals={s.decimals}
suffix={s.suffix}
className="evnd__bar-val"
/>
)}
</div>
))}
</div>
)}
</div>
</section>
</div>
</>
);
}