update blog page issue
This commit is contained in:
@@ -6,7 +6,7 @@ import { blogPosts } from "@/data/blog";
|
||||
|
||||
/**
|
||||
* Client-side blog search. The site is a static export, so there is no search
|
||||
* server — we filter the known posts in the browser and link straight to the
|
||||
* server. We filter the known posts in the browser and link straight to the
|
||||
* matching /blog/[slug] routes.
|
||||
*/
|
||||
export default function BlogSearch() {
|
||||
|
||||
@@ -72,10 +72,10 @@ export default function BlogSidebar({ current }: { current?: BlogPost }) {
|
||||
|
||||
{/* CTA Card */}
|
||||
<section className="dm-blog-widget dm-blog-cta-card">
|
||||
<h2 className="dm-blog-cta-title">Ready to optimise your fleet?</h2>
|
||||
<h2 className="dm-blog-cta-title">Planning delivery routes?</h2>
|
||||
<p className="dm-blog-cta-text">
|
||||
See how MileTruth™ AI cuts distance, vehicles and emissions — without
|
||||
missing an SLA.
|
||||
Talk to us about reducing wasted distance, missed windows, and avoidable
|
||||
vehicle time.
|
||||
</p>
|
||||
<Link href="/contact" className="dm-blog-cta-btn">
|
||||
Contact Us
|
||||
|
||||
@@ -216,14 +216,14 @@ export default function Footer() {
|
||||
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49" />
|
||||
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />
|
||||
</svg>
|
||||
<span>Social network</span>
|
||||
<span>Social</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="elementor-element elementor-element-a6bccba elementor-shape-square elementor-grid-0 elementor-widget elementor-widget-social-icons" data-id="a6bccba" data-element_type="widget" data-e-type="widget" data-widget_type="social-icons.default">
|
||||
<div className="elementor-widget-container">
|
||||
<div className="elementor-social-icons-wrapper elementor-grid" role="list" style={socialIconSpacing}>
|
||||
<span className="elementor-grid-item" role="listitem" style={{padding:"0 15px"}}>
|
||||
{/* <span className="elementor-grid-item" role="listitem" style={{padding:"0 15px"}}>
|
||||
<a className="elementor-icon elementor-social-icon elementor-social-icon-facebook-f elementor-repeater-item-3fbe893" href="https://www.facebook.com" target="_blank" rel="noopener noreferrer">
|
||||
<span className="elementor-screen-only">Facebook</span>
|
||||
<svg aria-hidden="true" className="e-font-icon-svg e-fab-facebook-f" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -238,7 +238,7 @@ export default function Footer() {
|
||||
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</span>
|
||||
</span> */}
|
||||
<span className="elementor-grid-item" role="listitem"style={{padding:"0 15px"}}>
|
||||
<a className="elementor-icon elementor-social-icon elementor-social-icon-linkedin-in elementor-repeater-item-38e1bcc" href="https://www.linkedin.com" target="_blank" rel="noopener noreferrer">
|
||||
<span className="elementor-screen-only">LinkedIn</span>
|
||||
@@ -403,7 +403,7 @@ export default function Footer() {
|
||||
<div className="elementor-element elementor-element-e4e6486 elementor-shape-square elementor-grid-0 elementor-widget elementor-widget-social-icons" data-id="e4e6486" data-element_type="widget" data-e-type="widget" data-widget_type="social-icons.default">
|
||||
<div className="elementor-widget-container">
|
||||
<div className="elementor-social-icons-wrapper elementor-grid" role="list" style={socialIconSpacing}>
|
||||
<span className="elementor-grid-item" role="listitem" style={{padding:"0 15px"}}>
|
||||
{/* <span className="elementor-grid-item" role="listitem" style={{padding:"0 15px"}}>
|
||||
<a className="elementor-icon elementor-social-icon elementor-social-icon-facebook-f" href="https://www.facebook.com" target="_blank" rel="noopener noreferrer">
|
||||
<span className="elementor-screen-only">Facebook</span>
|
||||
<svg aria-hidden="true" className="e-font-icon-svg e-fab-facebook-f" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -418,7 +418,7 @@ export default function Footer() {
|
||||
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</span>
|
||||
</span> */}
|
||||
<span className="elementor-grid-item" role="listitem" style={{padding:"0 15px"}}>
|
||||
<a className="elementor-icon elementor-social-icon elementor-social-icon-linkedin-in" href="https://www.linkedin.com" target="_blank" rel="noopener noreferrer">
|
||||
<span className="elementor-screen-only">LinkedIn</span>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
|
||||
/**
|
||||
@@ -10,21 +9,20 @@ import Image from "next/image";
|
||||
* Native reimplementation of the legacy WordPress page-loader: a black
|
||||
* full-screen overlay with a centered, pulsing Doormile logo that fades out.
|
||||
*
|
||||
* Shows on initial load (until the window finishes loading, min ~450ms to avoid
|
||||
* a flash, capped at 2.5s so it never blocks) and again briefly on each route
|
||||
* navigation. CWV-safe: fixed/out-of-flow (no layout shift), logo is priority,
|
||||
* and it never delays hydration.
|
||||
* Shows only on initial application boot (until the window finishes loading,
|
||||
* min ~450ms to avoid a flash, capped at 2.5s so it never blocks). It must not
|
||||
* reappear during client-side route transitions: Next keeps the current page
|
||||
* visible while the next route payload is prepared, and a global overlay here
|
||||
* would create an artificial black flash between otherwise-ready pages.
|
||||
*/
|
||||
type Phase = "visible" | "hiding" | "gone";
|
||||
|
||||
const MIN_SHOW_MS = 450;
|
||||
const MAX_SHOW_MS = 2500;
|
||||
const NAV_SHOW_MS = 520;
|
||||
|
||||
export default function LoadingScreen() {
|
||||
const pathname = usePathname();
|
||||
const [phase, setPhase] = useState<Phase>("visible");
|
||||
const isFirstRender = useRef(true);
|
||||
const bootComplete = useRef(false);
|
||||
|
||||
// Initial load: hide once the page is ready.
|
||||
useEffect(() => {
|
||||
@@ -33,7 +31,7 @@ export default function LoadingScreen() {
|
||||
let fadeTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
const begin = () => {
|
||||
if (began) return;
|
||||
if (began || bootComplete.current) return;
|
||||
began = true;
|
||||
const wait = Math.max(0, MIN_SHOW_MS - (performance.now() - start));
|
||||
fadeTimer = setTimeout(() => setPhase("hiding"), wait);
|
||||
@@ -52,17 +50,6 @@ export default function LoadingScreen() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Route navigations: flash the loader briefly for an app-like transition.
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current) {
|
||||
isFirstRender.current = false;
|
||||
return;
|
||||
}
|
||||
setPhase("visible");
|
||||
const t = setTimeout(() => setPhase("hiding"), NAV_SHOW_MS);
|
||||
return () => clearTimeout(t);
|
||||
}, [pathname]);
|
||||
|
||||
if (phase === "gone") return null;
|
||||
|
||||
return (
|
||||
@@ -72,7 +59,10 @@ export default function LoadingScreen() {
|
||||
aria-live="polite"
|
||||
aria-label="Loading"
|
||||
onTransitionEnd={(e) => {
|
||||
if (e.propertyName === "opacity" && phase === "hiding") setPhase("gone");
|
||||
if (e.propertyName === "opacity" && phase === "hiding") {
|
||||
bootComplete.current = true;
|
||||
setPhase("gone");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="dm-loader__pulse">
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
/* ===========================================================================
|
||||
Office satellite map — scoped styles.
|
||||
All Leaflet global classes are namespaced under `.root` via :global() so this
|
||||
module cannot leak into the rest of the app and vice-versa.
|
||||
Colours/radii reference the app's theme (dark surface, #C01227 brand red).
|
||||
The map fills its container 100% — no hard-coded layout values live here.
|
||||
=========================================================================== */
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* inherit the host container's rounded corners so tiles/controls clip cleanly */
|
||||
border-radius: inherit;
|
||||
overflow: hidden;
|
||||
background: #0b0b0b;
|
||||
@@ -34,10 +29,6 @@
|
||||
.controls {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
/* Span the full width (with side insets) instead of left:50%+auto-width:
|
||||
an absolutely-positioned auto-width box anchored at left:50% can only grow
|
||||
to 50% of the map, which on a narrow phone forced the city buttons to stack
|
||||
vertically. With left/right insets the row uses the full width and centres. */
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
z-index: 600; /* above tiles + markers; popups open lower so they never collide */
|
||||
@@ -47,6 +38,7 @@
|
||||
gap: 8px;
|
||||
pointer-events: none; /* let the row be transparent to drags; buttons re-enable */
|
||||
}
|
||||
|
||||
.controlBtn {
|
||||
pointer-events: auto;
|
||||
appearance: none;
|
||||
@@ -68,15 +60,18 @@
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease,
|
||||
transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.controlBtn:hover {
|
||||
background: rgba(192, 18, 39, 0.9);
|
||||
border-color: #c01227;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.controlBtn:focus-visible {
|
||||
outline: 2px solid #ffffff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.controlBtnActive,
|
||||
.controlBtnActive:hover {
|
||||
background: #c01227;
|
||||
@@ -90,6 +85,7 @@
|
||||
border-color: rgba(192, 18, 39, 0.55);
|
||||
box-shadow: 0 0 0 1px rgba(192, 18, 39, 0.25), 0 4px 14px rgba(192, 18, 39, 0.2);
|
||||
}
|
||||
|
||||
.controlBtnHq.controlBtnActive {
|
||||
box-shadow: 0 6px 20px rgba(192, 18, 39, 0.55);
|
||||
}
|
||||
@@ -98,8 +94,6 @@
|
||||
.controls {
|
||||
top: 12px;
|
||||
gap: 5px;
|
||||
/* Keep all three city buttons on a single line — shrink them (below) so the
|
||||
row fits instead of wrapping to two lines on narrow phones. */
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.controlBtn {
|
||||
@@ -110,12 +104,12 @@
|
||||
|
||||
/* ---- Branded marker pin ---- */
|
||||
.markerIcon {
|
||||
/* divIcon resets — keep only the pin's own drop shadow */
|
||||
background: transparent;
|
||||
border: 0;
|
||||
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
|
||||
transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.markerIcon:hover,
|
||||
.markerIcon:focus-visible {
|
||||
transform: translateY(-3px) scale(1.06);
|
||||
@@ -125,15 +119,16 @@
|
||||
.markerIconHq {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
/* red glow + grounding shadow */
|
||||
filter: drop-shadow(0 0 9px rgba(192, 18, 39, 0.85))
|
||||
drop-shadow(0 5px 7px rgba(0, 0, 0, 0.55));
|
||||
transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.markerIconHq svg {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.markerIconHq:hover,
|
||||
.markerIconHq:focus-visible {
|
||||
transform: translateY(-3px) scale(1.05);
|
||||
@@ -153,6 +148,7 @@
|
||||
pointer-events: none;
|
||||
animation: hqPulse 2.2s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes hqPulse {
|
||||
0% {
|
||||
transform: scale(0.6);
|
||||
@@ -168,26 +164,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Themed Leaflet internals (scoped to this map only) ---- */
|
||||
/* ---- Leaflet Branded Elements ---- */
|
||||
.root :global(.leaflet-container) {
|
||||
background: #0b0b0b;
|
||||
}
|
||||
|
||||
/* Zoom + attribution controls — dark, on-theme */
|
||||
.root :global(.leaflet-bar) {
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.root :global(.leaflet-bar a) {
|
||||
background: rgba(15, 15, 17, 0.92);
|
||||
color: #f5f5f5;
|
||||
border-bottom-color: rgba(255, 255, 255, 0.12);
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.root :global(.leaflet-bar a:hover) {
|
||||
background: #c01227;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.root :global(.leaflet-bar a:focus-visible) {
|
||||
outline: 2px solid #c01227;
|
||||
outline-offset: 2px;
|
||||
@@ -202,51 +200,122 @@
|
||||
border-radius: 6px 0 0 0;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.root :global(.leaflet-control-attribution a) {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
/* Popup — dark card matching the app surfaces */
|
||||
.root :global(.leaflet-popup-content-wrapper) {
|
||||
background: #141416;
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
.root :global(.leaflet-popup-content-wrapper) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
.root :global(.leaflet-popup-content) {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.root :global(.leaflet-popup-tip) {
|
||||
background: #141416;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
/* ---- Floating Popup overlays ---- */
|
||||
.root :global(.leaflet-popup) {
|
||||
animation: popupFade 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
/* ---- Compact, Google-Maps-style tooltip content ---- */
|
||||
.tip {
|
||||
.root :global(.leaflet-popup-content-wrapper) {
|
||||
background: #0f0f11;
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px; /* Clean business card rounded corners */
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
animation: popupScale 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
.root :global(.leaflet-popup-content) {
|
||||
margin: 0 !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.root :global(.leaflet-popup-tip) {
|
||||
background: #0f0f11;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
@keyframes popupFade {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes popupScale {
|
||||
from { transform: scale(0.92); }
|
||||
to { transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Compact Details Card inside Popup */
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 8px 12px;
|
||||
min-width: 120px;
|
||||
max-width: 200px;
|
||||
text-align: left;
|
||||
padding: 12px 16px;
|
||||
min-width: 230px;
|
||||
max-width: 280px;
|
||||
box-sizing: border-box;
|
||||
font-family: var(--font-manrope), system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
.tipTitle {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.01em;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
border-top: 2px solid #c01227; /* Thinner brand accent line */
|
||||
background: #0f0f11;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ---- Loading skeleton (prevents CLS — fills the fixed-height host) ---- */
|
||||
.cardHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.cardIcon {
|
||||
font-size: 15px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
font-size: 17px !important;
|
||||
font-weight: 800 !important;
|
||||
color: #ffffff !important; /* Force white header text */
|
||||
margin: 0 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.addressLine {
|
||||
font-size: 14px !important;
|
||||
line-height: 1.5 !important;
|
||||
color: rgba(255, 255, 255, 0.75) !important;
|
||||
margin: 0 0 6px 0 !important;
|
||||
padding: 0 !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.addressLine:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.card {
|
||||
padding: 9px 12px;
|
||||
min-width: 200px;
|
||||
max-width: 230px;
|
||||
}
|
||||
.cardTitle {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.addressLine {
|
||||
font-size: 12px !important;
|
||||
margin-bottom: 3px !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Loading skeleton ---- */
|
||||
.skeleton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -262,10 +331,12 @@
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
from { background-position: 200% 0; }
|
||||
to { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.skeleton { animation: none; }
|
||||
.markerIcon,
|
||||
@@ -289,12 +360,14 @@
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
font-family: var(--font-manrope), system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.errorTitle {
|
||||
font-size: clamp(15px, 2.4vw, 18px);
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.errorText {
|
||||
font-size: 13px;
|
||||
max-width: 38ch;
|
||||
@@ -302,6 +375,7 @@
|
||||
line-height: 1.55;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.errorList {
|
||||
list-style: none;
|
||||
margin: 4px 0 0;
|
||||
@@ -311,6 +385,7 @@
|
||||
gap: 8px 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.errorList li {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -319,6 +394,7 @@
|
||||
font-weight: 700;
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
.errorList li::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
@@ -327,7 +403,7 @@
|
||||
background: #c01227;
|
||||
}
|
||||
|
||||
/* ---- Screen-reader-only office list (semantic fallback) ---- */
|
||||
/* ---- Screen-reader-only office list ---- */
|
||||
.srOnly {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
|
||||
@@ -7,14 +7,9 @@
|
||||
* Doormile office markers, plus a row of "jump to office" buttons that fly the
|
||||
* map to a selected office's coordinates and open its popup.
|
||||
*
|
||||
* The experience opens focused on the Hyderabad headquarters — the largest,
|
||||
* glowing, pulsing marker — with its popup open by default, so the command
|
||||
* centre of the network is immediately legible. Hovering any marker opens its
|
||||
* popup; leaving closes it. Clicking a marker or a nav button flies to it.
|
||||
*
|
||||
* Loaded via a `ssr:false` dynamic import so Leaflet (which touches `window`)
|
||||
* never runs on the server and cannot cause hydration mismatches. Layout/spacing
|
||||
* is owned by the host container (see ContactMap).
|
||||
* The map centers with a North latitude offset to keep markers lower in the viewport,
|
||||
* avoiding collisions with the navigation tabs. Popups are compact and styled like
|
||||
* clean business cards.
|
||||
*/
|
||||
|
||||
import "leaflet/dist/leaflet.css";
|
||||
@@ -25,6 +20,7 @@ import L from "leaflet";
|
||||
import { MapContainer, Marker, Popup, TileLayer, ZoomControl, useMap } from "react-leaflet";
|
||||
|
||||
import {
|
||||
LatLng,
|
||||
ESRI_WORLD_IMAGERY,
|
||||
HQ_OFFICE,
|
||||
MAP_FOCUS_ZOOM,
|
||||
@@ -41,7 +37,6 @@ type FocusTarget = { id: string; nonce: number };
|
||||
/**
|
||||
* Build a branded SVG pin. The headquarters pin is larger, carries a red glow
|
||||
* and a soft pulse ring, and sits above every other marker.
|
||||
* (Module-scope-safe construction — this file is client-only.)
|
||||
*/
|
||||
function createMarkerIcon(isHeadquarters: boolean): L.DivIcon {
|
||||
if (isHeadquarters) {
|
||||
@@ -57,7 +52,7 @@ function createMarkerIcon(isHeadquarters: boolean): L.DivIcon {
|
||||
`,
|
||||
iconSize: [40, 52],
|
||||
iconAnchor: [20, 52],
|
||||
popupAnchor: [0, -46],
|
||||
popupAnchor: [0, -52], // Anchored to top tip of pin
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,15 +67,14 @@ function createMarkerIcon(isHeadquarters: boolean): L.DivIcon {
|
||||
`,
|
||||
iconSize: [30, 40],
|
||||
iconAnchor: [15, 40],
|
||||
popupAnchor: [0, -36],
|
||||
popupAnchor: [0, -40], // Anchored to top tip of pin
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Imperative map effects that need the Leaflet instance:
|
||||
* - keep the viewport sized correctly across resizes / lazy reveals
|
||||
* - on first paint, snap to the HQ and open its popup (no jarring long fly)
|
||||
* - fly to a single office when one is selected via the buttons
|
||||
* - snap to the offset HQ on first paint, fly thereafter and open popups.
|
||||
*/
|
||||
function MapController({
|
||||
focus,
|
||||
@@ -92,8 +86,7 @@ function MapController({
|
||||
const map = useMap();
|
||||
const didInit = useRef(false);
|
||||
|
||||
// Keep the map correctly sized on container resize / lazy reveal. We never
|
||||
// re-frame the view here, so resizing keeps whatever office is in focus.
|
||||
// Keep the map correctly sized on container resize / lazy reveal.
|
||||
useEffect(() => {
|
||||
const container = map.getContainer();
|
||||
let raf = 0;
|
||||
@@ -116,16 +109,20 @@ function MapController({
|
||||
|
||||
const openPopup = () => markerRefs.current[office.id]?.openPopup();
|
||||
|
||||
// Offset the center slightly North so the marker sits lower in the viewport,
|
||||
// preventing the popup card from colliding with the top controls.
|
||||
const offsetLatitude = 0.022;
|
||||
const centeredPosition: LatLng = [office.position[0] + offsetLatitude, office.position[1]];
|
||||
|
||||
if (!didInit.current) {
|
||||
didInit.current = true;
|
||||
map.invalidateSize();
|
||||
map.setView(office.position, MAP_FOCUS_ZOOM, { animate: false });
|
||||
// Open the HQ popup once the marker has mounted on this frame.
|
||||
map.setView(centeredPosition, MAP_FOCUS_ZOOM, { animate: false });
|
||||
const raf = requestAnimationFrame(openPopup);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}
|
||||
|
||||
map.flyTo(office.position, MAP_FOCUS_ZOOM, { duration: 1.1 });
|
||||
map.flyTo(centeredPosition, MAP_FOCUS_ZOOM, { duration: 1.1 });
|
||||
map.once("moveend", openPopup);
|
||||
return () => {
|
||||
map.off("moveend", openPopup);
|
||||
@@ -140,12 +137,21 @@ export default function OfficeMap() {
|
||||
const hqIcon = useMemo(() => createMarkerIcon(true), []);
|
||||
const markerRefs = useRef<Record<string, L.Marker>>({});
|
||||
|
||||
// Default to the headquarters so its button reads active and its popup opens.
|
||||
// Default to the headquarters.
|
||||
const [focus, setFocus] = useState<FocusTarget | null>({ id: HQ_OFFICE.id, nonce: 0 });
|
||||
const focusOffice = useCallback((id: string) => {
|
||||
setFocus((prev) => ({ id, nonce: (prev?.nonce ?? 0) + 1 }));
|
||||
}, []);
|
||||
|
||||
const [hoveredOfficeId, setHoveredOfficeId] = useState<string | null>(null);
|
||||
|
||||
// Restore the focused office popup when mouse leaves other markers.
|
||||
useEffect(() => {
|
||||
if (hoveredOfficeId === null && focus?.id) {
|
||||
markerRefs.current[focus.id]?.openPopup();
|
||||
}
|
||||
}, [hoveredOfficeId, focus]);
|
||||
|
||||
const [status, setStatus] = useState<MapStatus>("loading");
|
||||
const loadedRef = useRef(false);
|
||||
const errorCountRef = useRef(0);
|
||||
@@ -155,8 +161,6 @@ export default function OfficeMap() {
|
||||
setStatus("ready");
|
||||
}, []);
|
||||
|
||||
// Only surface an error if tiles never render (network/CORS/down). Once any
|
||||
// tile load succeeds the map is considered healthy and stays that way.
|
||||
const handleTileError = useCallback(() => {
|
||||
errorCountRef.current += 1;
|
||||
if (!loadedRef.current && errorCountRef.current >= 6) setStatus("error");
|
||||
@@ -223,8 +227,9 @@ export default function OfficeMap() {
|
||||
/>
|
||||
|
||||
{OFFICE_LOCATIONS.map((office) => {
|
||||
// Google-Maps-style tooltip: hovering shows just the location name;
|
||||
// clicking focuses the office.
|
||||
const isFocused = focus?.id === office.id;
|
||||
const isHovered = hoveredOfficeId === office.id;
|
||||
|
||||
return (
|
||||
<Marker
|
||||
key={office.id}
|
||||
@@ -236,9 +241,16 @@ export default function OfficeMap() {
|
||||
alt={office.name}
|
||||
eventHandlers={{
|
||||
click: () => focusOffice(office.id),
|
||||
// Hover opens the compact popup without moving the map.
|
||||
mouseover: (event) => event.target.openPopup(),
|
||||
mouseout: (event) => event.target.closePopup(),
|
||||
mouseover: (event) => {
|
||||
setHoveredOfficeId(office.id);
|
||||
event.target.openPopup();
|
||||
},
|
||||
mouseout: (event) => {
|
||||
setHoveredOfficeId(null);
|
||||
if (focus?.id !== office.id) {
|
||||
event.target.closePopup();
|
||||
}
|
||||
},
|
||||
}}
|
||||
ref={(instance) => {
|
||||
if (instance) markerRefs.current[office.id] = instance;
|
||||
@@ -246,16 +258,25 @@ export default function OfficeMap() {
|
||||
>
|
||||
<Popup
|
||||
className={styles.popup}
|
||||
autoPan={false}
|
||||
autoPan={isFocused}
|
||||
autoPanPadding={[25, 25]}
|
||||
closeButton={false}
|
||||
minWidth={120}
|
||||
maxWidth={200}
|
||||
minWidth={240}
|
||||
maxWidth={290}
|
||||
>
|
||||
<span className={styles.tip}>
|
||||
<span className={styles.tipTitle}>
|
||||
<span aria-hidden="true">📍</span> {office.shortLabel}
|
||||
</span>
|
||||
</span>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.cardHeader}>
|
||||
<span className={styles.cardIcon} aria-hidden="true">📍</span>
|
||||
<h4 className={styles.cardTitle}>{office.city} Office</h4>
|
||||
</div>
|
||||
<div className={styles.cardBody}>
|
||||
{office.address.map((line, idx) => (
|
||||
<p key={idx} className={styles.addressLine}>
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</Marker>
|
||||
);
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface OfficeLocation {
|
||||
readonly position: LatLng;
|
||||
/** Headquarters gets the largest, glowing, default-active marker. */
|
||||
readonly isHeadquarters?: boolean;
|
||||
/** Full office address lines. */
|
||||
readonly address: readonly string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,6 +44,12 @@ export const OFFICE_LOCATIONS: readonly OfficeLocation[] = [
|
||||
// Vision Ultima, Jayabheri Enclave, Gachibowli — verified on satellite.
|
||||
position: [17.4484, 78.3573],
|
||||
isHeadquarters: true,
|
||||
address: [
|
||||
"5th Floor, Vision Ultima,",
|
||||
"Street No.3, Jayabheri Enclave,",
|
||||
"Gachibowli, Hyderabad,",
|
||||
"Telangana 500032"
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "bengaluru",
|
||||
@@ -50,6 +58,13 @@ export const OFFICE_LOCATIONS: readonly OfficeLocation[] = [
|
||||
shortLabel: "Bengaluru Hub",
|
||||
// Resolved from the supplied Google Maps share link — verified on satellite.
|
||||
position: [12.9929351, 77.6988599],
|
||||
address: [
|
||||
"C612, 6th Floor,",
|
||||
"Trifecta Starlight, ITPL Road,",
|
||||
"Garudacharapalya, Mahadevapura,",
|
||||
"Bangalore 560048,",
|
||||
"Karnataka, India"
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "coimbatore",
|
||||
@@ -58,6 +73,12 @@ export const OFFICE_LOCATIONS: readonly OfficeLocation[] = [
|
||||
shortLabel: "Coimbatore Hub",
|
||||
// Mayflower Valencia, Coimbatore — verified against satellite view.
|
||||
position: [11.0191, 76.9883],
|
||||
address: [
|
||||
"Mayflower Valencia,",
|
||||
"Near Nava India Bus Stop,",
|
||||
"Avinashi Road, Udayampalayam,",
|
||||
"Tamil Nadu 641003"
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function BlogGrid() {
|
||||
font-family: var(--font-manrope), sans-serif !important;
|
||||
}
|
||||
|
||||
/* Bottom block pinned to the card base — keeps Read More + image at the
|
||||
/* Bottom block pinned to the card base. Keeps Read More + image at the
|
||||
same vertical position across cards with different text lengths. */
|
||||
.custom-blog-bottom {
|
||||
display: flex !important;
|
||||
|
||||
@@ -33,6 +33,25 @@ export default function ConnectedLogistics() {
|
||||
max-width: min(526px, 100%) !important;
|
||||
}
|
||||
|
||||
/* Sizing and identical padding on all 4 sides for Connected Logistics Image container */
|
||||
.elementor-element-99768ba .elementor-widget-container {
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
padding: 40px !important; /* Identical gap on left, right, top, and bottom */
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.elementor-element-99768ba img.wp-image-4481 {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important; /* Allow natural responsive scaling on desktop */
|
||||
height: auto !important;
|
||||
object-fit: cover !important;
|
||||
border-radius: 25px !important; /* Preserve 25px border radius */
|
||||
margin: 0 auto !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Desktop/Laptop (min-width: 1025px) column width and flex rules */
|
||||
@media (min-width: 1025px) {
|
||||
.elementor-element-9ffed33 {
|
||||
@@ -78,6 +97,30 @@ export default function ConnectedLogistics() {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Tablet overrides: ~15% smaller than site.css's 450px with centered layout */
|
||||
.elementor-element-99768ba .elementor-widget-container {
|
||||
padding: 30px !important;
|
||||
}
|
||||
|
||||
.elementor-element.elementor-element-99768ba .elementor-widget-container img.wp-image-4481 {
|
||||
width: 100% !important;
|
||||
max-width: 382px !important;
|
||||
border-radius: 25px !important; /* Explicitly keep 25px */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
/* Mobile overrides: ~15% smaller than site.css's 90% with 10% identical padding on all 4 sides */
|
||||
.elementor-element-99768ba .elementor-widget-container {
|
||||
padding: 10% !important;
|
||||
}
|
||||
|
||||
.elementor-element.elementor-element-99768ba .elementor-widget-container img.wp-image-4481 {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
border-radius: 25px !important; /* Explicitly keep 25px */
|
||||
}
|
||||
}
|
||||
`}} />
|
||||
<div className="elementor-element elementor-element-9ffed33 e-con-full e-flex cut-corner-no sticky-container-off e-con e-child" data-id="9ffed33" data-element_type="container" data-e-type="container" data-settings="{"background_background":"classic"}">
|
||||
|
||||
@@ -15,7 +15,7 @@ const ROADMAP_DATA = [
|
||||
trackLeft: "12.5%",
|
||||
phase: "Pilot Phase",
|
||||
phaseClass: "yellow",
|
||||
title: "Hyderabad Pilot",
|
||||
title: "Pilot",
|
||||
desc: "Launch operations in Hyderabad with dedicated EV hubs and MileTruth AI v1.0.",
|
||||
icon: (
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
@@ -25,7 +25,7 @@ const ROADMAP_DATA = [
|
||||
</svg>
|
||||
),
|
||||
stats: [
|
||||
{ text: "50-80 orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "100+ orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "1 city", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M3 21h18M19 21v-2a4 4 0 0 0-3-3.87M5 21v-2a4 4 0 0 1 3-3.87M9 21v-5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v5"></path></svg> },
|
||||
{ text: "10+ women partners", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle></svg> }
|
||||
]
|
||||
@@ -36,7 +36,7 @@ const ROADMAP_DATA = [
|
||||
trackLeft: "37.5%",
|
||||
phase: "Multi-City",
|
||||
phaseClass: "green",
|
||||
title: "Multi-City Scale",
|
||||
title: "Scale",
|
||||
desc: "Expand to Bengaluru and Chennai, securing key B2B enterprise traction.",
|
||||
icon: (
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
@@ -46,7 +46,7 @@ const ROADMAP_DATA = [
|
||||
</svg>
|
||||
),
|
||||
stats: [
|
||||
{ text: "300-500 orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "500+ orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "3 cities", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M3 21h18M19 21v-2a4 4 0 0 0-3-3.87M5 21v-2a4 4 0 0 1 3-3.87M9 21v-5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v5"></path></svg> },
|
||||
{ text: "50+ EVs", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg> }
|
||||
]
|
||||
@@ -57,8 +57,8 @@ const ROADMAP_DATA = [
|
||||
trackLeft: "62.5%",
|
||||
phase: "Platform",
|
||||
phaseClass: "blue",
|
||||
title: "Platform Expansion",
|
||||
desc: "Scale to 5+ cities. Launch developer API marketplace and Series A readiness.",
|
||||
title: "Expansion",
|
||||
desc: "Scale to 5+ cities. Strengthen regional operations.",
|
||||
icon: (
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
@@ -66,9 +66,9 @@ const ROADMAP_DATA = [
|
||||
</svg>
|
||||
),
|
||||
stats: [
|
||||
{ text: "1,200+ orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "5000+ orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "5+ cities", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M3 21h18M19 21v-2a4 4 0 0 0-3-3.87M5 21v-2a4 4 0 0 1 3-3.87M9 21v-5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v5"></path></svg> },
|
||||
{ text: "API marketplace", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg> }
|
||||
{ text: "100+ women partners", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg> }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -86,8 +86,8 @@ const ROADMAP_DATA = [
|
||||
</svg>
|
||||
),
|
||||
stats: [
|
||||
{ text: "5,000+ orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "Rs 65 Cr+ revenue", icon: <span className="currency-symbol" style={{ marginRight: "4px", fontSize: "11px", fontWeight: 800 }}>Rs</span> },
|
||||
{ text: "50,000+ orders/day", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg> },
|
||||
{ text: "50+ cities", icon: <span className="currency-symbol" style={{ marginRight: "4px", fontSize: "11px", fontWeight: 800 }}>Rs</span> },
|
||||
{ text: "2,000+ women partners", icon: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg> }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ function ContentRenderer({ block }: { block: ContentBlock }) {
|
||||
return (
|
||||
<blockquote className="dm-article-quote">
|
||||
<p>{block.text}</p>
|
||||
{block.cite && <cite>— {block.cite}</cite>}
|
||||
{block.cite && <cite>{block.cite}</cite>}
|
||||
</blockquote>
|
||||
);
|
||||
case "image":
|
||||
@@ -168,7 +168,7 @@ const STYLES = `
|
||||
font-family: var(--font-manrope), sans-serif;
|
||||
}
|
||||
|
||||
/* Heading normalization — beat the global theme's .elementor-kit-5 h1–h6
|
||||
/* Heading normalization. Beat the global theme's .elementor-kit-5 h1-h6
|
||||
(120/80/60px UPPERCASE) rules with !important on our own classes. */
|
||||
.dm-single-blog :where(h1, h2, h3, h4, h5, h6) {
|
||||
font-family: var(--font-manrope), sans-serif !important;
|
||||
@@ -180,7 +180,7 @@ const STYLES = `
|
||||
specificity 0-1-1) so blog links keep our colors and never get underlined. */
|
||||
.dm-single-blog a { text-decoration: none !important; }
|
||||
|
||||
/* ── Page banner — tall (homepage-scale); only badge + title inside ── */
|
||||
/* Page banner: tall homepage-scale frame with only badge + title inside */
|
||||
/* Compound selector (specificity 20) + !important beats the global 800px
|
||||
single-class height rules so the blog banner can use viewport heights. */
|
||||
.custom-standard-hero-card.dm-banner-card {
|
||||
@@ -216,7 +216,7 @@ const STYLES = `
|
||||
@media (max-width: 1024px) { .dm-banner-title { font-size: clamp(32px, 6vw, 48px) !important; max-width: 90%; } }
|
||||
@media (max-width: 600px) { .dm-banner-title { font-size: clamp(28px, 8vw, 38px) !important; max-width: 90%; } }
|
||||
|
||||
/* ── Content wrap — begins immediately below the banner ── */
|
||||
/* Content wrap begins immediately below the banner */
|
||||
/* Shared content container: the SAME max-width + horizontal padding is used
|
||||
by BlogPostFooter (.dm-blog-footer-inner) so the article body, headings,
|
||||
images, Prev/Next, Related Articles and the CTA banner all align to one
|
||||
@@ -287,7 +287,7 @@ const STYLES = `
|
||||
}
|
||||
/* Each article block is wrapped in its OWN ScrollReveal <div>, so a bare
|
||||
:first-child rule matched every heading (each is the only child of its
|
||||
wrapper) and zeroed its top margin — collapsing the gap above every
|
||||
wrapper) and zeroed its top margin, collapsing the gap above every
|
||||
section heading. Scope the reset to only the article body's first block. */
|
||||
.dm-article-body > :first-child :where(.dm-article-h2, .dm-article-h3),
|
||||
.dm-article-body > .dm-article-h2:first-child,
|
||||
|
||||
Reference in New Issue
Block a user