@karaoke-cms/astro 0.6.3 → 0.9.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.
@@ -1,31 +0,0 @@
1
- ---
2
- import { getCollection } from 'astro:content';
3
- import Base from '../../layouts/Base.astro';
4
- import { SITE_TITLE } from '../../consts';
5
-
6
- const docs = (await getCollection('docs', ({ data }) => data.publish === true))
7
- .sort((a, b) => a.data.title.localeCompare(b.data.title));
8
- ---
9
-
10
- <Base title={`Docs — ${SITE_TITLE}`}>
11
- <div class="listing-header">
12
- <h1>Docs</h1>
13
- </div>
14
- {docs.length > 0 ? (
15
- <ul class="post-list">
16
- {docs.map(doc => (
17
- <li>
18
- {doc.data.date && (
19
- <span class="post-date">{doc.data.date.toISOString().slice(0, 10)}</span>
20
- )}
21
- <a href={`/docs/${doc.id}`}>{doc.data.title}</a>
22
- </li>
23
- ))}
24
- </ul>
25
- ) : (
26
- <div class="empty-state">
27
- <p>No docs published yet.</p>
28
- <p>Create a Markdown file in your vault's <code>docs/</code> folder and set <code>publish: true</code> in the frontmatter to make it appear here.</p>
29
- </div>
30
- )}
31
- </Base>
@@ -1,65 +0,0 @@
1
- ---
2
- import { getCollection } from 'astro:content';
3
- import Base from '../layouts/Base.astro';
4
- import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
5
-
6
- const posts = (await getCollection('blog', ({ data }) => data.publish === true))
7
- .sort((a, b) => (b.data.date?.valueOf() ?? 0) - (a.data.date?.valueOf() ?? 0))
8
- .slice(0, 5);
9
-
10
- const docs = (await getCollection('docs', ({ data }) => data.publish === true))
11
- .sort((a, b) => a.data.title.localeCompare(b.data.title))
12
- .slice(0, 5);
13
- ---
14
-
15
- <Base title={SITE_TITLE} description={SITE_DESCRIPTION}>
16
- <div class="home-grid">
17
- <section class="home-section">
18
- <h2>Blog</h2>
19
- {posts.length > 0 ? (
20
- <>
21
- <ul class="post-list">
22
- {posts.map(post => (
23
- <li>
24
- <a href={`/blog/${post.id}`}>{post.data.title}</a>
25
- {post.data.date && (
26
- <span class="post-date">{post.data.date.toISOString().slice(0, 10)}</span>
27
- )}
28
- </li>
29
- ))}
30
- </ul>
31
- <a href="/blog" class="view-all">View all →</a>
32
- </>
33
- ) : (
34
- <div class="empty-state">
35
- <p>No posts yet.</p>
36
- <p>Add a Markdown file to <code>content/blog/</code> with <code>publish: true</code> in the frontmatter.</p>
37
- </div>
38
- )}
39
- </section>
40
-
41
- <section class="home-section">
42
- <h2>Docs</h2>
43
- {docs.length > 0 ? (
44
- <>
45
- <ul class="post-list">
46
- {docs.map(doc => (
47
- <li>
48
- <a href={`/docs/${doc.id}`}>{doc.data.title}</a>
49
- {doc.data.date && (
50
- <span class="post-date">{doc.data.date.toISOString().slice(0, 10)}</span>
51
- )}
52
- </li>
53
- ))}
54
- </ul>
55
- <a href="/docs" class="view-all">View all →</a>
56
- </>
57
- ) : (
58
- <div class="empty-state">
59
- <p>No docs yet.</p>
60
- <p>Add a Markdown file to <code>content/docs/</code> with <code>publish: true</code> in the frontmatter.</p>
61
- </div>
62
- )}
63
- </section>
64
- </div>
65
- </Base>
@@ -1,53 +0,0 @@
1
- ---
2
- import { getCollection } from 'astro:content';
3
- import Base from '../../layouts/Base.astro';
4
- import { SITE_TITLE } from '../../consts';
5
-
6
- export async function getStaticPaths() {
7
- const [blog, docs] = await Promise.all([
8
- getCollection('blog', ({ data }) => data.publish === true),
9
- getCollection('docs', ({ data }) => data.publish === true),
10
- ]);
11
-
12
- // Collect all unique tags
13
- const tags = new Set<string>();
14
- for (const entry of [...blog, ...docs]) {
15
- for (const tag of entry.data.tags ?? []) tags.add(tag);
16
- }
17
-
18
- return [...tags].map(tag => ({
19
- params: { tag },
20
- props: {
21
- tag,
22
- entries: [...blog, ...docs]
23
- .filter(e => e.data.tags?.includes(tag))
24
- .sort((a, b) => (b.data.date?.valueOf() ?? 0) - (a.data.date?.valueOf() ?? 0)),
25
- },
26
- }));
27
- }
28
-
29
- const { tag, entries } = Astro.props;
30
-
31
- // Determine the URL prefix for each entry by its collection
32
- function href(entry: { collection: string; id: string }) {
33
- return `/${entry.collection}/${entry.id}`;
34
- }
35
- ---
36
-
37
- <Base title={`#${tag} — ${SITE_TITLE}`}>
38
- <div class="listing-header">
39
- <h1>#{tag}</h1>
40
- <p><a href="/tags">← All tags</a></p>
41
- </div>
42
- <ul class="post-list">
43
- {entries.map(entry => (
44
- <li>
45
- {entry.data.date && (
46
- <span class="post-date">{entry.data.date.toISOString().slice(0, 10)}</span>
47
- )}
48
- <a href={href(entry)}>{entry.data.title}</a>
49
- <span class="post-collection">{entry.collection}</span>
50
- </li>
51
- ))}
52
- </ul>
53
- </Base>
@@ -1,41 +0,0 @@
1
- ---
2
- import { getCollection } from 'astro:content';
3
- import Base from '../../layouts/Base.astro';
4
- import { SITE_TITLE } from '../../consts';
5
-
6
- const [blog, docs] = await Promise.all([
7
- getCollection('blog', ({ data }) => data.publish === true),
8
- getCollection('docs', ({ data }) => data.publish === true),
9
- ]);
10
-
11
- // Count occurrences of each tag across both collections
12
- const counts = new Map<string, number>();
13
- for (const entry of [...blog, ...docs]) {
14
- for (const tag of entry.data.tags ?? []) {
15
- counts.set(tag, (counts.get(tag) ?? 0) + 1);
16
- }
17
- }
18
-
19
- const tags = [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0]));
20
- ---
21
-
22
- <Base title={`Tags — ${SITE_TITLE}`}>
23
- <div class="listing-header">
24
- <h1>Tags</h1>
25
- </div>
26
- {tags.length > 0 ? (
27
- <ul class="tag-list">
28
- {tags.map(([tag, count]) => (
29
- <li>
30
- <a href={`/tags/${tag}`}>{tag}</a>
31
- <span class="tag-count">{count}</span>
32
- </li>
33
- ))}
34
- </ul>
35
- ) : (
36
- <div class="empty-state">
37
- <p>No tags yet.</p>
38
- <p>Tags are added to posts via the <code>tags</code> frontmatter field or by AI enrichment.</p>
39
- </div>
40
- )}
41
- </Base>
@@ -1,446 +0,0 @@
1
- /* karaoke-cms default theme — system UI, clean, neutral */
2
-
3
- :root {
4
- --font-body: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
5
- --font-mono: ui-monospace, 'Cascadia Code', 'Fira Code', monospace;
6
-
7
- --font-size-base: 1.0625rem;
8
- --font-size-sm: 0.875rem;
9
- --font-size-lg: 1.25rem;
10
- --font-size-xl: 1.75rem;
11
-
12
- --line-height-body: 1.65;
13
- --line-height-heading: 1.25;
14
-
15
- --color-bg: #fafaf9;
16
- --color-text: #1a1a1a;
17
- --color-muted: #737373;
18
- --color-border: #e5e5e5;
19
- --color-link: #0066cc;
20
- --color-link-hover: #004499;
21
- --color-link-visited: #551a8b;
22
-
23
- --width-content: 680px;
24
- --width-site: 768px;
25
-
26
- --spacing-xs: 0.25rem;
27
- --spacing-sm: 0.5rem;
28
- --spacing-md: 1rem;
29
- --spacing-lg: 2rem;
30
- --spacing-xl: 4rem;
31
- }
32
-
33
- @media (prefers-color-scheme: dark) {
34
- :root {
35
- --color-bg: #0f0f0f;
36
- --color-text: #e5e5e5;
37
- --color-muted: #9ca3af;
38
- --color-border: #262626;
39
- --color-link: #60a5fa;
40
- --color-link-hover: #93c5fd;
41
- --color-link-visited: #a78bfa;
42
- }
43
- }
44
-
45
- *, *::before, *::after {
46
- box-sizing: border-box;
47
- }
48
-
49
- html {
50
- font-size: var(--font-size-base);
51
- }
52
-
53
- body {
54
- margin: 0;
55
- background: var(--color-bg);
56
- color: var(--color-text);
57
- font-family: var(--font-body);
58
- line-height: var(--line-height-body);
59
- }
60
-
61
- /* --- Layout --- */
62
-
63
- .site-wrap {
64
- max-width: var(--width-site);
65
- margin: 0 auto;
66
- padding: 0 var(--spacing-md);
67
- }
68
-
69
- /* --- Navigation --- */
70
-
71
- header {
72
- border-bottom: 1px solid var(--color-border);
73
- margin-bottom: var(--spacing-xl);
74
- }
75
-
76
- .header-inner {
77
- max-width: var(--width-site);
78
- margin: 0 auto;
79
- padding: 0 var(--spacing-md);
80
- display: flex;
81
- align-items: center;
82
- gap: var(--spacing-lg);
83
- height: 3rem;
84
- }
85
-
86
- .site-name {
87
- font-weight: 600;
88
- text-decoration: none;
89
- color: var(--color-text);
90
- flex-shrink: 0;
91
- }
92
-
93
- .site-name:hover {
94
- color: var(--color-link);
95
- }
96
-
97
- header nav ul {
98
- list-style: none;
99
- margin: 0;
100
- padding: 0;
101
- display: flex;
102
- gap: var(--spacing-md);
103
- }
104
-
105
- header nav ul a {
106
- text-decoration: none;
107
- color: var(--color-muted);
108
- font-size: var(--font-size-sm);
109
- padding: var(--spacing-xs) 0;
110
- min-height: 44px;
111
- display: flex;
112
- align-items: center;
113
- }
114
-
115
- header nav ul a:hover,
116
- header nav ul a[aria-current="page"] {
117
- color: var(--color-text);
118
- }
119
-
120
- /* --- Page body (left + main + right regions) --- */
121
-
122
- .page-body {
123
- display: flex;
124
- align-items: flex-start;
125
- max-width: var(--width-site);
126
- margin: 0 auto;
127
- padding: 0 var(--spacing-md);
128
- min-height: calc(100vh - 12rem);
129
- gap: var(--spacing-lg);
130
- }
131
-
132
- .page-body.has-left,
133
- .page-body.has-right {
134
- max-width: calc(var(--width-site) + var(--width-sidebar, 220px) + var(--spacing-lg));
135
- }
136
-
137
- .page-body.has-left.has-right {
138
- max-width: calc(var(--width-site) + 2 * (var(--width-sidebar, 220px) + var(--spacing-lg)));
139
- }
140
-
141
- main {
142
- flex: 1;
143
- min-width: 0;
144
- }
145
-
146
- .region-left,
147
- .region-right {
148
- width: var(--width-sidebar, 220px);
149
- flex-shrink: 0;
150
- padding-top: var(--spacing-xs);
151
- }
152
-
153
- /* --- Sidebar --- */
154
-
155
- .sidebar-section {
156
- margin-bottom: var(--spacing-lg);
157
- }
158
-
159
- .sidebar-heading {
160
- font-size: var(--font-size-sm);
161
- text-transform: uppercase;
162
- letter-spacing: 0.05em;
163
- color: var(--color-muted);
164
- margin: 0 0 var(--spacing-sm);
165
- }
166
-
167
- .sidebar-list {
168
- list-style: none;
169
- margin: 0;
170
- padding: 0;
171
- display: flex;
172
- flex-direction: column;
173
- gap: var(--spacing-xs);
174
- }
175
-
176
- .sidebar-list li {
177
- display: flex;
178
- flex-direction: column;
179
- gap: 2px;
180
- }
181
-
182
- .sidebar-list a {
183
- font-size: var(--font-size-sm);
184
- text-decoration: none;
185
- color: var(--color-text);
186
- }
187
-
188
- .sidebar-list a:hover {
189
- color: var(--color-link);
190
- }
191
-
192
- /* --- Footer --- */
193
-
194
- footer {
195
- border-top: 1px solid var(--color-border);
196
- margin-top: var(--spacing-xl);
197
- padding: var(--spacing-lg) var(--spacing-md);
198
- max-width: var(--width-site);
199
- margin-left: auto;
200
- margin-right: auto;
201
- color: var(--color-muted);
202
- font-size: var(--font-size-sm);
203
- }
204
-
205
- footer a {
206
- color: var(--color-muted);
207
- }
208
-
209
- footer a:hover {
210
- color: var(--color-link);
211
- }
212
-
213
- /* --- Typography --- */
214
-
215
- h1, h2, h3, h4 {
216
- line-height: var(--line-height-heading);
217
- margin-top: 0;
218
- }
219
-
220
- a {
221
- color: var(--color-link);
222
- }
223
-
224
- a:hover {
225
- color: var(--color-link-hover);
226
- }
227
-
228
- a:visited {
229
- color: var(--color-link-visited);
230
- }
231
-
232
- code {
233
- font-family: var(--font-mono);
234
- font-size: 0.9em;
235
- background: var(--color-border);
236
- padding: 0.1em 0.3em;
237
- border-radius: 3px;
238
- }
239
-
240
- pre {
241
- background: var(--color-border);
242
- padding: var(--spacing-md);
243
- overflow-x: auto;
244
- border-radius: 4px;
245
- }
246
-
247
- pre code {
248
- background: none;
249
- padding: 0;
250
- }
251
-
252
- /* --- Post/doc prose --- */
253
-
254
- .prose {
255
- max-width: var(--width-content);
256
- }
257
-
258
- .prose img {
259
- max-width: 100%;
260
- height: auto;
261
- }
262
-
263
- /* --- Listing pages --- */
264
-
265
- .listing-header {
266
- margin-bottom: var(--spacing-lg);
267
- }
268
-
269
- .listing-header h1 {
270
- font-size: var(--font-size-xl);
271
- margin-bottom: 0;
272
- }
273
-
274
- .post-list {
275
- list-style: none;
276
- margin: 0;
277
- padding: 0;
278
- }
279
-
280
- .post-list li {
281
- padding: var(--spacing-sm) 0;
282
- border-bottom: 1px solid var(--color-border);
283
- display: flex;
284
- gap: var(--spacing-md);
285
- align-items: baseline;
286
- }
287
-
288
- .post-list li:first-child {
289
- border-top: 1px solid var(--color-border);
290
- }
291
-
292
- .post-date {
293
- color: var(--color-muted);
294
- font-size: var(--font-size-sm);
295
- white-space: nowrap;
296
- flex-shrink: 0;
297
- }
298
-
299
- .empty-state {
300
- color: var(--color-muted);
301
- padding: var(--spacing-lg) 0;
302
- }
303
-
304
- .empty-state p {
305
- margin: 0 0 var(--spacing-sm);
306
- }
307
-
308
- /* --- Homepage two-column --- */
309
-
310
- .home-grid {
311
- display: grid;
312
- grid-template-columns: 1fr 1fr;
313
- gap: var(--spacing-xl);
314
- }
315
-
316
- @media (max-width: 640px) {
317
- .home-grid {
318
- grid-template-columns: 1fr;
319
- gap: var(--spacing-lg);
320
- }
321
-
322
- .page-body {
323
- flex-direction: column;
324
- }
325
-
326
- .region-left,
327
- .region-right {
328
- width: 100%;
329
- }
330
- }
331
-
332
- .home-section h2 {
333
- font-size: var(--font-size-base);
334
- text-transform: uppercase;
335
- letter-spacing: 0.05em;
336
- color: var(--color-muted);
337
- margin-bottom: var(--spacing-md);
338
- }
339
-
340
- .home-section .post-list li {
341
- flex-direction: column;
342
- gap: var(--spacing-xs);
343
- }
344
-
345
- .view-all {
346
- display: inline-block;
347
- margin-top: var(--spacing-md);
348
- font-size: var(--font-size-sm);
349
- color: var(--color-muted);
350
- }
351
-
352
- .view-all:hover {
353
- color: var(--color-link);
354
- }
355
-
356
- /* --- Single post/doc --- */
357
-
358
- .post-header {
359
- max-width: var(--width-content);
360
- margin-bottom: var(--spacing-lg);
361
- }
362
-
363
- .post-header h1 {
364
- font-size: var(--font-size-xl);
365
- margin-bottom: var(--spacing-sm);
366
- }
367
-
368
- .post-meta {
369
- color: var(--color-muted);
370
- font-size: var(--font-size-sm);
371
- }
372
-
373
- .post-footer {
374
- margin-top: var(--spacing-xl);
375
- padding-top: var(--spacing-md);
376
- border-top: 1px solid var(--color-border);
377
- max-width: var(--width-content);
378
- }
379
-
380
- .post-tags {
381
- display: flex;
382
- flex-wrap: wrap;
383
- gap: var(--spacing-xs);
384
- margin-bottom: var(--spacing-md);
385
- }
386
-
387
- .tag {
388
- font-size: var(--font-size-sm);
389
- color: var(--color-muted);
390
- text-decoration: none;
391
- border: 1px solid var(--color-border);
392
- border-radius: 3px;
393
- padding: 2px 7px;
394
- }
395
-
396
- .tag:hover {
397
- color: var(--color-text);
398
- border-color: var(--color-text);
399
- }
400
-
401
- .tag-list {
402
- list-style: none;
403
- padding: 0;
404
- display: flex;
405
- flex-direction: column;
406
- gap: var(--spacing-xs);
407
- }
408
-
409
- .tag-list li {
410
- display: flex;
411
- align-items: center;
412
- gap: var(--spacing-sm);
413
- }
414
-
415
- .tag-count {
416
- font-size: var(--font-size-sm);
417
- color: var(--color-muted);
418
- }
419
-
420
- .post-collection {
421
- font-size: var(--font-size-sm);
422
- color: var(--color-muted);
423
- }
424
-
425
- /* --- Related posts --- */
426
-
427
- .related-posts {
428
- margin-top: var(--spacing-lg);
429
- }
430
-
431
- .related-label {
432
- font-size: var(--font-size-sm);
433
- text-transform: uppercase;
434
- letter-spacing: 0.05em;
435
- color: var(--color-muted);
436
- margin: 0 0 var(--spacing-sm);
437
- }
438
-
439
- .related-posts ul {
440
- list-style: none;
441
- padding: 0;
442
- margin: 0;
443
- display: flex;
444
- flex-direction: column;
445
- gap: var(--spacing-xs);
446
- }