12 Commits

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

3
.gitignore vendored
View File

@@ -42,6 +42,3 @@ next-env.d.ts
# Local backups of pre-optimized GLB source models (not served) # Local backups of pre-optimized GLB source models (not served)
.glb-originals/ .glb-originals/
# Scraped demo assets
demo.artureanec.com/

View File

@@ -2,10 +2,19 @@ import type { NextConfig } from "next";
import path from "path"; import path from "path";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
async redirects() {
return [
{
source: "/air-freight",
destination: "/doormile-wings",
permanent: true,
},
];
},
turbopack: { turbopack: {
root: path.resolve(__dirname), root: path.resolve(__dirname),
}, },
// output: "export" removed to allow server actions output: "export",
// Required by the How It Works 3D experience. React StrictMode double-invokes // Required by the How It Works 3D experience. React StrictMode double-invokes
// mount/effects in dev, which tears down and re-creates the WebGL context of // mount/effects in dev, which tears down and re-creates the WebGL context of
// the heavy 32MB scene mid-initialization — the context is lost ("THREE. // the heavy 32MB scene mid-initialization — the context is lost ("THREE.
@@ -15,6 +24,7 @@ const nextConfig: NextConfig = {
// any other page's runtime behavior. // any other page's runtime behavior.
reactStrictMode: false, reactStrictMode: false,
images: { images: {
unoptimized: true,
formats: ["image/avif", "image/webp"], formats: ["image/avif", "image/webp"],
}, },
}; };

BIN
public/images/Aravinth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

BIN
public/images/Fazul.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

BIN
public/images/Investor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/images/Parthi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

BIN
public/images/Suriya.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
public/images/about-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
public/images/bg-404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

BIN
public/images/card 1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/images/card 2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/images/card 3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/images/card4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
public/images/card5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
public/images/card6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

BIN
public/images/ev.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

BIN
public/images/home-bg-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

BIN
public/images/home1-bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

BIN
public/images/mile-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

BIN
public/images/pahse1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

BIN
public/videos/fleet.mp4 Normal file

Binary file not shown.

View File

@@ -1,49 +0,0 @@
import fs from 'fs';
import path from 'path';
const files = [
"src/components/sections/BlogsHero.tsx",
"src/components/sections/AboutHero.tsx",
"src/components/sections/SingleBlog.tsx",
"src/components/sections/ContactsHero.tsx",
"src/components/sections/IndexHero.tsx",
"src/components/sections/SolutionsHero.tsx",
"src/components/sections/MileTruthHero.tsx",
"src/components/sections/HowItWorksHero.tsx"
];
for (const f of files) {
let content = fs.readFileSync(f, 'utf8');
const match = content.match(/<link\s+rel="preload"\s+as="image"\s+href="([^"]+)"\s*\/>/);
if (match) {
const href = match[1];
if (!content.includes('import { preload } from "react-dom"')) {
const lastImportIndex = content.lastIndexOf('import ');
const endOfLastImport = content.indexOf('\n', lastImportIndex);
content = content.substring(0, endOfLastImport + 1) + 'import { preload } from "react-dom";\n' + content.substring(endOfLastImport + 1);
}
content = content.replace(match[0], '');
// Find the default export function
const fnMatch = content.match(/export default function\s+\w+\s*\([^)]*\)\s*\{/);
if (fnMatch) {
const insertIdx = fnMatch.index + fnMatch[0].length;
content = content.substring(0, insertIdx) + `\n preload("${href}", { as: "image" });` + content.substring(insertIdx);
} else {
// try non-default export
const fnMatch2 = content.match(/export function\s+\w+\s*\([^)]*\)\s*\{/);
if (fnMatch2) {
const insertIdx = fnMatch2.index + fnMatch2[0].length;
content = content.substring(0, insertIdx) + `\n preload("${href}", { as: "image" });` + content.substring(insertIdx);
} else {
console.log("Could not find function for " + f);
}
}
fs.writeFileSync(f, content, 'utf8');
console.log("Fixed " + f);
}
}

View File

