@beatzball/create-litro 0.1.4 → 0.2.1

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/recipes/11ty-blog/template/e2e/index.spec.ts +19 -0
  3. package/dist/recipes/11ty-blog/template/package.json +4 -2
  4. package/dist/recipes/11ty-blog/template/playwright.config.ts +20 -0
  5. package/dist/recipes/fullstack/template/e2e/index.spec.ts +19 -0
  6. package/dist/recipes/fullstack/template/package.json +4 -2
  7. package/dist/recipes/fullstack/template/playwright.config.ts +20 -0
  8. package/dist/recipes/starlight/recipe.config.d.ts +4 -0
  9. package/dist/recipes/starlight/recipe.config.d.ts.map +1 -0
  10. package/dist/recipes/starlight/recipe.config.js +9 -0
  11. package/dist/recipes/starlight/recipe.config.js.map +1 -0
  12. package/dist/recipes/starlight/recipe.config.ts +11 -0
  13. package/dist/recipes/starlight/template/_data/metadata.js +10 -0
  14. package/dist/recipes/starlight/template/app.ts +18 -0
  15. package/dist/recipes/starlight/template/content/blog/.11tydata.json +1 -0
  16. package/dist/recipes/starlight/template/content/blog/release-notes.md +44 -0
  17. package/dist/recipes/starlight/template/content/blog/welcome.md +44 -0
  18. package/dist/recipes/starlight/template/content/docs/.11tydata.json +1 -0
  19. package/dist/recipes/starlight/template/content/docs/configuration.md +77 -0
  20. package/dist/recipes/starlight/template/content/docs/getting-started.md +53 -0
  21. package/dist/recipes/starlight/template/content/docs/guides-deploying.md +79 -0
  22. package/dist/recipes/starlight/template/content/docs/guides-first-page.md +64 -0
  23. package/dist/recipes/starlight/template/content/docs/installation.md +54 -0
  24. package/dist/recipes/starlight/template/e2e/index.spec.ts +32 -0
  25. package/dist/recipes/starlight/template/litro.recipe.json +7 -0
  26. package/dist/recipes/starlight/template/nitro.config.ts +57 -0
  27. package/dist/recipes/starlight/template/package.json +28 -0
  28. package/dist/recipes/starlight/template/pages/blog/[slug].ts +125 -0
  29. package/dist/recipes/starlight/template/pages/blog/index.ts +114 -0
  30. package/dist/recipes/starlight/template/pages/blog/tags/[tag].ts +110 -0
  31. package/dist/recipes/starlight/template/pages/docs/[slug].ts +147 -0
  32. package/dist/recipes/starlight/template/pages/index.ts +135 -0
  33. package/dist/recipes/starlight/template/playwright.config.ts +20 -0
  34. package/dist/recipes/starlight/template/public/styles/starlight.css +215 -0
  35. package/dist/recipes/starlight/template/server/middleware/vite-dev.ts +29 -0
  36. package/dist/recipes/starlight/template/server/routes/[...].ts +57 -0
  37. package/dist/recipes/starlight/template/server/starlight.config.js +29 -0
  38. package/dist/recipes/starlight/template/src/components/sl-aside.ts +91 -0
  39. package/dist/recipes/starlight/template/src/components/sl-badge.ts +76 -0
  40. package/dist/recipes/starlight/template/src/components/sl-card-grid.ts +34 -0
  41. package/dist/recipes/starlight/template/src/components/sl-card.ts +91 -0
  42. package/dist/recipes/starlight/template/src/components/sl-tab-item.ts +35 -0
  43. package/dist/recipes/starlight/template/src/components/sl-tabs.ts +108 -0
  44. package/dist/recipes/starlight/template/src/components/starlight-header.ts +152 -0
  45. package/dist/recipes/starlight/template/src/components/starlight-page.ts +168 -0
  46. package/dist/recipes/starlight/template/src/components/starlight-sidebar.ts +125 -0
  47. package/dist/recipes/starlight/template/src/components/starlight-toc.ts +133 -0
  48. package/dist/recipes/starlight/template/src/date-utils.ts +20 -0
  49. package/dist/recipes/starlight/template/src/extract-headings.ts +68 -0
  50. package/dist/recipes/starlight/template/src/route-meta.ts +16 -0
  51. package/dist/recipes/starlight/template/tsconfig.json +14 -0
  52. package/dist/recipes/starlight/template/vite.config.ts +19 -0
  53. package/dist/src/scaffold.test.js +134 -0
  54. package/dist/src/scaffold.test.js.map +1 -1
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +1 -1
  57. package/recipes/11ty-blog/template/e2e/index.spec.ts +19 -0
  58. package/recipes/11ty-blog/template/package.json +4 -2
  59. package/recipes/11ty-blog/template/playwright.config.ts +20 -0
  60. package/recipes/fullstack/template/e2e/index.spec.ts +19 -0
  61. package/recipes/fullstack/template/package.json +4 -2
  62. package/recipes/fullstack/template/playwright.config.ts +20 -0
  63. package/recipes/starlight/recipe.config.ts +11 -0
  64. package/recipes/starlight/template/_data/metadata.js +10 -0
  65. package/recipes/starlight/template/app.ts +18 -0
  66. package/recipes/starlight/template/content/blog/.11tydata.json +1 -0
  67. package/recipes/starlight/template/content/blog/release-notes.md +44 -0
  68. package/recipes/starlight/template/content/blog/welcome.md +44 -0
  69. package/recipes/starlight/template/content/docs/.11tydata.json +1 -0
  70. package/recipes/starlight/template/content/docs/configuration.md +77 -0
  71. package/recipes/starlight/template/content/docs/getting-started.md +53 -0
  72. package/recipes/starlight/template/content/docs/guides-deploying.md +79 -0
  73. package/recipes/starlight/template/content/docs/guides-first-page.md +64 -0
  74. package/recipes/starlight/template/content/docs/installation.md +54 -0
  75. package/recipes/starlight/template/e2e/index.spec.ts +32 -0
  76. package/recipes/starlight/template/litro.recipe.json +7 -0
  77. package/recipes/starlight/template/nitro.config.ts +57 -0
  78. package/recipes/starlight/template/package.json +28 -0
  79. package/recipes/starlight/template/pages/blog/[slug].ts +125 -0
  80. package/recipes/starlight/template/pages/blog/index.ts +114 -0
  81. package/recipes/starlight/template/pages/blog/tags/[tag].ts +110 -0
  82. package/recipes/starlight/template/pages/docs/[slug].ts +147 -0
  83. package/recipes/starlight/template/pages/index.ts +135 -0
  84. package/recipes/starlight/template/playwright.config.ts +20 -0
  85. package/recipes/starlight/template/public/styles/starlight.css +215 -0
  86. package/recipes/starlight/template/server/middleware/vite-dev.ts +29 -0
  87. package/recipes/starlight/template/server/routes/[...].ts +57 -0
  88. package/recipes/starlight/template/server/starlight.config.js +29 -0
  89. package/recipes/starlight/template/src/components/sl-aside.ts +91 -0
  90. package/recipes/starlight/template/src/components/sl-badge.ts +76 -0
  91. package/recipes/starlight/template/src/components/sl-card-grid.ts +34 -0
  92. package/recipes/starlight/template/src/components/sl-card.ts +91 -0
  93. package/recipes/starlight/template/src/components/sl-tab-item.ts +35 -0
  94. package/recipes/starlight/template/src/components/sl-tabs.ts +108 -0
  95. package/recipes/starlight/template/src/components/starlight-header.ts +152 -0
  96. package/recipes/starlight/template/src/components/starlight-page.ts +168 -0
  97. package/recipes/starlight/template/src/components/starlight-sidebar.ts +125 -0
  98. package/recipes/starlight/template/src/components/starlight-toc.ts +133 -0
  99. package/recipes/starlight/template/src/date-utils.ts +20 -0
  100. package/recipes/starlight/template/src/extract-headings.ts +68 -0
  101. package/recipes/starlight/template/src/route-meta.ts +16 -0
  102. package/recipes/starlight/template/tsconfig.json +14 -0
  103. package/recipes/starlight/template/vite.config.ts +19 -0
  104. package/src/scaffold.test.ts +148 -0
  105. package/vitest.config.ts +10 -0
