create-frootkit 1.2.1 → 1.2.3
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/dist/index.js +1 -1
- package/package.json +2 -2
- package/templates/auth/src/app/dashboard/layout.tsx +9 -9
- package/templates/auth/src/app/dashboard/page.tsx +16 -16
- package/templates/auth/src/app/login/page.tsx +2 -2
- package/templates/auth/src/app/page.tsx +77 -37
- package/templates/auth/src/app/signup/page.tsx +2 -2
- package/templates/auth/src/components/AuthForm.tsx +25 -17
- package/templates/auth/src/components/DashboardNav.tsx +53 -29
- package/templates/auth/src/components/MetricCard.tsx +4 -4
- package/templates/auth/src/components/Navbar.tsx +10 -8
- package/templates/auth/src/styles.css +19 -11
- package/templates/blog/src/app/blog/[slug]/page.tsx +9 -9
- package/templates/blog/src/app/page.tsx +26 -12
- package/templates/blog/src/components/Footer.tsx +5 -5
- package/templates/blog/src/components/Navbar.tsx +8 -6
- package/templates/blog/src/components/Newsletter.tsx +16 -10
- package/templates/blog/src/components/PostCard.tsx +60 -13
- package/templates/blog/src/styles.css +19 -11
- package/templates/ecommerce/src/app/cart/page.tsx +2 -2
- package/templates/ecommerce/src/app/page.tsx +20 -29
- package/templates/ecommerce/src/app/products/[id]/page.tsx +19 -18
- package/templates/ecommerce/src/components/CartDrawer.tsx +12 -12
- package/templates/ecommerce/src/components/Footer.tsx +5 -5
- package/templates/ecommerce/src/components/Navbar.tsx +13 -8
- package/templates/ecommerce/src/components/ProductCard.tsx +14 -13
- package/templates/ecommerce/src/styles.css +19 -11
- package/templates/empty/src/app/page.tsx +9 -9
- package/templates/empty/src/styles.css +19 -11
- package/templates/starter/src/app/page.tsx +75 -74
- package/templates/starter/src/components/FeatureCard.tsx +10 -6
- package/templates/starter/src/components/Footer.tsx +5 -5
- package/templates/starter/src/components/Header.tsx +5 -5
- package/templates/starter/src/components/TerminalPreview.tsx +28 -28
- package/templates/starter/src/styles.css +19 -11
- package/dist/templates/auth.d.ts +0 -3
- package/dist/templates/auth.d.ts.map +0 -1
- package/dist/templates/auth.js +0 -487
- package/dist/templates/auth.js.map +0 -1
- package/dist/templates/blog.d.ts +0 -3
- package/dist/templates/blog.d.ts.map +0 -1
- package/dist/templates/blog.js +0 -369
- package/dist/templates/blog.js.map +0 -1
- package/dist/templates/common.d.ts +0 -5
- package/dist/templates/common.d.ts.map +0 -1
- package/dist/templates/common.js +0 -116
- package/dist/templates/common.js.map +0 -1
- package/dist/templates/ecommerce.d.ts +0 -3
- package/dist/templates/ecommerce.d.ts.map +0 -1
- package/dist/templates/ecommerce.js +0 -474
- package/dist/templates/ecommerce.js.map +0 -1
- package/dist/templates/empty.d.ts +0 -3
- package/dist/templates/empty.d.ts.map +0 -1
- package/dist/templates/empty.js +0 -85
- package/dist/templates/empty.js.map +0 -1
- package/dist/templates/starter.d.ts +0 -3
- package/dist/templates/starter.d.ts.map +0 -1
- package/dist/templates/starter.js +0 -308
- package/dist/templates/starter.js.map +0 -1
|
@@ -11,20 +11,20 @@ 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 flex flex-col justify-between">
|
|
14
|
+
<div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
|
|
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-
|
|
20
|
+
class="inline-flex items-center gap-2 text-xs font-mono text-slate-400 hover:text-emerald-400 mb-8 transition-colors"
|
|
21
21
|
>
|
|
22
|
-
← Back to
|
|
22
|
+
← Back to articles
|
|
23
23
|
</a>
|
|
24
24
|
|
|
25
25
|
<header class="mb-10">
|
|
26
26
|
<div class="flex items-center gap-3 mb-4 text-xs font-mono">
|
|
27
|
-
<span class="px-2.5 py-0.5 rounded
|
|
27
|
+
<span class="px-2.5 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 font-semibold">
|
|
28
28
|
{post.category}
|
|
29
29
|
</span>
|
|
30
30
|
<span class="text-slate-500">•</span>
|
|
@@ -37,11 +37,11 @@ export default function BlogPostPage({ params }: { params?: { slug?: string } })
|
|
|
37
37
|
{post.title}
|
|
38
38
|
</h1>
|
|
39
39
|
|
|
40
|
-
<div class="flex items-center gap-4 p-4 rounded-
|
|
40
|
+
<div class="flex items-center gap-4 p-4 rounded-xl bg-[#0a0f18] border border-[#1a2333]">
|
|
41
41
|
<img
|
|
42
42
|
src={post.author.avatar}
|
|
43
43
|
alt={post.author.name}
|
|
44
|
-
class="w-
|
|
44
|
+
class="w-10 h-10 rounded-full object-cover border border-[#1a2333]"
|
|
45
45
|
/>
|
|
46
46
|
<div>
|
|
47
47
|
<div class="font-bold text-white text-sm">{post.author.name}</div>
|
|
@@ -50,17 +50,17 @@ export default function BlogPostPage({ params }: { params?: { slug?: string } })
|
|
|
50
50
|
</div>
|
|
51
51
|
</header>
|
|
52
52
|
|
|
53
|
-
<article class="prose
|
|
53
|
+
<article class="prose max-w-none space-y-6 text-slate-300 leading-relaxed text-base sm:text-lg">
|
|
54
54
|
{post.content.map((paragraph, idx) => (
|
|
55
55
|
<p key={idx}>{paragraph}</p>
|
|
56
56
|
))}
|
|
57
57
|
</article>
|
|
58
58
|
|
|
59
|
-
<div class="mt-12 pt-8 border-t border-
|
|
59
|
+
<div class="mt-12 pt-8 border-t border-[#1a2333] flex flex-wrap gap-2">
|
|
60
60
|
{post.tags.map((tag) => (
|
|
61
61
|
<span
|
|
62
62
|
key={tag}
|
|
63
|
-
class="px-
|
|
63
|
+
class="px-2.5 py-1 rounded bg-[#0a0f18] text-xs font-mono text-slate-400 border border-[#1a2333]"
|
|
64
64
|
>
|
|
65
65
|
#{tag}
|
|
66
66
|
</span>
|
|
@@ -5,32 +5,46 @@ import { Newsletter } from '../components/Newsletter.js';
|
|
|
5
5
|
import { Footer } from '../components/Footer.js';
|
|
6
6
|
|
|
7
7
|
export const metadata = {
|
|
8
|
-
title: 'FrootBlog —
|
|
8
|
+
title: 'FrootBlog — Engineering Articles & Systems Architecture',
|
|
9
9
|
description: 'Deep dives into Preact SSR, fullstack TypeScript, and native styling.',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export default function BlogHomePage() {
|
|
13
|
+
const [featuredPost, ...recentPosts] = POSTS;
|
|
14
|
+
|
|
13
15
|
return (
|
|
14
|
-
<div class="min-h-screen flex flex-col justify-between">
|
|
16
|
+
<div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
|
|
15
17
|
<Navbar />
|
|
16
18
|
|
|
17
19
|
<main class="max-w-5xl mx-auto px-6 py-12 flex-1 w-full">
|
|
18
|
-
{/* Header
|
|
19
|
-
<div class="mb-
|
|
20
|
-
<div class="
|
|
20
|
+
{/* Header Block */}
|
|
21
|
+
<div class="mb-10">
|
|
22
|
+
<div class="text-xs font-mono font-medium text-emerald-400 mb-2 uppercase tracking-wide">
|
|
21
23
|
Engineering Journal
|
|
22
24
|
</div>
|
|
23
|
-
<h1 class="text-
|
|
24
|
-
|
|
25
|
+
<h1 class="text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-3">
|
|
26
|
+
Systems Architecture & Web Engineering
|
|
25
27
|
</h1>
|
|
26
|
-
<p class="text-slate-400 text-base
|
|
27
|
-
Exploring
|
|
28
|
+
<p class="text-slate-400 text-base max-w-2xl leading-relaxed">
|
|
29
|
+
Exploring sub-millisecond server rendering, monomorphic dispatch trees, and zero-hydration web architectures.
|
|
28
30
|
</p>
|
|
29
31
|
</div>
|
|
30
32
|
|
|
31
|
-
{/*
|
|
32
|
-
<
|
|
33
|
-
|
|
33
|
+
{/* Featured Dominant Article */}
|
|
34
|
+
{featuredPost && <PostCard post={featuredPost} featured={true} />}
|
|
35
|
+
|
|
36
|
+
{/* 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
|
|
40
|
+
</h2>
|
|
41
|
+
<span class="text-xs font-mono text-slate-500">
|
|
42
|
+
{recentPosts.length + 1} publications
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
47
|
+
{recentPosts.map((post) => (
|
|
34
48
|
<PostCard key={post.slug} post={post} />
|
|
35
49
|
))}
|
|
36
50
|
</div>
|
|
@@ -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-[#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">
|
|
5
5
|
<div>
|
|
6
6
|
FrootBlog © {new Date().getFullYear()} OrbitNorth LLC. MIT Licensed.
|
|
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-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>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
14
14
|
</footer>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export function Navbar() {
|
|
2
2
|
return (
|
|
3
|
-
<header class="border-b border-
|
|
3
|
+
<header class="border-b border-[#1a2333] bg-[#06090f]/95 sticky top-0 z-50">
|
|
4
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-
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
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 class="w-4 h-4" 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" />
|
|
9
|
+
</svg>
|
|
10
|
+
</div>
|
|
9
11
|
<span>Froot<span class="text-emerald-400">Blog</span></span>
|
|
10
12
|
</a>
|
|
11
13
|
|
|
@@ -13,7 +15,7 @@ export function Navbar() {
|
|
|
13
15
|
<a href="/" class="text-slate-300 hover:text-white transition-colors">
|
|
14
16
|
Articles
|
|
15
17
|
</a>
|
|
16
|
-
<a href="/api/posts" class="text-
|
|
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">
|
|
17
19
|
/api/posts
|
|
18
20
|
</a>
|
|
19
21
|
<a
|
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
export function Newsletter() {
|
|
2
2
|
return (
|
|
3
|
-
<div class="rounded-
|
|
4
|
-
<div class="max-w-xl
|
|
5
|
-
<
|
|
6
|
-
|
|
3
|
+
<div class="rounded-xl border border-[#1a2333] bg-[#0a0f18] p-8 sm:p-10 my-16">
|
|
4
|
+
<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
|
|
7
|
+
</div>
|
|
8
|
+
<h3 class="text-2xl font-bold text-white mb-2">
|
|
9
|
+
Subscribe for Technical Deep Dives
|
|
7
10
|
</h3>
|
|
8
|
-
<p class="text-slate-400 text-sm mb-
|
|
9
|
-
|
|
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.
|
|
10
13
|
</p>
|
|
14
|
+
|
|
11
15
|
<form
|
|
12
16
|
action="/api/posts"
|
|
13
17
|
method="GET"
|
|
14
|
-
class="flex flex-col sm:flex-row gap-3 max-w-md
|
|
18
|
+
class="flex flex-col sm:flex-row gap-3 max-w-md"
|
|
15
19
|
>
|
|
20
|
+
<label class="sr-only" for="newsletter-email">Work Email</label>
|
|
16
21
|
<input
|
|
22
|
+
id="newsletter-email"
|
|
17
23
|
type="email"
|
|
18
|
-
placeholder="
|
|
24
|
+
placeholder="developer@company.com"
|
|
19
25
|
required
|
|
20
|
-
class="flex-1 px-4 py-
|
|
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"
|
|
21
27
|
/>
|
|
22
28
|
<button
|
|
23
29
|
type="submit"
|
|
24
|
-
class="px-
|
|
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"
|
|
25
31
|
>
|
|
26
32
|
Subscribe
|
|
27
33
|
</button>
|
|
@@ -1,35 +1,82 @@
|
|
|
1
1
|
import type { Post } from '../data/posts.js';
|
|
2
2
|
|
|
3
|
-
export function PostCard({ post }: { post: Post }) {
|
|
3
|
+
export function PostCard({ post, featured = false }: { post: Post; featured?: boolean }) {
|
|
4
|
+
if (featured) {
|
|
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">
|
|
9
|
+
Featured • {post.category}
|
|
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>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<h2 class="text-2xl sm:text-3xl font-extrabold text-white mb-4 leading-snug hover:text-emerald-400 transition-colors">
|
|
18
|
+
<a href={`/blog/${post.slug}`}>
|
|
19
|
+
{post.title}
|
|
20
|
+
</a>
|
|
21
|
+
</h2>
|
|
22
|
+
|
|
23
|
+
<p class="text-slate-300 text-base leading-relaxed mb-6 max-w-3xl">
|
|
24
|
+
{post.summary}
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<div class="pt-6 border-t border-[#1a2333] 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-[#1a2333]"
|
|
33
|
+
/>
|
|
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>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<a
|
|
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"
|
|
43
|
+
>
|
|
44
|
+
Read Article →
|
|
45
|
+
</a>
|
|
46
|
+
</div>
|
|
47
|
+
</article>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
4
51
|
return (
|
|
5
|
-
<article class="rounded-
|
|
52
|
+
<article class="rounded-xl border border-[#1a2333] bg-[#0a0f18] p-6 hover:border-slate-700 transition-colors flex flex-col justify-between">
|
|
6
53
|
<div>
|
|
7
|
-
<div class="flex items-center justify-between mb-3 text-xs">
|
|
8
|
-
<span class="
|
|
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">
|
|
9
56
|
{post.category}
|
|
10
57
|
</span>
|
|
11
|
-
<span class="text-slate-
|
|
58
|
+
<span class="text-slate-500">{post.readTime}</span>
|
|
12
59
|
</div>
|
|
13
60
|
|
|
14
|
-
<
|
|
61
|
+
<h3 class="text-lg font-bold text-white mb-3 hover:text-emerald-400 transition-colors line-clamp-2 leading-snug">
|
|
15
62
|
<a href={`/blog/${post.slug}`}>
|
|
16
63
|
{post.title}
|
|
17
64
|
</a>
|
|
18
|
-
</
|
|
65
|
+
</h3>
|
|
19
66
|
|
|
20
|
-
<p class="text-slate-
|
|
67
|
+
<p class="text-slate-400 text-sm leading-relaxed mb-6 line-clamp-3">
|
|
21
68
|
{post.summary}
|
|
22
69
|
</p>
|
|
23
70
|
</div>
|
|
24
71
|
|
|
25
|
-
<div class="pt-4 border-t border-
|
|
26
|
-
<div class="flex items-center gap-
|
|
72
|
+
<div class="pt-4 border-t border-[#1a2333] flex items-center justify-between">
|
|
73
|
+
<div class="flex items-center gap-2.5">
|
|
27
74
|
<img
|
|
28
75
|
src={post.author.avatar}
|
|
29
76
|
alt={post.author.name}
|
|
30
|
-
class="w-
|
|
77
|
+
class="w-6 h-6 rounded-full object-cover border border-[#1a2333]"
|
|
31
78
|
/>
|
|
32
|
-
<div class="text-
|
|
79
|
+
<div class="text-[11px]">
|
|
33
80
|
<span class="font-medium text-slate-200 block">{post.author.name}</span>
|
|
34
81
|
<span class="text-slate-500">{post.date}</span>
|
|
35
82
|
</div>
|
|
@@ -37,7 +84,7 @@ export function PostCard({ post }: { post: Post }) {
|
|
|
37
84
|
|
|
38
85
|
<a
|
|
39
86
|
href={`/blog/${post.slug}`}
|
|
40
|
-
class="text-emerald-400 text-xs font-semibold hover:
|
|
87
|
+
class="text-emerald-400 text-xs font-semibold hover:text-emerald-300 transition-colors"
|
|
41
88
|
>
|
|
42
89
|
Read →
|
|
43
90
|
</a>
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
3
|
@theme {
|
|
4
|
-
--color-brand-
|
|
5
|
-
--color-brand-
|
|
6
|
-
--color-brand-200: #a7f3d0;
|
|
7
|
-
--color-brand-300: #6ee7b7;
|
|
8
|
-
--color-brand-400: #34d399;
|
|
9
|
-
--color-brand-500: #10b981;
|
|
10
|
-
--color-brand-600: #059669;
|
|
11
|
-
--color-brand-700: #047857;
|
|
12
|
-
--color-brand-800: #065f46;
|
|
13
|
-
--color-brand-900: #064e3b;
|
|
14
|
-
--color-brand-950: #022c22;
|
|
4
|
+
--color-brand-primary: #10b981;
|
|
5
|
+
--color-brand-hover: #34d399;
|
|
15
6
|
}
|
|
16
7
|
|
|
17
8
|
:root {
|
|
18
9
|
color-scheme: dark;
|
|
19
10
|
}
|
|
11
|
+
|
|
12
|
+
html {
|
|
13
|
+
background-color: #06090f;
|
|
14
|
+
color: #f8fafc;
|
|
15
|
+
scroll-behavior: smooth;
|
|
16
|
+
-webkit-font-smoothing: antialiased;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Accessibility: Mandatory Reduced Motion */
|
|
20
|
+
@media (prefers-reduced-motion: reduce) {
|
|
21
|
+
*, *::before, *::after {
|
|
22
|
+
animation-duration: 0.01ms !important;
|
|
23
|
+
animation-iteration-count: 1 !important;
|
|
24
|
+
transition-duration: 0.01ms !important;
|
|
25
|
+
scroll-behavior: auto !important;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -8,11 +8,11 @@ export const metadata = {
|
|
|
8
8
|
|
|
9
9
|
export default function CartPage() {
|
|
10
10
|
return (
|
|
11
|
-
<div class="min-h-screen flex flex-col justify-between">
|
|
11
|
+
<div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
|
|
12
12
|
<Navbar />
|
|
13
13
|
|
|
14
14
|
<main class="max-w-4xl mx-auto px-6 py-12 flex-1 w-full">
|
|
15
|
-
<h1 class="text-3xl font-
|
|
15
|
+
<h1 class="text-3xl font-extrabold text-white mb-8">Order Cart</h1>
|
|
16
16
|
<CartDrawer />
|
|
17
17
|
</main>
|
|
18
18
|
|
|
@@ -4,46 +4,37 @@ import { ProductCard } from '../components/ProductCard.js';
|
|
|
4
4
|
import { Footer } from '../components/Footer.js';
|
|
5
5
|
|
|
6
6
|
export const metadata = {
|
|
7
|
-
title: 'FrootStore —
|
|
7
|
+
title: 'FrootStore — Hardware Instruments for Developers',
|
|
8
8
|
description: 'Precision mechanical keyboards, studio audio, and ergonomic setups.',
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export default function StoreHomePage() {
|
|
12
12
|
return (
|
|
13
|
-
<div class="min-h-screen flex flex-col justify-between">
|
|
13
|
+
<div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
|
|
14
14
|
<Navbar />
|
|
15
15
|
|
|
16
16
|
<main class="max-w-6xl mx-auto px-6 py-12 flex-1 w-full">
|
|
17
|
-
{/*
|
|
18
|
-
<div class="
|
|
19
|
-
<div class="
|
|
20
|
-
|
|
21
|
-
Autumn 2026 Collection
|
|
22
|
-
</span>
|
|
23
|
-
<h1 class="text-4xl sm:text-5xl font-black text-white tracking-tight mb-4">
|
|
24
|
-
Engineered for Focus. Crafted for Craft.
|
|
25
|
-
</h1>
|
|
26
|
-
<p class="text-slate-300 text-base sm:text-lg mb-8 leading-relaxed">
|
|
27
|
-
Curated minimal workspace hardware designed to elevate your daily development workflow with tactile precision.
|
|
28
|
-
</p>
|
|
29
|
-
<div class="flex items-center gap-4">
|
|
30
|
-
<a
|
|
31
|
-
href="#catalog"
|
|
32
|
-
class="px-6 py-3 rounded-xl bg-emerald-500 text-slate-950 font-bold hover:bg-emerald-400 transition-colors shadow-lg shadow-emerald-500/20 text-sm"
|
|
33
|
-
>
|
|
34
|
-
Browse All Equipment
|
|
35
|
-
</a>
|
|
36
|
-
<span class="text-xs text-slate-400 font-mono">Free shipping over $100</span>
|
|
37
|
-
</div>
|
|
17
|
+
{/* Collection Header */}
|
|
18
|
+
<div class="mb-12 border-b border-[#1a2333] pb-8">
|
|
19
|
+
<div class="text-xs font-mono font-medium text-emerald-400 mb-2 uppercase tracking-wide">
|
|
20
|
+
Curated Instruments
|
|
38
21
|
</div>
|
|
22
|
+
<h1 class="text-3xl sm:text-5xl font-extrabold text-white tracking-tight mb-3">
|
|
23
|
+
Hardware Engineered for Precision
|
|
24
|
+
</h1>
|
|
25
|
+
<p class="text-slate-400 text-base max-w-2xl leading-relaxed">
|
|
26
|
+
Tactile peripherals and minimal workspace instruments designed to eliminate friction from extended coding sessions.
|
|
27
|
+
</p>
|
|
39
28
|
</div>
|
|
40
29
|
|
|
41
|
-
{/* Catalog Section */}
|
|
42
|
-
<div id="catalog" class="mb-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
30
|
+
{/* Catalog Section Header */}
|
|
31
|
+
<div id="catalog" class="mb-6 flex items-center justify-between">
|
|
32
|
+
<h2 class="text-xs font-mono uppercase tracking-wider text-slate-400 font-semibold">
|
|
33
|
+
All Products
|
|
34
|
+
</h2>
|
|
35
|
+
<span class="text-xs font-mono text-slate-500">
|
|
36
|
+
{PRODUCTS.length} instruments available
|
|
37
|
+
</span>
|
|
47
38
|
</div>
|
|
48
39
|
|
|
49
40
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
@@ -11,19 +11,19 @@ export default function ProductDetailPage({ params }: { params?: { id?: string }
|
|
|
11
11
|
const product = PRODUCTS.find((p) => p.id === id) || PRODUCTS[0]!;
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
|
-
<div class="min-h-screen flex flex-col justify-between">
|
|
14
|
+
<div class="min-h-screen bg-[#06090f] text-slate-100 flex flex-col justify-between">
|
|
15
15
|
<Navbar />
|
|
16
16
|
|
|
17
17
|
<main class="max-w-6xl 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-
|
|
20
|
+
class="inline-flex items-center gap-2 text-xs font-mono text-slate-400 hover:text-emerald-400 mb-8 transition-colors"
|
|
21
21
|
>
|
|
22
22
|
← Back to catalog
|
|
23
23
|
</a>
|
|
24
24
|
|
|
25
25
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
|
|
26
|
-
<div class="rounded-
|
|
26
|
+
<div class="rounded-xl border border-[#1a2333] overflow-hidden bg-[#0a0f18] aspect-[4/3]">
|
|
27
27
|
<img
|
|
28
28
|
src={product.image}
|
|
29
29
|
alt={product.name}
|
|
@@ -32,20 +32,21 @@ export default function ProductDetailPage({ params }: { params?: { id?: string }
|
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
<div>
|
|
35
|
-
<div class="flex items-center gap-
|
|
36
|
-
<span class="px-2.5 py-0.5 rounded
|
|
35
|
+
<div class="flex items-center gap-3 mb-3 text-xs font-mono">
|
|
36
|
+
<span class="px-2.5 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 font-semibold">
|
|
37
37
|
{product.category}
|
|
38
38
|
</span>
|
|
39
|
-
<span class="text-
|
|
40
|
-
|
|
39
|
+
<span class="text-slate-500">•</span>
|
|
40
|
+
<span class="text-slate-300 font-semibold">
|
|
41
|
+
{product.rating.toFixed(1)} / 5.0 ({product.reviewsCount} reviews)
|
|
41
42
|
</span>
|
|
42
43
|
</div>
|
|
43
44
|
|
|
44
|
-
<h1 class="text-3xl sm:text-4xl font-
|
|
45
|
+
<h1 class="text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4">
|
|
45
46
|
{product.name}
|
|
46
47
|
</h1>
|
|
47
48
|
|
|
48
|
-
<div class="text-3xl font-
|
|
49
|
+
<div class="text-3xl font-bold text-white mb-6 font-mono">
|
|
49
50
|
${product.price.toFixed(2)}
|
|
50
51
|
</div>
|
|
51
52
|
|
|
@@ -53,14 +54,14 @@ export default function ProductDetailPage({ params }: { params?: { id?: string }
|
|
|
53
54
|
{product.description}
|
|
54
55
|
</p>
|
|
55
56
|
|
|
56
|
-
<div class="border-t border-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
</
|
|
60
|
-
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm text-slate-300">
|
|
57
|
+
<div class="border-t border-[#1a2333] pt-6 mb-8">
|
|
58
|
+
<h2 class="text-xs font-mono uppercase tracking-wider font-semibold text-slate-400 mb-4">
|
|
59
|
+
Engineering Specifications
|
|
60
|
+
</h2>
|
|
61
|
+
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm text-slate-300 font-mono">
|
|
61
62
|
{product.features.map((f) => (
|
|
62
63
|
<li key={f} class="flex items-center gap-2">
|
|
63
|
-
<span class="text-emerald-400 font-bold"
|
|
64
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
64
65
|
<span>{f}</span>
|
|
65
66
|
</li>
|
|
66
67
|
))}
|
|
@@ -70,12 +71,12 @@ export default function ProductDetailPage({ params }: { params?: { id?: string }
|
|
|
70
71
|
<div class="flex items-center gap-4">
|
|
71
72
|
<a
|
|
72
73
|
href="/cart"
|
|
73
|
-
class="flex-1 py-3
|
|
74
|
+
class="flex-1 py-3 rounded-lg bg-emerald-500 text-slate-950 font-semibold text-center text-sm hover:bg-emerald-400 transition-colors"
|
|
74
75
|
>
|
|
75
76
|
Add to Cart • ${product.price.toFixed(2)}
|
|
76
77
|
</a>
|
|
77
|
-
<span class="text-xs text-slate-
|
|
78
|
-
{product.inStock ? 'In Stock (
|
|
78
|
+
<span class="text-xs text-slate-400 font-mono">
|
|
79
|
+
{product.inStock ? 'In Stock (Ships in 24h)' : 'Backorder'}
|
|
79
80
|
</span>
|
|
80
81
|
</div>
|
|
81
82
|
</div>
|
|
@@ -11,23 +11,23 @@ export function CartDrawer() {
|
|
|
11
11
|
const total = subtotal + shipping;
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
|
-
<div class="rounded-
|
|
15
|
-
<h2 class="text-lg font-bold text-white mb-
|
|
14
|
+
<div class="rounded-xl border border-[#1a2333] bg-[#0a0f18] p-6 sm:p-8">
|
|
15
|
+
<h2 class="text-lg font-bold text-white mb-6 flex items-center justify-between border-b border-[#1a2333] pb-4">
|
|
16
16
|
<span>Order Summary</span>
|
|
17
17
|
<span class="text-xs font-mono text-emerald-400 font-semibold">{cartItems.length} items</span>
|
|
18
18
|
</h2>
|
|
19
19
|
|
|
20
20
|
<div class="space-y-4 mb-6">
|
|
21
21
|
{cartItems.map(({ product, quantity }) => (
|
|
22
|
-
<div key={product.id} class="flex items-center gap-
|
|
22
|
+
<div key={product.id} class="flex items-center gap-4 pb-4 border-b border-[#1a2333]">
|
|
23
23
|
<img
|
|
24
24
|
src={product.image}
|
|
25
25
|
alt={product.name}
|
|
26
|
-
class="w-
|
|
26
|
+
class="w-14 h-14 rounded-lg object-cover bg-[#06090f] border border-[#1a2333]"
|
|
27
27
|
/>
|
|
28
28
|
<div class="flex-1 min-w-0">
|
|
29
|
-
<h4 class="text-
|
|
30
|
-
<div class="text-
|
|
29
|
+
<h4 class="text-sm font-semibold text-white truncate">{product.name}</h4>
|
|
30
|
+
<div class="text-xs text-slate-400 font-mono mt-0.5">Qty: {quantity} × ${product.price.toFixed(2)}</div>
|
|
31
31
|
</div>
|
|
32
32
|
<div class="font-bold text-sm text-white font-mono">
|
|
33
33
|
${(product.price * quantity).toFixed(2)}
|
|
@@ -36,25 +36,25 @@ export function CartDrawer() {
|
|
|
36
36
|
))}
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
|
-
<div class="space-y-2 text-xs text-slate-
|
|
39
|
+
<div class="space-y-2.5 text-xs text-slate-300 mb-6 font-mono">
|
|
40
40
|
<div class="flex justify-between">
|
|
41
|
-
<span>Subtotal</span>
|
|
41
|
+
<span class="text-slate-400">Subtotal</span>
|
|
42
42
|
<span class="text-white font-bold">${subtotal.toFixed(2)}</span>
|
|
43
43
|
</div>
|
|
44
44
|
<div class="flex justify-between">
|
|
45
|
-
<span>
|
|
45
|
+
<span class="text-slate-400">Standard Shipping</span>
|
|
46
46
|
<span class="text-emerald-400 font-semibold">{shipping === 0 ? 'FREE' : `$${shipping.toFixed(2)}`}</span>
|
|
47
47
|
</div>
|
|
48
|
-
<div class="pt-3 border-t border-
|
|
48
|
+
<div class="pt-3 border-t border-[#1a2333] flex justify-between text-sm text-white font-bold">
|
|
49
49
|
<span>Total</span>
|
|
50
|
-
<span class="text-emerald-400 text-base font-black">${total.toFixed(2)}</span>
|
|
50
|
+
<span class="text-emerald-400 text-base font-mono font-black">${total.toFixed(2)}</span>
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
|
|
54
54
|
<form action="/api/checkout" method="POST">
|
|
55
55
|
<button
|
|
56
56
|
type="submit"
|
|
57
|
-
class="w-full py-3 rounded-
|
|
57
|
+
class="w-full py-3 rounded-lg bg-emerald-500 text-slate-950 font-semibold text-sm hover:bg-emerald-400 transition-colors"
|
|
58
58
|
>
|
|
59
59
|
Proceed to Checkout →
|
|
60
60
|
</button>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export function Footer() {
|
|
2
2
|
return (
|
|
3
|
-
<footer class="border-t border-
|
|
4
|
-
<div class="max-w-6xl mx-auto px-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
|
3
|
+
<footer class="border-t border-[#1a2333] bg-[#06090f] py-8 text-xs text-slate-400">
|
|
4
|
+
<div class="max-w-6xl mx-auto px-6 flex flex-col sm:flex-row items-center justify-between gap-4 font-mono">
|
|
5
5
|
<div>
|
|
6
6
|
FrootStore © {new Date().getFullYear()} OrbitNorth LLC. MIT Licensed.
|
|
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/products" class="hover:text-
|
|
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/products" class="hover:text-white transition-colors">Products API</a>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
14
14
|
</footer>
|