@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
package/README.md
CHANGED
|
@@ -1,65 +1,85 @@
|
|
|
1
1
|
# @karaoke-cms/theme-default
|
|
2
2
|
|
|
3
|
-
Two-column knowledge base theme for karaoke-cms
|
|
3
|
+
Two-column knowledge base theme for karaoke-cms — blog, docs, and tags with a clean system-UI design and automatic dark mode.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @karaoke-cms/theme-default @karaoke-cms/module-blog @karaoke-cms/module-docs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
8
12
|
|
|
9
13
|
```ts
|
|
10
14
|
// karaoke.config.ts
|
|
15
|
+
import { defineConfig } from '@karaoke-cms/astro';
|
|
11
16
|
import { loadEnv } from '@karaoke-cms/astro/env';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
import { blog } from '@karaoke-cms/module-blog';
|
|
18
|
+
import { docs } from '@karaoke-cms/module-docs';
|
|
19
|
+
import { themeDefault } from '@karaoke-cms/theme-default';
|
|
20
|
+
|
|
21
|
+
const env = loadEnv(new URL('.', import.meta.url));
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
vault: env.KARAOKE_VAULT,
|
|
25
|
+
title: 'My Site',
|
|
26
|
+
theme: themeDefault({
|
|
27
|
+
implements: [
|
|
28
|
+
blog({ mount: '/blog' }),
|
|
29
|
+
docs({ mount: '/docs' }),
|
|
30
|
+
],
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
18
33
|
```
|
|
19
34
|
|
|
20
|
-
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Pass modules to `implements` to tell the theme which content sections to wire up. Modules listed here get their routes injected from their own npm packages.
|
|
21
38
|
|
|
22
|
-
|
|
39
|
+
| Option | Type | Default | Description |
|
|
40
|
+
|--------|------|---------|-------------|
|
|
41
|
+
| `implements` | `ModuleInstance[]` | `[]` | Content modules this theme integrates |
|
|
23
42
|
|
|
24
|
-
|
|
43
|
+
## Routes
|
|
25
44
|
|
|
26
|
-
| Route |
|
|
27
|
-
|
|
45
|
+
| Route | Description |
|
|
46
|
+
|-------|-------------|
|
|
28
47
|
| `/` | Home — recent blog posts + recent docs |
|
|
29
|
-
| `/blog` | Blog
|
|
30
|
-
| `/
|
|
31
|
-
| `/
|
|
32
|
-
| `/
|
|
33
|
-
| `/tags` | Tags index — all tags with post counts |
|
|
34
|
-
| `/tags/[tag]` | Tag page — posts filtered by tag |
|
|
48
|
+
| `/blog`, `/blog/[slug]`, `/blog/page/[page]` | Blog section (from `module-blog`) |
|
|
49
|
+
| `/docs`, `/docs/list`, `/docs/[slug]` | Docs section (from `module-docs`) |
|
|
50
|
+
| `/tags` | All tags with post counts |
|
|
51
|
+
| `/tags/[tag]` | Posts filtered by tag |
|
|
35
52
|
| `/404` | Not found page |
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
Routes for modules listed in `implements` are injected by the module package. Other routes are injected by the theme as fallbacks.
|
|
38
55
|
|
|
39
|
-
|
|
56
|
+
## Design system
|
|
57
|
+
|
|
58
|
+
All tokens are CSS variables on `:root`:
|
|
40
59
|
|
|
41
60
|
- **Typography**: `--font-body`, `--font-mono`, `--font-size-base`, `--font-size-sm/lg/xl`
|
|
42
|
-
- **Color**: `--color-bg`, `--color-text`, `--color-muted`, `--color-border`, `--color-link
|
|
61
|
+
- **Color**: `--color-bg`, `--color-text`, `--color-muted`, `--color-border`, `--color-link`
|
|
43
62
|
- **Spacing**: `--spacing-xs/sm/md/lg/xl`
|
|
44
|
-
- **Sizing**: `--width-content` (680px), `--width-site` (800px)
|
|
63
|
+
- **Sizing**: `--width-content` (680px), `--width-site` (800px)
|
|
45
64
|
|
|
46
65
|
Dark mode is automatic via `@media (prefers-color-scheme: dark)` — no JavaScript toggle.
|
|
47
66
|
|
|
48
|
-
##
|
|
67
|
+
## Layout regions
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
Configure sidebar content in `karaoke.config.ts`:
|
|
51
70
|
|
|
52
|
-
```
|
|
53
|
-
|
|
71
|
+
```ts
|
|
72
|
+
layout: {
|
|
73
|
+
regions: {
|
|
74
|
+
right: { components: ['recent-posts'] },
|
|
75
|
+
},
|
|
76
|
+
}
|
|
54
77
|
```
|
|
55
78
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Your vault needs `blog/` and/or `docs/` directories with Markdown files. Only files with `publish: true` in frontmatter appear on the site.
|
|
79
|
+
Available: `'header'`, `'main-menu'`, `'search'`, `'recent-posts'`, `'footer'`.
|
|
59
80
|
|
|
60
|
-
##
|
|
81
|
+
## What's new in 0.9.5
|
|
61
82
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
- The layout is driven by `Base.astro`'s region system — sidebar content (recent posts, search) is controlled by the `layout.regions` config in `karaoke.config.ts`, not hardcoded in the theme.
|
|
83
|
+
- **`themeDefault()` function API** replaces the old string `theme: '@karaoke-cms/theme-default'`
|
|
84
|
+
- **`implements` option** — pass `blog()` and/or `docs()` module instances to wire up content sections; routes come from the module packages
|
|
85
|
+
- No user-facing visual changes in this release
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karaoke-cms/theme-default",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.5",
|
|
5
5
|
"description": "Default theme for karaoke-cms — two-column knowledge base with blog and docs",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"karaoke-cms"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@karaoke-cms/module-blog": "0.9.
|
|
20
|
+
"@karaoke-cms/module-blog": "^0.9.5"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"astro": ">=6.0.0",
|
|
24
|
-
"@karaoke-cms/astro": "^0.9.
|
|
24
|
+
"@karaoke-cms/astro": "^0.9.5"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"astro": "
|
|
28
|
-
"
|
|
27
|
+
"@karaoke-cms/astro": "workspace:*",
|
|
28
|
+
"astro": "^6.0.8"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"test": "echo \"Stub — no tests\""
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface AudienceCard {
|
|
3
|
+
icon: string;
|
|
4
|
+
title: string;
|
|
5
|
+
body: string;
|
|
6
|
+
items: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
label?: string;
|
|
11
|
+
heading?: string;
|
|
12
|
+
cards?: AudienceCard[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
label = "Who it's for",
|
|
17
|
+
heading = 'Built for everyone who publishes',
|
|
18
|
+
cards = [
|
|
19
|
+
{
|
|
20
|
+
icon: '⚡',
|
|
21
|
+
title: 'Developers',
|
|
22
|
+
body: 'One npm install. All-in on Astro — typed config, virtual modules, zero lock-in. Ship a site in an afternoon. Bring your own theme or build one from scratch.',
|
|
23
|
+
items: [
|
|
24
|
+
'TypeScript-first configuration',
|
|
25
|
+
'Composable theme packages',
|
|
26
|
+
'Turborepo monorepo, ready to extend',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
icon: '✍️',
|
|
31
|
+
title: 'Content creators',
|
|
32
|
+
body: 'Write in Obsidian — your notes app, not a CMS dashboard. No logins, no editors, no friction. Your vault IS your CMS.',
|
|
33
|
+
items: [
|
|
34
|
+
'Publish with a single frontmatter flag',
|
|
35
|
+
'Wikilinks work natively on your live site',
|
|
36
|
+
'AI fills in descriptions and tags for you',
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
icon: '🏢',
|
|
41
|
+
title: 'Enterprise teams',
|
|
42
|
+
body: 'Git-native audit trail, private-by-default publishing, AI enrichment pipeline, and a composable module system built to scale.',
|
|
43
|
+
items: [
|
|
44
|
+
'Collections scoped per team or department',
|
|
45
|
+
'CI-enforced privacy gate',
|
|
46
|
+
'Extends to multi-author editorial workflows',
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
} = Astro.props;
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
<section class="audiences">
|
|
54
|
+
<div class="container">
|
|
55
|
+
<span class="section-label">{label}</span>
|
|
56
|
+
<h2>{heading}</h2>
|
|
57
|
+
<div class="card-grid">
|
|
58
|
+
{cards.map(card => (
|
|
59
|
+
<div class="card">
|
|
60
|
+
<div class="card-icon" aria-hidden="true">{card.icon}</div>
|
|
61
|
+
<h3>{card.title}</h3>
|
|
62
|
+
<p>{card.body}</p>
|
|
63
|
+
<ul>
|
|
64
|
+
{card.items.map(item => <li>{item}</li>)}
|
|
65
|
+
</ul>
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</section>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
.audiences {
|
|
74
|
+
background: var(--color-bg-alt, #f8fafc);
|
|
75
|
+
padding: 88px 0;
|
|
76
|
+
}
|
|
77
|
+
.container {
|
|
78
|
+
max-width: var(--width-landing, 1100px);
|
|
79
|
+
margin: 0 auto;
|
|
80
|
+
padding: 0 24px;
|
|
81
|
+
}
|
|
82
|
+
h2 {
|
|
83
|
+
font-size: clamp(1.5rem, 3vw, 2.25rem);
|
|
84
|
+
font-weight: 700;
|
|
85
|
+
letter-spacing: -0.01em;
|
|
86
|
+
color: var(--color-text);
|
|
87
|
+
margin: 0 0 48px;
|
|
88
|
+
}
|
|
89
|
+
.card-grid {
|
|
90
|
+
display: grid;
|
|
91
|
+
grid-template-columns: repeat(3, 1fr);
|
|
92
|
+
gap: 24px;
|
|
93
|
+
}
|
|
94
|
+
.card {
|
|
95
|
+
background: var(--color-bg);
|
|
96
|
+
border: 1px solid var(--color-border);
|
|
97
|
+
border-radius: var(--radius-lg, 12px);
|
|
98
|
+
padding: 32px;
|
|
99
|
+
transition: box-shadow 0.2s, transform 0.2s;
|
|
100
|
+
}
|
|
101
|
+
.card:hover {
|
|
102
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
|
|
103
|
+
transform: translateY(-2px);
|
|
104
|
+
}
|
|
105
|
+
.card-icon {
|
|
106
|
+
font-size: 2rem;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
margin-bottom: 16px;
|
|
109
|
+
}
|
|
110
|
+
.card h3 {
|
|
111
|
+
font-size: 1.125rem;
|
|
112
|
+
font-weight: 700;
|
|
113
|
+
color: var(--color-text);
|
|
114
|
+
margin: 0 0 10px;
|
|
115
|
+
}
|
|
116
|
+
.card p {
|
|
117
|
+
font-size: 0.9375rem;
|
|
118
|
+
color: var(--color-muted);
|
|
119
|
+
line-height: 1.65;
|
|
120
|
+
margin: 0 0 16px;
|
|
121
|
+
}
|
|
122
|
+
.card ul {
|
|
123
|
+
list-style: none;
|
|
124
|
+
padding: 0;
|
|
125
|
+
margin: 0;
|
|
126
|
+
}
|
|
127
|
+
.card ul li {
|
|
128
|
+
font-size: 0.875rem;
|
|
129
|
+
color: var(--color-muted);
|
|
130
|
+
padding: 4px 0 4px 18px;
|
|
131
|
+
position: relative;
|
|
132
|
+
}
|
|
133
|
+
.card ul li::before {
|
|
134
|
+
content: '·';
|
|
135
|
+
position: absolute;
|
|
136
|
+
left: 6px;
|
|
137
|
+
color: var(--color-accent, #6366f1);
|
|
138
|
+
font-weight: 700;
|
|
139
|
+
}
|
|
140
|
+
@media (max-width: 768px) {
|
|
141
|
+
.card-grid { grid-template-columns: 1fr; }
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
import InstallBox from './InstallBox.astro';
|
|
3
|
+
|
|
4
|
+
interface CtaLink {
|
|
5
|
+
href: string;
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
heading?: string;
|
|
11
|
+
sub?: string;
|
|
12
|
+
ghUrl?: string;
|
|
13
|
+
links?: CtaLink[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
heading = 'Ready to sing?',
|
|
18
|
+
sub = 'One command. Your site is running in minutes.',
|
|
19
|
+
ghUrl,
|
|
20
|
+
links = [
|
|
21
|
+
...(ghUrl ? [{ href: ghUrl, text: 'GitHub →' }] : []),
|
|
22
|
+
{ href: '/docs', text: 'Documentation →' },
|
|
23
|
+
{ href: '/blog', text: 'Blog →' },
|
|
24
|
+
],
|
|
25
|
+
} = Astro.props;
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<section class="cta-section">
|
|
29
|
+
<div class="container">
|
|
30
|
+
<h2>{heading}</h2>
|
|
31
|
+
<p>{sub}</p>
|
|
32
|
+
<InstallBox size="lg" />
|
|
33
|
+
{links.length > 0 && (
|
|
34
|
+
<div class="cta-links">
|
|
35
|
+
{links.map(link => (
|
|
36
|
+
<a href={link.href} {...(link.href.startsWith('http') ? { target: '_blank', rel: 'noopener' } : {})}>
|
|
37
|
+
{link.text}
|
|
38
|
+
</a>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<style>
|
|
46
|
+
.cta-section {
|
|
47
|
+
background: var(--color-bg);
|
|
48
|
+
border-top: 1px solid var(--color-border);
|
|
49
|
+
padding: 96px 0;
|
|
50
|
+
text-align: center;
|
|
51
|
+
}
|
|
52
|
+
.container {
|
|
53
|
+
max-width: var(--width-landing, 1100px);
|
|
54
|
+
margin: 0 auto;
|
|
55
|
+
padding: 0 24px;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 0;
|
|
60
|
+
}
|
|
61
|
+
h2 {
|
|
62
|
+
font-size: clamp(1.6rem, 3vw, 2.25rem);
|
|
63
|
+
font-weight: 700;
|
|
64
|
+
letter-spacing: -0.01em;
|
|
65
|
+
color: var(--color-text);
|
|
66
|
+
margin: 0 0 12px;
|
|
67
|
+
}
|
|
68
|
+
p {
|
|
69
|
+
font-size: 1.125rem;
|
|
70
|
+
color: var(--color-muted);
|
|
71
|
+
margin: 0 0 32px;
|
|
72
|
+
}
|
|
73
|
+
.cta-links {
|
|
74
|
+
display: flex;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
gap: 32px;
|
|
77
|
+
flex-wrap: wrap;
|
|
78
|
+
margin-top: 32px;
|
|
79
|
+
}
|
|
80
|
+
.cta-links a {
|
|
81
|
+
font-size: 0.9375rem;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
color: var(--color-muted);
|
|
84
|
+
text-decoration: none;
|
|
85
|
+
transition: color 0.15s;
|
|
86
|
+
}
|
|
87
|
+
.cta-links a:hover {
|
|
88
|
+
color: var(--color-accent, #6366f1);
|
|
89
|
+
text-decoration: none;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Feature {
|
|
3
|
+
icon: string;
|
|
4
|
+
title: string;
|
|
5
|
+
body: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
label?: string;
|
|
10
|
+
heading?: string;
|
|
11
|
+
features?: Feature[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
label = "What's included",
|
|
16
|
+
heading = 'Everything wired up, out of the box',
|
|
17
|
+
features = [
|
|
18
|
+
{
|
|
19
|
+
icon: '🔒',
|
|
20
|
+
title: 'Private by default',
|
|
21
|
+
body: 'Nothing publishes until you add publish: true. The privacy gate runs at build time and in CI — nothing slips through.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
icon: '🤖',
|
|
25
|
+
title: 'AI-native',
|
|
26
|
+
body: 'Pre-commit hook enriches every file with OpenAI or Anthropic. Descriptions, reading times, tags, related links — automatic.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
icon: '🔗',
|
|
30
|
+
title: 'Obsidian wikilinks',
|
|
31
|
+
body: '[[note]] and [[note|alias]] resolve to real links on your site. Write in Obsidian, publish what you see.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
icon: '🎨',
|
|
35
|
+
title: 'Swap themes in one line',
|
|
36
|
+
body: 'Default, blog, and minimal themes ship out of the box. Switch with one config change. Build and publish your own as an npm package.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
icon: '📡',
|
|
40
|
+
title: 'RSS · Sitemap · Search',
|
|
41
|
+
body: 'RSS feed, XML sitemap, and Pagefind full-text search are built in. No plugins to hunt down. No configuration required.',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
icon: '🚀',
|
|
45
|
+
title: 'Cloudflare Pages deploy',
|
|
46
|
+
body: 'GitHub Actions workflow included. Push to main — site builds, privacy check runs, deploys. Done.',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
} = Astro.props;
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
<section class="features">
|
|
53
|
+
<div class="container">
|
|
54
|
+
<span class="section-label">{label}</span>
|
|
55
|
+
<h2>{heading}</h2>
|
|
56
|
+
<div class="feature-grid">
|
|
57
|
+
{features.map(f => (
|
|
58
|
+
<div class="feature-item">
|
|
59
|
+
<span class="fi-icon" aria-hidden="true">{f.icon}</span>
|
|
60
|
+
<div>
|
|
61
|
+
<h4>{f.title}</h4>
|
|
62
|
+
<p>{f.body}</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</section>
|
|
69
|
+
|
|
70
|
+
<style>
|
|
71
|
+
.features {
|
|
72
|
+
background: var(--color-bg);
|
|
73
|
+
border-top: 1px solid var(--color-border);
|
|
74
|
+
padding: 88px 0;
|
|
75
|
+
}
|
|
76
|
+
.container {
|
|
77
|
+
max-width: var(--width-landing, 1100px);
|
|
78
|
+
margin: 0 auto;
|
|
79
|
+
padding: 0 24px;
|
|
80
|
+
}
|
|
81
|
+
h2 {
|
|
82
|
+
font-size: clamp(1.5rem, 3vw, 2.25rem);
|
|
83
|
+
font-weight: 700;
|
|
84
|
+
letter-spacing: -0.01em;
|
|
85
|
+
color: var(--color-text);
|
|
86
|
+
margin: 0 0 48px;
|
|
87
|
+
}
|
|
88
|
+
.feature-grid {
|
|
89
|
+
display: grid;
|
|
90
|
+
grid-template-columns: repeat(3, 1fr);
|
|
91
|
+
border: 1px solid var(--color-border);
|
|
92
|
+
border-radius: var(--radius-lg, 12px);
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
}
|
|
95
|
+
.feature-item {
|
|
96
|
+
padding: 32px;
|
|
97
|
+
border-right: 1px solid var(--color-border);
|
|
98
|
+
border-bottom: 1px solid var(--color-border);
|
|
99
|
+
display: flex;
|
|
100
|
+
gap: 16px;
|
|
101
|
+
align-items: flex-start;
|
|
102
|
+
}
|
|
103
|
+
/* Remove right border from every 3rd item */
|
|
104
|
+
.feature-item:nth-child(3n) { border-right: none; }
|
|
105
|
+
/* Remove bottom border from last row */
|
|
106
|
+
.feature-item:nth-last-child(-n+3) { border-bottom: none; }
|
|
107
|
+
.fi-icon {
|
|
108
|
+
font-size: 1.5rem;
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
line-height: 1.2;
|
|
111
|
+
}
|
|
112
|
+
.feature-item h4 {
|
|
113
|
+
font-size: 1rem;
|
|
114
|
+
font-weight: 600;
|
|
115
|
+
color: var(--color-text);
|
|
116
|
+
margin: 0 0 6px;
|
|
117
|
+
}
|
|
118
|
+
.feature-item p {
|
|
119
|
+
font-size: 0.875rem;
|
|
120
|
+
line-height: 1.6;
|
|
121
|
+
color: var(--color-muted);
|
|
122
|
+
margin: 0;
|
|
123
|
+
}
|
|
124
|
+
@media (max-width: 768px) {
|
|
125
|
+
.feature-grid {
|
|
126
|
+
grid-template-columns: 1fr;
|
|
127
|
+
border: none;
|
|
128
|
+
gap: 1px;
|
|
129
|
+
background: var(--color-border);
|
|
130
|
+
border-radius: 0;
|
|
131
|
+
}
|
|
132
|
+
.feature-item {
|
|
133
|
+
border-right: none !important;
|
|
134
|
+
border-bottom: none !important;
|
|
135
|
+
background: var(--color-bg);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
import InstallBox from './InstallBox.astro';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
badge?: string;
|
|
6
|
+
ghUrl?: string;
|
|
7
|
+
tags?: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
badge = 'Open source · Git-native · AI-ready',
|
|
12
|
+
ghUrl,
|
|
13
|
+
tags = ['Astro', 'Obsidian', 'Git', 'AI', 'TypeScript', 'Cloudflare Pages'],
|
|
14
|
+
} = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<section class="hero">
|
|
18
|
+
<div class="hero-glow" aria-hidden="true"></div>
|
|
19
|
+
<div class="hero-inner">
|
|
20
|
+
{badge && <div class="badge">{badge}</div>}
|
|
21
|
+
|
|
22
|
+
<h1><slot name="headline">All you have to do<br />is sing.</slot></h1>
|
|
23
|
+
|
|
24
|
+
<p class="hero-sub">
|
|
25
|
+
<slot name="sub">
|
|
26
|
+
karaoke-cms is a CMS framework built on Obsidian and Git.
|
|
27
|
+
Write your content. Everything else — publishing, privacy,
|
|
28
|
+
AI enrichment, navigation — is handled for you.
|
|
29
|
+
</slot>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<div class="hero-actions">
|
|
33
|
+
<InstallBox />
|
|
34
|
+
{ghUrl && (
|
|
35
|
+
<a href={ghUrl} target="_blank" rel="noopener" class="btn-ghost">
|
|
36
|
+
View on GitHub
|
|
37
|
+
</a>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{tags.length > 0 && (
|
|
42
|
+
<div class="hero-tags">
|
|
43
|
+
{tags.map(tag => <span>{tag}</span>)}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
|
|
49
|
+
<style>
|
|
50
|
+
.hero {
|
|
51
|
+
position: relative;
|
|
52
|
+
background: var(--color-section-dark, #0f172a);
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
padding: 96px 0 80px;
|
|
55
|
+
text-align: center;
|
|
56
|
+
}
|
|
57
|
+
.hero-glow {
|
|
58
|
+
position: absolute;
|
|
59
|
+
inset: 0;
|
|
60
|
+
background:
|
|
61
|
+
radial-gradient(ellipse 60% 50% at 50% 0%, rgba(99, 102, 241, 0.25) 0%, transparent 70%),
|
|
62
|
+
radial-gradient(ellipse 40% 40% at 80% 60%, rgba(139, 92, 246, 0.15) 0%, transparent 60%);
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
}
|
|
65
|
+
.hero-inner {
|
|
66
|
+
position: relative;
|
|
67
|
+
max-width: var(--width-landing, 1100px);
|
|
68
|
+
margin: 0 auto;
|
|
69
|
+
padding: 0 24px;
|
|
70
|
+
}
|
|
71
|
+
.badge {
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 6px;
|
|
75
|
+
font-size: 0.75rem;
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
letter-spacing: 0.06em;
|
|
78
|
+
text-transform: uppercase;
|
|
79
|
+
color: #a5b4fc;
|
|
80
|
+
border: 1px solid rgba(165, 180, 252, 0.3);
|
|
81
|
+
background: rgba(99, 102, 241, 0.1);
|
|
82
|
+
padding: 5px 14px;
|
|
83
|
+
border-radius: 999px;
|
|
84
|
+
margin-bottom: 28px;
|
|
85
|
+
}
|
|
86
|
+
h1 {
|
|
87
|
+
color: #fff;
|
|
88
|
+
font-size: clamp(2.4rem, 5vw, 4rem);
|
|
89
|
+
font-weight: 800;
|
|
90
|
+
letter-spacing: -0.02em;
|
|
91
|
+
line-height: 1.15;
|
|
92
|
+
margin: 0 0 20px;
|
|
93
|
+
}
|
|
94
|
+
.hero-sub {
|
|
95
|
+
max-width: 580px;
|
|
96
|
+
margin: 0 auto 36px;
|
|
97
|
+
font-size: 1.125rem;
|
|
98
|
+
color: #94a3b8;
|
|
99
|
+
line-height: 1.65;
|
|
100
|
+
}
|
|
101
|
+
.hero-actions {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
gap: 16px;
|
|
106
|
+
flex-wrap: wrap;
|
|
107
|
+
margin-bottom: 32px;
|
|
108
|
+
}
|
|
109
|
+
.btn-ghost {
|
|
110
|
+
font-size: 0.9rem;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
color: #94a3b8;
|
|
113
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
114
|
+
padding: 10px 20px;
|
|
115
|
+
border-radius: var(--radius-lg, 12px);
|
|
116
|
+
text-decoration: none;
|
|
117
|
+
transition: color 0.15s, border-color 0.15s;
|
|
118
|
+
}
|
|
119
|
+
.btn-ghost:hover {
|
|
120
|
+
color: #fff;
|
|
121
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
122
|
+
text-decoration: none;
|
|
123
|
+
}
|
|
124
|
+
.hero-tags {
|
|
125
|
+
display: flex;
|
|
126
|
+
flex-wrap: wrap;
|
|
127
|
+
justify-content: center;
|
|
128
|
+
gap: 8px;
|
|
129
|
+
}
|
|
130
|
+
.hero-tags span {
|
|
131
|
+
font-size: 0.75rem;
|
|
132
|
+
color: #475569;
|
|
133
|
+
background: rgba(255, 255, 255, 0.04);
|
|
134
|
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
135
|
+
padding: 3px 10px;
|
|
136
|
+
border-radius: 999px;
|
|
137
|
+
}
|
|
138
|
+
@media (max-width: 640px) {
|
|
139
|
+
.hero { padding: 64px 0 56px; }
|
|
140
|
+
}
|
|
141
|
+
</style>
|