create-frootkit 1.2.12 → 1.2.14

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 (40) hide show
  1. package/package.json +1 -1
  2. package/templates/auth/package.json +2 -1
  3. package/templates/auth/src/app/dashboard/layout.tsx +8 -11
  4. package/templates/auth/src/app/dashboard/page.tsx +31 -27
  5. package/templates/auth/src/app/layout.tsx +5 -5
  6. package/templates/auth/src/app/login/page.tsx +11 -4
  7. package/templates/auth/src/app/page.tsx +278 -120
  8. package/templates/auth/src/app/signup/page.tsx +12 -6
  9. package/templates/auth/src/components/AuthForm.tsx +27 -34
  10. package/templates/auth/src/components/DashboardNav.tsx +41 -61
  11. package/templates/auth/src/components/MetricCard.tsx +12 -22
  12. package/templates/auth/src/components/Navbar.tsx +18 -21
  13. package/templates/auth/src/styles.css +157 -27
  14. package/templates/blog/package.json +2 -1
  15. package/templates/blog/src/app/blog/[slug]/page.tsx +85 -46
  16. package/templates/blog/src/app/layout.tsx +5 -5
  17. package/templates/blog/src/app/page.tsx +39 -26
  18. package/templates/blog/src/components/Footer.tsx +11 -9
  19. package/templates/blog/src/components/Navbar.tsx +21 -26
  20. package/templates/blog/src/components/Newsletter.tsx +11 -33
  21. package/templates/blog/src/components/PostCard.tsx +43 -76
  22. package/templates/blog/src/styles.css +113 -25
  23. package/templates/ecommerce/package.json +2 -1
  24. package/templates/ecommerce/src/app/cart/page.tsx +12 -4
  25. package/templates/ecommerce/src/app/layout.tsx +5 -5
  26. package/templates/ecommerce/src/app/page.tsx +92 -37
  27. package/templates/ecommerce/src/app/products/[id]/page.tsx +39 -31
  28. package/templates/ecommerce/src/components/CartDrawer.tsx +113 -45
  29. package/templates/ecommerce/src/components/Footer.tsx +11 -9
  30. package/templates/ecommerce/src/components/Navbar.tsx +32 -34
  31. package/templates/ecommerce/src/components/ProductCard.tsx +27 -38
  32. package/templates/ecommerce/src/styles.css +155 -36
  33. package/templates/empty/package.json +2 -1
  34. package/templates/starter/package.json +2 -1
  35. package/templates/starter/src/app/layout.tsx +5 -5
  36. package/templates/starter/src/app/page.tsx +114 -107
  37. package/templates/starter/src/components/FeatureCard.tsx +9 -15
  38. package/templates/starter/src/components/Footer.tsx +11 -9
  39. package/templates/starter/src/components/Header.tsx +15 -38
  40. package/templates/starter/src/styles.css +102 -27
@@ -9,13 +9,13 @@ export interface RootLayoutProps {
9
9
  }
10
10
 
