12 Commits

Author SHA1 Message Date
7587c622c3 fix workflow section 2026-07-13 15:10:13 +05:30
333460525a piller sectionn scroll update 2026-07-12 22:58:45 +05:30
1db8a466a5 update Entrepreneurship 2026-07-10 17:14:22 +05:30
ae881da02b update navber wings 2026-07-09 15:20:34 +05:30
d83b709245 update dropdown 2026-07-09 11:53:50 +05:30
6c267cc6f9 remove wings cta page 2026-07-08 20:24:43 +05:30
0fc09a934c navbar algin 2026-07-08 19:29:42 +05:30
d639415d89 fix mobile responsive 2026-07-08 19:13:36 +05:30
f7bab1fe70 update doormilewings card image update 2026-07-07 21:43:02 +05:30
fc190343eb update doormile wings page 2026-07-06 17:12:39 +05:30
09bfb957e6 update doormile wings dropdown 2026-07-04 16:39:42 +05:30
2fe7c0df76 Add Doormile Wings air freight page with nav, footer links, and sitemap.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 11:04:33 +05:30
56 changed files with 7327 additions and 81 deletions

View File

@@ -2,6 +2,15 @@ import type { NextConfig } from "next";
import path from "path";
const nextConfig: NextConfig = {
async redirects() {
return [
{
source: "/air-freight",
destination: "/doormile-wings",
permanent: true,
},
];
},
turbopack: {
root: path.resolve(__dirname),
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
public/images/card 1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/images/card 2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/images/card 3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/images/card4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
public/images/card5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
public/images/card6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
public/images/pahse1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

BIN
public/videos/fleet.mp4 Normal file

Binary file not shown.

View File

@@ -22,7 +22,7 @@ export default function AboutUsPage() {
<AboutHero />
<TheDoormileWay />
<EVLogisticSection />
<WomenSection />
{/* <WomenSection /> */}
<CompetitiveEdge />
<IntelligenceGrid />
<OurTeam />

View File

@@ -0,0 +1,15 @@
import { permanentRedirect } from "next/navigation";
export const metadata = {
alternates: {
canonical: "https://doormile.com/doormile-wings",
},
robots: {
index: false,
follow: true,
},
};
export default function AirFreightRedirectPage() {
permanentRedirect("/doormile-wings");
}

View File

@@ -0,0 +1,62 @@
import WingsHero from "@/components/sections/WingsHero";
import WingsWhyGrid from "@/components/sections/WingsWhyGrid";
import WingsSpeedAdvantage from "@/components/sections/WingsSpeedAdvantage";
import WingsFeatureGrid from "@/components/sections/WingsFeatureGrid";
import WingsIndustries from "@/components/sections/WingsIndustries";
import WingsHowItWorks from "@/components/sections/WingsHowItWorks";
import WingsFleetStrategy from "@/components/sections/WingsFleetStrategy";
import WingsInfrastructure from "@/components/sections/WingsInfrastructure";
import WingsMileTruth from "@/components/sections/WingsMileTruth";
import WingsRoadmap from "@/components/sections/WingsRoadmap";
export const metadata = {
title: "Doormile Wings Redefining Domestic Air Logistics Through AI",
description: "Doormile Wings links India's major regional capitals with AI-powered, same-day air logistics and zero-emission EV last-mile delivery — where quick commerce meets quick logistics.",
alternates: {
canonical: "https://doormile.com/doormile-wings",
},
openGraph: {
title: "Doormile Wings Redefining Domestic Air Logistics Through AI",
description: "Same-day air logistics linking India's major regional capitals, powered by MileTruth™ AI and zero-emission EV last-mile delivery.",
url: "https://doormile.com/doormile-wings",
siteName: "Doormile",
type: "website",
images: [
{
url: "https://doormile.com/images/wings-logo.png",
width: 1200,
height: 630,
alt: "Doormile Wings",
},
],
},
twitter: {
card: "summary_large_image",
title: "Doormile Wings Redefining Domestic Air Logistics Through AI",
description: "Same-day air logistics linking India's major regional capitals, powered by MileTruth™ AI and zero-emission EV last-mile delivery.",
images: ["https://doormile.com/images/wings-logo.png"],
},
};
export default function DoormileWingsPage() {
return (
<div id="doormile-wings" className="content-wrapper content-wrapper-may-contain-elementor-code content-wrapper-sidebar-position-none">
<div className="content">
<div className="content-inner">
<div data-elementor-type="wp-page" data-elementor-id="95" className="elementor elementor-95 elementor-59">
<WingsHero />
<WingsWhyGrid />
<WingsSpeedAdvantage />
<WingsFeatureGrid />
<WingsIndustries />
<WingsHowItWorks />
<WingsFleetStrategy />
<WingsInfrastructure />
<WingsMileTruth />
<WingsRoadmap />
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,123 @@
import EmpowermentHero from "@/components/sections/EmpowermentHero";
import EmpowermentVision from "@/components/sections/EmpowermentVision";
import EmpowermentStory from "@/components/sections/EmpowermentStory";
import WomenSection from "@/components/sections/WomenSection";
import EmpowermentPrograms from "@/components/sections/EmpowermentPrograms";
import EmpowermentImpact from "@/components/sections/EmpowermentImpact";
import EmpowermentHowItWorks from "@/components/sections/EmpowermentHowItWorks";
import EmpowermentStories from "@/components/sections/EmpowermentStories";
import EmpowermentPartners from "@/components/sections/EmpowermentPartners";
import EmpowermentCTA from "@/components/sections/EmpowermentCTA";
const WINGS_WOMEN_STATS = [
{ value: "500", plus: "+", label: "Women Founders" },
{ value: "35", plus: "+", label: "Cities Active" },
{ value: "100", plus: "+", label: "Enterprises Launched" },
];
const WINGS_WOMEN_CARDS = [
{
title: "Venture Building",
desc: "End-to-end support for women launching logistics enterprises — from ideation to market entry.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M4 19V5" />
<path d="M4 7h12l-2 4 2 4H4" />
<path d="M18 19h2" />
</svg>
),
},
{
title: "Entrepreneurship",
desc: "Women building, owning, and scaling sustainable businesses within the logistics ecosystem.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="7" width="18" height="13" rx="2" />
<path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 12h18" />
</svg>
),
},
{
title: "Founder Networks",
desc: "Peer communities and strategic connections that accelerate growth and open new markets.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<circle cx="8" cy="8" r="3" />
<circle cx="16" cy="8" r="3" />
<path d="M2.5 20c.7-3.1 2.8-5 5.5-5s4.8 1.9 5.5 5" />
<path d="M10.5 20c.7-3.1 2.8-5 5.5-5s4.8 1.9 5.5 5" />
</svg>
),
},
{
title: "Enterprise Leadership",
desc: "Strategic decision-making, operational excellence, and vision-driven leadership for women founders.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="9" r="5" />
<path d="M9 13.5 7.5 21 12 18l4.5 3-1.5-7.5" />
</svg>
),
},
];
export const metadata = {
title: "Doormile Wings — Women Entrepreneurship Initiative",
description: "Doormile Wings empowers women to build, own, and lead enterprises within the logistics ecosystem through strategic partnerships, founder networks, and entrepreneurial infrastructure.",
alternates: {
canonical: "https://doormile.com/empowerment",
},
openGraph: {
title: "Doormile Wings — Women Entrepreneurship Initiative",
description: "An entrepreneurial ecosystem empowering women to launch, grow, and lead logistics enterprises with strategic partnerships and industry collaboration.",
url: "https://doormile.com/empowerment",
siteName: "Doormile",
type: "website",
images: [
{
url: "https://doormile.com/images/wings-logo.png",
width: 1200,
height: 630,
alt: "Doormile Wings — Women Entrepreneurship Initiative",
},
],
},
twitter: {
card: "summary_large_image",
title: "Doormile Wings — Women Entrepreneurship Initiative",
description: "An entrepreneurial ecosystem empowering women to launch, grow, and lead logistics enterprises with strategic partnerships and industry collaboration.",
images: ["https://doormile.com/images/wings-logo.png"],
},
};
export default function EmpowermentPage() {
return (
<div id="empowerment" className="content-wrapper content-wrapper-may-contain-elementor-code content-wrapper-sidebar-position-none">
<div className="content">
<div className="content-inner">
<div data-elementor-type="wp-page" data-elementor-id="86" className="elementor elementor-86 elementor-59 elementor-empowerment">
<EmpowermentHero />
<EmpowermentVision />
<EmpowermentStory />
<WomenSection
eyebrow="/ Doormile Wings /"
bannerLabel="Women Entrepreneurship"
bannerCopy="An ecosystem where women build, own, and scale enterprises across logistics."
ctaLabel="Explore the ecosystem"
ctaHref="#wings-empowerment-programs"
heading="Women Building The Next Mile"
stats={WINGS_WOMEN_STATS}
cards={WINGS_WOMEN_CARDS}
/>
<EmpowermentPrograms />
<EmpowermentImpact />
<EmpowermentHowItWorks />
<EmpowermentStories />
<EmpowermentPartners />
<EmpowermentCTA />
</div>
</div>
</div>
</div>
);
}

133
src/app/express/page.tsx Normal file
View File

@@ -0,0 +1,133 @@
import type { Metadata } from "next";
import Link from "next/link";
export const metadata: Metadata = {
title: "Express Doormile",
description: "Doormile Express — a fast-lane logistics service for time-critical shipments. Coming soon.",
alternates: {
canonical: "https://doormile.com/express",
},
robots: {
index: false,
follow: true,
},
};
export default function ExpressPage() {
return (
<div id="express" className="content-wrapper content-wrapper-may-contain-elementor-code content-wrapper-sidebar-position-none">
<div className="content">
<div className="content-inner">
<section className="express-coming-soon">
<div className="express-coming-soon-inner">
<span className="express-coming-soon-eyebrow">Solutions / Express</span>
<h1 className="express-coming-soon-heading">
Doormile Express is <span className="express-coming-soon-accent">on its way</span>
</h1>
<p className="express-coming-soon-text">
We&apos;re building a fast-lane logistics service for time-critical shipments.
Check back soon, or explore what Doormile already delivers today.
</p>
<div className="express-coming-soon-actions">
<Link href="/doormile-wings" prefetch className="logico-alter-button">
Explore Doormile Wings
</Link>
<Link href="/contact" className="express-coming-soon-link">
Get notified when we launch
</Link>
</div>
</div>
</section>
</div>
</div>
{/* Self-contained placeholder styling, scoped to .express-coming-soon.
Re-declares --logico-dark-text-color/--logico-light-text-color/
--logico-accent-color locally: the sitewide :root fallback is the
light-theme value (dark text on white), which would render near-
black text on this dark section without the override. */}
<style
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `
.express-coming-soon {
--logico-dark-text-color: #ffffff;
--logico-light-text-color: #bebebe;
--logico-accent-color: #C01227;
min-height: calc(100vh - 160px);
display: flex;
align-items: center;
justify-content: center;
padding: clamp(48px, 8vw, 120px) 24px;
background: radial-gradient(120% 120% at 50% 0%, rgba(192, 18, 39, 0.18) 0%, rgba(26, 26, 26, 0.98) 55%, #111111 100%);
}
.express-coming-soon-inner {
max-width: 720px;
text-align: center;
}
.express-coming-soon-eyebrow {
display: inline-block;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--logico-accent-color);
margin-bottom: 20px;
}
.express-coming-soon-heading {
font-family: 'Manrope', sans-serif;
font-size: clamp(32px, 5vw, 56px);
line-height: 1.15em;
font-weight: 700;
color: var(--logico-dark-text-color);
margin: 0 0 20px;
}
.express-coming-soon-accent {
color: var(--logico-accent-color);
}
.express-coming-soon-text {
font-size: 16px;
line-height: 1.75em;
color: var(--logico-light-text-color);
margin: 0 auto 36px;
max-width: 520px;
}
.express-coming-soon-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 24px;
}
.express-coming-soon .logico-alter-button {
color: #ffffff !important;
border-color: rgba(255, 255, 255, 0.45) !important;
background: transparent !important;
}
.express-coming-soon .logico-alter-button:hover {
background: #ffffff !important;
color: #1a1a1a !important;
border-color: #ffffff !important;
}
.express-coming-soon-link {
font-size: 15px;
font-weight: 600;
color: var(--logico-light-text-color);
text-decoration: underline;
text-underline-offset: 4px;
}
.express-coming-soon-link:hover {
color: #ffffff;
}
@media (max-width: 480px) {
.express-coming-soon-actions {
flex-direction: column;
gap: 16px;
}
}
`,
}}
/>
</div>
);
}

View File

