1604 lines
61 KiB
TypeScript
1604 lines
61 KiB
TypeScript
"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 5–10% 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 5–10% 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>
|
||
</>
|
||
);
|
||
}
|