@anglefeint/astro-theme 0.1.0-alpha.0 → 0.1.0-alpha.1

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.
@@ -3,10 +3,8 @@ import { Image } from 'astro:assets';
3
3
  import type { CollectionEntry } from 'astro:content';
4
4
  import themeRedqueen1 from '../assets/theme/red-queen/theme-redqueen1.webp';
5
5
  import themeRedqueen2 from '../assets/theme/red-queen/theme-redqueen2.gif';
6
- import BaseHead from '../components/BaseHead.astro';
7
- import Footer from '../components/Footer.astro';
8
6
  import FormattedDate from '../components/FormattedDate.astro';
9
- import Header from '../components/Header.astro';
7
+ import AiShell from './shells/AiShell.astro';
10
8
  import { SITE_AUTHOR } from '../consts';
11
9
  import { DEFAULT_LOCALE, type Locale, isLocale, localePath, blogIdToSlugAnyLocale } from '../i18n/config';
12
10
  import { getMessages } from '../i18n/messages';
@@ -75,42 +73,40 @@ for (let i = 0; i < pts.length; i++) {
75
73
  }
76
74
  ---
77
75
 
78
- <html lang={resolvedLocale}>
79
- <head>
80
- <BaseHead
81
- title={title}
82
- description={description}
83
- image={heroImage}
84
- pageType="article"
85
- publishedTime={pubDate}
86
- modifiedTime={updatedDate}
87
- author={author ?? SITE_AUTHOR}
88
- tags={tags}
89
- />
90
- <link rel="stylesheet" href="/styles/blog-post.css" />
91
- </head>
92
-
93
- <body class="mesh-page">
94
- <div class="mesh-bg" aria-hidden="true">
95
- <div class="mesh-glow mesh-glow-shift"></div>
96
- <div class="mesh-haze" aria-hidden="true"></div>
97
- <div class="mesh-vignette" aria-hidden="true"></div>
98
- <div class="mesh-stripe"></div>
99
- <div class="mesh-noise" aria-hidden="true"></div>
100
- <div class="mesh-hex-grid" aria-hidden="true"></div>
101
- <div class="mesh-thought-particles" aria-hidden="true">
76
+ <AiShell
77
+ locale={resolvedLocale}
78
+ title={title}
79
+ description={description}
80
+ image={heroImage}
81
+ pageType="article"
82
+ publishedTime={pubDate}
83
+ modifiedTime={updatedDate}
84
+ author={author ?? SITE_AUTHOR}
85
+ tags={tags}
86
+ localeHrefs={localeHrefs}
87
+ >
88
+ <link slot="head" rel="stylesheet" href="/styles/blog-post.css" />
89
+ <Fragment slot="body-start">
90
+ <div class="ai-bg" aria-hidden="true">
91
+ <div class="ai-glow ai-glow-shift"></div>
92
+ <div class="ai-haze" aria-hidden="true"></div>
93
+ <div class="ai-vignette" aria-hidden="true"></div>
94
+ <div class="ai-stripe"></div>
95
+ <div class="ai-noise" aria-hidden="true"></div>
96
+ <div class="ai-hex-grid" aria-hidden="true"></div>
97
+ <div class="ai-thought-particles" aria-hidden="true">
102
98
  {[...Array(12)].map((_, i) => (
103
- <span class="mesh-particle" style={`--x: ${20 + (i * 7) % 60}%; --y: ${10 + (i * 11) % 70}%; --d: ${3 + (i % 4)}s`}></span>
99
+ <span class="ai-particle" style={`--x: ${20 + (i * 7) % 60}%; --y: ${10 + (i * 11) % 70}%; --d: ${3 + (i % 4)}s`}></span>
104
100
  ))}
105
101
  </div>
106
- <svg class="mesh-network" viewBox="0 0 1200 800" preserveAspectRatio="xMidYMid slice">
102
+ <svg class="ai-network" viewBox="0 0 1200 800" preserveAspectRatio="xMidYMid slice">
107
103
  <defs>
108
- <linearGradient id="mesh-line-grad" x1="0%" y1="0%" x2="100%" y2="0%">
104
+ <linearGradient id="ai-line-grad" x1="0%" y1="0%" x2="100%" y2="0%">
109
105
  <stop offset="0%" stop-color="rgba(180,235,255,0.26)" />
110
106
  <stop offset="50%" stop-color="rgba(236,252,255,0.74)" />
111
107
  <stop offset="100%" stop-color="rgba(180,235,255,0.26)" />
