fix(world-map): remove mock visitor data, adapt expanded modal to active theme, full viewport expansion, and fix dot hover readability

This commit is contained in:
ssbg04 committed 2026-10-18 19:57:47 +08:00
1 parent a0c2ccd1b7
commit 30b4764d5c
3 files changed
+251 -145

No files matched your search

+223 -120
View File
@@ -248,7 +248,7 @@ export default function VisitorWorldMap() {
});
};
// Helper to render map blips
// Helper to render map blips with smart positioning and high-contrast readability
const renderCountryBlips = (isModal = false) => {
if (!data?.stats?.allCountries) return null;
@@ -261,6 +261,22 @@ export default function VisitorWorldMap() {
(country.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
country.code.toLowerCase().includes(searchQuery.toLowerCase()));
// Smart vertical placement:
// If dot is in top 45% (northern hemisphere), place tooltip BELOW the dot to avoid container clipping
// If dot is in bottom 55% (southern hemisphere), place tooltip ABOVE the dot
const verticalPos = country.y < 45 ? 'top-full mt-2.5' : 'bottom-full mb-2.5';
// Smart horizontal alignment:
// If dot is near right edge (x > 75%), anchor to right
// If dot is near left edge (x < 25%), anchor to left
// Otherwise center
const horizontalPos =
country.x > 75
? 'right-0'
: country.x < 25
? 'left-0'
: 'left-1/2 -translate-x-1/2';
return (
<div
key={country.code}
@@ -275,13 +291,17 @@ export default function VisitorWorldMap() {
haptic.tap();
setHoveredCountry(country);
}}
className="absolute -translate-x-1/2 -translate-y-1/2 z-20 cursor-pointer p-2 group/dot touch-manipulation"
className="absolute -translate-x-1/2 -translate-y-1/2 z-20 cursor-pointer p-2.5 group/dot touch-manipulation"
>
{/* Pulsing ring for client or top countries or matching search */}
{(isClient || country.count > 25 || matchesSearch) && (
{/* Pulsing beacon for client or active traffic or search match */}
{(isClient || country.count > 0 || matchesSearch) && (
<span
className={`absolute inset-0 rounded-full animate-ping opacity-75 ${
matchesSearch ? 'bg-amber-400 ring-4 ring-amber-400/50' : 'bg-primary-custom'
matchesSearch
? 'bg-amber-400 ring-4 ring-amber-400/50'
: isClient
? 'bg-primary-custom'
: 'bg-primary-custom/60'
}`}
/>
)}
@@ -294,45 +314,52 @@ export default function VisitorWorldMap() {
: isClient
? 'w-3.5 h-3.5 bg-primary-custom ring-4 ring-primary-custom/30 shadow-[0_0_12px_var(--primary-color)]'
: isHovered
? 'w-3 h-3 bg-foreground-custom ring-2 ring-primary-custom'
? 'w-3.5 h-3.5 bg-foreground-custom ring-2 ring-primary-custom shadow-md'
: hasTraffic
? 'w-2.5 h-2.5 bg-primary-custom/90 shadow-[0_0_6px_var(--primary-color)] hover:scale-150'
: 'w-1.5 h-1.5 bg-muted-foreground-custom/40'
? 'w-2.5 h-2.5 bg-primary-custom shadow-[0_0_8px_var(--primary-color)] hover:scale-150'
: 'w-1.5 h-1.5 bg-muted-foreground-custom/30 hover:bg-muted-foreground-custom/70'
}`}
/>
{/* Hover / Highlight Tooltip Card */}
{/* High-Contrast Readable Tooltip Card (Theme-Adaptive & Never Clipped) */}
{(isHovered || (isModal && matchesSearch)) && (
<div
className={`absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-48 p-2.5 rounded-xl border shadow-xl text-left pointer-events-none z-30 animate-in fade-in zoom-in-95 duration-150 ${
isModal
? 'bg-zinc-900/95 backdrop-blur-md border-zinc-700 text-white'
: 'bg-card-custom/95 backdrop-blur-md border-border-custom text-foreground-custom'
}`}
className={`absolute ${verticalPos} ${horizontalPos} w-56 p-3 rounded-xl border-2 border-primary-custom/40 shadow-2xl text-left pointer-events-none z-50 animate-in fade-in zoom-in-95 duration-150 backdrop-blur-xl bg-card-custom/95 text-foreground-custom`}
>
<div className="flex items-center justify-between gap-1 mb-1.5">
<span className="flex items-center gap-1.5 font-bold font-heading text-xs">
<span className="text-sm">{country.flag}</span>
<div className="flex items-center justify-between gap-1.5 mb-2 pb-1.5 border-b border-border-custom">
<span className="flex items-center gap-2 font-bold font-heading text-xs truncate">
<span className="text-base shrink-0">{country.flag}</span>
<span className="truncate">{country.name}</span>
</span>
{isClient && (
<span className="text-[9px] font-mono px-1.5 py-0.2 rounded bg-primary-custom text-white font-medium">
<span className="shrink-0 text-[9px] font-mono px-2 py-0.5 rounded-full bg-primary-custom text-white font-bold tracking-wider shadow-xs">
YOU
</span>
)}
</div>
<div className="flex items-center justify-between text-[11px] font-mono opacity-80">
<span>Recorded Visitors:</span>
<strong className="font-semibold">{country.count}</strong>
</div>
<div className="space-y-1.5 font-mono text-[11px]">
<div className="flex items-center justify-between">
<span className="text-muted-foreground-custom">Recorded Sessions:</span>
<strong className="font-bold text-foreground-custom text-xs">
{country.count} {country.count === 1 ? 'session' : 'sessions'}
</strong>
</div>
{/* Share of Traffic Bar */}
<div className="w-full h-1.5 rounded-full bg-white/10 mt-1.5 overflow-hidden">
<div
className="h-full bg-primary-custom rounded-full transition-all duration-300"
style={{ width: `${Math.max(country.percentage, 5)}%` }}
/>
<div className="flex items-center justify-between">
<span className="text-muted-foreground-custom">Traffic Share:</span>
<span className="font-semibold text-primary-custom">
{country.percentage}%
</span>
</div>
{/* Progress bar */}
<div className="w-full h-1.5 rounded-full bg-foreground-custom/10 overflow-hidden mt-1">
<div
className="h-full bg-primary-custom rounded-full transition-all duration-300"
style={{ width: `${Math.max(country.percentage, country.count > 0 ? 8 : 0)}%` }}
/>
</div>
</div>
</div>
)}
@@ -354,11 +381,11 @@ export default function VisitorWorldMap() {
<h3 className="text-sm sm:text-base font-bold font-heading text-foreground-custom flex items-center gap-2">
<span>Global Visitor Radar</span>
<span className="text-[10px] font-mono px-2 py-0.5 rounded-md bg-foreground-custom/5 text-muted-foreground-custom border border-border-custom font-normal">
LIVE TELEMETRY
REAL-TIME TELEMETRY
</span>
</h3>
<p className="text-xs text-muted-foreground-custom mt-0.5 font-mono">
Real-time traffic origins detected via Cloudflare Edge
Live traffic origins detected via Cloudflare Edge
</p>
</div>
</div>
@@ -393,7 +420,7 @@ export default function VisitorWorldMap() {
<svg className="w-3.5 h-3.5 text-primary-custom" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
</svg>
<span className="hidden sm:inline">Expand Radar</span>
<span className="hidden sm:inline">Expand Fullscreen</span>
<span className="sm:hidden">Expand</span>
</button>
</div>
@@ -448,17 +475,48 @@ export default function VisitorWorldMap() {
{/* ── Interactive Country Blips ── */}
{renderCountryBlips(false)}
{/* ── Active Inspector HUD Overlay (Top Right Corner) ── */}
<div className="absolute top-3 right-3 hidden sm:flex items-center gap-2.5 px-3 py-2 rounded-xl bg-card-custom/95 backdrop-blur-md border border-border-custom shadow-md max-w-xs pointer-events-none transition-all z-30">
<span className="text-xl shrink-0">
{hoveredCountry ? hoveredCountry.flag : data?.client?.flag || '🌐'}
</span>
<div className="truncate text-left">
<div className="flex items-center gap-1.5">
<span className="text-xs font-bold font-heading text-foreground-custom truncate">
{hoveredCountry
? hoveredCountry.name
: data?.client
? `${data.client.city ? data.client.city + ', ' : ''}${data.client.countryName}`
: 'Resolving Location...'}
</span>
{(hoveredCountry?.isClient || (!hoveredCountry && data?.client)) && (
<span className="text-[8px] font-mono px-1.5 py-0.2 rounded bg-primary-custom text-white font-bold">
YOU
</span>
)}
</div>
<p className="text-[10px] font-mono text-muted-foreground-custom mt-0.5">
{hoveredCountry
? `${hoveredCountry.count} recorded ${hoveredCountry.count === 1 ? 'session' : 'sessions'} (${hoveredCountry.percentage}% share)`
: data?.stats?.totalVisitors
? `${data.stats.totalVisitors} real sessions • Hover dot for info`
: 'Real-time telemetry'}
</p>
</div>
</div>
{/* Legend / Overlay Note with Expand Callout */}
<div className="absolute bottom-2.5 left-3 flex flex-wrap items-center gap-3 text-[10px] font-mono text-muted-foreground-custom bg-card-custom/85 backdrop-blur-xs px-2.5 py-1 rounded-md border border-border-custom shadow-xs">
<div className="absolute bottom-2.5 left-3 flex flex-wrap items-center gap-3 text-[10px] font-mono text-muted-foreground-custom bg-card-custom/90 backdrop-blur-xs px-2.5 py-1 rounded-md border border-border-custom shadow-xs z-30">
<span className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-primary-custom shadow-[0_0_6px_var(--primary-color)]" />
<span>Active / Recorded</span>
<span>Active Origin</span>
</span>
<span className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-primary-custom animate-ping" />
<span>Your Location</span>
</span>
<button
type="button"
onClick={() => {
haptic.tap();
setIsFullscreen(true);
@@ -472,42 +530,48 @@ export default function VisitorWorldMap() {
{/* ── Top Country Stats Row ── */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2.5 pt-1">
{data?.stats?.topCountries?.slice(0, 4).map((c) => (
<div
key={c.code}
onMouseEnter={() => setHoveredCountry(c)}
onMouseLeave={() => setHoveredCountry(null)}
onClick={() => setHoveredCountry(c)}
className={`p-3 rounded-xl border transition-all cursor-pointer flex items-center justify-between ${
hoveredCountry?.code === c.code
? 'border-primary-custom bg-elevated-custom'
: 'border-border-custom bg-card-custom/50 hover:border-primary-custom/30'
}`}
>
<div className="flex items-center gap-2 truncate">
<span className="text-base shrink-0">{c.flag}</span>
<div className="truncate">
<p className="text-xs font-semibold text-foreground-custom truncate leading-none">
{c.name}
</p>
<p className="text-[10px] font-mono text-muted-foreground-custom mt-1">
{c.percentage}% of traffic
</p>
{data?.stats?.topCountries && data.stats.topCountries.length > 0 ? (
data.stats.topCountries.slice(0, 4).map((c) => (
<div
key={c.code}
onMouseEnter={() => setHoveredCountry(c)}
onMouseLeave={() => setHoveredCountry(null)}
onClick={() => setHoveredCountry(c)}
className={`p-3 rounded-xl border transition-all cursor-pointer flex items-center justify-between ${
hoveredCountry?.code === c.code
? 'border-primary-custom bg-elevated-custom'
: 'border-border-custom bg-card-custom/50 hover:border-primary-custom/30'
}`}
>
<div className="flex items-center gap-2 truncate">
<span className="text-base shrink-0">{c.flag}</span>
<div className="truncate">
<p className="text-xs font-semibold text-foreground-custom truncate leading-none">
{c.name}
</p>
<p className="text-[10px] font-mono text-muted-foreground-custom mt-1">
{c.percentage}% of traffic
</p>
</div>
</div>
<span className="text-xs font-mono font-bold text-foreground-custom pl-2 shrink-0">
{c.count}
</span>
</div>
<span className="text-xs font-mono font-bold text-foreground-custom pl-2 shrink-0">
{c.count}
</span>
))
) : (
<div className="col-span-2 sm:col-span-4 p-3 rounded-xl border border-dashed border-border-custom text-center text-xs font-mono text-muted-foreground-custom">
Real visitor data is actively accumulating via Cloudflare Edge...
</div>
))}
)}
</div>
{/* ══════════════════════════════════════════════════════════════════════════════
CINEMA FULLSCREEN WAR-ROOM MODAL (Interactive Zoom, Pan, Search & Leaderboard)
CINEMA FULLSCREEN WAR-ROOM MODAL (Theme-Adaptive & Immersive Full Viewport)
══════════════════════════════════════════════════════════════════════════════ */}
{isFullscreen && (
<div
className="fixed inset-0 z-[100] bg-black/92 backdrop-blur-xl flex flex-col overflow-hidden select-none animate-modal-fade text-white p-3 sm:p-5"
className="fixed inset-0 z-[100] w-screen h-screen flex flex-col overflow-hidden select-none animate-modal-fade bg-background-custom/95 dark:bg-black/95 backdrop-blur-2xl text-foreground-custom"
onClick={() => {
setIsFullscreen(false);
setZoom(1);
@@ -519,7 +583,7 @@ export default function VisitorWorldMap() {
>
{/* ── Top Controls Bar ── */}
<div
className="w-full flex flex-col md:flex-row items-stretch md:items-center justify-between gap-3 pb-3 border-b border-white/10 shrink-0"
className="w-full flex flex-col md:flex-row items-stretch md:items-center justify-between gap-3 px-4 sm:px-6 py-3 border-b border-border-custom bg-card-custom/80 backdrop-blur-md shrink-0 z-30"
onClick={(e) => e.stopPropagation()}
>
{/* Left: Title + Telemetry stats */}
@@ -529,18 +593,18 @@ export default function VisitorWorldMap() {
<span className="relative inline-flex rounded-full h-3 w-3 bg-emerald-500" />
</span>
<div>
<h3 className="text-xs sm:text-sm font-bold font-mono uppercase tracking-wider text-white flex items-center gap-2">
<span>GLOBAL TELEMETRY RADAR // WAR-ROOM</span>
<h3 className="text-xs sm:text-sm font-bold font-mono uppercase tracking-wider text-foreground-custom flex items-center gap-2">
<span>GLOBAL TELEMETRY RADAR // FULL SCREEN</span>
</h3>
<p className="text-[10px] font-mono text-zinc-400">
{data?.stats?.totalVisitors || 0} total sessions recorded across {data?.stats?.totalCountries || 0} sovereign territories
<p className="text-[10px] font-mono text-muted-foreground-custom">
{data?.stats?.totalVisitors || 0} real sessions recorded across {data?.stats?.totalCountries || 0} sovereign territories
</p>
</div>
</div>
{/* Center: Search country input */}
<div className="relative w-full md:w-80">
<svg className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400 pointer-events-none" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground-custom pointer-events-none" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>
<input
@@ -548,13 +612,13 @@ export default function VisitorWorldMap() {
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Find country (e.g. Philippines, USA, Japan)..."
className="w-full bg-white/10 border border-white/20 rounded-lg pl-8 pr-8 py-1.5 text-xs text-white placeholder:text-zinc-400 font-mono focus:outline-none focus:border-primary-custom shadow-xs"
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"
/>
{searchQuery && (
<button
type="button"
onClick={() => setSearchQuery('')}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-white text-xs cursor-pointer"
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground-custom hover:text-foreground-custom text-xs cursor-pointer"
>
✕
</button>
@@ -564,12 +628,12 @@ export default function VisitorWorldMap() {
{/* Right: Zoom Toolbar, Leaderboard Drawer Toggle, Close */}
<div className="flex items-center gap-2 self-end md:self-auto">
{/* Zoom Toolbar */}
<div className="flex items-center bg-white/10 border border-white/20 rounded-lg p-0.5">
<div className="flex items-center bg-elevated-custom border border-border-custom rounded-lg p-0.5">
<button
type="button"
onClick={handleZoomOut}
disabled={zoom <= 1}
className="p-1.5 rounded text-white hover:bg-white/10 disabled:opacity-30 cursor-pointer transition-all"
className="p-1.5 rounded text-foreground-custom hover:bg-card-custom disabled:opacity-30 cursor-pointer transition-all"
title="Zoom Out (-)"
>
<svg className="w-3.5 h-3.5 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -579,7 +643,7 @@ export default function VisitorWorldMap() {
<button
type="button"
onClick={handleResetZoom}
className="px-2 py-0.5 text-[11px] font-mono text-zinc-300 hover:text-white cursor-pointer"
className="px-2 py-0.5 text-[11px] font-mono text-muted-foreground-custom hover:text-foreground-custom cursor-pointer"
title="Reset Zoom (0)"
>
{Math.round(zoom * 100)}%
@@ -588,7 +652,7 @@ export default function VisitorWorldMap() {
type="button"
onClick={handleZoomIn}
disabled={zoom >= 4}
className="p-1.5 rounded text-white hover:bg-white/10 disabled:opacity-30 cursor-pointer transition-all"
className="p-1.5 rounded text-foreground-custom hover:bg-card-custom disabled:opacity-30 cursor-pointer transition-all"
title="Zoom In (+)"
>
<svg className="w-3.5 h-3.5 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -607,7 +671,7 @@ export default function VisitorWorldMap() {
className={`px-3 py-1.5 rounded-lg text-xs font-mono border transition-all cursor-pointer flex items-center gap-1.5 ${
isLeaderboardOpen
? 'bg-primary-custom text-white border-primary-custom font-medium'
: 'bg-white/10 text-zinc-300 border-white/20 hover:text-white'
: 'bg-card-custom text-foreground-custom border-border-custom hover:bg-elevated-custom'
}`}
>
<span>📊 Leaderboard</span>
@@ -623,7 +687,7 @@ export default function VisitorWorldMap() {
setPan({ x: 0, y: 0 });
setSearchQuery('');
}}
className="p-1.5 rounded-lg bg-white/10 hover:bg-rose-500/20 hover:text-rose-400 border border-white/20 text-zinc-300 transition-all cursor-pointer"
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"
aria-label="Close fullscreen radar"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
@@ -633,9 +697,9 @@ export default function VisitorWorldMap() {
</div>
</div>
{/* ── Main Viewport Container ── */}
{/* ── Main Full-Modal Viewport Container (Fills Entire Screen) ── */}
<div
className="relative flex-1 min-h-0 flex items-center justify-center overflow-hidden my-2"
className="relative flex-1 w-full h-full min-h-0 overflow-hidden flex items-center justify-center p-2 sm:p-4 md:p-6"
onClick={(e) => e.stopPropagation()}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
@@ -648,72 +712,105 @@ export default function VisitorWorldMap() {
>
{/* Draggable & Scalable Map Frame */}
<div
className="relative w-full max-w-6xl aspect-[16/8] max-h-[80vh] flex items-center justify-center select-none"
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)',
cursor: zoom > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default',
}}
>
{/* Dot-Matrix World SVG */}
<svg
viewBox="0 0 1000 500"
className="w-full h-full text-white pointer-events-none"
preserveAspectRatio="none"
>
<defs>
<pattern
id="fullscreen-ocean-matrix"
x="0"
y="0"
width="12.5"
height="12.5"
patternUnits="userSpaceOnUse"
>
<circle cx="6.25" cy="6.25" r="1" className="fill-current opacity-10" />
</pattern>
</defs>
{/* Inner 2:1 Aspect Ratio Canvas Frame that expands cleanly */}
<div className="relative w-full aspect-[2/1] max-w-full max-h-full flex items-center justify-center">
{/* Dot-Matrix World SVG */}
<svg
viewBox="0 0 1000 500"
className="w-full h-full text-foreground-custom pointer-events-none"
preserveAspectRatio="none"
>
<defs>
<pattern
id="fullscreen-ocean-matrix"
x="0"
y="0"
width="12.5"
height="12.5"
patternUnits="userSpaceOnUse"
>
<circle cx="6.25" cy="6.25" r="1" className="fill-current opacity-[0.06] dark:opacity-[0.10]" />
</pattern>
</defs>
<rect width="1000" height="500" fill="url(#fullscreen-ocean-matrix)" />
<rect width="1000" height="500" fill="url(#fullscreen-ocean-matrix)" />
<g className="fill-current opacity-40">
{landDots.map((dot) => (
<circle key={dot.key} cx={dot.cx} cy={dot.cy} r="2.6" />
))}
</g>
</svg>
<g className="fill-current opacity-[0.25] dark:opacity-[0.35]">
{landDots.map((dot) => (
<circle key={dot.key} cx={dot.cx} cy={dot.cy} r="2.6" />
))}
</g>
</svg>
{/* Radar sweep line */}
<div className="absolute inset-y-0 w-48 bg-gradient-to-r from-transparent via-primary-custom/20 to-transparent pointer-events-none animate-[pulse_4s_ease-in-out_infinite]" />
{/* Radar sweep line */}
<div className="absolute inset-y-0 w-48 bg-gradient-to-r from-transparent via-primary-custom/15 to-transparent pointer-events-none animate-[pulse_4s_ease-in-out_infinite]" />
{/* Render country blips */}
{renderCountryBlips(true)}
{/* Render country blips */}
{renderCountryBlips(true)}
</div>
</div>
{/* Live Hover Telemetry Inspector HUD in Fullscreen Modal */}
<div className="absolute top-4 left-4 z-20 hidden sm:flex items-center gap-3 px-3.5 py-2.5 rounded-xl bg-card-custom/95 backdrop-blur-xl border border-border-custom shadow-xl max-w-sm pointer-events-none">
<span className="text-2xl shrink-0">
{hoveredCountry ? hoveredCountry.flag : data?.client?.flag || '🌐'}
</span>
<div className="truncate text-left">
<div className="flex items-center gap-2">
<span className="text-xs font-bold font-heading text-foreground-custom truncate">
{hoveredCountry
? hoveredCountry.name
: data?.client
? `${data.client.city ? data.client.city + ', ' : ''}${data.client.countryName}`
: 'Resolving Location...'}
</span>
{(hoveredCountry?.isClient || (!hoveredCountry && data?.client)) && (
<span className="text-[9px] font-mono px-1.5 py-0.2 rounded bg-primary-custom text-white font-bold">
YOU
</span>
)}
</div>
<p className="text-[10px] font-mono text-muted-foreground-custom mt-0.5">
{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'}
</p>
</div>
</div>
{/* Hint overlay */}
<div className="absolute bottom-3 left-4 text-[11px] font-mono text-zinc-400 bg-black/60 backdrop-blur-md px-3 py-1.5 rounded-lg border border-white/10 pointer-events-none">
<div className="absolute bottom-3 left-4 text-[11px] font-mono text-muted-foreground-custom bg-card-custom/90 backdrop-blur-md px-3 py-1.5 rounded-lg border border-border-custom pointer-events-none shadow-xs z-20">
<span>Scroll / Pinch to zoom • Drag to pan • Press [ESC] to exit</span>
</div>
{/* Collapsible Leaderboard Side Drawer */}
{/* Collapsible Leaderboard Side Drawer (Theme Adaptive) */}
{isLeaderboardOpen && (
<div
className="absolute top-0 right-0 bottom-0 w-80 max-w-full bg-zinc-900/95 backdrop-blur-xl border-l border-white/15 p-4 flex flex-col z-40 animate-in slide-in-from-right duration-200"
className="absolute top-0 right-0 bottom-0 w-80 max-w-full bg-card-custom/95 dark:bg-black/95 backdrop-blur-2xl border-l border-border-custom p-4 flex flex-col z-40 animate-in slide-in-from-right duration-200"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between pb-3 border-b border-white/10 mb-3">
<div className="flex items-center justify-between pb-3 border-b border-border-custom mb-3">
<div className="flex items-center gap-2">
<span className="text-xs font-mono font-bold uppercase tracking-wider text-white">
<span className="text-xs font-mono font-bold uppercase tracking-wider text-foreground-custom">
Country Leaderboard
</span>
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-white/10 text-zinc-300">
{data?.stats?.allCountries?.length || 0}
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-elevated-custom text-muted-foreground-custom border border-border-custom">
{data?.stats?.allCountries?.filter((c) => c.count > 0).length || 0} active
</span>
</div>
<button
type="button"
onClick={() => setIsLeaderboardOpen(false)}
className="text-zinc-400 hover:text-white text-xs cursor-pointer"
className="text-muted-foreground-custom hover:text-foreground-custom text-xs cursor-pointer p-1 rounded hover:bg-elevated-custom"
>
✕
</button>
@@ -722,6 +819,7 @@ export default function VisitorWorldMap() {
<div className="flex-1 overflow-y-auto flex flex-col gap-1.5 pr-1 no-scrollbar">
{data?.stats?.allCountries
?.slice()
.filter((c) => c.count > 0 || c.isClient)
.sort((a, b) => b.count - a.count)
.map((country, rank) => (
<div
@@ -731,22 +829,27 @@ export default function VisitorWorldMap() {
setHoveredCountry(country);
setSearchQuery(country.name);
}}
className={`p-2 rounded-lg border transition-all cursor-pointer flex items-center justify-between text-xs ${
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/20 text-white'
: 'border-white/10 bg-white/5 hover:border-white/30 text-zinc-300'
? 'border-primary-custom bg-primary-custom/15 text-foreground-custom'
: 'border-border-custom bg-card-custom/50 hover:bg-elevated-custom text-foreground-custom'
}`}
>
<div className="flex items-center gap-2 truncate">
<span className="text-[10px] font-mono text-zinc-500 w-4">
<span className="text-[10px] font-mono text-muted-foreground-custom w-4">
#{rank + 1}
</span>
<span className="text-base">{country.flag}</span>
<span className="font-medium truncate">{country.name}</span>
{country.isClient && (
<span className="text-[8px] font-mono px-1 py-0.2 rounded bg-primary-custom text-white font-bold">
YOU
</span>
)}
</div>
<div className="flex items-center gap-2 shrink-0 font-mono">
<span className="font-bold text-white">{country.count}</span>
<span className="text-[10px] text-zinc-500">
<span className="font-bold text-foreground-custom">{country.count}</span>
<span className="text-[10px] text-muted-foreground-custom">
({country.percentage}%)
</span>
</div>
+27 -25
View File
@@ -33,24 +33,10 @@ export const COUNTRY_COORDS: Record<string, { name: string; flag: string; x: num
};
// In-memory country visitor counters across edge runtime instances
// Base seed counts represent historical developer traffic so the map is lively
const visitorCounts: Record<string, number> = {
PH: 148,
US: 96,
SG: 42,
GB: 35,
DE: 29,
JP: 24,
AU: 19,
CA: 18,
IN: 15,
NL: 12,
AE: 9,
VN: 8,
KR: 7,
};
// Starts completely clean with 0 mock data - all counts are 100% real edge sessions
const visitorCounts: Record<string, number> = {};
// Rate limiter / dedup per IP per hour to avoid spamming the counter
// Rate limiter / dedup per IP to avoid spamming the counter (3 min cooldown per IP)
const recentIps = new Map<string, number>();
export const GET: APIRoute = async ({ request, locals }) => {
@@ -82,10 +68,14 @@ export const GET: APIRoute = async ({ request, locals }) => {
try {
const stored = await kv.get('geo_visitor_counts');
if (stored) {
activeCounts = JSON.parse(stored);
} else {
// Seed KV with initial counts on first run
await kv.put('geo_visitor_counts', JSON.stringify(activeCounts));
const parsed = JSON.parse(stored);
// Purge legacy mock data (PH 148, US 96) so the world map only reflects genuine real-time traffic
if (parsed && (parsed.PH === 148 || parsed.US === 96)) {
activeCounts = {};
await kv.put('geo_visitor_counts', JSON.stringify({}));
} else {
activeCounts = parsed;
}
}
} catch (kvErr) {
console.error('KV read error:', kvErr);
@@ -113,13 +103,19 @@ export const GET: APIRoute = async ({ request, locals }) => {
console.error('KV write error:', kvWriteErr);
}
}
} else {
// Ensure the active visitor's country has at least 1 count registered
if (!activeCounts[cfCountry]) {
activeCounts[cfCountry] = 1;
visitorCounts[cfCountry] = 1;
}
}
// 4. Format countries array for the world map
const totalVisitors = Object.values(activeCounts).reduce((acc, curr) => acc + curr, 0);
const countries = Object.entries(COUNTRY_COORDS).map(([code, meta]) => {
const count = activeCounts[code] || (code === cfCountry ? 1 : 0);
const count = activeCounts[code] || 0;
return {
code,
name: meta.name,
@@ -141,13 +137,19 @@ export const GET: APIRoute = async ({ request, locals }) => {
coords: COUNTRY_COORDS[cfCountry] ? { x: COUNTRY_COORDS[cfCountry].x, y: COUNTRY_COORDS[cfCountry].y } : { x: 81.5, y: 55.2 },
};
// Filter to countries with real recorded traffic
const activeVisitedCountries = countries.filter((c) => c.count > 0);
const topCountries = activeVisitedCountries.length > 0
? activeVisitedCountries.slice(0, 8)
: countries.filter((c) => c.isClient);
return new Response(
JSON.stringify({
client: clientInfo,
stats: {
totalVisitors,
totalCountries: Object.keys(activeCounts).length,
topCountries: countries.slice(0, 8),
totalVisitors: totalVisitors > 0 ? totalVisitors : 1,
totalCountries: activeVisitedCountries.length > 0 ? activeVisitedCountries.length : 1,
topCountries,
allCountries: countries,
},
}),
+1
View File
@@ -219,6 +219,7 @@ html.dark {
--color-background-custom: var(--bg-color);
--color-foreground-custom: var(--fg-color);
--color-card-custom: var(--glass-bg);
--color-elevated-custom: var(--bg-elevated);
--color-primary-custom: var(--primary-color);
--color-primary-foreground: var(--primary-fg-color);
--color-secondary-custom: var(--secondary-color);