@@ -7,16 +7,23 @@ import Link from 'next/link'
77import { useEffect , useState } from 'react'
88
99import { CopyButton } from '@/components/copy-button'
10+ import { cn } from '@/lib/utils'
1011
1112import { COUNTRY_POINTS , WORLD_LAND_PATHS } from './world-map-data'
1213
1314import type { FreebuffLiveStats } from '@/server/live-stats'
1415import type { LucideIcon } from 'lucide-react'
1516
1617const INSTALL_COMMAND = 'npm install -g freebuff'
17- const POLL_MS = 15_000
18+ const POLL_MS = 60_000
1819const MAP_SIZE = { width : 1000 , height : 520 }
1920const REGION_NAMES = new Intl . DisplayNames ( [ 'en' ] , { type : 'region' } )
21+ const EMPTY_LIVE_STATS : FreebuffLiveStats = {
22+ totalLiveUsers : 0 ,
23+ countries : [ ] ,
24+ models : [ ] ,
25+ generatedAt : '1970-01-01T00:00:00.000Z' ,
26+ }
2027type CountryPoint = readonly [ lat : number , lon : number ]
2128type PlottedCountry = FreebuffLiveStats [ 'countries' ] [ number ] & {
2229 point : CountryPoint
@@ -106,7 +113,10 @@ function isPlottedCountry(
106113 return country !== null
107114}
108115
109- function useLiveStats ( initialStats : FreebuffLiveStats ) {
116+ function useLiveStats (
117+ initialStats : FreebuffLiveStats ,
118+ options : { refreshOnMount ?: boolean } = { } ,
119+ ) {
110120 const [ stats , setStats ] = useState ( initialStats )
111121
112122 useEffect ( ( ) => {
@@ -123,12 +133,16 @@ function useLiveStats(initialStats: FreebuffLiveStats) {
123133 }
124134 }
125135
136+ if ( options . refreshOnMount ) {
137+ void refresh ( )
138+ }
139+
126140 const interval = window . setInterval ( refresh , POLL_MS )
127141 return ( ) => {
128142 isMounted = false
129143 window . clearInterval ( interval )
130144 }
131- } , [ ] )
145+ } , [ options . refreshOnMount ] )
132146
133147 return stats
134148}
@@ -186,7 +200,15 @@ function EmptyState({ children }: { children: React.ReactNode }) {
186200 )
187201}
188202
189- function WorldMap ( { stats } : { stats : FreebuffLiveStats } ) {
203+ function WorldMap ( {
204+ stats,
205+ compact = false ,
206+ isLoading = false ,
207+ } : {
208+ stats : FreebuffLiveStats
209+ compact ?: boolean
210+ isLoading ?: boolean
211+ } ) {
190212 const maxCount = Math . max ( 1 , ...stats . countries . map ( ( row ) => row . count ) )
191213 const plottedCountries = stats . countries
192214 . map ( ( country ) => {
@@ -199,20 +221,25 @@ function WorldMap({ stats }: { stats: FreebuffLiveStats }) {
199221 return (
200222 < section className = "relative self-start overflow-hidden rounded-lg border border-white/10 bg-[#020807] shadow-[0_24px_90px_rgba(0,0,0,0.34),inset_0_1px_0_rgba(255,255,255,0.05)]" >
201223 < div className = "pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_28%,rgba(34,211,238,0.14),transparent_38%),linear-gradient(180deg,rgba(124,255,63,0.04),rgba(0,0,0,0.2))]" />
202- < div className = "pointer-events-none absolute left-4 top-4 z-10 rounded-md border border-white/10 bg-black/45 px-3 py-2 backdrop-blur md:left-5 md:top-5" >
203- < div className = "font-mono text-[10px] uppercase tracking-[0.22em] text-white/45" >
204- Active countries
205- </ div >
206- < div className = "mt-1 text-2xl font-serif leading-none text-white" >
207- { stats . countries . length . toLocaleString ( ) }
224+ { ! compact && (
225+ < div className = "pointer-events-none absolute left-4 top-4 z-10 rounded-md border border-white/10 bg-black/45 px-3 py-2 backdrop-blur md:left-5 md:top-5" >
226+ < div className = "font-mono text-[10px] uppercase tracking-[0.22em] text-white/45" >
227+ Active countries
228+ </ div >
229+ < div className = "mt-1 text-2xl font-serif leading-none text-white" >
230+ { stats . countries . length . toLocaleString ( ) }
231+ </ div >
208232 </ div >
209- </ div >
233+ ) }
210234
211235 < svg
212236 viewBox = { `0 0 ${ MAP_SIZE . width } ${ MAP_SIZE . height } ` }
213237 role = "img"
214238 aria-label = "World map of live Freebuff users by country"
215- className = "relative h-[300px] w-full md:h-[520px]"
239+ className = { cn (
240+ 'relative w-full' ,
241+ compact ? 'h-[230px] md:h-[380px]' : 'h-[300px] md:h-[520px]' ,
242+ ) }
216243 >
217244 < defs >
218245 < pattern
@@ -355,15 +382,20 @@ function WorldMap({ stats }: { stats: FreebuffLiveStats }) {
355382 } ) }
356383 </ svg >
357384
358- { plottedCountries . length === 0 && (
385+ { plottedCountries . length === 0 && isLoading && (
386+ < div className = "absolute inset-x-6 top-1/2 mx-auto max-w-sm -translate-y-1/2 rounded-lg border border-white/10 bg-black/55 px-5 py-4 text-center backdrop-blur" >
387+ < div className = "font-serif text-2xl text-white" > Loading live map</ div >
388+ </ div >
389+ ) }
390+ { plottedCountries . length === 0 && ! isLoading && (
359391 < div className = "absolute inset-x-6 top-1/2 mx-auto max-w-sm -translate-y-1/2 rounded-lg border border-white/10 bg-black/55 px-5 py-4 text-center backdrop-blur" >
360392 < div className = "font-serif text-2xl text-white" > Standing by</ div >
361393 < div className = "mt-1 text-sm text-white/50" >
362394 Live sessions will appear here as users start Freebuff.
363395 </ div >
364396 </ div >
365397 ) }
366- { unplottedCount > 0 && (
398+ { ! compact && unplottedCount > 0 && (
367399 < div className = "absolute bottom-4 right-4 rounded-md border border-white/10 bg-black/45 px-3 py-2 text-xs text-white/48 backdrop-blur" >
368400 { unplottedCount } region{ unplottedCount === 1 ? '' : 's' } listed
369401 off-map
@@ -373,6 +405,46 @@ function WorldMap({ stats }: { stats: FreebuffLiveStats }) {
373405 )
374406}
375407
408+ export function CompactLiveStats ( {
409+ initialStats = EMPTY_LIVE_STATS ,
410+ } : {
411+ initialStats ?: FreebuffLiveStats
412+ } ) {
413+ const stats = useLiveStats ( initialStats , { refreshOnMount : true } )
414+ const isLoading = stats . generatedAt === EMPTY_LIVE_STATS . generatedAt
415+
416+ return (
417+ < section className = "relative overflow-hidden bg-black py-14 md:py-20" >
418+ < div className = "absolute inset-0 bg-[linear-gradient(rgba(124,255,63,0.04)_1px,transparent_1px),linear-gradient(90deg,rgba(34,211,238,0.035)_1px,transparent_1px)] bg-[size:56px_56px]" />
419+ < div className = "relative container mx-auto px-4" >
420+ < div className = "mb-6 flex flex-col gap-3 md:mb-8 md:flex-row md:items-end md:justify-between" >
421+ < div >
422+ < div className = "flex items-center gap-3" >
423+ < motion . span
424+ className = "h-2.5 w-2.5 rounded-full bg-acid-matrix shadow-[0_0_20px_rgba(124,255,63,0.95)]"
425+ animate = { { opacity : [ 0.45 , 1 , 0.45 ] , scale : [ 0.8 , 1.2 , 0.8 ] } }
426+ transition = { {
427+ duration : 1.9 ,
428+ repeat : Infinity ,
429+ ease : 'easeInOut' ,
430+ } }
431+ />
432+ < span className = "font-mono text-xs uppercase tracking-[0.22em] text-white/48" >
433+ Active users
434+ </ span >
435+ </ div >
436+ < div className = "mt-2 font-mono text-5xl font-medium leading-none text-acid-matrix neon-text md:text-7xl" >
437+ { isLoading ? '...' : stats . totalLiveUsers . toLocaleString ( ) }
438+ </ div >
439+ </ div >
440+ </ div >
441+
442+ < WorldMap stats = { stats } compact isLoading = { isLoading } />
443+ </ div >
444+ </ section >
445+ )
446+ }
447+
376448function ModelBars ( { stats } : { stats : FreebuffLiveStats } ) {
377449 const maxCount = Math . max ( 1 , ...stats . models . map ( ( model ) => model . count ) )
378450
0 commit comments