@@ -10,6 +10,27 @@ html {
scroll-behavior: smooth;
}
/* ============================================================
SITE-WIDE STICKY FIX
The legacy Elementor build sets `.body-container { overflow: hidden }`
(public/css/site.css, generated — do not edit there). Any ancestor
with non-visible overflow becomes the containing block for
`position: sticky` descendants; since `.body-container` itself never
actually scrolls (the real scrollbar lives on <body>), that made every
`position: sticky` element on the site inert — it just scrolled away
in normal flow instead of pinning.
Note: setting overflow-x:hidden paired with overflow-y:visible gets
silently coerced to overflow-y:auto by the CSS spec (an "auto" value on
one axis when the other is non-visible) — which ALSO still counts as a
scroll container and breaks sticky the same way. So this must be fully
visible on both axes. <body> already has overflow-x: hidden site-wide
(from the same legacy stylesheet), so horizontal bleed is still safely
clipped there — this rule doesn't need to repeat it.
============================================================ */
.body-container {
overflow: visible !important;
}
/* ============================================================
GLOBAL SPACING SYSTEM
A single shared vertical-rhythm scale so every section uses
@@ -153,16 +174,21 @@ body {
#masthead .elementor-element.elementor-element-0b7bf6f > .elementor-widget-container {
padding: 0 42px !important;
border-radius: 22px !important;
height: 58px !important;
display: flex !important;
align-items: center !important;
}
#masthead .elementor-element.elementor-element-0b7bf6f .header-menu-container .main-menu > li > a {
padding-top: 15px !important;
padding-bottom: 16px !important;
padding-top: 6px !important;
padding-bottom: 6px !important;
padding-left: 14px !important;
padding-right: 14px !important;
font-size: 15px !important;
line-height: 1.2 !important;
white-space: nowrap !important;
min-height: auto !important;
height: auto !important;
}
#masthead .header-menu-container .main-menu {
@@ -179,7 +205,11 @@ body {
}
#masthead .elementor-element.elementor-element-466de1b.dm-header-scrolled {
top: 0 !important;
top: 15px !important;
left: 20px !important;
right: 20px !important;
border-radius: 24px !important;
width: auto !important;
}
}
@@ -964,6 +994,49 @@ body {
box-sizing: border-box;
}
/* ============================================================
SHARED PAGE CONTENT CONTAINER
Matches the Hero/Header/Footer's real rendered OUTER edge, not the
Footer's narrower inner text column. The Hero card
(.owl-stage-outer, via .wings-hero-page.elementor-element-741f56c)
and the Footer's outer band (.elementor-element-b29b8fc) both sit a
fixed, uncapped 20px in from the viewport edge at every desktop
width (verified 1280px-2400px — no max-width ever kicks in). Apply
this class to any Wings-page section's content wrapper to guarantee
its cards/timeline/grid hug those same edges instead of sitting
narrower and more centered.
============================================================ */
.dm-section-container {
width: 100%;
margin-left: auto;
margin-right: auto;
box-sizing: border-box;
padding-left: 20px;
padding-right: 20px;
}
@media (max-width: 1024px) {
.dm-section-container {
padding-left: var(--dm-mobile-gutter, 10px);
padding-right: var(--dm-mobile-gutter, 10px);
}
}
/* Doormile Wings hero frame: the generic vendor CSS assigns 32px padding to
.elementor-element-741f56c on all pages; Home's frame is 20px (scoped
to .elementor-61). Scope the same 20px frame to the Doormile Wings hero so
the card alignment matches Home and Solutions exactly. */
@media (min-width: 1025px) {
.wings-hero-page.elementor-element-741f56c,
.af-hero-page.elementor-element-741f56c {
--padding-top: 20px;
--padding-right: 20px;
--padding-bottom: 20px;
--padding-left: 20px;
padding: 20px !important;
}
}
/* Custom premium animations keyframes */
@keyframes shimmer-sweep {
0% {
@@ -1166,3 +1239,4 @@ body {
margin-top: var(--space-section) !important;
}
}

View File

@@ -2,6 +2,7 @@ import React from "react";
import MileTruthHero from "../../components/sections/MileTruthHero";
import Workflow1 from "../../components/sections/Workflow1";
import Workflow2 from "../../components/sections/Workflow2";
import Workflow4 from "../../components/sections/Workflow4";
import Workflow3Lazy from "../../components/sections/Workflow3Lazy";
export const metadata = {
@@ -18,6 +19,7 @@ export default function MileTruthPage() {
<MileTruthHero />
<Workflow1 />
<Workflow2 />
<Workflow4 />
<Workflow3Lazy />
</div>
</div>

80
src/app/sitemap.ts Normal file
View File

@@ -0,0 +1,80 @@
import type { MetadataRoute } from "next";
export const dynamic = "force-static";
const BASE_URL = "https://doormile.com";
export default function sitemap(): MetadataRoute.Sitemap {
const now = new Date();
const staticRoutes: MetadataRoute.Sitemap = [
{
url: `${BASE_URL}/`,
lastModified: now,
changeFrequency: "weekly",
priority: 1.0,
},
{
url: `${BASE_URL}/how-it-works`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/miletruth`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/solutions`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/doormile-wings`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/about-us`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.7,
},
{
url: `${BASE_URL}/blog`,
lastModified: now,
changeFrequency: "weekly",
priority: 0.7,
},
{
url: `${BASE_URL}/contact`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.6,
},
{
url: `${BASE_URL}/privacy-policy`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.3,
},
{
url: `${BASE_URL}/terms-of-service`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.3,
},
{
url: `${BASE_URL}/cookie-policy`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.3,
},
];
return staticRoutes;
}

View File

@@ -458,6 +458,9 @@ export default function Footer() {
<div className="elementor-element elementor-element-a219b7d elementor-widget elementor-widget-logico_custom_navigation_menu" data-id="a219b7d" data-element_type="widget" data-e-type="widget" data-widget_type="logico_custom_navigation_menu.default">
<div className="elementor-widget-container">
<ul className="logico-custom-menu-widget">
<li>
<Link href="/doormile-wings">Doormile Wings</Link>
</li>
<li>
<Link href="/about-us">About Us</Link>
</li>

View File

@@ -7,7 +7,7 @@
* Menu open/close + sidebar state is read from HeaderUIProvider so BodyOverlay (sibling at body level) can react.
*/
import { MouseEvent, useEffect, useState } from "react";
import { KeyboardEvent, FocusEvent, MouseEvent, useEffect, useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { usePathname } from "next/navigation";
@@ -19,6 +19,9 @@ const CURRENT_PAGE_MAP: Record<string, string> = {
"/how-it-works": "how-it-works",
"/miletruth": "miletruth",
"/solutions": "solutions",
"/doormile-wings": "doormile-wings",
"/empowerment": "entarpreneurship",
"/express": "express",
"/about-us": "about",
"/blog": "blogs",
};
@@ -28,9 +31,13 @@ const CURRENT_PAGE_ALIASES: Record<string, string[]> = {
home: ["home"],
"how-it-works": ["how-it-works", "how-its-works"],
miletruth: ["miletruth"],
// Solutions is now a plain top-level link — no child pages own its active state.
solutions: ["solutions"],
about: ["about", "about-us", "women"],
"doormile-wings": ["doormile-wings"],
entarpreneurship: ["entarpreneurship"],
blogs: ["blogs"],
// Company is the dropdown parent for About + Blogs + How It Works — active if any is.
company: ["blogs", "how-it-works", "about"],
};
export default function Header() {
@@ -60,7 +67,7 @@ export default function Header() {
targetId: string,
shouldCloseMenu = false,
) => {
if (shouldCloseMenu) closeAll();
if (shouldCloseMenu) closeMobileMenu();
if (pathname !== targetPath) return;
@@ -78,6 +85,23 @@ export default function Header() {
const [visibleScrolled, setVisibleScrolled] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
// Company dropdown (desktop) / accordion (mobile). Local to Header —
// unlike isMenuOpen/isSidebarOpen (HeaderUIProvider), no sibling component
// needs to read this.
const [isDesktopCompanyOpen, setDesktopCompanyOpen] = useState(false);
const [isMobileCompanyOpen, setMobileCompanyOpen] = useState(false);
// Collapse the accordion whenever the off-canvas menu closes, so it isn't
// left expanded the next time the menu opens.
const closeMobileMenu = () => {
setMobileCompanyOpen(false);
closeAll();
};
const toggleMobileMenu = () => {
setMobileCompanyOpen(false);
toggleMenu();
};
useEffect(() => {
const handle = requestAnimationFrame(() => {
setVisibleScrolled(true);
@@ -436,25 +460,86 @@ export default function Header() {
<li id="menu-item-10508" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10508${dmHeaderActive("home")}`}>
<Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home")}>Home</Link>
</li>
<li id="menu-item-10509" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}>
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works")}>How It Works</Link>
<li id="menu-item-10509" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("doormile-wings")}`}>
<Link href="/doormile-wings" prefetch>Wings</Link>
</li>
<li id="menu-item-10510" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10510${dmHeaderActive("miletruth")}`}>
<Link href="/miletruth#miletruth" onClick={(event) => handleNavClick(event, "/miletruth", "miletruth")}>MileTruth&trade; AI</Link>
</li>
<li id="menu-item-10511" className={`menu-item menu-item-type-custom menu-item-10511${dmHeaderActive("solutions")}`}>
<Link href="/solutions#solutions" onClick={(event) => handleNavClick(event, "/solutions", "solutions")}>Solutions</Link>
<li id="menu-item-10511" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10511${dmHeaderActive("solutions")}`}>
<Link href="/solutions" prefetch>Solutions</Link>
</li>
<li id="menu-item-10512" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10512${dmHeaderActive("about")}`}>
<Link href="/about-us#about" onClick={(event) => handleNavClick(event, "/about-us", "about")}>About</Link>
<li id="menu-item-10513" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10513${dmHeaderActive("entarpreneurship")}`}>
<Link href="/empowerment" prefetch>Entrepreneurship</Link>
</li>
<li id="menu-item-10535" className={`menu-item menu-item-type-post_type menu-item-object-page menu-item-10535${dmHeaderActive("blogs")}`}>
<Link href="/blog#blogs" onClick={(event) => handleNavClick(event, "/blog", "blogs")}>Blogs</Link>
<li
id="menu-item-10535"
className={`menu-item menu-item-type-custom menu-item-has-children menu-item-10535${dmHeaderActive("company")}`}
onMouseEnter={() => setDesktopCompanyOpen(true)}
onMouseLeave={() => setDesktopCompanyOpen(false)}
onKeyDown={(event: KeyboardEvent<HTMLLIElement>) => {
if (event.key !== "Escape") return;
setDesktopCompanyOpen(false);
event.currentTarget.querySelector<HTMLAnchorElement>(":scope > a")?.focus();
}}
onBlur={(event: FocusEvent<HTMLLIElement>) => {
if (!event.currentTarget.contains(event.relatedTarget as Node)) {
setDesktopCompanyOpen(false);
}
}}
>
<a
href="#"
aria-haspopup="true"
aria-expanded={isDesktopCompanyOpen}
onFocus={() => setDesktopCompanyOpen(true)}
onClick={(event) => {
event.preventDefault();
setDesktopCompanyOpen((open) => !open);
}}
>
Company
</a>
<ul className="dm-solutions-dropdown">
<li className={currentPage === "about" ? "dm-child-active" : ""}>
<Link
href="/about-us#about"
onClick={(event) => {
setDesktopCompanyOpen(false);
handleNavClick(event, "/about-us", "about");
}}
>
About
</Link>
</li>
<li className={currentPage === "blogs" ? "dm-child-active" : ""}>
<Link
href="/blog#blogs"
onClick={(event) => {
setDesktopCompanyOpen(false);
handleNavClick(event, "/blog", "blogs");
}}
>
Blogs
</Link>
</li>
<li className={currentPage === "how-it-works" ? "dm-child-active" : ""}>
<Link
href="/how-it-works#how-it-works"
onClick={(event) => {
setDesktopCompanyOpen(false);
handleNavClick(event, "/how-it-works", "how-it-works");
}}
>
How It Works
</Link>
</li>
</ul>
</li>
</ul>
</nav>
</div>
<div className="menu-trigger" data-id="0b7bf6f" onClick={toggleMenu}>
<div className="menu-trigger" data-id="0b7bf6f" onClick={toggleMobileMenu}>
<span className="menu-trigger-icon">
<span className="hamburger">
<span></span>
@@ -474,7 +559,7 @@ export default function Header() {
<div className="header-icon login-logout">
<a href="#" title="Login/Register" className="link-login"></a>
</div>
<div className="header-icon menu-close" onClick={closeAll}>
<div className="header-icon menu-close" onClick={closeMobileMenu}>
<span className="menu-close-icon"></span>
</div>
</div>
@@ -484,21 +569,54 @@ export default function Header() {
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10508${dmHeaderActive("home")}`}>
<Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home", true)}>Home</Link>
</li>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}>
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works", true)}>How It Works</Link>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("doormile-wings")}`}>
<Link href="/doormile-wings" prefetch onClick={closeMobileMenu}>Wings</Link>
</li>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10510${dmHeaderActive("miletruth")}`}>
<Link href="/miletruth#miletruth" onClick={(event) => handleNavClick(event, "/miletruth", "miletruth", true)}>MileTruth&trade; AI</Link>
</li>
<li className={`menu-item menu-item-type-custom menu-item-10511${dmHeaderActive("solutions")}`}>
<Link href="/solutions#solutions" onClick={(event) => handleNavClick(event, "/solutions", "solutions", true)}>Solutions</Link>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10511${dmHeaderActive("solutions")}`}>
<Link href="/solutions" prefetch onClick={closeMobileMenu}>Solutions</Link>
</li>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-10512${dmHeaderActive("about")}`}>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10513${dmHeaderActive("entarpreneurship")}`}>
<Link href="/empowerment" prefetch onClick={closeMobileMenu}>Entrepreneurship</Link>
</li>
<li
className={`menu-item menu-item-type-custom menu-item-10535 dm-mobile-solutions${dmHeaderActive("company")}${isMobileCompanyOpen ? " dm-accordion-open" : ""}`}
>
<div className="dm-mobile-solutions-row">
<a
href="#"
onClick={(event) => {
event.preventDefault();
setMobileCompanyOpen((open) => !open);
}}
>
Company
</a>
<button
type="button"
className="dm-mobile-solutions-toggle"
aria-label={isMobileCompanyOpen ? "Close Company menu" : "Open Company menu"}
aria-haspopup="true"
aria-expanded={isMobileCompanyOpen}
onClick={() => setMobileCompanyOpen((open) => !open)}
>
<span className="dm-accordion-chevron" aria-hidden="true"></span>
</button>
</div>
<ul className="dm-mobile-solutions-panel">
<li className={currentPage === "about" ? "dm-child-active" : ""}>
<Link href="/about-us#about" onClick={(event) => handleNavClick(event, "/about-us", "about", true)}>About</Link>
</li>
<li className={`menu-item menu-item-type-post_type menu-item-object-page menu-item-10535${dmHeaderActive("blogs")}`}>
<li className={currentPage === "blogs" ? "dm-child-active" : ""}>
<Link href="/blog#blogs" onClick={(event) => handleNavClick(event, "/blog", "blogs", true)}>Blogs</Link>
</li>
<li className={currentPage === "how-it-works" ? "dm-child-active" : ""}>
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works", true)}>How It Works</Link>
</li>
</ul>
</li>
</ul>
</nav>
<div className="header-mobile-contacts">
@@ -855,6 +973,281 @@ export default function Header() {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cline x1='5' y1='5' x2='19' y2='19'/%3E%3Cline x1='19' y1='5' x2='5' y2='19'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cline x1='5' y1='5' x2='19' y2='19'/%3E%3Cline x1='19' y1='5' x2='5' y2='19'/%3E%3C/svg%3E");
}
/* ── Company dropdown (desktop) / accordion (mobile) ──
Company (Blogs + How It Works) is a non-navigating parent —
its trigger is a plain anchor with no href target, only a
dropdown toggle. Class names below (.dm-solutions-dropdown,
.dm-mobile-solutions*) are unchanged from when this same
component/markup belonged to Solutions, reusing site.css's dead
.menu-item-has-children chevron (fontello glyph, already
coloured white for this exact header widget) instead of
inventing a new arrow. Desktop opens on hover/focus; mobile is a
height-collapsing accordion controlled by the chevron button. */
@media (min-width: 1025px) {
#masthead .header-menu-container nav {
display: flex;
align-items: center;
}
#masthead .header-menu-container .main-menu {
display: inline-flex !important;
align-items: center;
gap: 16px !important;
margin: 0 !important;
line-height: 1;
}
#masthead .header-menu-container .main-menu > li {
display: flex;
align-items: center;
margin-right: 0 !important;
}
#masthead .header-menu-container .main-menu > li > a {
display: inline-flex !important;
align-items: center;
justify-content: center;
padding-top: 0 !important;
padding-bottom: 0 !important;
min-height: 48px;
line-height: 1.2 !important;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children {
position: relative;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children:after {
content: "";
position: absolute;
top: 100%;
left: 50%;
width: 100%;
min-width: 360px;
height: 24px;
transform: translateX(-50%);
pointer-events: auto;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown {
position: absolute;
top: 100%;
left: 50%;
transform: translate(-50%, 34px);
width: 240px;
min-width: 240px;
margin: 0;
padding: 12px;
list-style: none;
background: linear-gradient(180deg, rgba(31, 31, 31, 0.98) 0%, rgba(18, 18, 18, 0.98) 100%);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.38), inset 0 1px 0 rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.09);
border-radius: 18px;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;
z-index: 20;
}
/* .main-menu li:not(:last-of-type) (site.css) is a descendant
selector that also matches these nested <li>s, pushing every
row but the last 12px to the right. Cancel it here. */
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li {
margin-right: 0 !important;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li + li {
margin-top: 10px;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10px;
height: 48px;
padding: 0 16px;
border-radius: 10px;
/* Hardcoded rather than var(--logico-dark-text-color): that
variable is only overridden to white inside a few unrelated
scoped contexts (e.g. #side-panel-2f31137) and otherwise
falls back to the sitewide light-theme value (#111111),
which would be invisible on this dark glass panel. */
color: rgba(255, 255, 255, 0.85);
font-family: var(--font-manrope), "Manrope", sans-serif;
font-size: 15px;
font-weight: 600;
line-height: 1.25;
letter-spacing: normal;
white-space: nowrap;
text-decoration: none;
transition: background-color 0.25s ease, color 0.25s ease, padding-left 0.25s ease;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:before {
content: "";
width: 11px;
height: 11px;
margin-left: -16px;
flex: 0 0 11px;
background-color: #c01227;
opacity: 0;
transform: translateX(-10px);
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12 12 4'/%3E%3Cpath d='M5 4h7v7'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12 12 4'/%3E%3Cpath d='M5 4h7v7'/%3E%3C/svg%3E");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: contain;
mask-size: contain;
transition: opacity 0.25s ease-in-out, transform 0.25s ease-in-out, background-color 0.25s ease-in-out;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:hover,
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:focus-visible {
background-color: rgba(255, 255, 255, 0.08);
color: #ffffff;
padding-left: 22px;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:hover:before,
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:focus-visible:before {
opacity: 1;
transform: translateX(0);
background-color: #c01227;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li.dm-child-active > a {
color: #ffffff;
font-weight: 700;
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"] ~ .dm-solutions-dropdown {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: translate(-50%, 24px);
}
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"]:after {
transform: rotate(180deg);
}
/* Outlines are globally suppressed for .main-menu>li>a above;
restore a visible focus ring scoped only to this trigger. */
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a:focus-visible {
outline: 2px solid var(--logico-dark-text-color, #ffffff);
outline-offset: 6px;
border-radius: 4px;
}
}
#masthead .mobile-header-menu-container .dm-mobile-solutions-row {
display: flex !important;
align-items: center;
justify-content: space-between;
}
#masthead .mobile-header-menu-container .dm-mobile-solutions-row > a {
display: block;
flex: 1 1 auto;
padding: 0.75em 0;
color: var(--logico-default-text-color);
text-decoration: none;
}
#masthead .mobile-header-menu-container .dm-mobile-solutions-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
margin: 0;
padding: 0;
flex: 0 0 44px;
border: 0;
background: transparent;
color: var(--logico-default-text-color);
cursor: pointer;
}
#masthead .mobile-header-menu-container .dm-mobile-solutions-toggle:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
border-radius: 4px;
}
#masthead .mobile-header-menu-container .dm-accordion-chevron {
display: inline-block;
width: 0.7em;
height: 0.7em;
flex: 0 0 auto;
background-color: currentColor;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
transition: transform 0.3s ease;
}
#masthead .mobile-header-menu-container .dm-mobile-solutions.dm-accordion-open .dm-accordion-chevron {
transform: rotate(180deg);
}
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel {
max-height: 0;
overflow: hidden;
list-style: none;
margin: 0;
padding: 0;
padding-left: 18px;
transition: max-height 0.3s ease;
}
#masthead .mobile-header-menu-container .dm-mobile-solutions.dm-accordion-open .dm-mobile-solutions-panel {
max-height: 160px;
}
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li {
font-size: 0.9em;
}
/* .mobile-header-menu-container ul.main-menu>li>a (site.css) only
matches direct children of .main-menu, so these nested links
fall through to the sitewide generic underlined-anchor default.
Restore the plain sibling-item look explicitly. */
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li > a {
display: block;
padding: 0.35em 0;
color: var(--logico-default-text-color);
text-decoration: none;
}
/* A vendor li:before rule (checkmark bullet, likely intended for a
feature-list style elsewhere) leaks through here since the reset
at .mobile-header-menu-container ul.main-menu>li:before only
matches direct children of .main-menu, not this nested li.
Suppress it on both the <li> and <a> — these child rows aren't
top-level nav items and shouldn't carry any bullet/indicator. */
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li:before,
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li > a:before {
content: none !important;
display: none !important;
}
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li.dm-child-active > a {
color: var(--logico-light-text-color);
text-decoration: underline;
}
`,
}}
/>

View File

@@ -42,6 +42,9 @@ export interface EVStat {
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
@@ -83,10 +86,19 @@ interface EVSectionProps {
* 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 ---- */
@@ -260,11 +272,13 @@ function DashboardPanel({
features,
heading,
theme,
sysLabel = "AI ENGINE",
}: {
metrics: EVStat[];
features: EVFeature[];
heading?: string;
theme?: EVCardsTheme;
sysLabel?: string;
}) {
const themeVars = theme
? ({
@@ -287,7 +301,7 @@ function DashboardPanel({
<span className="evnd__dash-livedot" />
{heading}
</span>
<span className="evnd__dash-sys">AI ENGINE</span>
<span className="evnd__dash-sys">{sysLabel}</span>
</div>
)}
@@ -295,12 +309,16 @@ function DashboardPanel({
<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>
))}
@@ -342,11 +360,15 @@ export default function EVSection({
ariaLabel,
mediaSlot,
metrics,
barStats,
cardsHeading,
cardsTheme,
sysLabel,
mediaClassName,
}: EVSectionProps) {
const bannerRef = useRef<HTMLDivElement>(null);
const useDashboard = !!metrics && metrics.length > 0;
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
@@ -595,13 +617,31 @@ export default function EVSection({
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 0 36px 0;
margin: 0;
}
@media (min-width: 768px) {
.evnd__title {
@@ -752,7 +792,7 @@ export default function EVSection({
.evnd-feature__desc {
color: rgba(255,255,255,0.65) !important;
font-weight: 400 !important;
font-size: 14px !important;
font-size: 16px !important;
line-height: 1.6 !important;
margin: 0;
}
@@ -867,6 +907,13 @@ export default function EVSection({
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).
@@ -969,8 +1016,8 @@ export default function EVSection({
.evnd__dfeat-desc {
color: rgba(255,255,255,0.6) !important;
font-weight: 400 !important;
font-size: 13px !important;
line-height: 1.45 !important;
font-size: 16px !important;
line-height: 1.55 !important;
margin: 0 !important;
}
@@ -1041,6 +1088,14 @@ export default function EVSection({
text-transform: none;
opacity: 0.9;
}
/* 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(16px, 1.8vw, 22px);
line-height: 1.25;
white-space: normal;
}
@keyframes evndGlow { 0%,100% { opacity: 0.75; } 50% { opacity: 1; } }
@@ -1052,7 +1107,6 @@ export default function EVSection({
@media (max-width: 991px) {
.evnd { padding: 48px 32px 56px; }
.evnd__grid { grid-template-columns: 1fr; gap: 40px; }
.evnd__title { margin-bottom: 28px; }
.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); }
@@ -1331,7 +1385,7 @@ export default function EVSection({
<div
className={`evnd__inner${useDashboard ? " evnd__inner--cards" : ""}${
useDashboard && !mediaSlot ? " evnd__inner--img" : ""
useDashboard && !mediaSlot && !isVideo ? " evnd__inner--img" : ""
}`}
>
<div className="evnd__heading">
@@ -1345,7 +1399,7 @@ export default function EVSection({
{/* MAIN GRID */}
<div
className={`evnd__grid${useDashboard ? " evnd__grid--cards" : ""}${
useDashboard && !mediaSlot ? " evnd__grid--img" : ""
useDashboard && !mediaSlot && !isVideo ? " evnd__grid--img" : ""
}`}
>
{/* Left column */}
@@ -1353,11 +1407,11 @@ export default function EVSection({
<div className="evnd__media">
<div className="evnd__glow" />
<div
className={`evnd__imgwrap${mediaSlot ? " evnd__imgwrap--media" : ""}${image.endsWith(".mp4") ? " evnd__imgwrap--video" : ""}`}
className={`evnd__imgwrap${mediaSlot ? " evnd__imgwrap--media" : ""}${isVideo ? " evnd__imgwrap--video" : ""}${mediaClassName ? ` ${mediaClassName}` : ""}`}
>
{mediaSlot ? (
mediaSlot
) : image.endsWith(".mp4") ? (
) : isVideo ? (
<video
className="evnd__video"
autoPlay
@@ -1401,6 +1455,7 @@ export default function EVSection({
features={features}
heading={cardsHeading}
theme={cardsTheme}
sysLabel={sysLabel}
/>
) : (
<div className="evnd__features">
@@ -1434,15 +1489,19 @@ export default function EVSection({
all breakpoints. In dashboard mode it summarises the same workflow
metrics; the homepage section uses its own stat set. */}
<div className="evnd__bar">
{(useDashboard ? metrics! : stats).map((s) => (
{(useDashboard ? (barStats ?? metrics!) : stats).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>

View File

@@ -0,0 +1,162 @@
export default function EmpowermentCTA() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.emp-cta-wrap {
width: calc(100% - 40px);
margin: clamp(24px, 4vw, 40px) auto clamp(40px, 5vw, 64px);
}
.emp-cta {
position: relative;
overflow: hidden;
border-radius: 28px;
min-height: clamp(480px, 48vw, 680px);
background: #09090b;
display: flex;
align-items: flex-end;
}
.emp-cta::before {
content: "";
position: absolute;
inset: 0;
background-image:
linear-gradient(180deg, rgba(9, 9, 11, 0.18) 0%, rgba(9, 9, 11, 0.52) 48%, rgba(9, 9, 11, 0.96) 100%),
linear-gradient(90deg, rgba(9, 9, 11, 0.88) 0%, rgba(9, 9, 11, 0.35) 58%, rgba(9, 9, 11, 0.12) 100%),
url("/images/bg-header-women.webp");
background-size: cover;
background-position: center;
transform: scale(1.02);
}
.emp-cta-inner {
position: relative;
z-index: 1;
max-width: 1380px;
width: 100%;
margin: 0 auto;
padding: clamp(46px, 7vw, 88px) clamp(24px, 5vw, 64px);
box-sizing: border-box;
}
.emp-cta-recap {
display: flex;
gap: clamp(28px, 4vw, 52px);
margin-bottom: clamp(28px, 4vw, 44px);
padding-bottom: clamp(24px, 3vw, 36px);
border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
.emp-cta-recap-item {
display: flex;
align-items: baseline;
gap: 10px;
}
.emp-cta-recap-num {
font-size: clamp(22px, 2.4vw, 32px);
font-weight: 900;
letter-spacing: -0.02em;
color: transparent;
-webkit-text-stroke: 1.3px rgba(255, 255, 255, 0.85);
}
.emp-cta-recap-label {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.6px;
color: rgba(255, 255, 255, 0.5);
}
.emp-cta-kicker {
font-size: 13px;
font-weight: 900;
letter-spacing: 2.4px;
text-transform: uppercase;
color: #ff5470;
margin-bottom: 22px;
}
.emp-cta-title {
max-width: 980px;
color: #ffffff !important;
font-size: clamp(44px, 7vw, 108px) !important;
font-weight: 900 !important;
line-height: 0.95 !important;
letter-spacing: -0.06em !important;
text-transform: uppercase !important;
margin: 0 0 clamp(24px, 3vw, 34px);
}
.emp-cta-title em {
color: #ff5470;
font-style: normal;
}
.emp-cta-bottom {
display: block;
max-width: 1120px;
}
.emp-cta-sub {
max-width: 620px;
font-size: 16px;
line-height: 1.68;
color: rgba(255, 255, 255, 0.68);
margin: 0;
}
@media (max-width: 1024px) {
.emp-cta-wrap { width: calc(100% - 24px); }
.emp-cta { border-radius: 22px; }
}
@media (max-width: 760px) {
.emp-cta { min-height: 600px; }
.emp-cta::before { background-position: 76% center; }
.emp-cta-title {
font-size: clamp(36px, 11vw, 44px) !important;
letter-spacing: -0.05em !important;
overflow-wrap: anywhere;
}
.emp-cta-recap { flex-wrap: wrap; row-gap: 14px; }
}
`,
}}
/>
<div className="emp-cta-wrap">
<section className="emp-cta" aria-labelledby="empowerment-final-cta">
<div className="emp-cta-inner">
<div className="emp-cta-recap">
<div className="emp-cta-recap-item">
<span className="emp-cta-recap-num">500+</span>
<span className="emp-cta-recap-label">Women Founders</span>
</div>
<div className="emp-cta-recap-item">
<span className="emp-cta-recap-num">100+</span>
<span className="emp-cta-recap-label">Enterprises Launched</span>
</div>
<div className="emp-cta-recap-item">
<span className="emp-cta-recap-num">25+</span>
<span className="emp-cta-recap-label">Industry Partners</span>
</div>
</div>
<div className="emp-cta-kicker">/ Build the next mile /</div>
<h2 className="emp-cta-title" id="empowerment-final-cta">
Build what&rsquo;s <em>next.</em>
</h2>
<div className="emp-cta-bottom">
<p className="emp-cta-sub">
Join Doormile Wings in building the entrepreneurial ecosystem where women launch, grow, and lead enterprises that create lasting value across logistics.
</p>
</div>
</div>
</section>
</div>
</>
);
}

View File

