11'use client'
22
33import { motion } from 'framer-motion'
4- import { Cpu , Globe2 } from 'lucide-react'
4+ import { ChevronDown , Cpu , Globe2 } from 'lucide-react'
5+ import Image from 'next/image'
6+ import Link from 'next/link'
57import { useEffect , useMemo , useState } from 'react'
68
9+ import { CopyButton } from '@/components/copy-button'
10+
711import type { FreebuffLiveStats } from '@/server/live-stats'
812import type { LucideIcon } from 'lucide-react'
913
14+ const INSTALL_COMMAND = 'npm install -g freebuff'
1015const POLL_MS = 15_000
1116const MAP_SIZE = { width : 1000 , height : 520 }
1217const REGION_NAMES = new Intl . DisplayNames ( [ 'en' ] , { type : 'region' } )
@@ -48,6 +53,13 @@ const LAND_PATHS = [
4853 'M421 96 C448 80 495 83 516 105 C486 118 454 121 421 96Z' ,
4954]
5055
56+ const SETUP_STEPS = [
57+ 'Open your terminal' ,
58+ 'Navigate to your project' ,
59+ INSTALL_COMMAND ,
60+ 'freebuff' ,
61+ ]
62+
5163function countryName ( code : string ) : string {
5264 return code === 'UNKNOWN' ? 'Unknown' : ( REGION_NAMES . of ( code ) ?? code )
5365}
@@ -307,6 +319,73 @@ function CountryList({ stats }: { stats: FreebuffLiveStats }) {
307319 )
308320}
309321
322+ function InstallCallout ( ) {
323+ const [ isOpen , setIsOpen ] = useState ( false )
324+
325+ return (
326+ < section className = "container mx-auto px-4 pb-10" >
327+ < div className = "grid gap-4 rounded-lg border border-white/10 bg-white/[0.04] p-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.05)] md:grid-cols-[minmax(220px,0.7fr)_minmax(0,1fr)] md:items-center" >
328+ < Link
329+ href = "/"
330+ className = "group flex items-center gap-3 rounded-md transition-colors hover:text-acid-matrix"
331+ >
332+ < Image
333+ src = "/logo-icon.png"
334+ alt = "Freebuff"
335+ width = { 32 }
336+ height = { 32 }
337+ className = "rounded-sm"
338+ />
339+ < div >
340+ < div className = "font-serif text-xl tracking-widest text-white transition-colors group-hover:text-acid-matrix" >
341+ freebuff
342+ </ div >
343+ < div className = "text-sm text-white/50" > The free coding agent</ div >
344+ </ div >
345+ </ Link >
346+
347+ < div className = "space-y-3" >
348+ < div className = "flex items-center gap-2 rounded-lg border border-acid-matrix/45 bg-black/35 px-4 py-3 font-mono text-sm shadow-[0_0_24px_rgba(124,255,63,0.12)]" >
349+ < span className = "text-acid-matrix" > $</ span >
350+ < code className = "min-w-0 flex-1 select-all overflow-x-auto whitespace-nowrap text-white/90" >
351+ { INSTALL_COMMAND }
352+ </ code >
353+ < CopyButton value = { INSTALL_COMMAND } />
354+ </ div >
355+
356+ < button
357+ type = "button"
358+ onClick = { ( ) => setIsOpen ( ( open ) => ! open ) }
359+ className = "flex items-center gap-2 text-sm text-white/50 transition-colors hover:text-acid-matrix"
360+ aria-expanded = { isOpen }
361+ >
362+ < span > Install guide</ span >
363+ < motion . span animate = { { rotate : isOpen ? 180 : 0 } } >
364+ < ChevronDown className = "h-4 w-4" aria-hidden />
365+ </ motion . span >
366+ </ button >
367+
368+ { isOpen && (
369+ < ol className = "grid gap-2 text-sm text-white/65 sm:grid-cols-2" >
370+ { SETUP_STEPS . map ( ( step , index ) => (
371+ < li
372+ key = { step }
373+ className = "flex items-center gap-2 rounded-md border border-white/10 bg-black/20 px-3 py-2"
374+ >
375+ < span className = "flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-acid-matrix/35 text-xs text-acid-matrix" >
376+ { index + 1 }
377+ </ span >
378+ < span className = "truncate font-mono" > { step } </ span >
379+ </ li >
380+ ) ) }
381+ </ ol >
382+ ) }
383+ </ div >
384+ </ div >
385+ </ section >
386+ )
387+ }
388+
310389export default function LiveClient ( {
311390 initialStats,
312391} : {
@@ -329,10 +408,10 @@ export default function LiveClient({
329408 < div className = "flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between" >
330409 < div >
331410 < div className = "flex flex-wrap items-baseline gap-x-4 gap-y-2" >
332- < h1 className = "flex max-w-3xl items-center gap-4 font-serif text-4xl leading-tight text-white md:text-6xl" >
411+ < h1 className = "relative max-w-3xl pl-7 font-serif text-4xl leading-tight text-white md:pl-8 md:text-6xl" >
333412 < motion . span
334413 aria-hidden
335- className = "h-3 w-3 shrink-0 rounded-full bg-acid-matrix shadow-[0_0_18px_rgba(124,255,63,0.9)] md:h-4 md:w-4"
414+ className = "absolute left-0 top-1/2 h-3 w-3 -translate-y-1/2 rounded-full bg-acid-matrix shadow-[0_0_18px_rgba(124,255,63,0.9)] md:h-4 md:w-4"
336415 animate = { {
337416 opacity : [ 0.45 , 1 , 0.45 ] ,
338417 scale : [ 0.86 , 1.18 , 0.86 ] ,
@@ -377,6 +456,8 @@ export default function LiveClient({
377456 </ div >
378457 </ div >
379458 </ section >
459+
460+ < InstallCallout />
380461 </ main >
381462 )
382463}
0 commit comments