From fe5c4be198aa9fbb0258c48c32476339cc40d539 Mon Sep 17 00:00:00 2001 From: ssbg04 Date: Sun, 18 Oct 2026 21:16:17 +0800 Subject: [PATCH] feat(map): match world map fullscreen expand with photo gallery lightbox modal --- src/components/VisitorWorldMap.tsx | 398 ++++++++++++++++------------- 1 file changed, 218 insertions(+), 180 deletions(-) diff --git a/src/components/VisitorWorldMap.tsx b/src/components/VisitorWorldMap.tsx index 9e219d0..848f8fc 100644 --- a/src/components/VisitorWorldMap.tsx +++ b/src/components/VisitorWorldMap.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { createPortal } from 'react-dom'; import haptic from '../lib/haptics'; interface CountryData { @@ -82,6 +83,8 @@ export default function VisitorWorldMap() { const [loading, setLoading] = useState(true); const [hoveredCountry, setHoveredCountry] = useState(null); + const [mounted, setMounted] = useState(false); + // Fullscreen War-Room Lightbox State const [isFullscreen, setIsFullscreen] = useState(false); const [zoom, setZoom] = useState(1); @@ -93,6 +96,10 @@ export default function VisitorWorldMap() { const dragStartRef = useRef({ x: 0, y: 0 }); const panStartRef = useRef({ x: 0, y: 0 }); + useEffect(() => { + setMounted(true); + }, []); + // Pre-calculate the SVG coordinates for the landmass dot matrix const landDots = useMemo(() => { const dots: { cx: number; cy: number; key: string }[] = []; @@ -567,12 +574,14 @@ export default function VisitorWorldMap() { {/* ══════════════════════════════════════════════════════════════════════════════ - CINEMA FULLSCREEN WAR-ROOM MODAL (Theme-Adaptive & Immersive Full Viewport) + CINEMA FULLSCREEN WAR-ROOM LIGHTBOX MODAL (Matching Photo Gallery Preview) ══════════════════════════════════════════════════════════════════════════════ */} - {isFullscreen && ( + {mounted && isFullscreen && typeof document !== 'undefined' && createPortal(
{ + haptic.tap(); setIsFullscreen(false); setZoom(1); setPan({ x: 0, y: 0 }); @@ -581,29 +590,29 @@ export default function VisitorWorldMap() { role="dialog" aria-modal="true" > - {/* ── Top Controls Bar ── */} + {/* ── Top Bar Controls: Telemetry info, Search, Zoom Toolbar, Leaderboard, Close ── */}
e.stopPropagation()} > - {/* Left: Title + Telemetry stats */} -
- + {/* Left: Telemetry status badge */} +
+ - + -
-

- GLOBAL TELEMETRY RADAR // FULL SCREEN -

-

- {data?.stats?.totalVisitors || 0} real sessions recorded across {data?.stats?.totalCountries || 0} sovereign territories -

+
+ + GLOBAL RADAR + + + {data?.stats?.totalVisitors || 0} sessions • {data?.stats?.totalCountries || 0} countries +
{/* Center: Search country input */} -
+
@@ -611,14 +620,14 @@ export default function VisitorWorldMap() { type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} - placeholder="Find country (e.g. Philippines, USA, Japan)..." - className="w-full bg-elevated-custom border border-border-custom rounded-lg pl-8 pr-8 py-1.5 text-xs text-foreground-custom placeholder:text-muted-foreground-custom font-mono focus:outline-none focus:border-primary-custom shadow-xs" + placeholder="Find country (e.g. Philippines, USA)..." + className="w-full bg-elevated-custom border border-border-custom rounded-lg pl-8 pr-7 py-1 text-xs text-foreground-custom placeholder:text-muted-foreground-custom font-mono focus:outline-none focus:border-primary-custom shadow-xs" /> {searchQuery && ( @@ -626,15 +635,16 @@ export default function VisitorWorldMap() {
{/* Right: Zoom Toolbar, Leaderboard Drawer Toggle, Close */} -
+
{/* Zoom Toolbar */} -
+
{/* Close Button */} @@ -687,7 +702,7 @@ export default function VisitorWorldMap() { setPan({ x: 0, y: 0 }); setSearchQuery(''); }} - className="p-1.5 rounded-lg bg-card-custom hover:bg-rose-500/10 hover:text-rose-500 hover:border-rose-500/30 border border-border-custom text-muted-foreground-custom transition-all cursor-pointer" + className="p-1.5 sm:p-2 rounded-lg bg-card-custom hover:bg-rose-500/10 hover:border-rose-500/30 hover:text-rose-500 text-muted-foreground-custom transition-all cursor-pointer border border-border-custom shadow-xs" aria-label="Close fullscreen radar" > @@ -697,169 +712,192 @@ export default function VisitorWorldMap() {
- {/* ── Main Full-Modal Viewport Container (Fills Entire Screen) ── */} + {/* ── Main Full-Modal Viewport Container (Centered Card Frame that takes the screen) ── */}
e.stopPropagation()} - onMouseDown={onMouseDown} - onMouseMove={onMouseMove} - onMouseUp={onMouseUp} - onMouseLeave={onMouseUp} - onTouchStart={onTouchStart} - onTouchMove={onTouchMove} - onTouchEnd={onTouchEnd} - onWheel={handleWheel} > - {/* Draggable & Scalable Map Frame */}
1 ? (isDragging ? 'grabbing' : 'grab') : 'default', - }} + className={`relative w-full h-full max-h-[78vh] sm:max-h-[82vh] rounded-2xl overflow-hidden border border-border-custom bg-card-custom/95 shadow-2xl flex items-center justify-center p-2 sm:p-4 select-none touch-none ${ + zoom > 1 ? (isDragging ? 'cursor-grabbing' : 'cursor-grab') : 'cursor-default' + }`} + onMouseDown={onMouseDown} + onMouseMove={onMouseMove} + onMouseUp={onMouseUp} + onMouseLeave={onMouseUp} + onTouchStart={onTouchStart} + onTouchMove={onTouchMove} + onTouchEnd={onTouchEnd} + onWheel={handleWheel} > - {/* Inner 2:1 Aspect Ratio Canvas Frame that expands cleanly */} -
- {/* Dot-Matrix World SVG */} - - - - - - - - - - - {landDots.map((dot) => ( - - ))} - - - - {/* Radar sweep line */} -
- - {/* Render country blips */} - {renderCountryBlips(true)} -
-
- - {/* Live Hover Telemetry Inspector HUD in Fullscreen Modal */} -
- - {hoveredCountry ? hoveredCountry.flag : data?.client?.flag || '🌐'} - -
-
- - {hoveredCountry - ? hoveredCountry.name - : data?.client - ? `${data.client.city ? data.client.city + ', ' : ''}${data.client.countryName}` - : 'Resolving Location...'} - - {(hoveredCountry?.isClient || (!hoveredCountry && data?.client)) && ( - - YOU - - )} -
-

- {hoveredCountry - ? `${hoveredCountry.count} recorded ${hoveredCountry.count === 1 ? 'session' : 'sessions'} • ${hoveredCountry.percentage}% total traffic` - : data?.stats?.totalVisitors - ? `${data.stats.totalVisitors} real sessions • Hover dot for country telemetry` - : 'Real-time Edge telemetry'} -

-
-
- - {/* Hint overlay */} -
- Scroll / Pinch to zoom • Drag to pan • Press [ESC] to exit -
- - {/* Collapsible Leaderboard Side Drawer (Theme Adaptive) */} - {isLeaderboardOpen && ( + {/* Scalable & Draggable Canvas Frame */}
e.stopPropagation()} + className="relative w-full h-full flex items-center justify-center select-none" + style={{ + transform: `translate3d(${pan.x}px, ${pan.y}px, 0) scale(${zoom})`, + transition: isDragging ? 'none' : 'transform 0.2s cubic-bezier(0.16, 1, 0.3, 1)', + }} > -
-
- - Country Leaderboard - - - {data?.stats?.allCountries?.filter((c) => c.count > 0).length || 0} active - -
- -
- -
- {data?.stats?.allCountries - ?.slice() - .filter((c) => c.count > 0 || c.isClient) - .sort((a, b) => b.count - a.count) - .map((country, rank) => ( -
{ - haptic.tap(); - setHoveredCountry(country); - setSearchQuery(country.name); - }} - className={`p-2.5 rounded-lg border transition-all cursor-pointer flex items-center justify-between text-xs ${ - hoveredCountry?.code === country.code - ? 'border-primary-custom bg-primary-custom/15 text-foreground-custom' - : 'border-border-custom bg-card-custom/50 hover:bg-elevated-custom text-foreground-custom' - }`} + + -
- - #{rank + 1} - - {country.flag} - {country.name} - {country.isClient && ( - - YOU - - )} -
-
- {country.count} - - ({country.percentage}%) - -
-
- ))} + + + + + + + + {landDots.map((dot) => ( + + ))} + + + + {/* Radar sweep line */} +
+ + {/* Render country blips */} + {renderCountryBlips(true)}
- )} + + {/* Live Hover Telemetry Inspector HUD (Top Left Corner inside card) */} +
+ + {hoveredCountry ? hoveredCountry.flag : data?.client?.flag || '🌐'} + +
+
+ + {hoveredCountry + ? hoveredCountry.name + : data?.client + ? `${data.client.city ? data.client.city + ', ' : ''}${data.client.countryName}` + : 'Resolving Location...'} + + {(hoveredCountry?.isClient || (!hoveredCountry && data?.client)) && ( + + YOU + + )} +
+

+ {hoveredCountry + ? `${hoveredCountry.count} ${hoveredCountry.count === 1 ? 'session' : 'sessions'} • ${hoveredCountry.percentage}% total traffic` + : data?.stats?.totalVisitors + ? `${data.stats.totalVisitors} real sessions • Hover dot for country telemetry` + : 'Real-time Edge telemetry'} +

+
+
+ + {/* Collapsible Leaderboard Side Drawer (Theme Adaptive) */} + {isLeaderboardOpen && ( +
e.stopPropagation()} + > +
+
+ + Country Leaderboard + + + {data?.stats?.allCountries?.filter((c) => c.count > 0).length || 0} active + +
+ +
+ +
+ {data?.stats?.allCountries + ?.slice() + .filter((c) => c.count > 0 || c.isClient) + .sort((a, b) => b.count - a.count) + .map((country, rank) => ( +
{ + haptic.tap(); + setHoveredCountry(country); + setSearchQuery(country.name); + }} + className={`p-2.5 rounded-lg border transition-all cursor-pointer flex items-center justify-between text-xs ${ + hoveredCountry?.code === country.code + ? 'border-primary-custom bg-primary-custom/15 text-foreground-custom' + : 'border-border-custom bg-card-custom/50 hover:bg-elevated-custom text-foreground-custom' + }`} + > +
+ + #{rank + 1} + + {country.flag} + {country.name} + {country.isClient && ( + + YOU + + )} +
+
+ {country.count} + + ({country.percentage}%) + +
+
+ ))} +
+
+ )} +
-
+ + {/* ── Floating Bottom Hint Bar ── */} +
e.stopPropagation()} + > + + Scroll / Pinch to zoom • Drag to pan • Click country for telemetry + + + Pinch to zoom • Drag to pan + + + Press ESC to exit + +
+
, + document.body )}
);