11
11
  export default function RootLayout({ children, metadata }: RootLayoutProps) {
12
- const title = metadata?.title || 'FrootBlog — Engineering Architecture';
12
+ const title = metadata?.title || 'Dispatch — Notes on building payments software';
13
13
  const description =
14
14
  metadata?.description ||
15
- 'Deep dives into Preact SSR, monomorphic engines, and zero-hydration architectures.';
15
+ 'Engineering, product, and finance stories from the team behind FrootKit.';
16
16
 
17
17
  return (
18
- <html lang="en" class="dark scroll-smooth">
18
+ <html lang="en" class="scroll-smooth">
19
19
  <head>
20
20
  <meta charSet="UTF-8" />
21
21
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -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&family=JetBrains+Mono:wght@400;500&display=swap"
27
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&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-[#08090a] text-zinc-100 min-h-screen antialiased selection:bg-indigo-500/20 selection:text-indigo-300 font-sans">
32
+ <body class="bg-white font-sans text-[#425466] antialiased">
33
33
  {children}
34
34
  </body>
35
35
  </html>
@@ -1,58 +1,71 @@
1
- import { getPosts, POSTS } from '../data/posts.js';
1
+ import { getPosts } 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: 'FrootJournal — Engineering Articles & Systems Architecture',
9
- description: 'Deep dives into Preact SSR, fullstack TypeScript, and native styling.',
8
+ title: 'Dispatch — Notes on building modern web software',
9
+ description: 'Engineering, product and architecture stories from the team behind FrootKit.',
10
10
  };
11
11
 
12
12
  export default async function BlogHomePage() {
13
13
  const allPosts = await getPosts();
14
14
  const [featuredPost, ...recentPosts] = allPosts;
15
+ const categories = ['All', 'Engineering', 'Architecture', 'Styling', 'Performance'];
15
16
 
16
17
  return (
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">
18
+ <div class="bg-white font-sans text-[#425466]">
18
19
  <Navbar />
19
20
 
20
- <main class="max-w-5xl mx-auto px-6 py-12 sm:py-16 flex-1 w-full">
21
- {/* Header Block */}
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>
26
- </div>
27
- <h1 class="text-3xl sm:text-5xl font-semibold text-white tracking-[-0.03em] leading-tight mb-4">
28
- Engineering Dispatch & Systems Architecture.
21
+ {/* GRADIENT HERO */}
22
+ <section class="relative overflow-hidden text-white" style="background: linear-gradient(160deg, #635bff 0%, #7a73ff 40%, #00d4ff 100%)">
23
+ <div class="mx-auto max-w-6xl px-6 py-14 md:py-20">
24
+ <h1 class="max-w-2xl text-[40px] font-light leading-[1.08] tracking-tight md:text-[56px] md:leading-[64px]">
25
+ Notes on building modern web systems
29
26
  </h1>
30
- <p class="text-zinc-400 text-base max-w-2xl leading-relaxed">
31
- Exploring sub-millisecond server rendering, monomorphic dispatch trees, and zero-hydration web architectures.
27
+ <p class="mt-5 max-w-lg text-[18px] font-light leading-7 text-white/90">
28
+ Engineering, product and systems architecture stories from the team behind FrootKit.
32
29
  </p>
33
30
  </div>
31
+ </section>
34
32
 
35
- {/* Featured Dominant Article */}
33
+ {/* FEATURED STORY CARD */}
34
+ <section class="mx-auto max-w-6xl px-6 -mt-8 pb-6">
36
35
  {featuredPost && <PostCard post={featuredPost} featured={true} />}
36
+ </section>
37
37
 
38
- {/* Recent Articles Grid */}
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
42
- </h2>
43
- <span class="text-xs text-zinc-500 tabular-nums">
44
- {recentPosts.length + 1} publications
45
- </span>
38
+ {/* ARTICLES GRID */}
39
+ <section id="articles" class="mx-auto max-w-6xl px-6 py-8">
40
+ <div class="flex flex-wrap items-center justify-between gap-3">
41
+ <div class="flex flex-wrap gap-2">
42
+ {categories.map((c, i) => (
43
+ <a
44
+ key={c}
45
+ href="/#articles"
46
+ class={`rounded-full px-4 py-1.5 text-[14px] font-medium transition ${
47
+ i === 0
48
+ ? 'bg-[#0a2540] text-white shadow-sm'
49
+ : 'bg-[#f6f9fc] text-[#425466] hover:bg-[#e6ebf1]'
50
+ }`}
51
+ >
52
+ {c}
53
+ </a>
54
+ ))}
55
+ </div>
56
+ <p class="text-[14px] text-[#425466]">{allPosts.length} publications</p>
46
57
  </div>
47
58
 
48
- <div class="grid grid-cols-1 md:grid-cols-2 gap-5">
59
+ <div class="mt-8 grid gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3">
49
60
  {recentPosts.map((post) => (
50
61
  <PostCard key={post.slug} post={post} />
51
62
  ))}
52
63
  </div>
64
+ </section>
53
65
 
66
+ <section class="mx-auto max-w-6xl px-6 pt-6">
54
67
  <Newsletter />
55
- </main>
68
+ </section>
56
69
 
57
70
  <Footer />
58
71
  </div>
@@ -1,14 +1,16 @@
1
1
  export function Footer() {
2
2
  return (
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
- <div>
6
- FrootBlog © {new Date().getFullYear()} OrbitNorth LLC. Written for systems engineers.
7
- </div>
8
- <div class="flex items-center gap-6">
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>
3
+ <footer class="border-t border-[#e6ebf1] py-10 text-[14px] bg-white">
4
+ <div class="mx-auto max-w-6xl px-6 flex flex-wrap justify-between gap-4 text-xs text-[#425466]">
5
+ <span>
6
+ <b class="text-[#635bff] font-semibold text-sm">dispatch</b> · Notes on building web systems with FrootKit.
7
+ </span>
8
+ <div class="flex items-center gap-4">
9
+ <a href="/#articles" class="hover:text-[#0a2540]">RSS</a>
10
+ <span>·</span>
11
+ <a href="/#articles" class="hover:text-[#0a2540]">Privacy</a>
12
+ <span>·</span>
13
+ <a href="/api/posts" class="font-mono hover:text-[#0a2540]">/api/posts</a>
12
14
  </div>
13
15
  </div>
14
16
  </footer>
@@ -1,34 +1,29 @@
1
1
  export function Navbar() {
2
2
  return (
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" />
10
- </svg>
11
- </div>
12
- <span>Froot<span class="text-indigo-400">Journal</span></span>
3
+ <header class="sticky top-0 z-40 border-b border-[#e6ebf1] bg-white/90 backdrop-blur">
4
+ <div class="mx-auto max-w-6xl px-6 flex items-center gap-6 py-3">
5
+ <a href="/" class="text-[22px] font-semibold tracking-tight text-[#635bff]">
6
+ dispatch
13
7
  </a>
14
8
 
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">
17
- Articles
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
- >
23
- /api/posts
24
- </a>
25
- <a
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"
28
- >
29
- Subscribe
30
- </a>
9
+ <nav class="hidden gap-6 text-[14px] font-medium md:flex">
10
+ <a href="/#articles" class="text-[#425466] hover:text-[#635bff] transition-colors">Engineering</a>
11
+ <a href="/#articles" class="text-[#425466] hover:text-[#635bff] transition-colors">Product</a>
12
+ <a href="/#articles" class="text-[#425466] hover:text-[#635bff] transition-colors">Finance</a>
13
+ <a href="/#articles" class="text-[#425466] hover:text-[#635bff] transition-colors">Company</a>
31
14
  </nav>
15
+
16
+ <div class="relative ml-auto w-44 sm:w-64">
17
+ <svg class="absolute left-3 top-2.5 h-4 w-4 text-[#425466]/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
18
+ <circle cx="11" cy="11" r="7" />
19
+ <path d="m20 20-3-3" />
20
+ </svg>
21
+ <input class="inp pl-9 py-1.5 text-xs" placeholder="Search articles" aria-label="Search articles" />
22
+ </div>
23
+
24
+ <a href="#subscribe" class="btn btn-p hidden rounded-full sm:inline-flex text-xs py-1.5 px-4">
25
+ Subscribe
26
+ </a>
32
27
  </div>
33
28
  </header>
34
29
  );
@@ -1,40 +1,18 @@
1
1
  export function Newsletter() {
2
2
  return (
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" />
5
- <div class="max-w-xl">
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>
3
+ <section id="subscribe" class="scroll-mt-20 pb-16">
4
+ <div class="grid items-center gap-6 rounded-lg bg-[#f6f9fc] p-8 md:p-12 border border-[#e6ebf1]">
5
+ <div>
6
+ <h2 class="text-[28px] font-light tracking-tight text-[#0a2540]">
7
+ Get new posts in your inbox
8
+ </h2>
9
+ <p class="mt-1 text-sm text-[#425466]">One email every other Thursday. No spam.</p>
9
10
  </div>
10
- <h3 class="text-xl sm:text-2xl font-semibold text-white tracking-tight mb-2">
11
- Subscribe for Technical Deep Dives
12
- </h3>
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.
15
- </p>
16
-
17
- <form
18
- action="/api/posts"
19
- method="GET"
20
- class="flex flex-col sm:flex-row gap-2.5 max-w-md"
21
- >
22
- <label class="sr-only" for="newsletter-email">Work Email</label>
23
- <input
24
- id="newsletter-email"
25
- type="email"
26
- placeholder="developer@company.com"
27
- required
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"
29
- />
30
- <button
31
- type="submit"
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"
33
- >
34
- Subscribe
35
- </button>
11
+ <form class="flex gap-2">
12
+ <input class="inp" type="email" placeholder="you@company.com" aria-label="Email" />
13
+ <button type="button" class="btn btn-p shrink-0">Subscribe</button>
36
14
  </form>
37
15
  </div>
38
- </div>
16
+ </section>
39
17
  );
40
18
  }
@@ -3,94 +3,61 @@ 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-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
- Featured • {post.category}
10
- </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>
6
+ <a
7
+ href={`/blog/${post.slug}`}
8
+ class="group grid overflow-hidden rounded-lg bg-white shadow-[0_13px_27px_-5px_rgba(50,50,93,0.25),0_8px_16px_-8px_rgba(0,0,0,0.3)] border border-[#e6ebf1] md:grid-cols-2 mb-10 transition hover:-translate-y-0.5"
9
+ >
10
+ <div class="p-3">
11
+ <div
12
+ class="h-full min-h-[220px] overflow-hidden rounded-lg p-6 flex flex-col justify-end text-white"
13
+ style="background: linear-gradient(145deg, #635bff, #00d4ff)"
14
+ >
15
+ <span class="bd w-fit bg-white/20 text-white text-[12px] mb-2">{post.category}</span>
16
+ <span class="font-mono text-xs opacity-80">{post.readTime} · {post.date}</span>
17
+ </div>
15
18
  </div>
16
19
 
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
- <a href={`/blog/${post.slug}`}>
20
+ <div class="flex flex-col justify-center p-6 md:p-10">
21
+ <span class="bd w-fit bg-[#635bff]/10 text-[#635bff]">Featured · {post.category}</span>
22
+ <h2 class="mt-3 text-[28px] font-light leading-9 tracking-tight text-[#0a2540] group-hover:text-[#635bff] transition-colors">
19
23
  {post.title}
20
- </a>
21
- </h2>
22
-
23
- <p class="text-zinc-400 text-sm sm:text-base leading-relaxed mb-6 max-w-3xl">
24
- {post.summary}
25
- </p>
26
-
27
- <div class="pt-6 border-t border-white/[0.06] flex items-center justify-between">
28
- <div class="flex items-center gap-3">
29
- <img
30
- src={post.author.avatar}
31
- alt={post.author.name}
32
- class="w-8 h-8 rounded-full object-cover border border-white/[0.1]"
33
- />
34
- <div class="text-xs">
35
- <span class="font-medium text-zinc-200 block">{post.author.name}</span>
36
- <span class="text-zinc-500">{post.author.role}</span>
37
- </div>
24
+ </h2>
25
+ <p class="mt-2 text-[16px] text-[#425466] leading-relaxed">
26
+ {post.summary}
27
+ </p>
28
+ <div class="mt-5 flex items-center gap-3 text-[13px] text-[#425466]">
29
+ <span class="grid h-8 w-8 shrink-0 place-items-center rounded-full font-medium text-white bg-[#635bff] text-[12px]">
30
+ {post.author.name.split(' ').map((n) => n[0]).join('')}
31
+ </span>
32
+ <span>{post.author.name} · {post.date} · {post.readTime}</span>
38
33
  </div>
39
-
40
- <a
41
- href={`/blog/${post.slug}`}
42
- class="text-zinc-200 text-xs font-medium group-hover:text-white transition-colors flex items-center gap-1.5"
43
- >
44
- <span>Read Article</span>
45
- <span class="text-zinc-500 group-hover:translate-x-0.5 transition-transform">→</span>
46
- </a>
47
34
  </div>
48
- </article>
35
+ </a>
49
36
  );
50
37
  }
51
38
 
52
39
  return (
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">
54
- <div>
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]">
57
- {post.category}
58
- </span>
59
- <span class="text-zinc-500 text-[11px]">{post.readTime}</span>
60
- </div>
61
-
62
- <h3 class="text-base font-semibold text-zinc-100 mb-2.5 group-hover:text-white transition-colors line-clamp-2 leading-snug">
63
- <a href={`/blog/${post.slug}`}>
64
- {post.title}
65
- </a>
66
- </h3>
67
-
68
- <p class="text-zinc-400 text-xs leading-relaxed mb-6 line-clamp-3">
69
- {post.summary}
70
- </p>
40
+ <a href={`/blog/${post.slug}`} class="group block">
41
+ <div
42
+ class="aspect-[16/10] overflow-hidden rounded-lg p-5 flex flex-col justify-end text-white shadow-[0_2px_5px_-1px_rgba(50,50,93,0.25),0_1px_3px_-1px_rgba(0,0,0,0.3)]"
43
+ style="background: linear-gradient(145deg, #0a2540, #635bff)"
44
+ >
45
+ <span class="font-mono text-xs opacity-75">{post.readTime}</span>
71
46
  </div>
72
47
 
73
- <div class="pt-4 border-t border-white/[0.06] flex items-center justify-between">
74
- <div class="flex items-center gap-2.5">
75
- <img
76
- src={post.author.avatar}
77
- alt={post.author.name}
78
- class="w-6 h-6 rounded-full object-cover border border-white/[0.1]"
79
- />
80
- <div class="text-[11px]">
81
- <span class="font-medium text-zinc-300 block">{post.author.name}</span>
82
- <span class="text-zinc-500">{post.date}</span>
83
- </div>
48
+ <div class="mt-4">
49
+ <span class="bd bg-[#f6f9fc] text-[#425466] border border-[#e6ebf1] text-xs">{post.category}</span>
50
+ <h3 class="mt-2 text-[19px] font-medium leading-7 text-[#0a2540] group-hover:text-[#635bff] transition-colors">
51
+ {post.title}
52
+ </h3>
53
+ <p class="mt-1 text-[14px] text-[#425466] line-clamp-2">{post.summary}</p>
54
+ <div class="mt-3 flex items-center gap-2 text-[13px] text-[#425466]">
55
+ <span class="grid h-7 w-7 shrink-0 place-items-center rounded-full font-medium text-white bg-[#0a2540] text-[11px]">
56
+ {post.author.name.split(' ').map((n) => n[0]).join('')}
57
+ </span>
58
+ <span>{post.author.name} · {post.date}</span>
84
59
  </div>
85
-
86
- <a
87
- href={`/blog/${post.slug}`}
88
- class="text-xs font-medium text-zinc-400 group-hover:text-white transition-colors flex items-center gap-1"
89
- >
90
- <span>Read</span>
91
- <span class="text-zinc-600">→</span>
92
- </a>
93
60
  </div>
94
- </article>
61
+ </a>
95
62
  );
96
63
  }
@@ -4,43 +4,131 @@
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: #6366f1;
8
- --color-brand-hover: #4f46e5;
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;
9
16
  }
10
17
 
11
18
  :root {
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;
21
- }
22
-
23
- html {
24
- background-color: var(--bg-canvas);
25
- color: var(--text-primary);
26
- font-family: var(--font-sans);
27
- scroll-behavior: smooth;
19
+ color-scheme: light;
20
+ --brand: #635bff;
21
+ --navy: #0a2540;
22
+ --slate: #425466;
23
+ --surface: #f6f9fc;
24
+ --hairline: #e6ebf1;
25
+ --cyan: #00d4ff;
26
+ --success: #24b47e;
27
+ --error: #cd3d64;
28
+
29
+ --shadow-card: 0 2px 5px -1px rgba(50, 50, 93, 0.25), 0 1px 3px -1px rgba(0, 0, 0, 0.3);
30
+ --shadow-pop: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3);
31
+ }
32
+
33
+ body {
34
+ font-size: 15px;
35
+ line-height: 24px;
36
+ background-color: #ffffff;
37
+ color: #425466;
28
38
  -webkit-font-smoothing: antialiased;
29
39
  -moz-osx-font-smoothing: grayscale;
30
40
  }
31
41
 
42
+ :focus-visible {
43
+ outline: 2px solid #635bff;
44
+ outline-offset: 2px;
45
+ }
46
+
32
47
  /* Strict SVG safety to prevent flex ballooning */
33
48
  svg {
34
49
  flex-shrink: 0;
35
50
  max-width: 100%;
36
51
  }
37
52
 
38
- /* Accessibility: Mandatory Reduced Motion */
39
- @media (prefers-reduced-motion: reduce) {
40
- *, *::before, *::after {
41
- animation-duration: 0.01ms !important;
42
- animation-iteration-count: 1 !important;
43
- transition-duration: 0.01ms !important;
44
- scroll-behavior: auto !important;
53
+ .btn {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ gap: 0.5rem;
58
+ border-radius: 6px;
59
+ padding: 0.5rem 1rem;
60
+ font-size: 14px;
61
+ font-weight: 500;
62
+ transition: all 150ms cubic-bezier(0.215, 0.61, 0.355, 1);
63
+ cursor: pointer;
64
+ }
65
+
66
+ .btn-p {
67
+ background-color: #635bff;
68
+ color: #ffffff;
69
+ box-shadow: var(--shadow-card);
70
+ }
71
+ .btn-p:hover {
72
+ background-color: #5249e6;
73
+ }
74
+
75
+ .btn-s {
76
+ border: 1px solid #e6ebf1;
77
+ background-color: #ffffff;
78
+ color: #0a2540;
79
+ box-shadow: var(--shadow-card);
80
+ }
81
+ .btn-s:hover {
82
+ background-color: #f6f9fc;
83
+ }
84
+
85
+ .inp {
86
+ width: 100%;
87
+ border-radius: 6px;
88
+ border: 1px solid #e6ebf1;
89
+ background-color: #ffffff;
90
+ padding: 0.5rem 0.75rem;
91
+ font-size: 15px;
92
+ color: #0a2540;
93
+ outline: none;
94
+ transition: all 150ms cubic-bezier(0.215, 0.61, 0.355, 1);
95
+ }
96
+ .inp:focus {
97
+ border-color: #635bff;
98
+ box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.25);
99
+ }
100
+
101
+ .bd {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ border-radius: 9999px;
105
+ padding: 0.125rem 0.625rem;
106
+ font-size: 13px;
107
+ font-weight: 500;
108
+ }
109
+
110
+ .prose-p {
111
+ margin-top: 1.25rem;
112
+ font-size: 17px;
113
+ line-height: 29px;
114
+ color: #425466;
115
+ }
116
+
117
+ .shadow-card {
118
+ box-shadow: var(--shadow-card);
119
+ }
120
+
121
+ .shadow-pop {
122
+ box-shadow: var(--shadow-pop);
123
+ }
124
+
125
+ @keyframes rise {
126
+ from {
127
+ opacity: 0;
128
+ transform: translateY(8px);
45
129
  }
46
130
  }
131
+
132
+ .rise {
133
+ animation: rise 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);
134
+ }
@@ -8,7 +8,8 @@
8
8
  "start": "frootkit start"
9
9
  },
