Skip to content

Commit dc9b885

Browse files
committed
Add Freebuff install prompt to live page
1 parent e61457e commit dc9b885

2 files changed

Lines changed: 91 additions & 3 deletions

File tree

freebuff/web/src/app/live/live-client.tsx

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
'use client'
22

33
import { 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'
57
import { useEffect, useMemo, useState } from 'react'
68

9+
import { CopyButton } from '@/components/copy-button'
10+
711
import type { FreebuffLiveStats } from '@/server/live-stats'
812
import type { LucideIcon } from 'lucide-react'
913

14+
const INSTALL_COMMAND = 'npm install -g freebuff'
1015
const POLL_MS = 15_000
1116
const MAP_SIZE = { width: 1000, height: 520 }
1217
const 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+
5163
function 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+
310389
export 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
}

freebuff/web/src/components/footer.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
'use client'
2+
13
import Image from 'next/image'
24
import Link from 'next/link'
5+
import { usePathname } from 'next/navigation'
36

47
export function Footer() {
8+
const pathname = usePathname()
9+
10+
if (pathname === '/live') return null
11+
512
return (
613
<footer className="w-full">
714
<div className="container mx-auto flex flex-col gap-4 py-8 px-4">

0 commit comments

Comments
 (0)