@karaoke-cms/theme-default 0.9.2 → 0.9.5
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/README.md +56 -36
- package/package.json +6 -6
- 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 +31 -21
- 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 +124 -18
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Step {
|
|
3
|
+
num: string;
|
|
4
|
+
title: string;
|
|
5
|
+
body: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
label?: string;
|
|
10
|
+
heading?: string;
|
|
11
|
+
steps?: Step[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
label = 'How it works',
|
|
16
|
+
heading = 'Three steps from note to live site',
|
|
17
|
+
steps = [
|
|
18
|
+
{
|
|
19
|
+
num: '01',
|
|
20
|
+
title: 'Write in Obsidian',
|
|
21
|
+
body: 'Open your vault. Write notes. Add publish: true to any file you want public. Everything else stays private.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
num: '02',
|
|
25
|
+
title: 'Commit to git',
|
|
26
|
+
body: 'The pre-commit hook runs automatically. AI enriches your content. Stage, commit, push — the hook handles enrichment silently.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
num: '03',
|
|
30
|
+
title: "It's live",
|
|
31
|
+
body: 'CI builds your Astro site, runs the privacy gate, and deploys to Cloudflare Pages. Your site is live. Private content stays private.',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
} = Astro.props;
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
<section class="how">
|
|
38
|
+
<div class="container">
|
|
39
|
+
<span class="section-label">{label}</span>
|
|
40
|
+
<h2>{heading}</h2>
|
|
41
|
+
<div class="steps">
|
|
42
|
+
{steps.map((step, i) => (
|
|
43
|
+
<>
|
|
44
|
+
{i > 0 && <span class="step-arrow" aria-hidden="true">→</span>}
|
|
45
|
+
<div class="step">
|
|
46
|
+
<div class="step-num" aria-hidden="true">{step.num}</div>
|
|
47
|
+
<h4>{step.title}</h4>
|
|
48
|
+
<p>{step.body}</p>
|
|
49
|
+
</div>
|
|
50
|
+
</>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.how {
|
|
58
|
+
background: var(--color-bg-alt, #f8fafc);
|
|
59
|
+
border-top: 1px solid var(--color-border);
|
|
60
|
+
padding: 88px 0;
|
|
61
|
+
}
|
|
62
|
+
.container {
|
|
63
|
+
max-width: var(--width-landing, 1100px);
|
|
64
|
+
margin: 0 auto;
|
|
65
|
+
padding: 0 24px;
|
|
66
|
+
}
|
|
67
|
+
h2 {
|
|
68
|
+
font-size: clamp(1.5rem, 3vw, 2.25rem);
|
|
69
|
+
font-weight: 700;
|
|
70
|
+
letter-spacing: -0.01em;
|
|
71
|
+
color: var(--color-text);
|
|
72
|
+
margin: 0 0 56px;
|
|
73
|
+
}
|
|
74
|
+
.steps {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: flex-start;
|
|
77
|
+
gap: 16px;
|
|
78
|
+
}
|
|
79
|
+
.step {
|
|
80
|
+
flex: 1;
|
|
81
|
+
background: var(--color-bg);
|
|
82
|
+
border: 1px solid var(--color-border);
|
|
83
|
+
border-radius: var(--radius-lg, 12px);
|
|
84
|
+
padding: 32px;
|
|
85
|
+
}
|
|
86
|
+
.step-num {
|
|
87
|
+
font-size: 2rem;
|
|
88
|
+
font-weight: 800;
|
|
89
|
+
color: var(--color-accent, #6366f1);
|
|
90
|
+
opacity: 0.4;
|
|
91
|
+
margin-bottom: 12px;
|
|
92
|
+
font-variant-numeric: tabular-nums;
|
|
93
|
+
letter-spacing: -0.03em;
|
|
94
|
+
}
|
|
95
|
+
.step h4 {
|
|
96
|
+
font-size: 1.05rem;
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
color: var(--color-text);
|
|
99
|
+
margin: 0 0 8px;
|
|
100
|
+
}
|
|
101
|
+
.step p {
|
|
102
|
+
font-size: 0.9rem;
|
|
103
|
+
color: var(--color-muted);
|
|
104
|
+
line-height: 1.6;
|
|
105
|
+
margin: 0;
|
|
106
|
+
}
|
|
107
|
+
.step-arrow {
|
|
108
|
+
color: var(--color-border);
|
|
109
|
+
font-size: 1.5rem;
|
|
110
|
+
padding-top: 40px;
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
}
|
|
113
|
+
@media (max-width: 640px) {
|
|
114
|
+
.steps { flex-direction: column; }
|
|
115
|
+
.step-arrow { display: none; }
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -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,28 +1,37 @@
|
|
|
1
1
|
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import type { ModuleInstance } from '@karaoke-cms/astro';
|
|
2
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
3
5
|
|
|
4
6
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
5
7
|
|
|
6
8
|
// Constructs the Astro integration for theme-default.
|
|
7
9
|
// Exported separately so it can be called by both the new factory (themeDefault)
|
|
8
10
|
// and the legacy default export without triggering a @karaoke-cms/astro import.
|
|
9
|
-
|
|
11
|
+
//
|
|
12
|
+
// When `implementedModuleIds` is provided, routes for those modules are skipped —
|
|
13
|
+
// the core integration injects them from the module package instead.
|
|
14
|
+
function buildIntegration(implementedModuleIds: string[] = []): AstroIntegration {
|
|
10
15
|
return {
|
|
11
16
|
name: '@karaoke-cms/theme-default',
|
|
12
17
|
hooks: {
|
|
13
|
-
'astro:config:setup': ({ injectRoute, updateConfig }) => {
|
|
14
|
-
// Blog
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// will stop injecting these and let the core integration do it.
|
|
19
|
-
injectRoute({ pattern: '/blog', entrypoint: '@karaoke-cms/module-blog/pages/list' });
|
|
20
|
-
injectRoute({ pattern: '/blog/[slug]', entrypoint: '@karaoke-cms/module-blog/pages/post' });
|
|
18
|
+
'astro:config:setup': ({ injectRoute, updateConfig, config: astroConfig }) => {
|
|
19
|
+
// Blog post route: fall back to module-blog post page when blog is not in implements[].
|
|
20
|
+
if (!implementedModuleIds.includes('blog')) {
|
|
21
|
+
injectRoute({ pattern: '/blog/[slug]', entrypoint: '@karaoke-cms/module-blog/pages/post' });
|
|
22
|
+
}
|
|
21
23
|
|
|
22
|
-
// Docs
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// Docs routes: fall back to built-in pages when docs is not in implements[].
|
|
25
|
+
if (!implementedModuleIds.includes('docs')) {
|
|
26
|
+
injectRoute({ pattern: '/docs', entrypoint: `${__dirname}pages/docs/index.astro` });
|
|
27
|
+
injectRoute({ pattern: '/docs/[slug]', entrypoint: `${__dirname}pages/docs/[slug].astro` });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Skip injecting '/' if the user already has src/pages/index.astro.
|
|
31
|
+
const userIndex = fileURLToPath(new URL('src/pages/index.astro', astroConfig.root));
|
|
32
|
+
if (!existsSync(userIndex)) {
|
|
33
|
+
injectRoute({ pattern: '/', entrypoint: `${__dirname}pages/index.astro` });
|
|
34
|
+
}
|
|
26
35
|
injectRoute({ pattern: '/tags', entrypoint: `${__dirname}pages/tags/index.astro` });
|
|
27
36
|
injectRoute({ pattern: '/tags/[tag]', entrypoint: `${__dirname}pages/tags/[tag].astro` });
|
|
28
37
|
injectRoute({ pattern: '/404', entrypoint: `${__dirname}pages/404.astro` });
|
|
@@ -42,10 +51,6 @@ function buildIntegration(): AstroIntegration {
|
|
|
42
51
|
/**
|
|
43
52
|
* New API: returns a ThemeInstance for use with defineConfig().
|
|
44
53
|
*
|
|
45
|
-
* Note: ThemeInstance is constructed manually here (without importing defineTheme
|
|
46
|
-
* from @karaoke-cms/astro) so that this file remains loadable via Node's native
|
|
47
|
-
* ESM when used through the legacy string-based theme config path.
|
|
48
|
-
*
|
|
49
54
|
* @example
|
|
50
55
|
* // karaoke.config.ts
|
|
51
56
|
* import { themeDefault } from '@karaoke-cms/theme-default';
|
|
@@ -53,12 +58,15 @@ function buildIntegration(): AstroIntegration {
|
|
|
53
58
|
* theme: themeDefault({ implements: [blog({ mount: '/blog' })] }),
|
|
54
59
|
* });
|
|
55
60
|
*/
|
|
56
|
-
export function themeDefault(config: { implements?:
|
|
61
|
+
export function themeDefault(config: { implements?: ModuleInstance[] } = {}) {
|
|
62
|
+
const implementedModules = config.implements ?? [];
|
|
63
|
+
const implementedModuleIds = implementedModules.map(m => m.id);
|
|
57
64
|
return {
|
|
58
65
|
_type: 'theme-instance' as const,
|
|
59
66
|
id: 'theme-default',
|
|
60
|
-
implementedModuleIds
|
|
61
|
-
|
|
67
|
+
implementedModuleIds,
|
|
68
|
+
implementedModules,
|
|
69
|
+
toAstroIntegration: () => buildIntegration(implementedModuleIds),
|
|
62
70
|
};
|
|
63
71
|
}
|
|
64
72
|
|
|
@@ -67,7 +75,9 @@ export function themeDefault(config: { implements?: Array<{ id: string }> } = {}
|
|
|
67
75
|
* theme: '@karaoke-cms/theme-default'
|
|
68
76
|
* The core integration calls themeModule.default(config) when loading
|
|
69
77
|
* themes by package name string.
|
|
78
|
+
*
|
|
79
|
+
* Injects blog routes directly (legacy path — blog module not in modules[]).
|
|
70
80
|
*/
|
|
71
81
|
export default function themeDefaultLegacy(_config: unknown): AstroIntegration {
|
|
72
|
-
return buildIntegration();
|
|
82
|
+
return buildIntegration([]);
|
|
73
83
|
}
|
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>
|