10
10
  "dependencies": {
11
- "frootkit": "npm:froot-kit@^1.3.4",
11
+ "frootkit": "npm:froot-kit@^1.3.6",
12
+ "frootui": "^1.0.4",
12
13
  "frootjs": "^1.0.6",
13
14
  "preact": "^11.1.0",
14
15
  "preact-render-to-string": "^6.8.2"
@@ -3,16 +3,24 @@ import { CartDrawer } from '../../components/CartDrawer.js';
3
3
  import { Footer } from '../../components/Footer.js';
4
4
 
5
5
  export const metadata = {
6
- title: 'Shopping Cart — FrootStore',
6
+ title: 'Shopping Cart & Checkout — Parcel',
7
7
  };
8
8
 
9
9
  export default function CartPage() {
10
10
  return (
11
- <div class="min-h-screen bg-[#090a0f] text-zinc-100 flex flex-col justify-between selection:bg-white/20 selection:text-white">
11
+ <div class="min-h-screen bg-[#f6f9fc] text-[#425466] flex flex-col justify-between">
12
12
  <Navbar />
13
13
 
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>
14
+ <main class="max-w-6xl mx-auto px-6 py-12 flex-1 w-full">
15
+ <div class="mb-8 flex items-center justify-between">
16
+ <div>
17
+ <h1 class="text-3xl font-light text-[#0a2540] tracking-tight">Your Shopping Cart</h1>
18
+ <p class="text-sm text-[#425466] mt-1">Review your items and complete checkout with encrypted billing.</p>
19
+ </div>
20
+ <a href="/#shop" class="text-xs text-[#635bff] hover:underline font-medium">
21
+ ← Continue shopping
22
+ </a>
23
+ </div>
16
24
  <CartDrawer />
17
25
  </main>
18
26