@@ -2,34 +2,25 @@
import nodemailer from "nodemailer"; import nodemailer from "nodemailer";
interface ContactFormData { export async function sendContactEmail(data: {
fullName: string; fullName: string;
email: string; email: string;
subject: string; subject: string;
message: string; message: string;
} }) {
export async function sendEmailAction(data: ContactFormData) {
const { fullName, email, subject, message } = data; const { fullName, email, subject, message } = data;
if (!fullName || !email || !message) { if (!fullName || !email || !subject || !message) {
return { success: false, error: "Name, email, and message are required." }; return { success: false, error: "All fields are required." };
} }
let password = process.env.SMTP_PASSWORD || "";
if (password.startsWith('"') && password.endsWith('"')) {
password = password.slice(1, -1);
}
// Remove spaces just in case
password = password.replace(/\s+/g, "");
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST, host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT) || 465, port: Number(process.env.SMTP_PORT) || 465,
secure: true, secure: true,
auth: { auth: {
user: process.env.SMTP_USER, user: process.env.SMTP_USER,
pass: password, pass: process.env.SMTP_PASSWORD,
}, },
}); });
@@ -38,25 +29,25 @@ export async function sendEmailAction(data: ContactFormData) {
from: `"${fullName}" <${process.env.SMTP_USER}>`, from: `"${fullName}" <${process.env.SMTP_USER}>`,
to: process.env.CONTACT_RECEIVER, to: process.env.CONTACT_RECEIVER,
replyTo: email, replyTo: email,
subject: `New Contact Form Submission: ${subject || "No Subject"}`, subject: `New Contact Form Submission: ${subject}`,
html: ` html: `
<div style="font-family: sans-serif; color: #333;"> <div style="font-family: sans-serif; color: #333; max-width: 600px; margin: 0 auto; border: 1px solid #eee; padding: 20px; border-radius: 8px;">
<h2>New Contact Form Submission</h2> <h2 style="color: #c01227; border-bottom: 2px solid #c01227; padding-bottom: 10px; margin-top: 0;">New Contact Form Submission</h2>
<p><strong>Name:</strong> ${fullName}</p> <p><strong>Name:</strong> ${fullName}</p>
<p><strong>Email:</strong> ${email}</p> <p><strong>Email:</strong> ${email}</p>
<p><strong>Subject:</strong> ${subject}</p> <p><strong>Subject:</strong> ${subject}</p>
<p><strong>Message:</strong></p> <p><strong>Message:</strong></p>
<blockquote style="background: #f9f9f9; padding: 15px; border-left: 4px solid #ccc;"> <blockquote style="background: #f9f9f9; padding: 15px; border-left: 4px solid #c01227; margin: 0; font-style: italic;">
${message.replace(/\n/g, "<br>")} ${message.replace(/\n/g, "<br />")}
</blockquote> </blockquote>
</div> </div>
`, `,
}); });
return { success: true }; return { success: true };
} catch (error: any) { } catch (error) {
console.error("Email send error:", error); console.error("Email send error:", error);
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : "Failed to send the email. Please try again later.";
return { success: false, error: errorMessage || "Failed to send the email. Please try again later." }; return { success: false, error: errorMessage };
} }
} }

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

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

View File

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

View File

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

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

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

View File

