update blog page issue

This commit is contained in:
2026-06-12 14:55:29 +05:30
parent f412b9f71e
commit ba34c80761
16 changed files with 405 additions and 239 deletions

View File

@@ -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>
);