diff --git a/public/css/all-inlined-head-styles.css b/public/css/all-inlined-head-styles.css index 2b03652..3fb42b6 100644 --- a/public/css/all-inlined-head-styles.css +++ b/public/css/all-inlined-head-styles.css @@ -1932,10 +1932,10 @@ h1:where(.wp-block-heading).has-background, --container-widget-flex-grow: 0; --container-widget-align-self: initial; --flex-wrap-mobile: wrap; - --padding-top: 20px; - --padding-bottom: 20px; - --padding-left: 20px; - --padding-right: 20px; + --padding-top: 32px; + --padding-bottom: 32px; + --padding-left: 32px; + --padding-right: 32px; } .elementor .elementor-element.elementor-element-6c7cbcb .elementor-repeater-item-3264830 { @@ -2095,7 +2095,7 @@ h1:where(.wp-block-heading).has-background, } .elementor .elementor-element.elementor-element-6c7cbcb .owl-carousel .owl-stage-outer { - border-radius: 25px 25px 25px 25px; + border-radius: 32px 32px 32px 32px; } .elementor .elementor-element.elementor-element-6c7cbcb .content-slider.nav-view-compact .owl-nav, @@ -13053,20 +13053,20 @@ h1:where(.wp-block-heading).has-background, } .elementor-61 .elementor-element.elementor-element-6c7cbcb .elementor-repeater-item-6867061 .slide-content-inner { - max-width: 64%; - margin: 110px 0px 0px 75px; + max-width: 88%; + margin: 0 auto; } .elementor-61 .elementor-element.elementor-element-6c7cbcb .elementor-repeater-item-6867061 .slide-content { - -webkit-align-items: flex-start; - -moz-align-items: flex-start; - -ms-align-items: flex-start; - align-items: flex-start; + -webkit-align-items: center; + -moz-align-items: center; + -ms-align-items: center; + align-items: center; -webkit-justify-content: center; -moz-justify-content: center; -ms-justify-content: center; justify-content: center; - text-align: left; + text-align: center; } .elementor-61 .elementor-element.elementor-element-6c7cbcb .elementor-repeater-item-6867061 .content-slider-item-heading { @@ -19114,6 +19114,7 @@ img.wp-smiley, --margin-bottom: 0px; --margin-left: 0px; --margin-right: 0px; + position: relative; } .elementor-element.elementor-element-f35119c { @@ -28753,8 +28754,8 @@ img.wp-smiley, --margin-right: 0px; --padding-top: 0px; --padding-bottom: 0px; - --padding-left: 20px; - --padding-right: 20px; + --padding-left: 0px; + --padding-right: 0px; } .elementor-element.elementor-element-13a7637 { @@ -38655,11 +38656,13 @@ body.rtl .elementor-6473 .elementor-element.elementor-element-13a7637 { --column-gap: 0px; --background-transition: 0s; --border-radius: 25px 25px 25px 25px; + border-radius: 25px; + overflow: hidden; --z-index: 1; } .elementor-6473 .elementor-element.elementor-element-7da6646:not(.elementor-motion-effects-element-type-background), .elementor-6473 .elementor-element.elementor-element-7da6646 > .elementor-motion-effects-container > .elementor-motion-effects-layer { - background-image: url("/images/home4-banner-4.png"); + background-image: url("/images/bg-header-5.png"); background-position: center center; background-repeat: no-repeat; background-size: cover; diff --git a/public/css/vendor/vendor-theme-core.css b/public/css/vendor/vendor-theme-core.css index 868c106..3ddbb95 100644 --- a/public/css/vendor/vendor-theme-core.css +++ b/public/css/vendor/vendor-theme-core.css @@ -16806,7 +16806,7 @@ html.elementor-html { } } -/* .team-listing-wrapper.team-grid-listing { +.team-listing-wrapper.team-grid-listing { display: block; margin: 0 -10px -20px } @@ -17060,7 +17060,7 @@ html.elementor-html { padding: 0 22px; margin-bottom: 78px } -} */ +} .body-container .single-team { display: -webkit-box; diff --git a/src/animations/AnimationProvider.tsx b/src/animations/AnimationProvider.tsx index 7718fbb..e1be8fb 100644 --- a/src/animations/AnimationProvider.tsx +++ b/src/animations/AnimationProvider.tsx @@ -19,6 +19,11 @@ export default function AnimationProvider({ children }: { children: React.ReactN duration: 0.8, }); + // Mobile browsers fire `resize` when the address bar shows/hides while + // scrolling. Refreshing ScrollTrigger on every one of those caused jumpy / + // re-hidden animations on phones. Ignore those spurious resizes. + ScrollTrigger.config({ ignoreMobileResize: true }); + const initDecorativeBlocks = () => { // Clean up previous block triggers to avoid duplicates ScrollTrigger.getAll().forEach((t) => { @@ -27,6 +32,7 @@ export default function AnimationProvider({ children }: { children: React.ReactN } }); + const vh = window.innerHeight || document.documentElement.clientHeight; const decorativeBlocks = document.querySelectorAll(".block-decoration"); decorativeBlocks.forEach((block) => { ScrollTrigger.create({ @@ -50,6 +56,14 @@ export default function AnimationProvider({ children }: { children: React.ReactN block.classList.remove("animated"); }, }); + + // ScrollTrigger does not fire onEnter for blocks already in view at + // creation — on larger / taller screens those stayed un-animated. + // Reveal any block already intersecting the viewport. + const r = block.getBoundingClientRect(); + if (r.top < vh && r.bottom > 0) { + block.classList.add("animated"); + } }); }; @@ -83,15 +97,22 @@ export default function AnimationProvider({ children }: { children: React.ReactN }; window.addEventListener("load", handleLoad); - // Also refresh on window resize + // Refresh on window resize — debounced so dragging the window across + // breakpoints recomputes trigger positions once it settles, instead of + // thrashing on every intermediate pixel. + let resizeTimer: ReturnType; const handleResize = () => { - initDecorativeBlocks(); - ScrollTrigger.refresh(true); + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + initDecorativeBlocks(); + ScrollTrigger.refresh(true); + }, 200); }; window.addEventListener("resize", handleResize); return () => { timeouts.forEach(clearTimeout); + clearTimeout(resizeTimer); window.removeEventListener("load", handleLoad); window.removeEventListener("resize", handleResize); ScrollTrigger.getAll().forEach((t) => t.kill()); diff --git a/src/animations/Reveal.tsx b/src/animations/Reveal.tsx index e29cb36..d4dc65e 100644 --- a/src/animations/Reveal.tsx +++ b/src/animations/Reveal.tsx @@ -9,6 +9,71 @@ if (typeof window !== "undefined") { gsap.registerPlugin(ScrollTrigger); } +/* ============================================================ + Shared scroll-reveal trigger factory. + + ScrollTrigger does NOT fire `onEnter` for elements that are ALREADY inside + the viewport when the trigger is created. On taller / larger screens more + content sits above the fold on load, so those elements stayed stuck at + opacity:0 (invisible). This was the root cause of "animations work on my + laptop but break on bigger screens / other devices". + + This factory fixes it by revealing any element already intersecting the + viewport right after creation, so behaviour is identical on every screen + size. It also honours prefers-reduced-motion by revealing instantly. + ============================================================ */ +function createRevealTrigger(opts: { + trigger: Element; + start?: string; + show: () => void; + hide: () => void; + triggerOnce?: boolean; +}): () => void { + const { trigger, start = "top 88%", show, hide, triggerOnce = false } = opts; + + // Reduced motion: reveal immediately, no scroll dependency. + if ( + typeof window !== "undefined" && + window.matchMedia?.("(prefers-reduced-motion: reduce)").matches + ) { + show(); + return () => {}; + } + + const st = ScrollTrigger.create({ + trigger, + start, + onEnter: (self) => { + show(); + if (triggerOnce) self.kill(); + }, + onEnterBack: () => { + if (!triggerOnce) show(); + }, + onLeave: () => { + if (!triggerOnce) hide(); + }, + onLeaveBack: () => { + if (!triggerOnce) hide(); + }, + }); + + // Reveal-if-already-in-view (deferred one frame so layout is measured). + const raf = requestAnimationFrame(() => { + const r = trigger.getBoundingClientRect(); + const vh = window.innerHeight || document.documentElement.clientHeight; + if (r.top < vh && r.bottom > 0) { + show(); + if (triggerOnce) st.kill(); + } + }); + + return () => { + cancelAnimationFrame(raf); + st.kill(); + }; +} + /* ============================================================ 1. ScrollReveal Fade-in + slide-up on scroll. The workhorse animation. @@ -40,47 +105,19 @@ export function ScrollReveal({ gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 }); - const trigger = ScrollTrigger.create({ - trigger: el, - start: "top 88%", - onEnter: (self) => { - gsap.to(el, { - y: 0, - x: 0, - opacity: 1, - duration, - ease: "power3.out", - delay, - overwrite: "auto", - }); - if (triggerOnce) self.kill(); - }, - onEnterBack: () => { - if (!triggerOnce) { - gsap.to(el, { - y: 0, - x: 0, - opacity: 1, - duration, - ease: "power3.out", - delay, - overwrite: "auto", - }); - } - }, - onLeave: () => { - if (!triggerOnce) { - gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 }); - } - }, - onLeaveBack: () => { - if (!triggerOnce) { - gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 }); - } - }, - }); + const show = () => + gsap.to(el, { + y: 0, + x: 0, + opacity: 1, + duration, + ease: "power3.out", + delay, + overwrite: "auto", + }); + const hide = () => gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 }); - return () => trigger?.kill(); + return createRevealTrigger({ trigger: el, show, hide, triggerOnce }); }, [delay, duration, yOffset, xOffset, triggerOnce]); return ( @@ -122,47 +159,19 @@ export function RevealText({ gsap.set(items, { y: "110%", opacity: 0 }); - const trigger = ScrollTrigger.create({ - trigger: el, - start: "top 88%", - onEnter: (self) => { - gsap.to(items, { - y: "0%", - opacity: 1, - duration, - ease: "power4.out", - stagger: type === "chars" ? 0.02 : 0.04, - delay, - overwrite: "auto", - }); - if (triggerOnce) self.kill(); - }, - onEnterBack: () => { - if (!triggerOnce) { - gsap.to(items, { - y: "0%", - opacity: 1, - duration, - ease: "power4.out", - stagger: type === "chars" ? 0.02 : 0.04, - delay, - overwrite: "auto", - }); - } - }, - onLeave: () => { - if (!triggerOnce) { - gsap.set(items, { y: "110%", opacity: 0 }); - } - }, - onLeaveBack: () => { - if (!triggerOnce) { - gsap.set(items, { y: "110%", opacity: 0 }); - } - }, - }); + const show = () => + gsap.to(items, { + y: "0%", + opacity: 1, + duration, + ease: "power4.out", + stagger: type === "chars" ? 0.02 : 0.04, + delay, + overwrite: "auto", + }); + const hide = () => gsap.set(items, { y: "110%", opacity: 0 }); - return () => trigger?.kill(); + return createRevealTrigger({ trigger: el, show, hide, triggerOnce }); }, [children, type, delay, duration, triggerOnce]); const renderContent = () => { @@ -290,45 +299,18 @@ export function StaggerChildren({ gsap.set(items, { y: yOffset, opacity: 0 }); - const trigger = ScrollTrigger.create({ - trigger: el, - start: "top 85%", - onEnter: (self) => { - gsap.to(items, { - y: 0, - opacity: 1, - duration, - ease: "power3.out", - stagger, - overwrite: "auto", - }); - if (triggerOnce) self.kill(); - }, - onEnterBack: () => { - if (!triggerOnce) { - gsap.to(items, { - y: 0, - opacity: 1, - duration, - ease: "power3.out", - stagger, - overwrite: "auto", - }); - } - }, - onLeave: () => { - if (!triggerOnce) { - gsap.set(items, { y: yOffset, opacity: 0 }); - } - }, - onLeaveBack: () => { - if (!triggerOnce) { - gsap.set(items, { y: yOffset, opacity: 0 }); - } - }, - }); + const show = () => + gsap.to(items, { + y: 0, + opacity: 1, + duration, + ease: "power3.out", + stagger, + overwrite: "auto", + }); + const hide = () => gsap.set(items, { y: yOffset, opacity: 0 }); - return () => trigger?.kill(); + return createRevealTrigger({ trigger: el, start: "top 85%", show, hide, triggerOnce }); }, [stagger, duration, yOffset, triggerOnce]); return ( @@ -365,45 +347,18 @@ export function ScaleReveal({ gsap.set(el, { scale: 0.85, opacity: 0 }); - const trigger = ScrollTrigger.create({ - trigger: el, - start: "top 88%", - onEnter: (self) => { - gsap.to(el, { - scale: 1, - opacity: 1, - duration, - ease: "power3.out", - delay, - overwrite: "auto", - }); - if (triggerOnce) self.kill(); - }, - onEnterBack: () => { - if (!triggerOnce) { - gsap.to(el, { - scale: 1, - opacity: 1, - duration, - ease: "power3.out", - delay, - overwrite: "auto", - }); - } - }, - onLeave: () => { - if (!triggerOnce) { - gsap.set(el, { scale: 0.85, opacity: 0 }); - } - }, - onLeaveBack: () => { - if (!triggerOnce) { - gsap.set(el, { scale: 0.85, opacity: 0 }); - } - }, - }); + const show = () => + gsap.to(el, { + scale: 1, + opacity: 1, + duration, + ease: "power3.out", + delay, + overwrite: "auto", + }); + const hide = () => gsap.set(el, { scale: 0.85, opacity: 0 }); - return () => trigger?.kill(); + return createRevealTrigger({ trigger: el, show, hide, triggerOnce }); }, [delay, duration, triggerOnce]); return ( @@ -443,45 +398,18 @@ export function SlideReveal({ const xStart = direction === "left" ? -60 : 60; gsap.set(el, { x: xStart, opacity: 0 }); - const trigger = ScrollTrigger.create({ - trigger: el, - start: "top 88%", - onEnter: (self) => { - gsap.to(el, { - x: 0, - opacity: 1, - duration, - ease: "power3.out", - delay, - overwrite: "auto", - }); - if (triggerOnce) self.kill(); - }, - onEnterBack: () => { - if (!triggerOnce) { - gsap.to(el, { - x: 0, - opacity: 1, - duration, - ease: "power3.out", - delay, - overwrite: "auto", - }); - } - }, - onLeave: () => { - if (!triggerOnce) { - gsap.set(el, { x: xStart, opacity: 0 }); - } - }, - onLeaveBack: () => { - if (!triggerOnce) { - gsap.set(el, { x: xStart, opacity: 0 }); - } - }, - }); + const show = () => + gsap.to(el, { + x: 0, + opacity: 1, + duration, + ease: "power3.out", + delay, + overwrite: "auto", + }); + const hide = () => gsap.set(el, { x: xStart, opacity: 0 }); - return () => trigger?.kill(); + return createRevealTrigger({ trigger: el, show, hide, triggerOnce }); }, [direction, delay, duration, triggerOnce]); return ( @@ -573,43 +501,18 @@ export function CountUp({ const el = elementRef.current; if (!el) return; - const trigger = ScrollTrigger.create({ - trigger: el, - start: "top 90%", - onEnter: (self) => { - const obj = { val: start }; - gsap.to(obj, { - val: end, - duration, - ease: "power2.out", - onUpdate: () => setValue(obj.val), - }); - if (triggerOnce) self.kill(); - }, - onEnterBack: () => { - if (!triggerOnce) { - const obj = { val: start }; - gsap.to(obj, { - val: end, - duration, - ease: "power2.out", - onUpdate: () => setValue(obj.val), - }); - } - }, - onLeave: () => { - if (!triggerOnce) { - setValue(start); - } - }, - onLeaveBack: () => { - if (!triggerOnce) { - setValue(start); - } - }, - }); + const show = () => { + const obj = { val: start }; + gsap.to(obj, { + val: end, + duration, + ease: "power2.out", + onUpdate: () => setValue(obj.val), + }); + }; + const hide = () => setValue(start); - return () => trigger?.kill(); + return createRevealTrigger({ trigger: el, start: "top 90%", show, hide, triggerOnce }); }, [start, end, duration, triggerOnce]); return ( diff --git a/src/app/about-us/page.tsx b/src/app/about-us/page.tsx index 9ff176e..50f082a 100644 --- a/src/app/about-us/page.tsx +++ b/src/app/about-us/page.tsx @@ -18,7 +18,7 @@ export default function AboutUsPage() {
-
+
diff --git a/src/app/globals.css b/src/app/globals.css index 5071d6a..84eabfb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -767,7 +767,7 @@ body { content: '' !important; position: absolute !important; inset: 0 !important; - background: linear-gradient(to bottom, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0.78) 55%, rgba(0, 0, 0, 0.95) 100%) !important; + background: var(--hero-overlay, linear-gradient(to bottom, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0.78) 55%, rgba(0, 0, 0, 0.95) 100%)) !important; z-index: 1 !important; } @@ -777,6 +777,13 @@ body { } /* Responsive constraints to keep all heroes matching the home page carousel perfectly */ +@media (max-width: 1536px) { + .custom-standard-hero-card { + height: 600px !important; + min-height: 600px !important; + } +} + @media (max-width: 1024px) { .custom-standard-hero-container { padding: 10px 10px 10px 10px !important; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ab15e66..c21c312 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { Manrope, Space_Grotesk } from "next/font/google"; +import { Manrope, Space_Grotesk, Syne, DM_Sans } from "next/font/google"; import "./globals.css"; import Header from "@/components/layout/Header"; import Footer from "@/components/layout/Footer"; @@ -21,6 +21,19 @@ const spaceGrotesk = Space_Grotesk({ weight: ["400", "500", "600", "700"], }); +// Fonts for the Solutions industry section (Syne headings, DM Sans body). +const syne = Syne({ + subsets: ["latin"], + variable: "--font-syne", + weight: ["600", "700", "800"], +}); + +const dmSans = DM_Sans({ + subsets: ["latin"], + variable: "--font-dm-sans", + weight: ["400", "500"], +}); + export const metadata: Metadata = { title: "Doormile — Last-Mile Logistics Intelligence", description: "Doormile powers last-mile logistics with MileTruth™ AI, providing connected miles, SLA protection, and carrier management.", @@ -36,7 +49,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + {/* FontAwesome icons */} diff --git a/src/components/sections/AboutHero.tsx b/src/components/sections/AboutHero.tsx index cfd722f..7206dc7 100644 --- a/src/components/sections/AboutHero.tsx +++ b/src/components/sections/AboutHero.tsx @@ -8,18 +8,18 @@ export default function AboutHero() { width: 100% !important; text-align: center !important; color: #fff !important; - padding: 60px 40px !important; + padding: 60px 40px 100px 40px !important; z-index: 2; display: flex !important; flex-direction: column !important; - justify-content: center !important; + justify-content: flex-end !important; align-items: center !important; height: 100% !important; } .about-us-hero-title { margin: 0 !important; - font-family: var(--font-manrope), sans-serif !important; + font-family: "Manrope", Sans-serif !important; font-size: clamp(34px, 5.5vw, 68px) !important; font-weight: 850 !important; line-height: 1.1 !important; @@ -33,7 +33,8 @@ export default function AboutHero() {
diff --git a/src/components/sections/CompetitiveEdge.tsx b/src/components/sections/CompetitiveEdge.tsx index 9ff0568..355906e 100644 --- a/src/components/sections/CompetitiveEdge.tsx +++ b/src/components/sections/CompetitiveEdge.tsx @@ -1,12 +1,81 @@ -import React from "react"; +"use client"; + +import React, { useEffect, useRef } from "react"; +import gsap from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; + +if (typeof window !== "undefined") { + gsap.registerPlugin(ScrollTrigger); +} export default function CompetitiveEdge() { + const sectionRef = useRef(null); + const tableWrapperRef = useRef(null); + const moatRef = useRef(null); + + useEffect(() => { + const section = sectionRef.current; + const tableWrapper = tableWrapperRef.current; + const moat = moatRef.current; + + if (!section || !tableWrapper || !moat) return; + + // GSAP Timeline ScrollTrigger for viewport entrances + const entryTl = gsap.timeline({ + scrollTrigger: { + trigger: section, + start: "top 78%", + toggleActions: "play none none none", + } + }); + + entryTl + // 1. Reveal Table & Right panel + .fromTo([tableWrapper, moat], { + opacity: 0, + y: 45, + }, { + opacity: 1, + y: 0, + duration: 0.95, + stagger: 0.12, + ease: "power4.out", + }) + // 2. Stagger slide up row items + .fromTo(section.querySelectorAll(".table-row-hover"), { + opacity: 0, + y: 20, + }, { + opacity: 1, + y: 0, + duration: 0.75, + stagger: 0.05, + ease: "power3.out", + }, "-=0.6") + // 3. Pop checkmarks and badges cleanly + .fromTo(section.querySelectorAll(".yes-badge, .advanced-badge"), { + opacity: 0, + scale: 0.8, + }, { + opacity: 1, + scale: 1, + duration: 0.55, + stagger: 0.03, + ease: "back.out(1.6)", + }, "-=0.45"); + + }, []); + return ( -
+
+ {/* Visual background layers */} +
+
+
- {/* Comparison matrix Table */} -
+ {/* Comparison Matrix Table (69% on Desktop) */} +
@@ -20,7 +89,7 @@ export default function CompetitiveEdge() { - +
- + @@ -35,7 +104,7 @@ export default function CompetitiveEdge() {
- + @@ -48,7 +117,7 @@ export default function CompetitiveEdge() {
- + EV-aware planning @@ -60,8 +129,8 @@ export default function CompetitiveEdge() {
- - + + @@ -76,7 +145,7 @@ export default function CompetitiveEdge() {
- + @@ -89,20 +158,20 @@ export default function CompetitiveEdge() {
- + Verified handling ✓ YesNo Partial NoNo
- - + + Hyperlocal learning @@ -114,7 +183,7 @@ export default function CompetitiveEdge() {
- + @@ -132,20 +201,379 @@ export default function CompetitiveEdge() {
- {/* Strategic Moat Side cards */} -
-
-
-
- Competitive Edge -

Where Doormile Sits And Why It Wins

-

A side-by-side technical capabilities comparison showing how operational fleet ownership and dynamic AI planning disrupt basic aggregators.

-
-
-
+ {/* Moat Highlight Card (28% on Desktop) */} +
+
+
DoorMile Advantage
+

WHERE DOORMILE SITS AND WHY IT WINS

+

+ A side-by-side technical capabilities comparison showing how operational fleet ownership and dynamic AI planning disrupt basic aggregators. +

+ +