create-frootkit 1.2.8 → 1.2.9
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 +14 -9
- package/templates/auth/src/app/dashboard/page.tsx +55 -24
- package/templates/auth/src/app/layout.tsx +3 -3
- package/templates/auth/src/app/login/page.tsx +5 -1
- package/templates/auth/src/app/page.tsx +103 -45
- package/templates/auth/src/app/signup/page.tsx +5 -1
- package/templates/auth/src/components/AuthForm.tsx +34 -19
- package/templates/auth/src/components/DashboardNav.tsx +97 -56
- package/templates/auth/src/components/MetricCard.tsx +13 -6
- package/templates/auth/src/components/Navbar.tsx +13 -12
- package/templates/auth/src/styles.css +27 -5
- package/templates/blog/src/app/blog/[slug]/page.tsx +19 -18
- package/templates/blog/src/app/layout.tsx +4 -4
- package/templates/blog/src/app/page.tsx +15 -14
- package/templates/blog/src/components/Footer.tsx +6 -6
- package/templates/blog/src/components/Navbar.tsx +17 -15
- package/templates/blog/src/components/Newsletter.tsx +11 -9
- package/templates/blog/src/components/PostCard.tsx +29 -27
- package/templates/blog/src/styles.css +14 -5
- package/templates/ecommerce/src/app/cart/page.tsx +3 -3
- package/templates/ecommerce/src/app/layout.tsx +2 -2
- package/templates/ecommerce/src/app/page.tsx +37 -15
- package/templates/ecommerce/src/app/products/[id]/page.tsx +34 -25
- package/templates/ecommerce/src/components/CartDrawer.tsx +20 -17
- package/templates/ecommerce/src/components/Footer.tsx +6 -6
- package/templates/ecommerce/src/components/Navbar.tsx +21 -14
- package/templates/ecommerce/src/components/ProductCard.tsx +22 -15
- package/templates/ecommerce/src/data/products.ts +17 -11
- package/templates/ecommerce/src/styles.css +29 -5
- package/templates/empty/src/app/layout.tsx +5 -5
- package/templates/empty/src/app/page.tsx +10 -9
- package/templates/empty/src/styles.css +13 -15
- package/templates/starter/src/app/layout.tsx +4 -4
- package/templates/starter/src/app/page.tsx +41 -33
- package/templates/starter/src/components/FeatureCard.tsx +5 -5
- package/templates/starter/src/components/Footer.tsx +6 -6
- package/templates/starter/src/components/Header.tsx +18 -14
- package/templates/starter/src/components/TerminalPreview.tsx +30 -30
- package/templates/starter/src/styles.css +14 -5
|
@@ -1,80 +1,121 @@
|
|
|
1
1
|
export function DashboardNav() {
|
|
2
2
|
return (
|
|
3
|
-
<aside class="w-
|
|
4
|
-
<div>
|
|
5
|
-
{/* Brand
|
|
6
|
-
<div class="
|
|
7
|
-
<div class="
|
|
8
|
-
<
|
|
9
|
-
<
|
|
3
|
+
<aside class="w-60 border-r border-white/[0.08] bg-[#0d0e12] p-3 flex flex-col justify-between shrink-0 min-h-screen">
|
|
4
|
+
<div class="space-y-6">
|
|
5
|
+
{/* Workspace Brand / Org Selector */}
|
|
6
|
+
<div class="px-2 pt-1">
|
|
7
|
+
<div class="flex items-center justify-between p-1.5 rounded-lg hover:bg-white/[0.04] transition-colors cursor-pointer border border-transparent hover:border-white/[0.06]">
|
|
8
|
+
<div class="flex items-center gap-2.5 min-w-0">
|
|
9
|
+
<div class="w-6 h-6 rounded-md bg-indigo-500/15 border border-indigo-500/30 flex items-center justify-center text-indigo-400 shrink-0">
|
|
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>
|
|
18
|
+
</div>
|
|
19
|
+
<svg style="width: 12px; height: 12px; min-width: 12px; min-height: 12px;" class="text-zinc-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
20
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
|
|
10
21
|
</svg>
|
|
11
22
|
</div>
|
|
12
|
-
<span class="tracking-tight">Workspace</span>
|
|
13
23
|
</div>
|
|
14
24
|
|
|
15
|
-
{/* Navigation
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
{/* Navigation Sections */}
|
|
26
|
+
<div class="space-y-4">
|
|
27
|
+
<div>
|
|
28
|
+
<div class="px-2.5 mb-1.5 text-[10px] font-semibold text-zinc-500 uppercase tracking-wider">
|
|
29
|
+
Platform
|
|
30
|
+
</div>
|
|
31
|
+
<nav class="space-y-0.5 text-xs font-medium">
|
|
32
|
+
<a
|
|
33
|
+
href="/dashboard"
|
|
34
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md bg-white/[0.08] text-white border border-white/[0.08] shadow-[inset_0_1px_0_0_rgba(255,255,255,0.06)]"
|
|
35
|
+
>
|
|
36
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-indigo-400">
|
|
37
|
+
<svg style="width: 14px; height: 14px; min-width: 14px; min-height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
38
|
+
<rect x="3" y="3" width="7" height="7" />
|
|
39
|
+
<rect x="14" y="3" width="7" height="7" />
|
|
40
|
+
<rect x="14" y="14" width="7" height="7" />
|
|
41
|
+
<rect x="3" y="14" width="7" height="7" />
|
|
42
|
+
</svg>
|
|
43
|
+
</span>
|
|
44
|
+
<span>Overview</span>
|
|
45
|
+
</a>
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
<a
|
|
48
|
+
href="/dashboard#analytics"
|
|
49
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.04] transition-colors"
|
|
50
|
+
>
|
|
51
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-zinc-500">
|
|
52
|
+
<svg style="width: 14px; height: 14px; min-width: 14px; min-height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
53
|
+
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
|
54
|
+
</svg>
|
|
55
|
+
</span>
|
|
56
|
+
<span>Telemetry</span>
|
|
57
|
+
</a>
|
|
36
58
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
<a
|
|
60
|
+
href="/dashboard#team"
|
|
61
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.04] transition-colors"
|
|
62
|
+
>
|
|
63
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-zinc-500">
|
|
64
|
+
<svg style="width: 14px; height: 14px; min-width: 14px; min-height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
65
|
+
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
|
66
|
+
<circle cx="9" cy="7" r="4" />
|
|
67
|
+
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
68
|
+
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
69
|
+
</svg>
|
|
70
|
+
</span>
|
|
71
|
+
<span>Members</span>
|
|
72
|
+
</a>
|
|
73
|
+
</nav>
|
|
74
|
+
</div>
|
|
46
75
|
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
+
<a
|
|
82
|
+
href="/api/auth"
|
|
83
|
+
class="flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.04] transition-colors"
|
|
84
|
+
>
|
|
85
|
+
<span class="w-4 h-4 flex items-center justify-center shrink-0 text-zinc-500">
|
|
86
|
+
<svg style="width: 14px; height: 14px; min-width: 14px; min-height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
87
|
+
<polyline points="16 18 22 12 16 6" />
|
|
88
|
+
<polyline points="8 6 2 12 8 18" />
|
|
89
|
+
</svg>
|
|
90
|
+
</span>
|
|
91
|
+
<span>Session API</span>
|
|
92
|
+
</a>
|
|
93
|
+
</nav>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
57
96
|
</div>
|
|
58
97
|
|
|
59
98
|
{/* User profile & Sign out */}
|
|
60
|
-
<div class="
|
|
99
|
+
<div class="p-2 border-t border-white/[0.06] flex items-center justify-between">
|
|
61
100
|
<div class="flex items-center gap-2.5 min-w-0">
|
|
62
|
-
<div class="w-
|
|
101
|
+
<div class="w-7 h-7 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center font-medium text-[11px] text-white shrink-0">
|
|
63
102
|
AD
|
|
64
103
|
</div>
|
|
65
|
-
<div class="text-xs truncate">
|
|
66
|
-
<div class="font-
|
|
67
|
-
<div class="text-
|
|
104
|
+
<div class="text-xs min-w-0 truncate">
|
|
105
|
+
<div class="font-medium text-zinc-200 truncate">admin@frootkit.dev</div>
|
|
106
|
+
<div class="text-zinc-500 text-[10px] truncate">Owner</div>
|
|
68
107
|
</div>
|
|
69
108
|
</div>
|
|
70
|
-
<form action="/api/auth?action=logout" method="POST" class="shrink-0 ml-
|
|
109
|
+
<form action="/api/auth?action=logout" method="POST" class="shrink-0 ml-1">
|
|
71
110
|
<button
|
|
72
111
|
type="submit"
|
|
73
|
-
class="p-1.5 rounded-md text-
|
|
112
|
+
class="p-1.5 rounded-md text-zinc-500 hover:text-zinc-200 hover:bg-white/[0.06] transition-colors"
|
|
74
113
|
title="Sign out"
|
|
75
114
|
>
|
|
76
|
-
<svg
|
|
77
|
-
<path
|
|
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" />
|
|
78
119
|
</svg>
|
|
79
120
|
</button>
|
|
80
121
|
</form>
|
|
@@ -3,26 +3,33 @@ export interface MetricCardProps {
|
|
|
3
3
|
value: string;
|
|
4
4
|
change: string;
|
|
5
5
|
isPositive: boolean;
|
|
6
|
+
context?: string;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
export function MetricCard({ label, value, change, isPositive }: MetricCardProps) {
|
|
9
|
+
export function MetricCard({ label, value, change, isPositive, context = 'vs last 30d' }: MetricCardProps) {
|
|
9
10
|
return (
|
|
10
|
-
<div class="rounded-xl border border-[
|
|
11
|
-
<div class="text-xs font-medium text-
|
|
11
|
+
<div class="rounded-xl border border-white/[0.08] bg-[#0f1117] p-4 sm:p-5 flex flex-col justify-between shadow-[inset_0_1px_0_0_rgba(255,255,255,0.05)] transition-all hover:border-white/[0.14]">
|
|
12
|
+
<div class="text-xs font-medium text-zinc-400 mb-3 tracking-normal">
|
|
12
13
|
{label}
|
|
13
14
|
</div>
|
|
14
15
|
<div class="flex items-baseline justify-between gap-3">
|
|
15
|
-
<div class="text-2xl font-
|
|
16
|
+
<div class="text-2xl sm:text-3xl font-semibold text-white tracking-tight tabular-nums">
|
|
17
|
+
{value}
|
|
18
|
+
</div>
|
|
16
19
|
<span
|
|
17
|
-
class={`text-
|
|
20
|
+
class={`inline-flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded-full border shrink-0 ${
|
|
18
21
|
isPositive
|
|
19
22
|
? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'
|
|
20
23
|
: 'bg-rose-500/10 text-rose-400 border-rose-500/20'
|
|
21
24
|
}`}
|
|
22
25
|
>
|
|
23
|
-
{
|
|
26
|
+
<span>{isPositive ? '↑' : '↓'}</span>
|
|
27
|
+
<span>{change}</span>
|
|
24
28
|
</span>
|
|
25
29
|
</div>
|
|
30
|
+
<div class="mt-2 text-[11px] text-zinc-500">
|
|
31
|
+
{context}
|
|
32
|
+
</div>
|
|
26
33
|
</div>
|
|
27
34
|
);
|
|
28
35
|
}
|
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
export function Navbar() {
|
|
2
2
|
return (
|
|
3
|
-
<header class="border-b border-[
|
|
4
|
-
<div class="max-w-6xl mx-auto px-6 h-
|
|
5
|
-
<a href="/" class="flex items-center gap-2.5 font-
|
|
6
|
-
<div class="w-
|
|
7
|
-
<svg
|
|
8
|
-
<
|
|
3
|
+
<header class="border-b border-white/[0.08] bg-[#08090a]/80 backdrop-blur-md sticky top-0 z-50">
|
|
4
|
+
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
|
5
|
+
<a href="/" class="flex items-center gap-2.5 font-semibold text-zinc-100 text-sm tracking-tight group">
|
|
6
|
+
<div class="w-7 h-7 rounded-md bg-indigo-500/10 border border-indigo-500/25 flex items-center justify-center text-indigo-400 group-hover:border-indigo-400/50 transition-colors shrink-0">
|
|
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" />
|
|
9
10
|
</svg>
|
|
10
11
|
</div>
|
|
11
|
-
<span>Froot<span class="text-
|
|
12
|
+
<span>Froot<span class="text-indigo-400">Auth</span></span>
|
|
12
13
|
</a>
|
|
13
14
|
|
|
14
|
-
<nav class="flex items-center gap-
|
|
15
|
-
<a href="/dashboard" class="text-
|
|
15
|
+
<nav class="flex items-center gap-5 text-xs font-medium">
|
|
16
|
+
<a href="/dashboard" class="text-zinc-400 hover:text-zinc-200 transition-colors">
|
|
16
17
|
Dashboard
|
|
17
18
|
</a>
|
|
18
|
-
<a href="/login" class="text-
|
|
19
|
+
<a href="/login" class="text-zinc-400 hover:text-zinc-200 transition-colors">
|
|
19
20
|
Sign In
|
|
20
21
|
</a>
|
|
21
22
|
<a
|
|
22
23
|
href="/signup"
|
|
23
|
-
class="px-
|
|
24
|
+
class="px-3 py-1.5 rounded-md bg-white text-zinc-950 font-medium hover:bg-zinc-200 transition-all text-xs shadow-sm flex items-center gap-1.5"
|
|
24
25
|
>
|
|
25
26
|
<span>Get Started</span>
|
|
26
|
-
<span>→</span>
|
|
27
|
+
<span class="text-zinc-500">→</span>
|
|
27
28
|
</a>
|
|
28
29
|
</nav>
|
|
29
30
|
</div>
|
|
@@ -4,28 +4,50 @@
|
|
|
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-primary: #
|
|
8
|
-
--color-brand-hover: #
|
|
7
|
+
--color-brand-primary: #6366f1;
|
|
8
|
+
--color-brand-hover: #4f46e5;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
:root {
|
|
12
12
|
color-scheme: dark;
|
|
13
|
+
--bg-canvas: #08090a;
|
|
14
|
+
--bg-surface: #0f1117;
|
|
15
|
+
--bg-elevated: #151822;
|
|
16
|
+
--border-subtle: rgba(255, 255, 255, 0.08);
|
|
17
|
+
--border-strong: rgba(255, 255, 255, 0.16);
|
|
18
|
+
--text-primary: #f4f4f5;
|
|
19
|
+
--text-secondary: #9496a1;
|
|
20
|
+
--text-muted: #5c6370;
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
html {
|
|
16
|
-
background-color:
|
|
17
|
-
color:
|
|
24
|
+
background-color: var(--bg-canvas);
|
|
25
|
+
color: var(--text-primary);
|
|
18
26
|
font-family: var(--font-sans);
|
|
19
27
|
scroll-behavior: smooth;
|
|
20
28
|
-webkit-font-smoothing: antialiased;
|
|
29
|
+
-moz-osx-font-smoothing: grayscale;
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
/* SVG safety
|
|
32
|
+
/* Strict SVG safety to prevent flex ballooning */
|
|
24
33
|
svg {
|
|
25
34
|
flex-shrink: 0;
|
|
26
35
|
max-width: 100%;
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
/* Linear-style subtle card highlight */
|
|
39
|
+
.linear-card {
|
|
40
|
+
background-color: var(--bg-surface);
|
|
41
|
+
border: 1px solid var(--border-subtle);
|
|
42
|
+
box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.06);
|
|
43
|
+
border-radius: 0.75rem;
|
|
44
|
+
transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.linear-card:hover {
|
|
48
|
+
border-color: var(--border-strong);
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
/* Accessibility: Mandatory Reduced Motion */
|
|
30
52
|
@media (prefers-reduced-motion: reduce) {
|
|
31
53
|
*, *::before, *::after {
|
|
@@ -11,58 +11,59 @@ export default function BlogPostPage({ params }: { params?: { slug?: string } })
|
|
|
11
11
|
const post = POSTS.find((p) => p.slug === slug) || POSTS[0]!;
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
|
-
<div class="min-h-screen bg-[#
|
|
14
|
+
<div class="min-h-screen bg-[#08090a] text-zinc-100 flex flex-col justify-between selection:bg-indigo-500/20 selection:text-indigo-300">
|
|
15
15
|
<Navbar />
|
|
16
16
|
|
|
17
17
|
<main class="max-w-3xl mx-auto px-6 py-12 flex-1 w-full">
|
|
18
18
|
<a
|
|
19
19
|
href="/"
|
|
20
|
-
class="inline-flex items-center gap-2 text-xs
|
|
20
|
+
class="inline-flex items-center gap-2 text-xs text-zinc-400 hover:text-white mb-8 transition-colors"
|
|
21
21
|
>
|
|
22
|
-
|
|
22
|
+
<span>←</span>
|
|
23
|
+
<span>Back to articles</span>
|
|
23
24
|
</a>
|
|
24
25
|
|
|
25
26
|
<header class="mb-10">
|
|
26
|
-
<div class="flex items-center gap-3 mb-4 text-xs
|
|
27
|
-
<span class="px-2.5 py-0.5 rounded bg-
|
|
27
|
+
<div class="flex items-center gap-3 mb-4 text-xs">
|
|
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]">
|
|
28
29
|
{post.category}
|
|
29
30
|
</span>
|
|
30
|
-
<span class="text-
|
|
31
|
-
<span class="text-
|
|
32
|
-
<span class="text-
|
|
33
|
-
<span class="text-
|
|
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>
|
|
34
35
|
</div>
|
|
35
36
|
|
|
36
|
-
<h1 class="text-3xl sm:text-
|
|
37
|
+
<h1 class="text-3xl sm:text-4xl font-semibold text-white tracking-[-0.025em] mb-6 leading-tight">
|
|
37
38
|
{post.title}
|
|
38
39
|
</h1>
|
|
39
40
|
|
|
40
|
-
<div class="flex items-center gap-
|
|
41
|
+
<div class="flex items-center gap-3.5 p-3.5 rounded-xl bg-[#0f1117] border border-white/[0.08] shadow-[inset_0_1px_0_0_rgba(255,255,255,0.05)]">
|
|
41
42
|
<img
|
|
42
43
|
src={post.author.avatar}
|
|
43
44
|
alt={post.author.name}
|
|
44
|
-
class="w-
|
|
45
|
+
class="w-9 h-9 rounded-full object-cover border border-white/[0.1]"
|
|
45
46
|
/>
|
|
46
47
|
<div>
|
|
47
|
-
<div class="font-
|
|
48
|
-
<div class="text-
|
|
48
|
+
<div class="font-medium text-white text-xs">{post.author.name}</div>
|
|
49
|
+
<div class="text-[11px] text-zinc-400">{post.author.role}</div>
|
|
49
50
|
</div>
|
|
50
51
|
</div>
|
|
51
52
|
</header>
|
|
52
53
|
|
|
53
|
-
<article class="prose max-w-none space-y-6 text-
|
|
54
|
+
<article class="prose max-w-none space-y-6 text-zinc-300 leading-relaxed text-base">
|
|
54
55
|
{post.content.map((paragraph, idx) => (
|
|
55
56
|
<p key={idx}>{paragraph}</p>
|
|
56
57
|
))}
|
|
57
58
|
</article>
|
|
58
59
|
|
|
59
|
-
<div class="mt-12 pt-8 border-t border-[
|
|
60
|
+
<div class="mt-12 pt-8 border-t border-white/[0.08] flex flex-wrap gap-2">
|
|
60
61
|
{post.tags.map((tag) => (
|
|
61
62
|
<span
|
|
62
63
|
key={tag}
|
|
63
|
-
class="px-2.5 py-1 rounded bg-[
|
|
64
|
+
class="px-2.5 py-1 rounded-full bg-white/[0.04] text-xs font-medium text-zinc-300 border border-white/[0.06]"
|
|
64
65
|
>
|
|
65
|
-
|
|
66
|
+
{tag}
|
|
66
67
|
</span>
|
|
67
68
|
))}
|
|
68
69
|
</div>
|
|
@@ -9,10 +9,10 @@ export interface RootLayoutProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export default function RootLayout({ children, metadata }: RootLayoutProps) {
|
|
12
|
-
const title = metadata?.title || 'FrootBlog —
|
|
12
|
+
const title = metadata?.title || 'FrootBlog — Engineering Architecture';
|
|
13
13
|
const description =
|
|
14
14
|
metadata?.description ||
|
|
15
|
-
'
|
|
15
|
+
'Deep dives into Preact SSR, monomorphic engines, and zero-hydration architectures.';
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<html lang="en" class="dark scroll-smooth">
|
|
@@ -24,12 +24,12 @@ export default function RootLayout({ children, metadata }: RootLayoutProps) {
|
|
|
24
24
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
25
25
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
26
26
|
<link
|
|
27
|
-
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700
|
|
27
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
28
28
|
rel="stylesheet"
|
|
29
29
|
/>
|
|
30
30
|
<link rel="stylesheet" href="/styles.css" />
|
|
31
31
|
</head>
|
|
32
|
-
<body class="bg-[#
|
|
32
|
+
<body class="bg-[#08090a] text-zinc-100 min-h-screen antialiased selection:bg-indigo-500/20 selection:text-indigo-300 font-sans">
|
|
33
33
|
{children}
|
|
34
34
|
</body>
|
|
35
35
|
</html>
|
|
@@ -5,7 +5,7 @@ import { Newsletter } from '../components/Newsletter.js';
|
|
|
5
5
|
import { Footer } from '../components/Footer.js';
|
|
6
6
|
|
|
7
7
|
export const metadata = {
|
|
8
|
-
title: '
|
|
8
|
+
title: 'FrootJournal — Engineering Articles & Systems Architecture',
|
|
9
9
|
description: 'Deep dives into Preact SSR, fullstack TypeScript, and native styling.',
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -13,19 +13,20 @@ export default function BlogHomePage() {
|
|
|
13
13
|
const [featuredPost, ...recentPosts] = POSTS;
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
|
-
<div class="min-h-screen bg-[#
|
|
16
|
+
<div class="min-h-screen bg-[#08090a] text-zinc-100 flex flex-col justify-between selection:bg-indigo-500/20 selection:text-indigo-300">
|
|
17
17
|
<Navbar />
|
|
18
18
|
|
|
19
|
-
<main class="max-w-5xl mx-auto px-6 py-12 flex-1 w-full">
|
|
19
|
+
<main class="max-w-5xl mx-auto px-6 py-12 sm:py-16 flex-1 w-full">
|
|
20
20
|
{/* Header Block */}
|
|
21
|
-
<div class="mb-
|
|
22
|
-
<div class="
|
|
23
|
-
|
|
21
|
+
<div class="mb-12">
|
|
22
|
+
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 text-xs font-medium mb-4">
|
|
23
|
+
<span class="w-1.5 h-1.5 rounded-full bg-indigo-400" />
|
|
24
|
+
<span>Systems Architecture Journal</span>
|
|
24
25
|
</div>
|
|
25
|
-
<h1 class="text-3xl sm:text-
|
|
26
|
-
|
|
26
|
+
<h1 class="text-3xl sm:text-5xl font-semibold text-white tracking-[-0.03em] leading-tight mb-4">
|
|
27
|
+
Engineering Dispatch & Systems Architecture.
|
|
27
28
|
</h1>
|
|
28
|
-
<p class="text-
|
|
29
|
+
<p class="text-zinc-400 text-base max-w-2xl leading-relaxed">
|
|
29
30
|
Exploring sub-millisecond server rendering, monomorphic dispatch trees, and zero-hydration web architectures.
|
|
30
31
|
</p>
|
|
31
32
|
</div>
|
|
@@ -34,16 +35,16 @@ export default function BlogHomePage() {
|
|
|
34
35
|
{featuredPost && <PostCard post={featuredPost} featured={true} />}
|
|
35
36
|
|
|
36
37
|
{/* Recent Articles Grid */}
|
|
37
|
-
<div class="mb-8 flex items-center justify-between border-b border-[
|
|
38
|
-
<h2 class="text-xs
|
|
39
|
-
|
|
38
|
+
<div class="mb-8 flex items-center justify-between border-b border-white/[0.08] pb-4">
|
|
39
|
+
<h2 class="text-xs uppercase tracking-wider text-zinc-400 font-semibold">
|
|
40
|
+
Recent Publications
|
|
40
41
|
</h2>
|
|
41
|
-
<span class="text-xs
|
|
42
|
+
<span class="text-xs text-zinc-500 tabular-nums">
|
|
42
43
|
{recentPosts.length + 1} publications
|
|
43
44
|
</span>
|
|
44
45
|
</div>
|
|
45
46
|
|
|
46
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-
|
|
47
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
|
47
48
|
{recentPosts.map((post) => (
|
|
48
49
|
<PostCard key={post.slug} post={post} />
|
|
49
50
|
))}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export function Footer() {
|
|
2
2
|
return (
|
|
3
|
-
<footer class="border-t border-[
|
|
4
|
-
<div class="max-w-5xl mx-auto px-6 flex flex-col sm:flex-row items-center justify-between gap-4
|
|
3
|
+
<footer class="border-t border-white/[0.08] bg-[#08090a] py-8 text-xs text-zinc-500">
|
|
4
|
+
<div class="max-w-5xl mx-auto px-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
|
5
5
|
<div>
|
|
6
|
-
FrootBlog © {new Date().getFullYear()} OrbitNorth LLC.
|
|
6
|
+
FrootBlog © {new Date().getFullYear()} OrbitNorth LLC. Written for systems engineers.
|
|
7
7
|
</div>
|
|
8
8
|
<div class="flex items-center gap-6">
|
|
9
|
-
<a href="https://github.com/orbitnorth/frootkit" class="hover:text-
|
|
10
|
-
<a href="https://frootjs.org" class="hover:text-
|
|
11
|
-
<a href="/api/posts" class="hover:text-
|
|
9
|
+
<a href="https://github.com/orbitnorth/frootkit" class="hover:text-zinc-300 transition-colors">GitHub</a>
|
|
10
|
+
<a href="https://frootjs.org" class="hover:text-zinc-300 transition-colors">Documentation</a>
|
|
11
|
+
<a href="/api/posts" class="hover:text-zinc-300 transition-colors font-mono text-[11px]">/api/posts</a>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
14
14
|
</footer>
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
export function Navbar() {
|
|
2
2
|
return (
|
|
3
|
-
<header class="border-b border-[
|
|
4
|
-
<div class="max-w-5xl mx-auto px-6 h-
|
|
5
|
-
<a href="/" class="flex items-center gap-
|
|
6
|
-
<div class="w-
|
|
7
|
-
<svg
|
|
8
|
-
<path
|
|
3
|
+
<header class="border-b border-white/[0.08] bg-[#08090a]/80 backdrop-blur-md sticky top-0 z-50">
|
|
4
|
+
<div class="max-w-5xl mx-auto px-6 h-14 flex items-center justify-between">
|
|
5
|
+
<a href="/" class="flex items-center gap-2.5 font-semibold text-zinc-100 text-sm tracking-tight group">
|
|
6
|
+
<div class="w-6 h-6 rounded-md bg-indigo-500/15 border border-indigo-500/30 flex items-center justify-center text-indigo-400 group-hover:border-indigo-400/50 transition-colors shrink-0">
|
|
7
|
+
<svg style="width: 13px; height: 13px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
8
|
+
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
|
9
|
+
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
|
9
10
|
</svg>
|
|
10
11
|
</div>
|
|
11
|
-
<span>Froot<span class="text-
|
|
12
|
+
<span>Froot<span class="text-indigo-400">Journal</span></span>
|
|
12
13
|
</a>
|
|
13
14
|
|
|
14
|
-
<nav class="flex items-center gap-
|
|
15
|
-
<a href="/" class="text-
|
|
15
|
+
<nav class="flex items-center gap-5 text-xs font-medium">
|
|
16
|
+
<a href="/" class="text-zinc-400 hover:text-zinc-200 transition-colors">
|
|
16
17
|
Articles
|
|
17
18
|
</a>
|
|
18
|
-
<a
|
|
19
|
+
<a
|
|
20
|
+
href="/api/posts"
|
|
21
|
+
class="text-zinc-400 hover:text-zinc-200 font-mono text-[11px] px-2 py-0.5 rounded bg-white/[0.03] border border-white/[0.06] transition-colors"
|
|
22
|
+
>
|
|
19
23
|
/api/posts
|
|
20
24
|
</a>
|
|
21
25
|
<a
|
|
22
|
-
href="
|
|
23
|
-
|
|
24
|
-
rel="noreferrer"
|
|
25
|
-
class="text-slate-300 hover:text-white transition-colors"
|
|
26
|
+
href="#newsletter"
|
|
27
|
+
class="px-3 py-1.5 rounded-md bg-white text-zinc-950 font-medium hover:bg-zinc-200 transition-all text-xs shadow-sm"
|
|
26
28
|
>
|
|
27
|
-
|
|
29
|
+
Subscribe
|
|
28
30
|
</a>
|
|
29
31
|
</nav>
|
|
30
32
|
</div>
|