create-frootkit 1.2.12 → 1.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/auth/src/app/dashboard/layout.tsx +8 -11
- package/templates/auth/src/app/dashboard/page.tsx +31 -27
- package/templates/auth/src/app/layout.tsx +5 -5
- package/templates/auth/src/app/login/page.tsx +11 -4
- package/templates/auth/src/app/page.tsx +278 -120
- package/templates/auth/src/app/signup/page.tsx +12 -6
- package/templates/auth/src/components/AuthForm.tsx +27 -34
- package/templates/auth/src/components/DashboardNav.tsx +41 -61
- package/templates/auth/src/components/MetricCard.tsx +12 -22
- package/templates/auth/src/components/Navbar.tsx +18 -21
- package/templates/auth/src/styles.css +157 -27
- package/templates/blog/src/app/blog/[slug]/page.tsx +85 -46
- package/templates/blog/src/app/layout.tsx +5 -5
- package/templates/blog/src/app/page.tsx +39 -26
- package/templates/blog/src/components/Footer.tsx +11 -9
- package/templates/blog/src/components/Navbar.tsx +21 -26
- package/templates/blog/src/components/Newsletter.tsx +11 -33
- package/templates/blog/src/components/PostCard.tsx +43 -76
- package/templates/blog/src/styles.css +113 -25
- package/templates/ecommerce/src/app/cart/page.tsx +12 -4
- package/templates/ecommerce/src/app/layout.tsx +5 -5
- package/templates/ecommerce/src/app/page.tsx +92 -37
- package/templates/ecommerce/src/app/products/[id]/page.tsx +39 -31
- package/templates/ecommerce/src/components/CartDrawer.tsx +113 -45
- package/templates/ecommerce/src/components/Footer.tsx +11 -9
- package/templates/ecommerce/src/components/Navbar.tsx +32 -34
- package/templates/ecommerce/src/components/ProductCard.tsx +27 -38
- package/templates/ecommerce/src/styles.css +155 -36
- package/templates/starter/src/app/layout.tsx +5 -5
- package/templates/starter/src/app/page.tsx +114 -107
- package/templates/starter/src/components/FeatureCard.tsx +9 -15
- package/templates/starter/src/components/Footer.tsx +11 -9
- package/templates/starter/src/components/Header.tsx +15 -38
- package/templates/starter/src/styles.css +102 -27
|
@@ -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 || '
|
|
12
|
+
const title = metadata?.title || 'Dispatch — Notes on building payments software';
|
|
13
13
|
const description =
|
|
14
14
|
metadata?.description ||
|
|
15
|
-
'
|
|
15
|
+
'Engineering, product, and finance stories from the team behind FrootKit.';
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<html lang="en" class="
|
|
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
|
|
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-
|
|
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
|
|
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: '
|
|
9
|
-
description: '
|
|
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="
|
|
18
|
+
<div class="bg-white font-sans text-[#425466]">
|
|
18
19
|
<Navbar />
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<div class="
|
|
23
|
-
<
|
|
24
|
-
|
|
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="
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
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
|
-
</
|
|
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-
|
|
4
|
-
<div class="max-w-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
</
|
|
8
|
-
<div class="flex items-center gap-
|
|
9
|
-
<a href="
|
|
10
|
-
<
|
|
11
|
-
<a href="
|
|
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-
|
|
4
|
-
<div class="max-w-
|
|
5
|
-
<a href="/" class="
|
|
6
|
-
|
|
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="
|
|
16
|
-
<a href="
|
|
17
|
-
|
|
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
|
-
<
|
|
4
|
-
<div class="
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
</
|
|
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
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
<
|
|
18
|
-
<
|
|
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
|
-
</
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
</
|
|
35
|
+
</a>
|
|
49
36
|
);
|
|
50
37
|
}
|
|
51
38
|
|
|
52
39
|
return (
|
|
53
|
-
<
|
|
54
|
-
<div
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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="
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
</
|
|
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
|
-
</
|
|
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
|
|
8
|
-
--color-brand-hover: #
|
|
7
|
+
--color-brand: #635bff;
|
|
8
|
+
--color-brand-hover: #5249e6;
|
|
9
|
+
--color-navy: #0a2540;
|
|
10
|
+
--color-slate: #425466;
|
|
11
|
+
--color-surface: #f6f9fc;
|
|
12
|
+
--color-hairline: #e6ebf1;
|
|
13
|
+
--color-cyan: #00d4ff;
|
|
14
|
+
--color-success: #24b47e;
|
|
15
|
+
--color-error: #cd3d64;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
:root {
|
|
12
|
-
color-scheme:
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
}
|
|
@@ -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 —
|
|
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-[#
|
|
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-
|
|
15
|
-
<
|
|
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
|
|
|
@@ -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 || '
|
|
12
|
+
const title = metadata?.title || 'Parcel — Everyday objects, well made';
|
|
13
13
|
const description =
|
|
14
14
|
metadata?.description ||
|
|
15
|
-
'
|
|
15
|
+
'Small-batch goods for your desk, kitchen, and commute. Built with FrootKit.';
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<html lang="en" class="
|
|
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
|
|
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-
|
|
32
|
+
<body class="bg-white font-sans text-[#425466] antialiased">
|
|
33
33
|
{children}
|
|
34
34
|
</body>
|
|
35
35
|
</html>
|