Skip to content

Commit bd4f031

Browse files
committed
feat: add Forgery page and related components for ecosystem extensions
1 parent bc7fec1 commit bd4f031

6 files changed

Lines changed: 678 additions & 0 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Puzzle, Star } from "lucide-react";
2+
import {
3+
FORGERY_EXTENSIONS,
4+
CATEGORY_ORDER,
5+
CATEGORY_STYLE,
6+
} from "@/lib/forgery-extensions";
7+
import { fetchAllRepoData } from "@/lib/forgery-github";
8+
import { ForgeryGrid } from "@/components/forgery/forgery-grid";
9+
10+
export const revalidate = 3600;
11+
12+
export const metadata = {
13+
title: "Forgery — Ecosystem Extensions",
14+
description:
15+
"Open-source Go libraries built alongside Forge. From authentication to AI agents, billing to webhooks.",
16+
};
17+
18+
export default async function ForgeryPage() {
19+
const githubData = await fetchAllRepoData(
20+
FORGERY_EXTENSIONS.map((e) => ({
21+
slug: e.slug,
22+
fallbackStars: e.fallbackStars,
23+
fallbackVersion: e.fallbackVersion,
24+
description: e.description,
25+
})),
26+
);
27+
28+
const enrichedExtensions = FORGERY_EXTENSIONS.map((ext) => {
29+
const live = githubData.get(ext.slug);
30+
return {
31+
slug: ext.slug,
32+
name: ext.name,
33+
description: live?.description ?? ext.description,
34+
category: ext.category,
35+
iconBg: ext.iconBg,
36+
iconColor: ext.iconColor,
37+
gradient: ext.gradient,
38+
stars: live?.stars ?? ext.fallbackStars,
39+
latestVersion: live?.latestVersion ?? ext.fallbackVersion,
40+
githubUrl: `https://github.com/xraph/${ext.slug}`,
41+
modulePath: ext.modulePath,
42+
};
43+
});
44+
45+
const totalStars = enrichedExtensions.reduce((sum, e) => sum + e.stars, 0);
46+
47+
return (
48+
<main className="container max-w-6xl mx-auto px-6 py-16 md:py-24">
49+
{/* Header */}
50+
<div className="mb-16">
51+
<div className="inline-flex items-center gap-2 border border-fd-border bg-fd-card/80 backdrop-blur-sm px-3 py-1.5 text-xs font-medium text-fd-muted-foreground mb-4">
52+
<Puzzle className="size-3" />
53+
{FORGERY_EXTENSIONS.length} ecosystem libraries
54+
</div>
55+
<h1 className="text-4xl font-bold mb-4 tracking-tight">Forgery</h1>
56+
<p className="text-lg text-fd-muted-foreground max-w-2xl">
57+
Standalone Go libraries built alongside Forge, each solving a specific
58+
infrastructure problem. Use them with Forge or completely standalone
59+
&mdash; no framework lock-in.
60+
</p>
61+
62+
{totalStars > 0 && (
63+
<div className="mt-6 inline-flex items-center gap-3">
64+
<div className="flex items-center gap-1.5 border border-fd-border bg-fd-card/60 px-3 py-1.5 text-sm">
65+
<Star className="size-3.5 text-amber-500" />
66+
<span className="font-mono font-semibold">
67+
{totalStars.toLocaleString()}
68+
</span>
69+
<span className="text-fd-muted-foreground text-xs">
70+
total stars
71+
</span>
72+
</div>
73+
</div>
74+
)}
75+
</div>
76+
77+
{/* Category filter + animated grid */}
78+
<ForgeryGrid
79+
extensions={enrichedExtensions}
80+
categoryOrder={CATEGORY_ORDER}
81+
categoryStyle={CATEGORY_STYLE}
82+
/>
83+
</main>
84+
);
85+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { motion, AnimatePresence } from "framer-motion";
5+
import { ExternalLink, Star, Tag } from "lucide-react";
6+
import type { ExtensionCategory } from "@/lib/forgery-extensions";
7+
import { FORGERY_ICON_MAP } from "@/lib/forgery-extensions";
8+
9+
function CardDecorator() {
10+
return (
11+
<>
12+
<span className="border-primary absolute -left-px -top-px block size-2 border-l-2 border-t-2" />
13+
<span className="border-primary absolute -right-px -top-px block size-2 border-r-2 border-t-2" />
14+
<span className="border-primary absolute -bottom-px -left-px block size-2 border-b-2 border-l-2" />
15+
<span className="border-primary absolute -bottom-px -right-px block size-2 border-b-2 border-r-2" />
16+
</>
17+
);
18+
}
19+
20+
interface EnrichedExtension {
21+
slug: string;
22+
name: string;
23+
description: string;
24+
category: ExtensionCategory;
25+
iconBg: string;
26+
iconColor: string;
27+
gradient: string;
28+
stars: number;
29+
latestVersion: string;
30+
githubUrl: string;
31+
modulePath: string;
32+
}
33+
34+
interface ForgeryGridProps {
35+
extensions: EnrichedExtension[];
36+
categoryOrder: ExtensionCategory[];
37+
categoryStyle: Record<ExtensionCategory, string>;
38+
}
39+
40+
const containerVariants = {
41+
hidden: {},
42+
visible: { transition: { staggerChildren: 0.06 } },
43+
};
44+
45+
const itemVariants = {
46+
hidden: { opacity: 0, y: 20 },
47+
visible: {
48+
opacity: 1,
49+
y: 0,
50+
transition: { duration: 0.45, ease: "easeOut" as const },
51+
},
52+
};
53+
54+
export function ForgeryGrid({
55+
extensions,
56+
categoryOrder,
57+
categoryStyle,
58+
}: ForgeryGridProps) {
59+
const [activeCategory, setActiveCategory] = useState<
60+
ExtensionCategory | "All"
61+
>("All");
62+
63+
const filtered =
64+
activeCategory === "All"
65+
? extensions
66+
: extensions.filter((e) => e.category === activeCategory);
67+
68+
return (
69+
<div>
70+
{/* Category filter pills */}
71+
<div className="flex flex-wrap gap-2 mb-10">
72+
<button
73+
type="button"
74+
onClick={() => setActiveCategory("All")}
75+
className={`text-[10px] font-semibold uppercase tracking-wider px-3 py-1.5 border transition-colors ${
76+
activeCategory === "All"
77+
? "border-fd-foreground/30 bg-fd-foreground/10 text-fd-foreground"
78+
: "border-fd-border text-fd-muted-foreground hover:text-fd-foreground hover:border-fd-border/80"
79+
}`}
80+
>
81+
All ({extensions.length})
82+
</button>
83+
{categoryOrder.map((cat) => {
84+
const count = extensions.filter((e) => e.category === cat).length;
85+
return (
86+
<button
87+
key={cat}
88+
type="button"
89+
onClick={() => setActiveCategory(cat)}
90+
className={`text-[10px] font-semibold uppercase tracking-wider px-3 py-1.5 border transition-colors ${
91+
activeCategory === cat
92+
? categoryStyle[cat]
93+
: "border-fd-border text-fd-muted-foreground hover:text-fd-foreground hover:border-fd-border/80"
94+
}`}
95+
>
96+
{cat} ({count})
97+
</button>
98+
);
99+
})}
100+
</div>
101+
102+
{/* Extension cards */}
103+
<AnimatePresence mode="wait">
104+
<motion.div
105+
key={activeCategory}
106+
variants={containerVariants}
107+
initial="hidden"
108+
animate="visible"
109+
className="grid md:grid-cols-2 lg:grid-cols-3 gap-5"
110+
>
111+
{filtered.map((ext) => {
112+
const Icon = FORGERY_ICON_MAP[ext.slug];
113+
return (
114+
<motion.a
115+
key={ext.slug}
116+
variants={itemVariants}
117+
href={ext.githubUrl}
118+
target="_blank"
119+
rel="noreferrer"
120+
className="group relative overflow-hidden border border-fd-border bg-fd-card transition-all hover:border-fd-border/80 hover:shadow-lg hover:shadow-black/5"
121+
>
122+
<CardDecorator />
123+
<div className="metal-shader absolute inset-0 opacity-40 pointer-events-none" />
124+
<div className="noise-overlay absolute inset-0 pointer-events-none opacity-40" />
125+
126+
{/* Gradient accent strip */}
127+
<div
128+
className={`h-1 w-full bg-linear-to-r ${ext.gradient}`}
129+
/>
130+
131+
<div className="relative z-10 p-5">
132+
{/* Icon + external link */}
133+
<div className="flex items-start justify-between mb-3">
134+
<div
135+
className={`flex items-center justify-center size-9 ${ext.iconBg} ${ext.iconColor}`}
136+
>
137+
{Icon && <Icon className="size-4" />}
138+
</div>
139+
<ExternalLink className="size-3.5 text-fd-muted-foreground group-hover:text-fd-foreground transition-colors mt-1" />
140+
</div>
141+
142+
{/* Name */}
143+
<h3 className="font-semibold text-base mb-1.5">
144+
{ext.name}
145+
</h3>
146+
147+
{/* Category badge */}
148+
<span
149+
className={`text-[9px] font-semibold uppercase tracking-wider px-2 py-0.5 border mb-3 inline-block ${categoryStyle[ext.category]}`}
150+
>
151+
{ext.category}
152+
</span>
153+
154+
{/* Description */}
155+
<p className="text-sm text-fd-muted-foreground leading-relaxed mb-4">
156+
{ext.description}
157+
</p>
158+
159+
{/* Module path */}
160+
<p className="text-[11px] font-mono text-fd-muted-foreground/70 mb-3 truncate">
161+
{ext.modulePath}
162+
</p>
163+
164+
{/* Metadata row */}
165+
<div className="flex items-center justify-between pt-3 border-t border-dashed border-fd-border/60">
166+
<div className="flex items-center gap-1.5 text-xs text-fd-muted-foreground">
167+
<Tag className="size-3" />
168+
<span className="font-mono">{ext.latestVersion}</span>
169+
</div>
170+
{ext.stars > 0 && (
171+
<div className="flex items-center gap-1 text-xs text-fd-muted-foreground">
172+
<Star className="size-3 text-amber-500" />
173+
<span className="font-mono">
174+
{ext.stars.toLocaleString()}
175+
</span>
176+
</div>
177+
)}
178+
</div>
179+
</div>
180+
</motion.a>
181+
);
182+
})}
183+
</motion.div>
184+
</AnimatePresence>
185+
</div>
186+
);
187+
}

docs/src/components/landing/footer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const footerLinks = {
2424
{ label: "FARP Protocol", href: "/docs/farp" },
2525
{ label: "Service Discovery", href: "/docs/farp/discovery" },
2626
{ label: "Roadmap", href: "/roadmap" },
27+
{ label: "Forgery", href: "/forgery" },
2728
],
2829
Community: [
2930
{ label: "GitHub", href: "https://github.com/xraph/forge", external: true },

0 commit comments

Comments
 (0)