spb/tools-spboucher-ai
Public
1import Link from "next/link";2import { homeData } from "@/lib/public/queries";3import { SearchBox } from "@/components/search-box";4import { ToolCard } from "@/components/tool-card";5import { fmtDate } from "@/lib/utils";6import { ArrowRight, Sparkles, ShieldCheck, Layers, BookMarked } from "lucide-react";78export const revalidate = 300;910const PATHS = [11 { href: "/tools?exec=local&category=ai", title: "Run AI locally", text: "Inference engines and model tooling that work on Apple Silicon and your own servers." },12 { href: "/tools?category=research", title: "Write and publish research", text: "Discovery, references, LaTeX, reproducible documents and dissemination." },13 { href: "/tools?category=market-data-apis&category=sec-filings&category=hf-finance-research", title: "Financial data for research", text: "Market data, fundamentals, filings and the engines that process them." },14 { href: "/tools?source=open-source&selfhost=1", title: "Self-host everything", text: "Open-source infrastructure you can run on your own hardware." },15];1617export default async function Home() {18 const { stats, featured, recent, tree, collections } = await homeData();19 return (20 <>21 <section className="relative overflow-hidden border-b border-line">22 <div className="grain pointer-events-none absolute inset-0 opacity-50 [mask-image:radial-gradient(60%_60%_at_50%_0%,black,transparent)]" aria-hidden />23 <div className="relative mx-auto max-w-7xl px-4 pb-16 pt-14 sm:px-6 sm:pt-20">24 <p className="eyebrow rise">Tool Atlas · curated, cited, verified</p>25 <h1 className="display h-display rise rise-1 mt-3 max-w-4xl">An encyclopedia of the software, AI systems and research tools that matter.</h1>26 <p className="rise rise-2 mt-5 max-w-2xl text-lg text-fg-2">Every entry is written from primary sources, states its license and pricing model without guesswork, lists real limitations, and shows when its links were last checked.</p>27 <div className="rise rise-3 mt-8"><SearchBox size="lg" /></div>28 <dl className="rise rise-3 mt-8 grid max-w-3xl grid-cols-2 gap-4 sm:grid-cols-4">29 {[["Tools", stats.tools], ["In-depth entries", stats.deep], ["Categories", stats.categories], ["Collections", stats.collections]].map(([k, v]) => (30 <div key={String(k)}><dt className="text-xs uppercase tracking-wider text-fg-3">{k}</dt><dd className="display text-3xl">{v as number}</dd></div>31 ))}32 </dl>33 </div>34 </section>3536 <section className="mx-auto max-w-7xl px-4 py-12 sm:px-6">37 <div className="mb-5 flex items-end justify-between gap-4">38 <div><p className="eyebrow">Browse</p><h2 className="display h2 mt-1">By discipline</h2></div>39 <Link href="/categories" className="inline-flex items-center gap-1 text-sm text-accent hover:underline">All categories <ArrowRight className="h-4 w-4" aria-hidden /></Link>40 </div>41 <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">42 {tree.map((c) => (43 <Link key={c.slug} href={`/categories/${c.slug}`} className="card group p-4">44 <div className="flex items-center justify-between"><h3 className="font-semibold tracking-tight">{c.name}</h3><span className="chip">{c.count}</span></div>45 <p className="mt-1.5 line-clamp-2 text-sm text-fg-2">{c.description}</p>46 <p className="mt-3 line-clamp-1 text-xs text-fg-3">{c.children.slice(0, 4).map((x) => x.name).join(" · ")}</p>47 </Link>48 ))}49 </div>50 </section>5152 {featured.length > 0 && (53 <section className="mx-auto max-w-7xl px-4 py-8 sm:px-6">54 <div className="mb-5 flex items-end justify-between gap-4">55 <div><p className="eyebrow inline-flex items-center gap-1"><Sparkles className="h-3.5 w-3.5" aria-hidden /> Featured</p><h2 className="display h2 mt-1">Editor's selection</h2></div>56 <Link href="/tools?depth=deep" className="inline-flex items-center gap-1 text-sm text-accent hover:underline">All in-depth entries <ArrowRight className="h-4 w-4" aria-hidden /></Link>57 </div>58 <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">{featured.map((t) => <ToolCard key={t.id} t={t} />)}</div>59 </section>60 )}6162 <section className="mx-auto max-w-7xl px-4 py-8 sm:px-6">63 <div className="mb-5"><p className="eyebrow inline-flex items-center gap-1"><Layers className="h-3.5 w-3.5" aria-hidden /> Discovery paths</p><h2 className="display h2 mt-1">Start from a goal</h2></div>64 <div className="grid gap-3 md:grid-cols-2 lg:grid-cols-4">65 {PATHS.map((p) => <Link key={p.href} href={p.href} className="card p-4"><h3 className="font-semibold">{p.title}</h3><p className="mt-1 text-sm text-fg-2">{p.text}</p></Link>)}66 </div>67 </section>6869 {collections.length > 0 && (70 <section className="mx-auto max-w-7xl px-4 py-8 sm:px-6">71 <div className="mb-5 flex items-end justify-between gap-4">72 <div><p className="eyebrow inline-flex items-center gap-1"><BookMarked className="h-3.5 w-3.5" aria-hidden /> Collections</p><h2 className="display h2 mt-1">Curated stacks</h2></div>73 <Link href="/collections" className="inline-flex items-center gap-1 text-sm text-accent hover:underline">All collections <ArrowRight className="h-4 w-4" aria-hidden /></Link>74 </div>75 <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">76 {collections.slice(0, 6).map((c) => (77 <Link key={c.slug} href={`/collections/${c.slug}`} className="card p-5">78 <h3 className="display text-xl">{c.title}</h3>79 <p className="mt-2 line-clamp-2 text-sm text-fg-2">{c.description}</p>80 <p className="mt-3 text-xs text-fg-3">{c.count} tools · {c.preview.map((p) => p.name).join(", ")}</p>81 </Link>82 ))}83 </div>84 </section>85 )}8687 {recent.length > 0 && (88 <section className="mx-auto max-w-7xl px-4 py-8 pb-4 sm:px-6">89 <div className="mb-5"><p className="eyebrow inline-flex items-center gap-1"><ShieldCheck className="h-3.5 w-3.5" aria-hidden /> Recently verified</p><h2 className="display h2 mt-1">Links checked {stats.lastVerified ? fmtDate(stats.lastVerified) : "soon"}</h2></div>90 <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">{recent.map((t) => <ToolCard key={t.id} t={t} />)}</div>91 </section>92 )}93 </>94 );95}96