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
|
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
|
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
|
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
|
scroll container and breaks sticky the same way.
|
||||||
visible on both axes. <body> already has overflow-x: hidden site-wide
|
|
||||||
(from the same legacy stylesheet), so horizontal bleed is still safely
|
`overflow-x: clip` is the exception the spec carves out for exactly this:
|
||||||
clipped there — this rule doesn't need to repeat it.
|
`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 {
|
.body-container {
|
||||||
overflow: visible !important;
|
overflow-x: clip !important;
|
||||||
|
overflow-y: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @next/next/no-css-tags */
|
/* 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 { Manrope, Space_Grotesk, Syne, DM_Sans, Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import Header from "@/components/layout/Header";
|
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({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
|
|||||||
@@ -818,6 +818,23 @@ export default function Header() {
|
|||||||
padding-bottom: 0 !important;
|
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) {
|
@media (min-width: 1025px) {
|
||||||
#masthead {
|
#masthead {
|
||||||
position: relative;
|
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 {
|
#masthead .header-menu-container .main-menu > li.active > a:before {
|
||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function ConnectedLogisticsSection({
|
|||||||
id = "vehicle-ai",
|
id = "vehicle-ai",
|
||||||
eyebrow = "/ Connected Logistics /",
|
eyebrow = "/ Connected Logistics /",
|
||||||
title = "Smart logistics solutions we deliver for modern supply chains",
|
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",
|
imageAlt = "Connected Logistics",
|
||||||
items = DEFAULT_ITEMS,
|
items = DEFAULT_ITEMS,
|
||||||
showButton = false,
|
showButton = false,
|
||||||
@@ -90,6 +90,48 @@ export default function ConnectedLogisticsSection({
|
|||||||
display: block !important;
|
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 */
|
/* Desktop/Laptop (min-width: 1025px) column width and flex rules */
|
||||||
@media (min-width: 1025px) {
|
@media (min-width: 1025px) {
|
||||||
.elementor-element-9ffed33 {
|
.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({
|
export default function EVSection({
|
||||||
bannerImage = "/images/ev.webp",
|
bannerImage = "/images/ev.webp",
|
||||||
cardNumber = "",
|
cardNumber = "",
|
||||||
@@ -1449,23 +1508,19 @@ export default function EVSection({
|
|||||||
{mediaSlot ? (
|
{mediaSlot ? (
|
||||||
mediaSlot
|
mediaSlot
|
||||||
) : isVideo ? (
|
) : isVideo ? (
|
||||||
<video
|
<LazyVideo src={image} ariaLabel={imageAlt} />
|
||||||
className="evnd__video"
|
|
||||||
autoPlay
|
|
||||||
muted
|
|
||||||
loop
|
|
||||||
playsInline
|
|
||||||
aria-label={imageAlt}
|
|
||||||
>
|
|
||||||
<source src={image} type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
) : (
|
) : (
|
||||||
|
/* 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 */
|
/* eslint-disable-next-line @next/next/no-img-element */
|
||||||
<img
|
<img
|
||||||
className="evnd__img"
|
className="evnd__img"
|
||||||
src={image}
|
src={image}
|
||||||
alt={imageAlt}
|
alt={imageAlt}
|
||||||
decoding="async"
|
decoding="async"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{badges[0] && (
|
{badges[0] && (
|
||||||
|
|||||||
@@ -1132,7 +1132,7 @@ function Sec03() {
|
|||||||
id="vehicle-ai"
|
id="vehicle-ai"
|
||||||
eyebrow="/ Vehicle Selection /"
|
eyebrow="/ Vehicle Selection /"
|
||||||
title="AI vehicle selection"
|
title="AI vehicle selection"
|
||||||
imageSrc="/images/home2-pic-3.webp"
|
imageSrc="/images/ev solution 4.png"
|
||||||
imageAlt="AI Vehicle Selection"
|
imageAlt="AI Vehicle Selection"
|
||||||
items={AI_VEHICLE_SELECTION_ITEMS}
|
items={AI_VEHICLE_SELECTION_ITEMS}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user