fix image and screen issue
This commit is contained in:
BIN
public/images/ev solution 4.png
Normal file
BIN
public/images/ev solution 4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
@@ -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. <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.
|
||||
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.
|
||||
<body>'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;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
@@ -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 <head>) so Next emits exactly one, early in <head>:
|
||||
// <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
// `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<{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 `<video autoPlay muted loop>`
|
||||
* makes Chrome buffer the ENTIRE file at page load even while it is off-screen
|
||||
* and still paused — and `preload="none"` does not stop it, because the browser
|
||||
* keeps buffering to satisfy the pending autoplay. Measured on /miletruth that
|
||||
* was 80.7 MB of mp4 pulled before the user scrolled at all (the 40 MB
|
||||
* Doormile-quantum-video3.mp4 fetched twice over restarted byte ranges).
|
||||
* On localhost that is free; on a deployed site it saturates the connection and
|
||||
* the media pipeline exactly while the user is scrolling.
|
||||
*
|
||||
* Attaching the source only once the element nears the viewport defers all of
|
||||
* it. Playback then starts exactly as before — `.evnd__video` is sized purely
|
||||
* by CSS (width/height 100%), so an empty <video> occupies the identical box
|
||||
* and nothing shifts.
|
||||
*/
|
||||
function LazyVideo({ src, ariaLabel }: { src: string; ariaLabel: string }) {
|
||||
const ref = useRef<HTMLVideoElement>(null);
|
||||
const [shouldLoad, setShouldLoad] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el || shouldLoad) return;
|
||||
const io = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries.some((e) => e.isIntersecting)) {
|
||||
setShouldLoad(true);
|
||||
io.disconnect();
|
||||
}
|
||||
},
|
||||
// ~1.3 viewports of lead time: enough for the fetch to be well under way
|
||||
// before the section arrives, without paying for it at page load.
|
||||
{ rootMargin: "1200px 0px" },
|
||||
);
|
||||
io.observe(el);
|
||||
return () => io.disconnect();
|
||||
}, [shouldLoad]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLoad) ref.current?.load();
|
||||
}, [shouldLoad]);
|
||||
|
||||
return (
|
||||
<video
|
||||
ref={ref}
|
||||
className="evnd__video"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="none"
|
||||
aria-label={ariaLabel}
|
||||
src={shouldLoad ? src : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EVSection({
|
||||
bannerImage = "/images/ev.webp",
|
||||
cardNumber = "",
|
||||
@@ -1449,23 +1508,19 @@ export default function EVSection({
|
||||
{mediaSlot ? (
|
||||
mediaSlot
|
||||
) : isVideo ? (
|
||||
<video
|
||||
className="evnd__video"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
aria-label={imageAlt}
|
||||
>
|
||||
<source src={image} type="video/mp4" />
|
||||
</video>
|
||||
<LazyVideo src={image} ariaLabel={imageAlt} />
|
||||
) : (
|
||||
/* Same reason as LazyVideo: these stills are multi-MB raw
|
||||
PNGs (e.g. /solutions' "ev section1.png" is 2.6 MB at
|
||||
document y≈6793) and were being fetched and decoded
|
||||
eagerly while far below the fold. */
|
||||
/* eslint-disable-next-line @next/next/no-img-element */
|
||||
<img
|
||||
className="evnd__img"
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
{badges[0] && (
|
||||
|
||||
@@ -1132,7 +1132,7 @@ function Sec03() {
|
||||
id="vehicle-ai"
|
||||
eyebrow="/ Vehicle Selection /"
|
||||
title="AI vehicle selection"
|
||||
imageSrc="/images/home2-pic-3.webp"
|
||||
imageSrc="/images/ev solution 4.png"
|
||||
imageAlt="AI Vehicle Selection"
|
||||
items={AI_VEHICLE_SELECTION_ITEMS}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user