@mcptoolshop/site-theme 0.1.0
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/LICENSE +21 -0
- package/README.md +127 -0
- package/cli/init.mjs +139 -0
- package/components/ApiList.astro +12 -0
- package/components/BaseLayout.astro +80 -0
- package/components/CodeCardGrid.astro +12 -0
- package/components/DataTable.astro +18 -0
- package/components/FeatureGrid.astro +12 -0
- package/components/Hero.astro +55 -0
- package/components/Section.astro +11 -0
- package/package.json +48 -0
- package/styles/theme.css +1 -0
- package/templates/astro.config.mjs.tpl +13 -0
- package/templates/global.css.tpl +2 -0
- package/templates/index.astro.tpl +32 -0
- package/templates/package.json.tpl +17 -0
- package/templates/pages.yml.tpl +50 -0
- package/templates/site-config.ts.tpl +48 -0
- package/templates/tsconfig.json.tpl +5 -0
- package/types/config.ts +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mcp-tool-shop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @mcptoolshop/site-theme
|
|
2
|
+
|
|
3
|
+
Shared Astro theme for MCP Tool Shop org repo landing pages. Dark zinc palette, Tailwind CSS v4, config-driven content.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Scaffold a new site
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @mcptoolshop/site-theme init
|
|
11
|
+
cd site && npm install
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This creates a `site/` directory with Astro + Tailwind + theme wired up, plus a GitHub Pages workflow.
|
|
16
|
+
|
|
17
|
+
### Edit your content
|
|
18
|
+
|
|
19
|
+
All page content lives in `site/src/site-config.ts`. Edit the config object to customize your landing page:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import type { SiteConfig } from '@mcptoolshop/site-theme';
|
|
23
|
+
|
|
24
|
+
export const config: SiteConfig = {
|
|
25
|
+
title: '@mcptoolshop/my-tool',
|
|
26
|
+
description: 'What my tool does.',
|
|
27
|
+
logoBadge: 'MT',
|
|
28
|
+
brandName: 'my-tool',
|
|
29
|
+
repoUrl: 'https://github.com/mcp-tool-shop-org/my-tool',
|
|
30
|
+
npmUrl: 'https://www.npmjs.com/package/@mcptoolshop/my-tool',
|
|
31
|
+
footerText: 'MIT Licensed',
|
|
32
|
+
|
|
33
|
+
hero: { /* ... */ },
|
|
34
|
+
sections: [ /* ... */ ],
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Components
|
|
39
|
+
|
|
40
|
+
Import components individually from the package:
|
|
41
|
+
|
|
42
|
+
```astro
|
|
43
|
+
---
|
|
44
|
+
import BaseLayout from '@mcptoolshop/site-theme/components/BaseLayout.astro';
|
|
45
|
+
import Hero from '@mcptoolshop/site-theme/components/Hero.astro';
|
|
46
|
+
import Section from '@mcptoolshop/site-theme/components/Section.astro';
|
|
47
|
+
import FeatureGrid from '@mcptoolshop/site-theme/components/FeatureGrid.astro';
|
|
48
|
+
import DataTable from '@mcptoolshop/site-theme/components/DataTable.astro';
|
|
49
|
+
import CodeCardGrid from '@mcptoolshop/site-theme/components/CodeCardGrid.astro';
|
|
50
|
+
import ApiList from '@mcptoolshop/site-theme/components/ApiList.astro';
|
|
51
|
+
---
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### BaseLayout
|
|
55
|
+
|
|
56
|
+
Full page shell with header (logo, nav, GitHub/npm buttons) and footer.
|
|
57
|
+
|
|
58
|
+
| Prop | Type | Description |
|
|
59
|
+
|------|------|-------------|
|
|
60
|
+
| `title` | `string` | Page `<title>` |
|
|
61
|
+
| `description` | `string` | Meta description |
|
|
62
|
+
| `logoBadge` | `string` | 1-2 char logo badge (e.g. "RS") |
|
|
63
|
+
| `brandName` | `string` | Name in header |
|
|
64
|
+
| `nav` | `{ href, label }[]` | Anchor nav links |
|
|
65
|
+
| `repoUrl` | `string` | GitHub repo URL |
|
|
66
|
+
| `npmUrl?` | `string` | npm package URL |
|
|
67
|
+
| `footerText` | `string` | Footer left text (HTML allowed) |
|
|
68
|
+
|
|
69
|
+
### Hero
|
|
70
|
+
|
|
71
|
+
Gradient hero section with status badge, headline, CTAs, and code preview cards.
|
|
72
|
+
|
|
73
|
+
| Prop | Type | Description |
|
|
74
|
+
|------|------|-------------|
|
|
75
|
+
| `badge` | `string` | Status badge text |
|
|
76
|
+
| `headline` | `string` | Main headline |
|
|
77
|
+
| `headlineAccent` | `string` | Muted suffix |
|
|
78
|
+
| `description` | `string` | Description (HTML allowed) |
|
|
79
|
+
| `primaryCta` | `{ href, label }` | Primary button |
|
|
80
|
+
| `secondaryCta` | `{ href, label }` | Secondary button |
|
|
81
|
+
| `previews` | `{ label, code }[]` | Code preview cards |
|
|
82
|
+
|
|
83
|
+
### Section
|
|
84
|
+
|
|
85
|
+
Section wrapper with anchor id, heading, and optional subtitle.
|
|
86
|
+
|
|
87
|
+
### FeatureGrid
|
|
88
|
+
|
|
89
|
+
3-column grid of feature cards. Props: `features: { title, desc }[]`
|
|
90
|
+
|
|
91
|
+
### DataTable
|
|
92
|
+
|
|
93
|
+
Grid-based table. Props: `columns: string[]`, `rows: string[][]`
|
|
94
|
+
|
|
95
|
+
### CodeCardGrid
|
|
96
|
+
|
|
97
|
+
2-column grid of code block cards. Props: `cards: { title, code }[]`
|
|
98
|
+
|
|
99
|
+
### ApiList
|
|
100
|
+
|
|
101
|
+
Full-width API reference cards. Props: `apis: { signature, description }[]`
|
|
102
|
+
|
|
103
|
+
## Section Types
|
|
104
|
+
|
|
105
|
+
The `sections` array in your config supports these `kind` values:
|
|
106
|
+
|
|
107
|
+
- `features` — 3-col feature card grid
|
|
108
|
+
- `data-table` — bordered data table
|
|
109
|
+
- `code-cards` — 2-col code example cards
|
|
110
|
+
- `api` — stacked API reference cards
|
|
111
|
+
|
|
112
|
+
Sections render in the order they appear in the array.
|
|
113
|
+
|
|
114
|
+
## Tailwind Setup
|
|
115
|
+
|
|
116
|
+
The theme uses Tailwind CSS v4. Your site's `global.css` needs the `@source` directive to scan theme components:
|
|
117
|
+
|
|
118
|
+
```css
|
|
119
|
+
@import "tailwindcss";
|
|
120
|
+
@source "../../../node_modules/@mcptoolshop/site-theme";
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The `init` CLI generates this automatically.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/cli/init.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const templatesDir = join(__dirname, '..', 'templates');
|
|
9
|
+
const cwd = process.cwd();
|
|
10
|
+
|
|
11
|
+
// --- Helpers ---
|
|
12
|
+
|
|
13
|
+
function die(msg) {
|
|
14
|
+
console.error(`\x1b[31mError:\x1b[0m ${msg}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function info(msg) {
|
|
19
|
+
console.log(`\x1b[36m>\x1b[0m ${msg}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function deriveBadge(name) {
|
|
23
|
+
// "registry-stats" → "RS", "mcpt" → "M", "tool-compass" → "TC"
|
|
24
|
+
return name
|
|
25
|
+
.split('-')
|
|
26
|
+
.map((w) => w[0]?.toUpperCase() ?? '')
|
|
27
|
+
.join('')
|
|
28
|
+
.slice(0, 2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function unscopeName(name) {
|
|
32
|
+
// "@mcptoolshop/registry-stats" → "registry-stats"
|
|
33
|
+
return name.replace(/^@[^/]+\//, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractRepoName(repoUrl) {
|
|
37
|
+
// "https://github.com/mcp-tool-shop-org/registry-stats.git" → "registry-stats"
|
|
38
|
+
if (!repoUrl) return '';
|
|
39
|
+
return repoUrl.replace(/\.git$/, '').split('/').pop() || '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readTemplate(name) {
|
|
43
|
+
return readFileSync(join(templatesDir, name), 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function applyVars(content, vars) {
|
|
47
|
+
return content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writeFile(path, content) {
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
writeFileSync(path, content, 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --- Main ---
|
|
56
|
+
|
|
57
|
+
function main() {
|
|
58
|
+
const siteDir = join(cwd, 'site');
|
|
59
|
+
|
|
60
|
+
if (existsSync(siteDir)) {
|
|
61
|
+
die('site/ directory already exists. Remove it first or run from a different repo.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Read repo's package.json
|
|
65
|
+
const pkgPath = join(cwd, 'package.json');
|
|
66
|
+
let pkg = {};
|
|
67
|
+
if (existsSync(pkgPath)) {
|
|
68
|
+
try {
|
|
69
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
70
|
+
} catch {
|
|
71
|
+
// ignore parse errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const packageName = pkg.name || 'my-package';
|
|
76
|
+
const brandName = unscopeName(packageName);
|
|
77
|
+
const description = pkg.description || 'A tool by mcp-tool-shop';
|
|
78
|
+
const repoUrl = (typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url)
|
|
79
|
+
?.replace(/\.git$/, '')
|
|
80
|
+
?.replace(/^git\+/, '') || `https://github.com/mcp-tool-shop-org/${brandName}`;
|
|
81
|
+
const npmUrl = packageName.startsWith('@')
|
|
82
|
+
? `https://www.npmjs.com/package/${packageName}`
|
|
83
|
+
: `https://www.npmjs.com/package/${packageName}`;
|
|
84
|
+
const logoBadge = deriveBadge(brandName);
|
|
85
|
+
const basePath = `/${extractRepoName(repoUrl) || brandName}`;
|
|
86
|
+
|
|
87
|
+
const vars = {
|
|
88
|
+
PACKAGE_NAME: packageName,
|
|
89
|
+
BRAND_NAME: brandName,
|
|
90
|
+
DESCRIPTION: description,
|
|
91
|
+
REPO_URL: repoUrl,
|
|
92
|
+
NPM_URL: npmUrl,
|
|
93
|
+
LOGO_BADGE: logoBadge,
|
|
94
|
+
BASE_PATH: basePath,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
info(`Package: ${packageName}`);
|
|
98
|
+
info(`Brand: ${brandName} (${logoBadge})`);
|
|
99
|
+
info(`Base path: ${basePath}`);
|
|
100
|
+
info('');
|
|
101
|
+
|
|
102
|
+
// Create site/ files
|
|
103
|
+
const files = [
|
|
104
|
+
['astro.config.mjs', 'astro.config.mjs.tpl'],
|
|
105
|
+
['package.json', 'package.json.tpl'],
|
|
106
|
+
['tsconfig.json', 'tsconfig.json.tpl'],
|
|
107
|
+
['src/styles/global.css', 'global.css.tpl'],
|
|
108
|
+
['src/pages/index.astro', 'index.astro.tpl'],
|
|
109
|
+
['src/site-config.ts', 'site-config.ts.tpl'],
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
for (const [dest, tpl] of files) {
|
|
113
|
+
const content = applyVars(readTemplate(tpl), vars);
|
|
114
|
+
writeFile(join(siteDir, dest), content);
|
|
115
|
+
info(`Created site/${dest}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Create .github/workflows/pages.yml
|
|
119
|
+
const workflowDir = join(cwd, '.github', 'workflows');
|
|
120
|
+
const workflowDest = join(workflowDir, 'pages.yml');
|
|
121
|
+
if (!existsSync(workflowDest)) {
|
|
122
|
+
const content = applyVars(readTemplate('pages.yml.tpl'), vars);
|
|
123
|
+
writeFile(workflowDest, content);
|
|
124
|
+
info('Created .github/workflows/pages.yml');
|
|
125
|
+
} else {
|
|
126
|
+
info('Skipped .github/workflows/pages.yml (already exists)');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log('\x1b[32mDone!\x1b[0m Site scaffold created.');
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log('Next steps:');
|
|
133
|
+
console.log(` cd site && npm install`);
|
|
134
|
+
console.log(` Edit src/site-config.ts with your content`);
|
|
135
|
+
console.log(` npm run dev`);
|
|
136
|
+
console.log('');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
main();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { apis } = Astro.props;
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<div class="mt-8 grid gap-6">
|
|
6
|
+
{apis.map((a: { signature: string; description: string }) => (
|
|
7
|
+
<div class="rounded-2xl border border-zinc-900 bg-zinc-950 p-6">
|
|
8
|
+
<h3 class="text-lg font-semibold"><code set:html={a.signature} /></h3>
|
|
9
|
+
<p class="mt-2 text-sm text-zinc-400" set:html={a.description} />
|
|
10
|
+
</div>
|
|
11
|
+
))}
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
const {
|
|
3
|
+
title,
|
|
4
|
+
description,
|
|
5
|
+
logoBadge,
|
|
6
|
+
brandName,
|
|
7
|
+
nav = [],
|
|
8
|
+
repoUrl,
|
|
9
|
+
npmUrl,
|
|
10
|
+
footerText,
|
|
11
|
+
} = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<!doctype html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="utf-8" />
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
19
|
+
<meta name="description" content={description} />
|
|
20
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
21
|
+
<title>{title}</title>
|
|
22
|
+
</head>
|
|
23
|
+
|
|
24
|
+
<body class="min-h-screen bg-zinc-950 text-zinc-50 antialiased">
|
|
25
|
+
<div class="mx-auto max-w-6xl px-4">
|
|
26
|
+
<header class="flex items-center justify-between py-6">
|
|
27
|
+
<a href="/" class="flex items-center gap-2 font-semibold tracking-tight">
|
|
28
|
+
<span class="inline-flex h-8 w-8 items-center justify-center rounded-xl bg-zinc-800">
|
|
29
|
+
{logoBadge}
|
|
30
|
+
</span>
|
|
31
|
+
<span>{brandName}</span>
|
|
32
|
+
</a>
|
|
33
|
+
|
|
34
|
+
<nav class="hidden items-center gap-6 text-sm text-zinc-300 md:flex">
|
|
35
|
+
{nav.map((item: { href: string; label: string }) => (
|
|
36
|
+
<a href={item.href} class="hover:text-zinc-50">{item.label}</a>
|
|
37
|
+
))}
|
|
38
|
+
</nav>
|
|
39
|
+
|
|
40
|
+
<div class="flex items-center gap-3">
|
|
41
|
+
{npmUrl && (
|
|
42
|
+
<a
|
|
43
|
+
class="hidden rounded-xl border border-zinc-800 px-4 py-2 text-sm text-zinc-200 hover:bg-zinc-900 md:inline-flex"
|
|
44
|
+
href={npmUrl}
|
|
45
|
+
target="_blank"
|
|
46
|
+
rel="noreferrer"
|
|
47
|
+
>
|
|
48
|
+
npm
|
|
49
|
+
</a>
|
|
50
|
+
)}
|
|
51
|
+
<a
|
|
52
|
+
class="inline-flex rounded-xl bg-zinc-50 px-4 py-2 text-sm font-medium text-zinc-950 hover:bg-zinc-200"
|
|
53
|
+
href={repoUrl}
|
|
54
|
+
target="_blank"
|
|
55
|
+
rel="noreferrer"
|
|
56
|
+
>
|
|
57
|
+
GitHub
|
|
58
|
+
</a>
|
|
59
|
+
</div>
|
|
60
|
+
</header>
|
|
61
|
+
|
|
62
|
+
<main class="pb-24">
|
|
63
|
+
<slot />
|
|
64
|
+
</main>
|
|
65
|
+
|
|
66
|
+
<footer class="border-t border-zinc-900 py-10 text-sm text-zinc-400">
|
|
67
|
+
<div class="flex flex-col justify-between gap-4 md:flex-row">
|
|
68
|
+
<p set:html={footerText} />
|
|
69
|
+
<div class="flex gap-4">
|
|
70
|
+
{npmUrl && (
|
|
71
|
+
<a class="hover:text-zinc-200" href={npmUrl} target="_blank" rel="noreferrer">npm</a>
|
|
72
|
+
)}
|
|
73
|
+
<a class="hover:text-zinc-200" href={repoUrl} target="_blank" rel="noreferrer">GitHub</a>
|
|
74
|
+
<a class="hover:text-zinc-200" href="https://opensource.org/license/mit/" target="_blank" rel="noreferrer">License</a>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</footer>
|
|
78
|
+
</div>
|
|
79
|
+
</body>
|
|
80
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { cards } = Astro.props;
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<div class="mt-6 grid gap-6 md:grid-cols-2">
|
|
6
|
+
{cards.map((c: { title: string; code: string }) => (
|
|
7
|
+
<div class="rounded-2xl border border-zinc-900 bg-zinc-950 p-6">
|
|
8
|
+
<h3 class="text-lg font-semibold">{c.title}</h3>
|
|
9
|
+
<pre class="mt-4 overflow-x-auto rounded-xl bg-zinc-900 p-4 text-sm text-zinc-200"><code set:html={c.code} /></pre>
|
|
10
|
+
</div>
|
|
11
|
+
))}
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { columns, rows } = Astro.props;
|
|
3
|
+
const colCount = columns.length;
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<div class="mt-8 overflow-hidden rounded-2xl border border-zinc-900">
|
|
7
|
+
<div class={`grid gap-0 bg-zinc-950 text-xs text-zinc-400`} style={`grid-template-columns: repeat(${colCount}, minmax(0, 1fr))`}>
|
|
8
|
+
{columns.map((col: string) => (
|
|
9
|
+
<div class="border-b border-zinc-900 p-4">{col}</div>
|
|
10
|
+
))}
|
|
11
|
+
|
|
12
|
+
{rows.map((row: string[]) => (
|
|
13
|
+
row.map((cell: string, i: number) => (
|
|
14
|
+
<div class={`border-b border-zinc-900 p-4${i === 0 ? ' font-medium text-zinc-200' : ''}`}>{cell}</div>
|
|
15
|
+
))
|
|
16
|
+
))}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { features } = Astro.props;
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<div class="mt-8 grid gap-6 md:grid-cols-3">
|
|
6
|
+
{features.map((f: { title: string; desc: string }) => (
|
|
7
|
+
<div class="rounded-2xl border border-zinc-900 bg-zinc-950 p-6">
|
|
8
|
+
<h3 class="text-lg font-semibold">{f.title}</h3>
|
|
9
|
+
<p class="mt-2 text-sm text-zinc-400">{f.desc}</p>
|
|
10
|
+
</div>
|
|
11
|
+
))}
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
const {
|
|
3
|
+
badge,
|
|
4
|
+
headline,
|
|
5
|
+
headlineAccent,
|
|
6
|
+
description,
|
|
7
|
+
primaryCta,
|
|
8
|
+
secondaryCta,
|
|
9
|
+
previews = [],
|
|
10
|
+
} = Astro.props;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<section class="relative overflow-hidden rounded-3xl border border-zinc-800 bg-gradient-to-b from-zinc-900/60 to-zinc-950 px-6 py-16 md:px-12">
|
|
14
|
+
<div class="absolute inset-0 opacity-40 [background:radial-gradient(900px_circle_at_20%_20%,rgba(255,255,255,0.10),transparent_60%),radial-gradient(700px_circle_at_80%_30%,rgba(255,255,255,0.06),transparent_60%)]"></div>
|
|
15
|
+
|
|
16
|
+
<div class="relative">
|
|
17
|
+
<div class="inline-flex items-center gap-2 rounded-full border border-zinc-800 bg-zinc-950/60 px-3 py-1 text-xs text-zinc-300">
|
|
18
|
+
<span class="h-2 w-2 rounded-full bg-emerald-400"></span>
|
|
19
|
+
<span set:html={badge} />
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<h1 class="mt-6 text-4xl font-semibold tracking-tight md:text-6xl">
|
|
23
|
+
{headline}
|
|
24
|
+
<span class="text-zinc-300"> {headlineAccent}</span>
|
|
25
|
+
</h1>
|
|
26
|
+
|
|
27
|
+
<p class="mt-4 max-w-2xl text-lg text-zinc-300" set:html={description} />
|
|
28
|
+
|
|
29
|
+
<div class="mt-8 flex flex-col gap-3 sm:flex-row">
|
|
30
|
+
<a
|
|
31
|
+
href={primaryCta.href}
|
|
32
|
+
class="inline-flex items-center justify-center rounded-xl bg-zinc-50 px-5 py-3 text-sm font-medium text-zinc-950 hover:bg-zinc-200"
|
|
33
|
+
>
|
|
34
|
+
{primaryCta.label}
|
|
35
|
+
</a>
|
|
36
|
+
<a
|
|
37
|
+
href={secondaryCta.href}
|
|
38
|
+
class="inline-flex items-center justify-center rounded-xl border border-zinc-800 px-5 py-3 text-sm text-zinc-200 hover:bg-zinc-900"
|
|
39
|
+
>
|
|
40
|
+
{secondaryCta.label}
|
|
41
|
+
</a>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
{previews.length > 0 && (
|
|
45
|
+
<div class="mt-10 grid gap-4 md:grid-cols-3">
|
|
46
|
+
{previews.map((p: { label: string; code: string }) => (
|
|
47
|
+
<div class="rounded-2xl border border-zinc-800 bg-zinc-950/60 p-5">
|
|
48
|
+
<p class="text-xs text-zinc-400">{p.label}</p>
|
|
49
|
+
<p class="mt-2 text-sm text-zinc-200"><code>{p.code}</code></p>
|
|
50
|
+
</div>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { id, title, subtitle } = Astro.props;
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<section id={id} class="mt-20">
|
|
6
|
+
<h2 class="text-3xl font-semibold tracking-tight">{title}</h2>
|
|
7
|
+
{subtitle && (
|
|
8
|
+
<p class="mt-2 max-w-2xl text-zinc-400">{subtitle}</p>
|
|
9
|
+
)}
|
|
10
|
+
<slot />
|
|
11
|
+
</section>
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcptoolshop/site-theme",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Shared Astro theme for MCP Tool Shop org repo landing pages",
|
|
6
|
+
"keywords": ["astro-component", "withastro", "theme", "mcp"],
|
|
7
|
+
"author": "mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/mcp-tool-shop-org/site-theme.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/mcp-tool-shop-org/site-theme",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./types/config.ts",
|
|
16
|
+
"./components/BaseLayout.astro": "./components/BaseLayout.astro",
|
|
17
|
+
"./components/Hero.astro": "./components/Hero.astro",
|
|
18
|
+
"./components/Section.astro": "./components/Section.astro",
|
|
19
|
+
"./components/FeatureGrid.astro": "./components/FeatureGrid.astro",
|
|
20
|
+
"./components/DataTable.astro": "./components/DataTable.astro",
|
|
21
|
+
"./components/CodeCardGrid.astro": "./components/CodeCardGrid.astro",
|
|
22
|
+
"./components/ApiList.astro": "./components/ApiList.astro"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"components",
|
|
26
|
+
"styles",
|
|
27
|
+
"types",
|
|
28
|
+
"templates",
|
|
29
|
+
"cli",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"bin": {
|
|
34
|
+
"site-theme": "./cli/init.mjs"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"registry": "https://registry.npmjs.org"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"astro": ">=5.0.0",
|
|
42
|
+
"tailwindcss": ">=4.0.0",
|
|
43
|
+
"@tailwindcss/vite": ">=4.0.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/styles/theme.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { defineConfig } from 'astro/config';
|
|
3
|
+
|
|
4
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
5
|
+
|
|
6
|
+
// https://astro.build/config
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
site: 'https://mcp-tool-shop-org.github.io',
|
|
9
|
+
base: '{{BASE_PATH}}',
|
|
10
|
+
vite: {
|
|
11
|
+
plugins: [tailwindcss()]
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import BaseLayout from '@mcptoolshop/site-theme/components/BaseLayout.astro';
|
|
3
|
+
import Hero from '@mcptoolshop/site-theme/components/Hero.astro';
|
|
4
|
+
import Section from '@mcptoolshop/site-theme/components/Section.astro';
|
|
5
|
+
import FeatureGrid from '@mcptoolshop/site-theme/components/FeatureGrid.astro';
|
|
6
|
+
import DataTable from '@mcptoolshop/site-theme/components/DataTable.astro';
|
|
7
|
+
import CodeCardGrid from '@mcptoolshop/site-theme/components/CodeCardGrid.astro';
|
|
8
|
+
import ApiList from '@mcptoolshop/site-theme/components/ApiList.astro';
|
|
9
|
+
import { config } from '../site-config';
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<BaseLayout
|
|
13
|
+
title={config.title}
|
|
14
|
+
description={config.description}
|
|
15
|
+
logoBadge={config.logoBadge}
|
|
16
|
+
brandName={config.brandName}
|
|
17
|
+
repoUrl={config.repoUrl}
|
|
18
|
+
npmUrl={config.npmUrl}
|
|
19
|
+
footerText={config.footerText}
|
|
20
|
+
nav={config.sections.map(s => ({ href: `#${s.id}`, label: s.title }))}
|
|
21
|
+
>
|
|
22
|
+
<Hero {...config.hero} />
|
|
23
|
+
|
|
24
|
+
{config.sections.map((s) => (
|
|
25
|
+
<Section id={s.id} title={s.title} subtitle={s.subtitle}>
|
|
26
|
+
{s.kind === 'features' && <FeatureGrid features={s.features} />}
|
|
27
|
+
{s.kind === 'data-table' && <DataTable columns={s.columns} rows={s.rows} />}
|
|
28
|
+
{s.kind === 'code-cards' && <CodeCardGrid cards={s.cards} />}
|
|
29
|
+
{s.kind === 'api' && <ApiList apis={s.apis} />}
|
|
30
|
+
</Section>
|
|
31
|
+
))}
|
|
32
|
+
</BaseLayout>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "site",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"private": true,
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "astro dev",
|
|
8
|
+
"build": "astro build",
|
|
9
|
+
"preview": "astro preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@mcptoolshop/site-theme": "^0.1.0",
|
|
13
|
+
"@tailwindcss/vite": "^4.2.0",
|
|
14
|
+
"astro": "^5.17.0",
|
|
15
|
+
"tailwindcss": "^4.2.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Deploy site to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- 'site/**'
|
|
8
|
+
- '.github/workflows/pages.yml'
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
pages: write
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 22
|
|
29
|
+
|
|
30
|
+
- name: Install site dependencies
|
|
31
|
+
working-directory: site
|
|
32
|
+
run: npm ci
|
|
33
|
+
|
|
34
|
+
- name: Build site
|
|
35
|
+
working-directory: site
|
|
36
|
+
run: npm run build
|
|
37
|
+
|
|
38
|
+
- uses: actions/upload-pages-artifact@v3
|
|
39
|
+
with:
|
|
40
|
+
path: site/dist
|
|
41
|
+
|
|
42
|
+
deploy:
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment:
|
|
46
|
+
name: github-pages
|
|
47
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
48
|
+
steps:
|
|
49
|
+
- id: deployment
|
|
50
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SiteConfig } from '@mcptoolshop/site-theme';
|
|
2
|
+
|
|
3
|
+
export const config: SiteConfig = {
|
|
4
|
+
title: '{{PACKAGE_NAME}}',
|
|
5
|
+
description: '{{DESCRIPTION}}',
|
|
6
|
+
logoBadge: '{{LOGO_BADGE}}',
|
|
7
|
+
brandName: '{{BRAND_NAME}}',
|
|
8
|
+
repoUrl: '{{REPO_URL}}',
|
|
9
|
+
npmUrl: '{{NPM_URL}}',
|
|
10
|
+
footerText: 'MIT Licensed',
|
|
11
|
+
|
|
12
|
+
hero: {
|
|
13
|
+
badge: 'Open source',
|
|
14
|
+
headline: '{{BRAND_NAME}}',
|
|
15
|
+
headlineAccent: 'by mcp-tool-shop.',
|
|
16
|
+
description: '{{DESCRIPTION}}',
|
|
17
|
+
primaryCta: { href: '#usage', label: 'Get started' },
|
|
18
|
+
secondaryCta: { href: '#features', label: 'Learn more' },
|
|
19
|
+
previews: [
|
|
20
|
+
{ label: 'Install', code: 'npm install {{PACKAGE_NAME}}' },
|
|
21
|
+
{ label: 'Import', code: "import { ... } from '{{PACKAGE_NAME}}'" },
|
|
22
|
+
{ label: 'Use', code: '// your code here' },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
sections: [
|
|
27
|
+
{
|
|
28
|
+
kind: 'features',
|
|
29
|
+
id: 'features',
|
|
30
|
+
title: 'Features',
|
|
31
|
+
subtitle: 'What makes {{BRAND_NAME}} useful.',
|
|
32
|
+
features: [
|
|
33
|
+
{ title: 'Feature one', desc: 'Describe the first feature.' },
|
|
34
|
+
{ title: 'Feature two', desc: 'Describe the second feature.' },
|
|
35
|
+
{ title: 'Feature three', desc: 'Describe the third feature.' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
kind: 'code-cards',
|
|
40
|
+
id: 'usage',
|
|
41
|
+
title: 'Usage',
|
|
42
|
+
cards: [
|
|
43
|
+
{ title: 'Install', code: 'npm install {{PACKAGE_NAME}}' },
|
|
44
|
+
{ title: 'Basic usage', code: "import { ... } from '{{PACKAGE_NAME}}';\n\n// your code here" },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
package/types/config.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export interface SiteConfig {
|
|
2
|
+
/** Page <title>, e.g. "@mcptoolshop/registry-stats" */
|
|
3
|
+
title: string;
|
|
4
|
+
/** Meta description */
|
|
5
|
+
description: string;
|
|
6
|
+
/** 1-2 char logo badge, e.g. "RS" */
|
|
7
|
+
logoBadge: string;
|
|
8
|
+
/** Brand name in header, e.g. "registry-stats" */
|
|
9
|
+
brandName: string;
|
|
10
|
+
/** GitHub repo URL */
|
|
11
|
+
repoUrl: string;
|
|
12
|
+
/** npm package URL (omit to hide npm header button) */
|
|
13
|
+
npmUrl?: string;
|
|
14
|
+
/** Footer left-side text, e.g. "MIT Licensed" */
|
|
15
|
+
footerText: string;
|
|
16
|
+
|
|
17
|
+
/** Hero section */
|
|
18
|
+
hero: HeroDef;
|
|
19
|
+
|
|
20
|
+
/** Ordered page sections (rendered top-to-bottom) */
|
|
21
|
+
sections: SectionDef[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface HeroDef {
|
|
25
|
+
/** Status badge text */
|
|
26
|
+
badge: string;
|
|
27
|
+
/** Main headline */
|
|
28
|
+
headline: string;
|
|
29
|
+
/** Muted/accent suffix of headline */
|
|
30
|
+
headlineAccent: string;
|
|
31
|
+
/** Description paragraph (HTML allowed, rendered via set:html) */
|
|
32
|
+
description: string;
|
|
33
|
+
/** Primary CTA button */
|
|
34
|
+
primaryCta: CtaDef;
|
|
35
|
+
/** Secondary CTA button */
|
|
36
|
+
secondaryCta: CtaDef;
|
|
37
|
+
/** Code preview cards (typically 3) */
|
|
38
|
+
previews: PreviewDef[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CtaDef {
|
|
42
|
+
href: string;
|
|
43
|
+
label: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface PreviewDef {
|
|
47
|
+
label: string;
|
|
48
|
+
code: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface NavItem {
|
|
52
|
+
href: string;
|
|
53
|
+
label: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --- Section variants ---
|
|
57
|
+
|
|
58
|
+
export type SectionDef =
|
|
59
|
+
| FeatureSectionDef
|
|
60
|
+
| DataTableSectionDef
|
|
61
|
+
| CodeCardSectionDef
|
|
62
|
+
| ApiSectionDef;
|
|
63
|
+
|
|
64
|
+
interface BaseSectionDef {
|
|
65
|
+
id: string;
|
|
66
|
+
title: string;
|
|
67
|
+
subtitle?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface FeatureSectionDef extends BaseSectionDef {
|
|
71
|
+
kind: 'features';
|
|
72
|
+
features: { title: string; desc: string }[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DataTableSectionDef extends BaseSectionDef {
|
|
76
|
+
kind: 'data-table';
|
|
77
|
+
columns: string[];
|
|
78
|
+
rows: string[][];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CodeCardSectionDef extends BaseSectionDef {
|
|
82
|
+
kind: 'code-cards';
|
|
83
|
+
cards: { title: string; code: string }[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ApiSectionDef extends BaseSectionDef {
|
|
87
|
+
kind: 'api';
|
|
88
|
+
apis: { signature: string; description: string }[];
|
|
89
|
+
}
|