@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 CHANGED
@@ -1,65 +1,85 @@
1
1
  # @karaoke-cms/theme-default
2
2
 
3
- Two-column knowledge base theme for karaoke-cms. Includes blog, docs, and tags sections with a clean system-UI design.
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
- ## Where it belongs
5
+ ## Installation
6
6
 
7
- `packages/theme-default/` in the monorepo. Activated via `karaoke.config.ts`:
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
- const { KARAOKE_VAULT } = loadEnv(new URL('.', import.meta.url));
13
-
14
- export default {
15
- vault: KARAOKE_VAULT,
16
- theme: '@karaoke-cms/theme-default',
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
- `@karaoke-cms/astro` resolves the theme string to this package and loads it as a nested Astro integration at build time.
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
- ## What it does
39
+ | Option | Type | Default | Description |
40
+ |--------|------|---------|-------------|
41
+ | `implements` | `ModuleInstance[]` | `[]` | Content modules this theme integrates |
23
42
 
24
- Injects all site routes and sets the `@theme` Vite alias so that `Base.astro` can resolve `@theme/styles.css`:
43
+ ## Routes
25
44
 
26
- | Route | Page |
27
- |-------|------|
45
+ | Route | Description |
46
+ |-------|-------------|
28
47
  | `/` | Home — recent blog posts + recent docs |
29
- | `/blog` | Blog index all published posts, sorted by date |
30
- | `/blog/[slug]` | Blog post full content with related posts |
31
- | `/docs` | Docs index all published docs, sorted alphabetically |
32
- | `/docs/[slug]` | Doc page full content |
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
- ### Design system
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
- `src/styles.css` defines all tokens as CSS variables on `:root`:
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`, `--color-link-hover`, `--color-link-visited`
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), `--radius-sm`
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
- ## How to use
67
+ ## Layout regions
49
68
 
50
- Install alongside `@karaoke-cms/astro`:
69
+ Configure sidebar content in `karaoke.config.ts`:
51
70
 
52
- ```bash
53
- npm install @karaoke-cms/astro @karaoke-cms/theme-default
71
+ ```ts
72
+ layout: {
73
+ regions: {
74
+ right: { components: ['recent-posts'] },
75
+ },
76
+ }
54
77
  ```
55
78
 
56
- Set `theme: '@karaoke-cms/theme-default'` (or omit — it's the default) in `karaoke.config.ts`.
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
- ## How it changes the behavior of the system
81
+ ## What's new in 0.9.5
61
82
 
62
- - Provides the only routes that make up the site's public pages. Without a theme, nothing is served at `/`.
63
- - The `@theme` alias means `Base.astro` and all pages resolve styles and components from this package at build time. Switching themes is a one-line change in `karaoke.config.ts`.
64
- - Does not include `/docs` routes — that differentiates it from `theme-blog`. Suitable for mixed blog + documentation sites.
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.2",
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.2"
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.2"
24
+ "@karaoke-cms/astro": "^0.9.5"
25
25
  },
26
26
  "devDependencies": {
27
- "astro": "^6.0.8",
28
- "@karaoke-cms/astro": "0.9.2"
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>