create-frootkit 1.2.8 → 1.2.10

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.
Files changed (52) hide show
  1. package/dist/addons/database.d.ts +13 -5
  2. package/dist/addons/database.d.ts.map +1 -1
  3. package/dist/addons/database.js +496 -94
  4. package/dist/addons/database.js.map +1 -1
  5. package/dist/scaffold.d.ts.map +1 -1
  6. package/dist/scaffold.js +5 -1
  7. package/dist/scaffold.js.map +1 -1
  8. package/package.json +1 -1
  9. package/templates/auth/src/app/api/auth/route.ts +17 -12
  10. package/templates/auth/src/app/dashboard/layout.tsx +14 -9
  11. package/templates/auth/src/app/dashboard/page.tsx +55 -24
  12. package/templates/auth/src/app/layout.tsx +3 -3
  13. package/templates/auth/src/app/login/page.tsx +5 -1
  14. package/templates/auth/src/app/page.tsx +103 -45
  15. package/templates/auth/src/app/signup/page.tsx +5 -1
  16. package/templates/auth/src/components/AuthForm.tsx +34 -19
  17. package/templates/auth/src/components/DashboardNav.tsx +97 -56
  18. package/templates/auth/src/components/MetricCard.tsx +13 -6
  19. package/templates/auth/src/components/Navbar.tsx +13 -12
  20. package/templates/auth/src/lib/auth.ts +123 -0
  21. package/templates/auth/src/styles.css +27 -5
  22. package/templates/blog/src/app/api/posts/route.ts +10 -34
  23. package/templates/blog/src/app/blog/[slug]/page.tsx +22 -21
  24. package/templates/blog/src/app/layout.tsx +4 -4
  25. package/templates/blog/src/app/page.tsx +19 -17
  26. package/templates/blog/src/components/Footer.tsx +6 -6
  27. package/templates/blog/src/components/Navbar.tsx +17 -15
  28. package/templates/blog/src/components/Newsletter.tsx +11 -9
  29. package/templates/blog/src/components/PostCard.tsx +29 -27
  30. package/templates/blog/src/data/posts.ts +48 -0
  31. package/templates/blog/src/styles.css +14 -5
  32. package/templates/ecommerce/src/app/api/products/route.ts +10 -26
  33. package/templates/ecommerce/src/app/cart/page.tsx +3 -3
  34. package/templates/ecommerce/src/app/layout.tsx +2 -2
  35. package/templates/ecommerce/src/app/page.tsx +41 -18
  36. package/templates/ecommerce/src/app/products/[id]/page.tsx +37 -28
  37. package/templates/ecommerce/src/components/CartDrawer.tsx +20 -17
  38. package/templates/ecommerce/src/components/Footer.tsx +6 -6
  39. package/templates/ecommerce/src/components/Navbar.tsx +21 -14
  40. package/templates/ecommerce/src/components/ProductCard.tsx +22 -15
  41. package/templates/ecommerce/src/data/products.ts +55 -11
  42. package/templates/ecommerce/src/styles.css +29 -5
  43. package/templates/empty/src/app/layout.tsx +5 -5
  44. package/templates/empty/src/app/page.tsx +10 -9
  45. package/templates/empty/src/styles.css +13 -15
  46. package/templates/starter/src/app/layout.tsx +4 -4
  47. package/templates/starter/src/app/page.tsx +41 -33
  48. package/templates/starter/src/components/FeatureCard.tsx +5 -5
  49. package/templates/starter/src/components/Footer.tsx +6 -6
  50. package/templates/starter/src/components/Header.tsx +18 -14
  51. package/templates/starter/src/components/TerminalPreview.tsx +30 -30
  52. package/templates/starter/src/styles.css +14 -5
@@ -1,4 +1,4 @@
1
- import { POSTS } from '../../../data/posts.js';
1
+ import { getPostBySlug, POSTS } from '../../../data/posts.js';
2
2
  import { Navbar } from '../../../components/Navbar.js';
3
3
  import { Footer } from '../../../components/Footer.js';
4
4
 
@@ -6,63 +6,64 @@ export async function generateStaticParams() {
6
6
  return POSTS.map((post) => ({ slug: post.slug }));
7
7
  }
8
8
 