@@ -0,0 +1,183 @@
import Link from "next/link";
import { ScrollReveal, ShimmerText } from "@/animations/Reveal";
export default function EmpowermentHero() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.eh-outer {
box-sizing: border-box;
padding: 20px;
}
.eh-section {
position: relative;
border-radius: 25px;
overflow: hidden;
box-sizing: border-box;
height: 800px;
display: flex;
align-items: center;
justify-content: center;
background: #07080c;
}
.eh-bg {
position: absolute;
inset: 0;
background-image: url("/images/women-hero-slider-2.png");
background-size: cover;
background-position: 30% 72%;
z-index: 0;
}
.eh-scrim {
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgba(7, 8, 12, 0.72) 0%, rgba(7, 8, 12, 0.5) 40%, rgba(7, 8, 12, 0.86) 100%);
z-index: 1;
}
.eh-inner {
position: relative;
z-index: 2;
width: 100%;
max-width: 1000px;
margin: 0 auto;
padding: 0 15px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
box-sizing: border-box;
}
.eh-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.6px;
text-transform: uppercase;
color: #ff5470;
margin-bottom: 20px;
}
.eh-title {
width: 100%;
font-size: clamp(30px, 5.5vw, 80px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.08 !important;
text-transform: uppercase !important;
color: #ffffff !important;
margin: 0;
word-wrap: break-word;
overflow-wrap: break-word;
}
.eh-title .eh-soft {
color: rgba(255, 255, 255, 0.72);
}
.eh-desc {
width: 100%;
max-width: 680px;
margin: 23px auto 0;
font-size: clamp(16px, 1.35vw, 23px);
line-height: 1.65;
color: rgba(255, 255, 255, 0.78);
}
.eh-btns {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
flex-wrap: wrap;
margin-top: 32px;
}
.eh-btn-primary,
.eh-btn-outline {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 15px 28px;
border-radius: 100px;
font-weight: 800;
font-size: 14px;
text-decoration: none;
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.35s ease, background-color 0.3s ease, border-color 0.3s ease;
}
.eh-btn-primary {
background: #c01227;
color: #ffffff !important;
border: 1.5px solid #c01227;
}
.eh-btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 14px 30px rgba(192, 18, 39, 0.4);
}
.eh-btn-outline {
background: rgba(255, 255, 255, 0.04);
color: #ffffff !important;
border: 1.5px solid rgba(255, 255, 255, 0.36);
}
.eh-btn-outline:hover {
border-color: rgba(255, 255, 255, 0.82);
background: rgba(255, 255, 255, 0.09);
transform: translateY(-3px);
}
@media (max-width: 1020px) {
.eh-outer { padding: 10px; }
}
@media (max-width: 840px) {
.eh-section { height: 600px; }
}
@media (max-width: 660px) {
.eh-title {
font-size: clamp(20px, 7vw, 32px) !important;
}
}
@media (max-width: 480px) {
.eh-section { height: 520px; }
.eh-btn-primary, .eh-btn-outline { width: 100%; justify-content: center; padding: 14px 22px; }
.eh-btns { width: 100%; }
}
`,
}}
/>
<div className="eh-outer">
<section className="eh-section" id="wings-empowerment-hero">
<div className="eh-bg" aria-hidden="true" />
<div className="eh-scrim" aria-hidden="true" />
<ScrollReveal delay={0.05} duration={0.85} yOffset={30} className="eh-inner">
{/* <div className="eh-eyebrow">Build the Next Mile</div> */}
<h1 className="eh-title">
Building <span className="eh-soft">Women</span><br />
<ShimmerText>Entrepreneurs.</ShimmerText>
</h1>
<p className="eh-desc">
Empowering ambitious women with the network, strategic partnerships, and opportunities to build, grow, and lead successful enterprises that shape the future of logistics.
</p>
{/* <div className="eh-btns">
<Link href="/contact" className="eh-btn-primary">Join Doormile Wings</Link>
<Link href="/contact" className="eh-btn-outline">Become a Partner</Link>
</div> */}
</ScrollReveal>
</section>
</div>
</>
);
}

View File

@@ -0,0 +1,259 @@
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
const STEPS = [
{ step: "01", title: "Apply", desc: "Submit your venture idea and tell us about your entrepreneurial vision." },
{ step: "02", title: "Discovery", desc: "Identify the right market opportunity and validate your business model." },
{ step: "03", title: "Build", desc: "Develop your enterprise with ecosystem support, mentorship, and partnerships." },
{ step: "04", title: "Launch", desc: "Go to market with strategic backing, industry connections, and operational infrastructure." },
{ step: "05", title: "Scale", desc: "Grow your team, expand operations, and lead your enterprise to sustainable impact." },
];
export default function EmpowermentHowItWorks() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.ehw-section {
background: #ffffff;
padding: var(--space-section-lg, 80px) 0 calc(var(--space-section-lg, 80px) + 32px) 0;
overflow: hidden;
}
.ehw-inner {
max-width: 1380px;
margin: 0 auto;
}
.ehw-head {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: clamp(126px, calc(7vw + 60px), 172px);
}
.ehw-head-reveal {
width: 100%;
}
.ehw-divider {
width: 100%;
height: 1px;
margin: 18px 0 clamp(24px, 3.5vw, 40px) 0;
border: none;
background-color: rgba(17, 17, 17, 0.14);
}
.ehw-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.4px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.42);
}
.ehw-title {
max-width: 820px;
font-size: clamp(32px, 4vw, 54px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.08 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0 !important;
}
/* ----------------------------------------------------------------
Premium editorial process row: five milestones in a single
sequence, separated by thin hairline dividers (no cards, no
shadows, no boxed containers). One flex-wrap sequence with
responsive flex-basis handles the composition at every size —
5-across on desktop, 3 + 2 (centered) on tablet, a single
vertical column on mobile — from one shared markup.
---------------------------------------------------------------- */
.ehw-sequence {
display: flex;
flex-wrap: wrap;
justify-content: center;
--ehw-gap: clamp(24px, 2.6vw, 48px);
column-gap: var(--ehw-gap);
row-gap: clamp(48px, 6vw, 84px);
}
.ehw-step {
position: relative;
flex: 0 1 calc((100% - 4 * var(--ehw-gap)) / 5);
min-width: 168px;
padding-left: clamp(20px, 2vw, 32px);
border-left: 1px solid rgba(17, 17, 17, 0.14);
transition: border-color 0.3s ease;
}
.ehw-step:first-child {
border-left: none;
padding-left: 0;
}
.ehw-step:hover {
border-left-color: rgba(200, 16, 46, 0.4);
}
.ehw-step-num {
font-size: clamp(48px, 4.6vw, 78px);
font-weight: 900;
line-height: 0.84;
letter-spacing: -0.03em;
color: transparent;
-webkit-text-stroke: 1.5px rgba(17, 17, 17, 0.24);
margin: 0 0 18px 0;
transition: -webkit-text-stroke-color 0.3s ease;
}
.ehw-step.is-accent .ehw-step-num {
-webkit-text-stroke: 1.5px rgba(200, 16, 46, 0.58);
}
.ehw-step-kicker {
display: flex;
align-items: center;
gap: 7px;
margin-bottom: 18px;
}
.ehw-step-kicker svg {
width: 13px;
height: 13px;
color: rgba(17, 17, 17, 0.36);
flex-shrink: 0;
}
.ehw-step-kicker span {
font-size: 11px;
font-weight: 800;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.4);
}
.ehw-step.is-accent .ehw-step-kicker svg,
.ehw-step.is-accent .ehw-step-kicker span {
color: #c8102e;
}
.ehw-step-title {
font-size: clamp(18px, 1.4vw, 22px) !important;
font-weight: 800 !important;
letter-spacing: -0.02em !important;
line-height: 1.2 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0 0 22px 0;
}
.ehw-step-desc {
max-width: 27ch;
font-size: 14px;
line-height: 1.6;
color: rgba(17, 17, 17, 0.55);
margin: 0;
}
/* Tablet: 3 + 2, centered — sever the divider at the start of each row. */
@media (max-width: 1100px) {
.ehw-step {
flex-basis: calc((100% - 2 * var(--ehw-gap)) / 3);
}
.ehw-step:nth-child(4) {
border-left: none;
padding-left: 0;
}
}
/* Mobile: single column — dividers rotate from vertical to horizontal. */
@media (max-width: 640px) {
.ehw-sequence {
row-gap: 0;
}
.ehw-step {
flex-basis: 100%;
padding-left: 0;
padding-top: clamp(28px, 8vw, 38px);
border-left: none;
border-top: 1px solid rgba(17, 17, 17, 0.14);
}
.ehw-step:first-child {
padding-top: 0;
border-top: none;
}
.ehw-step:nth-child(4) {
border-top: 1px solid rgba(17, 17, 17, 0.14);
}
.ehw-step:hover {
border-left-color: transparent;
}
.ehw-step-num {
font-size: clamp(44px, 13vw, 56px);
}
}
/* ---- Mobile title-to-description gap fix ----
.ehw-step-title is an <h3>, so it's matched by a global,
unscoped site.css rule (.logico-front-end h3:not(...) { margin:
0 0 30px }) that outranks this component's own single-class
".ehw-step-title { margin: 0 0 22px }" by specificity — bumping
the real margin-bottom to 30px everywhere, not just here. There
is no min-height, space-between, or fixed row height anywhere in
this component; the gap is purely that extra 8px of margin
reading as oversized once every other element around it (kicker,
description line-height) is tightly spaced. Overridden with
!important, matching how every other property on this selector
already has to defeat the same global rule. Scoped to mobile
only, per spec — desktop/tablet are left exactly as they were
(still resolving to the global 30px; out of scope here). */
@media (max-width: 767px) {
.ehw-step-title {
margin-bottom: 16px !important;
}
}
@media (max-width: 480px) {
.ehw-section { padding-top: 48px; padding-bottom: 68px; }
}
`,
}}
/>
<section className="ehw-section" id="wings-empowerment-how-it-works">
<div className="dm-section-container">
<div className="ehw-inner">
<div className="ehw-head">
<ScrollReveal delay={0.05} duration={0.75} yOffset={20} className="ehw-head-reveal">
<div className="ehw-eyebrow">/ The Founder Journey /</div>
<hr className="ehw-divider" />
</ScrollReveal>
<ScrollReveal delay={0.1} duration={0.85} yOffset={24}>
<h2 className="ehw-title">A clear path, from idea to enterprise</h2>
</ScrollReveal>
</div>
<StaggerChildren className="ehw-sequence" stagger={0.12} duration={0.7} yOffset={30} triggerOnce>
{STEPS.map((s, i) => (
<div className={`ehw-step${i === 2 ? " is-accent" : ""}`} key={s.step}>
<div className="ehw-step-num">{s.step}</div>
<div className="ehw-step-kicker" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M7 17 17 7M9 7h8v8" />
</svg>
<span>Step</span>
</div>
<h3 className="ehw-step-title">{s.title}</h3>
<p className="ehw-step-desc">{s.desc}</p>
</div>
))}
</StaggerChildren>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,192 @@
import { ScrollReveal, CountUp } from "@/animations/Reveal";
const SECONDARY_STATS = [
{ end: 100, suffix: "+", label: "Enterprises Launched", isCount: true },
{ end: 25, suffix: "+", label: "Industry Partners", isCount: true },
{ end: 15, suffix: "+", label: "Cities Active", isCount: true },
{ value: "Millions", label: "In Economic Value Created", isCount: false },
];
export default function EmpowermentImpact() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.eim-section {
position: relative;
width: calc(100% - 40px);
margin: clamp(24px, 4vw, 40px) auto;
border-radius: 28px;
overflow: hidden;
box-sizing: border-box;
background: #09090b;
padding: clamp(64px, 8vw, 100px) clamp(24px, 5vw, 48px);
}
.eim-inner {
position: relative;
z-index: 1;
max-width: 1380px;
margin: 0 auto;
}
.eim-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.5px;
text-transform: uppercase;
color: #c8102e;
margin-bottom: 20px;
}
.eim-title {
font-size: clamp(28px, 3.4vw, 46px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0 0 clamp(32px, 4vw, 48px) 0;
}
.eim-divider {
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.12);
margin: 0 0 clamp(40px, 5vw, 56px) 0;
}
.eim-hero-row {
display: flex;
align-items: flex-end;
gap: clamp(20px, 3vw, 36px);
margin-bottom: clamp(48px, 6vw, 72px);
}
.eim-hero-num {
font-size: clamp(90px, 13vw, 190px);
font-weight: 800;
line-height: 0.85;
letter-spacing: -0.03em;
color: transparent;
-webkit-text-stroke: 2px #c8102e;
}
.eim-hero-label {
font-size: 13px;
font-weight: 700;
letter-spacing: 1.5px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.55);
max-width: 90px;
line-height: 1.4;
padding-bottom: clamp(12px, 1.5vw, 24px);
}
.eim-divider-2 {
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.12);
margin: 0 0 clamp(40px, 5vw, 56px) 0;
}
.eim-secondary-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.eim-secondary-item {
padding-right: 24px;
border-left: 1px solid rgba(255, 255, 255, 0.12);
padding-left: 24px;
}
.eim-secondary-item:first-child {
border-left: none;
padding-left: 0;
}
.eim-secondary-num {
display: block;
font-size: clamp(32px, 3vw, 44px);
font-weight: 800;
color: #ff5470;
letter-spacing: -0.02em;
margin-bottom: 10px;
}
.eim-secondary-label {
font-size: 13px;
font-weight: 600;
color: rgba(255, 255, 255, 0.5);
line-height: 1.4;
}
.eim-copy {
max-width: 720px;
margin-top: clamp(48px, 6vw, 72px);
font-size: 15.5px;
line-height: 1.7;
color: rgba(255, 255, 255, 0.55);
}
@media (max-width: 1024px) {
.eim-section { width: calc(100% - 24px); border-radius: 22px; }
}
@media (max-width: 700px) {
.eim-hero-row { align-items: flex-start; flex-direction: column; gap: 12px; }
.eim-hero-label { padding-bottom: 0; max-width: none; }
.eim-secondary-row { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 28px; }
.eim-secondary-item:nth-child(3) { border-left: none; padding-left: 0; }
}
@media (max-width: 460px) {
.eim-section { padding-top: 48px; padding-bottom: 48px; }
}
`,
}}
/>
<section className="eim-section" id="wings-empowerment-impact">
<div className="eim-inner dm-section-container">
<ScrollReveal delay={0.05} duration={0.75} yOffset={20}>
<div className="eim-eyebrow">/ Our Impact /</div>
<h2 className="eim-title">Growth we can measure</h2>
</ScrollReveal>
<hr className="eim-divider" />
<ScrollReveal delay={0.08} duration={0.8} yOffset={20} className="eim-hero-row">
<span className="eim-hero-num">
<CountUp end={500} suffix="+" duration={2.2} triggerOnce />
</span>
<span className="eim-hero-label">Women-Led Enterprises In The Ecosystem</span>
</ScrollReveal>
<hr className="eim-divider-2" />
<ScrollReveal delay={0.1} duration={0.8} yOffset={20}>
<div className="eim-secondary-row">
{SECONDARY_STATS.map((s) => (
<div className="eim-secondary-item" key={s.label}>
<span className="eim-secondary-num">
{s.isCount ? <CountUp end={s.end as number} suffix={s.suffix} duration={2} triggerOnce /> : s.value}
</span>
<span className="eim-secondary-label">{s.label}</span>
</div>
))}
</div>
</ScrollReveal>
<ScrollReveal delay={0.12} duration={0.75} yOffset={20} className="eim-copy">
<p>
Every number on this page represents a woman who walked into a room with an idea and
walked out with a business. An enterprise launched, a partnership secured, a market entered each is a
marker of real economic impact, not a statistic collected for its own sake.
</p>
</ScrollReveal>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,290 @@
import Link from "next/link";
import { ScrollReveal } from "@/animations/Reveal";
const PARTNER_TYPES = [
{
title: "Industry Partners",
desc: "Strategic alliances with logistics operators, supply chain leaders, and distribution networks that open doors for women-led enterprises.",
},
{
title: "Technology Partners",
desc: "Platforms, tools, and infrastructure that give founders the operational edge to compete and scale efficiently.",
},
{
title: "Community Networks",
desc: "Founder communities, women's business networks, and ecosystem builders who extend reach and amplify impact.",
},
{
title: "Investment Partners",
desc: "Funding access, grant programs, angel networks, and capital partners aligned with women-led enterprise growth.",
},
];
export default function EmpowermentPartners() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.epn-section {
position: relative;
width: calc(100% - 40px);
margin: clamp(24px, 4vw, 40px) auto;
border-radius: 28px;
overflow: hidden;
box-sizing: border-box;
background: #09090b;
padding: clamp(68px, 8vw, 112px) 0;
}
.epn-section::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(120deg, rgba(9, 9, 11, 0.98) 0%, rgba(9, 9, 11, 0.88) 54%, rgba(192, 18, 39, 0.18) 100%),
url("/images/wings-heroslider3.png");
background-size: cover;
background-position: center 70%;
opacity: 1;
}
.epn-inner {
position: relative;
z-index: 1;
max-width: 1380px;
margin: 0 auto;
}
.epn-head {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: clamp(50px, 7vw, 84px);
}
.epn-head-reveal {
width: 100%;
}
.epn-divider {
width: 100%;
height: 1px;
margin: 18px 0 clamp(24px, 3.5vw, 40px) 0;
border: none;
background-color: rgba(255, 255, 255, 0.16);
}
.epn-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.5px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.52);
}
.epn-title {
max-width: 930px;
font-size: clamp(42px, 6.3vw, 90px) !important;
font-weight: 800 !important;
letter-spacing: -0.055em !important;
line-height: 0.98 !important;
text-transform: uppercase !important;
color: #ffffff !important;
margin: 0;
}
.epn-title em {
color: #ff5470;
font-style: normal;
}
.epn-body {
display: grid;
grid-template-columns: minmax(280px, 0.46fr) minmax(0, 1fr);
gap: clamp(32px, 5vw, 72px);
align-items: stretch;
}
.epn-manifesto {
border-left: 4px solid #c8102e;
padding-left: clamp(22px, 3vw, 36px);
align-self: start;
}
.epn-intro {
font-size: clamp(22px, 2.5vw, 36px);
font-weight: 800;
line-height: 1.12;
letter-spacing: -0.035em;
color: #ffffff;
margin: 0 0 22px;
}
.epn-note {
font-size: 15.5px;
line-height: 1.68;
color: rgba(255, 255, 255, 0.62);
margin: 0;
max-width: 38ch;
}
.epn-grid {
display: flex;
flex-direction: column;
border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.epn-card {
position: relative;
display: grid;
grid-template-columns: 70px minmax(0, 1fr) 32px;
align-items: center;
gap: clamp(18px, 3vw, 40px);
padding: clamp(22px, 2.6vw, 32px) clamp(4px, 1vw, 16px);
border-bottom: 1px solid rgba(255, 255, 255, 0.16);
border-left: 2px solid transparent;
transition: border-color 0.3s ease, padding-left 0.3s ease, background-color 0.3s ease;
}
.epn-card:hover {
border-left-color: #c8102e;
background: rgba(200, 16, 46, 0.06);
padding-left: clamp(20px, 2.5vw, 32px);
}
.epn-card-index {
font-size: 15px;
font-weight: 900;
color: rgba(255, 255, 255, 0.35);
}
.epn-card-title {
font-size: clamp(22px, 2.2vw, 30px) !important;
font-weight: 900 !important;
line-height: 1.1 !important;
letter-spacing: -0.03em !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0 0 8px;
}
.epn-card-desc {
max-width: 46ch;
font-size: 14.5px;
line-height: 1.6;
color: rgba(255, 255, 255, 0.58);
margin: 0;
}
.epn-card-arrow {
display: flex;
color: rgba(255, 255, 255, 0.3);
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), color 0.3s ease;
}
.epn-card:hover .epn-card-arrow {
transform: translateX(4px);
color: #ff5470;
}
.epn-cta {
margin-top: clamp(42px, 5vw, 64px);
}
.epn-cta-btn {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 16px 36px;
border-radius: 100px;
background: #c01227;
color: #ffffff !important;
font-weight: 800;
font-size: 15px;
text-decoration: none !important;
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.35s ease;
}
.epn-cta-btn:hover {
transform: translateY(-3px);
box-shadow: 0 14px 30px rgba(192, 18, 39, 0.35);
}
@media (max-width: 1024px) {
.epn-section { width: calc(100% - 24px); border-radius: 22px; }
}
@media (max-width: 900px) {
.epn-head,
.epn-body { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
.epn-title {
font-size: clamp(34px, 10vw, 40px) !important;
letter-spacing: -0.045em !important;
overflow-wrap: anywhere;
}
.epn-card {
grid-template-columns: 44px minmax(0, 1fr);
}
.epn-card-arrow { display: none; }
}
@media (max-width: 460px) {
.epn-section { padding-top: 54px; padding-bottom: 54px; }
}
`,
}}
/>
<section className="epn-section" id="wings-empowerment-partners">
<div className="dm-section-container">
<div className="epn-inner">
<div className="epn-head">
<ScrollReveal delay={0.05} duration={0.75} yOffset={20} className="epn-head-reveal">
<div className="epn-eyebrow">/ Collaboration /</div>
<hr className="epn-divider" />
</ScrollReveal>
<ScrollReveal delay={0.1} duration={0.85} yOffset={24}>
<h2 className="epn-title">Partnership is how <em>ecosystems grow.</em></h2>
</ScrollReveal>
</div>
<div className="epn-body">
<ScrollReveal delay={0.08} duration={0.8} yOffset={22} className="epn-manifesto">
<p className="epn-intro">The strongest ecosystems are built by many hands.</p>
<p className="epn-note">
Wings partners bring industry access, strategic infrastructure, capital, and networks. Together, we build the foundation women-led enterprises need to thrive.
</p>
{/* <div className="epn-cta">
<Link href="/contact" className="epn-cta-btn">Become a Partner</Link>
</div> */}
</ScrollReveal>
<ScrollReveal delay={0.12} duration={0.8} yOffset={22}>
<div className="epn-grid">
{PARTNER_TYPES.map((p, i) => (
<article className="epn-card" key={p.title}>
<span className="epn-card-index">{String(i + 1).padStart(2, "0")}</span>
<div>
<h3 className="epn-card-title">{p.title}</h3>
<p className="epn-card-desc">{p.desc}</p>
</div>
<span className="epn-card-arrow" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</span>
</article>
))}
</div>
</ScrollReveal>
</div>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,405 @@
import Link from "next/link";
import { ScrollReveal } from "@/animations/Reveal";
import IndustryWorldMap from "./IndustryWorldMap";
const PROGRAMS = [
{
title: "Venture Launchpad",
desc: "Business model validation, market entry strategy, entity formation, and go-to-market execution for women-led logistics enterprises.",
type: "Start building",
},
{
title: "Strategic Partnerships",
desc: "Direct access to supply chain networks, B2B relationships, industry alliances, and commercial agreements that accelerate growth.",
type: "Connect and grow",
},
{
title: "Founder Mentorship",
desc: "One-on-one guidance from established entrepreneurs, operators, and investors who understand what it takes to scale.",
type: "Learn from builders",
},
{
title: "Growth & Scale",
desc: "Operational expansion frameworks, team building, funding readiness, and sustainable unit economics for ventures ready to grow.",
type: "Scale with confidence",
},
{
title: "Industry Immersion",
desc: "Real exposure to logistics operations, supply chain infrastructure, and distribution networks before founders commit to a vertical.",
type: "See the opportunity",
},
];
export default function EmpowermentPrograms() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.epr-section {
background: #ffffff;
padding: clamp(78px, 9vw, 132px) 0;
overflow: visible;
}
.epr-inner {
max-width: 1380px;
min-width: 0;
margin: 0 auto;
}
.epr-head {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: clamp(54px, 7vw, 86px);
}
.epr-head-reveal {
width: 100%;
}
.epr-divider {
width: 100%;
height: 1px;
margin: 18px 0 clamp(24px, 3.5vw, 40px) 0;
border: none;
background-color: rgba(17, 17, 17, 0.14);
}
.epr-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.4px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.42);
}
.epr-title {
max-width: 950px;
font-size: clamp(42px, 6.2vw, 88px) !important;
font-weight: 800 !important;
letter-spacing: -0.055em !important;
line-height: 0.98 !important;
text-transform: uppercase !important;
color: #111111 !important;
margin: 0;
}
.epr-stage {
display: grid;
grid-template-columns: minmax(300px, 0.38fr) minmax(0, 0.62fr);
gap: clamp(32px, 5vw, 78px);
align-items: start;
min-width: 0;
}
.epr-stage > * {
min-width: 0;
}
.epr-aside {
position: sticky;
top: 110px;
min-width: 0;
min-height: clamp(480px, 44vw, 660px);
padding: clamp(18px, 3vw, 34px) 0 clamp(24px, 3.5vw, 42px);
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
color: #111111;
}
.epr-aside-map {
position: absolute;
inset: 8% -22% 8% -28%;
z-index: 0;
opacity: 0.22;
pointer-events: none;
}
.epr-aside-map canvas {
display: block;
}
.epr-aside-content {
position: relative;
z-index: 1;
display: grid;
gap: clamp(18px, 2.2vw, 28px);
}
.epr-aside-num {
position: relative;
font-size: clamp(164px, 17vw, 300px);
font-weight: 900;
line-height: 0.78;
letter-spacing: -0.06em;
color: transparent;
-webkit-text-stroke: 2px rgba(200, 16, 46, 0.2);
}
.epr-aside-copy {
position: relative;
max-width: 12ch;
font-size: clamp(30px, 3.6vw, 56px);
font-weight: 800;
line-height: 0.98;
letter-spacing: -0.055em;
text-transform: uppercase;
color: #111111;
margin: 0;
}
.epr-aside-copy em {
font-style: normal;
color: #c8102e;
}
.epr-aside-desc {
position: relative;
max-width: 34ch;
font-size: 15.5px;
line-height: 1.65;
color: rgba(17, 17, 17, 0.58);
margin: 0;
}
.epr-list {
display: grid;
gap: clamp(28px, 4vw, 52px);
}
.epr-row {
display: grid;
grid-template-columns: 110px minmax(0, 1fr);
gap: clamp(22px, 4vw, 58px);
padding-bottom: clamp(28px, 4vw, 52px);
border-bottom: 1px solid rgba(17, 17, 17, 0.14);
}
.epr-row:nth-child(even) {
margin-left: clamp(0px, 7vw, 96px);
}
.epr-row-index {
font-size: clamp(38px, 4.2vw, 62px);
font-weight: 900;
line-height: 1;
letter-spacing: -0.05em;
color: #c8102e;
}
.epr-row-featured {
padding: clamp(28px, 4vw, 44px) clamp(24px, 3vw, 40px);
background: #09090b;
border-radius: 22px;
border-bottom: none;
margin-bottom: clamp(28px, 4vw, 52px);
}
.epr-row-featured .epr-row-index {
color: rgba(255, 255, 255, 0.22);
-webkit-text-stroke: 1.5px rgba(255, 255, 255, 0.5);
}
.epr-row-featured .epr-row-title {
font-size: clamp(32px, 4vw, 64px) !important;
color: #ffffff !important;
}
.epr-row-featured .epr-row-desc {
color: rgba(255, 255, 255, 0.62);
}
.epr-row-flag {
display: inline-block;
font-size: 12px;
font-weight: 800;
letter-spacing: 1.3px;
text-transform: uppercase;
color: #ffffff;
background: #c8102e;
padding: 6px 14px;
border-radius: 100px;
margin-bottom: 16px;
}
.epr-row-type {
display: block;
font-size: 12px;
font-weight: 800;
letter-spacing: 1.7px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.42);
margin-bottom: 14px;
}
.epr-row-featured .epr-row-type {
color: rgba(255, 255, 255, 0.42);
}
.epr-row-title {
font-size: clamp(28px, 3.4vw, 52px) !important;
font-weight: 800 !important;
letter-spacing: -0.045em !important;
line-height: 1.02 !important;
text-transform: uppercase !important;
color: #111111 !important;
margin: 0 0 14px 0;
}
.epr-row-desc {
max-width: 660px;
font-size: 15.5px;
line-height: 1.65;
color: rgba(17, 17, 17, 0.58);
margin: 0;
}
.epr-cta {
margin-top: clamp(48px, 6vw, 72px);
}
.epr-cta-link {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 15px;
font-weight: 800;
color: #111111 !important;
text-decoration: none !important;
border-bottom: 1.5px solid rgba(17, 17, 17, 0.2);
padding-bottom: 5px;
transition: border-color 0.3s ease, color 0.3s ease;
}
.epr-cta-link:hover {
color: #c8102e !important;
border-color: #c8102e;
}
/* Laptop: sticky stays enabled, just a narrower gutter. */
@media (max-width: 1180px) {
.epr-stage { grid-template-columns: minmax(220px, 0.34fr) minmax(0, 0.66fr); gap: clamp(28px, 4vw, 46px); }
.epr-aside { min-height: 420px; }
.epr-row:nth-child(even) { margin-left: 0; }
}
/* Tablet: sticky disabled — the aside scrolls in normal flow instead
of pinning, since there's no longer enough vertical runway next to
the (now narrower, shorter) program list for pinning to read well. */
@media (max-width: 1023px) {
.epr-aside {
position: relative;
top: auto;
}
}
@media (max-width: 767px) {
.epr-stage { grid-template-columns: 1fr; }
.epr-aside {
position: relative;
top: auto;
min-height: 300px;
padding-top: 4px;
padding-bottom: clamp(24px, 7vw, 38px);
}
.epr-aside-map {
inset: -8% -18% 0 -20%;
opacity: 0.18;
}
.epr-aside-content {
gap: 20px;
}
.epr-aside-num {
font-size: clamp(128px, 34vw, 180px);
}
.epr-aside-copy {
max-width: 11ch;
font-size: clamp(34px, 10vw, 48px);
}
.epr-aside-desc {
max-width: 32ch;
font-size: 14.5px;
}
}
@media (max-width: 640px) {
.epr-section { padding-top: 54px; padding-bottom: 54px; }
.epr-title {
font-size: clamp(34px, 10vw, 40px) !important;
letter-spacing: -0.045em !important;
overflow-wrap: anywhere;
}
.epr-row-title {
font-size: clamp(27px, 8.5vw, 36px) !important;
overflow-wrap: anywhere;
}
.epr-row { grid-template-columns: 1fr; gap: 12px; }
}
`,
}}
/>
<section className="epr-section" id="wings-empowerment-programs">
<div className="dm-section-container">
<div className="epr-inner">
<div className="epr-head">
<ScrollReveal delay={0.05} duration={0.75} yOffset={20} className="epr-head-reveal">
<div className="epr-eyebrow">/ Entrepreneurship Pillars /</div>
<hr className="epr-divider" />
</ScrollReveal>
<ScrollReveal delay={0.1} duration={0.85} yOffset={24}>
<h2 className="epr-title">Every pillar is built around ownership.</h2>
</ScrollReveal>
</div>
<div className="epr-stage">
<aside className="epr-aside">
<div className="epr-aside-map" aria-hidden="true">
<IndustryWorldMap />
</div>
<ScrollReveal delay={0.05} duration={0.8} yOffset={24} className="epr-aside-content">
<div className="epr-aside-num">05</div>
<p className="epr-aside-copy">Building tomorrow&rsquo;s <em>women entrepreneurs.</em></p>
<p className="epr-aside-desc">
Every enterprise starts with one decision. Our pillars move women from idea to ownership through mentorship, real industry partnerships, and hands-on venture building across the logistics ecosystem.
</p>
</ScrollReveal>
</aside>
<div>
<ScrollReveal delay={0.1} duration={0.8} yOffset={24}>
<div className="epr-list">
{PROGRAMS.map((p, i) => (
<article className={`epr-row${i === 0 ? " epr-row-featured" : ""}`} key={p.title}>
<div className="epr-row-index">{String(i + 1).padStart(2, "0")}</div>
<div>
{i === 0 && <span className="epr-row-flag">Where most founders begin</span>}
<span className="epr-row-type">{p.type}</span>
<h3 className="epr-row-title">{p.title}</h3>
<p className="epr-row-desc">{p.desc}</p>
</div>
</article>
))}
</div>
</ScrollReveal>
<div className="epr-cta">
<Link href="/contact" className="epr-cta-link">
Start your venture
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</Link>
</div>
</div>
</div>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,335 @@
import Image from "next/image";
import { ScrollReveal } from "@/animations/Reveal";
export default function EmpowermentStories() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.est-section {
position: relative;
background: #ffffff;
padding-top: clamp(24px, 3vw, 48px);
padding-bottom: clamp(78px, 9vw, 132px);
overflow: hidden;
}
.est-inner {
max-width: 1380px;
margin: 0 auto;
}
.est-head {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: clamp(54px, 7vw, 86px);
}
.est-head-reveal {
width: 100%;
}
.est-divider {
width: 100%;
height: 1px;
margin: 18px 0 clamp(24px, 3.5vw, 40px) 0;
border: none;
background-color: rgba(17, 17, 17, 0.14);
}
.est-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.4px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.42);
}
.est-title {
max-width: 940px;
font-size: clamp(42px, 6.3vw, 90px) !important;
font-weight: 800 !important;
letter-spacing: -0.055em !important;
line-height: 0.98 !important;
text-transform: uppercase !important;
color: #111111 !important;
margin: 0;
}
.est-feature {
display: grid;
grid-template-columns: minmax(0, 0.94fr) minmax(340px, 0.64fr);
gap: clamp(28px, 4vw, 62px);
align-items: stretch;
margin-bottom: clamp(44px, 6vw, 76px);
}
.est-photo {
position: relative;
min-height: clamp(460px, 48vw, 680px);
border-radius: 26px;
overflow: hidden;
background: #09090b;
}
.est-photo img {
object-fit: cover;
object-position: center;
}
.est-photo::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(9, 9, 11, 0) 34%, rgba(9, 9, 11, 0.72) 100%);
pointer-events: none;
}
.est-photo-caption {
position: absolute;
left: clamp(24px, 3vw, 42px);
right: clamp(24px, 3vw, 42px);
bottom: clamp(24px, 3vw, 42px);
z-index: 1;
color: #ffffff;
}
.est-photo-caption span {
display: block;
font-size: 12px;
font-weight: 800;
letter-spacing: 1.7px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 8px;
}
.est-photo-caption strong {
display: block;
font-size: clamp(24px, 3vw, 42px);
font-weight: 900;
line-height: 1.02;
letter-spacing: -0.04em;
}
.est-copy {
border-radius: 26px;
background: #09090b;
color: #ffffff;
padding: clamp(34px, 5vw, 62px);
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
position: relative;
}
.est-copy::before {
content: "";
position: absolute;
width: 320px;
height: 320px;
right: -120px;
top: -120px;
background: radial-gradient(circle, rgba(200, 16, 46, 0.42), transparent 68%);
pointer-events: none;
}
.est-quote-mark {
position: relative;
font-family: Georgia, serif;
font-size: 92px;
line-height: 0.8;
color: #c8102e;
display: block;
margin-bottom: 20px;
}
.est-quote-text {
position: relative;
font-size: clamp(25px, 3vw, 44px);
font-weight: 800;
line-height: 1.12;
letter-spacing: -0.04em;
color: #ffffff;
margin: 0 0 28px 0;
}
.est-narrative {
position: relative;
font-size: 15.5px;
line-height: 1.68;
color: rgba(255, 255, 255, 0.66);
margin: 0;
max-width: 46ch;
}
.est-person {
position: relative;
padding-top: clamp(38px, 5vw, 64px);
}
.est-person-name {
font-size: 18px;
font-weight: 900;
color: #ffffff;
}
.est-person-role {
font-size: 13.5px;
color: rgba(255, 255, 255, 0.52);
margin-top: 3px;
}
.est-list-label {
font-size: 12px;
font-weight: 800;
letter-spacing: 1.8px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.42);
padding-top: clamp(24px, 3vw, 38px);
border-top: 1px solid rgba(17, 17, 17, 0.14);
margin-bottom: clamp(20px, 3vw, 30px);
}
.est-list {
display: flex;
gap: 18px;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding-bottom: 12px;
margin-bottom: clamp(6px, 1vw, 10px);
-webkit-overflow-scrolling: touch;
}
.est-list::-webkit-scrollbar {
height: 4px;
}
.est-list::-webkit-scrollbar-thumb {
background: rgba(17, 17, 17, 0.18);
border-radius: 100px;
}
.est-row {
scroll-snap-align: start;
flex: 0 0 clamp(260px, 30vw, 340px);
min-height: 220px;
padding: clamp(24px, 2.6vw, 32px);
border: 1px solid rgba(17, 17, 17, 0.14);
border-radius: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: border-color 0.3s ease, transform 0.3s ease;
}
.est-row:hover {
border-color: rgba(200, 16, 46, 0.4);
transform: translateY(-4px);
}
.est-row-name {
font-size: 20px;
font-weight: 900;
color: #111111;
}
.est-row-role {
font-size: 13px;
font-weight: 800;
letter-spacing: 1.4px;
text-transform: uppercase;
color: #c8102e;
margin-top: 6px;
}
.est-row-line {
font-size: 15px;
line-height: 1.6;
color: rgba(17, 17, 17, 0.62);
margin: 28px 0 0;
}
@media (max-width: 1000px) {
.est-head,
.est-feature { grid-template-columns: 1fr; }
.est-row {
min-height: 0;
flex-basis: 78vw;
}
}
@media (max-width: 640px) {
.est-section { padding-top: 54px; padding-bottom: 54px; }
.est-title {
font-size: clamp(34px, 10vw, 40px) !important;
letter-spacing: -0.045em !important;
overflow-wrap: anywhere;
}
.est-quote-text {
font-size: clamp(25px, 8vw, 34px);
overflow-wrap: anywhere;
}
.est-photo,
.est-copy { border-radius: 20px; }
.est-photo { min-height: 420px; }
}
`,
}}
/>
<section className="est-section" id="wings-empowerment-stories">
<div className="dm-section-container">
<div className="est-inner">
<div className="est-head">
<ScrollReveal delay={0.05} duration={0.75} yOffset={20} className="est-head-reveal">
<div className="est-eyebrow">/ Women in Motion /</div>
<hr className="est-divider" />
</ScrollReveal>
<ScrollReveal delay={0.1} duration={0.85} yOffset={24}>
<h2 className="est-title">Small steps. Lasting careers.</h2>
</ScrollReveal>
</div>
<div className="est-feature">
<ScrollReveal delay={0.05} duration={0.85} yOffset={24} className="est-photo">
<Image
src="/images/women-hero-slider-3.png"
alt="A Wings program participant on the operations floor at a Doormile logistics hub"
fill
sizes="(max-width: 1000px) 100vw, 58vw"
style={{ objectPosition: "80% 22%" }}
/>
<div className="est-photo-caption">
<span>Wings Participant</span>
<strong> building a career, one milestone at a time.</strong>
</div>
</ScrollReveal>
<ScrollReveal delay={0.12} duration={0.85} yOffset={24} className="est-copy">
<div>
<span className="est-quote-mark" aria-hidden="true">&ldquo;</span>
<p className="est-quote-text">I came in looking for a way forward. I found the confidence to lead.</p>
<p className="est-narrative">
She started with limited exposure to logistics. Through structured training, mentorship, and hands-on experience, she built real operational skills and now supports other women stepping into the industry.
</p>
</div>
{/* <div className="est-person">
<div className="est-person-name">Kavya</div>
<div className="est-person-role">Logistics Coordinator, Wings Program</div>
</div> */}
</ScrollReveal>
</div>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,164 @@
import { ScrollReveal } from "@/animations/Reveal";
export default function EmpowermentStory() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.esy-section {
position: relative;
width: calc(100% - 40px);
margin: 0 auto clamp(24px, 4vw, 40px);
border-radius: 28px;
overflow: hidden;
box-sizing: border-box;
min-height: clamp(500px, 50vw, 690px);
display: flex;
align-items: stretch;
background: #09090b;
}
.esy-bg {
position: absolute;
inset: 0;
background-image:
linear-gradient(100deg, rgba(9, 9, 11, 0.96) 0%, rgba(9, 9, 11, 0.84) 43%, rgba(192, 18, 39, 0.28) 100%),
url("/images/wings-heroslider4.png");
background-size: cover;
background-position: center;
z-index: 0;
}
.esy-inner {
position: relative;
z-index: 1;
max-width: 1380px;
margin: 0 auto;
width: 100%;
padding-top: clamp(60px, 7vw, 98px);
padding-bottom: clamp(60px, 7vw, 98px);
display: grid;
grid-template-columns: minmax(0, 0.9fr) minmax(280px, 0.52fr);
gap: clamp(36px, 6vw, 96px);
align-items: end;
}
.esy-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.5px;
text-transform: uppercase;
color: #ff5470;
margin-bottom: 24px;
}
.esy-statement {
max-width: 920px;
font-size: clamp(38px, 5.7vw, 86px);
font-weight: 800;
letter-spacing: -0.055em;
line-height: 0.98;
text-transform: uppercase;
color: #ffffff;
margin: 0;
}
.esy-statement em {
color: #ff5470;
font-style: normal;
}
.esy-side {
border-left: 1px solid rgba(255, 255, 255, 0.18);
padding-left: clamp(24px, 3vw, 42px);
}
.esy-label {
display: block;
font-size: 12px;
font-weight: 800;
letter-spacing: 1.8px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.45);
margin-bottom: 18px;
}
.esy-pull {
font-size: clamp(22px, 2.3vw, 34px);
font-weight: 800;
line-height: 1.16;
letter-spacing: -0.03em;
color: #ffffff;
margin: 0 0 22px 0;
}
.esy-sub {
font-size: 15.5px;
line-height: 1.65;
color: rgba(255, 255, 255, 0.64);
margin: 0;
}
/* ---- Desktop-only inner spacing compaction ----
.esy-section never set its own padding, so it was silently
inheriting 96px top/bottom from the global bare-tag "section"
padding rule (6rem 0) in site.css — stacking with .esy-inner's
own clamp(60-98px) padding into a huge, unbalanced inset. Cancel
that leak here. Also tighten .esy-inner's own padding and widen its
max-width (so a fixed padding, not a narrow centered column,
controls the left/right inset at every desktop width) — and bring
min-height down to match the now-tighter natural content height,
since the old 690px floor was itself forcing extra empty space
once the padding was reduced. Scoped to desktop only; tablet/
mobile keep their original spacing untouched below. */
@media (min-width: 1025px) {
.esy-section {
padding: 0;
min-height: clamp(420px, 38vw, 500px);
}
.esy-inner {
max-width: 1900px;
padding-top: 64px;
padding-bottom: 56px;
padding-left: 64px;
padding-right: 64px;
}
}
@media (max-width: 1024px) {
.esy-section { width: calc(100% - 24px); border-radius: 22px; }
.esy-inner { grid-template-columns: 1fr; align-items: start; }
.esy-side { max-width: 640px; }
}
@media (max-width: 640px) {
.esy-section { min-height: 560px; }
.esy-side { border-left: none; padding-left: 0; border-top: 1px solid rgba(255, 255, 255, 0.18); padding-top: 24px; }
}
`,
}}
/>
<section className="esy-section" id="wings-empowerment-story">
<div className="esy-bg" aria-hidden="true" />
<div className="esy-inner dm-section-container">
<ScrollReveal delay={0.05} duration={0.85} yOffset={25}>
<div className="esy-eyebrow">/ Our Approach /</div>
<p className="esy-statement">
Not a program on a brochure. <em>An ecosystem women can build in.</em>
</p>
</ScrollReveal>
<ScrollReveal delay={0.15} duration={0.8} yOffset={25} className="esy-side">
<span className="esy-label">Manifesto</span>
<p className="esy-pull">A venture that starts with conviction. A network that turns it into momentum.</p>
<p className="esy-sub">
Wings is designed around the moments that define a founder: the first partnership signed, the first client won, the first team hired, the first enterprise scaled.
</p>
</ScrollReveal>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,215 @@
import { ScrollReveal } from "@/animations/Reveal";
import IndustryWorldMap from "./IndustryWorldMap";
export default function EmpowermentVision() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.ev-section {
position: relative;
background: #ffffff;
padding: clamp(96px, 11vw, 150px) 0;
overflow: hidden;
}
.ev-map {
position: absolute;
inset: 0;
z-index: 0;
opacity: 0.4;
pointer-events: none;
}
.ev-inner {
position: relative;
z-index: 1;
max-width: 1380px;
min-width: 0;
margin: 0 auto;
}
.ev-eyebrow {
margin: 0 0 18px 0;
font-size: 14px;
font-weight: 500;
letter-spacing: 1.5px;
color: rgba(17, 17, 17, 0.6);
}
.ev-divider {
width: 100%;
height: 1px;
margin: 0 0 clamp(64px, 7.8vw, 92px) 0;
border: none;
background-color: rgba(17, 17, 17, 0.16);
}
.ev-title {
margin: 0 0 clamp(92px, 11.5vw, 156px) 0 !important;
max-width: 1320px;
display: block;
font-weight: 800 !important;
font-style: normal !important;
letter-spacing: -0.018em !important;
word-spacing: 0.02em;
line-height: 1.04em !important;
text-transform: none !important;
color: #ffffff !important;
-webkit-text-stroke: 2px #c01227;
paint-order: stroke fill;
font-size: clamp(48px, 8.5vw, 128px) !important;
white-space: normal;
overflow-wrap: normal;
text-wrap: balance;
}
@media (max-width: 1280px) {
.ev-title {
font-size: 118px !important;
}
}
@media (max-width: 1020px) {
.ev-title {
font-size: 96px !important;
}
}
@media (max-width: 767px) {
.ev-title {
font-size: 72px !important;
}
}
.ev-columns {
display: grid;
grid-template-columns: minmax(0, 0.6fr) minmax(0, 1fr);
gap: clamp(60px, 8vw, 122px);
align-items: start;
}
.ev-columns > * {
min-width: 0;
}
.ev-statement {
margin: 0;
font-size: clamp(28px, 3vw, 52px);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03em;
color: #111111;
overflow-wrap: break-word;
}
.ev-support {
display: grid;
gap: clamp(34px, 3.8vw, 52px);
min-width: 0;
}
.ev-block {
display: grid;
gap: 12px;
}
.ev-block-label {
margin: 0;
font-size: 12px;
font-weight: 700;
letter-spacing: 1.4px;
line-height: 1;
text-transform: uppercase;
color: rgba(192, 18, 39, 0.86);
}
.ev-lead,
.ev-note {
font-size: clamp(17px, 1.3vw, 20px);
line-height: 1.6;
color: #333333;
margin: 0;
overflow-wrap: break-word;
}
.ev-lead {
font-weight: 700;
}
.ev-note {
font-weight: 400;
}
@media (max-width: 900px) {
.ev-columns {
grid-template-columns: 1fr;
}
}
@media (max-width: 600px) {
.ev-section { padding-top: 64px; padding-bottom: 64px; }
.ev-title {
max-width: calc(100vw - (var(--dm-mobile-gutter, 10px) * 2));
font-size: 56px !important;
letter-spacing: -0.025em !important;
line-height: 1.02em !important;
-webkit-text-stroke-width: 1.5px;
}
}
`,
}}
/>
<section className="ev-section" id="wings-empowerment-vision">
<div className="ev-map" aria-hidden="true">
<IndustryWorldMap />
</div>
<div className="dm-section-container">
<div className="ev-inner">
<ScrollReveal delay={0.05} duration={0.7} yOffset={20}>
<p className="ev-eyebrow">/ Our Vision /</p>
<hr className="ev-divider" />
</ScrollReveal>
<ScrollReveal delay={0.12} duration={0.85} yOffset={30}>
<h2 className="ev-title">Women Own The Future.</h2>
</ScrollReveal>
<div className="ev-columns">
<ScrollReveal delay={0.18} duration={0.8} yOffset={25}>
<p className="ev-statement">
A logistics ecosystem where women don&rsquo;t just participate &mdash; they own, build, and lead.
</p>
</ScrollReveal>
<ScrollReveal delay={0.26} duration={0.8} yOffset={25} className="ev-support">
<div className="ev-block">
<p className="ev-block-label">The vision</p>
<p className="ev-lead">
We are building an entrepreneurial ecosystem where women launch enterprises, scale operations, and shape the future of logistics from the inside.
</p>
</div>
<div className="ev-block">
<p className="ev-block-label">Why it matters</p>
<p className="ev-note">
Industries transform when founders emerge from every corner. Women-led enterprises bring fresh perspective, operational resilience, and long-term economic impact to logistics.
</p>
</div>
<div className="ev-block">
<p className="ev-block-label">How Wings contributes</p>
<p className="ev-note">
Wings provides the infrastructure &mdash; strategic partnerships, funding access, founder networks, and industry connections &mdash; that turns ambition into enterprise.
</p>
</div>
</ScrollReveal>
</div>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,75 @@
/* "The Doormile Way" — eyebrow + hairline divider + a huge display/graphic
headline (transparent fill, thin red stroke), composed as a poster-scale
typographic element rather than a plain large heading. Font, scale, and
line length are deliberately tuned to the reference composition, not
derived from a generic fluid-fill formula. */
.header {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
}
.kicker {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
}
.eyebrow {
margin: 0 0 18px 0;
font-size: 14px;
font-weight: 500;
letter-spacing: 1.5px;
color: rgba(23, 20, 15, 0.6);
}
.divider {
width: 100%;
height: 1px;
margin: 0 0 40px 0;
border: none;
background-color: rgba(23, 20, 15, 0.16);
}
.headingWrap {
width: 100%;
}
/* !important beats the sitewide `.elementor-kit-5 h2` rule
(public/css/site.css:48-56, specificity 0-1-1 > this single class). */
.heading {
margin: 0;
max-width: 1220px;
font-family: "Manrope Alt", Sans-serif !important;
font-weight: 700 !important;
font-style: normal !important;
letter-spacing: -0.03em !important;
line-height: 1em !important;
text-transform: none !important;
color: transparent !important;
-webkit-text-stroke-width: 2px;
-webkit-text-stroke-color: #c01227;
font-size: 180px !important;
}
@media (max-width: 1020px) {
.heading {
font-size: 120px !important;
}
}
@media (max-width: 767px) {
.heading {
font-size: 80px !important;
}
}
@media (max-width: 640px) {
.heading {
font-size: 56px !important;
-webkit-text-stroke-width: 1.5px;
}
}

