Skip to content

Commit 113254b

Browse files
Copilothotlong
andcommitted
feat: Redesign homepage with shadcn/ui and fumadocs best practices
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent ea01f0d commit 113254b

File tree

13 files changed

+505
-127
lines changed

13 files changed

+505
-127
lines changed

apps/docs/app/[lang]/page.tsx

Lines changed: 36 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import Link from 'next/link';
21
import { Database, FileJson, Layers, ShieldCheck, Zap, Globe, Cpu, LayoutTemplate, Bot } from 'lucide-react';
32
import { HomeLayout } from 'fumadocs-ui/layouts/home';
43
import { baseOptions } from '@/app/layout.config';
54
import { getHomepageTranslations } from '@/lib/homepage-i18n';
5+
import { HeroSection } from '@/components/hero-section';
6+
import { CodePreview } from '@/components/code-preview';
7+
import { FeatureCard } from '@/components/feature-card';
8+
import { PersonaCard } from '@/components/persona-card';
69

710
export default async function HomePage({
811
params,
@@ -17,73 +20,21 @@ export default async function HomePage({
1720
<main className="flex min-h-screen flex-col items-center justify-center text-center px-4 py-16 sm:py-24 md:py-32 overflow-hidden bg-background text-foreground selection:bg-primary/20">
1821

1922
{/* Hero Section */}
20-
<div className="relative z-10 max-w-5xl space-y-8">
21-
<div className="inline-flex items-center rounded-full border border-fd-primary/20 bg-fd-primary/5 px-3 py-1 text-sm text-fd-primary backdrop-blur-sm transition-colors hover:bg-fd-primary/10 hover:border-fd-primary/30">
22-
<span className="flex h-2 w-2 rounded-full bg-fd-primary mr-2 animate-pulse"></span>
23-
{t.badge.status} {t.badge.version}
24-
</div>
25-
26-
<h1 className="text-5xl font-extrabold tracking-tight sm:text-7xl md:text-8xl bg-gradient-to-br from-foreground via-foreground/90 to-fd-primary/60 bg-clip-text text-transparent pb-4">
27-
{t.hero.title.line1} <br/> {t.hero.title.line2}
28-
</h1>
29-
30-
<p className="mx-auto max-w-2xl text-lg text-fd-foreground/80 sm:text-xl leading-relaxed">
31-
{t.hero.subtitle.line1}
32-
<br className="hidden sm:inline" />
33-
<span className="text-fd-foreground font-semibold">{t.hero.subtitle.line2}</span>
34-
</p>
23+
<HeroSection
24+
badge={t.badge}
25+
title={t.hero.title}
26+
subtitle={t.hero.subtitle}
27+
cta={t.hero.cta}
28+
/>
3529

36-
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
37-
<Link
38-
href="/docs/guides/getting-started"
39-
className="inline-flex h-12 items-center justify-center rounded-lg bg-fd-primary px-8 text-sm font-medium text-fd-primary-foreground shadow-lg shadow-fd-primary/20 transition-all hover:bg-fd-primary/90 hover:scale-105 hover:shadow-fd-primary/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring disabled:pointer-events-none disabled:opacity-50"
40-
>
41-
{t.hero.cta.primary}
42-
</Link>
43-
<Link
44-
href="/docs"
45-
className="inline-flex h-12 items-center justify-center rounded-lg border border-fd-border bg-fd-card/50 px-8 text-sm font-medium shadow-sm transition-all hover:bg-fd-accent hover:text-fd-accent-foreground backdrop-blur-sm hover:scale-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring disabled:pointer-events-none disabled:opacity-50"
46-
>
47-
{t.hero.cta.secondary}
48-
</Link>
49-
</div>
50-
51-
{/* Code Preview Decorator */}
52-
<div className="relative mx-auto mt-12 w-full max-w-3xl transform rounded-xlbg-gradient-to-br from-fd-border/50 to-fd-border/10 p-2 opacity-90 transition-all hover:scale-[1.01] hover:opacity-100 sm:mt-16">
53-
<div className="overflow-hidden rounded-xl border border-fd-border bg-fd-card shadow-2xl">
54-
<div className="flex items-center gap-2 border-b border-fd-border bg-fd-muted/50 px-4 py-3">
55-
<div className="h-3 w-3 rounded-full bg-red-500/80" />
56-
<div className="h-3 w-3 rounded-full bg-yellow-500/80" />
57-
<div className="h-3 w-3 rounded-full bg-green-500/80" />
58-
<div className="ml-2 text-xs font-medium text-fd-muted-foreground font-mono">
59-
{t.codePreview.filename}
60-
</div>
61-
</div>
62-
<div className="overflow-x-auto p-6 text-left">
63-
<pre className="font-mono text-sm leading-6">
64-
<span className="text-purple-400">import</span> <span className="text-fd-foreground">{'{'}</span> <span className="text-yellow-300">ObjectProtocol</span> <span className="text-fd-foreground">{'}'}</span> <span className="text-purple-400">from</span> <span className="text-green-300">'@objectstack/spec'</span>;<br/><br/>
65-
<span className="text-purple-400">export const</span> <span className="text-blue-300">Issue</span> <span className="text-purple-400">=</span> <span className="text-yellow-300">ObjectProtocol</span>.<span className="text-blue-300">define</span>(<span className="text-fd-foreground">{'{'}</span><br/>
66-
&nbsp;&nbsp;code: <span className="text-green-300">'issue_tracker'</span>,<br/>
67-
&nbsp;&nbsp;fields: <span className="text-fd-foreground">{'{'}</span><br/>
68-
&nbsp;&nbsp;&nbsp;&nbsp;summary: <span className="text-yellow-300">Field</span>.<span className="text-blue-300">text</span>(<span className="text-fd-foreground">{'{'}</span> required: <span className="text-red-300">true</span> <span className="text-fd-foreground">{'}'}</span>),<br/>
69-
&nbsp;&nbsp;&nbsp;&nbsp;priority: <span className="text-yellow-300">Field</span>.<span className="text-blue-300">select</span>([<span className="text-green-300">'P0'</span>, <span className="text-green-300">'P1'</span>, <span className="text-green-300">'P2'</span>]),<br/>
70-
&nbsp;&nbsp;&nbsp;&nbsp;assignee: <span className="text-yellow-300">Field</span>.<span className="text-blue-300">lookup</span>(<span className="text-green-300">'users'</span>)<br/>
71-
&nbsp;&nbsp;<span className="text-fd-foreground">{'}'}</span>,<br/>
72-
&nbsp;&nbsp;policy: <span className="text-fd-foreground">{'{'}</span> <span className="text-blue-300">audit</span>: <span className="text-red-300">true</span>, <span className="text-blue-300">api_access</span>: <span className="text-green-300">'public'</span> <span className="text-fd-foreground">{'}'}</span><br/>
73-
<span className="text-fd-foreground">{'}'}</span>);
74-
</pre>
75-
</div>
76-
</div>
77-
{/* Glow Effect behind Code */}
78-
<div className="absolute -inset-4 -z-10 bg-fd-primary/20 blur-3xl opacity-30 rounded-[50%]" />
79-
</div>
80-
</div>
30+
{/* Code Preview */}
31+
<CodePreview filename={t.codePreview.filename} />
8132

8233
{/* Grid Pattern Background */}
8334
<div className="absolute inset-0 -z-10 h-full w-full bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)] pointer-events-none" />
8435

8536
{/* Feature Grid */}
86-
<div className="mt-24 grid grid-cols-1 gap-8 text-left sm:grid-cols-2 lg:grid-cols-3 max-w-6xl w-full">
37+
<div className="mt-24 grid grid-cols-1 gap-6 text-left sm:grid-cols-2 lg:grid-cols-3 max-w-6xl w-full">
8738
<FeatureCard
8839
icon={<Database className="h-6 w-6" />}
8940
title={t.features.objectql.title}
@@ -124,31 +75,31 @@ export default async function HomePage({
12475

12576
{/* Personas Section */}
12677
<div className="mt-32 mb-16 w-full max-w-5xl px-4">
127-
<h2 className="text-3xl font-bold tracking-tight mb-12 bg-gradient-to-r from-fd-foreground to-fd-foreground/70 bg-clip-text text-transparent">
128-
{t.personas.heading}
78+
<h2 className="text-3xl sm:text-4xl font-bold tracking-tight mb-12 bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
79+
{t.personas.heading}
12980
</h2>
13081
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
131-
<PersonaCard
132-
icon={<LayoutTemplate className="w-8 h-8 mb-4 text-blue-500" />}
133-
title={t.personas.architect.title}
134-
description={t.personas.architect.description}
135-
href="/docs/concepts/enterprise-patterns"
136-
action={t.personas.architect.action}
137-
/>
138-
<PersonaCard
139-
icon={<Bot className="w-8 h-8 mb-4 text-purple-500" />}
140-
title={t.personas.aiEngineer.title}
141-
description={t.personas.aiEngineer.description}
142-
href="/docs/concepts/ai-codex"
143-
action={t.personas.aiEngineer.action}
144-
/>
145-
<PersonaCard
146-
icon={<Cpu className="w-8 h-8 mb-4 text-green-500" />}
147-
title={t.personas.frameworkBuilder.title}
148-
description={t.personas.frameworkBuilder.description}
149-
href="/docs/specifications/data/architecture"
150-
action={t.personas.frameworkBuilder.action}
151-
/>
82+
<PersonaCard
83+
icon={<LayoutTemplate className="w-8 h-8 text-blue-500" />}
84+
title={t.personas.architect.title}
85+
description={t.personas.architect.description}
86+
href="/docs/concepts/enterprise-patterns"
87+
action={t.personas.architect.action}
88+
/>
89+
<PersonaCard
90+
icon={<Bot className="w-8 h-8 text-purple-500" />}
91+
title={t.personas.aiEngineer.title}
92+
description={t.personas.aiEngineer.description}
93+
href="/docs/concepts/ai-codex"
94+
action={t.personas.aiEngineer.action}
95+
/>
96+
<PersonaCard
97+
icon={<Cpu className="w-8 h-8 text-green-500" />}
98+
title={t.personas.frameworkBuilder.title}
99+
description={t.personas.frameworkBuilder.description}
100+
href="/docs/specifications/data/architecture"
101+
action={t.personas.frameworkBuilder.action}
102+
/>
152103
</div>
153104
</div>
154105

@@ -157,43 +108,3 @@ export default async function HomePage({
157108
);
158109
}
159110

160-
function FeatureCard({ icon, title, description, href }: { icon: React.ReactNode; title: string; description: string; href?: string }) {
161-
const CardContent = (
162-
<div className="group relative h-full rounded-2xl border border-fd-border bg-fd-card p-6 shadow-sm transition-all hover:border-fd-primary/20 hover:shadow-md hover:shadow-fd-primary/5 cursor-pointer">
163-
<div className="mb-4 inline-flex items-center justify-center rounded-lg bg-fd-primary/10 p-2 text-fd-primary transition-colors group-hover:bg-fd-primary/20">
164-
{icon}
165-
</div>
166-
<h3 className="mb-2 text-lg font-semibold text-fd-card-foreground group-hover:text-fd-primary transition-colors">
167-
{title}
168-
</h3>
169-
<p className="text-sm text-fd-foreground/70 leading-relaxed">
170-
{description}
171-
</p>
172-
</div>
173-
);
174-
175-
if (href) {
176-
return (
177-
<Link href={href} className="block h-full">
178-
{CardContent}
179-
</Link>
180-
);
181-
}
182-
183-
return CardContent;
184-
}
185-
186-
function PersonaCard({ icon, title, description, href, action }: { icon: React.ReactNode; title: string; description: string; href: string; action: string }) {
187-
return (
188-
<Link href={href} className="flex flex-col items-start p-8 rounded-2xl bg-fd-secondary/30 border border-fd-border/50 hover:bg-fd-secondary/60 hover:border-fd-primary/30 transition-all group text-left">
189-
{icon}
190-
<h3 className="text-xl font-bold mb-3">{title}</h3>
191-
<p className="text-fd-foreground/70 mb-6 text-sm leading-relaxed flex-grow text-left">
192-
{description}
193-
</p>
194-
<div className="flex items-center text-sm font-semibold text-fd-primary mt-auto group-hover:translate-x-1 transition-transform">
195-
{action} <span className="ml-1"></span>
196-
</div>
197-
</Link>
198-
)
199-
}

apps/docs/app/global.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
@import "tailwindcss";
22
@import "fumadocs-ui/style.css";
3+
4+
@layer base {
5+
:root {
6+
--radius: 0.5rem;
7+
}
8+
}
9+
10+
@layer utilities {
11+
.text-balance {
12+
text-wrap: balance;
13+
}
14+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { cn } from '@/lib/utils';
2+
3+
interface CodePreviewProps {
4+
filename: string;
5+
className?: string;
6+
}
7+
8+
export function CodePreview({ filename, className }: CodePreviewProps) {
9+
return (
10+
<div className={cn(
11+
"relative mx-auto mt-12 w-full max-w-3xl transform rounded-xl bg-gradient-to-br from-border/50 to-border/10 p-[2px] opacity-90 transition-all hover:scale-[1.01] hover:opacity-100 sm:mt-16",
12+
className
13+
)}>
14+
<div className="overflow-hidden rounded-xl border border-border bg-card shadow-2xl">
15+
{/* Window Controls */}
16+
<div className="flex items-center gap-2 border-b border-border bg-muted/50 px-4 py-3">
17+
<div className="h-3 w-3 rounded-full bg-red-500/80 transition-colors hover:bg-red-500" />
18+
<div className="h-3 w-3 rounded-full bg-yellow-500/80 transition-colors hover:bg-yellow-500" />
19+
<div className="h-3 w-3 rounded-full bg-green-500/80 transition-colors hover:bg-green-500" />
20+
<div className="ml-2 text-xs font-medium text-muted-foreground font-mono">
21+
{filename}
22+
</div>
23+
</div>
24+
25+
{/* Code Content */}
26+
<div className="overflow-x-auto p-6 text-left bg-gradient-to-br from-card to-muted/20">
27+
<pre className="font-mono text-sm leading-7">
28+
<code>
29+
<span className="text-purple-400 font-semibold">import</span>{' '}
30+
<span className="text-foreground">{'{'}</span>{' '}
31+
<span className="text-yellow-300">ObjectProtocol</span>{' '}
32+
<span className="text-foreground">{'}'}</span>{' '}
33+
<span className="text-purple-400 font-semibold">from</span>{' '}
34+
<span className="text-green-300">&apos;@objectstack/spec&apos;</span>;
35+
<br/><br/>
36+
<span className="text-purple-400 font-semibold">export const</span>{' '}
37+
<span className="text-blue-300">Issue</span>{' '}
38+
<span className="text-purple-400">=</span>{' '}
39+
<span className="text-yellow-300">ObjectProtocol</span>.
40+
<span className="text-blue-300">define</span>
41+
<span className="text-foreground">(</span>
42+
<span className="text-foreground">{'{'}</span>
43+
<br/>
44+
&nbsp;&nbsp;<span className="text-sky-300">code</span>: <span className="text-green-300">&apos;issue_tracker&apos;</span>,
45+
<br/>
46+
&nbsp;&nbsp;<span className="text-sky-300">fields</span>: <span className="text-foreground">{'{'}</span>
47+
<br/>
48+
&nbsp;&nbsp;&nbsp;&nbsp;<span className="text-sky-300">summary</span>: <span className="text-yellow-300">Field</span>.<span className="text-blue-300">text</span>(<span className="text-foreground">{'{'}</span> <span className="text-sky-300">required</span>: <span className="text-red-300">true</span> <span className="text-foreground">{'}'}</span>),
49+
<br/>
50+
&nbsp;&nbsp;&nbsp;&nbsp;<span className="text-sky-300">priority</span>: <span className="text-yellow-300">Field</span>.<span className="text-blue-300">select</span>([<span className="text-green-300">&apos;P0&apos;</span>, <span className="text-green-300">&apos;P1&apos;</span>, <span className="text-green-300">&apos;P2&apos;</span>]),
51+
<br/>
52+
&nbsp;&nbsp;&nbsp;&nbsp;<span className="text-sky-300">assignee</span>: <span className="text-yellow-300">Field</span>.<span className="text-blue-300">lookup</span>(<span className="text-green-300">&apos;users&apos;</span>)
53+
<br/>
54+
&nbsp;&nbsp;<span className="text-foreground">{'}'}</span>,
55+
<br/>
56+
&nbsp;&nbsp;<span className="text-sky-300">policy</span>: <span className="text-foreground">{'{'}</span>{' '}
57+
<span className="text-sky-300">audit</span>: <span className="text-red-300">true</span>, {' '}
58+
<span className="text-sky-300">api_access</span>: <span className="text-green-300">&apos;public&apos;</span>{' '}
59+
<span className="text-foreground">{'}'}</span>
60+
<br/>
61+
<span className="text-foreground">{'}'}</span>);
62+
</code>
63+
</pre>
64+
</div>
65+
</div>
66+
67+
{/* Glow Effect */}
68+
<div className="absolute -inset-4 -z-10 bg-primary/20 blur-3xl opacity-30 rounded-[50%]" />
69+
</div>
70+
);
71+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Link from 'next/link';
2+
import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
3+
import { cn } from '@/lib/utils';
4+
5+
interface FeatureCardProps {
6+
icon: React.ReactNode;
7+
title: string;
8+
description: string;
9+
href?: string;
10+
className?: string;
11+
}
12+
13+
export function FeatureCard({ icon, title, description, href, className }: FeatureCardProps) {
14+
const CardContent = (
15+
<Card className={cn(
16+
"group relative h-full p-6 hover:border-primary/20 hover:shadow-md hover:shadow-primary/5 cursor-pointer transition-all duration-300",
17+
className
18+
)}>
19+
<div className="mb-4 inline-flex items-center justify-center rounded-lg bg-primary/10 p-3 text-primary transition-all duration-300 group-hover:bg-primary/20 group-hover:scale-110">
20+
{icon}
21+
</div>
22+
<h3 className="mb-2 text-lg font-semibold text-card-foreground group-hover:text-primary transition-colors">
23+
{title}
24+
</h3>
25+
<p className="text-sm text-foreground/70 leading-relaxed">
26+
{description}
27+
</p>
28+
</Card>
29+
);
30+
31+
if (href) {
32+
return (
33+
<Link href={href} className="block h-full">
34+
{CardContent}
35+
</Link>
36+
);
37+
}
38+
39+
return CardContent;
40+
}

0 commit comments

Comments
 (0)