9
- export default function BlogPostPage({ params }: { params?: { slug?: string } }) {
9
+ export default async function BlogPostPage({ params }: { params?: { slug?: string } }) {
10
10
  const slug = params?.slug || 'welcome-to-frootkit';
11
- const post = POSTS.find((p) => p.slug === slug) || POSTS[0]!;
11
+ const post = (await getPostBySlug(slug)) || POSTS[0]!;
12
12
 
13
13
  return (
14
- <div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
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 font-mono text-slate-400 hover:text-emerald-400 mb-8 transition-colors"
20
+ class="inline-flex items-center gap-2 text-xs text-zinc-400 hover:text-white mb-8 transition-colors"
21
21
  >
22
- ← Back to articles
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 font-mono">
27
- <span class="px-2.5 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 font-semibold">
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-slate-500">•</span>
31
- <span class="text-slate-400">{post.readTime}</span>
32
- <span class="text-slate-500">•</span>
33
- <span class="text-slate-400">{post.date}</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>
34
35
  </div>
35
36
 
36
- <h1 class="text-3xl sm:text-5xl font-extrabold text-white tracking-tight mb-6 leading-tight">
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-4 p-4 rounded-xl bg-[#0a0f18] border border-[#1a2333]">
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-10 h-10 rounded-full object-cover border border-[#1a2333]"
45
+ class="w-9 h-9 rounded-full object-cover border border-white/[0.1]"
45
46
  />
46
47
  <div>
47
- <div class="font-bold text-white text-sm">{post.author.name}</div>
48
- <div class="text-xs text-slate-400">{post.author.role}</div>
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-slate-300 leading-relaxed text-base sm:text-lg">
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-[#1a2333] flex flex-wrap gap-2">
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-[#0a0f18] text-xs font-mono text-slate-400 border border-[#1a2333]"
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
- #{tag}
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 — Insights on Modern Web Performance';
12
+ const title = metadata?.title || 'FrootBlog — Engineering Architecture';
13
13
  const description =
14
14
  metadata?.description ||
15
- 'High-performance engineering articles and tutorials powered by Preact SSR and FrootKit.';
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;800&family=JetBrains+Mono:wght@400;500;600&display=swap"
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-[#030712] text-slate-100 min-h-screen antialiased selection:bg-emerald-500/20 selection:text-emerald-300 font-sans">
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>
@@ -1,31 +1,33 @@
1
- import { POSTS } from '../data/posts.js';
1
+ import { getPosts, POSTS } from '../data/posts.js';
2
2
  import { Navbar } from '../components/Navbar.js';
3
3
  import { PostCard } from '../components/PostCard.js';
4
4
  import { Newsletter } from '../components/Newsletter.js';
5
5
  import { Footer } from '../components/Footer.js';
6
6
 
7
7
  export const metadata = {
8
- title: 'FrootBlog — Engineering Articles & Systems Architecture',
8
+ title: 'FrootJournal — Engineering Articles & Systems Architecture',
9
9
  description: 'Deep dives into Preact SSR, fullstack TypeScript, and native styling.',
10
10
  };
11
11
 
12
- export default function BlogHomePage() {
13
- const [featuredPost, ...recentPosts] = POSTS;
12
+ export default async function BlogHomePage() {
13
+ const allPosts = await getPosts();
14
+ const [featuredPost, ...recentPosts] = allPosts;
14
15
 
15
16
  return (
16
- <div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
17
+ <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
18
  <Navbar />
18
19
 
19
- <main class="max-w-5xl mx-auto px-6 py-12 flex-1 w-full">
20
+ <main class="max-w-5xl mx-auto px-6 py-12 sm:py-16 flex-1 w-full">
20
21
  {/* Header Block */}
21
- <div class="mb-10">
22
- <div class="text-xs font-mono font-medium text-emerald-400 mb-2 uppercase tracking-wide">
23
- Engineering Journal
22
+ <div class="mb-12">
23
+ <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">
24
+ <span class="w-1.5 h-1.5 rounded-full bg-indigo-400" />
25
+ <span>Systems Architecture Journal</span>
24
26
  </div>
25
- <h1 class="text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-3">
26
- Systems Architecture & Web Engineering
27
+ <h1 class="text-3xl sm:text-5xl font-semibold text-white tracking-[-0.03em] leading-tight mb-4">
28
+ Engineering Dispatch & Systems Architecture.
27
29
  </h1>
28
- <p class="text-slate-400 text-base max-w-2xl leading-relaxed">
30
+ <p class="text-zinc-400 text-base max-w-2xl leading-relaxed">
29
31
  Exploring sub-millisecond server rendering, monomorphic dispatch trees, and zero-hydration web architectures.
30
32
  </p>
31
33
  </div>
@@ -34,16 +36,16 @@ export default function BlogHomePage() {
34
36
  {featuredPost && <PostCard post={featuredPost} featured={true} />}
35
37
 
36
38
  {/* Recent Articles Grid */}
37
- <div class="mb-8 flex items-center justify-between border-b border-[#1a2333] pb-3">
38
- <h2 class="text-xs font-mono uppercase tracking-wider text-slate-400 font-semibold">
39
- All Articles
39
+ <div class="mb-8 flex items-center justify-between border-b border-white/[0.08] pb-4">
40
+ <h2 class="text-xs uppercase tracking-wider text-zinc-400 font-semibold">
41
+ Recent Publications
40
42
  </h2>
41
- <span class="text-xs font-mono text-slate-500">
43
+ <span class="text-xs text-zinc-500 tabular-nums">
42
44
  {recentPosts.length + 1} publications
43
45
  </span>
44
46
  </div>
45
47
 
46
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
48
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-5">
47
49
  {recentPosts.map((post) => (
48
50
  <PostCard key={post.slug} post={post} />
49
51
  ))}
@@ -1,14 +1,14 @@
1
1
  export function Footer() {
2
2
  return (
3
- <footer class="border-t border-[#1a2333] bg-[#06090f] py-8 text-xs text-slate-400">
4
- <div class="max-w-5xl mx-auto px-6 flex flex-col sm:flex-row items-center justify-between gap-4 font-mono">
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. MIT Licensed.
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-white transition-colors">GitHub</a>
10
- <a href="https://frootjs.org" class="hover:text-white transition-colors">Documentation</a>
11
- <a href="/api/posts" class="hover:text-white transition-colors">Posts API</a>
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-[#1a2333] bg-[#06090f]/95 sticky top-0 z-50">
4
- <div class="max-w-5xl mx-auto px-6 h-16 flex items-center justify-between">
5
- <a href="/" class="flex items-center gap-3 font-bold text-white text-base tracking-tight hover:text-emerald-400 transition-colors">
6
- <div class="w-8 h-8 rounded-lg bg-emerald-500/10 border border-emerald-500/30 flex items-center justify-center text-emerald-400">
7
- <svg width="16" height="16" class="w-4 h-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
8
- <path stroke-linecap="round" stroke-linejoin="round" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
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-emerald-400">Blog</span></span>
12
+ <span>Froot<span class="text-indigo-400">Journal</span></span>
12
13
  </a>
13
14
 
14
- <nav class="flex items-center gap-6 text-sm font-medium">
15
- <a href="/" class="text-slate-300 hover:text-white transition-colors">
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 href="/api/posts" class="text-emerald-400 hover:text-emerald-300 font-mono text-xs px-2.5 py-1 rounded-md bg-[#0a0f18] border border-[#1a2333] transition-colors">
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="https://github.com/orbitnorth/frootkit"
23
- target="_blank"
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
- GitHub
29
+ Subscribe
28
30
  </a>
29
31
  </nav>
30
32
  </div>
@@ -1,21 +1,23 @@
1
1
  export function Newsletter() {
2
2
  return (
3
- <div class="rounded-xl border border-[#1a2333] bg-[#0a0f18] p-8 sm:p-10 my-16">
3
+ <div id="newsletter" class="rounded-xl border border-white/[0.08] bg-[#0f1117] p-8 sm:p-10 my-16 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.05)] relative overflow-hidden">
4
+ <div class="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-indigo-500/30 to-transparent" />
4
5
  <div class="max-w-xl">
5
- <div class="text-xs font-mono font-medium text-emerald-400 mb-2 uppercase tracking-wide">
6
- Engineering Dispatch
6
+ <div class="inline-flex items-center gap-2 px-2.5 py-0.5 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 text-[11px] font-medium mb-3">
7
+ <span class="w-1.5 h-1.5 rounded-full bg-indigo-400" />
8
+ <span>Engineering Dispatch</span>
7
9
  </div>
8
- <h3 class="text-2xl font-bold text-white mb-2">
10
+ <h3 class="text-xl sm:text-2xl font-semibold text-white tracking-tight mb-2">
9
11
  Subscribe for Technical Deep Dives
10
12
  </h3>
11
- <p class="text-slate-400 text-sm mb-6 leading-relaxed">
12
- Bi-weekly write-ups on sub-millisecond server rendering, zero-hydration architectures, and high-performance TypeScript.
13
+ <p class="text-zinc-400 text-xs sm:text-sm mb-6 leading-relaxed">
14
+ Bi-weekly write-ups on sub-millisecond server rendering, zero-hydration architectures, and high-performance TypeScript runtime optimizations.
13
15
  </p>
14
16
 
15
17
  <form
16
18
  action="/api/posts"
17
19
  method="GET"
18
- class="flex flex-col sm:flex-row gap-3 max-w-md"
20
+ class="flex flex-col sm:flex-row gap-2.5 max-w-md"
19
21
  >
20
22
  <label class="sr-only" for="newsletter-email">Work Email</label>
21
23
  <input
@@ -23,11 +25,11 @@ export function Newsletter() {
23
25
  type="email"
24
26
  placeholder="developer@company.com"
25
27
  required
26
- class="flex-1 px-4 py-2.5 rounded-lg bg-[#06090f] border border-[#1a2333] text-white text-sm focus:outline-none focus:border-emerald-500 transition-colors"
28
+ class="flex-1 px-3.5 py-2 rounded-lg bg-[#08090a] border border-white/[0.08] text-white text-xs placeholder:text-zinc-600 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors"
27
29
  />
28
30
  <button
29
31
  type="submit"
30
- class="px-5 py-2.5 rounded-lg bg-emerald-500 text-slate-950 font-semibold hover:bg-emerald-400 transition-colors text-sm"
32
+ class="px-4 py-2 rounded-lg bg-white text-zinc-950 font-medium hover:bg-zinc-200 transition-all text-xs shadow-sm"
31
33
  >
32
34
  Subscribe
33
35
  </button>
@@ -3,45 +3,46 @@ import type { Post } from '../data/posts.js';
3
3
  export function PostCard({ post, featured = false }: { post: Post; featured?: boolean }) {
4
4
  if (featured) {
5
5
  return (
6
- <article class="rounded-xl border border-[#1a2333] bg-[#0a0f18] p-8 hover:border-slate-700 transition-colors mb-10">
7
- <div class="flex items-center gap-3 mb-4 text-xs font-mono">
8
- <span class="text-emerald-400 font-semibold px-2.5 py-0.5 rounded bg-emerald-500/10 border border-emerald-500/20">
6
+ <article class="rounded-xl border border-white/[0.08] bg-[#0f1117] p-7 sm:p-8 hover:border-white/[0.18] transition-all duration-200 mb-10 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.05)] group">
7
+ <div class="flex flex-wrap items-center gap-2.5 mb-4 text-xs">
8
+ <span class="text-indigo-400 font-medium px-2.5 py-0.5 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-[11px]">
9
9
  Featured • {post.category}
10
10
  </span>
11
- <span class="text-slate-500">•</span>
12
- <span class="text-slate-400">{post.readTime}</span>
13
- <span class="text-slate-500">•</span>
14
- <span class="text-slate-400">{post.date}</span>
11
+ <span class="text-zinc-600">•</span>
12
+ <span class="text-zinc-400">{post.readTime}</span>
13
+ <span class="text-zinc-600">•</span>
14
+ <span class="text-zinc-400">{post.date}</span>
15
15
  </div>
16
16
 
17
- <h2 class="text-2xl sm:text-3xl font-extrabold text-white mb-4 leading-snug hover:text-emerald-400 transition-colors">
17
+ <h2 class="text-2xl sm:text-3xl font-semibold text-white mb-3 tracking-[-0.02em] leading-snug group-hover:text-indigo-300 transition-colors">
18
18
  <a href={`/blog/${post.slug}`}>
19
19
  {post.title}
20
20
  </a>
21
21
  </h2>
22
22
 
23
- <p class="text-slate-300 text-base leading-relaxed mb-6 max-w-3xl">
23
+ <p class="text-zinc-400 text-sm sm:text-base leading-relaxed mb-6 max-w-3xl">
24
24
  {post.summary}
25
25
  </p>
26
26
 
27
- <div class="pt-6 border-t border-[#1a2333] flex items-center justify-between">
27
+ <div class="pt-6 border-t border-white/[0.06] flex items-center justify-between">
28
28
  <div class="flex items-center gap-3">
29
29
  <img
30
30
  src={post.author.avatar}
31
31
  alt={post.author.name}
32
- class="w-8 h-8 rounded-full object-cover border border-[#1a2333]"
32
+ class="w-8 h-8 rounded-full object-cover border border-white/[0.1]"
33
33
  />
34
34
  <div class="text-xs">
35
- <span class="font-medium text-white block">{post.author.name}</span>
36
- <span class="text-slate-500">{post.author.role}</span>
35
+ <span class="font-medium text-zinc-200 block">{post.author.name}</span>
36
+ <span class="text-zinc-500">{post.author.role}</span>
37
37
  </div>
38
38
  </div>
39
39
 
40
40
  <a
41
41
  href={`/blog/${post.slug}`}
42
- class="text-emerald-400 text-xs font-semibold hover:text-emerald-300 transition-colors flex items-center gap-1"
42
+ class="text-zinc-200 text-xs font-medium group-hover:text-white transition-colors flex items-center gap-1.5"
43
43
  >
44
- Read Article →
44
+ <span>Read Article</span>
45
+ <span class="text-zinc-500 group-hover:translate-x-0.5 transition-transform">→</span>
45
46
  </a>
46
47
  </div>
47
48
  </article>
@@ -49,44 +50,45 @@ export function PostCard({ post, featured = false }: { post: Post; featured?: bo
49
50
  }
50
51
 
51
52
  return (
52
- <article class="rounded-xl border border-[#1a2333] bg-[#0a0f18] p-6 hover:border-slate-700 transition-colors flex flex-col justify-between">
53
+ <article class="rounded-xl border border-white/[0.08] bg-[#0f1117] p-5 sm:p-6 hover:border-white/[0.18] transition-all duration-200 flex flex-col justify-between shadow-[inset_0_1px_0_0_rgba(255,255,255,0.05)] group">
53
54
  <div>
54
- <div class="flex items-center justify-between mb-3 text-xs font-mono">
55
- <span class="text-emerald-400 font-semibold px-2 py-0.5 rounded bg-emerald-500/10 border border-emerald-500/20">
55
+ <div class="flex items-center justify-between mb-3 text-xs">
56
+ <span class="text-zinc-300 font-medium px-2 py-0.5 rounded-full bg-white/[0.04] border border-white/[0.06] text-[10px]">
56
57
  {post.category}
57
58
  </span>
58
- <span class="text-slate-500">{post.readTime}</span>
59
+ <span class="text-zinc-500 text-[11px]">{post.readTime}</span>
59
60
  </div>
60
61
 
61
- <h3 class="text-lg font-bold text-white mb-3 hover:text-emerald-400 transition-colors line-clamp-2 leading-snug">
62
+ <h3 class="text-base font-semibold text-zinc-100 mb-2.5 group-hover:text-white transition-colors line-clamp-2 leading-snug">
62
63
  <a href={`/blog/${post.slug}`}>
63
64
  {post.title}
64
65
  </a>
65
66
  </h3>
66
67
 
67
- <p class="text-slate-400 text-sm leading-relaxed mb-6 line-clamp-3">
68
+ <p class="text-zinc-400 text-xs leading-relaxed mb-6 line-clamp-3">
68
69
  {post.summary}
69
70
  </p>
70
71
  </div>
71
72
 
72
- <div class="pt-4 border-t border-[#1a2333] flex items-center justify-between">
73
+ <div class="pt-4 border-t border-white/[0.06] flex items-center justify-between">
73
74
  <div class="flex items-center gap-2.5">
74
75
  <img
75
76
  src={post.author.avatar}
76
77
  alt={post.author.name}
77
- class="w-6 h-6 rounded-full object-cover border border-[#1a2333]"
78
+ class="w-6 h-6 rounded-full object-cover border border-white/[0.1]"
78
79
  />
79
80
  <div class="text-[11px]">
80
- <span class="font-medium text-slate-200 block">{post.author.name}</span>
81
- <span class="text-slate-500">{post.date}</span>
81
+ <span class="font-medium text-zinc-300 block">{post.author.name}</span>
82
+ <span class="text-zinc-500">{post.date}</span>
82
83
  </div>
83
84
  </div>
84
85
 
85
86
  <a
86
87
  href={`/blog/${post.slug}`}
87
- class="text-emerald-400 text-xs font-semibold hover:text-emerald-300 transition-colors"
88
+ class="text-xs font-medium text-zinc-400 group-hover:text-white transition-colors flex items-center gap-1"
88
89
  >
89
- Read →
90
+ <span>Read</span>
91
+ <span class="text-zinc-600">→</span>
90
92
  </a>
91
93
  </div>
92
94
  </article>
@@ -73,3 +73,51 @@ export const POSTS: Post[] = [
73
73
  ],
74
74
  },
75
75
  ];
76
+
77
+ /**
78
+ * Returns all articles, querying the database if configured or falling back to static POSTS.
79
+ */
80
+ export async function getPosts(): Promise<Post[]> {
81
+ try {
82
+ const { db } = await import('../db/index.js');
83
+ const { posts, authors } = await import('../db/schema.js');
84
+ const dbPosts = await db.select().from(posts);
85
+ if (dbPosts && dbPosts.length > 0) {
86
+ const allAuthors = await db.select().from(authors);
87
+ const authorMap = new Map(allAuthors.map((a: any) => [a.id, a]));
88
+ return dbPosts.map((p: any) => {
89
+ const auth = authorMap.get(p.authorId) || {
90
+ name: 'Alex Rivera',
91
+ role: 'Staff Systems Architect',
92
+ avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop&crop=faces',
93
+ };
94
+ return {
95
+ slug: p.slug,
96
+ title: p.title,
97
+ summary: p.summary,
98
+ date: 'Recent',
99
+ readTime: p.readTime,
100
+ category: p.category,
101
+ author: {
102
+ name: auth.name,
103
+ role: auth.role,
104
+ avatar: auth.avatar,
105
+ },
106
+ tags: ['FrootKit', 'Architecture', 'TypeScript'],
107
+ content: typeof p.content === 'string' ? JSON.parse(p.content) : p.content,
108
+ };
109
+ });
110
+ }
111
+ } catch {
112
+ // Database not attached, fallback
113
+ }
114
+ return POSTS;
115
+ }
116
+
117
+ /**
118
+ * Returns a single article by slug.
119
+ */
120
+ export async function getPostBySlug(slug: string): Promise<Post | undefined> {
121
+ const allPosts = await getPosts();
122
+ return allPosts.find((p) => p.slug === slug);
123
+ }
@@ -4,23 +4,32 @@
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: #10b981;
8
- --color-brand-hover: #34d399;
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: #06090f;
17
- color: #f8fafc;
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: prevents flex ballooning across all browsers */
32
+ /* Strict SVG safety to prevent flex ballooning */
24
33
  svg {
25
34
  flex-shrink: 0;
26
35
  max-width: 100%;
@@ -1,28 +1,12 @@
1
- import { PRODUCTS } from '../../../data/products.js';
2
-
3
- export async function GET(req: Request): Promise<Response> {
4
- const url = new URL(req.url);
5
- const category = url.searchParams.get('category');
6
- const limit = Number(url.searchParams.get('limit') || 20);
7
-
8
- let results = [...PRODUCTS];
9
- if (category) {
10
- results = results.filter((p) => p.category.toLowerCase() === category.toLowerCase());
11
- }
12
-
13
- results = results.slice(0, limit);
14
-
15
- return Response.json(
16
- {
17
- products: results,
18
- total: results.length,
19
- timestamp: new Date().toISOString(),
1
+ import { getProducts } from '../../../data/products.js';
2
+
3
+ export async function GET(): Promise<Response> {
4
+ const products = await getProducts();
5
+ return Response.json(products, {
6
+ status: 200,
7
+ headers: {
8
+ 'content-type': 'application/json',
9
+ 'cache-control': 'public, max-age=60',
20
10
  },
21
- {
22
- status: 200,
23
- headers: {
24
- 'content-type': 'application/json',
25
- },
26
- }
27
- );
11
+ });
28
12
  }
@@ -8,11 +8,11 @@ export const metadata = {
8
8
 
9
9
  export default function CartPage() {
10
10
  return (
11
- <div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
11
+ <div class="min-h-screen bg-[#090a0f] text-zinc-100 flex flex-col justify-between selection:bg-white/20 selection:text-white">
12
12
  <Navbar />
13
13
 
14
- <main class="max-w-4xl mx-auto px-6 py-12 flex-1 w-full">
15
- <h1 class="text-3xl font-extrabold text-white mb-8">Order Cart</h1>
14
+ <main class="max-w-3xl mx-auto px-6 py-12 flex-1 w-full">
15
+ <h1 class="text-2xl font-semibold text-white tracking-tight mb-6">Shopping Cart</h1>
16
16
  <CartDrawer />
17
17
  </main>
18
18
 
@@ -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;800&family=JetBrains+Mono:wght@400;500;600&display=swap"
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-[#030712] text-slate-100 min-h-screen antialiased selection:bg-emerald-500/20 selection:text-emerald-300 font-sans">
32
+ <body class="bg-[#090a0f] text-zinc-100 min-h-screen antialiased selection:bg-white/20 selection:text-white font-sans">
33
33
  {children}
34
34
  </body>
35
35
  </html>