@@ -10,6 +10,27 @@ html {
scroll-behavior: smooth; 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;
} }
} }
@@ -964,6 +994,49 @@ body {
box-sizing: border-box; box-sizing: border-box;
} }
/* ============================================================
SHARED PAGE CONTENT CONTAINER
Matches the Hero/Header/Footer's real rendered OUTER edge, not the
Footer's narrower inner text column. The Hero card
(.owl-stage-outer, via .wings-hero-page.elementor-element-741f56c)
and the Footer's outer band (.elementor-element-b29b8fc) both sit a
fixed, uncapped 20px in from the viewport edge at every desktop
width (verified 1280px-2400px — no max-width ever kicks in). Apply
this class to any Wings-page section's content wrapper to guarantee
its cards/timeline/grid hug those same edges instead of sitting
narrower and more centered.
============================================================ */
.dm-section-container {
width: 100%;
margin-left: auto;
margin-right: auto;
box-sizing: border-box;
padding-left: 20px;
padding-right: 20px;
}
@media (max-width: 1024px) {
.dm-section-container {
padding-left: var(--dm-mobile-gutter, 10px);
padding-right: var(--dm-mobile-gutter, 10px);
}
}
/* Doormile Wings hero frame: the generic vendor CSS assigns 32px padding to
.elementor-element-741f56c on all pages; Home's frame is 20px (scoped
to .elementor-61). Scope the same 20px frame to the Doormile Wings hero so
the card alignment matches Home and Solutions exactly. */
@media (min-width: 1025px) {
.wings-hero-page.elementor-element-741f56c,
.af-hero-page.elementor-element-741f56c {
--padding-top: 20px;
--padding-right: 20px;
--padding-bottom: 20px;
--padding-left: 20px;
padding: 20px !important;
}
}
/* Custom premium animations keyframes */ /* Custom premium animations keyframes */
@keyframes shimmer-sweep { @keyframes shimmer-sweep {
0% { 0% {
@@ -1166,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

@@ -1,32 +1,80 @@
import { MetadataRoute } from "next"; import type { MetadataRoute } from "next";
import { blogPosts, SITE_URL } from "@/data/blog";
export const dynamic = "force-static";
const BASE_URL = "https://doormile.com";
export default function sitemap(): MetadataRoute.Sitemap { export default function sitemap(): MetadataRoute.Sitemap {
const staticRoutes = [ const now = new Date();
"",
"/about",
"/contact",
"/blog",
"/air-freight",
"/first-mile-delivery",
"/mid-mile-delivery",
"/last-mile-delivery",
"/ev-logistics",
"/strategy-optimization",
"/how-it-works",
].map((route) => ({
url: `${SITE_URL}${route}`,
lastModified: new Date().toISOString().split("T")[0],
changeFrequency: "weekly" as const,
priority: route === "" ? 1 : 0.8,
}));
const postRoutes = blogPosts.map((post) => ({ const staticRoutes: MetadataRoute.Sitemap = [
url: `${SITE_URL}/blog/${post.slug}`, {
lastModified: post.date, url: `${BASE_URL}/`,
changeFrequency: "monthly" as const, lastModified: now,
priority: 0.6, changeFrequency: "weekly",
})); priority: 1.0,
},
{
url: `${BASE_URL}/how-it-works`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/miletruth`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/solutions`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/doormile-wings`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/about-us`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.7,
},
{
url: `${BASE_URL}/blog`,
lastModified: now,
changeFrequency: "weekly",
priority: 0.7,
},
{
url: `${BASE_URL}/contact`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.6,
},
{
url: `${BASE_URL}/privacy-policy`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.3,
},
{
url: `${BASE_URL}/terms-of-service`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.3,
},
{
url: `${BASE_URL}/cookie-policy`,
lastModified: now,
changeFrequency: "yearly",
priority: 0.3,
},
];
return [...staticRoutes, ...postRoutes]; return staticRoutes;
} }

View File

@@ -65,6 +65,7 @@ export default function BlogSearch() {
setOpen(true); setOpen(true);
}} }}
onFocus={() => setOpen(true)} onFocus={() => setOpen(true)}
aria-expanded={showPanel}
aria-controls="dm-blog-search-results" aria-controls="dm-blog-search-results"
/> />
<span className="dm-blog-search-icon" aria-hidden="true"> <span className="dm-blog-search-icon" aria-hidden="true">

View File

@@ -3,7 +3,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { sendEmailAction } from "@/actions/sendEmail"; import { submitContactForm } from "@/lib/contactForm";
import { ScrollReveal } from "@/animations/Reveal"; import { ScrollReveal } from "@/animations/Reveal";
export default function Footer() { export default function Footer() {
@@ -55,13 +55,13 @@ export default function Footer() {
setFormStatus("submitting"); setFormStatus("submitting");
try { try {
const res = await sendEmailAction(formData); const res = await submitContactForm(formData);
if (res.success) { if (res.success) {
setFormStatus("success"); setFormStatus("success");
setFormData({ fullName: "", email: "", subject: "", message: "" }); setFormData({ fullName: "", email: "", subject: "", message: "" });
} else { } else {
console.error("Failed to submit contact form:", res.error); console.error("Failed to submit contact form");
setFormStatus("error"); setFormStatus("error");
} }
} catch (err) { } catch (err) {
@@ -138,7 +138,7 @@ export default function Footer() {
<svg className="dm-foot-ic" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> <svg className="dm-foot-ic" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z" /> <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z" />
</svg> </svg>
<span>Contact</span> <span>Contect</span>
</div> </div>
</div> </div>
</div> </div>
@@ -337,8 +337,8 @@ export default function Footer() {
{formStatus === "submitting" ? "Sending..." : "Send a message"} {formStatus === "submitting" ? "Sending..." : "Send a message"}
</button> </button>
{formStatus === "success" && ( {formStatus === "success" && (
<div style={{ marginTop: "16px", color: "#4CAF50", fontSize: "14px", fontWeight: "bold" }}> <div style={{ color: "#4caf50", marginTop: "10px", fontSize: "14px" }}>
Message sent successfully! Message sent successfully.
</div> </div>
)} )}
{formStatus === "error" && ( {formStatus === "error" && (
@@ -381,7 +381,7 @@ export default function Footer() {
<div className="elementor-element elementor-element-b5c897d elementor-widget elementor-widget-image" data-id="b5c897d" data-element_type="widget" data-e-type="widget" data-widget_type="image.default"> <div className="elementor-element elementor-element-b5c897d elementor-widget elementor-widget-image" data-id="b5c897d" data-element_type="widget" data-e-type="widget" data-widget_type="image.default">
<div className="elementor-widget-container"> <div className="elementor-widget-container">
<Link href="/"> <Link href="/">
<Image width={748} height={184} src="/images/logo-slogan.png" style={{ width: "280px", height: "auto" }} className="attachment-full size-full wp-image-5851" alt="Doormile Tagline" /> <Image width={300} height={57} src="/images/logo-slogan.png" style={{ width: "280px", height: "auto" }} className="attachment-full size-full wp-image-5851" alt="Doormile Tagline" />
</Link> </Link>
</div> </div>
</div> </div>
@@ -458,6 +458,9 @@ export default function Footer() {
<div className="elementor-element elementor-element-a219b7d elementor-widget elementor-widget-logico_custom_navigation_menu" data-id="a219b7d" data-element_type="widget" data-e-type="widget" data-widget_type="logico_custom_navigation_menu.default"> <div className="elementor-element elementor-element-a219b7d elementor-widget elementor-widget-logico_custom_navigation_menu" data-id="a219b7d" data-element_type="widget" data-e-type="widget" data-widget_type="logico_custom_navigation_menu.default">
<div className="elementor-widget-container"> <div className="elementor-widget-container">
<ul className="logico-custom-menu-widget"> <ul className="logico-custom-menu-widget">
<li>
<Link href="/doormile-wings">Doormile Wings</Link>
</li>
<li> <li>
<Link href="/about-us">About Us</Link> <Link href="/about-us">About Us</Link>
</li> </li>
@@ -487,7 +490,7 @@ export default function Footer() {
<Link href="/privacy-policy">Privacy Policy</Link> <Link href="/privacy-policy">Privacy Policy</Link>
</li> </li>
<li> <li>
<Link href="/terms-of-service">Terms of Service</Link> <Link href="/terms-of-service">Terms Service</Link>
</li> </li>
<li> <li>
<Link href="/cookie-policy">Cookie Policy</Link> <Link href="/cookie-policy">Cookie Policy</Link>

View File

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

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import React, { useRef, useState, useEffect } from "react"; import React, { useEffect, useRef, useState, useSyncExternalStore } from "react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { motion, useMotionValue, useTransform, type MotionValue } from "framer-motion"; import { motion, useMotionValue, useTransform, type MotionValue } from "framer-motion";
import gsap from "gsap"; import gsap from "gsap";

View File

@@ -228,7 +228,7 @@ export default function OfficeMap() {
{OFFICE_LOCATIONS.map((office) => { {OFFICE_LOCATIONS.map((office) => {
const isFocused = focus?.id === office.id; const isFocused = focus?.id === office.id;
const isHovered = hoveredOfficeId === office.id;
return ( return (
<Marker <Marker

View File

@@ -54,7 +54,6 @@ function CameraRig({ progress, framing }: { progress: React.RefObject<number>; f
// construction-time prop on <Canvas>, so update it imperatively here. // construction-time prop on <Canvas>, so update it imperatively here.
useEffect(() => { useEffect(() => {
if ("fov" in camera) { if ("fov" in camera) {
// eslint-disable-next-line react-hooks/immutability
(camera as THREE_PerspectiveCamera).fov = framing.fov; (camera as THREE_PerspectiveCamera).fov = framing.fov;
(camera as THREE_PerspectiveCamera).updateProjectionMatrix(); (camera as THREE_PerspectiveCamera).updateProjectionMatrix();
} }

View File

@@ -1,11 +1,9 @@
import React from "react"; import React from "react";
import { preload } from "react-dom";
export default function AboutHero() { export default function AboutHero() {
preload("/images/about-bg.webp", { as: "image" });
return ( return (
<> <>
<link rel="preload" as="image" href="/images/about-bg.webp" />
<style dangerouslySetInnerHTML={{ __html: ` <style dangerouslySetInnerHTML={{ __html: `
.about-us-hero-content { .about-us-hero-content {
width: 100% !important; width: 100% !important;

View File

@@ -1,11 +1,9 @@
import React from "react"; import React from "react";
import { preload } from "react-dom";
export default function BlogsHero() { export default function BlogsHero() {
preload("/images/home2-banner-1.webp", { as: "image" });
return ( return (
<> <>
<link rel="preload" as="image" href="/images/home2-banner-1.webp" />
<style dangerouslySetInnerHTML={{ __html: ` <style dangerouslySetInnerHTML={{ __html: `
.blogs-hero-title { .blogs-hero-title {
color: #ffffff !important; color: #ffffff !important;

View File

@@ -23,14 +23,6 @@ export default function ConnectedLogistics() {
overflow-wrap: break-word !important; overflow-wrap: break-word !important;
} }
/* Fix text colors for the dark background */
.elementor-element-71c3e1d .logico-title {
color: #ffffff !important;
}
.elementor-element-71c3e1d p {
color: rgba(255, 255, 255, 0.85) !important;
}
/* Make sure paragraphs are responsive on all screen widths */ /* Make sure paragraphs are responsive on all screen widths */
.elementor-element-165dfa5 { .elementor-element-165dfa5 {
width: 100% !important; width: 100% !important;
@@ -206,7 +198,7 @@ export default function ConnectedLogistics() {
<ScrollReveal delay={0.2} duration={0.85} yOffset={25}> <ScrollReveal delay={0.2} duration={0.85} yOffset={25}>
<div className="elementor-element elementor-element-7500280 elementor-widget elementor-widget-logico_heading" data-id="7500280" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default"> <div className="elementor-element elementor-element-7500280 elementor-widget elementor-widget-logico_heading" data-id="7500280" data-element_type="widget" data-e-type="widget" data-widget_type="logico_heading.default">
<div className="elementor-widget-container"> <div className="elementor-widget-container">
<h2 className="logico-title">Smart logistics solutions we deliver for modern supply chains</h2> <h3 className="logico-title">Smart logistics solutions we deliver for modern supply chains</h3>
</div> </div>
</div> </div>
</ScrollReveal> </ScrollReveal>

View File

@@ -1,11 +1,9 @@
import React from "react"; import React from "react";
import { preload } from "react-dom";
export default function ContactsHero() { export default function ContactsHero() {
preload("/images/home2-banner-3.webp", { as: "image" });
return ( return (
<> <>
<link rel="preload" as="image" href="/images/home2-banner-3.webp" />
<style dangerouslySetInnerHTML={{ __html: ` <style dangerouslySetInnerHTML={{ __html: `
.contacts-hero-custom { .contacts-hero-custom {
background-color: #0b0b0b !important; background-color: #0b0b0b !important;

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
@@ -461,10 +483,6 @@ export default function EVSection({
--padding-top: 46px; --padding-top: 46px;
--padding-bottom: 38px; --padding-bottom: 38px;
} }
/* Fix missing font-size for card title that caused it to inherit 80px h2 */
.elementor-element.elementor-element-239afbb .logico-title {
font-size: clamp(28px, 3.5vw, 40px) !important;
}
.elementor-element.elementor-element-5aea22e { .elementor-element.elementor-element-5aea22e {
--min-height: 38px; --min-height: 38px;
--padding-top: 14px; --padding-top: 14px;
@@ -599,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 {
@@ -756,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;
} }
@@ -871,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).
@@ -973,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;
} }
@@ -1045,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; } }
@@ -1056,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); }
@@ -1292,7 +1342,7 @@ export default function EVSection({
data-widget_type="logico_heading.default" data-widget_type="logico_heading.default"
> >
<div className="elementor-widget-container"> <div className="elementor-widget-container">
<h2 className="logico-title">{cardTitle}</h2> <h5 className="logico-title">{cardTitle}</h5>
</div> </div>
</div> </div>
<div <div
@@ -1335,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">
@@ -1349,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 */}
@@ -1357,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
@@ -1405,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">
@@ -1438,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>

Some files were not shown because too many files have changed in this diff Show More