9 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
46 changed files with 4663 additions and 778 deletions

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

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 /> <AboutHero />
<TheDoormileWay /> <TheDoormileWay />
<EVLogisticSection /> <EVLogisticSection />
<WomenSection /> {/* <WomenSection /> */}
<CompetitiveEdge /> <CompetitiveEdge />
<IntelligenceGrid /> <IntelligenceGrid />
<OurTeam /> <OurTeam />

View File

@@ -8,7 +8,6 @@ import WingsFleetStrategy from "@/components/sections/WingsFleetStrategy";
import WingsInfrastructure from "@/components/sections/WingsInfrastructure"; import WingsInfrastructure from "@/components/sections/WingsInfrastructure";
import WingsMileTruth from "@/components/sections/WingsMileTruth"; import WingsMileTruth from "@/components/sections/WingsMileTruth";
import WingsRoadmap from "@/components/sections/WingsRoadmap"; import WingsRoadmap from "@/components/sections/WingsRoadmap";
import WingsFinalCTA from "@/components/sections/WingsFinalCTA";
export const metadata = { export const metadata = {
title: "Doormile Wings Redefining Domestic Air Logistics Through AI", title: "Doormile Wings Redefining Domestic Air Logistics Through AI",
@@ -55,7 +54,6 @@ export default function DoormileWingsPage() {
<WingsInfrastructure /> <WingsInfrastructure />
<WingsMileTruth /> <WingsMileTruth />
<WingsRoadmap /> <WingsRoadmap />
<WingsFinalCTA />
</div> </div>
</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>
);
}

View File

@@ -10,6 +10,27 @@ html {
scroll-behavior: smooth; 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 GLOBAL SPACING SYSTEM
A single shared vertical-rhythm scale so every section uses A single shared vertical-rhythm scale so every section uses
@@ -153,16 +174,21 @@ body {
#masthead .elementor-element.elementor-element-0b7bf6f > .elementor-widget-container { #masthead .elementor-element.elementor-element-0b7bf6f > .elementor-widget-container {
padding: 0 42px !important; padding: 0 42px !important;
border-radius: 22px !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 { #masthead .elementor-element.elementor-element-0b7bf6f .header-menu-container .main-menu > li > a {
padding-top: 15px !important; padding-top: 6px !important;
padding-bottom: 16px !important; padding-bottom: 6px !important;
padding-left: 14px !important; padding-left: 14px !important;
padding-right: 14px !important; padding-right: 14px !important;
font-size: 15px !important; font-size: 15px !important;
line-height: 1.2 !important; line-height: 1.2 !important;
white-space: nowrap !important; white-space: nowrap !important;
min-height: auto !important;
height: auto !important;
} }
#masthead .header-menu-container .main-menu { #masthead .header-menu-container .main-menu {
@@ -179,7 +205,11 @@ body {
} }
#masthead .elementor-element.elementor-element-466de1b.dm-header-scrolled { #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;
} }
} }
@@ -966,18 +996,30 @@ body {
/* ============================================================ /* ============================================================
SHARED PAGE CONTENT CONTAINER SHARED PAGE CONTENT CONTAINER
Mirrors the site's e-con-boxed > e-con-inner system: 1480px Matches the Hero/Header/Footer's real rendered OUTER edge, not the
max-width centred with fluid horizontal padding identical to the Footer's narrower inner text column. The Hero card
Industry Solutions / OurTeam override above. Apply this class to (.owl-stage-outer, via .wings-hero-page.elementor-element-741f56c)
any section's inner container div to guarantee that content edges and the Footer's outer band (.elementor-element-b29b8fc) both sit a
line up with every other page section (Home, About, Solutions…). 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 { .dm-section-container {
max-width: 1480px; width: 100%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
box-sizing: border-box; box-sizing: border-box;
width: 100%; 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 /* Doormile Wings hero frame: the generic vendor CSS assigns 32px padding to
@@ -1197,3 +1239,4 @@ body {
margin-top: var(--space-section) !important; margin-top: var(--space-section) !important;
} }
} }

View File

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

View File

@@ -20,6 +20,7 @@ const CURRENT_PAGE_MAP: Record<string, string> = {
"/miletruth": "miletruth", "/miletruth": "miletruth",
"/solutions": "solutions", "/solutions": "solutions",
"/doormile-wings": "doormile-wings", "/doormile-wings": "doormile-wings",
"/empowerment": "entarpreneurship",
"/express": "express", "/express": "express",
"/about-us": "about", "/about-us": "about",
"/blog": "blogs", "/blog": "blogs",
@@ -30,11 +31,13 @@ const CURRENT_PAGE_ALIASES: Record<string, string[]> = {
home: ["home"], home: ["home"],
"how-it-works": ["how-it-works", "how-its-works"], "how-it-works": ["how-it-works", "how-its-works"],
miletruth: ["miletruth"], miletruth: ["miletruth"],
// Solutions is a normal nav page and also owns the Wings/Express dropdown. // Solutions is now a plain top-level link — no child pages own its active state.
solutions: ["solutions", "doormile-wings", "express"], solutions: ["solutions"],
"doormile-wings": ["doormile-wings"], "doormile-wings": ["doormile-wings"],
about: ["about", "about-us", "women"], entarpreneurship: ["entarpreneurship"],
blogs: ["blogs"], 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() { export default function Header() {
@@ -82,20 +85,20 @@ export default function Header() {
const [visibleScrolled, setVisibleScrolled] = useState(false); const [visibleScrolled, setVisibleScrolled] = useState(false);
const [isScrolled, setIsScrolled] = useState(false); const [isScrolled, setIsScrolled] = useState(false);
// Solutions dropdown (desktop) / accordion (mobile). Local to Header — // Company dropdown (desktop) / accordion (mobile). Local to Header —
// unlike isMenuOpen/isSidebarOpen (HeaderUIProvider), no sibling component // unlike isMenuOpen/isSidebarOpen (HeaderUIProvider), no sibling component
// needs to read this. // needs to read this.
const [isDesktopSolutionsOpen, setDesktopSolutionsOpen] = useState(false); const [isDesktopCompanyOpen, setDesktopCompanyOpen] = useState(false);
const [isMobileSolutionsOpen, setMobileSolutionsOpen] = useState(false); const [isMobileCompanyOpen, setMobileCompanyOpen] = useState(false);
// Collapse the accordion whenever the off-canvas menu closes, so it isn't // Collapse the accordion whenever the off-canvas menu closes, so it isn't
// left expanded the next time the menu opens. // left expanded the next time the menu opens.
const closeMobileMenu = () => { const closeMobileMenu = () => {
setMobileSolutionsOpen(false); setMobileCompanyOpen(false);
closeAll(); closeAll();
}; };
const toggleMobileMenu = () => { const toggleMobileMenu = () => {
setMobileSolutionsOpen(false); setMobileCompanyOpen(false);
toggleMenu(); toggleMenu();
}; };
@@ -457,57 +460,82 @@ 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")}`}> <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> <Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home")}>Home</Link>
</li> </li>
<li id="menu-item-10509" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}> <li id="menu-item-10509" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("doormile-wings")}`}>
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works")}>How It Works</Link> <Link href="/doormile-wings" prefetch>Wings</Link>
</li> </li>
<li id="menu-item-10510" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10510${dmHeaderActive("miletruth")}`}> <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> <Link href="/miletruth#miletruth" onClick={(event) => handleNavClick(event, "/miletruth", "miletruth")}>MileTruth&trade; AI</Link>
</li> </li>
<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-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 <li
id="menu-item-10511" id="menu-item-10535"
className={`menu-item menu-item-type-custom menu-item-has-children menu-item-10511${dmHeaderActive("solutions")}`} className={`menu-item menu-item-type-custom menu-item-has-children menu-item-10535${dmHeaderActive("company")}`}
onMouseEnter={() => setDesktopSolutionsOpen(true)} onMouseEnter={() => setDesktopCompanyOpen(true)}
onMouseLeave={() => setDesktopSolutionsOpen(false)} onMouseLeave={() => setDesktopCompanyOpen(false)}
onKeyDown={(event: KeyboardEvent<HTMLLIElement>) => { onKeyDown={(event: KeyboardEvent<HTMLLIElement>) => {
if (event.key !== "Escape") return; if (event.key !== "Escape") return;
setDesktopSolutionsOpen(false); setDesktopCompanyOpen(false);
event.currentTarget.querySelector<HTMLAnchorElement>(":scope > a")?.focus(); event.currentTarget.querySelector<HTMLAnchorElement>(":scope > a")?.focus();
}} }}
onBlur={(event: FocusEvent<HTMLLIElement>) => { onBlur={(event: FocusEvent<HTMLLIElement>) => {
if (!event.currentTarget.contains(event.relatedTarget as Node)) { if (!event.currentTarget.contains(event.relatedTarget as Node)) {
setDesktopSolutionsOpen(false); setDesktopCompanyOpen(false);
} }
}} }}
> >
<Link <a
href="/solutions" href="#"
prefetch
aria-haspopup="true" aria-haspopup="true"
aria-expanded={isDesktopSolutionsOpen} aria-expanded={isDesktopCompanyOpen}
onFocus={() => setDesktopSolutionsOpen(true)} onFocus={() => setDesktopCompanyOpen(true)}
onClick={() => setDesktopSolutionsOpen(false)} onClick={(event) => {
event.preventDefault();
setDesktopCompanyOpen((open) => !open);
}}
> >
Solutions Company
</Link> </a>
<ul className="dm-solutions-dropdown"> <ul className="dm-solutions-dropdown">
<li className={currentPage === "doormile-wings" ? "dm-child-active" : ""}> <li className={currentPage === "about" ? "dm-child-active" : ""}>
<Link href="/doormile-wings" prefetch onClick={() => setDesktopSolutionsOpen(false)}> <Link
Doormile Wings href="/about-us#about"
onClick={(event) => {
setDesktopCompanyOpen(false);
handleNavClick(event, "/about-us", "about");
}}
>
About
</Link> </Link>
</li> </li>
<li className={currentPage === "express" ? "dm-child-active" : ""}> <li className={currentPage === "blogs" ? "dm-child-active" : ""}>
<Link href="/express" prefetch onClick={() => setDesktopSolutionsOpen(false)}> <Link
Express 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> </Link>
</li> </li>
</ul> </ul>
</li> </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>
<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>
</ul> </ul>
</nav> </nav>
</div> </div>
@@ -541,45 +569,54 @@ export default function Header() {
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10508${dmHeaderActive("home")}`}> <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> <Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home", true)}>Home</Link>
</li> </li>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}> <li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("doormile-wings")}`}>
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works", true)}>How It Works</Link> <Link href="/doormile-wings" prefetch onClick={closeMobileMenu}>Wings</Link>
</li> </li>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10510${dmHeaderActive("miletruth")}`}> <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> <Link href="/miletruth#miletruth" onClick={(event) => handleNavClick(event, "/miletruth", "miletruth", true)}>MileTruth&trade; AI</Link>
</li> </li>
<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-10513${dmHeaderActive("entarpreneurship")}`}>
<Link href="/empowerment" prefetch onClick={closeMobileMenu}>Entrepreneurship</Link>
</li>
<li <li
className={`menu-item menu-item-type-custom menu-item-10511 dm-mobile-solutions${dmHeaderActive("solutions")}${isMobileSolutionsOpen ? " dm-accordion-open" : ""}`} 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"> <div className="dm-mobile-solutions-row">
<Link href="/solutions" prefetch onClick={closeMobileMenu}> <a
Solutions href="#"
</Link> onClick={(event) => {
event.preventDefault();
setMobileCompanyOpen((open) => !open);
}}
>
Company
</a>
<button <button
type="button" type="button"
className="dm-mobile-solutions-toggle" className="dm-mobile-solutions-toggle"
aria-label={isMobileSolutionsOpen ? "Close Solutions menu" : "Open Solutions menu"} aria-label={isMobileCompanyOpen ? "Close Company menu" : "Open Company menu"}
aria-haspopup="true" aria-haspopup="true"
aria-expanded={isMobileSolutionsOpen} aria-expanded={isMobileCompanyOpen}
onClick={() => setMobileSolutionsOpen((open) => !open)} onClick={() => setMobileCompanyOpen((open) => !open)}
> >
<span className="dm-accordion-chevron" aria-hidden="true"></span> <span className="dm-accordion-chevron" aria-hidden="true"></span>
</button> </button>
</div> </div>
<ul className="dm-mobile-solutions-panel"> <ul className="dm-mobile-solutions-panel">
<li className={currentPage === "doormile-wings" ? "dm-child-active" : ""}> <li className={currentPage === "about" ? "dm-child-active" : ""}>
<Link href="/doormile-wings" prefetch onClick={closeMobileMenu}>Doormile Wings</Link> <Link href="/about-us#about" onClick={(event) => handleNavClick(event, "/about-us", "about", true)}>About</Link>
</li> </li>
<li className={currentPage === "express" ? "dm-child-active" : ""}> <li className={currentPage === "blogs" ? "dm-child-active" : ""}>
<Link href="/express" prefetch onClick={closeMobileMenu}>Express</Link> <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> </li>
</ul> </ul>
</li> </li>
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-10512${dmHeaderActive("about")}`}>
<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")}`}>
<Link href="/blog#blogs" onClick={(event) => handleNavClick(event, "/blog", "blogs", true)}>Blogs</Link>
</li>
</ul> </ul>
</nav> </nav>
<div className="header-mobile-contacts"> <div className="header-mobile-contacts">
@@ -839,22 +876,6 @@ export default function Header() {
} }
} }
/* Compact nav items at middle desktop widths (8 items fit without overflow).
Slightly tighter letter-spacing keeps the active underline animation intact. */
@media (min-width: 1025px) and (max-width: 1400px) {
#masthead .header-menu-container .main-menu > li > a {
letter-spacing: -0.01em;
}
}
@media (min-width: 1025px) and (max-width: 1200px) {
#masthead .header-menu-container .main-menu {
gap: clamp(4px, 0.8vw, 12px) !important;
}
#masthead .header-menu-container .main-menu > li > a {
font-size: clamp(12px, 0.95vw, 14px);
}
}
#masthead .header-menu-container .main-menu > li.active > a:before { #masthead .header-menu-container .main-menu > li.active > a:before {
background-color: #ffffff !important; background-color: #ffffff !important;
opacity: 1 !important; opacity: 1 !important;
@@ -953,12 +974,15 @@ export default function Header() {
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");
} }
/* ── Solutions dropdown (desktop) / accordion (mobile) ── /* ── Company dropdown (desktop) / accordion (mobile) ──
Solutions remains linked to /solutions while the nested menu opens Company (Blogs + How It Works) is a non-navigating parent —
independently. It reuses site.css's dead .menu-item-has-children chevron (fontello its trigger is a plain anchor with no href target, only a
glyph, already coloured white for this exact header widget) instead dropdown toggle. Class names below (.dm-solutions-dropdown,
of inventing a new arrow, so it matches the site's existing premium .dm-mobile-solutions*) are unchanged from when this same
nav styling. Desktop opens on hover/focus; mobile is a 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. */ height-collapsing accordion controlled by the chevron button. */
@media (min-width: 1025px) { @media (min-width: 1025px) {
#masthead .header-menu-container nav { #masthead .header-menu-container nav {
@@ -1103,8 +1127,6 @@ export default function Header() {
font-weight: 700; font-weight: 700;
} }
#masthead .header-menu-container .main-menu > li.menu-item-has-children:hover .dm-solutions-dropdown,
#masthead .header-menu-container .main-menu > li.menu-item-has-children:focus-within .dm-solutions-dropdown,
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"] ~ .dm-solutions-dropdown { #masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"] ~ .dm-solutions-dropdown {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
@@ -1112,8 +1134,6 @@ export default function Header() {
transform: translate(-50%, 24px); transform: translate(-50%, 24px);
} }
#masthead .header-menu-container .main-menu > li.menu-item-has-children:hover > a:after,
#masthead .header-menu-container .main-menu > li.menu-item-has-children:focus-within > a:after,
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"]:after { #masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"]:after {
transform: rotate(180deg); transform: rotate(180deg);
} }