@@ -0,0 +1,135 @@
1
+ import { html } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+ import { LitroPage } from '@beatzball/litro/runtime';
4
+ import { definePageData } from '@beatzball/litro';
5
+ import { getGlobalData } from 'litro:content';
6
+ import { siteConfig } from '../server/starlight.config.js';
7
+ import { starlightHead } from '../src/route-meta.js';
8
+
9
+ // Register components used in render()
10
+ import '../src/components/starlight-header.js';
11
+ import '../src/components/sl-card.js';
12
+ import '../src/components/sl-card-grid.js';
13
+
14
+ export interface SplashData {
15
+ siteTitle: string;
16
+ description: string;
17
+ nav: Array<{ label: string; href: string }>;
18
+ features: Array<{ title: string; description: string; icon?: string }>;
19
+ }
20
+
21
+ export const pageData = definePageData(async (_event) => {
22
+ const metadata = await getGlobalData();
23
+ return {
24
+ siteTitle: String(metadata.title ?? siteConfig.title),
25
+ description: String(metadata.description ?? siteConfig.description),
26
+ nav: siteConfig.nav,
27
+ features: [
28
+ {
29
+ icon: '📄',
30
+ title: 'Docs',
31
+ description: 'Structured documentation with sidebar, TOC, and prev/next navigation.',
32
+ },
33
+ {
34
+ icon: '✍️',
35
+ title: 'Blog',
36
+ description: 'Write posts in Markdown. Tags, dates, and listing pages auto-generated.',
37
+ },
38
+ {
39
+ icon: '🎨',
40
+ title: 'Theming',
41
+ description: 'Light and dark mode via CSS custom properties. Zero JavaScript required.',
42
+ },
43
+ {
44
+ icon: '⚡',
45
+ title: 'Static',
46
+ description: 'Pre-rendered to plain HTML. Deploy to any CDN with no server required.',
47
+ },
48
+ ],
49
+ } satisfies SplashData;
50
+ });
51
+
52
+ export const routeMeta = {
53
+ head: starlightHead,
54
+ title: '{{projectName}}',
55
+ };
56
+
57
+ @customElement('page-home')
58
+ export class SplashPage extends LitroPage {
59
+ override render() {
60
+ const data = this.serverData as SplashData | null;
61
+ const { siteTitle = '{{projectName}}', description = '', nav = [], features = [] } = data ?? {};
62
+
63
+ return html`
64
+ <div style="min-height:100vh;display:flex;flex-direction:column;">
65
+ <starlight-header
66
+ siteTitle="${siteTitle}"
67
+ .nav="${nav}"
68
+ currentPath="/"
69
+ ></starlight-header>
70
+ <main style="
71
+ flex:1;
72
+ max-width:56rem;
73
+ margin:0 auto;
74
+ padding:4rem 1.5rem 3rem;
75
+ width:100%;
76
+ ">
77
+ <section style="text-align:center;margin-bottom:4rem;">
78
+ <h1 style="
79
+ font-size:clamp(2rem,5vw,3.5rem);
80
+ font-weight:800;
81
+ color:var(--sl-color-text);
82
+ margin:0 0 1rem;
83
+ line-height:1.1;
84
+ ">${siteTitle}</h1>
85
+ ${description ? html`
86
+ <p style="
87
+ font-size:var(--sl-text-xl);
88
+ color:var(--sl-color-gray-4);
89
+ max-width:36rem;
90
+ margin:0 auto 2.5rem;
91
+ line-height:1.6;
92
+ ">${description}</p>
93
+ ` : ''}
94
+ <div style="display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;">
95
+ <a href="/docs/getting-started" style="
96
+ display:inline-block;
97
+ padding:0.6rem 1.5rem;
98
+ background:var(--sl-color-accent);
99
+ color:var(--sl-color-text-invert,#fff);
100
+ border-radius:var(--sl-border-radius);
101
+ font-weight:600;
102
+ text-decoration:none;
103
+ font-size:var(--sl-text-base);
104
+ ">Get Started</a>
105
+ <a href="/blog" style="
106
+ display:inline-block;
107
+ padding:0.6rem 1.5rem;
108
+ border:1px solid var(--sl-color-border);
109
+ color:var(--sl-color-text);
110
+ border-radius:var(--sl-border-radius);
111
+ font-weight:600;
112
+ text-decoration:none;
113
+ font-size:var(--sl-text-base);
114
+ ">Blog</a>
115
+ </div>
116
+ </section>
117
+
118
+ <section>
119
+ <sl-card-grid>
120
+ ${features.map(f => html`
121
+ <sl-card
122
+ icon="${f.icon ?? ''}"
123
+ title="${f.title}"
124
+ description="${f.description}"
125
+ ></sl-card>
126
+ `)}
127
+ </sl-card-grid>
128
+ </section>
129
+ </main>
130
+ </div>
131
+ `;
132
+ }
133
+ }
134
+
135
+ export default SplashPage;
@@ -0,0 +1,20 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ export default defineConfig({
4
+ testDir: './e2e',
5
+ fullyParallel: false,
6
+ retries: process.env.CI ? 2 : 0,
7
+ workers: 1,
8
+ reporter: 'html',
9
+ use: {
10
+ baseURL: 'http://localhost:3030',
11
+ trace: 'on-first-retry',
12
+ },
13
+ projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
14
+ webServer: {
15
+ command: 'pnpm dev',
16
+ url: 'http://localhost:3030',
17
+ reuseExistingServer: !process.env.CI,
18
+ timeout: 60000,
19
+ },
20
+ });
@@ -0,0 +1,215 @@
1
+ /* Starlight design tokens — light mode defaults */
2
+ :root {
3
+ --sl-color-white: #fff;
4
+ --sl-color-black: #23262f;
5
+ --sl-color-gray-1: #f6f6f6;
6
+ --sl-color-gray-2: #e8e8e8;
7
+ --sl-color-gray-3: #bdbdbd;
8
+ --sl-color-gray-4: #757575;
9
+ --sl-color-gray-5: #4b4b4b;
10
+ --sl-color-gray-6: #2a2a2a;
11
+
12
+ --sl-color-accent: #7c3aed;
13
+ --sl-color-accent-high: #5b21b6;
14
+ --sl-color-accent-low: #ede9fe;
15
+
16
+ --sl-color-note: #1d4ed8;
17
+ --sl-color-tip: #15803d;
18
+ --sl-color-caution: #b45309;
19
+ --sl-color-danger: #b91c1c;
20
+
21
+ --sl-color-text: var(--sl-color-black);
22
+ --sl-color-text-accent: var(--sl-color-accent);
23
+ --sl-color-text-invert: var(--sl-color-white);
24
+ --sl-color-bg: var(--sl-color-white);
25
+ --sl-color-bg-sidebar: var(--sl-color-gray-1);
26
+ --sl-color-bg-nav: var(--sl-color-white);
27
+ --sl-color-bg-inline-code: var(--sl-color-gray-2);
28
+ --sl-color-border: var(--sl-color-gray-2);
29
+
30
+ --sl-font-sans: ui-sans-serif, system-ui, -apple-system, sans-serif;
31
+ --sl-font-mono: ui-monospace, "Cascadia Code", "Fira Code", monospace;
32
+
33
+ --sl-text-xs: 0.75rem;
34
+ --sl-text-sm: 0.875rem;
35
+ --sl-text-base: 1rem;
36
+ --sl-text-lg: 1.125rem;
37
+ --sl-text-xl: 1.25rem;
38
+ --sl-text-2xl: 1.5rem;
39
+ --sl-text-3xl: 1.875rem;
40
+ --sl-text-4xl: 2.25rem;
41
+
42
+ --sl-nav-height: 3.5rem;
43
+ --sl-sidebar-width: 16rem;
44
+ --sl-toc-width: 14rem;
45
+ --sl-content-width: 48rem;
46
+ --sl-content-pad-x: 1.5rem;
47
+ --sl-content-pad-y: 2rem;
48
+
49
+ --sl-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
50
+ --sl-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
51
+
52
+ --sl-border-radius: 0.375rem;
53
+ --sl-border-radius-sm: 0.25rem;
54
+ }
55
+
56
+ /* Dark mode overrides */
57
+ [data-theme='dark'] {
58
+ --sl-color-text: #f0f0f0;
59
+ --sl-color-bg: #17181c;
60
+ --sl-color-bg-sidebar: #1e1f24;
61
+ --sl-color-bg-nav: #17181c;
62
+ --sl-color-bg-inline-code: #2a2b30;
63
+ --sl-color-border: #2e2f35;
64
+
65
+ --sl-color-gray-1: #2a2b30;
66
+ --sl-color-gray-2: #35363d;
67
+ --sl-color-gray-3: #5a5b63;
68
+ --sl-color-gray-4: #8a8b94;
69
+ --sl-color-gray-5: #c0c1cb;
70
+ --sl-color-gray-6: #e8e9f0;
71
+
72
+ --sl-color-accent: #a78bfa;
73
+ --sl-color-accent-high: #c4b5fd;
74
+ --sl-color-accent-low: #2e1b54;
75
+
76
+ --sl-color-note: #60a5fa;
77
+ --sl-color-tip: #4ade80;
78
+ --sl-color-caution: #fbbf24;
79
+ --sl-color-danger: #f87171;
80
+ }
81
+
82
+ /* Base reset */
83
+ *, *::before, *::after {
84
+ box-sizing: border-box;
85
+ }
86
+
87
+ html {
88
+ font-family: var(--sl-font-sans);
89
+ font-size: var(--sl-text-base);
90
+ color: var(--sl-color-text);
91
+ background-color: var(--sl-color-bg);
92
+ -webkit-font-smoothing: antialiased;
93
+ scroll-behavior: smooth;
94
+ }
95
+
96
+ body {
97
+ margin: 0;
98
+ min-height: 100vh;
99
+ }
100
+
101
+ /* Typography reset for content areas */
102
+ h1, h2, h3, h4, h5, h6 {
103
+ margin-top: 1.5em;
104
+ margin-bottom: 0.5em;
105
+ font-weight: 600;
106
+ line-height: 1.25;
107
+ color: var(--sl-color-text);
108
+ }
109
+
110
+ h1 { font-size: var(--sl-text-4xl); }
111
+ h2 { font-size: var(--sl-text-2xl); border-bottom: 1px solid var(--sl-color-border); padding-bottom: 0.25em; }
112
+ h3 { font-size: var(--sl-text-xl); }
113
+ h4 { font-size: var(--sl-text-lg); }
114
+
115
+ p {
116
+ margin-top: 0;
117
+ margin-bottom: 1rem;
118
+ line-height: 1.7;
119
+ }
120
+
121
+ a {
122
+ color: var(--sl-color-text-accent);
123
+ text-decoration: none;
124
+ }
125
+
126
+ a:hover {
127
+ text-decoration: underline;
128
+ }
129
+
130
+ code {
131
+ font-family: var(--sl-font-mono);
132
+ font-size: 0.875em;
133
+ background-color: var(--sl-color-bg-inline-code);
134
+ border: 1px solid var(--sl-color-border);
135
+ border-radius: var(--sl-border-radius-sm);
136
+ padding: 0.15em 0.4em;
137
+ }
138
+
139
+ pre {
140
+ background-color: var(--sl-color-gray-6);
141
+ color: var(--sl-color-gray-1);
142
+ border-radius: var(--sl-border-radius);
143
+ padding: 1rem 1.25rem;
144
+ overflow-x: auto;
145
+ margin: 1.5rem 0;
146
+ font-size: var(--sl-text-sm);
147
+ line-height: 1.6;
148
+ }
149
+
150
+ [data-theme='dark'] pre {
151
+ background-color: #0d0e11;
152
+ color: #e2e4e9;
153
+ }
154
+
155
+ pre code {
156
+ background: none;
157
+ border: none;
158
+ padding: 0;
159
+ font-size: inherit;
160
+ }
161
+
162
+ table {
163
+ width: 100%;
164
+ border-collapse: collapse;
165
+ margin: 1.5rem 0;
166
+ font-size: var(--sl-text-sm);
167
+ }
168
+
169
+ th, td {
170
+ border: 1px solid var(--sl-color-border);
171
+ padding: 0.5rem 0.75rem;
172
+ text-align: left;
173
+ }
174
+
175
+ th {
176
+ background-color: var(--sl-color-gray-1);
177
+ font-weight: 600;
178
+ }
179
+
180
+ [data-theme='dark'] th {
181
+ background-color: var(--sl-color-gray-1);
182
+ }
183
+
184
+ ul, ol {
185
+ padding-left: 1.5rem;
186
+ margin: 0 0 1rem;
187
+ }
188
+
189
+ li {
190
+ margin-bottom: 0.25rem;
191
+ line-height: 1.7;
192
+ }
193
+
194
+ blockquote {
195
+ margin: 1.5rem 0;
196
+ padding: 0.75rem 1rem;
197
+ border-left: 4px solid var(--sl-color-accent);
198
+ background-color: var(--sl-color-accent-low);
199
+ border-radius: 0 var(--sl-border-radius) var(--sl-border-radius) 0;
200
+ }
201
+
202
+ [data-theme='dark'] blockquote {
203
+ background-color: var(--sl-color-accent-low);
204
+ }
205
+
206
+ hr {
207
+ border: none;
208
+ border-top: 1px solid var(--sl-color-border);
209
+ margin: 2rem 0;
210
+ }
211
+
212
+ img {
213
+ max-width: 100%;
214
+ height: auto;
215
+ }
@@ -0,0 +1,29 @@
1
+ import { defineEventHandler, fromNodeMiddleware } from 'h3';
2
+
3
+ let viteHandlerPromise: Promise<ReturnType<typeof fromNodeMiddleware>> | null = null;
4
+
5
+ export default defineEventHandler(async (event) => {
6
+ if (!process.dev || !process.env.NITRO_DEV_WORKER_ID) return;
7
+
8
+ if (!viteHandlerPromise) {
9
+ const httpServer = (event.node.req.socket as import('node:net').Socket & {
10
+ server?: import('node:http').Server;
11
+ }).server;
12
+
13
+ viteHandlerPromise = import('vite')
14
+ .then(({ createServer }) =>
15
+ createServer({
16
+ server: {
17
+ middlewareMode: true,
18
+ hmr: httpServer ? { server: httpServer } : true,
19
+ },
20
+ appType: 'custom',
21
+ root: process.cwd(),
22
+ }),
23
+ )
24
+ .then((server) => fromNodeMiddleware(server.middlewares));
25
+ }
26
+
27
+ const viteHandler = await viteHandlerPromise;
28
+ return viteHandler(event);
29
+ });
@@ -0,0 +1,57 @@
1
+ import { defineEventHandler, setResponseHeader, getRequestURL } from 'h3';
2
+ import { createPageHandler } from '@beatzball/litro/runtime/create-page-handler.js';
3
+ import type { LitroRoute } from '@beatzball/litro';
4
+ import { routes, pageModules } from '#litro/page-manifest';
5
+
6
+ function matchRoute(
7
+ pathname: string,
8
+ ): { route: LitroRoute; params: Record<string, string> } | undefined {
9
+ for (const route of routes) {
10
+ if (route.isCatchAll) return { route, params: {} };
11
+
12
+ if (!route.isDynamic) {
13
+ if (pathname === route.path) return { route, params: {} };
14
+ continue;
15
+ }
16
+
17
+ const regexStr =
18
+ '^' +
19
+ route.path
20
+ .replace(/:([^/]+)\(\.\*\)\*/g, '(?<$1>.+)')
21
+ .replace(/:([^/?]+)\?/g, '(?<$1>[^/]*)?')
22
+ .replace(/:([^/]+)/g, '(?<$1>[^/]+)') +
23
+ '$';
24
+
25
+ try {
26
+ const match = pathname.match(new RegExp(regexStr));
27
+ if (match) return { route, params: (match.groups ?? {}) as Record<string, string> };
28
+ } catch {
29
+ // malformed pattern — skip
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+
35
+ export default defineEventHandler(async (event) => {
36
+ const pathname = getRequestURL(event).pathname;
37
+ const result = matchRoute(pathname);
38
+
39
+ if (!result) {
40
+ setResponseHeader(event, 'content-type', 'text/html; charset=utf-8');
41
+ return `<!DOCTYPE html>
42
+ <html lang="en"><head><meta charset="UTF-8" /><title>404</title></head>
43
+ <body><h1>404 — Not Found</h1><p>No page matched <code>${pathname}</code>.</p></body>
44
+ </html>`;
45
+ }
46
+
47
+ const { route: matched, params } = result;
48
+ event.context.params = { ...event.context.params, ...params };
49
+
50
+ const mod = pageModules[matched.filePath];
51
+ const handler = createPageHandler({
52
+ route: matched,
53
+ routeMeta: (mod?.routeMeta as { title?: string; head?: string } | undefined),
54
+ pageModule: mod,
55
+ });
56
+ return handler(event);
57
+ });
@@ -0,0 +1,29 @@
1
+ export const siteConfig = {
2
+ title: '{{projectName}}',
3
+ description: 'Documentation and blog powered by Litro',
4
+ logo: null,
5
+ editUrlBase: null, // e.g. 'https://github.com/you/repo/edit/main'
6
+ nav: [
7
+ { label: 'Docs', href: '/docs/getting-started' },
8
+ { label: 'Blog', href: '/blog' },
9
+ ],
10
+ sidebar: [
11
+ {
12
+ label: 'Start Here',
13
+ items: [
14
+ { label: 'Getting Started', slug: 'getting-started' },
15
+ { label: 'Installation', slug: 'installation' },
16
+ { label: 'Configuration', slug: 'configuration' },
17
+ ],
18
+ },
19
+ {
20
+ label: 'Guides',
21
+ items: [
22
+ { label: 'Your First Page', slug: 'guides-first-page' },
23
+ { label: 'Deploying', slug: 'guides-deploying' },
24
+ ],
25
+ },
26
+ ],
27
+ };
28
+
29
+ export default siteConfig;
@@ -0,0 +1,91 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ type AsideType = 'note' | 'tip' | 'caution' | 'danger';
5
+
6
+ const ICONS: Record<AsideType, string> = {
7
+ note: '&#x2139;&#xFE0F;', // ℹ️
8
+ tip: '&#x1F4A1;', // 💡
9
+ caution: '&#x26A0;&#xFE0F;', // ⚠️
10
+ danger: '&#x1F6A8;', // 🚨
11
+ };
12
+
13
+ const LABELS: Record<AsideType, string> = {
14
+ note: 'Note',
15
+ tip: 'Tip',
16
+ caution: 'Caution',
17
+ danger: 'Danger',
18
+ };
19
+
20
+ /**
21
+ * <sl-aside type="tip" title="Custom Title">
22
+ * Callout box with an icon and colored left border.
23
+ * Slot content is the body.
24
+ * </sl-aside>
25
+ */
26
+ @customElement('sl-aside')
27
+ export class SlAside extends LitElement {
28
+ static override properties = {
29
+ type: { type: String },
30
+ title: { type: String },
31
+ };
32
+
33
+ static override styles = css`
34
+ :host {
35
+ display: block;
36
+ }
37
+
38
+ .aside {
39
+ margin: 1.5rem 0;
40
+ padding: 1rem 1.25rem;
41
+ border-left: 4px solid;
42
+ border-radius: 0 var(--sl-border-radius, 0.375rem) var(--sl-border-radius, 0.375rem) 0;
43
+ }
44
+
45
+ .aside.note { border-color: var(--sl-color-note, #1d4ed8); background-color: color-mix(in srgb, var(--sl-color-note, #1d4ed8) 8%, transparent); }
46
+ .aside.tip { border-color: var(--sl-color-tip, #15803d); background-color: color-mix(in srgb, var(--sl-color-tip, #15803d) 8%, transparent); }
47
+ .aside.caution{ border-color: var(--sl-color-caution, #b45309); background-color: color-mix(in srgb, var(--sl-color-caution, #b45309) 8%, transparent); }
48
+ .aside.danger { border-color: var(--sl-color-danger, #b91c1c); background-color: color-mix(in srgb, var(--sl-color-danger, #b91c1c) 8%, transparent); }
49
+
50
+ .aside-title {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 0.4em;
54
+ font-size: var(--sl-text-sm, 0.875rem);
55
+ font-weight: 700;
56
+ text-transform: uppercase;
57
+ letter-spacing: 0.06em;
58
+ margin-bottom: 0.5rem;
59
+ }
60
+
61
+ .aside-title .icon { font-style: normal; }
62
+
63
+ .aside.note .aside-title { color: var(--sl-color-note, #1d4ed8); }
64
+ .aside.tip .aside-title { color: var(--sl-color-tip, #15803d); }
65
+ .aside.caution .aside-title { color: var(--sl-color-caution, #b45309); }
66
+ .aside.danger .aside-title { color: var(--sl-color-danger, #b91c1c); }
67
+
68
+ ::slotted(p:last-child) { margin-bottom: 0; }
69
+ `;
70
+
71
+ type: AsideType = 'note';
72
+ title = '';
73
+
74
+ override render() {
75
+ const t = (['note', 'tip', 'caution', 'danger'].includes(this.type)
76
+ ? this.type
77
+ : 'note') as AsideType;
78
+ const label = this.title || LABELS[t];
79
+ return html`
80
+ <aside class="aside ${t}">
81
+ <p class="aside-title">
82
+ <em class="icon">${ICONS[t]}</em>
83
+ ${label}
84
+ </p>
85
+ <slot></slot>
86
+ </aside>
87
+ `;
88
+ }
89
+ }
90
+
91
+ export default SlAside;
@@ -0,0 +1,76 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ type BadgeVariant = 'note' | 'tip' | 'caution' | 'danger' | 'default';
5
+
6
+ /**
7
+ * <sl-badge variant="tip" text="New">
8
+ * Inline color-coded chip. Use `text` attribute or slot content.
9
+ * </sl-badge>
10
+ */
11
+ @customElement('sl-badge')
12
+ export class SlBadge extends LitElement {
13
+ static override properties = {
14
+ variant: { type: String },
15
+ text: { type: String },
16
+ };
17
+
18
+ static override styles = css`
19
+ :host {
20
+ display: inline-flex;
21
+ }
22
+
23
+ .badge {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ padding: 0.15em 0.55em;
27
+ border-radius: 9999px;
28
+ font-size: var(--sl-text-xs, 0.75rem);
29
+ font-weight: 600;
30
+ line-height: 1.5;
31
+ text-transform: uppercase;
32
+ letter-spacing: 0.04em;
33
+ }
34
+
35
+ .badge.note {
36
+ background-color: color-mix(in srgb, var(--sl-color-note, #1d4ed8) 15%, transparent);
37
+ color: var(--sl-color-note, #1d4ed8);
38
+ }
39
+
40
+ .badge.tip {
41
+ background-color: color-mix(in srgb, var(--sl-color-tip, #15803d) 15%, transparent);
42
+ color: var(--sl-color-tip, #15803d);
43
+ }
44
+
45
+ .badge.caution {
46
+ background-color: color-mix(in srgb, var(--sl-color-caution, #b45309) 15%, transparent);
47
+ color: var(--sl-color-caution, #b45309);
48
+ }
49
+
50
+ .badge.danger {
51
+ background-color: color-mix(in srgb, var(--sl-color-danger, #b91c1c) 15%, transparent);
52
+ color: var(--sl-color-danger, #b91c1c);
53
+ }
54
+
55
+ .badge.default {
56
+ background-color: var(--sl-color-accent-low, #ede9fe);
57
+ color: var(--sl-color-accent-high, #5b21b6);
58
+ }
59
+ `;
60
+
61
+ variant: BadgeVariant = 'default';
62
+ text = '';
63
+
64
+ override render() {
65
+ const cls = ['note', 'tip', 'caution', 'danger'].includes(this.variant)
66
+ ? this.variant
67
+ : 'default';
68
+ return html`
69
+ <span class="badge ${cls}">
70
+ ${this.text || html`<slot></slot>`}
71
+ </span>
72
+ `;
73
+ }
74
+ }
75
+
76
+ export default SlBadge;
@@ -0,0 +1,34 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * <sl-card-grid>
6
+ * Responsive auto-fit grid for <sl-card> elements.
7
+ * Single slot — place <sl-card> elements directly inside.
8
+ * </sl-card-grid>
9
+ */
10
+ @customElement('sl-card-grid')
11
+ export class SlCardGrid extends LitElement {
12
+ static override styles = css`
13
+ :host {
14
+ display: block;
15
+ counter-reset: card;
16
+ }
17
+
18
+ .grid {
19
+ display: grid;
20
+ grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
21
+ gap: 1.25rem;
22
+ }
23
+ `;
24
+
25
+ override render() {
26
+ return html`
27
+ <div class="grid">
28
+ <slot></slot>
29
+ </div>
30
+ `;
31
+ }
32
+ }
33
+
34
+ export default SlCardGrid;