fix image and screen issue

This commit is contained in:
2026-08-03 14:56:30 +05:30
parent 65ea0640f8
commit ab7540c757
7 changed files with 188 additions and 18 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -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;
} }
/* ============================================================ /* ============================================================

View File

@@ -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<{

View File

@@ -818,6 +818,23 @@ export default function Header() {
padding-bottom: 0 !important; padding-bottom: 0 !important;
} }
/* ── Elementor's "laptop" tier (10211200px) ──
Elementor's own "laptop" tier is 10211200px: 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 10251200px 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 (10211200px) 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 36116px 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;

View File

@@ -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 {

View File

@@ -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] && (

View File

@@ -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}
/> />