/** * IranMapSection — server component * Fetches real café locations from the API and overlays them as blinking dots * on a stylised SVG silhouette of Iran. */ import { Suspense } from "react"; // ── Types ───────────────────────────────────────────────────────────────────── type MapMarker = { id: string; name: string; city: string | null; latitude: number; longitude: number; }; type MarkersApiResponse = { success: boolean; data: MapMarker[]; }; // ── Coordinate transform ────────────────────────────────────────────────────── // Iran bounding box (degrees) — fitted to the real border extent // (lng 44.11–63.32, lat 25.08–39.71) with a small margin so the // silhouette fills the viewBox. Markers reproject with the same box, // so they stay aligned with the outline. const MIN_LNG = 43.6; const MAX_LNG = 63.8; const MIN_LAT = 24.6; const MAX_LAT = 40.2; const SVG_W = 600; const SVG_H = 500; const toX = (lng: number) => ((lng - MIN_LNG) / (MAX_LNG - MIN_LNG)) * SVG_W; const toY = (lat: number) => ((MAX_LAT - lat) / (MAX_LAT - MIN_LAT)) * SVG_H; function toPt([lng, lat]: [number, number]) { return `${toX(lng).toFixed(1)},${toY(lat).toFixed(1)}`; } // ── Iran silhouette ──────────────────────────────────────────────────────────── // Real national border, simplified to 74 vertices (source: Natural Earth via // world.geo.json). Coordinates are [longitude, latitude]; the ring starts on // the Caspian (NE) and runs clockwise. Projected through toX/toY below, the // same transform used for the café markers, so dots land in the right place. const IRAN_OUTLINE: [number, number][] = [ [53.92, 37.20], [54.80, 37.39], [55.51, 37.96], [56.18, 37.94], [56.62, 38.12], [57.33, 38.03], [58.44, 37.52], [59.23, 37.41], [60.38, 36.53], [61.12, 36.49], [61.21, 35.65], [60.80, 34.40], [60.53, 33.68], [60.96, 33.53], [60.54, 32.98], [60.86, 32.18], [60.94, 31.55], [61.70, 31.38], [61.78, 30.74], [60.87, 29.83], [61.37, 29.30], [61.77, 28.70], [62.73, 28.26], [62.76, 27.38], [63.23, 27.22], [63.32, 26.76], [61.87, 26.24], [61.50, 25.08], [59.62, 25.38], [58.53, 25.61], [57.40, 25.74], [56.97, 26.97], [56.49, 27.14], [55.72, 26.96], [54.72, 26.48], [53.49, 26.81], [52.48, 27.58], [51.52, 27.87], [50.85, 28.81], [50.12, 30.15], [49.58, 29.99], [48.94, 30.32], [48.57, 29.93], [48.01, 30.45], [48.00, 30.99], [47.69, 30.98], [47.85, 31.71], [47.33, 32.47], [46.11, 33.02], [45.42, 33.97], [45.65, 34.75], [46.15, 35.09], [46.08, 35.68], [45.42, 35.98], [44.77, 37.17], [44.23, 37.97], [44.42, 38.28], [44.11, 39.43], [44.79, 39.71], [44.95, 39.34], [45.46, 38.87], [46.14, 38.74], [46.51, 38.77], [47.69, 39.51], [48.06, 39.58], [48.36, 39.29], [48.01, 38.79], [48.63, 38.27], [48.88, 38.32], [49.20, 37.58], [50.15, 37.37], [50.84, 36.87], [52.26, 36.70], [53.83, 36.97], ]; const IRAN_PATH = "M " + IRAN_OUTLINE.map(toPt).join(" L ") + " Z"; // A handful of major cities shown as faint reference dots const MAJOR_CITIES: { name: string; lng: number; lat: number }[] = [ { name: "تهران", lng: 51.389, lat: 35.689 }, { name: "مشهد", lng: 59.608, lat: 36.297 }, { name: "اصفهان", lng: 51.668, lat: 32.661 }, { name: "شیراز", lng: 52.531, lat: 29.594 }, { name: "تبریز", lng: 46.291, lat: 38.08 }, { name: "اهواز", lng: 48.683, lat: 31.318 }, ]; // ── Data fetcher ────────────────────────────────────────────────────────────── async function fetchMarkers(): Promise { try { const apiBase = process.env.MEEZI_API_URL ?? "https://api.meezi.ir"; const res = await fetch(`${apiBase}/api/public/map-markers`, { next: { revalidate: 3600 }, }); if (!res.ok) return []; const json = (await res.json()) as MarkersApiResponse; return json.data ?? []; } catch { return []; } } // ── Sub-components ──────────────────────────────────────────────────────────── async function IranMapSvg() { const markers = await fetchMarkers(); return (
{/* Glow filter */} {/* Iran silhouette */} {/* Major city reference dots (faint) */} {MAJOR_CITIES.map((city) => ( ))} {/* Café markers — each glows slowly on and off like a small lamp. Halo and core brighten/dim together (ease-in-out), staggered so the map twinkles organically rather than pulsing in unison. */} {markers.map((m, idx) => { const cx = toX(m.longitude); const cy = toY(m.latitude); const delay = `${((idx * 0.7) % 3.6).toFixed(2)}s`; const dur = "3.6s"; // ease-in-out for a smooth lamp-like fade const ease = "0.4 0 0.6 1; 0.4 0 0.6 1"; return ( {/* Soft halo */} {/* Core dot — turns on (bright, slightly larger) and off (dim) */} ); })} {/* Floating legend */} {markers.length > 0 && (
{markers.length} کافه و رستوران
)}
); } // ── Export ──────────────────────────────────────────────────────────────────── export function IranMapSection() { return (
{/* Subtle background pattern */}
{/* Heading */}
پراکنش جغرافیایی

میزی در سراسر ایران

از تهران تا مشهد، از تبریز تا شیراز — کافه‌ها و رستوران‌های بیشتری هر روز به میزی می‌پیوندند.

{/* Map */}
} >
); }