View File

@@ -2,6 +2,7 @@
import React from "react";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import styles from "./TheDoormileWay.module.css";
export default function TheDoormileWay() {
return (
@@ -10,12 +11,10 @@ export default function TheDoormileWay() {
<div className="elementor-element elementor-element-88745f4 e-flex e-con-boxed cut-corner-no sticky-container-off e-con e-parent" data-id="88745f4" data-element_type="container" data-e-type="container">
<div className="e-con-inner">
<div className="elementor-element elementor-element-343b363 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="343b363" data-element_type="container" data-e-type="container">
<ScrollReveal delay={0.05} duration={0.7} yOffset={20}>
<div className="elementor-element elementor-element-7afb238 elementor-widget elementor-widget-logico_heading" data-id="7afb238" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default">
<div className="elementor-widget-container">
<div className="logico-title">/ Doormile Approach /</div>
</div>
</div>
<div className={styles.header}>
<ScrollReveal delay={0.05} duration={0.7} yOffset={20} className={styles.kicker}>
<p className={styles.eyebrow}>/ Doormile Approach /</p>
<hr className={styles.divider} />
</ScrollReveal>
<ScrollReveal delay={0.15} duration={0.85} yOffset={30}>
<div className="elementor-element elementor-element-1cc335a elementor-widget elementor-widget-logico_heading" data-id="1cc335a" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default">
@@ -33,6 +32,7 @@ export default function TheDoormileWay() {
</div>
</div>
</ScrollReveal>
</div>
<div className="elementor-element elementor-element-4c3e632 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="4c3e632" data-element_type="container" data-e-type="container">
<ScrollReveal delay={0.2} duration={0.8} yOffset={25}>
<div className="elementor-element elementor-element-5ba6bbf e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="5ba6bbf" data-element_type="container" data-e-type="container">

View File

@@ -0,0 +1,177 @@
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
const FEATURES = [
{
title: "AI Decision Engine",
desc: "MileTruth™ AI continuously forecasts demand and optimizes routing and load decisions in real time.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 2a4 4 0 0 1 4 4v1a4 4 0 0 1-1.2 2.86A4 4 0 0 1 16 13v1a4 4 0 0 1-4 4 4 4 0 0 1-4-4v-1a4 4 0 0 1 1.2-3.14A4 4 0 0 1 8 7V6a4 4 0 0 1 4-4Z" />
<path d="M9 21h6M12 18v3" />
</svg>
),
},
{
title: "Real-Time Visibility",
desc: "Live shipment tracking keeps shippers, ground teams, and customers updated at every stage.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z" />
<circle cx="12" cy="12" r="3" />
</svg>
),
},
{
title: "Faster Regional Reach",
desc: "A dedicated network linking major regional capitals, built for speed rather than passenger schedules.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="13 6 19 12 13 18" />
</svg>
),
},
{
title: "Reliable Scheduled Operations",
desc: "Dedicated freighter capacity with consistent schedules — not subject to passenger-belly bumping.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="4" width="18" height="18" rx="3" />
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
<path d="M9 16l2 2 4-4" />
</svg>
),
},
];
export default function WingsFeatureGrid() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.wfg-section {
background: #ffffff;
padding: var(--space-section-lg, 80px) 0;
}
.wfg-header {
margin: 0 auto 48px auto;
max-width: 720px;
text-align: center;
}
.wfg-eyebrow {
font-size: 13px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.45);
margin-bottom: 16px;
}
.wfg-title {
font-size: clamp(28px, 3.2vw, 46px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0;
}
.wfg-inner {
max-width: 1380px;
margin: 0 auto;
}
.wfg-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 20px;
}
.wfg-card {
min-width: 0;
background: #ffffff;
border: 1px solid rgba(17, 17, 17, 0.1);
border-radius: 20px;
padding: 30px 26px;
transition: border-color 0.35s ease, box-shadow 0.35s ease, transform 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.wfg-card:hover {
border-color: #c8102e;
box-shadow: 0 20px 40px rgba(200, 16, 46, 0.08);
transform: translateY(-8px);
}
.wfg-icon {
width: 46px;
height: 46px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: rgba(200, 16, 46, 0.06);
color: #c8102e;
margin-bottom: 20px;
}
.wfg-card-title {
font-size: 16.5px !important;
font-weight: 700 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0 0 8px 0;
}
.wfg-card-desc {
font-size: 14px;
line-height: 1.55;
color: rgba(17, 17, 17, 0.6);
margin: 0;
}
@media (max-width: 1024px) {
.wfg-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
.wfg-grid { grid-template-columns: 1fr; }
/* var(--space-section-lg) floors at a fixed 52px on any phone
width (clamp() has no further breakpoint-based step-down), which
reads as noticeably heavier than the section rhythm near the
footer. Trimmed to a real mobile value here instead. */
.wfg-section { padding-top: 44px; padding-bottom: 44px; }
}
`,
}}
/>
<section className="wfg-section" id="wings-features">
<div className="dm-section-container">
<div className="wfg-inner">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wfg-header">
<div className="wfg-eyebrow">/ Why Businesses Choose Doormile Wings /</div>
<h2 className="wfg-title">Built to be depended on</h2>
</ScrollReveal>
<StaggerChildren stagger={0.1} duration={0.7} yOffset={30} className="wfg-grid">
{FEATURES.map((f) => (
<div key={f.title} className="wfg-card">
<div className="wfg-icon" aria-hidden="true">{f.icon}</div>
<h3 className="wfg-card-title">{f.title}</h3>
<p className="wfg-card-desc">{f.desc}</p>
</div>
))}
</StaggerChildren>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,105 @@
import EVSection, { type EVFeature, type EVStat } from "./EVSection";
// Placeholder banner — plain dark gradient, no photography (no ATR72/Q400
// or airport-hub imagery exists in the repo yet). Swap `bannerImage` for a
// sourced photo later; this keeps the section shippable in the meantime.
// EVSection embeds this value in an UNQUOTED CSS url(...).
const METRICS: EVStat[] = [
{ value: 8.2, decimals: 1, suffix: "t", label: "ATR72 Payload" },
{ value: 1600, suffix: "km", label: "ATR72 Range" },
{ value: 8.6, decimals: 1, suffix: "t", label: "Q400 Payload" },
{ value: 667, suffix: "km/h", label: "Q400 Speed" },
];
const FEATURES: EVFeature[] = [
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 12h-6l-4-9-2 1 3 8H5l-2-3-1.5.6L3 14l1.5 4.4L6 18l-2-3h8l3 8 2-1-3-8h6z" />
</svg>
),
title: "Dedicated Freighter Capacity",
desc: "Purpose-built cargo lift, not passenger-belly space competing with baggage.",
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="6" cy="6" r="2.5" />
<circle cx="18" cy="6" r="2.5" />
<circle cx="12" cy="18" r="2.5" />
<path d="M8.2 7.2 10 16M15.8 7.2 14 16M8.5 6h7" />
</svg>
),
title: "Regional Route Design",
desc: "Turboprop range and speed matched to India's major regional-capital corridors.",
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 3v18h18" />
<path d="M7 14l4-4 3 3 6-7" />
</svg>
),
title: "Scaling to 25 Aircraft",
desc: "Phase 2 vision: pan-India connectivity hubs and international regional routes.",
},
];
export default function WingsFleetStrategy() {
return (
<div id="wings-fleet">
{/* EVSection is shared across other pages (Workflow1/Workflow2 etc.),
so its own .evnd-gap-bottom rule isn't touched — this override is
scoped to #wings-fleet only, and reduces just the blank margin
immediately before Infrastructure by ~42% (24px -> 14px) on
mobile, matching the site's tighter mobile rhythm elsewhere. */}
<style
dangerouslySetInnerHTML={{
__html: `
@media (max-width: 767px) {
#wings-fleet .evnd-gap-bottom { margin-bottom: 14px !important; }
}
`,
}}
/>
<EVSection
bannerImage="/images/pahse1.png"
cardTitle="Quick Logistics"
cardSubtitle="Same-Day Air Network"
eyebrow="/ Fleet Strategy /"
titleLead="Purpose-Built for "
titleAccent="Regional Speed"
features={FEATURES}
metrics={METRICS}
cardsHeading="FLEET SPECIFICATIONS"
cardsTheme={{ accent: "#c8102e", accent2: "#ff6a3d", glow: "rgba(200,16,46,0.22)" }}
gapTop
gapBottom
badges={[
{ value: "3", label: "Phase 1 Aircraft" },
{ value: "25", label: "Phase 2 Vision" },
]}
mediaSlot={
<video
autoPlay
loop
muted
playsInline
style={{
width: "100%",
height: "100%",
minHeight: 420,
objectFit: "cover",
display: "block",
background: "#17171b"
}}
>
<source src="/videos/fleet.mp4" type="video/mp4" />
</video>
}
ariaLabel="Doormile Wings fleet strategy"
/>
</div>
);
}

View File

@@ -0,0 +1,584 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import Link from "next/link";
import gsap from "gsap";
import { ShimmerText } from "@/animations/Reveal";
export default function WingsHero() {
const [activeSlide, setActiveSlide] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const interval = setInterval(() => {
setActiveSlide((prev) => (prev === 0 ? 1 : 0));
}, 7000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (!containerRef.current) return;
const activeItem = containerRef.current.querySelector(".owl-item.active");
if (!activeItem) return;
const heading = activeItem.querySelector(".heading-content");
const text = activeItem.querySelector(".text-content");
const actions = activeItem.querySelector(".wings-hero-actions");
const animatedEls = [heading, text, actions].filter(Boolean);
if (!animatedEls.length) return;
gsap.killTweensOf(animatedEls);
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
gsap.set(animatedEls, { y: 0, opacity: 1, scale: 1 });
return;
}
if (heading) {
gsap.fromTo(
heading,
{ y: 55, opacity: 0, scale: 0.95 },
{ y: 0, opacity: 1, scale: 1, duration: 1.1, ease: "power4.out" }
);
}
if (text) {
gsap.fromTo(
text,
{ y: 30, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.95, ease: "power3.out", delay: 0.25 }
);
}
if (actions) {
gsap.fromTo(
actions,
{ y: 24, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.85, ease: "power3.out", delay: 0.38 }
);
}
}, [activeSlide]);
const handleDotClick = (index: number) => {
setActiveSlide(index);
};
const toggleSlide = () => {
setActiveSlide((prev) => (prev === 0 ? 1 : 0));
};
return (
<>
<link rel="preload" as="image" href="/images/wings-heroslider4.png" />
<link rel="preload" as="image" href="/images/wings-heroslide2.png" />
<style
dangerouslySetInnerHTML={{
__html: `
.wings-hero-bg.elementor-repeater-item-3264830,
.wings-hero-bg.elementor-repeater-item-6867061 {
background-image: linear-gradient(rgba(0, 0, 0, 0.38), rgba(0, 0, 0, 0.06)), url('/images/wings-heroslider4.png') !important;
background-position: center !important;
background-repeat: no-repeat !important;
background-size: cover !important;
}
.wings-hero-bg.elementor-repeater-item-6867061 {
background-image: linear-gradient(rgba(0, 0, 0, 0.38), rgba(0, 0, 0, 0.06)), url('/images/wings-heroslide2.png') !important;
}
#doormile-wings,
#doormile-wings .content,
#doormile-wings .content-inner,
#doormile-wings .elementor {
max-width: 100vw !important;
overflow-x: hidden !important;
}
.wings-hero-page,
.wings-hero-page .elementor-widget-container,
.wings-hero-page .logico-content-slider-widget,
.wings-hero-page .content-slider-wrapper,
.wings-hero-page .content-slider-container,
.wings-hero-page .content-slider,
.wings-hero-page .owl-stage-outer,
.wings-hero-page .owl-stage,
.wings-hero-page .owl-item,
.wings-hero-page .slider-item,
.wings-hero-page .slide-content,
.wings-hero-page .slide-content-inner {
max-width: 100% !important;
box-sizing: border-box !important;
}
.wings-hero-page .slide-content {
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
align-items: center !important;
text-align: center !important;
width: 100% !important;
height: 100% !important;
padding: 0 !important;
box-sizing: border-box !important;
}
.wings-hero-page .slide-content-inner {
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
align-items: center !important;
text-align: center !important;
width: 100% !important;
max-width: 1000px !important;
margin: 0 auto !important;
box-sizing: border-box !important;
}
.wings-hero-page .content-slider-item-heading,
.wings-hero-page .content-slider-item-heading .heading-content {
text-align: center !important;
display: block !important;
width: 100% !important;
margin-left: auto !important;
margin-right: auto !important;
padding-left: 15px !important;
padding-right: 15px !important;
white-space: normal !important;
overflow-wrap: break-word !important;
}
.wings-hero-page .content-slider-item-heading {
font-size: clamp(30px, 5.5vw, 80px) !important;
line-height: 1.05 !important;
}
.wings-hero-page .content-slider-item-text {
display: flex !important;
justify-content: center !important;
width: 100% !important;
margin-top: 23px !important;
}
.wings-hero-page .text-content {
text-align: center !important;
max-width: 680px !important;
width: 100% !important;
margin: 0 auto !important;
padding-left: 15px !important;
padding-right: 15px !important;
box-sizing: border-box !important;
white-space: normal !important;
overflow-wrap: break-word !important;
}
.wings-hero-page .content-slider-item-text p {
font-size: clamp(16px, 1.35vw, 23px) !important;
line-height: 1.65 !important;
white-space: normal !important;
overflow-wrap: break-word !important;
}
.wings-hero-actions {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 16px;
margin-top: 30px;
position: relative;
z-index: 5;
opacity: 1;
visibility: visible;
}
.wings-hero-actions .logico-alter-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
opacity: 1;
visibility: visible;
}
.wings-mobile-break {
display: none;
}
.wings-hero-page a.wings-hero-primary,
.wings-hero-page a.wings-hero-secondary {
--logico-button-text-color: #ffffff;
--logico-button-border-color: rgba(255, 255, 255, 0.72);
--logico-button-background-color: rgba(255, 255, 255, 0);
--logico-button-text-hover: #ffffff;
--logico-button-border-hover: #c01227;
--logico-button-background-hover: #c01227;
--logico-button-icon-hover: #ffffff;
}
.wings-hero-page a.wings-hero-primary {
--logico-button-border-color: #c01227;
--logico-button-background-color: #c01227;
}
.wings-hero-page a.wings-hero-primary:hover,
.wings-hero-page a.wings-hero-secondary:hover {
color: #ffffff !important;
}
@media (min-width: 1025px) {
.wings-hero-page.elementor-element-741f56c {
padding: 20px !important;
}
.wings-hero-page .owl-carousel.owl-theme .content-item {
height: 800px !important;
min-height: 800px !important;
border-radius: 24px !important;
overflow: hidden !important;
}
}
@media (max-width: 1024px) {
.wings-hero-page.elementor-element-741f56c {
width: 100% !important;
max-width: 100% !important;
margin-left: auto !important;
margin-right: auto !important;
--padding-top: 0px !important;
--padding-block-start: 0px !important;
padding: 0 10px 10px 10px !important;
overflow: hidden !important;
border-radius: 25px !important;
}
.wings-hero-page .owl-stage-outer {
height: 600px !important;
}
.wings-hero-page .owl-carousel.owl-theme .content-item {
width: 100% !important;
min-height: 600px !important;
height: 600px !important;
border-radius: 25px !important;
overflow: hidden !important;
}
.wings-hero-page .slide-content {
min-height: 600px !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
}
@media (max-width: 767px) {
.wings-hero-page.elementor-element-741f56c {
width: 100vw !important;
--padding-top: 0px !important;
--padding-block-start: 0px !important;
padding: 0 10px 10px 10px !important;
border-radius: 22px !important;
}
.wings-hero-page .slide-content,
.wings-hero-page .slide-content-inner {
width: calc(100vw - 20px) !important;
max-width: calc(100vw - 20px) !important;
}
.wings-hero-page .slide-content-inner {
margin-left: 0 !important;
margin-right: auto !important;
}
.wings-hero-page .owl-stage-outer {
height: 520px !important;
}
.wings-hero-page .owl-carousel.owl-theme .content-item,
.wings-hero-page .slide-content {
min-height: 520px !important;
height: 520px !important;
border-radius: 22px !important;
}
.wings-hero-page .slide-content-inner {
width: 100% !important;
max-width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
.wings-hero-page .content-slider-item-heading {
max-width: 100% !important;
font-size: clamp(20px, 7vw, 32px) !important;
line-height: 1.12 !important;
}
.wings-hero-page .content-slider-item-text p {
font-size: 14px !important;
line-height: 1.58 !important;
}
.wings-hero-page .content-slider-item-text,
.wings-hero-page .text-content {
max-width: min(280px, calc(100vw - 70px)) !important;
width: min(280px, calc(100vw - 70px)) !important;
}
.wings-hero-actions {
flex-direction: column;
gap: 10px;
margin-top: 18px;
}
.wings-hero-actions .logico-alter-button {
min-width: 0 !important;
width: 190px !important;
padding: 0.78em 3.7em 0.78em 1.35em !important;
min-height: 42px !important;
font-size: 12px !important;
}
.wings-mobile-break {
display: block;
}
.wings-hero-page .slider-footer {
text-align: right !important;
margin-bottom: 24px !important;
}
.wings-hero-page .slider-footer .slider-pagination {
justify-content: flex-end !important;
margin-left: 22px !important;
margin-right: 22px !important;
}
.wings-hero-page .owl-dots,
.wings-hero-page .owl-nav {
display: none !important;
}
}
`,
}}
/>
<div
className="wings-hero-page elementor-element elementor-element-741f56c e-con-full e-flex cut-corner-no sticky-container-off e-con e-parent"
data-id="741f56c"
data-element_type="container"
data-e-type="container"
>
<div
className="elementor-element elementor-element-6c7cbcb elementor-widget elementor-widget-logico_content_slider"
data-id="6c7cbcb"
data-element_type="widget"
data-e-type="widget"
data-widget_type="logico_content_slider.default"
>
<div className="elementor-widget-container">
<div className="logico-content-slider-widget">
<div className="content-slider-wrapper">
<div className="content-slider-container">
<div
className="content-slider owl-carousel owl-theme nav-view-vertical nav-h-position-right nav-v-position-bottom owl-loaded owl-drag"
ref={containerRef}
>
<div
className="owl-stage-outer"
style={{ position: "relative", overflow: "hidden", height: "800px" }}
>
<div
className="owl-stage"
style={{ position: "relative", width: "100%", height: "100%" }}
>
<div
className={`owl-item ${activeSlide === 0 ? "active" : ""}`}
style={{
position: "relative",
width: "100%",
opacity: activeSlide === 0 ? 1 : 0,
visibility: activeSlide === 0 ? "visible" : "hidden",
transition: "opacity 0.8s ease-in-out, visibility 0.8s ease-in-out",
zIndex: activeSlide === 0 ? 2 : 1,
}}
>
<div className="content-item slider-item elementor-repeater-item-3264830 slide-style-standard wings-hero-bg">
<div className="slide-content">
<div className="slide-content-inner">
<h1 className="content-slider-item-heading logico-content-wrapper-1">
<span className="heading-content">
Where Quick Commerce
<br />
<ShimmerText className="font-extrabold">
Meets Quick Logistics
</ShimmerText>
</span>
</h1>
<div className="content-slider-item-text logico-content-wrapper-2">
<div className="text-content">
<p>
MileTruth AI forecasts demand, protects promises,
and coordinates every handoff from runway to
doorstep.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div
className={`owl-item ${activeSlide === 1 ? "active" : ""}`}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
opacity: activeSlide === 1 ? 1 : 0,
visibility: activeSlide === 1 ? "visible" : "hidden",
transition: "opacity 0.8s ease-in-out, visibility 0.8s ease-in-out",
zIndex: activeSlide === 1 ? 2 : 1,
}}
>
<div className="content-item slider-item elementor-repeater-item-6867061 slide-style-standard wings-hero-bg">
<div className="slide-content">
<div className="slide-content-inner">
<h1 className="content-slider-item-heading logico-content-wrapper-1">
<span className="heading-content">
Doormile Wings
<br />
<ShimmerText className="font-extrabold">
Same-Day Air
</ShimmerText>
<br className="wings-mobile-break" />
{" "}
<ShimmerText className="font-extrabold">
Logistics
</ShimmerText>
</span>
</h1>
<div className="content-slider-item-text logico-content-wrapper-2">
<div className="text-content">
<p>
Linking India&apos;s major regional capitals with
AI-powered air movement and zero-emission EV
last-mile delivery.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="owl-nav">
<button
type="button"
className="owl-next"
onClick={toggleSlide}
aria-label="Next"
style={{ cursor: "pointer", border: "none", outline: "none" }}
/>
<button
type="button"
className="owl-prev"
onClick={toggleSlide}
aria-label="Previous"
style={{ cursor: "pointer", border: "none", outline: "none" }}
/>
</div>
<div className="slider-footer slider-footer-position-after slider-footer-width-full slider-footer-view-inside">
<div className="slider-footer-content">
<div
className="slider-pagination"
style={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
gap: "10px",
}}
>
<div
className="slider-progress-wrapper"
style={{
marginRight: "35px",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
}}
>
<div
style={{
fontSize: "16px",
fontWeight: 600,
color: "#FFFFFF",
marginBottom: "4px",
}}
>
<span className="slider-progress-current">
{activeSlide === 0 ? "01" : "02"}
</span>
{" / "}
<span className="slider-progress-all" style={{ opacity: 0.6 }}>
02
</span>
</div>
<div
style={{
width: "80px",
height: "2px",
background: "rgba(255, 255, 255, 0.2)",
position: "relative",
borderRadius: "1px",
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
left: activeSlide === 0 ? "0" : "50%",
width: "50%",
height: "100%",
background: "#c01227",
transition: "left 0.3s ease",
}}
/>
</div>
</div>
<div className="owl-dots owl-dots-6c7cbcb" style={{ display: "none" }}>
<button
type="button"
role="button"
className={`owl-dot ${activeSlide === 0 ? "active" : ""}`}
onClick={() => handleDotClick(0)}
>
<span></span>
</button>
<button
type="button"
role="button"
className={`owl-dot ${activeSlide === 1 ? "active" : ""}`}
onClick={() => handleDotClick(1)}
>
<span></span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,312 @@
/* Premium editorial "How It Works" — bright warm-white surface, huge uppercase
display headline, top-right slide counter, and a five-column process row
divided by hairlines with oversized outlined numerals pinned to the top and
copy pinned to the bottom, echoing a tall, breathing editorial layout. */
.section {
background: linear-gradient(180deg, #fbfaf7 0%, #f5f2ec 100%);
padding: clamp(56px, 7vw, 96px) 0 clamp(80px, 8vw, 120px);
margin-bottom: 40px;
margin-right: 24px;
margin-left: 24px;
border-radius: 30px;
position: relative;
overflow: hidden;
}
.sectionShell {
width: 100%;
max-width: 1420px;
margin: 0 auto;
padding: 0 5vw;
box-sizing: border-box;
position: relative;
}
@media (max-width: 1024px) {
.sectionShell {
padding: 0 24px;
}
}
/* ---------- Header: eyebrow + giant display headline, counter top-right ---------- */
.headerRow {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 32px;
margin-bottom: clamp(48px, 6vw, 88px);
}
.header {
text-align: left;
max-width: 780px;
margin: 0;
}
.eyebrow {
font-size: 12px;
font-weight: 700;
letter-spacing: 1.5px;
text-transform: lowercase;
color: rgba(20, 18, 15, 0.5);
margin-bottom: 22px;
}
.title {
font-size: clamp(32px, 4.6vw, 68px) !important;
font-weight: 800 !important;
letter-spacing: -0.01em !important;
line-height: 1.02 !important;
text-transform: uppercase !important;
color: #17140f !important;
margin: 0;
}
.progress {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 10px;
margin-top: 4px;
}
.progressCount {
font-size: 13px;
font-weight: 700;
letter-spacing: 0.5px;
color: #17140f;
white-space: nowrap;
}
.progressTrack {
position: relative;
width: 110px;
height: 2px;
border-radius: 2px;
background: rgba(23, 20, 15, 0.12);
overflow: hidden;
}
.progressFill {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: #c8102e;
transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
/* ---------- Process row: hairline-divided columns, numeral up top, copy at the bottom ---------- */
.flow {
width: 100%;
display: flex;
align-items: stretch;
}
.step {
position: relative;
flex: 1 1 0;
min-width: 0;
min-height: clamp(200px, 18vw, 260px);
display: grid;
grid-template-rows: auto 1fr auto;
padding: 0 32px;
border-left: 1px solid rgba(23, 20, 15, 0.14);
cursor: pointer;
}
.step:first-child {
padding-left: 0;
border-left: none;
}
.step:last-child {
padding-right: 0;
}
.step:focus-visible {
outline: 2px solid rgba(200, 16, 46, 0.55);
outline-offset: 8px;
border-radius: 4px;
}
.stepTop {
display: flex;
align-items: flex-start;
gap: 8px;
}
.stepNum {
font-size: clamp(44px, 5.1vw, 92px);
font-weight: 800;
line-height: 0.85;
letter-spacing: -0.02em;
color: transparent;
-webkit-text-stroke: 1.5px rgba(23, 20, 15, 0.22);
transition: -webkit-text-stroke-color 0.4s ease, filter 0.4s ease;
}
.stepActive .stepNum {
-webkit-text-stroke-color: #c8102e;
filter: drop-shadow(0 0 14px rgba(200, 16, 46, 0.18));
}
.stepTag {
display: flex;
align-items: center;
gap: 6px;
margin-top: 6px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(23, 20, 15, 0.4);
transition: color 0.4s ease;
}
.stepIcon {
display: flex;
align-items: center;
justify-content: center;
color: inherit;
}
.stepActive .stepTag {
color: #c8102e;
}
.stepBottom {
align-self: end;
max-width: 280px;
}
.stepTitle {
font-size: 19px !important;
font-weight: 700 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #17140f !important;
margin: 0 0 10px 0 !important;
}
.stepDesc {
font-size: 13.5px;
line-height: 1.65;
color: rgba(23, 20, 15, 0.55);
margin: 0;
}
/* ---- Step 04 / Step 05 title-alignment fix ----
.stepBottom sits in .step's 1fr grid row with align-self:end, so its
title starts wherever the leftover space above it runs out — driven by
its OWN content height, not a shared position. Steps 01-03's
descriptions wrap to 3 lines, giving .stepBottom a natural height of
~101px; "Destination Hub" (04) and "EV Last-Mile" (05) wrap to only 2
lines (~79px) at several desktop widths, so under that same
end-alignment they're left with ~22px more leftover space above them
and their titles land visibly lower. Reserving that same ~101px on
.stepBottom for these two steps only — a floor, not a cap, so it's
inert wherever their content is already taller — reproduces Steps
01-03's exact title position without touching their CSS, the shared
grid, or align-self. */
@media (min-width: 1200px) {
.step:nth-child(4) .stepBottom,
.step:nth-child(5) .stepBottom {
min-height: 102px;
}
}
@media (max-width: 1024px) {
.headerRow {
flex-direction: column;
align-items: flex-start;
gap: 24px;
}
.progress {
align-items: flex-start;
}
.flow {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 40px 28px;
}
.step {
flex: none;
border-left: none;
padding: 0;
min-height: 0;
display: block;
}
.stepBottom {
max-width: none;
margin-top: 24px;
}
.step:nth-child(n + 3) {
border-top: 1px solid rgba(23, 20, 15, 0.14);
padding-top: 28px;
}
.flow > .step:last-child {
grid-column: 1 / -1;
}
}
@media (max-width: 600px) {
/* The clamp() on .section floors at a fixed 56px/80px on any phone width
(no further breakpoint-based step-down), noticeably heavier than the
rhythm every other Wings section settles into on mobile — normalized to
the same 44px/44px here.
.section's own margin-left/right also stays fixed at 24px below 1024px
(set at the top of this file, for the card's inset from the viewport
edge). Stacked with .sectionShell's padding that put the card's content
34px from the screen edge on phones — noticeably narrower than the
Footer's real 10px edge gutter (--dm-mobile-gutter, see globals.css's
.dm-section-container). Moving the full 10px gutter onto .section's
margin reproduces that same 10px screen-to-card edge, so the card sits
flush with its neighbors. .sectionShell then gets its own small 10px
breathing-room padding (independent of the outer gutter) so the eyebrow,
heading, progress indicator, and step content aren't flush against the
card's inner edge. */
.section {
padding-top: 44px;
padding-bottom: 44px;
margin-left: var(--dm-mobile-gutter, 10px);
margin-right: var(--dm-mobile-gutter, 10px);
}
.sectionShell {
padding-left: 10px;
padding-right: 10px;
}
.flow {
display: flex;
flex-direction: column;
}
.step {
padding: 28px 0;
}
.step:first-child {
padding-top: 0;
}
.step:not(:first-child) {
border-top: 1px solid rgba(23, 20, 15, 0.14);
}
.step:last-child {
padding-bottom: 0;
}
}

View File

@@ -0,0 +1,124 @@
"use client";
import { useState } from "react";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import styles from "./WingsHowItWorks.module.css";
const STEPS = [
{
label: "Local Pickup",
desc: "Fast, on-demand first-mile logistics dispatched by electric vehicle.",
icon: (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 13h13l3 4h2v3H3z" />
<circle cx="7" cy="20" r="1.6" />
<circle cx="16" cy="20" r="1.6" />
<path d="M6 13V8a1 1 0 0 1 1-1h5l3 6" />
</svg>
),
},
{
label: "Airport Clearance",
desc: "Automated screening, grouping, and rapid cross-docking at the hub.",
icon: (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z" />
<path d="m9 12 2 2 4-4" />
</svg>
),
},
{
label: "Air Transit",
desc: "Optimized cargo flights bypass surface congestion on our regional fleet.",
icon: (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 12h-6l-4-9-2 1 3 8H5l-2-3-1.5.6L3 14l1.5 4.4L6 18l-2-3h8l3 8 2-1-3-8h6z" />
</svg>
),
},
{
label: "Destination Hub",
desc: "Immediate de-consolidation and screening upon arrival.",
icon: (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<rect x="4" y="10" width="16" height="11" rx="1" />
<path d="M9 21v-6h6v6M4 10 12 3l8 7" />
</svg>
),
},
{
label: "EV Last-Mile",
desc: "Zero-emission electric vehicles complete the final delivery.",
icon: (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 13h13l3 4h2v3H3z" />
<circle cx="7" cy="20" r="1.6" />
<circle cx="16" cy="20" r="1.6" />
<path d="M6 13V8a1 1 0 0 1 1-1h5l3 6" />
</svg>
),
},
];
export default function WingsHowItWorks() {
const [active, setActive] = useState(0);
return (
<section className={styles.section} id="wings-how-it-works">
<div className={styles.sectionShell}>
<div className={styles.headerRow}>
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className={styles.header}>
<div className={styles.eyebrow}>/ How It Works /</div>
<h2 className={styles.title}>
Pickup to
<br />
delivery, one
<br />
connected flow
</h2>
</ScrollReveal>
<div className={styles.progress} aria-hidden="true">
<span className={styles.progressCount}>
{String(active + 1).padStart(2, "0")} / {String(STEPS.length).padStart(2, "0")}
</span>
<span className={styles.progressTrack}>
<span
className={styles.progressFill}
style={{ width: `${((active + 1) / STEPS.length) * 100}%` }}
/>
</span>
</div>
</div>
<StaggerChildren stagger={0.06} duration={0.6} yOffset={24} className={styles.flow}>
{STEPS.map((step, i) => (
<div
key={step.label}
className={`${styles.step} ${i === active ? styles.stepActive : ""}`}
onMouseEnter={() => setActive(i)}
onClick={() => setActive(i)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") setActive(i);
}}
>
<div className={styles.stepTop}>
<span className={styles.stepNum}>{String(i + 1).padStart(2, "0")}</span>
<span className={styles.stepTag}>
<span className={styles.stepIcon} aria-hidden="true">{step.icon}</span>
Step
</span>
</div>
<div className={styles.stepBottom}>
<h3 className={styles.stepTitle}>{step.label}</h3>
<p className={styles.stepDesc}>{step.desc}</p>
</div>
</div>
))}
</StaggerChildren>
</div>
</section>
);
}

View File

@@ -0,0 +1,238 @@
import { ScrollReveal } from "@/animations/Reveal";
const INDUSTRIES = [
{
title: "Quick Commerce",
challenge: "Sub-3-hour delivery windows for dark stores and micro-warehouses.",
solution: "Dedicated same-day air capacity linking metros to fulfillment hubs.",
image: "/images/card 1.png",
},
{
title: "Express B2B Cargo",
challenge: "Time-critical industrial parts and components stuck on slow trunk routes.",
solution: "Rapid airport-to-airport transport between major economic nodes.",
image: "/images/card 2.png",
},
{
title: "Healthcare & Pharma",
challenge: "Temperature-sensitive, compliance-critical shipments with zero margin for error.",
solution: "Active and passive cold-chain packaging with full chain-of-custody tracking.",
image: "/images/card 3.png",
},
{
title: "Cold Chain Logistics",
challenge: "Perishables and fresh goods that can't survive slow surface transit.",
solution: "Precision temperature tracking matched to airline-speed transit.",
image: "/images/card4.png",
},
{
title: "High-Value Cargo",
challenge: "Zero tolerance for loss, tampering, or delay on high-value shipments.",
solution: "Maximum-security handling with real-time electronic custody tracking.",
image: "/images/card5.png",
},
{
title: "E-Commerce Logistics",
challenge: "Next-day and same-day fulfillment expectations across tier-1 and tier-2 cities.",
solution: "Direct sorting-hub-to-airport clearance network for faster regional delivery.",
image: "/images/card6.png",
},
];
export default function WingsIndustries() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.wind-section {
background: #fafafa;
padding: var(--space-section-lg, 80px) 0 20px 0; /* Reduced bottom padding to minimize gap */
}
.wind-header {
margin: 0 auto 48px auto;
max-width: 720px;
text-align: center;
}
.wind-eyebrow {
font-size: 13px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.45);
margin-bottom: 16px;
}
.wind-title {
font-size: clamp(28px, 3.2vw, 46px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0;
}
.wind-inner {
max-width: 1380px;
margin: 0 auto;
}
.wind-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 20px;
}
.wind-card {
position: relative;
min-width: 0;
aspect-ratio: 4 / 5;
border-radius: 20px;
overflow: hidden;
background: linear-gradient(150deg, #17171b 0%, #0b0b0d 100%);
isolation: isolate;
}
.wind-card-wash {
position: absolute;
inset: 0;
background: linear-gradient(160deg, rgba(200, 16, 46, 0.35) 0%, rgba(200, 16, 46, 0) 60%);
opacity: 0.5;
transition: opacity 0.4s ease;
}
.wind-card:hover .wind-card-wash {
opacity: 1;
}
.wind-card-default {
position: absolute;
left: 24px;
right: 24px;
bottom: 24px;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.wind-card:hover .wind-card-default {
opacity: 0;
transform: translateY(8px);
pointer-events: none;
}
.wind-card .wind-card-default-title {
font-size: 20px !important;
font-weight: 800 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0;
}
.wind-card-hover {
position: absolute;
inset: 0;
padding: 26px 24px;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 14px;
opacity: 0;
transform: translateY(10px);
transition: opacity 0.35s ease, transform 0.35s ease;
}
.wind-card:hover .wind-card-hover {
opacity: 1;
transform: translateY(0);
}
.wind-card .wind-card-hover-title {
font-size: 19px !important;
font-weight: 800 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0;
}
.wind-card-label {
font-size: 10.5px;
font-weight: 800;
letter-spacing: 1.2px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.5);
margin: 0 0 4px 0;
}
.wind-card-text {
font-size: 13px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.85);
margin: 0;
}
@media (max-width: 1024px) {
.wind-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
.wind-grid { grid-template-columns: 1fr; }
.wind-card { aspect-ratio: 3 / 4; }
/* var(--space-section-lg) floors at a fixed 52px on any phone
width; bottom is already trimmed to 20px, so only the top needs
a real mobile reduction to match the section rhythm near the
footer. */
.wind-section { padding-top: 44px; }
}
`,
}}
/>
<section className="wind-section" id="wings-industries">
<div className="dm-section-container">
<div className="wind-inner">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wind-header">
<div className="wind-eyebrow">/ Industries We Serve /</div>
<h2 className="wind-title">Built for the shipments that can&rsquo;t wait</h2>
</ScrollReveal>
<ScrollReveal delay={0.1} duration={0.8} yOffset={30} className="wind-grid">
{INDUSTRIES.map((ind) => (
<div
key={ind.title}
className="wind-card"
style={ind.image ? {
backgroundImage: `linear-gradient(to bottom, rgba(0,0,0,0.2), rgba(0,0,0,0.8)), url('${ind.image}')`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : undefined}
>
<div className="wind-card-wash" aria-hidden="true" />
<div className="wind-card-default">
<h3 className="wind-card-default-title">{ind.title}</h3>
</div>
<div className="wind-card-hover">
<h3 className="wind-card-hover-title">{ind.title}</h3>
<div>
<p className="wind-card-label">Challenge</p>
<p className="wind-card-text">{ind.challenge}</p>
</div>
<div>
<p className="wind-card-label">Doormile Wings</p>
<p className="wind-card-text">{ind.solution}</p>
</div>
</div>
</div>
))}
</ScrollReveal>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,247 @@
.section {
background: #09090b;
padding: var(--space-section-lg, 100px) 0;
margin-bottom: 30px;
margin-right: 24px;
margin-left: 24px;
border-radius: 30px;
position: relative;
overflow: hidden;
}
/* Ambient depth: calm, near-static light (stable mood) instead of the more
energetic red glow used on How It Works, plus a matching vignette. */
.section::before {
content: "";
position: absolute;
top: -18%;
left: 50%;
width: 58%;
aspect-ratio: 1;
background: radial-gradient(circle, rgba(255, 255, 255, 0.05) 0%, rgba(200, 16, 46, 0.05) 40%, rgba(0, 0, 0, 0) 68%);
transform: translateX(-50%);
filter: blur(8px);
pointer-events: none;
z-index: 0;
animation: infraGlowDrift 14s ease-in-out infinite alternate;
}
.section::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(120% 90% at 50% 0%, transparent 50%, rgba(0, 0, 0, 0.5) 100%);
pointer-events: none;
z-index: 0;
}
@keyframes infraGlowDrift {
0% {
transform: translateX(-52%) translateY(0);
opacity: 0.7;
}
100% {
transform: translateX(-48%) translateY(10px);
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.section::before {
animation: none;
}
}
/*
* Single page-alignment wrapper. Numbers verified against the Footer's real
* rendered content container (elementor-element-e183a77) with Playwright at
* 320-2560px: that row sits flush at a 20px gutter up to a 1420px cap, then
* centers beyond it, dropping to a 10px gutter under ~1024px. max-width:1420
* + padding:20px reproduces the same edge as a 20px-gutter + inner
* max-width:1380px pair in one box (no nested max-width needed). Kept
* byte-identical to WingsHowItWorks.module.css's .sectionShell.
*/
.sectionShell {
width: 100%;
max-width: 1420px;
margin: 0 auto;
padding: 0 5vw;
box-sizing: border-box;
position: relative;
z-index: 1;
}
@media (max-width: 1024px) {
.sectionShell {
padding: 0 24px;
}
}
.header {
width: 100%;
max-width: 700px;
margin: 0 auto 56px auto;
text-align: center;
}
.eyebrow {
font-size: 13px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.45);
margin-bottom: 16px;
}
.title {
font-size: clamp(28px, 3.2vw, 46px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0;
}
/* Capability row — editorial columns divided by hairline rules, matching
How It Works' language but unnumbered (these aren't sequential steps). */
.grid {
width: 100%;
display: flex;
align-items: flex-start;
}
.card {
flex: 1 1 0;
min-width: 0;
padding: 0 30px;
border-left: 1px solid rgba(255, 255, 255, 0.09);
transition: border-color 0.4s ease;
}
.card:first-child {
padding-left: 0;
border-left: none;
}
.icon {
flex-shrink: 0;
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.14);
color: rgba(255, 255, 255, 0.55);
margin-bottom: 26px;
transition: border-color 0.4s ease, color 0.4s ease, background-color 0.4s ease, box-shadow 0.4s ease;
}
.card:hover .icon {
border-color: rgba(200, 16, 46, 0.55);
color: #ffffff;
background: rgba(200, 16, 46, 0.12);
box-shadow: 0 0 18px rgba(200, 16, 46, 0.28);
}
.cardTitle {
font-size: 17px !important;
font-weight: 700 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: rgba(255, 255, 255, 0.82) !important;
margin: 0 0 10px 0;
transition: color 0.4s ease;
}
.card:hover .cardTitle {
color: #ffffff !important;
}
.cardDesc {
font-size: 16px;
line-height: 1.6;
color: rgba(255, 255, 255, 0.45);
margin: 0;
transition: color 0.4s ease;
}
.card:hover .cardDesc {
color: rgba(255, 255, 255, 0.62);
}
@media (max-width: 1024px) {
.section {
padding: var(--space-section, 64px) 0;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 34px 28px;
}
.card {
border-left: none;
padding: 0;
}
.card:nth-child(n + 3) {
border-top: 1px solid rgba(255, 255, 255, 0.09);
padding-top: 28px;
}
}
@media (max-width: 600px) {
/* var(--space-section) floors at a fixed 40px on any phone width (no
further breakpoint-based step-down), and .sectionShell's horizontal
padding otherwise stays fixed at 24px below 1024px — both read as
inconsistent with the rhythm every other Wings section settles into on
mobile. Normalized to match: 44px/44px vertical, and the shared
--dm-mobile-gutter (10px) horizontal.
.section's own margin-left/right also stays fixed at 24px below 1024px
(set at the top of this file, for the card's inset from the viewport
edge). Stacked with .sectionShell's padding that put the card's content
34px from the screen edge on phones — noticeably narrower than the
Footer's real 10px edge gutter (--dm-mobile-gutter, see globals.css's
.dm-section-container). Moving the full 10px gutter onto .section's
margin reproduces that same 10px screen-to-card edge, so the card sits
flush with its neighbors. .sectionShell then gets its own small 10px
breathing-room padding (independent of the outer gutter) so the eyebrow,
heading, icons, card titles/descriptions, and dividers (which span
.card's full width) aren't flush against the card's inner edge. */
.section {
padding-top: 44px;
padding-bottom: 44px;
margin-left: var(--dm-mobile-gutter, 10px);
margin-right: var(--dm-mobile-gutter, 10px);
}
.sectionShell {
padding-left: 10px;
padding-right: 10px;
}
.grid {
display: flex;
flex-direction: column;
}
.card {
padding: 28px 0;
}
.card:first-child {
padding-top: 0;
}
.card:not(:first-child) {
border-top: 1px solid rgba(255, 255, 255, 0.09);
}
.card:last-child {
padding-bottom: 0;
}
}

