-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlayout.tsx
More file actions
147 lines (137 loc) · 6.69 KB
/
Copy pathlayout.tsx
File metadata and controls
147 lines (137 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { BlogSource, getPostImage } from '@/blog/cms';
import { ArrowUpRight, CalendarIcon, ClockIcon } from 'lucide-react';
import BlogThumbnail from '../components/blog-thumbnail';
import Link from 'next/link';
import { AuthorAvatars } from './components/author-avatars';
import { CategoryFilter } from '../components/category-filter';
import { BlogRssCTA } from '../components/blog-rss-cta';
import type { Metadata } from 'next';
import { blogAlternates } from '@/lib/metadata';
import AuthorNames from './components/author-names';
export const metadata: Metadata = {
title: { default: 'ArcBox Blog', template: '%s | ArcBox Blog' },
description: 'Engineering deep dives, product releases, and security research from the team building ArcBox.',
alternates: blogAlternates()
};
export default function BlogListLayout({ children }: React.PropsWithChildren) {
const categories = BlogSource.getCategories();
const posts = BlogSource.getPosts();
const featured = posts[0];
// Posts without a bespoke cover fall back to their generated OG card.
const featuredCover = featured.data.cover ?? getPostImage(featured).url;
return (
<div className="px-4 pt-28 md:pt-32 lg:pt-36">
{/* Hero header */}
<section className="relative overflow-hidden pb-12 md:pb-16">
{/* Grid texture */}
<div
className="pointer-events-none absolute inset-0"
style={{
backgroundImage: `
linear-gradient(oklch(0.7 0.16 45 / 0.06) 1px, transparent 1px),
linear-gradient(90deg, oklch(0.7 0.16 45 / 0.06) 1px, transparent 1px)
`,
backgroundSize: '40px 40px'
}}
/>
{/* Radial fade out */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_100%,transparent_60%,var(--background)_100%)]" />
<div className="relative mx-auto max-w-6xl text-center">
<div className="mb-4 inline-flex items-center gap-2 rounded-full border border-accent/40 bg-accent/10 px-3 py-1 text-sm text-accent">
Announcements, updates & deep dives
</div>
<h1 className="text-4xl font-bold tracking-tight text-foreground md:text-5xl lg:text-6xl text-balance">
The ArcBox Blog<span className="text-accent">.</span>
</h1>
<p className="mt-4 text-base text-muted-foreground max-w-lg mx-auto text-pretty">
Engineering deep dives, product releases, and security research from the team building ArcBox.
</p>
</div>
</section>
{/* Featured post */}
<section className="mx-auto max-w-6xl pb-12 md:pb-16">
<Link href={`/blog/${featured.slugs[0]}`} className="group block">
<div className="grid lg:grid-cols-2 gap-0 overflow-hidden rounded-2xl border border-border bg-card transition-colors hover:border-accent/40">
{/* Thumbnail */}
<div className="relative w-full aspect-9/5 overflow-hidden">
<BlogThumbnail
src={featuredCover}
alt={featured.data.title}
fill
placeholder={featured.data.cover ? 'blur' : 'empty'}
preload
loading="eager"
className="h-full w-full object-cover"
/>
<div className="absolute inset-0 bg-linear-to-r from-transparent to-card/30" />
</div>
{/* Content */}
<div className="flex flex-col justify-center p-8 md:p-10">
<span className="mb-3 inline-block text-xs font-semibold uppercase tracking-widest text-accent">
[{featured.data.category}]
</span>
<h2 className="text-2xl font-bold tracking-tight text-foreground text-balance md:text-3xl group-hover:text-accent transition-colors">
{featured.data.title}
</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground text-pretty">
{featured.data.description}
</p>
<div className="mt-6 flex items-center justify-between">
<div className="flex items-center gap-2.5">
<AuthorAvatars authors={featured.data.author} />
<div>
<p className="text-sm font-medium text-foreground">
<AuthorNames authorIds={featured.data.author} />
</p>
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<CalendarIcon className="h-3 w-3" />
{featured.data.date}
</span>
{typeof featured.data._exports.readingTime === 'number' && featured.data._exports.readingTime > 0 && (
<>
<span className="h-3 w-px bg-border" />
<span className="flex items-center gap-1">
<ClockIcon className="h-3 w-3" />
{featured.data._exports.readingTime} min read
</span>
</>
)}
</div>
</div>
</div>
<div className="flex h-8 w-8 items-center justify-center rounded-full border border-border bg-secondary transition-colors group-hover:border-accent group-hover:bg-accent/10">
<ArrowUpRight className="h-3.5 w-3.5 text-muted-foreground group-hover:text-accent" />
</div>
</div>
</div>
</div>
</Link>
</section>
{/**
* All posts + filter
*
* There is no point of showing "All posts" section if we only have 1 post (the featured one). In that case, we can just show the featured post and skip the rest of the section.
*/}
{posts.length > 1 && (
<section className="mx-auto max-w-6xl pb-12 md:pb-16">
<div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<h2 className="text-2xl md:text-3xl font-bold tracking-tight text-foreground">
All Posts<span className="text-accent">.</span>
</h2>
{/* there is no point of category filter if we don't have enough posts */}
{posts.length > 5 && <CategoryFilter categories={categories} />}
</div>
{/* Grid */}
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{children}
</div>
</section>
)}
{/* RSS CTA */}
<section className="mx-auto max-w-6xl pb-16">
<BlogRssCTA />
</section>
</div>
);
}