Compare commits
1 Commits
cursor/air
...
71875ea20f
| Author | SHA1 | Date | |
|---|---|---|---|
| 71875ea20f |
3
.gitignore
vendored
@@ -42,3 +42,6 @@ next-env.d.ts
|
||||
|
||||
# Local backups of pre-optimized GLB source models (not served)
|
||||
.glb-originals/
|
||||
|
||||
# Scraped demo assets
|
||||
demo.artureanec.com/
|
||||
|
||||
@@ -2,19 +2,10 @@ import type { NextConfig } from "next";
|
||||
import path from "path";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/air-freight",
|
||||
destination: "/doormile-wings",
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
turbopack: {
|
||||
root: path.resolve(__dirname),
|
||||
},
|
||||
output: "export",
|
||||
// output: "export" removed to allow server actions
|
||||
// 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
|
||||
// the heavy 32MB scene mid-initialization — the context is lost ("THREE.
|
||||
@@ -24,7 +15,6 @@ const nextConfig: NextConfig = {
|
||||
// any other page's runtime behavior.
|
||||
reactStrictMode: false,
|
||||
images: {
|
||||
unoptimized: true,
|
||||
formats: ["image/avif", "image/webp"],
|
||||
},
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 492 KiB |
|
Before Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 476 KiB |
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 5.7 MiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 7.8 MiB |
|
Before Width: | Height: | Size: 6.3 MiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 3.4 MiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 8.6 MiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 999 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 489 KiB |
|
Before Width: | Height: | Size: 11 MiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 6.7 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 321 KiB |
|
Before Width: | Height: | Size: 395 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 546 KiB |
|
Before Width: | Height: | Size: 421 KiB |
|
Before Width: | Height: | Size: 8.0 MiB |
|
Before Width: | Height: | Size: 10 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 3.8 MiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 5.9 MiB |
|
Before Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
49
scripts/fix_preload.mjs
Normal file
@@ -0,0 +1,49 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,52 +2,61 @@
|
||||
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export async function sendContactEmail(data: {
|
||||
interface ContactFormData {
|
||||
fullName: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
}) {
|
||||
}
|
||||
|
||||
export async function sendEmailAction(data: ContactFormData) {
|
||||
const { fullName, email, subject, message } = data;
|
||||
|
||||
if (!fullName || !email || !subject || !message) {
|
||||
return { success: false, error: "All fields are required." };
|
||||
if (!fullName || !email || !message) {
|
||||
return { success: false, error: "Name, email, and message 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({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: Number(process.env.SMTP_PORT) || 465,
|
||||
secure: true,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
pass: password,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
from: `"${fullName}" <${process.env.SMTP_USER}>`,
|
||||
to: process.env.CONTACT_RECEIVER,
|
||||
replyTo: email,
|
||||
subject: `New Contact Form Submission: ${subject}`,
|
||||
from: `"${fullName}" <${process.env.SMTP_USER}>`,
|
||||
to: process.env.CONTACT_RECEIVER,
|
||||
replyTo: email,
|
||||
subject: `New Contact Form Submission: ${subject || "No Subject"}`,
|
||||
html: `
|
||||
<div style="font-family: sans-serif; color: #333; max-width: 600px; margin: 0 auto; border: 1px solid #eee; padding: 20px; border-radius: 8px;">
|
||||
<h2 style="color: #c01227; border-bottom: 2px solid #c01227; padding-bottom: 10px; margin-top: 0;">New Contact Form Submission</h2>
|
||||
<div style="font-family: sans-serif; color: #333;">
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${fullName}</p>
|
||||
<p><strong>Email:</strong> ${email}</p>
|
||||
<p><strong>Subject:</strong> ${subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<blockquote style="background: #f9f9f9; padding: 15px; border-left: 4px solid #c01227; margin: 0; font-style: italic;">
|
||||
${message.replace(/\n/g, "<br />")}
|
||||
<blockquote style="background: #f9f9f9; padding: 15px; border-left: 4px solid #ccc;">
|
||||
${message.replace(/\n/g, "<br>")}
|
||||
</blockquote>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("Email send error:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to send the email. Please try again later.";
|
||||
return { success: false, error: errorMessage };
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return { success: false, error: errorMessage || "Failed to send the email. Please try again later." };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function AboutUsPage() {
|
||||
<AboutHero />
|
||||
<TheDoormileWay />
|
||||
<EVLogisticSection />
|
||||
{/* <WomenSection /> */}
|
||||
<WomenSection />
|
||||
<CompetitiveEdge />
|
||||
<IntelligenceGrid />
|
||||
<OurTeam />
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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");
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
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'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>
|
||||
);
|
||||
}
|
||||
@@ -10,27 +10,6 @@ html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
SITE-WIDE STICKY FIX
|
||||
The legacy Elementor build sets `.body-container { overflow: hidden }`
|
||||
(public/css/site.css, generated — do not edit there). Any ancestor
|
||||
with non-visible overflow becomes the containing block for
|
||||
`position: sticky` descendants; since `.body-container` itself never
|
||||
actually scrolls (the real scrollbar lives on <body>), that made every
|
||||
`position: sticky` element on the site inert — it just scrolled away
|
||||
in normal flow instead of pinning.
|
||||
Note: setting overflow-x:hidden paired with overflow-y:visible gets
|
||||
silently coerced to overflow-y:auto by the CSS spec (an "auto" value on
|
||||
one axis when the other is non-visible) — which ALSO still counts as a
|
||||
scroll container and breaks sticky the same way. So this must be fully
|
||||
visible on both axes. <body> already has overflow-x: hidden site-wide
|
||||
(from the same legacy stylesheet), so horizontal bleed is still safely
|
||||
clipped there — this rule doesn't need to repeat it.
|
||||
============================================================ */
|
||||
.body-container {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
GLOBAL SPACING SYSTEM
|
||||
A single shared vertical-rhythm scale so every section uses
|
||||
@@ -174,21 +153,16 @@ body {
|
||||
#masthead .elementor-element.elementor-element-0b7bf6f > .elementor-widget-container {
|
||||
padding: 0 42px !important;
|
||||
border-radius: 22px !important;
|
||||
height: 58px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
#masthead .elementor-element.elementor-element-0b7bf6f .header-menu-container .main-menu > li > a {
|
||||
padding-top: 6px !important;
|
||||
padding-bottom: 6px !important;
|
||||
padding-top: 15px !important;
|
||||
padding-bottom: 16px !important;
|
||||
padding-left: 14px !important;
|
||||
padding-right: 14px !important;
|
||||
font-size: 15px !important;
|
||||
line-height: 1.2 !important;
|
||||
white-space: nowrap !important;
|
||||
min-height: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu {
|
||||
@@ -205,11 +179,7 @@ body {
|
||||
}
|
||||
|
||||
#masthead .elementor-element.elementor-element-466de1b.dm-header-scrolled {
|
||||
top: 15px !important;
|
||||
left: 20px !important;
|
||||
right: 20px !important;
|
||||
border-radius: 24px !important;
|
||||
width: auto !important;
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -994,49 +964,6 @@ body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
SHARED PAGE CONTENT CONTAINER
|
||||
Matches the Hero/Header/Footer's real rendered OUTER edge, not the
|
||||
Footer's narrower inner text column. The Hero card
|
||||
(.owl-stage-outer, via .wings-hero-page.elementor-element-741f56c)
|
||||
and the Footer's outer band (.elementor-element-b29b8fc) both sit a
|
||||
fixed, uncapped 20px in from the viewport edge at every desktop
|
||||
width (verified 1280px-2400px — no max-width ever kicks in). Apply
|
||||
this class to any Wings-page section's content wrapper to guarantee
|
||||
its cards/timeline/grid hug those same edges instead of sitting
|
||||
narrower and more centered.
|
||||
============================================================ */
|
||||
.dm-section-container {
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
box-sizing: border-box;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.dm-section-container {
|
||||
padding-left: var(--dm-mobile-gutter, 10px);
|
||||
padding-right: var(--dm-mobile-gutter, 10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Doormile Wings hero frame: the generic vendor CSS assigns 32px padding to
|
||||
.elementor-element-741f56c on all pages; Home's frame is 20px (scoped
|
||||
to .elementor-61). Scope the same 20px frame to the Doormile Wings hero so
|
||||
the card alignment matches Home and Solutions exactly. */
|
||||
@media (min-width: 1025px) {
|
||||
.wings-hero-page.elementor-element-741f56c,
|
||||
.af-hero-page.elementor-element-741f56c {
|
||||
--padding-top: 20px;
|
||||
--padding-right: 20px;
|
||||
--padding-bottom: 20px;
|
||||
--padding-left: 20px;
|
||||
padding: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom premium animations keyframes */
|
||||
@keyframes shimmer-sweep {
|
||||
0% {
|
||||
@@ -1239,4 +1166,3 @@ body {
|
||||
margin-top: var(--space-section) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import MileTruthHero from "../../components/sections/MileTruthHero";
|
||||
import Workflow1 from "../../components/sections/Workflow1";
|
||||
import Workflow2 from "../../components/sections/Workflow2";
|
||||
import Workflow4 from "../../components/sections/Workflow4";
|
||||
import Workflow3Lazy from "../../components/sections/Workflow3Lazy";
|
||||
|
||||
export const metadata = {
|
||||
@@ -19,7 +18,6 @@ export default function MileTruthPage() {
|
||||
<MileTruthHero />
|
||||
<Workflow1 />
|
||||
<Workflow2 />
|
||||
<Workflow4 />
|
||||
<Workflow3Lazy />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,80 +1,32 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
const BASE_URL = "https://doormile.com";
|
||||
import { MetadataRoute } from "next";
|
||||
import { blogPosts, SITE_URL } from "@/data/blog";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const now = new Date();
|
||||
const staticRoutes = [
|
||||
"",
|
||||
"/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 staticRoutes: MetadataRoute.Sitemap = [
|
||||
{
|
||||
url: `${BASE_URL}/`,
|
||||
lastModified: now,
|
||||
changeFrequency: "weekly",
|
||||
priority: 1.0,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/how-it-works`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/miletruth`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/solutions`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/doormile-wings`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/about-us`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/blog`,
|
||||
lastModified: now,
|
||||
changeFrequency: "weekly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/contact`,
|
||||
lastModified: now,
|
||||
changeFrequency: "yearly",
|
||||
priority: 0.6,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/privacy-policy`,
|
||||
lastModified: now,
|
||||
changeFrequency: "yearly",
|
||||
priority: 0.3,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/terms-of-service`,
|
||||
lastModified: now,
|
||||
changeFrequency: "yearly",
|
||||
priority: 0.3,
|
||||
},
|
||||
{
|
||||
url: `${BASE_URL}/cookie-policy`,
|
||||
lastModified: now,
|
||||
changeFrequency: "yearly",
|
||||
priority: 0.3,
|
||||
},
|
||||
];
|
||||
const postRoutes = blogPosts.map((post) => ({
|
||||
url: `${SITE_URL}/blog/${post.slug}`,
|
||||
lastModified: post.date,
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.6,
|
||||
}));
|
||||
|
||||
return staticRoutes;
|
||||
return [...staticRoutes, ...postRoutes];
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ export default function BlogSearch() {
|
||||
setOpen(true);
|
||||
}}
|
||||
onFocus={() => setOpen(true)}
|
||||
aria-expanded={showPanel}
|
||||
aria-controls="dm-blog-search-results"
|
||||
/>
|
||||
<span className="dm-blog-search-icon" aria-hidden="true">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { submitContactForm } from "@/lib/contactForm";
|
||||
import { sendEmailAction } from "@/actions/sendEmail";
|
||||
import { ScrollReveal } from "@/animations/Reveal";
|
||||
|
||||
export default function Footer() {
|
||||
@@ -55,13 +55,13 @@ export default function Footer() {
|
||||
|
||||
setFormStatus("submitting");
|
||||
try {
|
||||
const res = await submitContactForm(formData);
|
||||
const res = await sendEmailAction(formData);
|
||||
|
||||
if (res.success) {
|
||||
setFormStatus("success");
|
||||
setFormData({ fullName: "", email: "", subject: "", message: "" });
|
||||
} else {
|
||||
console.error("Failed to submit contact form");
|
||||
console.error("Failed to submit contact form:", res.error);
|
||||
setFormStatus("error");
|
||||
}
|
||||
} 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">
|
||||
<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>
|
||||
<span>Contect</span>
|
||||
<span>Contact</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -337,8 +337,8 @@ export default function Footer() {
|
||||
{formStatus === "submitting" ? "Sending..." : "Send a message"}
|
||||
</button>
|
||||
{formStatus === "success" && (
|
||||
<div style={{ color: "#4caf50", marginTop: "10px", fontSize: "14px" }}>
|
||||
Message sent successfully.
|
||||
<div style={{ marginTop: "16px", color: "#4CAF50", fontSize: "14px", fontWeight: "bold" }}>
|
||||
Message sent successfully!
|
||||
</div>
|
||||
)}
|
||||
{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-widget-container">
|
||||
<Link href="/">
|
||||
<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" />
|
||||
<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" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -458,9 +458,6 @@ export default function Footer() {
|
||||
<div className="elementor-element elementor-element-a219b7d elementor-widget elementor-widget-logico_custom_navigation_menu" data-id="a219b7d" data-element_type="widget" data-e-type="widget" data-widget_type="logico_custom_navigation_menu.default">
|
||||
<div className="elementor-widget-container">
|
||||
<ul className="logico-custom-menu-widget">
|
||||
<li>
|
||||
<Link href="/doormile-wings">Doormile Wings</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/about-us">About Us</Link>
|
||||
</li>
|
||||
@@ -490,7 +487,7 @@ export default function Footer() {
|
||||
<Link href="/privacy-policy">Privacy Policy</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/terms-of-service">Terms Service</Link>
|
||||
<Link href="/terms-of-service">Terms of Service</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/cookie-policy">Cookie Policy</Link>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* Menu open/close + sidebar state is read from HeaderUIProvider so BodyOverlay (sibling at body level) can react.
|
||||
*/
|
||||
|
||||
import { KeyboardEvent, FocusEvent, MouseEvent, useEffect, useState } from "react";
|
||||
import { MouseEvent, useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
@@ -19,11 +19,13 @@ const CURRENT_PAGE_MAP: Record<string, string> = {
|
||||
"/how-it-works": "how-it-works",
|
||||
"/miletruth": "miletruth",
|
||||
"/solutions": "solutions",
|
||||
"/doormile-wings": "doormile-wings",
|
||||
"/empowerment": "entarpreneurship",
|
||||
"/express": "express",
|
||||
"/about": "about",
|
||||
"/about-us": "about",
|
||||
"/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)
|
||||
@@ -31,13 +33,13 @@ const CURRENT_PAGE_ALIASES: Record<string, string[]> = {
|
||||
home: ["home"],
|
||||
"how-it-works": ["how-it-works", "how-its-works"],
|
||||
miletruth: ["miletruth"],
|
||||
// Solutions is now a plain top-level link — no child pages own its active state.
|
||||
solutions: ["solutions"],
|
||||
"doormile-wings": ["doormile-wings"],
|
||||
entarpreneurship: ["entarpreneurship"],
|
||||
about: ["about", "about-us", "women"],
|
||||
blogs: ["blogs"],
|
||||
// Company is the dropdown parent for About + Blogs + How It Works — active if any is.
|
||||
company: ["blogs", "how-it-works", "about"],
|
||||
contact: ["contact"],
|
||||
"privacy-policy": ["privacy-policy"],
|
||||
"terms-of-service": ["terms-of-service"],
|
||||
"cookie-policy": ["cookie-policy"],
|
||||
};
|
||||
|
||||
export default function Header() {
|
||||
@@ -45,7 +47,10 @@ export default function Header() {
|
||||
const { isMenuOpen, isSidebarOpen, toggleMenu, toggleSidebar, closeAll } = useHeaderUI();
|
||||
|
||||
const isHome = pathname === "/";
|
||||
const currentPage = CURRENT_PAGE_MAP[pathname] ?? "";
|
||||
let currentPage = CURRENT_PAGE_MAP[pathname] ?? "";
|
||||
if (pathname.startsWith("/blog/")) {
|
||||
currentPage = "blogs";
|
||||
}
|
||||
|
||||
// Mirror of header.php $dm_header_active (lines 45-47)
|
||||
const dmHeaderActive = (key: string) =>
|
||||
@@ -67,7 +72,7 @@ export default function Header() {
|
||||
targetId: string,
|
||||
shouldCloseMenu = false,
|
||||
) => {
|
||||
if (shouldCloseMenu) closeMobileMenu();
|
||||
if (shouldCloseMenu) closeAll();
|
||||
|
||||
if (pathname !== targetPath) return;
|
||||
|
||||
@@ -85,23 +90,6 @@ export default function Header() {
|
||||
const [visibleScrolled, setVisibleScrolled] = useState(false);
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
// Company dropdown (desktop) / accordion (mobile). Local to Header —
|
||||
// unlike isMenuOpen/isSidebarOpen (HeaderUIProvider), no sibling component
|
||||
// needs to read this.
|
||||
const [isDesktopCompanyOpen, setDesktopCompanyOpen] = useState(false);
|
||||
const [isMobileCompanyOpen, setMobileCompanyOpen] = useState(false);
|
||||
|
||||
// Collapse the accordion whenever the off-canvas menu closes, so it isn't
|
||||
// left expanded the next time the menu opens.
|
||||
const closeMobileMenu = () => {
|
||||
setMobileCompanyOpen(false);
|
||||
closeAll();
|
||||
};
|
||||
const toggleMobileMenu = () => {
|
||||
setMobileCompanyOpen(false);
|
||||
toggleMenu();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handle = requestAnimationFrame(() => {
|
||||
setVisibleScrolled(true);
|
||||
@@ -433,8 +421,8 @@ export default function Header() {
|
||||
<Link data-elementor-open-lightbox="" className="elementor-clickable" href="/">
|
||||
<div className="hfe-site-logo-set">
|
||||
<div className="hfe-site-logo-container">
|
||||
<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={400} height={76} className="hfe-site-logo-img logo-mobile elementor-animation-" src="/images/doormile-black.png" alt="doormile-logo" priority />
|
||||
<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={748} height={100} className="hfe-site-logo-img logo-mobile elementor-animation-" src="/images/doormile-black.png" alt="doormile-logo" priority />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -460,86 +448,25 @@ export default function Header() {
|
||||
<li id="menu-item-10508" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10508${dmHeaderActive("home")}`}>
|
||||
<Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home")}>Home</Link>
|
||||
</li>
|
||||
<li id="menu-item-10509" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("doormile-wings")}`}>
|
||||
<Link href="/doormile-wings" prefetch>Wings</Link>
|
||||
<li id="menu-item-10509" className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}>
|
||||
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works")}>How It Works</Link>
|
||||
</li>
|
||||
<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™ AI</Link>
|
||||
</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 id="menu-item-10511" className={`menu-item menu-item-type-custom menu-item-10511${dmHeaderActive("solutions")}`}>
|
||||
<Link href="/solutions#solutions" onClick={(event) => handleNavClick(event, "/solutions", "solutions")}>Solutions</Link>
|
||||
</li>
|
||||
<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 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-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 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>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="menu-trigger" data-id="0b7bf6f" onClick={toggleMobileMenu}>
|
||||
<div className="menu-trigger" data-id="0b7bf6f" onClick={toggleMenu}>
|
||||
<span className="menu-trigger-icon">
|
||||
<span className="hamburger">
|
||||
<span></span>
|
||||
@@ -559,7 +486,7 @@ export default function Header() {
|
||||
<div className="header-icon login-logout">
|
||||
<a href="#" title="Login/Register" className="link-login"></a>
|
||||
</div>
|
||||
<div className="header-icon menu-close" onClick={closeMobileMenu}>
|
||||
<div className="header-icon menu-close" onClick={closeAll}>
|
||||
<span className="menu-close-icon"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -569,53 +496,20 @@ export default function Header() {
|
||||
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10508${dmHeaderActive("home")}`}>
|
||||
<Link href="/#home" onClick={(event) => handleNavClick(event, "/", "home", true)}>Home</Link>
|
||||
</li>
|
||||
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("doormile-wings")}`}>
|
||||
<Link href="/doormile-wings" prefetch onClick={closeMobileMenu}>Wings</Link>
|
||||
<li className={`menu-item menu-item-type-custom menu-item-object-custom menu-item-10509${dmHeaderActive("how-it-works")}`}>
|
||||
<Link href="/how-it-works#how-it-works" onClick={(event) => handleNavClick(event, "/how-it-works", "how-it-works", true)}>How It Works</Link>
|
||||
</li>
|
||||
<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™ AI</Link>
|
||||
</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 className={`menu-item menu-item-type-custom menu-item-10511${dmHeaderActive("solutions")}`}>
|
||||
<Link href="/solutions#solutions" onClick={(event) => handleNavClick(event, "/solutions", "solutions", true)}>Solutions</Link>
|
||||
</li>
|
||||
<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 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-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 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>
|
||||
</nav>
|
||||
@@ -973,281 +867,6 @@ export default function Header() {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cline x1='5' y1='5' x2='19' y2='19'/%3E%3Cline x1='19' y1='5' x2='5' y2='19'/%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cline x1='5' y1='5' x2='19' y2='19'/%3E%3Cline x1='19' y1='5' x2='5' y2='19'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* ── Company dropdown (desktop) / accordion (mobile) ──
|
||||
Company (Blogs + How It Works) is a non-navigating parent —
|
||||
its trigger is a plain anchor with no href target, only a
|
||||
dropdown toggle. Class names below (.dm-solutions-dropdown,
|
||||
.dm-mobile-solutions*) are unchanged from when this same
|
||||
component/markup belonged to Solutions, reusing site.css's dead
|
||||
.menu-item-has-children chevron (fontello glyph, already
|
||||
coloured white for this exact header widget) instead of
|
||||
inventing a new arrow. Desktop opens on hover/focus; mobile is a
|
||||
height-collapsing accordion controlled by the chevron button. */
|
||||
@media (min-width: 1025px) {
|
||||
#masthead .header-menu-container nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
gap: 16px !important;
|
||||
margin: 0 !important;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li > a {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
min-height: 48px;
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
min-width: 360px;
|
||||
height: 24px;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 34px);
|
||||
width: 240px;
|
||||
min-width: 240px;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
list-style: none;
|
||||
background: linear-gradient(180deg, rgba(31, 31, 31, 0.98) 0%, rgba(18, 18, 18, 0.98) 100%);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.38), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.09);
|
||||
border-radius: 18px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
/* .main-menu li:not(:last-of-type) (site.css) is a descendant
|
||||
selector that also matches these nested <li>s, pushing every
|
||||
row but the last 12px to the right. Cancel it here. */
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li + li {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
border-radius: 10px;
|
||||
/* Hardcoded rather than var(--logico-dark-text-color): that
|
||||
variable is only overridden to white inside a few unrelated
|
||||
scoped contexts (e.g. #side-panel-2f31137) and otherwise
|
||||
falls back to the sitewide light-theme value (#111111),
|
||||
which would be invisible on this dark glass panel. */
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-family: var(--font-manrope), "Manrope", sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
letter-spacing: normal;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.25s ease, color 0.25s ease, padding-left 0.25s ease;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:before {
|
||||
content: "";
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
margin-left: -16px;
|
||||
flex: 0 0 11px;
|
||||
background-color: #c01227;
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12 12 4'/%3E%3Cpath d='M5 4h7v7'/%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12 12 4'/%3E%3Cpath d='M5 4h7v7'/%3E%3C/svg%3E");
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
transition: opacity 0.25s ease-in-out, transform 0.25s ease-in-out, background-color 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:hover,
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:focus-visible {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
color: #ffffff;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:hover:before,
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li > a:focus-visible:before {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
background-color: #c01227;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children .dm-solutions-dropdown li.dm-child-active > a {
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"] ~ .dm-solutions-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transform: translate(-50%, 24px);
|
||||
}
|
||||
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a[aria-expanded="true"]:after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Outlines are globally suppressed for .main-menu>li>a above;
|
||||
restore a visible focus ring scoped only to this trigger. */
|
||||
#masthead .header-menu-container .main-menu > li.menu-item-has-children > a:focus-visible {
|
||||
outline: 2px solid var(--logico-dark-text-color, #ffffff);
|
||||
outline-offset: 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-row {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-row > a {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
padding: 0.75em 0;
|
||||
color: var(--logico-default-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex: 0 0 44px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--logico-default-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-toggle:focus-visible {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-accordion-chevron {
|
||||
display: inline-block;
|
||||
width: 0.7em;
|
||||
height: 0.7em;
|
||||
flex: 0 0 auto;
|
||||
background-color: currentColor;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions.dm-accordion-open .dm-accordion-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 18px;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions.dm-accordion-open .dm-mobile-solutions-panel {
|
||||
max-height: 160px;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* .mobile-header-menu-container ul.main-menu>li>a (site.css) only
|
||||
matches direct children of .main-menu, so these nested links
|
||||
fall through to the sitewide generic underlined-anchor default.
|
||||
Restore the plain sibling-item look explicitly. */
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li > a {
|
||||
display: block;
|
||||
padding: 0.35em 0;
|
||||
color: var(--logico-default-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* A vendor li:before rule (checkmark bullet, likely intended for a
|
||||
feature-list style elsewhere) leaks through here since the reset
|
||||
at .mobile-header-menu-container ul.main-menu>li:before only
|
||||
matches direct children of .main-menu, not this nested li.
|
||||
Suppress it on both the <li> and <a> — these child rows aren't
|
||||
top-level nav items and shouldn't carry any bullet/indicator. */
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li:before,
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li > a:before {
|
||||
content: none !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#masthead .mobile-header-menu-container .dm-mobile-solutions-panel > li.dm-child-active > a {
|
||||
color: var(--logico-light-text-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { motion, useMotionValue, useTransform, type MotionValue } from "framer-motion";
|
||||
import gsap from "gsap";
|
||||
|
||||
@@ -228,7 +228,7 @@ export default function OfficeMap() {
|
||||
|
||||
{OFFICE_LOCATIONS.map((office) => {
|
||||
const isFocused = focus?.id === office.id;
|
||||
const isHovered = hoveredOfficeId === office.id;
|
||||
|
||||
|
||||
return (
|
||||
<Marker
|
||||
|
||||
@@ -54,6 +54,7 @@ function CameraRig({ progress, framing }: { progress: React.RefObject<number>; f
|
||||
// construction-time prop on <Canvas>, so update it imperatively here.
|
||||
useEffect(() => {
|
||||
if ("fov" in camera) {
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
(camera as THREE_PerspectiveCamera).fov = framing.fov;
|
||||
(camera as THREE_PerspectiveCamera).updateProjectionMatrix();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from "react";
|
||||
import { preload } from "react-dom";
|
||||
|
||||
export default function AboutHero() {
|
||||
preload("/images/about-bg.webp", { as: "image" });
|
||||
return (
|
||||
<>
|
||||
<link rel="preload" as="image" href="/images/about-bg.webp" />
|
||||
|
||||
<style dangerouslySetInnerHTML={{ __html: `
|
||||
.about-us-hero-content {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from "react";
|
||||
import { preload } from "react-dom";
|
||||
|
||||
export default function BlogsHero() {
|
||||
preload("/images/home2-banner-1.webp", { as: "image" });
|
||||
return (
|
||||
<>
|
||||
<link rel="preload" as="image" href="/images/home2-banner-1.webp" />
|
||||
|
||||
<style dangerouslySetInnerHTML={{ __html: `
|
||||
.blogs-hero-title {
|
||||
color: #ffffff !important;
|
||||
|
||||
@@ -23,6 +23,14 @@ export default function ConnectedLogistics() {
|
||||
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 */
|
||||
.elementor-element-165dfa5 {
|
||||
width: 100% !important;
|
||||
@@ -198,7 +206,7 @@ export default function ConnectedLogistics() {
|
||||
<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-widget-container">
|
||||
<h3 className="logico-title">Smart logistics solutions we deliver for modern supply chains</h3>
|
||||
<h2 className="logico-title">Smart logistics solutions we deliver for modern supply chains</h2>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from "react";
|
||||
import { preload } from "react-dom";
|
||||
|
||||
export default function ContactsHero() {
|
||||
preload("/images/home2-banner-3.webp", { as: "image" });
|
||||
return (
|
||||
<>
|
||||
<link rel="preload" as="image" href="/images/home2-banner-3.webp" />
|
||||
|
||||
<style dangerouslySetInnerHTML={{ __html: `
|
||||
.contacts-hero-custom {
|
||||
background-color: #0b0b0b !important;
|
||||
|
||||
@@ -42,9 +42,6 @@ export interface EVStat {
|
||||
decimals?: number;
|
||||
suffix: string;
|
||||
label: string;
|
||||
/** Optional static text override — when set, renders this text instead of
|
||||
* an animated CountUp (for non-numeric KPI concepts, e.g. "QUBO"). */
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/** Accent theming for the dashboard column so it matches the scene on the left
|
||||
@@ -86,19 +83,10 @@ interface EVSectionProps {
|
||||
* static homepage feature list. The bottom stat bar is dropped in this mode
|
||||
* to avoid repeating the metrics. */
|
||||
metrics?: EVStat[];
|
||||
/** Optional distinct copy for the bottom `.evnd__bar` strip in dashboard
|
||||
* mode. Defaults to reusing `metrics` (WF1/WF2 behaviour) when omitted. */
|
||||
barStats?: EVStat[];
|
||||
/** Heading above the dashboard, e.g. "PERFORMANCE INSIGHT". */
|
||||
cardsHeading?: string;
|
||||
/** Accent theme for the dashboard column (defaults to the section red). */
|
||||
cardsTheme?: EVCardsTheme;
|
||||
/** Dashboard system label, top-right of the header row. Defaults to "AI ENGINE". */
|
||||
sysLabel?: string;
|
||||
/** Extra class appended to `.evnd__imgwrap`, for workflow-specific media
|
||||
* sizing overrides (e.g. video object-fit) defined in the caller's own
|
||||
* scoped <style> tag rather than here. */
|
||||
mediaClassName?: string;
|
||||
}
|
||||
|
||||
/* ---- Default content = original homepage EV Logistics section ---- */
|
||||
@@ -272,13 +260,11 @@ function DashboardPanel({
|
||||
features,
|
||||
heading,
|
||||
theme,
|
||||
sysLabel = "AI ENGINE",
|
||||
}: {
|
||||
metrics: EVStat[];
|
||||
features: EVFeature[];
|
||||
heading?: string;
|
||||
theme?: EVCardsTheme;
|
||||
sysLabel?: string;
|
||||
}) {
|
||||
const themeVars = theme
|
||||
? ({
|
||||
@@ -301,7 +287,7 @@ function DashboardPanel({
|
||||
<span className="evnd__dash-livedot" />
|
||||
{heading}
|
||||
</span>
|
||||
<span className="evnd__dash-sys">{sysLabel}</span>
|
||||
<span className="evnd__dash-sys">AI ENGINE</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -309,16 +295,12 @@ function DashboardPanel({
|
||||
<div className="evnd__dash-kpis">
|
||||
{metrics.map((m) => (
|
||||
<div className="evnd__kpi" key={m.label}>
|
||||
{m.text ? (
|
||||
<b className="evnd__kpi-val evnd__kpi-val--text">{m.text}</b>
|
||||
) : (
|
||||
<CountUp
|
||||
value={m.value}
|
||||
decimals={m.decimals}
|
||||
suffix={m.suffix}
|
||||
className="evnd__kpi-val"
|
||||
/>
|
||||
)}
|
||||
<CountUp
|
||||
value={m.value}
|
||||
decimals={m.decimals}
|
||||
suffix={m.suffix}
|
||||
className="evnd__kpi-val"
|
||||
/>
|
||||
<span className="evnd__kpi-label">{m.label}</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -360,15 +342,11 @@ export default function EVSection({
|
||||
ariaLabel,
|
||||
mediaSlot,
|
||||
metrics,
|
||||
barStats,
|
||||
cardsHeading,
|
||||
cardsTheme,
|
||||
sysLabel,
|
||||
mediaClassName,
|
||||
}: EVSectionProps) {
|
||||
const bannerRef = useRef<HTMLDivElement>(null);
|
||||
const useDashboard = !!metrics && metrics.length > 0;
|
||||
const isVideo = image.endsWith(".mp4");
|
||||
|
||||
/* In dashboard mode the workflow's accent theme is applied at the .evnd
|
||||
section level so the shared animated network backdrop (below) is tinted to
|
||||
@@ -483,6 +461,10 @@ export default function EVSection({
|
||||
--padding-top: 46px;
|
||||
--padding-bottom: 38px;
|
||||
}
|
||||
/* Fix missing font-size for card title that caused it to inherit 80px h2 */
|
||||
.elementor-element.elementor-element-239afbb .logico-title {
|
||||
font-size: clamp(28px, 3.5vw, 40px) !important;
|
||||
}
|
||||
.elementor-element.elementor-element-5aea22e {
|
||||
--min-height: 38px;
|
||||
--padding-top: 14px;
|
||||
@@ -617,31 +599,13 @@ export default function EVSection({
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
/* ---- Heading → content gap ----
|
||||
Deliberately set here (on the wrapper div) rather than as
|
||||
margin-bottom on .evnd__title (the <h2>): a global site.css rule
|
||||
(.logico-front-end h2:not([class*="logico-title-h"]):last-child)
|
||||
outweighs .evnd__title's own specificity and zeroes any bottom
|
||||
margin placed on the heading itself, since the h2 is always the
|
||||
last child of .evnd__heading. Setting it here sidesteps that
|
||||
collision and gives every workflow section + Fleet Strategy one
|
||||
shared, consistent gap. */
|
||||
.evnd__heading {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
@media (max-width: 991px) and (min-width: 768px) {
|
||||
.evnd__heading {
|
||||
margin-bottom: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.evnd__title {
|
||||
color: #fff !important;
|
||||
font-weight: 800 !important;
|
||||
font-size: clamp(32px, 3.8vw, 48px) !important;
|
||||
line-height: 1.15 !important;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0;
|
||||
margin: 0 0 36px 0;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.evnd__title {
|
||||
@@ -792,7 +756,7 @@ export default function EVSection({
|
||||
.evnd-feature__desc {
|
||||
color: rgba(255,255,255,0.65) !important;
|
||||
font-weight: 400 !important;
|
||||
font-size: 16px !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 1.6 !important;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -907,13 +871,6 @@ export default function EVSection({
|
||||
font-size: 12.5px;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
/* Non-numeric KPI concepts (EVStat.text) — same gradient treatment as
|
||||
the counted values, sized down so short codes/words don't inherit
|
||||
the numeric clamp() meant for 2-3 digit figures. */
|
||||
.evnd__kpi-val--text {
|
||||
font-size: clamp(18px, 1.6vw, 22px) !important;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Large-screen layout balance (WF1/WF2 dashboard only, ≥1440px).
|
||||
@@ -1016,8 +973,8 @@ export default function EVSection({
|
||||
.evnd__dfeat-desc {
|
||||
color: rgba(255,255,255,0.6) !important;
|
||||
font-weight: 400 !important;
|
||||
font-size: 16px !important;
|
||||
line-height: 1.55 !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.45 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
@@ -1088,14 +1045,6 @@ export default function EVSection({
|
||||
text-transform: none;
|
||||
opacity: 0.9;
|
||||
}
|
||||
/* Non-numeric bar values (EVStat.text) — the bar's numeric clamp()
|
||||
(up to 56px) is too large for words/phrases; size down and let
|
||||
longer phrases (e.g. "Cost + Distance + SLA") wrap cleanly. */
|
||||
.evnd__bar-val--text {
|
||||
font-size: clamp(16px, 1.8vw, 22px);
|
||||
line-height: 1.25;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@keyframes evndGlow { 0%,100% { opacity: 0.75; } 50% { opacity: 1; } }
|
||||
|
||||
@@ -1107,6 +1056,7 @@ export default function EVSection({
|
||||
@media (max-width: 991px) {
|
||||
.evnd { padding: 48px 32px 56px; }
|
||||
.evnd__grid { grid-template-columns: 1fr; gap: 40px; }
|
||||
.evnd__title { margin-bottom: 28px; }
|
||||
.evnd__features { gap: 14px; }
|
||||
/* Stacked: KPI row can spread to 4-up since it now has full width. */
|
||||
.evnd__dash-kpis { grid-template-columns: repeat(4, 1fr); }
|
||||
@@ -1342,7 +1292,7 @@ export default function EVSection({
|
||||
data-widget_type="logico_heading.default"
|
||||
>
|
||||
<div className="elementor-widget-container">
|
||||
<h5 className="logico-title">{cardTitle}</h5>
|
||||
<h2 className="logico-title">{cardTitle}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -1385,7 +1335,7 @@ export default function EVSection({
|
||||
|
||||
<div
|
||||
className={`evnd__inner${useDashboard ? " evnd__inner--cards" : ""}${
|
||||
useDashboard && !mediaSlot && !isVideo ? " evnd__inner--img" : ""
|
||||
useDashboard && !mediaSlot ? " evnd__inner--img" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="evnd__heading">
|
||||
@@ -1399,7 +1349,7 @@ export default function EVSection({
|
||||
{/* MAIN GRID */}
|
||||
<div
|
||||
className={`evnd__grid${useDashboard ? " evnd__grid--cards" : ""}${
|
||||
useDashboard && !mediaSlot && !isVideo ? " evnd__grid--img" : ""
|
||||
useDashboard && !mediaSlot ? " evnd__grid--img" : ""
|
||||
}`}
|
||||
>
|
||||
{/* Left column */}
|
||||
@@ -1407,11 +1357,11 @@ export default function EVSection({
|
||||
<div className="evnd__media">
|
||||
<div className="evnd__glow" />
|
||||
<div
|
||||
className={`evnd__imgwrap${mediaSlot ? " evnd__imgwrap--media" : ""}${isVideo ? " evnd__imgwrap--video" : ""}${mediaClassName ? ` ${mediaClassName}` : ""}`}
|
||||
className={`evnd__imgwrap${mediaSlot ? " evnd__imgwrap--media" : ""}${image.endsWith(".mp4") ? " evnd__imgwrap--video" : ""}`}
|
||||
>
|
||||
{mediaSlot ? (
|
||||
mediaSlot
|
||||
) : isVideo ? (
|
||||
) : image.endsWith(".mp4") ? (
|
||||
<video
|
||||
className="evnd__video"
|
||||
autoPlay
|
||||
@@ -1455,7 +1405,6 @@ export default function EVSection({
|
||||
features={features}
|
||||
heading={cardsHeading}
|
||||
theme={cardsTheme}
|
||||
sysLabel={sysLabel}
|
||||
/>
|
||||
) : (
|
||||
<div className="evnd__features">
|
||||
@@ -1489,19 +1438,15 @@ export default function EVSection({
|
||||
all breakpoints. In dashboard mode it summarises the same workflow
|
||||
metrics; the homepage section uses its own stat set. */}
|
||||
<div className="evnd__bar">
|
||||
{(useDashboard ? (barStats ?? metrics!) : stats).map((s) => (
|
||||
{(useDashboard ? metrics! : stats).map((s) => (
|
||||
<div className="evnd__bar-item" key={s.label}>
|
||||
<span className="evnd__bar-label">{s.label}</span>
|
||||
{s.text ? (
|
||||
<b className="evnd__bar-val evnd__bar-val--text">{s.text}</b>
|
||||
) : (
|
||||
<CountUp
|
||||
value={s.value}
|
||||
decimals={s.decimals}
|
||||
suffix={s.suffix}
|
||||
className="evnd__bar-val"
|
||||
/>
|
||||
)}
|
||||
<CountUp
|
||||
value={s.value}
|
||||
decimals={s.decimals}
|
||||
suffix={s.suffix}
|
||||
className="evnd__bar-val"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||