View File

@@ -0,0 +1,67 @@
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import styles from "./WingsInfrastructure.module.css";
const CARDS = [
{
title: "Airport Cargo Hubs",
desc: "Near-airport clearance hubs instead of standalone warehouses — closer to the tarmac, faster to the aircraft.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z" />
</svg>
),
},
{
title: "Cross-Docking",
desc: "Screening, grouping, and consolidation happen in one motion, minimizing dwell time between arrival and departure.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 7h7l2 3h9M3 7v10h18V10" />
</svg>
),
},
{
title: "Asset-Light Network",
desc: "Leased terminal space that flexes with demand, keeping the network nimble as new routes come online.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="14" y="3" width="7" height="7" rx="1" />
<rect x="3" y="14" width="7" height="7" rx="1" />
<rect x="14" y="14" width="7" height="7" rx="1" />
</svg>
),
},
{
title: "Scalable Operations",
desc: "Modular ground infrastructure built to expand city by city without slowing down day-to-day operations.",
icon: (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 21V9l6-4 6 4v12M9 21V13h6v8" />
</svg>
),
},
];
export default function WingsInfrastructure() {
return (
<section className={styles.section} id="wings-infrastructure">
<div className={styles.sectionShell}>
<ScrollReveal delay={0.05} duration={0.7} yOffset={20} className={styles.header}>
<div className={styles.eyebrow}>/ Infrastructure /</div>
<h2 className={styles.title}>A network built to move, not to sit idle</h2>
</ScrollReveal>
<StaggerChildren stagger={0.08} duration={0.6} yOffset={20} className={styles.grid}>
{CARDS.map((c) => (
<div key={c.title} className={styles.card}>
<div className={styles.icon} aria-hidden="true">{c.icon}</div>
<h3 className={styles.cardTitle}>{c.title}</h3>
<p className={styles.cardDesc}>{c.desc}</p>
</div>
))}
</StaggerChildren>
</div>
</section>
);
}