112
108
  </linearGradient>
113
- <filter id="mesh-dot-glow">
109
+ <filter id="ai-dot-glow">
114
110
  <feGaussianBlur stdDeviation="2.2" result="blur" />
115
111
  <feMerge>
116
112
  <feMergeNode in="blur" />
@@ -118,57 +114,45 @@ for (let i = 0; i < pts.length; i++) {
118
114
  </feMerge>
119
115
  </filter>
120
116
  </defs>
121
- <g class="mesh-lines">
117
+ <g class="ai-lines">
122
118
  {edges.map(([i, j]) => (
123
119
  <line
124
120
  x1={pts[i].x}
125
121
  y1={pts[i].y}
126
122
  x2={pts[j].x}
127
123
  y2={pts[j].y}
128
- stroke="url(#mesh-line-grad)"
124
+ stroke="url(#ai-line-grad)"
129
125
  stroke-width="0.4"
130
126
  />
131
127
  ))}
132
128
  </g>
133
- <g class="mesh-dots">
129
+ <g class="ai-dots">
134
130
  {pts.map((p) => (
135
131
  <circle
136
132
  cx={p.x}
137
133
  cy={p.y}
138
134
  r={p.r}
139
135
  fill={`rgba(232,255,255,${0.72 + p.depth * 0.48})`}
140
- filter="url(#mesh-dot-glow)"
136
+ filter="url(#ai-dot-glow)"
141
137
  />
142
138
  ))}
143
139
  </g>
144
140
  </svg>
145
141
  </div>
146
- <Header
147
- locale={resolvedLocale}
148
- localeHrefs={localeHrefs}
149
- scanlines
150
- labels={{
151
- home: messages.nav.home,
152
- blog: messages.nav.blog,
153
- about: messages.nav.about,
154
- status: messages.nav.status,
155
- language: messages.langLabel,
156
- }}
157
- />
158
142
  <aside class="rq-tv rq-tv-collapsed">
159
143
  <div class="rq-tv-stage" data-rq-src={themeRedqueen1.src} data-rq-src2={themeRedqueen2.src}></div>
160
144
  <div class="rq-tv-badge">monitor feed<span class="rq-tv-dot"></span></div>
161
145
  <button type="button" class="rq-tv-toggle" aria-label="Replay monitor feed" aria-expanded="false">▶</button>
162
146
  </aside>
163
- <div class="mesh-read-progress" aria-hidden="true"></div>
164
- <button type="button" class="mesh-back-to-top" aria-label="Back to top" title="Back to top">↑</button>
165
- <div class="mesh-stage-toast" aria-live="polite" aria-atomic="true"></div>
166
- <div class="mesh-mouse-glow" aria-hidden="true"></div>
167
- <div class="mesh-depth-blur" aria-hidden="true"></div>
168
- <div class="mesh-thinking-dots" aria-hidden="true"><span></span><span></span><span></span></div>
169
- <main class="mesh-content">
170
- <div class="mesh-load-scan" aria-hidden="true"></div>
171
- <article class="mesh-article">
147
+ <div class="ai-read-progress" aria-hidden="true"></div>
148
+ <button type="button" class="ai-back-to-top" aria-label="Back to top" title="Back to top">↑</button>
149
+ <div class="ai-stage-toast" aria-live="polite" aria-atomic="true"></div>
150
+ <div class="ai-mouse-glow" aria-hidden="true"></div>
151
+ <div class="ai-depth-blur" aria-hidden="true"></div>
152
+ <div class="ai-thinking-dots" aria-hidden="true"><span></span><span></span><span></span></div>
153
+ </Fragment>
154
+ <div class="ai-load-scan" aria-hidden="true"></div>
155
+ <article class="ai-article">
172
156
  {heroImage && (
173
157
  <div class="hero-shell">
174
158
  <div class="hero-pane">
@@ -191,74 +175,74 @@ for (let i = 0; i < pts.length; i++) {
191
175
  </div>
192
176
  )}
193
177
  <div class="prose">
194
- <div class="title mesh-title">
195
- <div class="mesh-meta-terminal">
178
+ <div class="title ai-title">
179
+ <div class="ai-meta-terminal">
196
180
  $ published {fmt(pubDate)}
197
181
  {updatedDate && <> | updated {fmt(updatedDate)}</>}
198
182
  {readMinutes !== undefined && <> | ~{readMinutes} min read</>}
199
183
  </div>
200
184
  {hasSystemMeta && (
201
- <div class="mesh-system-row" aria-label="Model status">
202
- {aiModel && <span class="mesh-system-chip">model: {aiModel}</span>}
203
- {aiMode && <span class="mesh-system-chip">mode: {aiMode}</span>}
204
- {aiState && <span class="mesh-system-chip">state: {aiState}</span>}
185
+ <div class="ai-system-row" aria-label="Model status">
186
+ {aiModel && <span class="ai-system-chip">model: {aiModel}</span>}
187
+ {aiMode && <span class="ai-system-chip">mode: {aiMode}</span>}
188
+ {aiState && <span class="ai-system-chip">state: {aiState}</span>}
205
189
  </div>
206
190
  )}
207
191
  {contextText && (
208
- <div class="mesh-prompt-line">
209
- Context: <span class="mesh-prompt-topic">{contextText}</span> →
192
+ <div class="ai-prompt-line">
193
+ Context: <span class="ai-prompt-topic">{contextText}</span> →
210
194
  </div>
211
195
  )}
212
- <h1 class="mesh-title-text">{title}</h1>
213
- {subtitle && <p class="mesh-subtitle-text">{subtitle}</p>}
196
+ <h1 class="ai-title-text">{title}</h1>
197
+ {subtitle && <p class="ai-subtitle-text">{subtitle}</p>}
214
198
  <hr />
215
- <div class="mesh-title-flow" aria-hidden="true"></div>
199
+ <div class="ai-title-flow" aria-hidden="true"></div>
216
200
  </div>
217
- <div class="mesh-response-wrap">
218
- <div class="mesh-response-header">
219
- <div class="mesh-response-avatar" aria-hidden="true"></div>
220
- <span class="mesh-response-label">Output</span>
201
+ <div class="ai-response-wrap">
202
+ <div class="ai-response-header">
203
+ <div class="ai-response-avatar" aria-hidden="true"></div>
204
+ <span class="ai-response-label">Output</span>
221
205
  {hasResponseMeta && (
222
- <div class="mesh-response-meta">
206
+ <div class="ai-response-meta">
223
207
  {aiLatencyMs !== undefined && <span>latency est <strong>{aiLatencyMs}</strong> ms</span>}
224
208
  {confidenceText !== undefined && <span>confidence <strong>{confidenceText}</strong></span>}
225
209
  </div>
226
210
  )}
227
211
  </div>
228
- <div class="mesh-prose-body mesh-prose-fade">
212
+ <div class="ai-prose-body ai-prose-fade">
229
213
  <slot />
230
- <span class="mesh-block-cursor" aria-hidden="true"></span>
214
+ <span class="ai-block-cursor" aria-hidden="true"></span>
231
215
  </div>
232
216
  </div>
233
217
  {hasStats && (
234
- <div class="mesh-stats-corner">
235
- {aiModel && <span class="mesh-model-id">{aiModel}</span>}
218
+ <div class="ai-stats-corner">
219
+ {aiModel && <span class="ai-model-id">{aiModel}</span>}
236
220
  {aiModel && (wordCount !== undefined || tokenCount !== undefined) && ' · '}
237
221
  {wordCount !== undefined && <span>{compact(wordCount)} words</span>}
238
222
  {wordCount !== undefined && tokenCount !== undefined && ' · '}
239
223
  {tokenCount !== undefined && <span>{compact(tokenCount)} tokens</span>}
240
224
  </div>
241
225
  )}
242
- <button type="button" class="mesh-regenerate" aria-label={messages.blog.regenerate}>{messages.blog.regenerate}</button>
226
+ <button type="button" class="ai-regenerate" aria-label={messages.blog.regenerate}>{messages.blog.regenerate}</button>
243
227
  </div>
244
228
  </article>
245
229
  {related.length > 0 && (
246
- <section class="mesh-related" aria-label="Related posts">
247
- <h2 class="mesh-related-title">{messages.blog.related}</h2>
248
- <div class="mesh-related-grid">
230
+ <section class="ai-related" aria-label="Related posts">
231
+ <h2 class="ai-related-title">{messages.blog.related}</h2>
232
+ <div class="ai-related-grid">
249
233
  {related.map((p) => (
250
- <a href={localePath(resolvedLocale, `/blog/${blogIdToSlugAnyLocale(p.id)}`)} class="mesh-related-card">
234
+ <a href={localePath(resolvedLocale, `/blog/${blogIdToSlugAnyLocale(p.id)}`)} class="ai-related-card">
251
235
  {p.data.heroImage ? (
252
- <div class="mesh-related-img">
236
+ <div class="ai-related-img">
253
237
  <Image width={320} height={180} src={p.data.heroImage} alt={p.data.title} />
254
238
  </div>
255
239
  ) : (
256
- <div class="mesh-related-placeholder">
240
+ <div class="ai-related-placeholder">
257
241
  <span>{p.data.title.charAt(0)}</span>
258
242
  </div>
259
243
  )}
260
- <h3 class="mesh-related-card-title">{p.data.title}</h3>
261
- <p class="mesh-related-card-date">
244
+ <h3 class="ai-related-card-title">{p.data.title}</h3>
245
+ <p class="ai-related-card-date">
262
246
  <FormattedDate date={p.data.pubDate} />
263
247
  </p>
264
248
  </a>
@@ -266,14 +250,13 @@ for (let i = 0; i < pts.length; i++) {
266
250
  </div>
267
251
  </section>
268
252
  )}
269
- <nav class="mesh-back-to-blog" aria-label="Back to blog">
253
+ <nav class="ai-back-to-blog" aria-label="Back to blog">
270
254
  <a href={localePath(resolvedLocale, '/blog/')}>
271
- <span class="mesh-back-prompt">$</span>
272
- <span class="mesh-back-text">← {messages.blog.backToBlog}</span>
255
+ <span class="ai-back-prompt">$</span>
256
+ <span class="ai-back-text">← {messages.blog.backToBlog}</span>
273
257
  </a>
274
258
  </nav>
275
- </main>
276
- <Footer scanlines />
277
- <script is:inline src="/scripts/blogpost-effects.js" defer></script>
278
- </body>
279
- </html>
259
+ <Fragment slot="body-end">
260
+ <script is:inline src="/scripts/blogpost-effects.js" defer></script>
261
+ </Fragment>
262
+ </AiShell>
@@ -0,0 +1,16 @@
1
+ ---
2
+ import type { Locale } from '../i18n/config';
3
+ import CyberShell from './shells/CyberShell.astro';
4
+
5
+ interface Props {
6
+ locale?: Locale;
7
+ title: string;
8
+ description: string;
9
+ }
10
+
11
+ const { locale, title, description } = Astro.props as Props;
12
+ ---
13
+
14
+ <CyberShell locale={locale} title={title} description={description}>
15
+ <slot />
16
+ </CyberShell>
@@ -0,0 +1,16 @@
1
+ ---
2
+ import type { Locale } from '../i18n/config';
3
+ import HackerShell from './shells/HackerShell.astro';
4
+
5
+ interface Props {
6
+ locale?: Locale;
7
+ title: string;
8
+ description: string;
9
+ }
10
+
11
+ const { locale, title, description } = Astro.props as Props;
12
+ ---
13
+
14
+ <HackerShell locale={locale} title={title} description={description}>
15
+ <slot />
16
+ </HackerShell>
@@ -1,12 +1,10 @@
1
1
  ---
2
2
  import type { CollectionEntry } from 'astro:content';
3
- import BaseHead from '../components/BaseHead.astro';
4
- import Footer from '../components/Footer.astro';
5
3
  import FormattedDate from '../components/FormattedDate.astro';
6
- import Header from '../components/Header.astro';
7
4
  import { SITE_HERO_BY_LOCALE, SITE_TITLE } from '../consts';
8
5
  import { type Locale, blogIdToSlugAnyLocale, localePath } from '../i18n/config';
9
6
  import { getMessages } from '../i18n/messages';
7
+ import MatrixShell from './shells/MatrixShell.astro';
10
8
 
11
9
  interface Props {
12
10
  locale: Locale;
@@ -18,56 +16,32 @@ const messages = getMessages(locale);
18
16
  const heroText = SITE_HERO_BY_LOCALE[locale] ?? messages.home.hero;
19
17
  ---
20
18
 
21
- <!doctype html>
22
- <html lang={locale}>
23
- <head>
24
- <BaseHead title={SITE_TITLE} description={messages.siteDescription} />
25
- <link rel="stylesheet" href="/styles/home-page.css" />
26
- <link rel="preload" href="/fonts/ff2a407fe06752facf8828a86955e988.woff2" as="font" type="font/woff2" crossorigin />
27
- </head>
28
- <body class="page-home">
29
- <canvas id="matrix-bg" aria-hidden="true"></canvas>
30
- <Header
31
- locale={locale}
32
- labels={{
33
- home: messages.nav.home,
34
- blog: messages.nav.blog,
35
- about: messages.nav.about,
36
- status: messages.nav.status,
37
- language: messages.langLabel,
38
- }}
39
- />
40
- <main>
41
- <h1>{SITE_TITLE}</h1>
42
- <p class="home-hero-copy">
43
- {heroText}
44
- </p>
19
+ <MatrixShell locale={locale} title={SITE_TITLE} description={messages.siteDescription}>
20
+ <link slot="head" rel="preload" href="/fonts/ff2a407fe06752facf8828a86955e988.woff2" as="font" type="font/woff2" crossorigin />
21
+ <h1>{SITE_TITLE}</h1>
22
+ <p class="home-hero-copy">{heroText}</p>
45
23
 
46
- <section class="home-latest">
47
- <h2 class="home-section-title">{messages.home.latest}</h2>
48
- <ul class="home-post-list">
49
- {
50
- latestPosts.length > 0 ? (
51
- latestPosts.map((post) => (
52
- <li>
53
- <a class="home-post-title" href={localePath(locale, `/blog/${blogIdToSlugAnyLocale(post.id)}`)}>
54
- {post.data.title}
55
- </a>
56
- <div class="home-post-meta">
57
- <FormattedDate date={post.data.pubDate} />
58
- {post.data.description && <> · {post.data.description}</>}
59
- </div>
60
- </li>
61
- ))
62
- ) : (
63
- <li>{messages.home.noPosts}</li>
64
- )
65
- }
66
- </ul>
67
- <p><a href={localePath(locale, '/blog/')}>{messages.home.viewAll}</a></p>
68
- </section>
69
- </main>
70
- <Footer />
71
- <script is:inline src="/scripts/home-matrix.js" defer></script>
72
- </body>
73
- </html>
24
+ <section class="home-latest">
25
+ <h2 class="home-section-title">{messages.home.latest}</h2>
26
+ <ul class="home-post-list">
27
+ {
28
+ latestPosts.length > 0 ? (
29
+ latestPosts.map((post) => (
30
+ <li>
31
+ <a class="home-post-title" href={localePath(locale, `/blog/${blogIdToSlugAnyLocale(post.id)}`)}>
32
+ {post.data.title}
33
+ </a>
34
+ <div class="home-post-meta">
35
+ <FormattedDate date={post.data.pubDate} />
36
+ {post.data.description && <> · {post.data.description}</>}
37
+ </div>
38
+ </li>
39
+ ))
40
+ ) : (
41
+ <li>{messages.home.noPosts}</li>
42
+ )
43
+ }
44
+ </ul>
45
+ <p><a href={localePath(locale, '/blog/')}>{messages.home.viewAll}</a></p>
46
+ </section>
47
+ </MatrixShell>
@@ -0,0 +1,16 @@
1
+ ---
2
+ import type { Locale } from '../i18n/config';
3
+ import MatrixShell from './shells/MatrixShell.astro';
4
+
5
+ interface Props {
6
+ locale?: Locale;
7
+ title: string;
8
+ description: string;
9
+ }
10
+
11
+ const { locale, title, description } = Astro.props as Props;
12
+ ---
13
+
14
+ <MatrixShell locale={locale} title={title} description={description}>
15
+ <slot />
16
+ </MatrixShell>
@@ -0,0 +1,58 @@
1
+ ---
2
+ import type { Locale } from '../../i18n/config';
3
+ import ThemeFrame from '../../components/shared/ThemeFrame.astro';
4
+ import type { ImageMetadata } from 'astro';
5
+
6
+ interface Props {
7
+ locale?: Locale;
8
+ title: string;
9
+ description: string;
10
+ image?: ImageMetadata;
11
+ pageType?: 'website' | 'article';
12
+ publishedTime?: Date;
13
+ modifiedTime?: Date;
14
+ author?: string;
15
+ tags?: string[];
16
+ schema?: Record<string, unknown> | Record<string, unknown>[];
17
+ noindex?: boolean;
18
+ localeHrefs?: Partial<Record<Locale, string>>;
19
+ }
20
+
21
+ const {
22
+ locale,
23
+ title,
24
+ description,
25
+ image,
26
+ pageType,
27
+ publishedTime,
28
+ modifiedTime,
29
+ author,
30
+ tags,
31
+ schema,
32
+ noindex,
33
+ localeHrefs,
34
+ } = Astro.props as Props;
35
+ ---
36
+
37
+ <ThemeFrame
38
+ locale={locale}
39
+ title={title}
40
+ description={description}
41
+ image={image}
42
+ pageType={pageType}
43
+ publishedTime={publishedTime}
44
+ modifiedTime={modifiedTime}
45
+ author={author}
46
+ tags={tags}
47
+ schema={schema}
48
+ noindex={noindex}
49
+ bodyClass="ai-page"
50
+ mainClass="page-main ai-content"
51
+ scanlines
52
+ localeHrefs={localeHrefs}
53
+ >
54
+ <slot name="head" slot="head" />
55
+ <slot name="body-start" slot="body-start" />
56
+ <slot />
57
+ <slot name="body-end" slot="body-end" />
58
+ </ThemeFrame>
@@ -0,0 +1,16 @@
1
+ ---
2
+ import type { Locale } from '../../i18n/config';
3
+ import ThemeFrame from '../../components/shared/ThemeFrame.astro';
4
+
5
+ interface Props {
6
+ locale?: Locale;
7
+ title: string;
8
+ description: string;
9
+ }
10
+
11
+ const { locale, title, description } = Astro.props as Props;
12
+ ---
13
+
14
+ <ThemeFrame locale={locale} title={title} description={description} bodyClass="page-default" mainClass="page-main">
15
+ <slot />
16
+ </ThemeFrame>
@@ -0,0 +1,30 @@
1
+ ---
2
+ import type { Locale } from '../../i18n/config';
3
+ import ThemeFrame from '../../components/shared/ThemeFrame.astro';
4
+
5
+ interface Props {
6
+ locale?: Locale;
7
+ title: string;
8
+ description: string;
9
+ localeHrefs?: Partial<Record<Locale, string>>;
10
+ }
11
+
12
+ const { locale, title, description, localeHrefs } = Astro.props as Props;
13
+ ---
14
+
15
+ <ThemeFrame locale={locale} title={title} description={description} bodyClass="cyber-page" mainClass="page-main cyber-content" localeHrefs={localeHrefs}>
16
+ <Fragment slot="body-start">
17
+ <div class="cyber-load-glitch" aria-hidden="true"></div>
18
+ <div class="cyber-spotlight" aria-hidden="true">
19
+ <div class="cyber-spotlight-tr"></div>
20
+ <div class="cyber-spotlight-tl"></div>
21
+ </div>
22
+ <div class="cyber-flicker" aria-hidden="true"></div>
23
+ <div class="cyber-haze" aria-hidden="true"></div>
24
+ <div class="cyber-vignette" aria-hidden="true"></div>
25
+ <slot name="body-start" />
26
+ </Fragment>
27
+ <slot name="head" slot="head" />
28
+ <slot />
29
+ <slot name="body-end" slot="body-end" />
30
+ </ThemeFrame>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { Locale } from '../../i18n/config';
3
+ import ThemeFrame from '../../components/shared/ThemeFrame.astro';
4
+
5
+ interface Props {
6
+ locale?: Locale;
7
+ title: string;
8
+ description: string;
9
+ bodyClass?: string;
10
+ }
11
+
12
+ const { locale, title, description, bodyClass = 'hacker-page' } = Astro.props as Props;
13
+ ---
14
+
15
+ <ThemeFrame locale={locale} title={title} description={description} bodyClass={bodyClass} mainClass="page-main hacker-content">
16
+ <link slot="head" rel="stylesheet" href="/styles/about-page.css" />
17
+ <slot name="head" slot="head" />
18
+ <slot name="body-start" slot="body-start" />
19
+ <slot />
20
+ <slot name="body-end" slot="body-end" />
21
+ </ThemeFrame>
@@ -0,0 +1,19 @@
1
+ ---
2
+ import type { Locale } from '../../i18n/config';
3
+ import ThemeFrame from '../../components/shared/ThemeFrame.astro';
4
+
5
+ interface Props {
6
+ locale?: Locale;
7
+ title: string;
8
+ description: string;
9
+ }
10
+
11
+ const { locale, title, description } = Astro.props as Props;
12
+ ---
13
+
14
+ <ThemeFrame locale={locale} title={title} description={description} bodyClass="page-home" mainClass="page-main home-content">
15
+ <link slot="head" rel="stylesheet" href="/styles/home-page.css" />
16
+ <canvas slot="body-start" id="matrix-bg" aria-hidden="true"></canvas>
17
+ <script slot="body-end" is:inline src="/scripts/home-matrix.js" defer></script>
18
+ <slot />
19
+ </ThemeFrame>