diff --git a/public/images/ev solution 4.png b/public/images/ev solution 4.png new file mode 100644 index 0000000..8d06e29 Binary files /dev/null and b/public/images/ev solution 4.png differ diff --git a/src/app/globals.css b/src/app/globals.css index 6881777..e9ea3f0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -22,13 +22,30 @@ html { 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.
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. + scroll container and breaks sticky the same way. + + `overflow-x: clip` is the exception the spec carves out for exactly this: + `clip` + `visible` is a legal pair that is NOT coerced, and `clip` does not + create a scroll container — so `position: sticky` keeps working while the + horizontal clip that the legacy layout depends on is restored. + + That horizontal clip matters because the off-canvas mobile menu drawer + (`.mobile-header-menu-container`, site.css) is parked at + `position: fixed; right: -320px; width: 320px` on EVERY page. Its + containing block is the header container `.elementor-element-466de1b` + (it carries a transform), which is absolutely positioned against + `.body-container` — so with `.body-container` fully visible, that parked + drawer extended the document's scrollable overflow by 320px sitewide. + 's `overflow-x: hidden` (legacy stylesheet) propagates to the + viewport, which suppresses the scrollbar but still leaves the 320px in the + scrollable overflow region — so `documentElement.scrollWidth` read + `viewport + 320`. Mobile Chrome sizes its initial page scale from that + width, which is what made every page load shrunk-to-fit and pinned to the + top-left until the first scroll forced a re-fit. ============================================================ */ .body-container { - overflow: visible !important; + overflow-x: clip !important; + overflow-y: visible !important; } /* ============================================================ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index cb4b768..3dd4a48 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,5 @@ /* eslint-disable @next/next/no-css-tags */ -import type { Metadata } from "next"; +import type { Metadata, Viewport } from "next"; import { Manrope, Space_Grotesk, Syne, DM_Sans, Inter } from "next/font/google"; import "./globals.css"; import Header from "@/components/layout/Header"; @@ -58,6 +58,17 @@ export const metadata: Metadata = { }, }; +// Single source of truth for the viewport meta tag. Declared here (rather than +// hand-written in ) so Next emits exactly one, early in : +// +// `viewport-fit: cover` is inert for this site — no stylesheet uses +// env(safe-area-inset-*) — so it changes nothing visually. +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + viewportFit: "cover", +}; + export default function RootLayout({ children, }: Readonly<{ diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 8f37e3b..837c392 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -818,6 +818,23 @@ export default function Header() { padding-bottom: 0 !important; } + /* ── Elementor's "laptop" tier (1021–1200px) ── + Elementor's own "laptop" tier is 1021–1200px: the Contact button + container (f961133) carries .elementor-hidden-laptop, which + site.css turns into display:none inside + @media (min-width:1021px) and (max-width:1200px). The nav widget + likewise carries .logico-breakpoint-laptop, whose theme rules + (body[data-elementor-device-mode="laptop"]) hide the desktop + .header-menu-container and reveal the hamburger — but those are + driven by Elementor's frontend JS, which this port does not ship, + so they never matched and the desktop nav stayed on down to 1025px + while the Contact button had already been hidden by the pure-CSS + half of the same tier. That left 1025–1200px rendering a + three-column desktop grid with an empty third column, so the + centred nav slid left and overlapped the logo (measured: nav starts + 6px left of the logo's right edge at 1164px, 40px at 1093px). + The grid is repaired for that band further down (search + "laptop tier"); everything >= 1201px is untouched. */ @media (min-width: 1025px) { #masthead { position: relative; @@ -903,6 +920,34 @@ export default function Header() { } } + /* ── laptop tier: drop the phantom third column ── + In Elementor's laptop tier (1021–1200px) the Contact button is + display:none, so the grid's third track holds nothing — but + "1fr auto 1fr" still reserves it. At these widths the auto (nav) + track is wider than the space left over, so both 1fr tracks + collapse to 0 and the centred nav bleeds out of its track in BOTH + directions, landing on top of the logo: measured overlap was 39px + at 1200px, 56px at 1164px and 121px at 1026px ("Home" printed over + "DoorMile"). Un-hiding the Contact button does not help — measured + at every width in the band the three sections still do not fit + (the nav would then overlap the button by 36–116px instead). + + Dropping to two tracks and pinning the nav to the end removes the + phantom column: the logo keeps its intrinsic width, the nav takes + what is left, and they no longer intersect. This block only ever + matches while .elementor-hidden-laptop is hiding the button, so + >= 1201px — every Mac (1440/1470/1512/1728 CSS px) and an + EliteBook at 100% or 125% Windows scaling — is byte-identical. */ + @media (min-width: 1025px) and (max-width: 1200px) { + #masthead .elementor-element.elementor-element-466de1b { + grid-template-columns: auto minmax(0, 1fr) !important; + } + + #masthead .elementor-element.elementor-element-e44ee7e { + justify-self: end !important; + } + } + #masthead .header-menu-container .main-menu > li.active > a:before { background-color: #ffffff !important; opacity: 1 !important; diff --git a/src/components/sections/ConnectedLogisticsSection.tsx b/src/components/sections/ConnectedLogisticsSection.tsx index 8fe3804..b1fca62 100644 --- a/src/components/sections/ConnectedLogisticsSection.tsx +++ b/src/components/sections/ConnectedLogisticsSection.tsx @@ -27,7 +27,7 @@ export default function ConnectedLogisticsSection({ id = "vehicle-ai", eyebrow = "/ Connected Logistics /", title = "Smart logistics solutions we deliver for modern supply chains", - imageSrc = "/images/home2-pic-3.webp", + imageSrc = "/images/ev solution 4.png", imageAlt = "Connected Logistics", items = DEFAULT_ITEMS, showButton = false, @@ -90,6 +90,48 @@ export default function ConnectedLogisticsSection({ display: block !important; } + /* ── Left image: fill the row instead of floating centred in it ── + The two columns are 50/50 and the row is align-items:center. The + image is width:100%/height:auto, so its height is fixed by the + file's aspect ratio (ev solution 4.png is 1426x1103 landscape, + 1.29:1) — at a 1440px viewport that is a 555x429 box inside a + 705px-tall row. The 276px left over was split evenly by + align-items:center, giving ~98px of dead space above AND below, + on top of the widget-container's intentional 40px inset — 163px + of gap at each end, and it grows as the viewport narrows (190px + at 1280px) because the image shrinks with the column while the + text column's height stays put. + + Stretching the image column to the row height and letting the + image fill it (object-fit:cover, already the declared value) makes + the picture 625px instead of 429px, so it now tracks the 705px + text column and the dead space collapses to the intended 40px + inset. cover scales without distorting; because the source is + landscape in a now-portrait slot it crops horizontally rather + than letter-boxing. + + Everything here is scoped to #${id} because ConnectedLogistics.tsx + (Home) reuses these exact Elementor class names with no wrapping + id — unscoped rules would move Home's section too. Desktop only; + the stacked <=1024px layout is untouched. */ + @media (min-width: 1025px) { + #${id} .elementor-element-9ffed33 { + align-items: stretch !important; + } + + #${id} .elementor-element-96343ba > div, + #${id} .elementor-element-99768ba, + #${id} .elementor-element-99768ba .elementor-widget-container { + height: 100% !important; + } + + #${id} .elementor-element-99768ba img.wp-image-4481 { + height: 100% !important; + object-fit: cover !important; + object-position: center !important; + } + } + /* Desktop/Laptop (min-width: 1025px) column width and flex rules */ @media (min-width: 1025px) { .elementor-element-9ffed33 { diff --git a/src/components/sections/EVSection.tsx b/src/components/sections/EVSection.tsx index 68a0b6a..6f3e89a 100644 --- a/src/components/sections/EVSection.tsx +++ b/src/components/sections/EVSection.tsx @@ -342,6 +342,65 @@ function DashboardPanel({ ); } +/** + * LazyVideo + * --------------------------------------------------------------------------- + * The workflow videos sit thousands of pixels below the fold (e.g. /miletruth + * renders one at document y≈9978), but a plain `