View File

@@ -0,0 +1,507 @@
"use client";
import { useEffect, useRef } from "react";
import gsap from "gsap";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
const CAPABILITIES = [
{
title: "Demand Forecasting",
desc: "AI-driven forward projection of cargo demand, preemptively positioning aircraft and ground capacity.",
icon: (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 3v18h18" />
<path d="M7 14l4-4 3 3 6-7" />
</svg>
),
},
{
title: "Dynamic Pricing",
desc: "Real-time rate adjustments that track market capacity fluctuations and optimize yield.",
icon: (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M9.5 9.5c0-1.4 1.1-2.3 2.5-2.3s2.5.9 2.5 2c0 1.6-2.2 1.8-2.5 3.3M12 17h.01" />
</svg>
),
},
{
title: "Route Optimization",
desc: "Dynamic scheduling for ground EV pickups and flights that bypasses congestion for the fastest path.",
icon: (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="13 6 19 12 13 18" />
</svg>
),
},
{
title: "Load Balancing",
desc: "Automated weight-and-balance modeling for optimal, safe aircraft load layout within minutes.",
icon: (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 3v18M5 8l7-5 7 5M5 8v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8" />
</svg>
),
},
{
title: "Predictive Maintenance",
desc: "Continuous telemetry monitoring triggers preemptive alerts, drastically reducing aircraft downtime.",
icon: (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 12h-6l-4 8-4-16-4 8H2" />
</svg>
),
},
{
title: "Real-Time Tracking",
desc: "End-to-end sensor monitoring keeps shippers, airline, and EV operators updated on exact shipment state.",
icon: (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z" />
<circle cx="12" cy="12" r="3" />
</svg>
),
},
];
function TiltCard({ children, className }: { children: React.ReactNode, className: string }) {
const cardRef = useRef<HTMLDivElement>(null);
const glareRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const card = cardRef.current;
const glare = glareRef.current;
if (!card || !glare) return;
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
if (reduced) return;
const handleMouseMove = (e: MouseEvent) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
// Softer, more premium 3D tilt
const rotateX = ((y - centerY) / centerY) * -3;
const rotateY = ((x - centerX) / centerX) * 3;
gsap.to(card, {
duration: 0.4,
rotateX,
rotateY,
y: -4,
transformPerspective: 1000,
ease: "power2.out"
});
// Dynamic glare tracking
gsap.set(glare, {
"--mx": `${x}px`,
"--my": `${y}px`,
});
gsap.to(glare, {
duration: 0.3,
opacity: 1,
ease: "power2.out"
});
// Z-axis pop
gsap.to(card.querySelector('.wmt-card-icon'), {
duration: 0.4,
z: 25,
ease: "power2.out"
});
gsap.to(card.querySelector('.wmt-card-title'), {
duration: 0.4,
z: 15,
ease: "power2.out"
});
};
const handleMouseLeave = () => {
gsap.to(card, {
duration: 0.8,
rotateX: 0,
rotateY: 0,
y: 0,
ease: "power3.out"
});
gsap.to(glare, {
duration: 0.5,
opacity: 0,
ease: "power3.out"
});
gsap.to(card.querySelectorAll('.wmt-card-icon, .wmt-card-title'), {
duration: 0.8,
z: 0,
ease: "power3.out"
});
};
card.addEventListener("mousemove", handleMouseMove);
card.addEventListener("mouseleave", handleMouseLeave);
return () => {
card.removeEventListener("mousemove", handleMouseMove);
card.removeEventListener("mouseleave", handleMouseLeave);
};
}, []);
return (
<div ref={cardRef} className={className}>
<div
ref={glareRef}
style={{
position: "absolute",
inset: 0,
borderRadius: "inherit",
background: "radial-gradient(circle 220px at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, 0.08) 0%, rgba(200, 16, 46, 0.03) 40%, transparent 100%)",
opacity: 0,
pointerEvents: "none",
zIndex: 0,
transform: "translateZ(1px)",
mixBlendMode: "screen"
}}
/>
<div style={{ position: "relative", zIndex: 1, transformStyle: "preserve-3d" }}>
{children}
</div>
</div>
);
}
export default function WingsMileTruth() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
let width = (canvas.width = canvas.offsetWidth);
let height = (canvas.height = canvas.offsetHeight);
const handleResize = () => {
width = canvas.width = canvas.offsetWidth;
height = canvas.height = canvas.offsetHeight;
};
window.addEventListener("resize", handleResize);
const nodes = [
{ x: 0.12, y: 0.5 },
{ x: 0.34, y: 0.25 },
{ x: 0.56, y: 0.65 },
{ x: 0.78, y: 0.3 },
{ x: 0.92, y: 0.6 },
];
const particles = [
{ from: 0, to: 1, t: 0, speed: 0.006 },
{ from: 1, to: 2, t: 0.4, speed: 0.005 },
{ from: 2, to: 3, t: 0.15, speed: 0.007 },
{ from: 3, to: 4, t: 0.6, speed: 0.005 },
];
let raf: number;
const draw = () => {
ctx.clearRect(0, 0, width, height);
ctx.lineWidth = 1.5;
ctx.setLineDash([6, 6]);
ctx.strokeStyle = "rgba(200, 16, 46, 0.16)";
ctx.beginPath();
for (let i = 0; i < nodes.length - 1; i++) {
ctx.moveTo(nodes[i].x * width, nodes[i].y * height);
ctx.lineTo(nodes[i + 1].x * width, nodes[i + 1].y * height);
}
ctx.stroke();
ctx.setLineDash([]);
if (!reduced) {
particles.forEach((p) => {
p.t += p.speed;
if (p.t >= 1) p.t = 0;
const a = nodes[p.from];
const b = nodes[p.to];
const x = a.x * width + (b.x * width - a.x * width) * p.t;
const y = a.y * height + (b.y * height - a.y * height) * p.t;
ctx.beginPath();
ctx.arc(x, y, 3.5, 0, Math.PI * 2);
ctx.fillStyle = "#c8102e";
ctx.shadowColor = "#c8102e";
ctx.shadowBlur = 10;
ctx.fill();
ctx.shadowBlur = 0;
});
}
nodes.forEach((n) => {
const x = n.x * width;
const y = n.y * height;
ctx.beginPath();
ctx.arc(x, y, 7, 0, Math.PI * 2);
ctx.fillStyle = "rgba(200, 16, 46, 0.1)";
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, 3, 0, Math.PI * 2);
ctx.fillStyle = "rgba(255, 255, 255, 0.25)";
ctx.fill();
});
if (!reduced) raf = requestAnimationFrame(draw);
};
draw();
return () => {
window.removeEventListener("resize", handleResize);
if (raf) cancelAnimationFrame(raf);
};
}, []);
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.wmt-section {
position: relative;
width: calc(100% - 40px);
margin: clamp(24px, 4vw, 40px) auto;
border-radius: 28px;
overflow: hidden;
box-sizing: border-box;
background: #09090b;
padding: clamp(64px, 8vw, 100px) clamp(24px, 5vw, 48px);
}
.wmt-canvas {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.12;
}
.wmt-glow {
position: absolute;
width: 480px;
height: 480px;
border-radius: 50%;
pointer-events: none;
filter: blur(140px);
z-index: 0;
opacity: 0.06;
}
.wmt-glow.tl { left: -8%; top: -12%; background: radial-gradient(circle, #c8102e 0%, transparent 70%); }
.wmt-glow.br { right: -8%; bottom: -12%; background: radial-gradient(circle, #ff3b3b 0%, transparent 70%); }
.wmt-inner {
position: relative;
z-index: 1;
max-width: 1380px;
margin: 0 auto;
}
.wmt-header {
margin: 0 auto 48px auto;
max-width: 720px;
text-align: center;
}
.wmt-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.5px;
text-transform: uppercase;
color: #c8102e;
margin-bottom: 16px;
}
.wmt-title {
font-size: clamp(28px, 3.4vw, 48px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0;
}
.wmt-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
margin-bottom: 20px;
}
.wmt-card {
min-width: 0;
position: relative;
background: rgba(255, 255, 255, 0.035);
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 18px;
padding: 26px 22px;
transition: border-color 0.5s cubic-bezier(0.16, 1, 0.3, 1), background-color 0.5s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.5s cubic-bezier(0.16, 1, 0.3, 1);
transform-style: preserve-3d;
will-change: transform;
}
.wmt-card:hover {
border-color: rgba(255, 255, 255, 0.16);
background-color: rgba(255, 255, 255, 0.055);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35);
}
.wmt-card-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: rgba(200, 16, 46, 0.12);
color: #ffffff;
margin-bottom: 18px;
}
.wmt-card-title {
font-size: 15.5px !important;
font-weight: 700 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0 0 8px 0;
}
.wmt-card-desc {
font-size: 16px;
line-height: 1.55;
color: rgba(255, 255, 255, 0.55);
margin: 0;
}
.wmt-callout {
position: relative;
border-radius: 20px;
padding: 34px 32px;
background: linear-gradient(135deg, rgba(200, 16, 46, 0.95) 0%, rgba(110, 8, 26, 0.98) 100%);
border: 1px solid rgba(255, 255, 255, 0.15);
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
flex-wrap: wrap;
overflow: hidden;
}
.wmt-callout::after {
content: "";
position: absolute;
inset: -1.5px;
border-radius: 20px;
background: linear-gradient(90deg, #c8102e, #ff3366, #ff0033, #c8102e);
background-size: 300% 100%;
animation: wmt-sweep 6s linear infinite;
z-index: -1;
opacity: 0.6;
}
@keyframes wmt-sweep {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.wmt-callout-body { max-width: 640px; }
.wmt-callout-title {
font-size: 20px !important;
font-weight: 800 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0 0 8px 0;
}
.wmt-callout-desc {
font-size: 14px;
line-height: 1.55;
color: rgba(255, 255, 255, 0.85);
margin: 0;
}
.wmt-callout-badge {
flex-shrink: 0;
font-size: 11.5px;
font-weight: 800;
letter-spacing: 1px;
text-transform: uppercase;
color: #ffffff;
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 999px;
padding: 8px 16px;
}
@media (max-width: 1024px) {
.wmt-section { width: calc(100% - 24px); border-radius: 22px; }
.wmt-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
.wmt-grid { grid-template-columns: 1fr; }
.wmt-callout { flex-direction: column; align-items: flex-start; }
/* The vertical clamp() floors at a fixed 64px on any phone width;
trimmed to a real mobile value to match the section rhythm near
the footer. Horizontal padding is left untouched (its own
clamp already resolves small on phones). */
.wmt-section { padding-top: 48px; padding-bottom: 48px; }
}
`,
}}
/>
<section className="wmt-section" id="wings-miletruth">
<canvas ref={canvasRef} className="wmt-canvas" aria-hidden="true" />
<div className="wmt-glow tl" aria-hidden="true" />
<div className="wmt-glow br" aria-hidden="true" />
<div className="wmt-inner dm-section-container">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wmt-header">
<div className="wmt-eyebrow">/ MileTruth&trade; AI /</div>
<h2 className="wmt-title">The intelligence layer behind every delivery</h2>
</ScrollReveal>
<StaggerChildren stagger={0.08} duration={0.65} yOffset={25} className="wmt-grid">
{CAPABILITIES.map((c) => (
<TiltCard key={c.title} className="wmt-card">
<div className="wmt-card-icon" aria-hidden="true">{c.icon}</div>
<h3 className="wmt-card-title">{c.title}</h3>
<p className="wmt-card-desc">{c.desc}</p>
</TiltCard>
))}
</StaggerChildren>
<ScrollReveal delay={0.15} duration={0.8} yOffset={25}>
<div className="wmt-callout">
<div className="wmt-callout-body">
<h3 className="wmt-callout-title">Digital Twin of the Logistics Network</h3>
<p className="wmt-callout-desc">
A live virtual model of every ground vehicle, terminal, and flight simulating
what-if scenarios before bottlenecks ever form.
</p>
</div>
<span className="wmt-callout-badge">SaaS Ready</span>
</div>
</ScrollReveal>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,301 @@
"use client";
import { useState } from "react";
const ROADMAP_DATA = [
{
year: "Year 1",
trackLeft: "10%",
phase: "Launch Phase",
phaseClass: "yellow",
desc: "Establish localized clearance hubs and launch cargo services connecting 56 key commercial cities.",
},
{
year: "Year 2",
trackLeft: "30%",
phase: "Network Growth",
phaseClass: "green",
desc: "Expand hub operations and airfield schedules, serving 15 regional and secondary cargo airports.",
},
{
year: "Year 3",
trackLeft: "50%",
phase: "Cross-Border Nodes",
phaseClass: "blue",
desc: "Add international regional routes, bridging key freight corridors in South Asia.",
},
{
year: "Year 5",
trackLeft: "70%",
phase: "Operational Scale",
phaseClass: "blue",
desc: "Expand to a fleet of 25 leased cargo aircraft, servicing over 50 regional airports nationwide.",
},
{
year: "Year 10",
trackLeft: "90%",
phase: "Global Duplication",
phaseClass: "white-pill",
desc: "Replicate our asset-light, AI-powered logistics framework across Asia, Africa, and the Middle East.",
glow: true,
},
];
export default function WingsRoadmap() {
const [active, setActive] = useState(ROADMAP_DATA.length - 1);
const fillPct = ((active + 1) / ROADMAP_DATA.length) * 100;
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.wrm-section {
position: relative;
width: calc(100% - 40px);
margin: clamp(32px, 4vw, 56px) auto clamp(16px, 2vw, 32px);
border-radius: 28px;
overflow: hidden;
box-sizing: border-box;
background: #09090b;
padding: clamp(40px, 5vw, 64px) clamp(24px, 5vw, 48px);
}
.wrm-header {
margin: 0 auto 44px auto;
max-width: 720px;
text-align: center;
}
.wrm-eyebrow {
font-size: 13px;
font-weight: 800;
letter-spacing: 2.5px;
text-transform: uppercase;
color: #c8102e;
margin-bottom: 16px;
}
.wrm-title {
font-size: clamp(28px, 3.4vw, 48px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0;
}
.wrm-inner {
max-width: 1380px;
margin: 0 auto;
}
.wrm-track-wrap {
position: relative;
height: 4px;
background: rgba(255, 255, 255, 0.08);
border-radius: 2px;
margin: 0 auto 44px auto;
}
.wrm-track-fill {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: #c8102e;
box-shadow: 0 0 10px #c8102e;
border-radius: 2px;
transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.wrm-nodes { position: absolute; inset: 0; }
.wrm-node {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 12px;
height: 12px;
border-radius: 50%;
background: #18181b;
border: 2px solid rgba(255, 255, 255, 0.2);
transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease;
}
.wrm-node.is-active {
background: #c8102e;
border-color: #ffffff;
box-shadow: 0 0 10px #c8102e;
}
.wrm-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 16px;
}
.wrm-card {
position: relative;
min-width: 0;
padding: 24px 20px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.07);
background: rgba(255, 255, 255, 0.03);
cursor: pointer;
transition: border-color 0.4s ease, background-color 0.4s ease, transform 0.4s ease, box-shadow 0.4s ease;
}
.wrm-card:hover,
.wrm-card.is-active {
transform: translateY(-6px);
border-color: rgba(200, 16, 46, 0.5);
background: rgba(200, 16, 46, 0.06);
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.3);
}
.wrm-card-year {
font-size: 20px;
font-weight: 900;
color: #ffffff;
letter-spacing: -0.5px;
margin-bottom: 14px;
}
.wrm-phase-pill {
display: inline-block;
font-size: 10.5px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 4px 9px;
border-radius: 6px;
margin-bottom: 12px;
}
.wrm-phase-pill.yellow { background: rgba(234,179,8,0.12); color: #eab308; border: 1px solid rgba(234,179,8,0.2); }
.wrm-phase-pill.green { background: rgba(34,197,94,0.12); color: #22c55e; border: 1px solid rgba(34,197,94,0.2); }
.wrm-phase-pill.blue { background: rgba(59,130,246,0.12); color: #3b82f6; border: 1px solid rgba(59,130,246,0.2); }
.wrm-phase-pill.white-pill { background: rgba(255,255,255,0.12); color: #ffffff; border: 1px solid rgba(255,255,255,0.2); }
.wrm-card-desc {
font-size: 14.5px;
line-height: 1.55;
color: rgba(255, 255, 255, 0.55);
margin: 0;
}
.wrm-card.wrm-glow {
background: linear-gradient(135deg, rgba(200, 16, 46, 0.95) 0%, rgba(110, 8, 26, 0.98) 100%);
border-color: rgba(255, 255, 255, 0.15);
}
.wrm-card.wrm-glow .wrm-card-desc { color: rgba(255, 255, 255, 0.85); }
.wrm-card.wrm-glow:hover,
.wrm-card.wrm-glow.is-active {
box-shadow: 0 20px 40px rgba(200, 16, 46, 0.45);
}
@media (max-width: 1024px) {
.wrm-section { width: calc(100% - 24px); border-radius: 22px; }
.wrm-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
}
@media (max-width: 767px) {
/* Both the outer margin-top and the internal vertical padding
clamp()s floor at fixed, sizeable values (56px + 64px = ~120px
combined) on any phone width — noticeably heavier than the
section rhythm near the footer. Horizontal padding is left
untouched (its own clamp already resolves small on phones). */
.wrm-section {
margin-top: 24px;
padding-top: 32px;
padding-bottom: 32px;
}
.wrm-track-wrap { display: none; }
.wrm-grid {
grid-template-columns: 1fr;
gap: 14px;
position: relative;
padding-left: 26px;
}
.wrm-grid::before {
content: '';
position: absolute;
left: 5px;
top: 8px;
bottom: 8px;
width: 2px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.12);
}
.wrm-card { position: relative; }
.wrm-card::before {
content: '';
position: absolute;
left: -25px;
top: 22px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #18181b;
border: 2px solid rgba(255, 255, 255, 0.2);
}
.wrm-card.is-active::before,
.wrm-card:hover::before {
background: #c8102e;
border-color: #ffffff;
box-shadow: 0 0 10px rgba(200, 16, 46, 0.7);
}
}
`,
}}
/>
<section className="wrm-section" id="wings-roadmap">
<div className="dm-section-container">
<div className="wrm-header">
<div className="wrm-eyebrow">/ Future Roadmap /</div>
<h2 className="wrm-title">A ten-year path to national scale</h2>
</div>
<div className="wrm-inner">
<div className="wrm-track-wrap" aria-hidden="true">
<div className="wrm-track-fill" style={{ width: `${fillPct}%` }} />
<div className="wrm-nodes">
{ROADMAP_DATA.map((_, i) => (
<div
key={i}
className={`wrm-node${i <= active ? " is-active" : ""}`}
style={{ left: `${((i + 0.5) / ROADMAP_DATA.length) * 100}%` }}
/>
))}
</div>
</div>
<div className="wrm-grid">
{ROADMAP_DATA.map((item, i) => (
<div
key={item.year}
className={`wrm-card${item.glow ? " wrm-glow" : ""}${i === active ? " is-active" : ""}`}
onMouseEnter={() => setActive(i)}
onClick={() => setActive(i)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") setActive(i);
}}
>
<div className="wrm-card-year">{item.year}</div>
<span className={`wrm-phase-pill ${item.phaseClass}`}>{item.phase}</span>
<p className="wrm-card-desc">{item.desc}</p>
</div>
))}
</div>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,215 @@
"use client";
import { useEffect, useRef } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { CountUp } from "@/animations/Reveal";
if (typeof window !== "undefined") {
gsap.registerPlugin(ScrollTrigger);
}
export default function WingsSpeedAdvantage() {
const sectionRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLDivElement>(null);
const leftRef = useRef<HTMLDivElement>(null);
const rightRef = useRef<HTMLDivElement>(null);
const vsRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const section = sectionRef.current;
const header = headerRef.current;
const left = leftRef.current;
const right = rightRef.current;
const vs = vsRef.current;
if (!section || !header || !left || !right || !vs) return;
gsap.set([header, left, right], { opacity: 0, y: 40 });
gsap.set(vs, { opacity: 0, scale: 0.7 });
const tl = gsap.timeline({
scrollTrigger: { trigger: section, start: "top 78%", toggleActions: "play none none none" },
});
tl.to(header, { opacity: 1, y: 0, duration: 0.85, ease: "power3.out" })
.to([left, right], { opacity: 1, y: 0, duration: 0.8, stagger: 0.12, ease: "power3.out" }, "-=0.5")
.to(vs, { opacity: 1, scale: 1, duration: 0.5, ease: "back.out(1.7)" }, "-=0.35");
}, []);
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.wsa-section {
background: #fafafa;
padding: var(--space-section-lg, 80px) 0;
}
.wsa-header {
max-width: 720px;
margin: 0 auto 48px auto;
text-align: center;
}
.wsa-eyebrow {
font-size: 13px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.45);
margin-bottom: 16px;
}
.wsa-title {
font-size: clamp(28px, 3.2vw, 46px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0;
}
.wsa-inner {
max-width: 1380px;
margin: 0 auto;
}
.wsa-face-off {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 24px;
}
.wsa-panel {
min-width: 0;
border-radius: 22px;
padding: clamp(36px, 4vw, 56px) clamp(24px, 3vw, 40px);
text-align: center;
}
.wsa-panel-surface {
background: #ffffff;
border: 1px solid rgba(17, 17, 17, 0.08);
}
.wsa-panel-wings {
background: #111114;
border: 1px solid rgba(255, 255, 255, 0.06);
}
.wsa-panel-label {
font-size: 13px;
font-weight: 800;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 20px;
}
.wsa-panel-surface .wsa-panel-label { color: rgba(17, 17, 17, 0.4); }
.wsa-panel-wings .wsa-panel-label { color: #c8102e; }
.wsa-value {
font-size: clamp(40px, 6vw, 76px);
font-weight: 900;
letter-spacing: -0.03em;
line-height: 1;
}
.wsa-panel-surface .wsa-value { color: #c8102e; }
.wsa-panel-wings .wsa-value { color: #ffffff; }
.wsa-unit {
margin-top: 10px;
font-size: 15px;
font-weight: 700;
}
.wsa-panel-surface .wsa-unit { color: rgba(17, 17, 17, 0.45); }
.wsa-panel-wings .wsa-unit { color: rgba(255, 255, 255, 0.75); }
.wsa-panel-sub {
margin-top: 18px;
font-size: 14px;
line-height: 1.5;
}
.wsa-panel-surface .wsa-panel-sub { color: rgba(17, 17, 17, 0.4); }
.wsa-panel-wings .wsa-panel-sub { color: rgba(255, 255, 255, 0.55); }
.wsa-vs {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 56px;
height: 56px;
border-radius: 50%;
background: #ffffff;
border: 1px solid rgba(17, 17, 17, 0.1);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 800;
letter-spacing: 1px;
color: #c8102e;
z-index: 2;
}
@media (max-width: 767px) {
.wsa-face-off { grid-template-columns: 1fr; gap: 40px; }
/* Effective visible gap above/below the VS circle is (grid gap +
this margin), i.e. 40px - 28px = 12px on each side — an equal,
~40% reduction from the previous 20px/20px so the circle reads
as bridging the two cards instead of floating in a large empty
gap. margin: auto keeps it horizontally centered; the shorthand
applies identically to top and bottom so both stay equal. */
.wsa-vs { position: static; transform: none; margin: -28px auto; }
/* var(--space-section-lg) floors at a fixed 52px on any phone
width; trimmed to a real mobile value to match the section
rhythm near the footer. */
.wsa-section { padding-top: 44px; padding-bottom: 44px; }
}
`,
}}
/>
<section className="wsa-section" ref={sectionRef} id="wings-speed">
<div className="dm-section-container">
<div className="wsa-header" ref={headerRef}>
<div className="wsa-eyebrow">/ The Speed Advantage /</div>
<h2 className="wsa-title">Same-day, not some-day</h2>
</div>
<div className="wsa-inner">
<div className="wsa-face-off">
<div className="wsa-panel wsa-panel-surface" ref={leftRef}>
<div className="wsa-panel-label">Surface Logistics</div>
<div className="wsa-value">
48<CountUp end={96} duration={1.6} triggerOnce />
</div>
<div className="wsa-unit">Hours</div>
<p className="wsa-panel-sub">DelhiMumbai standard surface transit time.</p>
</div>
<div className="wsa-vs" ref={vsRef}>VS</div>
<div className="wsa-panel wsa-panel-wings" ref={rightRef}>
<div className="wsa-panel-label">Doormile Wings</div>
<div className="wsa-value">
Max <CountUp end={3} duration={1.4} triggerOnce />
</div>
<div className="wsa-unit">Hours, Door-to-Door</div>
<p className="wsa-panel-sub">Same-day delivery between major regional capitals.</p>
</div>
</div>
</div>
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,955 @@
"use client";
import { useEffect, useRef } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { MotionPathPlugin } from "gsap/MotionPathPlugin";
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
let hasDrawSVG = false;
if (typeof window !== "undefined") {
gsap.registerPlugin(ScrollTrigger, MotionPathPlugin);
try {
// DrawSVGPlugin ships free with gsap 3.13+. Registration (not the import
// itself) is guarded so the draw step can fall back to plain
// stroke-dasharray if it's ever unavailable, without touching timing.
gsap.registerPlugin(DrawSVGPlugin);
hasDrawSVG = true;
} catch {
hasDrawSVG = false;
}
}
const MOBILE_BQ = 600;
const AIRCRAFT_ROTATION_OFFSET = 90; // glyph's rest pose points "up"
const WAVE_AMPLITUDE_MIN = 20;
const WAVE_AMPLITUDE_MAX = 38;
// Row-wrapped grid layouts only (e.g. the 601-1024px 2-column tablet
// breakpoint) — single-row layouts (desktop, mobile) never use these.
const ROW_Y_TOLERANCE = 12; // px tolerance for clustering card anchors into the same row
const ROW_CLEARANCE_PADDING = 6; // safety margin between a crest and the row above's card bottom
// Exact max of buildWavePoints' ampFactor (1 + 0.28 + 0.15), used to clamp a
// row's wave amplitude so no crest can ever reach into the row above it,
// regardless of segment index/phase.
const MAX_WAVE_AMP_FACTOR = 1 + 0.28 + 0.15;
const FEATURES = [
{
title: "Same-Day Air Logistics",
desc: "Max 3-hour door-to-door delivery between India's major regional capitals.",
icon: (
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
),
},
{
title: "AI-Powered Operations",
desc: "MileTruth™ AI continuously forecasts demand and optimizes every route and load.",
icon: (
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<rect x="4" y="4" width="16" height="16" rx="3" />
<path d="M9 9h6v6H9z" />
<path d="M9 1v3M15 1v3M9 20v3M15 20v3M1 9h3M1 15h3M20 9h3M20 15h3" />
</svg>
),
},
{
title: "Regional Connectivity",
desc: "Dedicated air capacity linking major economic hubs, not passenger-belly space.",
icon: (
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<circle cx="6" cy="6" r="2.5" />
<circle cx="18" cy="6" r="2.5" />
<circle cx="12" cy="18" r="2.5" />
<path d="M8.2 7.2 10 16M15.8 7.2 14 16M8.5 6h7" />
</svg>
),
},
{
title: "EV Last Mile",
desc: "Zero-emission electric vehicles complete every first- and final-mile delivery.",
icon: (
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 13h13l3 4h2v3H3z" />
<circle cx="7" cy="20" r="1.6" />
<circle cx="16" cy="20" r="1.6" />
<path d="M6 13V8a1 1 0 0 1 1-1h5l3 6" />
</svg>
),
},
];
interface Point {
x: number;
y: number;
}
// A non-mobile card anchor, carrying its source card's full box (wrap-
// relative) alongside the {x,y} route point — top/bottom are needed to
// group anchors into rows and clamp a row's wave amplitude against the row
// above it; left/right are needed to find the empty column gutter (the
// icon's own x sits near the left edge of its card, not the card's true
// horizontal center, so card edges — not icon x — must drive that
// calculation). Never leaves measure(); only plain Point[] is returned.
interface CardAnchor extends Point {
top: number;
bottom: number;
left: number;
right: number;
}
function buildPathD(points: Point[]): string {
if (points.length < 2) return "";
let d = `M ${points[0].x} ${points[0].y}`;
for (let i = 0; i < points.length - 1; i++) {
const p0 = points[i];
const p1 = points[i + 1];
const dx = (p1.x - p0.x) / 2;
d += ` C ${p0.x + dx} ${p0.y}, ${p1.x - dx} ${p1.y}, ${p1.x} ${p1.y}`;
}
return d;
}
// Inserts one raised "crest" point between every consecutive pair of card
// anchors, turning the mostly-straight spline into an alternating
// trough-crest-trough wave: each card position stays a trough (so the pulse
// still arrives level with its icon, unchanged), while the gap between cards
// arcs upward. buildPathD's per-segment horizontal-tangent control points
// already keep every trough/crest transition smooth (no sharp bends) without
// needing a fancier spline — only the input point list changes.
//
// Each crest's height and horizontal position are nudged by a small,
// index-derived (not random) offset, so consecutive humps are never
// identical — reading as a real, slightly irregular air route rather than a
// generated sine wave — while staying perfectly stable across re-measures
// (resize, etc.) since the offsets depend only on segment index, not chance.
// Each offset sums two sine terms at unrelated frequencies/phases rather
// than one, so the sequence of crest heights doesn't itself read as a
// single obvious repeating rhythm.
function buildWavePoints(points: Point[], amplitude: number): Point[] {
if (points.length < 2 || amplitude <= 0) return points;
const waved: Point[] = [points[0]];
for (let i = 0; i < points.length - 1; i++) {
const p0 = points[i];
const p1 = points[i + 1];
const ampFactor =
1 + 0.28 * Math.sin(i * 2.9 + 0.4) + 0.15 * Math.sin(i * 5.3 + 2.1);
const xJitter =
(p1.x - p0.x) * (0.08 * Math.sin(i * 1.7 + 1.3) + 0.06 * Math.sin(i * 3.6 + 0.2));
waved.push({
x: (p0.x + p1.x) / 2 + xJitter,
y: Math.min(p0.y, p1.y) - amplitude * ampFactor,
});
waved.push(p1);
}
return waved;
}
// Clusters card anchors into visual rows by their shared top edge. CSS grid
// auto-flow is row-major and `align-items: stretch` (the grid default) makes
// same-row cards share cardRect.top exactly, so a single-pass walk against
// the current row's reference y (with a small tolerance for sub-pixel
// getBoundingClientRect noise) is sufficient — no need for full clustering.
function groupIntoRows(anchors: CardAnchor[]): CardAnchor[][] {
const rows: CardAnchor[][] = [];
for (const a of anchors) {
const row = rows[rows.length - 1];
if (row && Math.abs(row[0].top - a.top) <= ROW_Y_TOLERANCE) {
row.push(a);
} else {
rows.push([a]);
}
}
return rows;
}
// The x midpoint of the empty strip between two adjacent columns in a row —
// i.e. a real, always-card-free gutter, derived from actual card edges (not
// icon x, which sits near each card's left padding rather than its true
// horizontal center). `side` picks which internal gutter: "right" is
// between the two rightmost columns, "left" between the two leftmost — for
// the current 2-column layout these are the same single gutter, but this
// stays correct if a wider grid (3+ columns) ever wraps into multiple rows.
function computeColumnGutterX(row: CardAnchor[], side: "left" | "right"): number {
const sorted = [...row].sort((a, b) => a.left - b.left);
if (sorted.length < 2) return (sorted[0].left + sorted[0].right) / 2;
return side === "right"
? (sorted[sorted.length - 2].right + sorted[sorted.length - 1].left) / 2
: (sorted[0].right + sorted[1].left) / 2;
}
// Builds a safe serpentine (boustrophedon) route across a row-wrapped grid:
// each row is traversed in alternating direction so consecutive rows' ends
// line up on the same side, and the vertical drop between rows is routed
// through the always-empty column gutter on that side rather than through
// either column's own x — the only way to guarantee the connector never
// crosses a card, since a card anchor sits only ~10px below the card above
// it in the same column. Each row's own wave amplitude is clamped to the
// measured clearance above it so no crest can reach into the row above.
function buildMultiRowRoute(rows: CardAnchor[][], baseAmplitude: number): Point[] {
const out: Point[] = [];
let prevRowBottom: number | null = null;
rows.forEach((row, ri) => {
const ordered = ri % 2 === 0 ? row : [...row].reverse();
const rowAnchorY = ordered[0].y;
const amplitude =
prevRowBottom === null
? baseAmplitude
: Math.max(
0,
Math.min(baseAmplitude, (rowAnchorY - prevRowBottom - ROW_CLEARANCE_PADDING) / MAX_WAVE_AMP_FACTOR)
);
const waved = buildWavePoints(
ordered.map(({ x, y }) => ({ x, y })),
amplitude
);
if (out.length > 0) {
const prevPoint = out[out.length - 1];
const nextPoint = waved[0];
// Row (ri-1) ends at its right extreme when it traveled left-to-right
// (even index, unreversed), left extreme when it traveled
// right-to-left (odd index, reversed) — the gutter must sit on that
// same side so the dogleg never has to cross back over a card.
const side: "left" | "right" = (ri - 1) % 2 === 0 ? "right" : "left";
const gutterX = computeColumnGutterX(rows[ri - 1], side);
out.push({ x: gutterX, y: prevPoint.y }, { x: gutterX, y: nextPoint.y });
}
out.push(...waved);
prevRowBottom = Math.max(...row.map((a) => a.bottom));
});
return out;
}
export default function WingsWhyGrid() {
const wrapRef = useRef<HTMLDivElement>(null);
const svgRef = useRef<SVGSVGElement>(null);
const pathRef = useRef<SVGPathElement>(null);
// A second copy of the same "d", drawn brighter/thicker/blurred. A moving
// dasharray window (kept in sync with the pulse's own progress, see
// onUpdate below) makes a soft, feathered segment of the route glow right
// under the pulse and fade back to the resting line just behind it — the
// route being "energized" as the pulse passes, not just a static line.
const illumRef = useRef<SVGPathElement>(null);
// The traveler is the primary visual: a bright optical pulse riding the
// route. It's the outer group MotionPathPlugin translates. The aircraft is
// nested inside it as a small, secondary glyph — never its own focal point.
const travelerRef = useRef<SVGGElement>(null);
// Only this inner group rotates to face the direction of travel (the
// halo). Kept separate from travelerRef with an explicit 0,0 transform
// origin so rotation never pivots around a fill-box-derived point.
const directionalRef = useRef<SVGGElement>(null);
// The aircraft's own rotation wrapper, painted as a sibling *after* the
// core circle (see JSX) so its silhouette stays readable on top of the
// bright core instead of being buried under it. Mirrors directionalRef's
// rotation exactly (set alongside it in onUpdate) so both halves of the
// traveler always face the same way despite being separate DOM branches.
const aircraftDirectionalRef = useRef<SVGGElement>(null);
const cardRefs = useRef<(HTMLDivElement | null)[]>([]);
const iconRefs = useRef<(HTMLDivElement | null)[]>([]);
useEffect(() => {
const wrap = wrapRef.current;
const svg = svgRef.current;
const path = pathRef.current;
const illum = illumRef.current;
const traveler = travelerRef.current;
const directional = directionalRef.current;
const aircraftDirectional = aircraftDirectionalRef.current;
if (!wrap || !svg || !path || !illum || !traveler || !directional || !aircraftDirectional) return;
// Declared here (not inside the gsap.context callback below) so the
// cleanup function — which must run on every unmount, including ones
// gsap.context() itself doesn't know about, like these plain rAF/
// ResizeObserver handles — can always reach them.
let resizeRaf = 0;
let initialRaf = 0;
let rafIn = 0;
let ro: ResizeObserver | undefined;
let loadHandler: (() => void) | undefined;
// Also assigned (not re-declared) inside the gsap.context callback below,
// for the same reason: cleanup needs to reach them even though
// ctx.revert() already kills anything the context tracked, since the
// ScrollTrigger is created synchronously (tracked) but the flight
// timeline is only created later, asynchronously, once onEnter actually
// fires (not reliably tracked by the context in that case).
let st: ScrollTrigger | null = null;
let playedTimeline: gsap.core.Timeline | null = null;
// Everything GSAP-related (ScrollTrigger, timelines, gsap.set calls) is
// created inside this context so a single ctx.revert() on unmount kills
// all of it — this is what makes the whole setup safe to re-run on every
// mount (initial load, client-side route navigation, back/forward, hot
// reload) instead of only working after a hard refresh, where a prior
// mount's leftover ScrollTrigger/timeline could otherwise linger or hold
// a stale measurement.
const ctx = gsap.context(() => {
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let played = false;
const isMobileLayout = () => window.matchMedia(`(max-width: ${MOBILE_BQ}px)`).matches;
const measure = (): { points: Point[]; isMobile: boolean } => {
const wrapRect = wrap.getBoundingClientRect();
const isMobile = isMobileLayout();
// Card box (wrap-relative) is captured alongside x/y for every card,
// mobile included, so the shape is uniform — only the non-mobile
// branch below actually reads top/bottom/left/right (to group into
// rows, clamp wave amplitude against the row above, and find the
// empty column gutter for inter-row transitions).
const anchors = cardRefs.current
.map((card, i) => {
const icon = iconRefs.current[i];
if (!card || !icon) return null;
const cardRect = card.getBoundingClientRect();
const iconRect = icon.getBoundingClientRect();
const top = cardRect.top - wrapRect.top;
const bottom = cardRect.bottom - wrapRect.top;
const left = cardRect.left - wrapRect.left;
const right = cardRect.right - wrapRect.left;
if (isMobile) {
return {
x: 12,
y: iconRect.top + iconRect.height / 2 - wrapRect.top,
top,
bottom,
left,
right,
};
}
return {
x: iconRect.left + iconRect.width / 2 - wrapRect.left,
y: cardRect.top - wrapRect.top - 14,
top,
bottom,
left,
right,
};
})
.filter((p): p is CardAnchor => p !== null);
const points: Point[] = anchors.map(({ x, y }) => ({ x, y }));
svg.setAttribute("width", String(wrapRect.width));
svg.setAttribute("height", String(wrapRect.height));
svg.setAttribute("viewBox", `0 0 ${wrapRect.width} ${wrapRect.height}`);
// Wave only applies to the horizontal (non-mobile) layout — mobile
// keeps its existing straight vertical line unchanged. Amplitude
// scales with the section's width so it stays "gentle" (never a
// straight line, never overly aggressive) at any breakpoint.
const amplitude = isMobile
? 0
: Math.max(WAVE_AMPLITUDE_MIN, Math.min(WAVE_AMPLITUDE_MAX, wrapRect.width / 20));
let routePoints: Point[];
if (isMobile) {
routePoints = points;
} else {
const rows = groupIntoRows(anchors);
// A single row (desktop, always 4-up) takes the exact original
// path — no behavior change there. Only a row-wrapped layout (the
// tablet/small-laptop 2-column breakpoint) uses the row-aware
// serpentine route.
routePoints = rows.length <= 1 ? buildWavePoints(points, amplitude) : buildMultiRowRoute(rows, amplitude);
}
const d = buildPathD(routePoints);
path.setAttribute("d", d);
illum.setAttribute("d", d);
return { points, isMobile };
};
ro = new ResizeObserver(() => {
cancelAnimationFrame(resizeRaf);
resizeRaf = requestAnimationFrame(() => {
if (!reduced) {
buildSequence();
} else {
measure();
}
});
});
ro.observe(wrap);
// Initial synchronous measurement so `path` has real geometry before any
// gsap.set/timeline touches it (ResizeObserver's first callback is async).
// Followed by one deferred re-measure: StaggerChildren's own mount effect
// (a sibling/child effect) can still have cards translated by their
// pre-reveal yOffset at this exact tick, so the very first synchronous
// read can be transiently stale — a post-paint rAF corrects it.
measure();
initialRaf = requestAnimationFrame(() => {
measure();
// Recalculates every ScrollTrigger's start/end position against the
// now-settled layout. Without this, a trigger created a moment before
// web fonts/images finish shifting the page can end up with a stale
// "top 85%" position that a normal scroll never actually crosses —
// the classic reason a GSAP ScrollTrigger works after a hard refresh
// (cache-warm, layout already stable) but not after a fresh client-side
// navigation.
ScrollTrigger.refresh();
});
// Extra safety net: images loading in after this point can still shift
// layout. One more refresh once the whole page (including images) has
// finished loading catches that — a no-op if `load` already fired.
if (document.readyState !== "complete") {
loadHandler = () => ScrollTrigger.refresh();
window.addEventListener("load", loadHandler, { once: true });
}
const activateCard = (i: number) => {
const card = cardRefs.current[i];
const icon = iconRefs.current[i];
if (!card) return;
// Arrival: border gently strengthens, ambient glow grows, icon
// brightens, card lifts a touch (CSS-driven, eased over ~0.5s — see
// .wwg-card / .wwg-card-glow transition durations below — so it grows
// and settles rather than snapping on).
card.classList.add("wwg-card-flown", "wwg-card-pulse");
// Icon's own gentle scale pulse starts a beat after the arrival
// begins, so the sequence reads as "arrives, then responds" rather
// than everything happening at once.
if (icon) {
gsap.timeline({ delay: 0.28 })
.to(icon, { scale: 1.02, duration: 0.32, ease: "expo.out" })
.to(icon, { scale: 1, duration: 0.32, ease: "sine.inOut" });
}
// Hold the arrival state, then ease into the subtler, persistent
// "flown" resting state.
gsap.delayedCall(0.95, () => card.classList.remove("wwg-card-pulse"));
};
const buildSequence = () => {
if (playedTimeline) playedTimeline.kill();
const { points, isMobile } = measure();
if (points.length < 2) return;
const restOpacity = isMobile ? 0.15 : 0.18;
const total = path.getTotalLength();
// The illuminated window trailing the pulse: roughly a fifth of the
// route's length, so it reads as a genuine glowing trail rather than
// a small blip or the entire line at once.
const trailLen = total * 0.22;
const illumDashGap = total + 200;
illum.setAttribute("stroke-dasharray", `${trailLen} ${illumDashGap}`);
const fractions = points.map((pt) => {
let best = 0;
let bestDist = Infinity;
const step = Math.max(2, total / 400);
for (let l = 0; l <= total; l += step) {
const p = path.getPointAtLength(l);
const dist = (p.x - pt.x) ** 2 + (p.y - pt.y) ** 2;
if (dist < bestDist) {
bestDist = dist;
best = l;
}
}
return best / total;
});
const fired = fractions.map(() => false);
let currentAngle = 0;
let angleInit = false;
const tl = gsap.timeline({
scrollTrigger: {
trigger: wrap,
start: "top 75%",
end: "bottom 35%",
scrub: 1,
},
});
playedTimeline = tl;
// The energy pulse's own journey — calm and deliberate, ~4.8-5.5s so
// it glides rather than rushes from the first card to the last.
const FLIGHT_DURATION = 5.2;
// The route draws slightly faster than the pulse travels, both
// starting together at t=0 — so the drawn extent always leads the
// pulse's position (never the other way round) and the whole route
// is guaranteed fully illuminated well before the pulse's own journey
// ends, while both still read as one continuous, synchronized motion.
const DRAW_DURATION = FLIGHT_DURATION * 0.9;
if (hasDrawSVG) {
tl.to(path, { drawSVG: "100%", duration: DRAW_DURATION, ease: "power2.inOut" }, 0);
} else {
gsap.set(path, { strokeDasharray: total, strokeDashoffset: total });
tl.to(path, { strokeDashoffset: 0, duration: DRAW_DURATION, ease: "power2.inOut" }, 0);
}
// The base route ramps straight to its final resting opacity as it
// draws — never to an elevated "traveling" brightness — so there is
// only ever one illuminated layer on screen: the bright illum trail
// riding with the pulse (below). Without this, the whole drawn route
// would sit at a visibly elevated opacity for the entire flight,
// reading as a second, competing "lit" route behind the pulse.
tl.to(path, { opacity: restOpacity, duration: 0.9, ease: "sine.out" }, 0);
// GSAP computes its own rotation pivot for SVG elements from the
// target's bounding box (stamped as a data-svg-origin attribute) and
// ignores plain CSS transform-origin for that purpose — so without
// this, both directional groups were actually rotating around their
// bbox corner instead of (0,0), visibly orbiting off the path's
// centerline as they turned. svgOrigin is GSAP's own equivalent,
// stated in the element's literal SVG coordinates (not bbox-relative
// percentages), and only needs setting once — the per-frame
// rotation-only gsap.set calls below then reuse it automatically.
gsap.set([directional, aircraftDirectional], { svgOrigin: "0 0" });
// A soft fade-in rather than an instant pop, so the pulse's arrival
// reads as one continuous gesture from the very first frame.
tl.to(traveler, { opacity: 1, duration: 0.3, ease: "sine.out" }, 0);
tl.to(illum, { opacity: 1, duration: 0.4, ease: "sine.out" }, 0);
tl.to(traveler, {
// alignOrigin locks the traveler's own center (not a default
// corner/bbox reference) to the path, matching the explicit
// transform-origin: 50% 50% above — keeps the pulse/aircraft
// precisely on the route's centerline with no vertical drift.
motionPath: { path, autoRotate: false, alignOrigin: [0.5, 0.5] },
duration: FLIGHT_DURATION,
ease: "power2.inOut",
onUpdate: function () {
const p = this.progress();
const l = p * total;
const eps = Math.max(1, total * 0.01);
const a = path.getPointAtLength(Math.max(0, l - eps));
const b = path.getPointAtLength(Math.min(total, l + eps));
const target = (Math.atan2(b.y - a.y, b.x - a.x) * 180) / Math.PI + AIRCRAFT_ROTATION_OFFSET;
if (!angleInit) {
currentAngle = target;
angleInit = true;
} else {
let delta = target - currentAngle;
// shortest angular path so the lerp never spins the long way round
delta = ((delta + 180) % 360 + 360) % 360 - 180;
currentAngle += delta * 0.12;
}
// Only the two directional groups (trail + halo, and the aircraft)
// rotate — the core stays a perfectly round point of light on the
// path. Both are set to the same angle every frame so they always
// face the same way despite being separate DOM branches (see
// aircraftDirectionalRef for why they're split).
gsap.set(directional, { rotation: currentAngle });
gsap.set(aircraftDirectional, { rotation: currentAngle });
// Slide the illuminated window's trailing edge to l - trailLen and
// its leading edge to l (i.e. right under the pulse) by shifting
// the dash pattern's phase — the CSS blur on .wwg-illum-trail turns
// those two hard edges into a soft glow that fades into the resting
// route, rather than a sharp on/off cut.
illum.setAttribute("stroke-dashoffset", String(trailLen - l));
fractions.forEach((f, i) => {
if (!fired[i] && p >= f) {
fired[i] = true;
activateCard(i);
} else if (fired[i] && p < f) {
fired[i] = false;
// Reset card state if scrubbed backwards
cardRefs.current[i]?.classList.remove("wwg-card-flown", "wwg-card-pulse");
}
});
},
}, 0);
// Only once the pulse's own journey is complete does it fade out —
// the route itself is already at its resting opacity by this point
// (see above), so nothing further happens to it here.
tl.to(traveler, { opacity: 0, duration: 0.6, ease: "sine.out" }, FLIGHT_DURATION);
tl.to(illum, { opacity: 0, duration: 0.6, ease: "sine.out" }, FLIGHT_DURATION);
};
if (reduced) {
const { isMobile } = measure();
gsap.set(path, { opacity: isMobile ? 0.15 : 0.18 });
if (hasDrawSVG) {
gsap.set(path, { drawSVG: "100%" });
} else {
gsap.set(path, { strokeDasharray: "none", strokeDashoffset: 0 });
}
gsap.set(traveler, { opacity: 0 });
gsap.set(illum, { opacity: 0 });
} else {
gsap.set(path, { opacity: 0 });
if (hasDrawSVG) {
gsap.set(path, { drawSVG: "0%" });
}
gsap.set(traveler, { opacity: 0 });
gsap.set(illum, { opacity: 0 });
// Build the scrubbing sequence immediately
buildSequence();
}
}, wrap);
return () => {
ro?.disconnect();
cancelAnimationFrame(resizeRaf);
cancelAnimationFrame(initialRaf);
cancelAnimationFrame(rafIn);
if (loadHandler) window.removeEventListener("load", loadHandler);
st?.kill();
playedTimeline?.kill();
// Reverts/kills everything gsap.context tracked (ScrollTrigger.create,
// gsap.set calls, etc.) as a safety net beyond the explicit kills
// above, which exist specifically because the flight timeline is
// created asynchronously (inside onEnter) and so isn't guaranteed to
// be tracked by the context itself.
ctx.revert();
};
}, []);
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.wwg-section {
background: #ffffff;
padding: var(--space-section-lg, 80px) 0;
}
.wwg-header {
margin: 0 auto 48px auto;
max-width: 720px;
text-align: center;
}
.wwg-eyebrow {
font-size: 13px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(17, 17, 17, 0.45);
margin-bottom: 16px;
}
.wwg-title {
font-size: clamp(28px, 3.2vw, 46px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.15 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0;
}
.wwg-inner {
max-width: 1380px;
margin: 0 auto;
}
.wwg-grid-wrap {
position: relative;
overflow: visible;
}
.wwg-flightpath {
position: absolute;
inset: 0;
pointer-events: none;
overflow: visible;
z-index: 5;
}
.wwg-flightpath-line {
fill: none;
stroke: #c8102e;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
opacity: 0;
}
/* A brighter, blurred copy of the same route, painted *behind* the
crisp foreground line (see JSX order). Its dasharray is driven
per-frame (see onUpdate) to keep a soft window of it trailing
directly beneath the pulse; because it sits behind rather than on
top, its blur reads as a glow radiating from under the route
instead of visually thickening the route's own edges. Stroke
width intentionally matches the base route so it never peeks out
wider than the crisp line drawn on top of it. */
.wwg-illum-trail {
fill: none;
stroke: #ff3d5a;
stroke-width: 2;
stroke-linecap: round;
filter: blur(5px);
opacity: 0;
}
/* The traveler is the primary visual: a single centered optical
pulse riding the route, with a small secondary aircraft glyph
carried inside it. Position (translate) lives on .wwg-traveler;
only the inner .wwg-traveler-directional group rotates to face
the direction of travel — the round pulse core stays outside it
so it never rotates, always reading as a clean point of light. */
.wwg-traveler {
opacity: 0;
pointer-events: none;
will-change: transform;
/* Explicit center alignment (paired with alignOrigin: [0.5, 0.5]
on the motionPath tween) so the traveler's own center — not a
corner/default reference point — is what MotionPathPlugin locks
to the route, keeping it precisely on the path's centerline
with no vertical drift. */
transform-origin: 50% 50%;
}
.wwg-traveler-directional {
transform-origin: 0px 0px;
will-change: transform;
}
/* Large, radially-symmetric ambient glow — the true hero of the
traveler. Centered on the pulse with no directional offset, so
there is never a brighter patch leading ahead of the aircraft.
Non-directional, so it lives outside the rotating
.wwg-traveler-directional group. */
.wwg-pulse-bloom {
fill: #c8102e;
opacity: 0.2;
filter: blur(18px);
}
.wwg-pulse-halo {
fill: #c8102e;
opacity: 0.52;
filter: blur(7px);
}
.wwg-pulse-core {
fill: #ffffff;
filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.98)) drop-shadow(0 0 9px rgba(200, 16, 46, 0.85))
drop-shadow(0 0 15px rgba(200, 16, 46, 0.5));
}
/* Deliberately no drop-shadow of its own — the bloom/halo/trail
above supply all the glow. Sized to be clearly recognizable as an
aircraft without becoming the focal point: the pulse is still the
brightest, largest element on screen at every moment. */
.wwg-aircraft {
color: #c8102e;
opacity: 0.65;
}
.wwg-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 24px;
}
.wwg-card {
position: relative;
min-width: 0;
padding: 30px 24px;
border: 1px solid rgba(17, 17, 17, 0.08);
border-radius: 18px;
background: #fafafa;
/* Staggered delays so activation reads as a cascade — glow moves
first (see .wwg-card-glow below, no delay), then border/shape,
then the background tint settles last — rather than every
property snapping at once. The "expo-out" curve (rather than
plain ease) is what gives it a soft, premium settle instead of a
mechanical linear-ish snap. */
transition:
border-color 0.75s cubic-bezier(0.16, 1, 0.3, 1) 0.08s,
box-shadow 0.75s cubic-bezier(0.16, 1, 0.3, 1) 0.08s,
transform 0.75s cubic-bezier(0.16, 1, 0.3, 1) 0.08s,
background-color 0.75s cubic-bezier(0.16, 1, 0.3, 1) 0.22s;
}
.wwg-card:hover {
border-color: rgba(200, 16, 46, 0.35);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.06);
transform: translateY(-4px);
}
.wwg-card-glow {
position: absolute;
inset: -12px;
border-radius: 22px;
background: radial-gradient(circle, rgba(200, 16, 46, 0.45) 0%, rgba(200, 16, 46, 0) 70%);
opacity: 0;
z-index: -1;
pointer-events: none;
filter: blur(6px);
transition: opacity 0.75s cubic-bezier(0.16, 1, 0.3, 1);
}
.wwg-card-flown {
border-color: rgba(200, 16, 46, 0.25);
background-color: #fdf7f8;
}
.wwg-card-flown .wwg-card-glow {
opacity: 0.32;
}
.wwg-card-pulse {
border-color: rgba(200, 16, 46, 0.5);
box-shadow: 0 16px 34px rgba(0, 0, 0, 0.07);
transform: translateY(-3px);
}
.wwg-card-pulse .wwg-card-glow {
opacity: 0.85;
}
.wwg-icon {
width: 52px;
height: 52px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 14px;
background: rgba(200, 16, 46, 0.06);
color: #c8102e;
margin-bottom: 20px;
transition:
background-color 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0.16s,
filter 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0.16s;
}
.wwg-card-flown .wwg-icon {
background: rgba(200, 16, 46, 0.1);
}
.wwg-card-pulse .wwg-icon {
background: rgba(200, 16, 46, 0.14);
filter: brightness(1.12);
}
.wwg-card-title {
font-size: 17px !important;
font-weight: 700 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #111111 !important;
margin: 0 0 8px 0;
}
.wwg-card-desc {
font-size: 14.5px;
line-height: 1.55;
color: rgba(17, 17, 17, 0.6);
margin: 0;
}
@media (max-width: 1024px) {
.wwg-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
.wwg-grid { grid-template-columns: 1fr; padding-left: 28px; }
/* var(--space-section-lg) floors at a fixed 52px on any phone
width; trimmed to a real mobile value to match the section
rhythm near the footer. Matches this file's own MOBILE_BQ
breakpoint used by the flight-path animation logic. */
.wwg-section { padding-top: 44px; padding-bottom: 44px; }
}
@media (prefers-reduced-motion: reduce) {
.wwg-card, .wwg-card-glow, .wwg-icon {
transition: none;
}
}
`,
}}
/>
<section className="wwg-section" id="wings-why">
<div className="dm-section-container">
<div className="wwg-inner">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wwg-header">
<div className="wwg-eyebrow">/ Why Doormile Wings /</div>
<h2 className="wwg-title">Built for the speed of the Gen-Z economy</h2>
</ScrollReveal>
<div className="wwg-grid-wrap" ref={wrapRef}>
<svg ref={svgRef} className="wwg-flightpath" aria-hidden="true" focusable="false">
{/* Rendered first (behind) so its blur/extra width reads as a
soft glow sitting under the route rather than visually
thickening it — the crisp .wwg-flightpath-line painted
after it always defines the route's actual edges. */}
<path ref={illumRef} className="wwg-illum-trail" d="" />
<path ref={pathRef} className="wwg-flightpath-line" d="" />
<g ref={travelerRef} className="wwg-traveler">
{/* Large feathered bloom: the true hero of the traveler, an
ambient glow with no direction of its own. */}
<circle className="wwg-pulse-bloom" cx="0" cy="0" r="26" />
<g ref={directionalRef} className="wwg-traveler-directional">
<ellipse className="wwg-pulse-halo" cx="0" cy="0" rx="11" ry="6" />
</g>
<circle className="wwg-pulse-core" cx="0" cy="0" r="2.6" />
{/* Painted after (on top of) the core so the aircraft's
silhouette stays readable instead of being buried under
the bright core dot. Rotation mirrors directionalRef
exactly (see onUpdate) despite living in its own group. */}
<g ref={aircraftDirectionalRef} className="wwg-traveler-directional">
{/* The stock glyph's original 24x24 viewBox had ~3.5
units of dead padding on every side around the actual
ink (true silhouette bbox was only 17x20) — scaling
from the nominal 24-unit box would have kept baking
that padding in and rendering smaller than intended.
These coordinates instead start from the silhouette's
own tight bbox (no padding), centered on (0,0), then
scaled 1.3x — giving a true rendered size of ~22x26px,
clearing 22px on both axes. Deliberately no
scale()/translate() transform: baked directly into
the path data so it renders at full native
resolution rather than through a transform stacked on
top of this group's own rotation transform. */}
<path
className="wwg-aircraft"
d="M0,-13 C-0.78,-13 -1.3,-12.22 -1.3,-11.31 L-1.3,-3.9 L-11.05,1.3 L-11.05,3.9 L-1.3,0.91 L-1.3,9.1 L-4.55,11.44 L-4.55,13 L0,11.7 L4.55,13 L4.55,11.44 L1.3,9.1 L1.3,0.91 L11.05,3.9 L11.05,1.3 L1.3,-3.9 L1.3,-11.31 C1.3,-12.22 0.78,-13 0,-13 Z"
fill="currentColor"
/>
</g>
</g>
</svg>
<StaggerChildren stagger={0.1} duration={0.7} yOffset={30} className="wwg-grid">
{FEATURES.map((f, i) => (
<div
key={f.title}
className="wwg-card"
ref={(el) => {
cardRefs.current[i] = el;
}}
>
<span className="wwg-card-glow" aria-hidden="true" />
<div
className="wwg-icon"
aria-hidden="true"
ref={(el) => {
iconRefs.current[i] = el;
}}
>
{f.icon}
</div>
<h3 className="wwg-card-title">{f.title}</h3>
<p className="wwg-card-desc">{f.desc}</p>
</div>
))}
</StaggerChildren>
</div>
</div>
</div>
</section>
</>
);
}