View File

@@ -42,6 +42,9 @@ export interface EVStat {
decimals?: number; decimals?: number;
suffix: string; suffix: string;
label: 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 /** 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 * static homepage feature list. The bottom stat bar is dropped in this mode
* to avoid repeating the metrics. */ * to avoid repeating the metrics. */
metrics?: EVStat[]; 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". */ /** Heading above the dashboard, e.g. "PERFORMANCE INSIGHT". */
cardsHeading?: string; cardsHeading?: string;
/** Accent theme for the dashboard column (defaults to the section red). */ /** Accent theme for the dashboard column (defaults to the section red). */
cardsTheme?: EVCardsTheme; 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 ---- */ /* ---- Default content = original homepage EV Logistics section ---- */
@@ -260,11 +272,13 @@ function DashboardPanel({
features, features,
heading, heading,
theme, theme,
sysLabel = "AI ENGINE",
}: { }: {
metrics: EVStat[]; metrics: EVStat[];
features: EVFeature[]; features: EVFeature[];
heading?: string; heading?: string;
theme?: EVCardsTheme; theme?: EVCardsTheme;
sysLabel?: string;
}) { }) {
const themeVars = theme const themeVars = theme
? ({ ? ({
@@ -287,7 +301,7 @@ function DashboardPanel({
<span className="evnd__dash-livedot" /> <span className="evnd__dash-livedot" />
{heading} {heading}
</span> </span>
<span className="evnd__dash-sys">AI ENGINE</span> <span className="evnd__dash-sys">{sysLabel}</span>
</div> </div>
)} )}
@@ -295,12 +309,16 @@ function DashboardPanel({
<div className="evnd__dash-kpis"> <div className="evnd__dash-kpis">
{metrics.map((m) => ( {metrics.map((m) => (
<div className="evnd__kpi" key={m.label}> <div className="evnd__kpi" key={m.label}>
<CountUp {m.text ? (
value={m.value} <b className="evnd__kpi-val evnd__kpi-val--text">{m.text}</b>
decimals={m.decimals} ) : (
suffix={m.suffix} <CountUp
className="evnd__kpi-val" value={m.value}
/> decimals={m.decimals}
suffix={m.suffix}
className="evnd__kpi-val"
/>
)}
<span className="evnd__kpi-label">{m.label}</span> <span className="evnd__kpi-label">{m.label}</span>
</div> </div>
))} ))}
@@ -342,11 +360,15 @@ export default function EVSection({
ariaLabel, ariaLabel,
mediaSlot, mediaSlot,
metrics, metrics,
barStats,
cardsHeading, cardsHeading,
cardsTheme, cardsTheme,
sysLabel,
mediaClassName,
}: EVSectionProps) { }: EVSectionProps) {
const bannerRef = useRef<HTMLDivElement>(null); const bannerRef = useRef<HTMLDivElement>(null);
const useDashboard = !!metrics && metrics.length > 0; const useDashboard = !!metrics && metrics.length > 0;
const isVideo = image.endsWith(".mp4");
/* In dashboard mode the workflow's accent theme is applied at the .evnd /* 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 section level so the shared animated network backdrop (below) is tinted to
@@ -595,13 +617,31 @@ export default function EVSection({
background: #ef4444; 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 { .evnd__title {
color: #fff !important; color: #fff !important;
font-weight: 800 !important; font-weight: 800 !important;
font-size: clamp(32px, 3.8vw, 48px) !important; font-size: clamp(32px, 3.8vw, 48px) !important;
line-height: 1.15 !important; line-height: 1.15 !important;
letter-spacing: -0.01em; letter-spacing: -0.01em;
margin: 0 0 36px 0; margin: 0;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.evnd__title { .evnd__title {
@@ -752,7 +792,7 @@ export default function EVSection({
.evnd-feature__desc { .evnd-feature__desc {
color: rgba(255,255,255,0.65) !important; color: rgba(255,255,255,0.65) !important;
font-weight: 400 !important; font-weight: 400 !important;
font-size: 14px !important; font-size: 16px !important;
line-height: 1.6 !important; line-height: 1.6 !important;
margin: 0; margin: 0;
} }
@@ -867,6 +907,13 @@ export default function EVSection({
font-size: 12.5px; font-size: 12.5px;
letter-spacing: 0.01em; 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). Large-screen layout balance (WF1/WF2 dashboard only, ≥1440px).
@@ -969,8 +1016,8 @@ export default function EVSection({
.evnd__dfeat-desc { .evnd__dfeat-desc {
color: rgba(255,255,255,0.6) !important; color: rgba(255,255,255,0.6) !important;
font-weight: 400 !important; font-weight: 400 !important;
font-size: 13px !important; font-size: 16px !important;
line-height: 1.45 !important; line-height: 1.55 !important;
margin: 0 !important; margin: 0 !important;
} }
@@ -1041,6 +1088,14 @@ export default function EVSection({
text-transform: none; text-transform: none;
opacity: 0.9; 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; } } @keyframes evndGlow { 0%,100% { opacity: 0.75; } 50% { opacity: 1; } }
@@ -1052,7 +1107,6 @@ export default function EVSection({
@media (max-width: 991px) { @media (max-width: 991px) {
.evnd { padding: 48px 32px 56px; } .evnd { padding: 48px 32px 56px; }
.evnd__grid { grid-template-columns: 1fr; gap: 40px; } .evnd__grid { grid-template-columns: 1fr; gap: 40px; }
.evnd__title { margin-bottom: 28px; }
.evnd__features { gap: 14px; } .evnd__features { gap: 14px; }
/* Stacked: KPI row can spread to 4-up since it now has full width. */ /* Stacked: KPI row can spread to 4-up since it now has full width. */
.evnd__dash-kpis { grid-template-columns: repeat(4, 1fr); } .evnd__dash-kpis { grid-template-columns: repeat(4, 1fr); }
@@ -1331,7 +1385,7 @@ export default function EVSection({
<div <div
className={`evnd__inner${useDashboard ? " evnd__inner--cards" : ""}${ className={`evnd__inner${useDashboard ? " evnd__inner--cards" : ""}${
useDashboard && !mediaSlot ? " evnd__inner--img" : "" useDashboard && !mediaSlot && !isVideo ? " evnd__inner--img" : ""
}`} }`}
> >
<div className="evnd__heading"> <div className="evnd__heading">
@@ -1345,7 +1399,7 @@ export default function EVSection({
{/* MAIN GRID */} {/* MAIN GRID */}
<div <div
className={`evnd__grid${useDashboard ? " evnd__grid--cards" : ""}${ className={`evnd__grid${useDashboard ? " evnd__grid--cards" : ""}${
useDashboard && !mediaSlot ? " evnd__grid--img" : "" useDashboard && !mediaSlot && !isVideo ? " evnd__grid--img" : ""
}`} }`}
> >
{/* Left column */} {/* Left column */}
@@ -1353,11 +1407,11 @@ export default function EVSection({
<div className="evnd__media"> <div className="evnd__media">
<div className="evnd__glow" /> <div className="evnd__glow" />
<div <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 ? (
mediaSlot mediaSlot
) : image.endsWith(".mp4") ? ( ) : isVideo ? (
<video <video
className="evnd__video" className="evnd__video"
autoPlay autoPlay
@@ -1401,6 +1455,7 @@ export default function EVSection({
features={features} features={features}
heading={cardsHeading} heading={cardsHeading}
theme={cardsTheme} theme={cardsTheme}
sysLabel={sysLabel}
/> />
) : ( ) : (
<div className="evnd__features"> <div className="evnd__features">
@@ -1434,15 +1489,19 @@ export default function EVSection({
all breakpoints. In dashboard mode it summarises the same workflow all breakpoints. In dashboard mode it summarises the same workflow
metrics; the homepage section uses its own stat set. */} metrics; the homepage section uses its own stat set. */}
<div className="evnd__bar"> <div className="evnd__bar">
{(useDashboard ? metrics! : stats).map((s) => ( {(useDashboard ? (barStats ?? metrics!) : stats).map((s) => (
<div className="evnd__bar-item" key={s.label}> <div className="evnd__bar-item" key={s.label}>
<span className="evnd__bar-label">{s.label}</span> <span className="evnd__bar-label">{s.label}</span>
<CountUp {s.text ? (
value={s.value} <b className="evnd__bar-val evnd__bar-val--text">{s.text}</b>
decimals={s.decimals} ) : (
suffix={s.suffix} <CountUp
className="evnd__bar-val" value={s.value}
/> decimals={s.decimals}
suffix={s.suffix}
className="evnd__bar-val"
/>
)}
</div> </div>
))} ))}
</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 React from "react";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal"; import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import styles from "./TheDoormileWay.module.css";
export default function TheDoormileWay() { export default function TheDoormileWay() {
return ( return (
@@ -10,29 +11,28 @@ 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="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="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"> <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={styles.header}>
<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"> <ScrollReveal delay={0.05} duration={0.7} yOffset={20} className={styles.kicker}>
<div className="elementor-widget-container"> <p className={styles.eyebrow}>/ Doormile Approach /</p>
<div className="logico-title">/ Doormile Approach /</div> <hr className={styles.divider} />
</div> </ScrollReveal>
</div> <ScrollReveal delay={0.15} duration={0.85} yOffset={30}>
</ScrollReveal> <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">
<ScrollReveal delay={0.15} duration={0.85} yOffset={30}> <div className="elementor-widget-container">
<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"> <div
<div className="elementor-widget-container"> className="logico-title"
<div style={{
className="logico-title" WebkitTextStroke: "4px #c01227",
style={{ color: "#fff",
WebkitTextStroke: "4px #c01227", fontWeight: 800,
color: "#fff", }}
fontWeight: 800, >
}} The Doormile Way
> </div>
The Doormile Way
</div> </div>
</div> </div>
</div> </ScrollReveal>
</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"> <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}> <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"> <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

@@ -54,7 +54,7 @@ export default function WingsFeatureGrid() {
__html: ` __html: `
.wfg-section { .wfg-section {
background: #ffffff; background: #ffffff;
padding: var(--space-section-lg, 80px) clamp(20px, 5vw, 40px); padding: var(--space-section-lg, 80px) 0;
} }
.wfg-header { .wfg-header {
@@ -82,9 +82,12 @@ export default function WingsFeatureGrid() {
margin: 0; margin: 0;
} }
.wfg-grid { .wfg-inner {
max-width: 1280px; max-width: 1380px;
margin: 0 auto; margin: 0 auto;
}
.wfg-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 20px; gap: 20px;
@@ -139,26 +142,35 @@ export default function WingsFeatureGrid() {
@media (max-width: 600px) { @media (max-width: 600px) {
.wfg-grid { grid-template-columns: 1fr; } .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"> <section className="wfg-section" id="wings-features">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wfg-header"> <div className="dm-section-container">
<div className="wfg-eyebrow">/ Why Businesses Choose Doormile Wings /</div> <div className="wfg-inner">
<h2 className="wfg-title">Built to be depended on</h2> <ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wfg-header">
</ScrollReveal> <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"> <StaggerChildren stagger={0.1} duration={0.7} yOffset={30} className="wfg-grid">
{FEATURES.map((f) => ( {FEATURES.map((f) => (
<div key={f.title} className="wfg-card"> <div key={f.title} className="wfg-card">
<div className="wfg-icon" aria-hidden="true">{f.icon}</div> <div className="wfg-icon" aria-hidden="true">{f.icon}</div>
<h3 className="wfg-card-title">{f.title}</h3> <h3 className="wfg-card-title">{f.title}</h3>
<p className="wfg-card-desc">{f.desc}</p> <p className="wfg-card-desc">{f.desc}</p>
</div> </div>
))} ))}
</StaggerChildren> </StaggerChildren>
</div>
</div>
</section> </section>
</> </>
); );

View File

@@ -1,96 +0,0 @@
import Link from "next/link";
export default function WingsFinalCTA() {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
.wfcta-box {
position: relative;
overflow: hidden;
/* Placeholder — swap for a sourced photo/illustration with a dark
overlay once real Doormile Wings imagery exists. */
background: linear-gradient(150deg, #0b0b0d 0%, #1f1f1f 60%, #0b0b0d 100%) !important;
padding: clamp(72px, 9vw, 120px) clamp(24px, 5vw, 48px) !important;
}
.wfcta-box::before {
content: '';
position: absolute;
inset: 0;
z-index: 0;
background: radial-gradient(60% 80% at 50% 0%, rgba(200,16,46,0.18) 0%, transparent 70%);
pointer-events: none;
}
.wfcta-box .we-cta-inner {
position: relative;
z-index: 1;
}
.wfcta-title {
font-size: clamp(32px, 4.5vw, 60px) !important;
color: #ffffff !important;
}
.wfcta-title em {
color: #ffffff !important;
}
.wfcta-sub {
max-width: 640px;
margin-left: auto !important;
margin-right: auto !important;
}
@media (max-width: 768px) {
.wfcta-container {
margin-top: 40px !important;
margin-bottom: 40px !important;
padding: 0 10px !important;
}
.wfcta-box {
padding: 48px 20px !important;
border-radius: 22px !important;
}
.wfcta-title {
font-size: clamp(28px, 6vw, 38px) !important;
line-height: 1.25 !important;
margin-bottom: 16px !important;
}
.wfcta-sub {
font-size: 15px !important;
line-height: 1.5 !important;
margin-bottom: 24px !important;
}
}
`,
}}
/>
<div className="we-container wfcta-container" id="wings-final-cta">
<div className="we-cta wfcta-box">
<div className="we-cta-inner">
<h2 className="we-cta-title wfcta-title">
Ready to move at<br />
<em>the speed of now?</em>
</h2>
<p className="we-cta-sub wfcta-sub">
Partner with Doormile Wings for same-day air logistics across India&rsquo;s major
regional capitals.
</p>
<div className="we-cta-btns">
<Link href="/contact" className="btn-we-primary" style={{ textDecoration: "none" }}>
Get Started
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ marginLeft: "8px", display: "inline-block", verticalAlign: "middle" }}>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
</Link>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -49,11 +49,24 @@ const FEATURES: EVFeature[] = [
export default function WingsFleetStrategy() { export default function WingsFleetStrategy() {
return ( return (
<div id="wings-fleet"> <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 <EVSection
bannerImage="/images/pahse1.png" bannerImage="/images/pahse1.png"
cardNumber="Phase 1" cardTitle="Quick Logistics"
cardTitle="Initial Fleet" cardSubtitle="Same-Day Air Network"
cardSubtitle="2x ATR72 + 1x Q400 Freighters"
eyebrow="/ Fleet Strategy /" eyebrow="/ Fleet Strategy /"
titleLead="Purpose-Built for " titleLead="Purpose-Built for "
titleAccent="Regional Speed" titleAccent="Regional Speed"
@@ -61,29 +74,29 @@ export default function WingsFleetStrategy() {
metrics={METRICS} metrics={METRICS}
cardsHeading="FLEET SPECIFICATIONS" cardsHeading="FLEET SPECIFICATIONS"
cardsTheme={{ accent: "#c8102e", accent2: "#ff6a3d", glow: "rgba(200,16,46,0.22)" }} cardsTheme={{ accent: "#c8102e", accent2: "#ff6a3d", glow: "rgba(200,16,46,0.22)" }}
gapTop
gapBottom
badges={[ badges={[
{ value: "3", label: "Phase 1 Aircraft" }, { value: "3", label: "Phase 1 Aircraft" },
{ value: "25", label: "Phase 2 Vision" }, { value: "25", label: "Phase 2 Vision" },
]} ]}
mediaSlot={ mediaSlot={
<div <video
autoPlay
loop
muted
playsInline
style={{ style={{
width: "100%", width: "100%",
height: "100%", height: "100%",
minHeight: 420, minHeight: 420,
background: "linear-gradient(150deg, #17171b 0%, #0b0b0d 100%)", objectFit: "cover",
display: "flex", display: "block",
alignItems: "center", background: "#17171b"
justifyContent: "center",
}} }}
aria-hidden="true"
> >
{/* Placeholder — swap for sourced ATR72/Q400 photography or a <source src="/videos/fleet.mp4" type="video/mp4" />
designed illustration once available. */} </video>
<svg width="88" height="88" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.15)" strokeWidth="1.5" 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>
</div>
} }
ariaLabel="Doormile Wings fleet strategy" ariaLabel="Doormile Wings fleet strategy"
/> />

View File

@@ -36,23 +36,29 @@ export default function WingsHero() {
return; return;
} }
gsap.fromTo( if (heading) {
heading, gsap.fromTo(
{ y: 55, opacity: 0, scale: 0.95 }, heading,
{ y: 0, opacity: 1, scale: 1, duration: 1.1, ease: "power4.out" } { y: 55, opacity: 0, scale: 0.95 },
); { y: 0, opacity: 1, scale: 1, duration: 1.1, ease: "power4.out" }
);
}
gsap.fromTo( if (text) {
text, gsap.fromTo(
{ y: 30, opacity: 0 }, text,
{ y: 0, opacity: 1, duration: 0.95, ease: "power3.out", delay: 0.25 } { y: 30, opacity: 0 },
); { y: 0, opacity: 1, duration: 0.95, ease: "power3.out", delay: 0.25 }
);
}
gsap.fromTo( if (actions) {
actions, gsap.fromTo(
{ y: 24, opacity: 0 }, actions,
{ y: 0, opacity: 1, duration: 0.85, ease: "power3.out", delay: 0.38 } { y: 24, opacity: 0 },
); { y: 0, opacity: 1, duration: 0.85, ease: "power3.out", delay: 0.38 }
);
}
}, [activeSlide]); }, [activeSlide]);
const handleDotClick = (index: number) => { const handleDotClick = (index: number) => {
@@ -65,21 +71,21 @@ export default function WingsHero() {
return ( return (
<> <>
<link rel="preload" as="image" href="/images/wings-heroslider3.png" /> <link rel="preload" as="image" href="/images/wings-heroslider4.png" />
<link rel="preload" as="image" href="/images/wings-heroslide2.png" /> <link rel="preload" as="image" href="/images/wings-heroslide2.png" />
<style <style
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __html: `
.wings-hero-bg.elementor-repeater-item-3264830, .wings-hero-bg.elementor-repeater-item-3264830,
.wings-hero-bg.elementor-repeater-item-6867061 { .wings-hero-bg.elementor-repeater-item-6867061 {
background-image: linear-gradient(rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0.16)), url('/images/wings-heroslider3.png') !important; 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-position: center !important;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-size: cover !important; background-size: cover !important;
} }
.wings-hero-bg.elementor-repeater-item-6867061 { .wings-hero-bg.elementor-repeater-item-6867061 {
background-image: linear-gradient(rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0.16)), url('/images/wings-heroslide2.png') !important; 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,
@@ -125,7 +131,7 @@ export default function WingsHero() {
align-items: center !important; align-items: center !important;
text-align: center !important; text-align: center !important;
width: 100% !important; width: 100% !important;
max-width: 1040px !important; max-width: 1000px !important;
margin: 0 auto !important; margin: 0 auto !important;
box-sizing: border-box !important; box-sizing: border-box !important;
} }
@@ -157,7 +163,7 @@ export default function WingsHero() {
.wings-hero-page .text-content { .wings-hero-page .text-content {
text-align: center !important; text-align: center !important;
max-width: 760px !important; max-width: 680px !important;
width: 100% !important; width: 100% !important;
margin: 0 auto !important; margin: 0 auto !important;
padding-left: 15px !important; padding-left: 15px !important;
@@ -228,6 +234,8 @@ export default function WingsHero() {
.wings-hero-page .owl-carousel.owl-theme .content-item { .wings-hero-page .owl-carousel.owl-theme .content-item {
height: 800px !important; height: 800px !important;
min-height: 800px !important; min-height: 800px !important;
border-radius: 24px !important;
overflow: hidden !important;
} }
} }
@@ -245,19 +253,19 @@ export default function WingsHero() {
} }
.wings-hero-page .owl-stage-outer { .wings-hero-page .owl-stage-outer {
height: 620px !important; height: 600px !important;
} }
.wings-hero-page .owl-carousel.owl-theme .content-item { .wings-hero-page .owl-carousel.owl-theme .content-item {
width: 100% !important; width: 100% !important;
min-height: 620px !important; min-height: 600px !important;
height: 620px !important; height: 600px !important;
border-radius: 25px !important; border-radius: 25px !important;
overflow: hidden !important; overflow: hidden !important;
} }
.wings-hero-page .slide-content { .wings-hero-page .slide-content {
min-height: 620px !important; min-height: 600px !important;
padding-left: 0 !important; padding-left: 0 !important;
padding-right: 0 !important; padding-right: 0 !important;
} }
@@ -284,13 +292,13 @@ export default function WingsHero() {
} }
.wings-hero-page .owl-stage-outer { .wings-hero-page .owl-stage-outer {
height: 560px !important; height: 520px !important;
} }
.wings-hero-page .owl-carousel.owl-theme .content-item, .wings-hero-page .owl-carousel.owl-theme .content-item,
.wings-hero-page .slide-content { .wings-hero-page .slide-content {
min-height: 560px !important; min-height: 520px !important;
height: 560px !important; height: 520px !important;
border-radius: 22px !important; border-radius: 22px !important;
} }
@@ -303,7 +311,7 @@ export default function WingsHero() {
.wings-hero-page .content-slider-item-heading { .wings-hero-page .content-slider-item-heading {
max-width: 100% !important; max-width: 100% !important;
font-size: clamp(24px, 7.2vw, 32px) !important; font-size: clamp(20px, 7vw, 32px) !important;
line-height: 1.12 !important; line-height: 1.12 !important;
} }
@@ -401,24 +409,19 @@ export default function WingsHero() {
<div className="slide-content-inner"> <div className="slide-content-inner">
<h1 className="content-slider-item-heading logico-content-wrapper-1"> <h1 className="content-slider-item-heading logico-content-wrapper-1">
<span className="heading-content"> <span className="heading-content">
Doormile Wings Where Quick Commerce
<br /> <br />
<ShimmerText className="font-extrabold"> <ShimmerText className="font-extrabold">
Same-Day Air Meets Quick Logistics
</ShimmerText>
<br className="wings-mobile-break" />
{" "}
<ShimmerText className="font-extrabold">
Logistics
</ShimmerText> </ShimmerText>
</span> </span>
</h1> </h1>
<div className="content-slider-item-text logico-content-wrapper-2"> <div className="content-slider-item-text logico-content-wrapper-2">
<div className="text-content"> <div className="text-content">
<p> <p>
Linking India&apos;s major regional capitals with MileTruth AI forecasts demand, protects promises,
AI-powered air movement and zero-emission EV and coordinates every handoff from runway to
last-mile delivery. doorstep.
</p> </p>
</div> </div>
</div> </div>
@@ -445,19 +448,24 @@ export default function WingsHero() {
<div className="slide-content-inner"> <div className="slide-content-inner">
<h1 className="content-slider-item-heading logico-content-wrapper-1"> <h1 className="content-slider-item-heading logico-content-wrapper-1">
<span className="heading-content"> <span className="heading-content">
Where Quick Commerce Doormile Wings
<br /> <br />
<ShimmerText className="font-extrabold"> <ShimmerText className="font-extrabold">
Meets Quick Logistics Same-Day Air
</ShimmerText>
<br className="wings-mobile-break" />
{" "}
<ShimmerText className="font-extrabold">
Logistics
</ShimmerText> </ShimmerText>
</span> </span>
</h1> </h1>
<div className="content-slider-item-text logico-content-wrapper-2"> <div className="content-slider-item-text logico-content-wrapper-2">
<div className="text-content"> <div className="text-content">
<p> <p>
MileTruth AI forecasts demand, protects promises, Linking India&apos;s major regional capitals with
and coordinates every handoff from runway to AI-powered air movement and zero-emission EV
doorstep. last-mile delivery.
</p> </p>
</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

@@ -1,14 +1,15 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { ScrollReveal } from "@/animations/Reveal"; import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import styles from "./WingsHowItWorks.module.css";
const STEPS = [ const STEPS = [
{ {
label: "Local Pickup", label: "Local Pickup",
desc: "Fast, on-demand first-mile logistics dispatched by electric vehicle.", desc: "Fast, on-demand first-mile logistics dispatched by electric vehicle.",
icon: ( icon: (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round"> <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" /> <path d="M3 13h13l3 4h2v3H3z" />
<circle cx="7" cy="20" r="1.6" /> <circle cx="7" cy="20" r="1.6" />
<circle cx="16" cy="20" r="1.6" /> <circle cx="16" cy="20" r="1.6" />
@@ -20,7 +21,7 @@ const STEPS = [
label: "Airport Clearance", label: "Airport Clearance",
desc: "Automated screening, grouping, and rapid cross-docking at the hub.", desc: "Automated screening, grouping, and rapid cross-docking at the hub.",
icon: ( icon: (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round"> <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="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z" />
<path d="m9 12 2 2 4-4" /> <path d="m9 12 2 2 4-4" />
</svg> </svg>
@@ -30,7 +31,7 @@ const STEPS = [
label: "Air Transit", label: "Air Transit",
desc: "Optimized cargo flights bypass surface congestion on our regional fleet.", desc: "Optimized cargo flights bypass surface congestion on our regional fleet.",
icon: ( icon: (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round"> <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" /> <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> </svg>
), ),
@@ -39,7 +40,7 @@ const STEPS = [
label: "Destination Hub", label: "Destination Hub",
desc: "Immediate de-consolidation and screening upon arrival.", desc: "Immediate de-consolidation and screening upon arrival.",
icon: ( icon: (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round"> <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" /> <rect x="4" y="10" width="16" height="11" rx="1" />
<path d="M9 21v-6h6v6M4 10 12 3l8 7" /> <path d="M9 21v-6h6v6M4 10 12 3l8 7" />
</svg> </svg>
@@ -49,7 +50,7 @@ const STEPS = [
label: "EV Last-Mile", label: "EV Last-Mile",
desc: "Zero-emission electric vehicles complete the final delivery.", desc: "Zero-emission electric vehicles complete the final delivery.",
icon: ( icon: (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round"> <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" /> <path d="M3 13h13l3 4h2v3H3z" />
<circle cx="7" cy="20" r="1.6" /> <circle cx="7" cy="20" r="1.6" />
<circle cx="16" cy="20" r="1.6" /> <circle cx="16" cy="20" r="1.6" />
@@ -61,246 +62,63 @@ const STEPS = [
export default function WingsHowItWorks() { export default function WingsHowItWorks() {
const [active, setActive] = useState(0); const [active, setActive] = useState(0);
const fillPct = ((active + 1) / STEPS.length) * 100;
return ( return (
<> <section className={styles.section} id="wings-how-it-works">
<style <div className={styles.sectionShell}>
dangerouslySetInnerHTML={{ <div className={styles.headerRow}>
__html: ` <ScrollReveal delay={0.05} duration={0.75} yOffset={25} className={styles.header}>
.whw-section { <div className={styles.eyebrow}>/ How It Works /</div>
background: #09090b; <h2 className={styles.title}>
padding: var(--space-section-lg, 80px) clamp(20px, 5vw, 40px); Pickup to
} <br />
delivery, one
<br />
connected flow
</h2>
</ScrollReveal>
.whw-header { <div className={styles.progress} aria-hidden="true">
margin: 0 auto 56px auto; <span className={styles.progressCount}>
max-width: 720px; {String(active + 1).padStart(2, "0")} / {String(STEPS.length).padStart(2, "0")}
text-align: center; </span>
} <span className={styles.progressTrack}>
<span
.whw-eyebrow { className={styles.progressFill}
font-size: 13px; style={{ width: `${((active + 1) / STEPS.length) * 100}%` }}
font-weight: 700; />
letter-spacing: 2px; </span>
text-transform: uppercase;
color: #c8102e;
margin-bottom: 16px;
}
.whw-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;
}
.whw-inner {
max-width: 1200px;
margin: 0 auto;
}
.whw-track-wrap {
position: relative;
height: 4px;
background: rgba(255, 255, 255, 0.08);
border-radius: 2px;
margin: 0 auto 44px auto;
}
.whw-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);
}
.whw-nodes {
position: absolute;
inset: 0;
}
.whw-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;
}
.whw-node.is-active {
background: #c8102e;
border-color: #ffffff;
box-shadow: 0 0 10px #c8102e;
}
.whw-steps {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 20px;
}
.whw-step {
min-width: 0;
padding: 22px 18px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(255, 255, 255, 0.02);
cursor: pointer;
transition: border-color 0.35s ease, background-color 0.35s ease, transform 0.35s ease;
}
.whw-step.is-active {
border-color: rgba(200, 16, 46, 0.5);
background: rgba(200, 16, 46, 0.06);
transform: translateY(-4px);
}
.whw-step-num {
font-size: 12px;
font-weight: 800;
letter-spacing: 1px;
color: rgba(255, 255, 255, 0.35);
margin-bottom: 14px;
}
.whw-step.is-active .whw-step-num {
color: #c8102e;
}
.whw-step-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.7);
margin-bottom: 14px;
}
.whw-step.is-active .whw-step-icon {
background: rgba(200, 16, 46, 0.15);
color: #ffffff;
}
.whw-step-label {
font-size: 15px !important;
font-weight: 700 !important;
line-height: 1.3 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0 0 6px 0;
}
.whw-step-desc {
font-size: 12.5px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.5);
margin: 0;
}
@media (max-width: 767px) {
.whw-track-wrap { display: none; }
.whw-steps {
grid-template-columns: 1fr;
gap: 16px;
position: relative;
padding-left: 28px;
}
.whw-steps::before {
content: '';
position: absolute;
left: 5px;
top: 8px;
bottom: 8px;
width: 2px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.12);
}
.whw-step {
position: relative;
}
.whw-step::before {
content: '';
position: absolute;
left: -27px;
top: 24px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #18181b;
border: 2px solid rgba(255, 255, 255, 0.2);
}
.whw-step.is-active::before {
background: #c8102e;
border-color: #ffffff;
box-shadow: 0 0 10px rgba(200, 16, 46, 0.7);
}
}
`,
}}
/>
<section className="whw-section" id="wings-how-it-works">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="whw-header">
<div className="whw-eyebrow">/ How It Works /</div>
<h2 className="whw-title">Pickup to delivery, one connected flow</h2>
</ScrollReveal>
<ScrollReveal delay={0.1} duration={0.8} yOffset={30} className="whw-inner">
<div className="whw-track-wrap" aria-hidden="true">
<div className="whw-track-fill" style={{ width: `${fillPct}%` }} />
<div className="whw-nodes">
{STEPS.map((_, i) => (
<div
key={i}
className={`whw-node${i <= active ? " is-active" : ""}`}
style={{ left: `${((i + 0.5) / STEPS.length) * 100}%` }}
/>
))}
</div>
</div> </div>
</div>
<div className="whw-steps"> <StaggerChildren stagger={0.06} duration={0.6} yOffset={24} className={styles.flow}>
{STEPS.map((step, i) => ( {STEPS.map((step, i) => (
<div <div
key={step.label} key={step.label}
className={`whw-step${i === active ? " is-active" : ""}`} className={`${styles.step} ${i === active ? styles.stepActive : ""}`}
onMouseEnter={() => setActive(i)} onMouseEnter={() => setActive(i)}
onClick={() => setActive(i)} onClick={() => setActive(i)}
role="button" role="button"
tabIndex={0} tabIndex={0}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") setActive(i); if (e.key === "Enter" || e.key === " ") setActive(i);
}} }}
> >
<div className="whw-step-num">0{i + 1}</div> <div className={styles.stepTop}>
<div className="whw-step-icon" aria-hidden="true">{step.icon}</div> <span className={styles.stepNum}>{String(i + 1).padStart(2, "0")}</span>
<h3 className="whw-step-label">{step.label}</h3> <span className={styles.stepTag}>
<p className="whw-step-desc">{step.desc}</p> <span className={styles.stepIcon} aria-hidden="true">{step.icon}</span>
Step
</span>
</div> </div>
))} <div className={styles.stepBottom}>
</div> <h3 className={styles.stepTitle}>{step.label}</h3>
</ScrollReveal> <p className={styles.stepDesc}>{step.desc}</p>
</section> </div>
</> </div>
))}
</StaggerChildren>
</div>
</section>
); );
} }

View File

@@ -5,31 +5,37 @@ const INDUSTRIES = [
title: "Quick Commerce", title: "Quick Commerce",
challenge: "Sub-3-hour delivery windows for dark stores and micro-warehouses.", challenge: "Sub-3-hour delivery windows for dark stores and micro-warehouses.",
solution: "Dedicated same-day air capacity linking metros to fulfillment hubs.", solution: "Dedicated same-day air capacity linking metros to fulfillment hubs.",
image: "/images/card 1.png",
}, },
{ {
title: "Express B2B Cargo", title: "Express B2B Cargo",
challenge: "Time-critical industrial parts and components stuck on slow trunk routes.", challenge: "Time-critical industrial parts and components stuck on slow trunk routes.",
solution: "Rapid airport-to-airport transport between major economic nodes.", solution: "Rapid airport-to-airport transport between major economic nodes.",
image: "/images/card 2.png",
}, },
{ {
title: "Healthcare & Pharma", title: "Healthcare & Pharma",
challenge: "Temperature-sensitive, compliance-critical shipments with zero margin for error.", challenge: "Temperature-sensitive, compliance-critical shipments with zero margin for error.",
solution: "Active and passive cold-chain packaging with full chain-of-custody tracking.", solution: "Active and passive cold-chain packaging with full chain-of-custody tracking.",
image: "/images/card 3.png",
}, },
{ {
title: "Cold Chain Logistics", title: "Cold Chain Logistics",
challenge: "Perishables and fresh goods that can't survive slow surface transit.", challenge: "Perishables and fresh goods that can't survive slow surface transit.",
solution: "Precision temperature tracking matched to airline-speed transit.", solution: "Precision temperature tracking matched to airline-speed transit.",
image: "/images/card4.png",
}, },
{ {
title: "High-Value Cargo", title: "High-Value Cargo",
challenge: "Zero tolerance for loss, tampering, or delay on high-value shipments.", challenge: "Zero tolerance for loss, tampering, or delay on high-value shipments.",
solution: "Maximum-security handling with real-time electronic custody tracking.", solution: "Maximum-security handling with real-time electronic custody tracking.",
image: "/images/card5.png",
}, },
{ {
title: "E-Commerce Logistics", title: "E-Commerce Logistics",
challenge: "Next-day and same-day fulfillment expectations across tier-1 and tier-2 cities.", 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.", solution: "Direct sorting-hub-to-airport clearance network for faster regional delivery.",
image: "/images/card6.png",
}, },
]; ];
@@ -41,7 +47,7 @@ export default function WingsIndustries() {
__html: ` __html: `
.wind-section { .wind-section {
background: #fafafa; background: #fafafa;
padding: var(--space-section-lg, 80px) clamp(20px, 5vw, 40px); padding: var(--space-section-lg, 80px) 0 20px 0; /* Reduced bottom padding to minimize gap */
} }
.wind-header { .wind-header {
@@ -69,9 +75,12 @@ export default function WingsIndustries() {
margin: 0; margin: 0;
} }
.wind-grid { .wind-inner {
max-width: 1280px; max-width: 1380px;
margin: 0 auto; margin: 0 auto;
}
.wind-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 20px; gap: 20px;
@@ -172,40 +181,57 @@ export default function WingsIndustries() {
@media (max-width: 600px) { @media (max-width: 600px) {
.wind-grid { grid-template-columns: 1fr; } .wind-grid { grid-template-columns: 1fr; }
.wind-card { aspect-ratio: 3 / 4; } .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"> <section className="wind-section" id="wings-industries">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wind-header"> <div className="dm-section-container">
<div className="wind-eyebrow">/ Industries We Serve /</div> <div className="wind-inner">
<h2 className="wind-title">Built for the shipments that can&rsquo;t wait</h2> <ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wind-header">
</ScrollReveal> <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"> <ScrollReveal delay={0.1} duration={0.8} yOffset={30} className="wind-grid">
{INDUSTRIES.map((ind) => ( {INDUSTRIES.map((ind) => (
<div key={ind.title} className="wind-card"> <div
<div className="wind-card-wash" aria-hidden="true" /> 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"> <div className="wind-card-default">
<h3 className="wind-card-default-title">{ind.title}</h3> <h3 className="wind-card-default-title">{ind.title}</h3>
</div> </div>
<div className="wind-card-hover"> <div className="wind-card-hover">
<h3 className="wind-card-hover-title">{ind.title}</h3> <h3 className="wind-card-hover-title">{ind.title}</h3>
<div> <div>
<p className="wind-card-label">Challenge</p> <p className="wind-card-label">Challenge</p>
<p className="wind-card-text">{ind.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> </div>
<div> ))}
<p className="wind-card-label">Doormile Wings</p> </ScrollReveal>
<p className="wind-card-text">{ind.solution}</p> </div>
</div> </div>
</div>
</div>
))}
</ScrollReveal>
</section> </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

@@ -1,4 +1,5 @@
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal"; import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import styles from "./WingsInfrastructure.module.css";
const CARDS = [ const CARDS = [
{ {
@@ -44,117 +45,23 @@ const CARDS = [
export default function WingsInfrastructure() { export default function WingsInfrastructure() {
return ( return (
<> <section className={styles.section} id="wings-infrastructure">
<style <div className={styles.sectionShell}>
dangerouslySetInnerHTML={{ <ScrollReveal delay={0.05} duration={0.7} yOffset={20} className={styles.header}>
__html: ` <div className={styles.eyebrow}>/ Infrastructure /</div>
.wia-section { <h2 className={styles.title}>A network built to move, not to sit idle</h2>
background: #09090b;
padding: var(--space-section, 64px) clamp(20px, 5vw, 40px);
}
.wia-header {
margin: 0 auto 44px auto;
max-width: 720px;
text-align: center;
}
.wia-eyebrow {
font-size: 13px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.4);
margin-bottom: 16px;
}
.wia-title {
font-size: clamp(26px, 3vw, 40px) !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
line-height: 1.2 !important;
text-transform: none !important;
color: #ffffff !important;
margin: 0;
}
.wia-grid {
max-width: 1280px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 20px;
}
.wia-card {
min-width: 0;
padding: 28px 24px;
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 18px;
background: rgba(255, 255, 255, 0.02);
transition: border-color 0.3s ease, background-color 0.3s ease;
}
.wia-card:hover {
border-color: rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.035);
}
.wia-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.7);
margin-bottom: 18px;
}
.wia-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;
}
.wia-card-desc {
font-size: 13.5px;
line-height: 1.55;
color: rgba(255, 255, 255, 0.5);
margin: 0;
}
@media (max-width: 1024px) {
.wia-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
.wia-grid { grid-template-columns: 1fr; }
}
`,
}}
/>
<section className="wia-section" id="wings-infrastructure">
<ScrollReveal delay={0.05} duration={0.7} yOffset={20} className="wia-header">
<div className="wia-eyebrow">/ Infrastructure /</div>
<h2 className="wia-title">A network built to move, not to sit idle</h2>
</ScrollReveal> </ScrollReveal>
<StaggerChildren stagger={0.08} duration={0.6} yOffset={20} className="wia-grid"> <StaggerChildren stagger={0.08} duration={0.6} yOffset={20} className={styles.grid}>
{CARDS.map((c) => ( {CARDS.map((c) => (
<div key={c.title} className="wia-card"> <div key={c.title} className={styles.card}>
<div className="wia-icon" aria-hidden="true">{c.icon}</div> <div className={styles.icon} aria-hidden="true">{c.icon}</div>
<h3 className="wia-card-title">{c.title}</h3> <h3 className={styles.cardTitle}>{c.title}</h3>
<p className="wia-card-desc">{c.desc}</p> <p className={styles.cardDesc}>{c.desc}</p>
</div> </div>
))} ))}
</StaggerChildren> </StaggerChildren>
</section> </div>
</> </section>
); );
} }

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import gsap from "gsap";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal"; import { ScrollReveal, StaggerChildren } from "@/animations/Reveal";
const CAPABILITIES = [ const CAPABILITIES = [
@@ -64,6 +65,114 @@ const CAPABILITIES = [
}, },
]; ];
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() { export default function WingsMileTruth() {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -160,7 +269,7 @@ export default function WingsMileTruth() {
.wmt-section { .wmt-section {
position: relative; position: relative;
width: calc(100% - 40px); width: calc(100% - 40px);
margin: 0 auto; margin: clamp(24px, 4vw, 40px) auto;
border-radius: 28px; border-radius: 28px;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
@@ -195,7 +304,7 @@ export default function WingsMileTruth() {
.wmt-inner { .wmt-inner {
position: relative; position: relative;
z-index: 1; z-index: 1;
max-width: 1200px; max-width: 1380px;
margin: 0 auto; margin: 0 auto;
} }
@@ -238,11 +347,12 @@ export default function WingsMileTruth() {
border: 1px solid rgba(255, 255, 255, 0.07); border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 18px; border-radius: 18px;
padding: 26px 22px; padding: 26px 22px;
transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), 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); 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 { .wmt-card:hover {
transform: translateY(-6px);
border-color: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.16);
background-color: rgba(255, 255, 255, 0.055); background-color: rgba(255, 255, 255, 0.055);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35);
@@ -270,7 +380,7 @@ export default function WingsMileTruth() {
} }
.wmt-card-desc { .wmt-card-desc {
font-size: 13px; font-size: 16px;
line-height: 1.55; line-height: 1.55;
color: rgba(255, 255, 255, 0.55); color: rgba(255, 255, 255, 0.55);
margin: 0; margin: 0;
@@ -347,6 +457,11 @@ export default function WingsMileTruth() {
@media (max-width: 600px) { @media (max-width: 600px) {
.wmt-grid { grid-template-columns: 1fr; } .wmt-grid { grid-template-columns: 1fr; }
.wmt-callout { flex-direction: column; align-items: flex-start; } .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; }
} }
`, `,
}} }}
@@ -357,7 +472,7 @@ export default function WingsMileTruth() {
<div className="wmt-glow tl" aria-hidden="true" /> <div className="wmt-glow tl" aria-hidden="true" />
<div className="wmt-glow br" aria-hidden="true" /> <div className="wmt-glow br" aria-hidden="true" />
<div className="wmt-inner"> <div className="wmt-inner dm-section-container">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wmt-header"> <ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wmt-header">
<div className="wmt-eyebrow">/ MileTruth&trade; AI /</div> <div className="wmt-eyebrow">/ MileTruth&trade; AI /</div>
<h2 className="wmt-title">The intelligence layer behind every delivery</h2> <h2 className="wmt-title">The intelligence layer behind every delivery</h2>
@@ -365,11 +480,11 @@ export default function WingsMileTruth() {
<StaggerChildren stagger={0.08} duration={0.65} yOffset={25} className="wmt-grid"> <StaggerChildren stagger={0.08} duration={0.65} yOffset={25} className="wmt-grid">
{CAPABILITIES.map((c) => ( {CAPABILITIES.map((c) => (
<div key={c.title} className="wmt-card"> <TiltCard key={c.title} className="wmt-card">
<div className="wmt-card-icon" aria-hidden="true">{c.icon}</div> <div className="wmt-card-icon" aria-hidden="true">{c.icon}</div>
<h3 className="wmt-card-title">{c.title}</h3> <h3 className="wmt-card-title">{c.title}</h3>
<p className="wmt-card-desc">{c.desc}</p> <p className="wmt-card-desc">{c.desc}</p>
</div> </TiltCard>
))} ))}
</StaggerChildren> </StaggerChildren>

View File

@@ -53,12 +53,12 @@ export default function WingsRoadmap() {
.wrm-section { .wrm-section {
position: relative; position: relative;
width: calc(100% - 40px); width: calc(100% - 40px);
margin: 0 auto; margin: clamp(32px, 4vw, 56px) auto clamp(16px, 2vw, 32px);
border-radius: 28px; border-radius: 28px;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
background: #09090b; background: #09090b;
padding: clamp(64px, 8vw, 100px) clamp(24px, 5vw, 48px); padding: clamp(40px, 5vw, 64px) clamp(24px, 5vw, 48px);
} }
.wrm-header { .wrm-header {
@@ -87,7 +87,7 @@ export default function WingsRoadmap() {
} }
.wrm-inner { .wrm-inner {
max-width: 1200px; max-width: 1380px;
margin: 0 auto; margin: 0 auto;
} }
@@ -180,8 +180,8 @@ export default function WingsRoadmap() {
.wrm-phase-pill.white-pill { background: rgba(255,255,255,0.12); color: #ffffff; border: 1px solid rgba(255,255,255,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 { .wrm-card-desc {
font-size: 12.5px; font-size: 14.5px;
line-height: 1.5; line-height: 1.55;
color: rgba(255, 255, 255, 0.55); color: rgba(255, 255, 255, 0.55);
margin: 0; margin: 0;
} }
@@ -203,6 +203,16 @@ export default function WingsRoadmap() {
} }
@media (max-width: 767px) { @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-track-wrap { display: none; }
.wrm-grid { .wrm-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -244,43 +254,45 @@ export default function WingsRoadmap() {
/> />
<section className="wrm-section" id="wings-roadmap"> <section className="wrm-section" id="wings-roadmap">
<div className="wrm-header"> <div className="dm-section-container">
<div className="wrm-eyebrow">/ Future Roadmap /</div> <div className="wrm-header">
<h2 className="wrm-title">A ten-year path to national scale</h2> <div className="wrm-eyebrow">/ Future Roadmap /</div>
</div> <h2 className="wrm-title">A ten-year path to national scale</h2>
<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>
<div className="wrm-grid"> <div className="wrm-inner">
{ROADMAP_DATA.map((item, i) => ( <div className="wrm-track-wrap" aria-hidden="true">
<div <div className="wrm-track-fill" style={{ width: `${fillPct}%` }} />
key={item.year} <div className="wrm-nodes">
className={`wrm-card${item.glow ? " wrm-glow" : ""}${i === active ? " is-active" : ""}`} {ROADMAP_DATA.map((_, i) => (
onMouseEnter={() => setActive(i)} <div
onClick={() => setActive(i)} key={i}
role="button" className={`wrm-node${i <= active ? " is-active" : ""}`}
tabIndex={0} style={{ left: `${((i + 0.5) / ROADMAP_DATA.length) * 100}%` }}
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 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>
</div> </div>
</section> </section>

View File

@@ -43,7 +43,7 @@ export default function WingsSpeedAdvantage() {
__html: ` __html: `
.wsa-section { .wsa-section {
background: #fafafa; background: #fafafa;
padding: var(--space-section-lg, 80px) clamp(20px, 5vw, 40px); padding: var(--space-section-lg, 80px) 0;
} }
.wsa-header { .wsa-header {
@@ -71,10 +71,13 @@ export default function WingsSpeedAdvantage() {
margin: 0; margin: 0;
} }
.wsa-inner {
max-width: 1380px;
margin: 0 auto;
}
.wsa-face-off { .wsa-face-off {
position: relative; position: relative;
max-width: 1100px;
margin: 0 auto;
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 24px; gap: 24px;
@@ -115,7 +118,7 @@ export default function WingsSpeedAdvantage() {
line-height: 1; line-height: 1;
} }
.wsa-panel-surface .wsa-value { color: #b3b6bf; } .wsa-panel-surface .wsa-value { color: #c8102e; }
.wsa-panel-wings .wsa-value { color: #ffffff; } .wsa-panel-wings .wsa-value { color: #ffffff; }
.wsa-unit { .wsa-unit {
@@ -159,39 +162,53 @@ export default function WingsSpeedAdvantage() {
@media (max-width: 767px) { @media (max-width: 767px) {
.wsa-face-off { grid-template-columns: 1fr; gap: 40px; } .wsa-face-off { grid-template-columns: 1fr; gap: 40px; }
.wsa-vs { position: static; transform: none; margin: -20px auto; } /* 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"> <section className="wsa-section" ref={sectionRef} id="wings-speed">
<div className="dm-section-container">
<div className="wsa-header" ref={headerRef}> <div className="wsa-header" ref={headerRef}>
<div className="wsa-eyebrow">/ The Speed Advantage /</div> <div className="wsa-eyebrow">/ The Speed Advantage /</div>
<h2 className="wsa-title">Same-day, not some-day</h2> <h2 className="wsa-title">Same-day, not some-day</h2>
</div> </div>
<div className="wsa-face-off"> <div className="wsa-inner">
<div className="wsa-panel wsa-panel-surface" ref={leftRef}> <div className="wsa-face-off">
<div className="wsa-panel-label">Surface Logistics</div> <div className="wsa-panel wsa-panel-surface" ref={leftRef}>
<div className="wsa-value"> <div className="wsa-panel-label">Surface Logistics</div>
48<CountUp end={96} duration={1.6} triggerOnce /> <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>
<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-vs" ref={vsRef}>VS</div>
<div className="wsa-panel wsa-panel-wings" ref={rightRef}> <div className="wsa-panel wsa-panel-wings" ref={rightRef}>
<div className="wsa-panel-label">Doormile Wings</div> <div className="wsa-panel-label">Doormile Wings</div>
<div className="wsa-value"> <div className="wsa-value">
Max <CountUp end={3} duration={1.4} triggerOnce /> 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 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>
</div>
</section> </section>
</> </>
); );

View File

@@ -1,5 +1,39 @@
"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"; 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 = [ const FEATURES = [
{ {
title: "Same-Day Air Logistics", title: "Same-Day Air Logistics",
@@ -48,7 +82,536 @@ const FEATURES = [
}, },
]; ];
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() { 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 ( return (
<> <>
<style <style
@@ -56,7 +619,7 @@ export default function WingsWhyGrid() {
__html: ` __html: `
.wwg-section { .wwg-section {
background: #ffffff; background: #ffffff;
padding: var(--space-section-lg, 80px) clamp(20px, 5vw, 40px); padding: var(--space-section-lg, 80px) 0;
} }
.wwg-header { .wwg-header {
@@ -84,21 +647,129 @@ export default function WingsWhyGrid() {
margin: 0; margin: 0;
} }
.wwg-grid { .wwg-inner {
max-width: 1280px; max-width: 1380px;
margin: 0 auto; 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; display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 24px; gap: 24px;
} }
.wwg-card { .wwg-card {
position: relative;
min-width: 0; min-width: 0;
padding: 30px 24px; padding: 30px 24px;
border: 1px solid rgba(17, 17, 17, 0.08); border: 1px solid rgba(17, 17, 17, 0.08);
border-radius: 18px; border-radius: 18px;
background: #fafafa; background: #fafafa;
transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease; /* 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 { .wwg-card:hover {
@@ -107,6 +778,37 @@ export default function WingsWhyGrid() {
transform: translateY(-4px); 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 { .wwg-icon {
width: 52px; width: 52px;
height: 52px; height: 52px;
@@ -117,6 +819,18 @@ export default function WingsWhyGrid() {
background: rgba(200, 16, 46, 0.06); background: rgba(200, 16, 46, 0.06);
color: #c8102e; color: #c8102e;
margin-bottom: 20px; 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 { .wwg-card-title {
@@ -140,27 +854,101 @@ export default function WingsWhyGrid() {
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.wwg-grid { grid-template-columns: 1fr; } .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"> <section className="wwg-section" id="wings-why">
<ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wwg-header"> <div className="dm-section-container">
<div className="wwg-eyebrow">/ Why Doormile Wings /</div> <div className="wwg-inner">
<h2 className="wwg-title">Built for the speed of the Gen-Z economy</h2> <ScrollReveal delay={0.05} duration={0.75} yOffset={25} className="wwg-header">
</ScrollReveal> <div className="wwg-eyebrow">/ Why Doormile Wings /</div>
<h2 className="wwg-title">Built for the speed of the Gen-Z economy</h2>
</ScrollReveal>
<StaggerChildren stagger={0.1} duration={0.7} yOffset={30} className="wwg-grid"> <div className="wwg-grid-wrap" ref={wrapRef}>
{FEATURES.map((f) => ( <svg ref={svgRef} className="wwg-flightpath" aria-hidden="true" focusable="false">
<div key={f.title} className="wwg-card"> {/* Rendered first (behind) so its blur/extra width reads as a
<div className="wwg-icon" aria-hidden="true">{f.icon}</div> soft glow sitting under the route rather than visually
<h3 className="wwg-card-title">{f.title}</h3> thickening it — the crisp .wwg-flightpath-line painted
<p className="wwg-card-desc">{f.desc}</p> 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>
</StaggerChildren> </div>
</section> </section>
</> </>
); );

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
import { ScrollReveal, StaggerChildren } from "@/animations/Reveal"; import { CountUp, ScrollReveal, StaggerChildren } from "@/animations/Reveal";
import IndustryWorldMap from "./IndustryWorldMap"; import IndustryWorldMap from "./IndustryWorldMap";
const WS_STATS = [ const WS_STATS = [
@@ -42,7 +42,7 @@ const WS_CARDS = [
}, },
{ {
title: "Community Growth", title: "Community Growth",
desc: "Creating local opportunities through training, employment, and empowerment.", desc: "Creating local opportunities through entrepreneurship, collaboration, and community-driven growth.",
icon: ( icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<circle cx="9" cy="8" r="3" /> <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 ( return (
<> <>
<style dangerouslySetInnerHTML={{ __html: ` <style dangerouslySetInnerHTML={{ __html: `
@@ -419,12 +460,12 @@ export default function WomenSection() {
</div> </div>
<div className="elementor-element elementor-element-239afbb elementor-widget elementor-widget-logico_heading" data-id="239afbb" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default"> <div className="elementor-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"> <div className="elementor-widget-container">
<h5 className="logico-title">Empowering Women</h5> <h5 className="logico-title">{bannerLabel}</h5>
</div> </div>
</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-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"> <div className="elementor-widget-container">
<p>Doormile empowers women to lead the future of logistics.</p> <p>{bannerCopy}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -433,8 +474,8 @@ export default function WomenSection() {
<div className="elementor-widget-container"> <div className="elementor-widget-container">
<div className="button-widget"> <div className="button-widget">
<div className="button-container"> <div className="button-container">
<a href="#" className="logico-small-button"> <a href={ctaHref} className="logico-small-button">
Explore more {ctaLabel}
<svg viewBox="0 0 13 20"> <svg viewBox="0 0 13 20">
<polyline points="0.5 19.5 3 19.5 12.5 10 3 0.5"></polyline> <polyline points="0.5 19.5 3 19.5 12.5 10 3 0.5"></polyline>
</svg> </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-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-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="elementor-widget-container">
<div className="logico-title">/ Women Empowerment /</div> <div className="logico-title">{eyebrow}</div>
</div> </div>
</div> </div>
@@ -464,7 +505,7 @@ export default function WomenSection() {
<ScrollReveal duration={0.8} yOffset={28} triggerOnce> <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-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"> <div className="elementor-widget-container">
<h3 className="logico-title">Women Leading the Way</h3> <h3 className="logico-title">{heading}</h3>
</div> </div>
</div> </div>
</ScrollReveal> </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 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"> <div id="ws-stories">
<StaggerChildren className="ws__stats" stagger={0.1} duration={0.6} yOffset={20} triggerOnce> <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" 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 className="ws__stat-label">{s.label}</div>
</div> </div>
))} ))}
</StaggerChildren> </StaggerChildren>
<StaggerChildren className="ws__cards" stagger={0.08} duration={0.6} yOffset={24} triggerOnce> <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}> <div className="ws__card" key={c.title}>
<span className="ws__card-icon" aria-hidden="true">{c.icon}</span> <span className="ws__card-icon" aria-hidden="true">{c.icon}</span>
<div className="ws__card-title">{c.title}</div> <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`, "/solutions": `${SHARED} page-id-59 elementor-page-59`,
// PHP source quirk: about-us.php has `home` class (upstream bug, preserved) // PHP source quirk: about-us.php has `home` class (upstream bug, preserved)
"/about-us": `home ${SHARED} page-id-59 elementor-page-59`, "/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`, "/blog": `${SHARED} page-id-59 elementor-page-59`,
"/contact": `${SHARED} page-id-59 elementor-page-59`, "/contact": `${SHARED} page-id-59 elementor-page-59`,
}; };