create-frootkit 1.2.12 → 1.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/auth/src/app/dashboard/layout.tsx +8 -11
- package/templates/auth/src/app/dashboard/page.tsx +31 -27
- package/templates/auth/src/app/layout.tsx +5 -5
- package/templates/auth/src/app/login/page.tsx +11 -4
- package/templates/auth/src/app/page.tsx +278 -120
- package/templates/auth/src/app/signup/page.tsx +12 -6
- package/templates/auth/src/components/AuthForm.tsx +27 -34
- package/templates/auth/src/components/DashboardNav.tsx +41 -61
- package/templates/auth/src/components/MetricCard.tsx +12 -22
- package/templates/auth/src/components/Navbar.tsx +18 -21
- package/templates/auth/src/styles.css +157 -27
- package/templates/blog/src/app/blog/[slug]/page.tsx +85 -46
- package/templates/blog/src/app/layout.tsx +5 -5
- package/templates/blog/src/app/page.tsx +39 -26
- package/templates/blog/src/components/Footer.tsx +11 -9
- package/templates/blog/src/components/Navbar.tsx +21 -26
- package/templates/blog/src/components/Newsletter.tsx +11 -33
- package/templates/blog/src/components/PostCard.tsx +43 -76
- package/templates/blog/src/styles.css +113 -25
- package/templates/ecommerce/src/app/cart/page.tsx +12 -4
- package/templates/ecommerce/src/app/layout.tsx +5 -5
- package/templates/ecommerce/src/app/page.tsx +92 -37
- package/templates/ecommerce/src/app/products/[id]/page.tsx +39 -31
- package/templates/ecommerce/src/components/CartDrawer.tsx +113 -45
- package/templates/ecommerce/src/components/Footer.tsx +11 -9
- package/templates/ecommerce/src/components/Navbar.tsx +32 -34
- package/templates/ecommerce/src/components/ProductCard.tsx +27 -38
- package/templates/ecommerce/src/styles.css +155 -36
- package/templates/starter/src/app/layout.tsx +5 -5
- package/templates/starter/src/app/page.tsx +114 -107
- package/templates/starter/src/components/FeatureCard.tsx +9 -15
- package/templates/starter/src/components/Footer.tsx +11 -9
- package/templates/starter/src/components/Header.tsx +15 -38
- package/templates/starter/src/styles.css +102 -27
|
@@ -1,40 +1,33 @@
|
|
|
1
1
|
export function DashboardNav() {
|
|
2
2
|
return (
|
|
3
|
-
<aside class="w-60 border-r border-
|
|
3
|
+
<aside class="w-60 border-r border-[#e6ebf1] bg-white p-3 flex flex-col justify-between shrink-0 min-h-screen">
|
|
4
4
|
<div class="space-y-6">
|
|
5
|
-
{/* Workspace Brand
|
|
5
|
+
{/* Workspace Brand */}
|
|
6
6
|
<div class="px-2 pt-1">
|
|
7
|
-
<
|
|
8
|
-
<div class="flex items-center
|
|
9
|
-
|
|
10
|
-
<svg style="width: 13px; height: 13px; min-width: 13px; min-height: 13px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
11
|
-
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
|
12
|
-
</svg>
|
|
13
|
-
</div>
|
|
14
|
-
<div class="truncate">
|
|
15
|
-
<div class="text-xs font-semibold text-zinc-200 truncate">Orbit Workspace</div>
|
|
16
|
-
<div class="text-[10px] text-zinc-500 truncate">Production Cluster</div>
|
|
17
|
-
</div>
|
|
7
|
+
<a href="/" class="flex items-center gap-2.5 p-1.5 rounded-lg hover:bg-[#f6f9fc] transition-colors">
|
|
8
|
+
<div class="w-7 h-7 rounded-md bg-[#635bff]/10 border border-[#635bff]/20 flex items-center justify-center text-[#635bff] font-bold text-xs shrink-0">
|
|
9
|
+
FA
|
|
18
10
|
</div>
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
<div class="truncate">
|
|
12
|
+
<div class="text-xs font-semibold text-[#0a2540] truncate">Orbit Workspace</div>
|
|
13
|
+
<div class="text-[10px] text-[#425466] truncate">US-East Production</div>
|
|
14
|
+
</div>
|
|
15
|
+
</a>
|
|
23
16
|
</div>
|
|
24
17
|
|
|
25
18
|
{/* Navigation Sections */}
|
|
26
19
|
<div class="space-y-4">
|
|
27
20
|
<div>
|
|
28
|
-
<div class="px-2.5 mb-1.5 text-[
|
|
21
|
+
<div class="px-2.5 mb-1.5 text-[11px] font-semibold text-[#425466]/70 uppercase tracking-wider">
|
|
29
22
|
Platform
|
|
30
23
|
</div>
|
|
31
24
|
<nav class="space-y-0.5 text-xs font-medium">
|
|
32
25
|
<a
|
|
33
26
|
href="/dashboard"
|
|
34
|
-
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md bg-
|
|
27
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md bg-[#635bff]/10 text-[#635bff] font-semibold"
|
|
35
28
|
>
|
|
36
|
-
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-
|
|
37
|
-
<svg
|
|
29
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-[#635bff]">
|
|
30
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
38
31
|
<rect x="3" y="3" width="7" height="7" />
|
|
39
32
|
<rect x="14" y="3" width="7" height="7" />
|
|
40
33
|
<rect x="14" y="14" width="7" height="7" />
|
|
@@ -46,10 +39,10 @@ export function DashboardNav() {
|
|
|
46
39
|
|
|
47
40
|
<a
|
|
48
41
|
href="/dashboard#analytics"
|
|
49
|
-
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-
|
|
42
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-[#425466] hover:text-[#0a2540] hover:bg-[#f6f9fc] transition-colors"
|
|
50
43
|
>
|
|
51
|
-
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-
|
|
52
|
-
<svg
|
|
44
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-[#425466]">
|
|
45
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
53
46
|
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
|
54
47
|
</svg>
|
|
55
48
|
</span>
|
|
@@ -58,10 +51,10 @@ export function DashboardNav() {
|
|
|
58
51
|
|
|
59
52
|
<a
|
|
60
53
|
href="/dashboard#team"
|
|
61
|
-
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-
|
|
54
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-[#425466] hover:text-[#0a2540] hover:bg-[#f6f9fc] transition-colors"
|
|
62
55
|
>
|
|
63
|
-
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-
|
|
64
|
-
<svg
|
|
56
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-[#425466]">
|
|
57
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
65
58
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
|
66
59
|
<circle cx="9" cy="7" r="4" />
|
|
67
60
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
@@ -70,22 +63,15 @@ export function DashboardNav() {
|
|
|
70
63
|
</span>
|
|
71
64
|
<span>Members</span>
|
|
72
65
|
</a>
|
|
73
|
-
</nav>
|
|
74
|
-
</div>
|
|
75
66
|
|
|
76
|
-
<div>
|
|
77
|
-
<div class="px-2.5 mb-1.5 text-[10px] font-semibold text-zinc-500 uppercase tracking-wider">
|
|
78
|
-
Developer
|
|
79
|
-
</div>
|
|
80
|
-
<nav class="space-y-0.5 text-xs font-medium">
|
|
81
67
|
<a
|
|
82
68
|
href="/api/auth"
|
|
83
|
-
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-
|
|
69
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-[#425466] hover:text-[#0a2540] hover:bg-[#f6f9fc] transition-colors"
|
|
84
70
|
>
|
|
85
|
-
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-
|
|
86
|
-
<svg
|
|
87
|
-
<
|
|
88
|
-
<
|
|
71
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-[#425466]">
|
|
72
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
73
|
+
<path d="m21 2-2 2m-2-2 2 2" />
|
|
74
|
+
<path d="M15.5 8.5 7 17l-4 1 1-4 8.5-8.5a2.12 2.12 0 0 1 3 3z" />
|
|
89
75
|
</svg>
|
|
90
76
|
</span>
|
|
91
77
|
<span>Session API</span>
|
|
@@ -95,30 +81,24 @@ export function DashboardNav() {
|
|
|
95
81
|
</div>
|
|
96
82
|
</div>
|
|
97
83
|
|
|
98
|
-
{/* User
|
|
99
|
-
<div class="
|
|
100
|
-
<div class="flex items-center
|
|
101
|
-
<div class="
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<div class="
|
|
106
|
-
|
|
84
|
+
{/* User Footer Profile */}
|
|
85
|
+
<div class="pt-3 border-t border-[#e6ebf1]">
|
|
86
|
+
<div class="flex items-center justify-between p-2 rounded-lg bg-[#f6f9fc] text-xs">
|
|
87
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
88
|
+
<div class="w-7 h-7 rounded-full bg-[#635bff] text-white flex items-center justify-center text-xs font-semibold shrink-0">
|
|
89
|
+
AD
|
|
90
|
+
</div>
|
|
91
|
+
<div class="truncate">
|
|
92
|
+
<div class="font-medium text-[#0a2540] truncate">admin@frootkit.dev</div>
|
|
93
|
+
<div class="text-[10px] text-[#425466]">Administrator</div>
|
|
94
|
+
</div>
|
|
107
95
|
</div>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
type="submit"
|
|
112
|
-
class="p-1.5 rounded-md text-zinc-500 hover:text-zinc-200 hover:bg-white/[0.06] transition-colors"
|
|
113
|
-
title="Sign out"
|
|
114
|
-
>
|
|
115
|
-
<svg style="width: 14px; height: 14px; min-width: 14px; min-height: 14px;" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
116
|
-
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
117
|
-
<polyline points="16 17 21 12 16 7" />
|
|
118
|
-
<line x1="21" y1="12" x2="9" y2="12" />
|
|
96
|
+
<a href="/login" class="text-[#425466] hover:text-[#cd3d64] p-1" title="Sign out">
|
|
97
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
98
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
|
119
99
|
</svg>
|
|
120
|
-
</
|
|
121
|
-
</
|
|
100
|
+
</a>
|
|
101
|
+
</div>
|
|
122
102
|
</div>
|
|
123
103
|
</aside>
|
|
124
104
|
);
|
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
export interface MetricCardProps {
|
|
2
2
|
label: string;
|
|
3
|
-
value: string;
|
|
3
|
+
value: string | number;
|
|
4
4
|
change: string;
|
|
5
|
-
isPositive
|
|
6
|
-
context?: string;
|
|
5
|
+
isPositive?: boolean;
|
|
7
6
|
}
|
|
8
7
|
|
|
9
|
-
export function MetricCard({ label, value, change, isPositive
|
|
8
|
+
export function MetricCard({ label, value, change, isPositive = true }: MetricCardProps) {
|
|
10
9
|
return (
|
|
11
|
-
<div class="rounded-
|
|
12
|
-
<div class="
|
|
13
|
-
{label}
|
|
10
|
+
<div class="rounded-lg border border-[#e6ebf1] bg-white p-5 shadow-[0_2px_5px_-1px_rgba(50,50,93,0.25),0_1px_3px_-1px_rgba(0,0,0,0.3)] transition hover:-translate-y-0.5 hover:shadow-[0_13px_27px_-5px_rgba(50,50,93,0.25),0_8px_16px_-8px_rgba(0,0,0,0.3)]">
|
|
11
|
+
<div class="flex items-center justify-between">
|
|
12
|
+
<span class="text-[13px] font-medium text-[#425466]">{label}</span>
|
|
14
13
|
</div>
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
|
|
15
|
+
<div class="mt-3 flex items-baseline justify-between gap-2">
|
|
16
|
+
<span class="text-[28px] sm:text-[30px] font-mono font-light text-[#0a2540] tracking-tight">
|
|
17
17
|
{value}
|
|
18
|
-
</div>
|
|
19
|
-
<span
|
|
20
|
-
class={`inline-flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded-full border shrink-0 ${
|
|
21
|
-
isPositive
|
|
22
|
-
? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'
|
|
23
|
-
: 'bg-rose-500/10 text-rose-400 border-rose-500/20'
|
|
24
|
-
}`}
|
|
25
|
-
>
|
|
26
|
-
<span>{isPositive ? '↑' : '↓'}</span>
|
|
27
|
-
<span>{change}</span>
|
|
28
18
|
</span>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
<span class={`bd ${isPositive ? 'bg-[#24b47e]/15 text-[#0e6245]' : 'bg-[#cd3d64]/10 text-[#cd3d64]'}`}>
|
|
20
|
+
{change}
|
|
21
|
+
</span>
|
|
32
22
|
</div>
|
|
33
23
|
</div>
|
|
34
24
|
);
|
|
@@ -1,33 +1,30 @@
|
|
|
1
1
|
export function Navbar() {
|
|
2
2
|
return (
|
|
3
|
-
<header class="
|
|
4
|
-
<
|
|
5
|
-
<a href="/" class="
|
|
6
|
-
<
|
|
7
|
-
<svg style="width: 14px; height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
8
|
-
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
9
|
-
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
10
|
-
</svg>
|
|
11
|
-
</div>
|
|
12
|
-
<span>Froot<span class="text-indigo-400">Auth</span></span>
|
|
3
|
+
<header class="absolute inset-x-0 top-0 z-30">
|
|
4
|
+
<nav class="mx-auto max-w-6xl px-6 flex items-center justify-between py-5 text-white">
|
|
5
|
+
<a href="/" class="text-[22px] font-semibold tracking-tight flex items-center gap-2">
|
|
6
|
+
<span>froot<span class="opacity-90">auth</span></span>
|
|
13
7
|
</a>
|
|
14
8
|
|
|
15
|
-
<
|
|
16
|
-
<a href="
|
|
17
|
-
|
|
18
|
-
</a>
|
|
19
|
-
<a href="/
|
|
20
|
-
|
|
9
|
+
<div class="hidden items-center gap-8 text-[15px] font-medium md:flex">
|
|
10
|
+
<a href="#features" class="opacity-90 hover:opacity-100 transition-opacity">Product</a>
|
|
11
|
+
<a href="#developers" class="opacity-90 hover:opacity-100 transition-opacity">Developers</a>
|
|
12
|
+
<a href="#pricing" class="opacity-90 hover:opacity-100 transition-opacity">Pricing</a>
|
|
13
|
+
<a href="/dashboard" class="opacity-90 hover:opacity-100 transition-opacity">Dashboard</a>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="flex items-center gap-4 text-[15px] font-medium">
|
|
17
|
+
<a href="/login" class="hidden opacity-90 hover:opacity-100 sm:block transition-opacity">
|
|
18
|
+
Sign in
|
|
21
19
|
</a>
|
|
22
20
|
<a
|
|
23
21
|
href="/signup"
|
|
24
|
-
class="px-
|
|
22
|
+
class="rounded-full bg-white px-4 py-1.5 text-[14px] text-[#635bff] font-medium shadow-[0_2px_5px_-1px_rgba(50,50,93,0.25),0_1px_3px_-1px_rgba(0,0,0,0.3)] hover:bg-[#f6f9fc] transition-colors"
|
|
25
23
|
>
|
|
26
|
-
|
|
27
|
-
<span class="text-zinc-500">→</span>
|
|
24
|
+
Start free
|
|
28
25
|
</a>
|
|
29
|
-
</
|
|
30
|
-
</
|
|
26
|
+
</div>
|
|
27
|
+
</nav>
|
|
31
28
|
</header>
|
|
32
29
|
);
|
|
33
30
|
}
|
|
@@ -4,48 +4,178 @@
|
|
|
4
4
|
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
5
5
|
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
6
6
|
|
|
7
|
-
--color-brand
|
|
8
|
-
--color-brand-hover: #
|
|
7
|
+
--color-brand: #635bff;
|
|
8
|
+
--color-brand-hover: #5249e6;
|
|
9
|
+
--color-navy: #0a2540;
|
|
10
|
+
--color-slate: #425466;
|
|
11
|
+
--color-surface: #f6f9fc;
|
|
12
|
+
--color-hairline: #e6ebf1;
|
|
13
|
+
--color-cyan: #00d4ff;
|
|
14
|
+
--color-success: #24b47e;
|
|
15
|
+
--color-error: #cd3d64;
|
|
16
|
+
--color-warn: #c98a00;
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
:root {
|
|
12
|
-
color-scheme:
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
color-scheme: light;
|
|
21
|
+
--brand: #635bff;
|
|
22
|
+
--navy: #0a2540;
|
|
23
|
+
--slate: #425466;
|
|
24
|
+
--surface: #f6f9fc;
|
|
25
|
+
--hairline: #e6ebf1;
|
|
26
|
+
--cyan: #00d4ff;
|
|
27
|
+
--success: #24b47e;
|
|
28
|
+
--error: #cd3d64;
|
|
29
|
+
--warn: #c98a00;
|
|
30
|
+
|
|
31
|
+
--shadow-card: 0 2px 5px -1px rgba(50, 50, 93, 0.25), 0 1px 3px -1px rgba(0, 0, 0, 0.3);
|
|
32
|
+
--shadow-pop: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
body {
|
|
36
|
+
font-size: 15px;
|
|
37
|
+
line-height: 24px;
|
|
38
|
+
background-color: #ffffff;
|
|
39
|
+
color: #425466;
|
|
28
40
|
-webkit-font-smoothing: antialiased;
|
|
29
41
|
-moz-osx-font-smoothing: grayscale;
|
|
30
42
|
}
|
|
31
43
|
|
|
44
|
+
:focus-visible {
|
|
45
|
+
outline: 2px solid #635bff;
|
|
46
|
+
outline-offset: 2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
/* Strict SVG safety to prevent flex ballooning */
|
|
33
50
|
svg {
|
|
34
51
|
flex-shrink: 0;
|
|
35
52
|
max-width: 100%;
|
|
36
53
|
}
|
|
37
54
|
|
|
38
|
-
/*
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
/* Stripe Design Tokens & Utility Classes */
|
|
56
|
+
.btn {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
gap: 0.5rem;
|
|
61
|
+
border-radius: 9999px;
|
|
62
|
+
padding: 0.625rem 1.25rem;
|
|
63
|
+
font-size: 15px;
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
transition: all 150ms cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.btn-p {
|
|
70
|
+
background-color: #635bff;
|
|
71
|
+
color: #ffffff;
|
|
72
|
+
box-shadow: var(--shadow-card);
|
|
73
|
+
}
|
|
74
|
+
.btn-p:hover {
|
|
75
|
+
background-color: #5249e6;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.btn-w {
|
|
79
|
+
background-color: #ffffff;
|
|
80
|
+
color: #635bff;
|
|
81
|
+
box-shadow: var(--shadow-card);
|
|
82
|
+
}
|
|
83
|
+
.btn-w:hover {
|
|
84
|
+
background-color: #f6f9fc;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.btn-o {
|
|
88
|
+
border: 1px solid #e6ebf1;
|
|
89
|
+
background-color: #ffffff;
|
|
90
|
+
color: #0a2540;
|
|
91
|
+
box-shadow: var(--shadow-card);
|
|
92
|
+
}
|
|
93
|
+
.btn-o:hover {
|
|
94
|
+
background-color: #f6f9fc;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.btn-gl {
|
|
98
|
+
border: 1px solid rgba(255, 255, 255, 0.4);
|
|
99
|
+
color: #ffffff;
|
|
100
|
+
}
|
|
101
|
+
.btn-gl:hover {
|
|
102
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.btn-s {
|
|
106
|
+
border: 1px solid #e6ebf1;
|
|
107
|
+
background-color: #ffffff;
|
|
108
|
+
color: #0a2540;
|
|
109
|
+
box-shadow: var(--shadow-card);
|
|
110
|
+
}
|
|
111
|
+
.btn-s:hover {
|
|
112
|
+
background-color: #f6f9fc;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.btn-n {
|
|
116
|
+
background-color: #0a2540;
|
|
117
|
+
color: #ffffff;
|
|
118
|
+
box-shadow: var(--shadow-card);
|
|
119
|
+
}
|
|
120
|
+
.btn-n:hover {
|
|
121
|
+
background-color: #425466;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.bd {
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
border-radius: 9999px;
|
|
128
|
+
padding: 0.125rem 0.625rem;
|
|
129
|
+
font-size: 13px;
|
|
130
|
+
font-weight: 500;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.inp {
|
|
134
|
+
width: 100%;
|
|
135
|
+
border-radius: 8px;
|
|
136
|
+
border: 1px solid #e6ebf1;
|
|
137
|
+
background-color: #ffffff;
|
|
138
|
+
padding: 0.5rem 0.75rem;
|
|
139
|
+
font-size: 15px;
|
|
140
|
+
color: #0a2540;
|
|
141
|
+
outline: none;
|
|
142
|
+
transition: all 150ms cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
143
|
+
}
|
|
144
|
+
.inp:focus {
|
|
145
|
+
border-color: #635bff;
|
|
146
|
+
box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.25);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.card {
|
|
150
|
+
border-radius: 8px;
|
|
151
|
+
border: 1px solid #e6ebf1;
|
|
152
|
+
background-color: #ffffff;
|
|
153
|
+
padding: 1.25rem;
|
|
154
|
+
box-shadow: var(--shadow-card);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.shadow-card {
|
|
158
|
+
box-shadow: var(--shadow-card);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.shadow-pop {
|
|
162
|
+
box-shadow: var(--shadow-pop);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.hero {
|
|
166
|
+
background: linear-gradient(160deg, #635bff 0%, #7a73ff 38%, #00d4ff 100%);
|
|
167
|
+
clip-path: polygon(0 0, 100% 0, 100% 88%, 0 100%);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@keyframes rise {
|
|
171
|
+
from {
|
|
172
|
+
opacity: 0;
|
|
173
|
+
transform: translateY(14px);
|
|
174
|
+
}
|
|
45
175
|
}
|
|
46
176
|
|
|
47
|
-
.
|
|
48
|
-
|
|
177
|
+
.rise {
|
|
178
|
+
animation: rise 0.6s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
|
49
179
|
}
|
|
50
180
|
|
|
51
181
|
/* Accessibility: Mandatory Reduced Motion */
|
|
@@ -9,66 +9,105 @@ export async function generateStaticParams() {
|
|
|
9
9
|
export default async function BlogPostPage({ params }: { params?: { slug?: string } }) {
|
|
10
10
|
const slug = params?.slug || 'welcome-to-frootkit';
|
|
11
11
|
const post = (await getPostBySlug(slug)) || POSTS[0]!;
|
|
12
|
+
const relatedPosts = POSTS.filter((p) => p.slug !== post.slug).slice(0, 3);
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
|
-
<div class="min-h-screen bg-[#
|
|
15
|
+
<div class="min-h-screen bg-white text-[#425466] flex flex-col justify-between">
|
|
15
16
|
<Navbar />
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
<span class="px-2.5 py-0.5 rounded-full bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 font-medium text-[11px]">
|
|
29
|
-
{post.category}
|
|
30
|
-
</span>
|
|
31
|
-
<span class="text-zinc-600">•</span>
|
|
32
|
-
<span class="text-zinc-400">{post.readTime}</span>
|
|
33
|
-
<span class="text-zinc-600">•</span>
|
|
34
|
-
<span class="text-zinc-400">{post.date}</span>
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<h1 class="text-3xl sm:text-4xl font-semibold text-white tracking-[-0.025em] mb-6 leading-tight">
|
|
18
|
+
{/* Article Gradient Header */}
|
|
19
|
+
<header
|
|
20
|
+
class="relative overflow-hidden text-white"
|
|
21
|
+
style="background: linear-gradient(145deg, #635bff, #00d4ff)"
|
|
22
|
+
>
|
|
23
|
+
<div class="mx-auto max-w-3xl px-6 py-14 md:py-20">
|
|
24
|
+
<a href="/#articles" class="text-[14px] text-white/80 hover:text-white transition-colors">
|
|
25
|
+
← All articles
|
|
26
|
+
</a>
|
|
27
|
+
<span class="bd ml-3 bg-white/15 text-white">{post.category}</span>
|
|
28
|
+
<h1 class="mt-5 text-[34px] font-light leading-[1.15] tracking-tight md:text-[48px]">
|
|
38
29
|
{post.title}
|
|
39
30
|
</h1>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
</
|
|
31
|
+
<p class="mt-4 text-[18px] font-light text-white/90">
|
|
32
|
+
{post.summary}
|
|
33
|
+
</p>
|
|
34
|
+
<div class="mt-6 flex items-center gap-3">
|
|
35
|
+
<span class="grid h-10 w-10 text-[13px] shrink-0 place-items-center rounded-full font-medium text-white bg-[#0a2540]">
|
|
36
|
+
{post.author.name.split(' ').map((n) => n[0]).join('')}
|
|
37
|
+
</span>
|
|
38
|
+
<span class="text-[14px]">
|
|
39
|
+
<b class="block">{post.author.name}</b>
|
|
40
|
+
<span class="text-white/80">{post.author.role} · {post.date} · {post.readTime}</span>
|
|
41
|
+
</span>
|
|
51
42
|
</div>
|
|
52
|
-
</
|
|
43
|
+
</div>
|
|
44
|
+
</header>
|
|
53
45
|
|
|
54
|
-
|
|
46
|
+
{/* Article Content */}
|
|
47
|
+
<main class="mx-auto max-w-3xl px-6 py-12 flex-1 w-full">
|
|
48
|
+
<div class="space-y-6">
|
|
55
49
|
{post.content.map((paragraph, idx) => (
|
|
56
|
-
<p key={idx}>{paragraph}</p>
|
|
50
|
+
<p key={idx} class="prose-p">{paragraph}</p>
|
|
57
51
|
))}
|
|
58
|
-
</article>
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
<blockquote class="my-8 border-l-4 border-[#635bff] pl-5 text-[22px] font-light leading-8 text-[#0a2540]">
|
|
54
|
+
“If a request can be repeated, it must be safe to repeat.”
|
|
55
|
+
</blockquote>
|
|
56
|
+
|
|
57
|
+
<div class="my-6 flex gap-3 rounded-md border border-[#635bff]/20 bg-[#635bff]/5 p-4 text-[15px] text-[#0a2540]">
|
|
58
|
+
<b class="text-[#635bff]">Note:</b>
|
|
59
|
+
<span>SSR renders directly with sub-millisecond execution latency on FrootKit.</span>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{/* Feedback Row */}
|
|
64
|
+
<div class="mt-10 flex flex-wrap items-center gap-3 border-t border-[#e6ebf1] pt-6 text-sm">
|
|
65
|
+
<span class="text-[#425466]">Was this useful?</span>
|
|
66
|
+
<button type="button" class="btn btn-s text-xs py-1 px-3">Yes</button>
|
|
67
|
+
<button type="button" class="btn btn-s text-xs py-1 px-3">No</button>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
{/* Author Bio Card */}
|
|
71
|
+
<div class="mt-8 flex gap-4 rounded-lg bg-[#f6f9fc] p-5 border border-[#e6ebf1]">
|
|
72
|
+
<span class="grid h-12 w-12 text-[15px] shrink-0 place-items-center rounded-full font-medium text-white bg-[#635bff]">
|
|
73
|
+
{post.author.name.split(' ').map((n) => n[0]).join('')}
|
|
74
|
+
</span>
|
|
75
|
+
<div>
|
|
76
|
+
<b class="text-[#0a2540]">{post.author.name}</b>
|
|
77
|
+
<p class="text-[14px] text-[#425466] mt-0.5">
|
|
78
|
+
{post.author.role} at FrootKit. Writes about how high-throughput systems scale with zero hydration bottlenecks.
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
69
81
|
</div>
|
|
70
82
|
</main>
|
|
71
83
|
|
|
84
|
+
{/* Keep Reading */}
|
|
85
|
+
<section class="bg-[#f6f9fc] py-14 border-t border-[#e6ebf1]">
|
|
86
|
+
<div class="mx-auto max-w-6xl px-6">
|
|
87
|
+
<h2 class="text-[26px] font-light text-[#0a2540]">Keep reading</h2>
|
|
88
|
+
<div class="mt-6 grid gap-6 sm:grid-cols-3">
|
|
89
|
+
{relatedPosts.map((r) => (
|
|
90
|
+
<a
|
|
91
|
+
key={r.slug}
|
|
92
|
+
href={`/blog/${r.slug}`}
|
|
93
|
+
class="group block rounded-lg bg-white p-3 shadow-[0_2px_5px_-1px_rgba(50,50,93,0.25),0_1px_3px_-1px_rgba(0,0,0,0.3)] transition hover:shadow-[0_13px_27px_-5px_rgba(50,50,93,0.25),0_8px_16px_-8px_rgba(0,0,0,0.3)] border border-[#e6ebf1]"
|
|
94
|
+
>
|
|
95
|
+
<div
|
|
96
|
+
class="aspect-[16/10] overflow-hidden rounded-lg p-3 text-white flex flex-col justify-end"
|
|
97
|
+
style="background: linear-gradient(145deg, #0a2540, #635bff)"
|
|
98
|
+
>
|
|
99
|
+
<span class="font-mono text-[11px] opacity-75">{r.readTime}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<h3 class="mt-3 px-1 font-medium leading-6 text-[#0a2540] group-hover:text-[#635bff] transition-colors">
|
|
102
|
+
{r.title}
|
|
103
|
+
</h3>
|
|
104
|
+
<p class="px-1 pb-1 text-[13px] text-[#425466] mt-1">{r.readTime}</p>
|
|
105
|
+
</a>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
72
111
|
<Footer />
|
|
73
112
|
</div>
|
|
74
113
|
);
|