View File

@@ -1,6 +1,6 @@
import React from "react";
import Image from "next/image";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import { CountUp, ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import IndustryWorldMap from "./IndustryWorldMap";
const WS_STATS = [
@@ -42,7 +42,7 @@ const WS_CARDS = [
},
{
title: "Community Growth",
desc: "Creating local opportunities through training, employment, and empowerment.",
desc: "Creating local opportunities through entrepreneurship, collaboration, and community-driven growth.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<circle cx="9" cy="8" r="3" />
@@ -52,7 +52,48 @@ const WS_CARDS = [
},
];
export default function WomenSection() {
interface WomenSectionStat {
value: string;
plus: string;
label: string;
}
interface WomenSectionCard {
title: string;
desc: string;
icon: React.ReactNode;
}
interface WomenSectionProps {
eyebrow?: string;
bannerLabel?: string;
bannerCopy?: string;
ctaLabel?: string;
ctaHref?: string;
heading?: string;
stats?: WomenSectionStat[];
cards?: WomenSectionCard[];
}
function parseStatValue(value: string) {
const match = value.trim().match(/^(\d+(?:\.\d+)?)(.*)$/);
if (!match) return null;
return {
end: Number(match[1]),
suffix: match[2],
};
}
export default function WomenSection({
eyebrow = "/ Women Empowerment /",
bannerLabel = "Empowering Women",
bannerCopy = "Doormile empowers women to lead the future of logistics.",
ctaLabel = "Explore more",
ctaHref = "#",
heading = "Women Leading the Way",
stats = WS_STATS,
cards = WS_CARDS,
}: WomenSectionProps = {}) {
return (
<>
<style dangerouslySetInnerHTML={{ __html: `
@@ -419,12 +460,12 @@ export default function WomenSection() {
</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">
<h5 className="logico-title">Empowering Women</h5>
<h5 className="logico-title">{bannerLabel}</h5>
</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>Doormile empowers women to lead the future of logistics.</p>
<p>{bannerCopy}</p>
</div>
</div>
</div>
@@ -433,8 +474,8 @@ export default function WomenSection() {
<div className="elementor-widget-container">
<div className="button-widget">
<div className="button-container">
<a href="#" className="logico-small-button">
Explore more
<a href={ctaHref} className="logico-small-button">
{ctaLabel}
<svg viewBox="0 0 13 20">
<polyline points="0.5 19.5 3 19.5 12.5 10 3 0.5"></polyline>
</svg>
@@ -455,7 +496,7 @@ export default function WomenSection() {
<div className="elementor-element elementor-element-90cc867 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="90cc867" data-element_type="container" data-e-type="container">
<div className="elementor-element elementor-element-24c0280 elementor-widget__width-inherit elementor-widget elementor-widget-logico_heading" data-id="24c0280" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default">
<div className="elementor-widget-container">
<div className="logico-title">/ Women Empowerment /</div>
<div className="logico-title">{eyebrow}</div>
</div>
</div>
@@ -464,7 +505,7 @@ export default function WomenSection() {
<ScrollReveal duration={0.8} yOffset={28} triggerOnce>
<div className="elementor-element elementor-element-778840d elementor-widget elementor-widget-logico_heading" data-id="778840d" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default">
<div className="elementor-widget-container">
<h3 className="logico-title">Women Leading the Way</h3>
<h3 className="logico-title">{heading}</h3>
</div>
</div>
</ScrollReveal>
@@ -484,16 +525,30 @@ export default function WomenSection() {
<div className="elementor-element elementor-element-b2c956f e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="b2c956f" data-element_type="container" data-e-type="container">
<div id="ws-stories">
<StaggerChildren className="ws__stats" stagger={0.1} duration={0.6} yOffset={20} triggerOnce>
{WS_STATS.map((s) => (
{stats.map((s) => (
<div className="ws__stat" key={s.label}>
<div className="ws__stat-num">{s.value}<span>{s.plus}</span></div>
<div className="ws__stat-num">
{(() => {
const parsed = parseStatValue(s.value);
if (!parsed) return s.value;
return (
<CountUp
end={parsed.end}
suffix={parsed.suffix}
duration={1.8}
triggerOnce
/>
);
})()}
<span>{s.plus}</span>
</div>
<div className="ws__stat-label">{s.label}</div>
</div>
))}
</StaggerChildren>
<StaggerChildren className="ws__cards" stagger={0.08} duration={0.6} yOffset={24} triggerOnce>
{WS_CARDS.map((c) => (
{cards.map((c) => (
<div className="ws__card" key={c.title}>
<span className="ws__card-icon" aria-hidden="true">{c.icon}</span>
<div className="ws__card-title">{c.title}</div>

View File

@@ -0,0 +1,123 @@
"use client";
import React from "react";
import EVSection, { EVStat, EVBadge, EVFeature, EVCardsTheme } from "./EVSection";
/* Violet / Electric Purple — distinct from WF1 (cyan/teal) and WF2 (red/crimson). */
const THEME: EVCardsTheme = {
accent: "#7C5CFF",
accent2: "#4C3AE3",
glow: "rgba(124,92,255,0.20)",
};
/**
* Workflow 4 — Quantum Intelligence (hybrid split-screen).
*
* • Left — video: the hybrid optimization / energy-landscape visual.
* • Right — a COMPACT DASHBOARD: a quick KPI row (QUBO / Global / Hybrid /
* Multi) over feature cards describing the encode → energy
* landscape → global minimum → hybrid intelligence pipeline,
* sitting on the animated network backdrop (violet).
*
* Messaging is deliberately framed as work-in-progress ("building",
* "exploring") rather than a shipped quantum-computing claim.
*/
const METRICS: EVStat[] = [
{ text: "QUBO", value: 0, suffix: "", label: "Problem Model" },
{ text: "GLOBAL", value: 0, suffix: "", label: "Minimum Search" },
{ text: "HYBRID", value: 0, suffix: "", label: "Intelligence" },
{ text: "MULTI", value: 0, suffix: "", label: "Objective" },
];
/* Distinct copy for the bottom capability bar (not a repeat of the KPI row —
* Workflow 4's brief specifies separate content for each). */
const BAR_STATS: EVStat[] = [
{ text: "Multi-Rider", value: 0, suffix: "", label: "Dynamic Routing" },
{ text: "Optimized", value: 0, suffix: "", label: "Time Windows" },
{ text: "Real-Time", value: 0, suffix: "", label: "Fleet Balancing" },
{ text: "Cost + Distance + SLA", value: 0, suffix: "", label: "Objectives" },
];
const ico = (path: React.ReactNode) => (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
{path}
</svg>
);
const FEATURES: EVFeature[] = [
{
icon: ico(
<>
<rect x="3" y="3" width="7" height="7" />
<rect x="14" y="3" width="7" height="7" />
<rect x="14" y="14" width="7" height="7" />
<rect x="3" y="14" width="7" height="7" />
</>,
),
title: "Encode the Problem",
desc: "Rider assignments, delivery sequences, capacity, traffic, service time, and SLA constraints become part of one mathematical optimization model.",
},
{
icon: ico(<path d="m8 3 4 8 5-5 5 15H2L8 3z" />),
title: "Build the Energy Landscape",
desc: "Each feasible routing solution is represented as a state with its own energy through a QUBO formulation.",
},
{
icon: ico(
<>
<circle cx="12" cy="12" r="9" />
<circle cx="12" cy="12" r="5" />
<circle cx="12" cy="12" r="1" />
</>,
),
title: "Search the Global Minimum",
desc: "Optimization becomes the search for the lowest-energy state instead of evaluating route combinations one by one.",
},
{
icon: ico(
<>
<polygon points="12 2 2 7 12 12 22 7 12 2" />
<polyline points="2 17 12 22 22 17" />
<polyline points="2 12 12 17 22 12" />
</>,
),
title: "Hybrid Intelligence",
desc: "Classical algorithms, AI models, and quantum optimization work together using the right computational model for each problem.",
},
];
const BADGES: EVBadge[] = [
{ value: "QUBO", label: "PROBLEM MODEL" },
{ value: "HYBRID", label: "INTELLIGENCE" },
];
export default function Workflow4() {
return (
<EVSection
ariaLabel="Workflow 4 — Quantum Intelligence"
gapBottom
image="/videos/Doormile-quantum-video3.mp4"
cardTitle="BUILDING QUANTUM INTELLIGENCE"
cardSubtitle="Exploring a hybrid optimization layer that treats routing as an energy landscape, not a search problem."
eyebrow="/ Quantum Intelligence /"
titleLead="FROM ROUTE SEARCH. "
titleAccent="TO ENERGY MINIMIZATION."
imageAlt="Hybrid quantum-classical optimization engine visualizing an energy landscape"
metrics={METRICS}
barStats={BAR_STATS}
features={FEATURES}
cardsHeading="Quantum Intelligence"
sysLabel="HYBRID ENGINE"
cardsTheme={THEME}
badges={BADGES}
mediaClassName="evnd__imgwrap--wf4"
/>
);
}

View File

@@ -24,6 +24,9 @@ const ROUTE_CLASSES: Record<string, string> = {
"/solutions": `${SHARED} page-id-59 elementor-page-59`,
// PHP source quirk: about-us.php has `home` class (upstream bug, preserved)
"/about-us": `home ${SHARED} page-id-59 elementor-page-59`,
// Reuse the About page Elementor/runtime context for the Wings Empowerment
// page sections that intentionally share the same migrated Elementor markup.
"/empowerment": `home ${SHARED} page-id-59 elementor-page-59`,
"/blog": `${SHARED} page-id-59 elementor-page-59`,
"/contact": `${SHARED} page-id-59 elementor-page-59`,
};