@karaoke-cms/theme-default 0.9.2 → 0.9.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/package.json +5 -5
- package/src/components/AudienceGrid.astro +143 -0
- package/src/components/CtaSection.astro +91 -0
- package/src/components/FeatureGrid.astro +138 -0
- package/src/components/Hero.astro +141 -0
- package/src/components/HowItWorks.astro +117 -0
- package/src/components/InstallBox.astro +71 -0
- package/src/components/MetaphorSection.astro +69 -0
- package/src/components/ScaleSection.astro +146 -0
- package/src/index.ts +8 -3
- package/src/pages/404.astro +3 -3
- package/src/pages/docs/[slug].astro +3 -3
- package/src/pages/docs/index.astro +3 -3
- package/src/pages/index.astro +18 -60
- package/src/pages/tags/[tag].astro +3 -3
- package/src/pages/tags/index.astro +3 -3
- package/src/styles.css +99 -18
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
command?: string;
|
|
4
|
+
size?: 'sm' | 'lg';
|
|
5
|
+
}
|
|
6
|
+
const { command = 'npm create @karaoke-cms@latest', size = 'sm' } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class:list={['install-box', { large: size === 'lg' }]}>
|
|
10
|
+
<code>{command}</code>
|
|
11
|
+
<button class="copy-btn" data-copy={command} aria-label="Copy to clipboard">
|
|
12
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
13
|
+
<rect x="9" y="9" width="13" height="13" rx="2"/>
|
|
14
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
|
15
|
+
</svg>
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
const CHECK_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
21
|
+
const COPY_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
|
|
22
|
+
|
|
23
|
+
document.querySelectorAll<HTMLButtonElement>('.copy-btn').forEach(btn => {
|
|
24
|
+
if (btn.dataset.bound) return;
|
|
25
|
+
btn.dataset.bound = '1';
|
|
26
|
+
btn.addEventListener('click', () => {
|
|
27
|
+
const text = btn.getAttribute('data-copy') ?? '';
|
|
28
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
29
|
+
btn.innerHTML = CHECK_SVG;
|
|
30
|
+
setTimeout(() => { btn.innerHTML = COPY_SVG; }, 1800);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.install-box {
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
background: var(--color-section-dark-2, #1e293b);
|
|
41
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
42
|
+
border-radius: var(--radius-lg, 12px);
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
}
|
|
45
|
+
.install-box code {
|
|
46
|
+
background: transparent;
|
|
47
|
+
border: none;
|
|
48
|
+
padding: 10px 16px;
|
|
49
|
+
color: #a5b4fc;
|
|
50
|
+
font-size: 0.9rem;
|
|
51
|
+
font-family: var(--font-mono);
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
}
|
|
54
|
+
.install-box.large code {
|
|
55
|
+
font-size: 1rem;
|
|
56
|
+
padding: 12px 20px;
|
|
57
|
+
}
|
|
58
|
+
.copy-btn {
|
|
59
|
+
background: none;
|
|
60
|
+
border: none;
|
|
61
|
+
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
|
62
|
+
padding: 10px 14px;
|
|
63
|
+
color: #64748b;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
transition: color 0.15s;
|
|
69
|
+
}
|
|
70
|
+
.copy-btn:hover { color: #a5b4fc; }
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
icon?: string;
|
|
4
|
+
}
|
|
5
|
+
const { icon = '🎤' } = Astro.props;
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<section class="metaphor">
|
|
9
|
+
<div class="container">
|
|
10
|
+
<div class="metaphor-inner">
|
|
11
|
+
<div class="metaphor-icon" aria-hidden="true">{icon}</div>
|
|
12
|
+
<div class="metaphor-text">
|
|
13
|
+
<h2><slot name="heading">The karaoke idea</slot></h2>
|
|
14
|
+
<p>
|
|
15
|
+
<slot>
|
|
16
|
+
In karaoke, the stage is set, the music is playing, the lyrics scroll on screen.
|
|
17
|
+
<strong>You just show up and sing.</strong>
|
|
18
|
+
karaoke-cms works the same way: the infrastructure is in place, the AI is running,
|
|
19
|
+
the site is deployed. You write. We handle the rest.
|
|
20
|
+
</slot>
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</section>
|
|
26
|
+
|
|
27
|
+
<style>
|
|
28
|
+
.metaphor {
|
|
29
|
+
background: var(--color-bg);
|
|
30
|
+
border-bottom: 1px solid var(--color-border);
|
|
31
|
+
padding: 72px 0;
|
|
32
|
+
}
|
|
33
|
+
.container {
|
|
34
|
+
max-width: var(--width-landing, 1100px);
|
|
35
|
+
margin: 0 auto;
|
|
36
|
+
padding: 0 24px;
|
|
37
|
+
}
|
|
38
|
+
.metaphor-inner {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: 48px;
|
|
42
|
+
}
|
|
43
|
+
.metaphor-icon {
|
|
44
|
+
font-size: 4rem;
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
line-height: 1;
|
|
47
|
+
}
|
|
48
|
+
.metaphor-text h2 {
|
|
49
|
+
font-size: clamp(1.5rem, 2.5vw, 2rem);
|
|
50
|
+
font-weight: 700;
|
|
51
|
+
letter-spacing: -0.01em;
|
|
52
|
+
color: var(--color-text);
|
|
53
|
+
margin: 0 0 12px;
|
|
54
|
+
}
|
|
55
|
+
.metaphor-text p {
|
|
56
|
+
font-size: 1.0625rem;
|
|
57
|
+
line-height: 1.7;
|
|
58
|
+
color: var(--color-muted);
|
|
59
|
+
max-width: 600px;
|
|
60
|
+
margin: 0;
|
|
61
|
+
}
|
|
62
|
+
.metaphor-text strong {
|
|
63
|
+
color: var(--color-text);
|
|
64
|
+
}
|
|
65
|
+
@media (max-width: 640px) {
|
|
66
|
+
.metaphor-inner { flex-direction: column; gap: 24px; }
|
|
67
|
+
.metaphor-icon { font-size: 3rem; }
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface ScaleRow {
|
|
3
|
+
label: string;
|
|
4
|
+
pct: number; // 0–100
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
label?: string;
|
|
9
|
+
heading?: string;
|
|
10
|
+
body?: string;
|
|
11
|
+
items?: string[];
|
|
12
|
+
scale?: ScaleRow[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
label = 'Scale',
|
|
17
|
+
heading = 'Starts simple. Grows with you.',
|
|
18
|
+
body = 'Launch a personal blog in an afternoon. Grow into a multi-author editorial platform on the same foundation — same npm packages, same git workflow, same privacy model.',
|
|
19
|
+
items = [
|
|
20
|
+
'Collections scoped per team or department',
|
|
21
|
+
'Module system for search, comments, custom features',
|
|
22
|
+
'Composable layout regions — swap any component',
|
|
23
|
+
'Publish your own npm theme for your brand',
|
|
24
|
+
],
|
|
25
|
+
scale = [
|
|
26
|
+
{ label: 'Personal blog', pct: 28 },
|
|
27
|
+
{ label: 'Team wiki', pct: 52 },
|
|
28
|
+
{ label: 'Docs site', pct: 70 },
|
|
29
|
+
{ label: 'Enterprise platform', pct: 100 },
|
|
30
|
+
],
|
|
31
|
+
} = Astro.props;
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<section class="scale-section">
|
|
35
|
+
<div class="container">
|
|
36
|
+
<div class="inner">
|
|
37
|
+
<div class="copy">
|
|
38
|
+
<span class="section-label on-dark">{label}</span>
|
|
39
|
+
<h2>{heading}</h2>
|
|
40
|
+
<p>{body}</p>
|
|
41
|
+
<ul>
|
|
42
|
+
{items.map(item => (
|
|
43
|
+
<li>
|
|
44
|
+
<span class="dot" aria-hidden="true"></span>
|
|
45
|
+
<span>{item}</span>
|
|
46
|
+
</li>
|
|
47
|
+
))}
|
|
48
|
+
</ul>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="scale-bars" aria-hidden="true">
|
|
51
|
+
{scale.map(row => (
|
|
52
|
+
<div class="scale-row">
|
|
53
|
+
<span class="scale-label">{row.label}</span>
|
|
54
|
+
<div class="scale-track">
|
|
55
|
+
<div class="scale-fill" style={`width: ${row.pct}%`}></div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.scale-section {
|
|
66
|
+
background: var(--color-section-dark, #0f172a);
|
|
67
|
+
padding: 88px 0;
|
|
68
|
+
}
|
|
69
|
+
.container {
|
|
70
|
+
max-width: var(--width-landing, 1100px);
|
|
71
|
+
margin: 0 auto;
|
|
72
|
+
padding: 0 24px;
|
|
73
|
+
}
|
|
74
|
+
.inner {
|
|
75
|
+
display: grid;
|
|
76
|
+
grid-template-columns: 1fr 1fr;
|
|
77
|
+
gap: 64px;
|
|
78
|
+
align-items: center;
|
|
79
|
+
}
|
|
80
|
+
.copy h2 {
|
|
81
|
+
font-size: clamp(1.5rem, 3vw, 2.25rem);
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
letter-spacing: -0.01em;
|
|
84
|
+
color: var(--color-on-dark, #e2e8f0);
|
|
85
|
+
margin: 0 0 16px;
|
|
86
|
+
}
|
|
87
|
+
.copy p {
|
|
88
|
+
color: var(--color-on-dark-2, #94a3b8);
|
|
89
|
+
font-size: 1.0625rem;
|
|
90
|
+
line-height: 1.7;
|
|
91
|
+
margin: 0 0 28px;
|
|
92
|
+
}
|
|
93
|
+
ul {
|
|
94
|
+
list-style: none;
|
|
95
|
+
padding: 0;
|
|
96
|
+
margin: 0;
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
gap: 12px;
|
|
100
|
+
}
|
|
101
|
+
ul li {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: flex-start;
|
|
104
|
+
gap: 10px;
|
|
105
|
+
color: var(--color-on-dark-2, #94a3b8);
|
|
106
|
+
font-size: 0.9375rem;
|
|
107
|
+
}
|
|
108
|
+
.dot {
|
|
109
|
+
width: 6px;
|
|
110
|
+
height: 6px;
|
|
111
|
+
border-radius: 50%;
|
|
112
|
+
background: var(--color-accent, #6366f1);
|
|
113
|
+
flex-shrink: 0;
|
|
114
|
+
margin-top: 8px;
|
|
115
|
+
}
|
|
116
|
+
.scale-bars {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
gap: 20px;
|
|
120
|
+
}
|
|
121
|
+
.scale-row {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: 6px;
|
|
125
|
+
}
|
|
126
|
+
.scale-label {
|
|
127
|
+
font-size: 0.8125rem;
|
|
128
|
+
color: var(--color-on-dark-2, #94a3b8);
|
|
129
|
+
font-weight: 500;
|
|
130
|
+
}
|
|
131
|
+
.scale-track {
|
|
132
|
+
height: 8px;
|
|
133
|
+
background: rgba(255, 255, 255, 0.08);
|
|
134
|
+
border-radius: 999px;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
}
|
|
137
|
+
.scale-fill {
|
|
138
|
+
height: 100%;
|
|
139
|
+
background: linear-gradient(90deg, var(--color-accent, #6366f1), var(--color-accent-2, #8b5cf6));
|
|
140
|
+
border-radius: 999px;
|
|
141
|
+
}
|
|
142
|
+
@media (max-width: 768px) {
|
|
143
|
+
.inner { grid-template-columns: 1fr; gap: 40px; }
|
|
144
|
+
.scale-bars { display: none; }
|
|
145
|
+
}
|
|
146
|
+
</style>
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AstroIntegration } from 'astro';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
3
4
|
|
|
4
5
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
5
6
|
|
|
@@ -10,7 +11,7 @@ function buildIntegration(): AstroIntegration {
|
|
|
10
11
|
return {
|
|
11
12
|
name: '@karaoke-cms/theme-default',
|
|
12
13
|
hooks: {
|
|
13
|
-
'astro:config:setup': ({ injectRoute, updateConfig }) => {
|
|
14
|
+
'astro:config:setup': ({ injectRoute, updateConfig, config: astroConfig }) => {
|
|
14
15
|
// Blog routes are owned by @karaoke-cms/module-blog.
|
|
15
16
|
// theme-default injects them here so that the old string-based theme
|
|
16
17
|
// config (theme: '@karaoke-cms/theme-default') continues to work.
|
|
@@ -19,8 +20,12 @@ function buildIntegration(): AstroIntegration {
|
|
|
19
20
|
injectRoute({ pattern: '/blog', entrypoint: '@karaoke-cms/module-blog/pages/list' });
|
|
20
21
|
injectRoute({ pattern: '/blog/[slug]', entrypoint: '@karaoke-cms/module-blog/pages/post' });
|
|
21
22
|
|
|
22
|
-
// Docs, tags, and homepage remain owned by theme-default
|
|
23
|
-
|
|
23
|
+
// Docs, tags, and homepage remain owned by theme-default.
|
|
24
|
+
// Skip injecting '/' if the user already has src/pages/index.astro.
|
|
25
|
+
const userIndex = fileURLToPath(new URL('src/pages/index.astro', astroConfig.root));
|
|
26
|
+
if (!existsSync(userIndex)) {
|
|
27
|
+
injectRoute({ pattern: '/', entrypoint: `${__dirname}pages/index.astro` });
|
|
28
|
+
}
|
|
24
29
|
injectRoute({ pattern: '/docs', entrypoint: `${__dirname}pages/docs/index.astro` });
|
|
25
30
|
injectRoute({ pattern: '/docs/[slug]', entrypoint: `${__dirname}pages/docs/[slug].astro` });
|
|
26
31
|
injectRoute({ pattern: '/tags', entrypoint: `${__dirname}pages/tags/index.astro` });
|
package/src/pages/404.astro
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
|
|
3
3
|
import { siteTitle } from 'virtual:karaoke-cms/config';
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<
|
|
6
|
+
<DefaultPage title={`Page not found — ${siteTitle}`}>
|
|
7
7
|
<div class="post-header">
|
|
8
8
|
<h1>Page not found</h1>
|
|
9
9
|
<p class="post-meta">The page you're looking for doesn't exist or hasn't been published.</p>
|
|
@@ -11,4 +11,4 @@ import { siteTitle } from 'virtual:karaoke-cms/config';
|
|
|
11
11
|
<div class="prose">
|
|
12
12
|
<p><a href="/">Go home →</a></p>
|
|
13
13
|
</div>
|
|
14
|
-
</
|
|
14
|
+
</DefaultPage>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection, render } from 'astro:content';
|
|
3
|
-
import
|
|
3
|
+
import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
|
|
4
4
|
import ModuleLoader from '@karaoke-cms/astro/components/ModuleLoader.astro';
|
|
5
5
|
import { siteTitle } from 'virtual:karaoke-cms/config';
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ const related = relatedIds.length > 0
|
|
|
27
27
|
: [];
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
<
|
|
30
|
+
<DefaultPage title={`${entry.data.title} — ${siteTitle}`} description={entry.data.description} type="article">
|
|
31
31
|
<article>
|
|
32
32
|
<div class="post-header">
|
|
33
33
|
<h1>{entry.data.title}</h1>
|
|
@@ -63,4 +63,4 @@ const related = relatedIds.length > 0
|
|
|
63
63
|
</div>
|
|
64
64
|
</article>
|
|
65
65
|
<ModuleLoader comments={entry.data.comments} />
|
|
66
|
-
</
|
|
66
|
+
</DefaultPage>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
|
-
import
|
|
3
|
+
import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
|
|
4
4
|
import { siteTitle } from 'virtual:karaoke-cms/config';
|
|
5
5
|
|
|
6
6
|
const docs = (await getCollection('docs', ({ data }) => data.publish === true))
|
|
7
7
|
.sort((a, b) => a.data.title.localeCompare(b.data.title));
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
<
|
|
10
|
+
<DefaultPage title={`Docs — ${siteTitle}`}>
|
|
11
11
|
<div class="listing-header">
|
|
12
12
|
<h1>Docs</h1>
|
|
13
13
|
</div>
|
|
@@ -28,4 +28,4 @@ const docs = (await getCollection('docs', ({ data }) => data.publish === true))
|
|
|
28
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
29
|
</div>
|
|
30
30
|
)}
|
|
31
|
-
</
|
|
31
|
+
</DefaultPage>
|
package/src/pages/index.astro
CHANGED
|
@@ -1,65 +1,23 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
3
|
-
import Base from '@karaoke-cms/astro/layouts/Base.astro';
|
|
2
|
+
import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
|
|
4
3
|
import { siteTitle, siteDescription } from 'virtual:karaoke-cms/config';
|
|
4
|
+
import Hero from '../components/Hero.astro';
|
|
5
|
+
import MetaphorSection from '../components/MetaphorSection.astro';
|
|
6
|
+
import AudienceGrid from '../components/AudienceGrid.astro';
|
|
7
|
+
import FeatureGrid from '../components/FeatureGrid.astro';
|
|
8
|
+
import HowItWorks from '../components/HowItWorks.astro';
|
|
9
|
+
import ScaleSection from '../components/ScaleSection.astro';
|
|
10
|
+
import CtaSection from '../components/CtaSection.astro';
|
|
5
11
|
|
|
6
|
-
const
|
|
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);
|
|
12
|
+
const ghUrl = 'https://github.com/Tautai-Net/karaoke-cms';
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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>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>docs/</code> with <code>publish: true</code> in the frontmatter.</p>
|
|
61
|
-
</div>
|
|
62
|
-
)}
|
|
63
|
-
</section>
|
|
64
|
-
</div>
|
|
65
|
-
</Base>
|
|
15
|
+
<DefaultPage title={siteTitle} description={siteDescription} variant="landing">
|
|
16
|
+
<Hero ghUrl={ghUrl} />
|
|
17
|
+
<MetaphorSection />
|
|
18
|
+
<AudienceGrid />
|
|
19
|
+
<FeatureGrid />
|
|
20
|
+
<HowItWorks />
|
|
21
|
+
<ScaleSection />
|
|
22
|
+
<CtaSection ghUrl={ghUrl} />
|
|
23
|
+
</DefaultPage>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
|
-
import
|
|
3
|
+
import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
|
|
4
4
|
import { siteTitle } from 'virtual:karaoke-cms/config';
|
|
5
5
|
|
|
6
6
|
export async function getStaticPaths() {
|
|
@@ -34,7 +34,7 @@ function href(entry: { collection: string; id: string }) {
|
|
|
34
34
|
}
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
<
|
|
37
|
+
<DefaultPage title={`#${tag} — ${siteTitle}`}>
|
|
38
38
|
<div class="listing-header">
|
|
39
39
|
<h1>#{tag}</h1>
|
|
40
40
|
<p><a href="/tags">← All tags</a></p>
|
|
@@ -50,4 +50,4 @@ function href(entry: { collection: string; id: string }) {
|
|
|
50
50
|
</li>
|
|
51
51
|
))}
|
|
52
52
|
</ul>
|
|
53
|
-
</
|
|
53
|
+
</DefaultPage>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
|
-
import
|
|
3
|
+
import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
|
|
4
4
|
import { siteTitle } from 'virtual:karaoke-cms/config';
|
|
5
5
|
|
|
6
6
|
const [blog, docs] = await Promise.all([
|
|
@@ -19,7 +19,7 @@ for (const entry of [...blog, ...docs]) {
|
|
|
19
19
|
const tags = [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
<
|
|
22
|
+
<DefaultPage title={`Tags — ${siteTitle}`}>
|
|
23
23
|
<div class="listing-header">
|
|
24
24
|
<h1>Tags</h1>
|
|
25
25
|
</div>
|
|
@@ -38,4 +38,4 @@ const tags = [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
|
38
38
|
<p>Tags are added to posts via the <code>tags</code> frontmatter field or by AI enrichment.</p>
|
|
39
39
|
</div>
|
|
40
40
|
)}
|
|
41
|
-
</
|
|
41
|
+
</DefaultPage>
|