@beatzball/create-litro 0.1.4 → 0.2.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/recipes/starlight/recipe.config.d.ts +4 -0
  3. package/dist/recipes/starlight/recipe.config.d.ts.map +1 -0
  4. package/dist/recipes/starlight/recipe.config.js +9 -0
  5. package/dist/recipes/starlight/recipe.config.js.map +1 -0
  6. package/dist/recipes/starlight/recipe.config.ts +11 -0
  7. package/dist/recipes/starlight/template/_data/metadata.js +10 -0
  8. package/dist/recipes/starlight/template/app.ts +18 -0
  9. package/dist/recipes/starlight/template/content/blog/.11tydata.json +1 -0
  10. package/dist/recipes/starlight/template/content/blog/release-notes.md +44 -0
  11. package/dist/recipes/starlight/template/content/blog/welcome.md +44 -0
  12. package/dist/recipes/starlight/template/content/docs/.11tydata.json +1 -0
  13. package/dist/recipes/starlight/template/content/docs/configuration.md +77 -0
  14. package/dist/recipes/starlight/template/content/docs/getting-started.md +53 -0
  15. package/dist/recipes/starlight/template/content/docs/guides-deploying.md +79 -0
  16. package/dist/recipes/starlight/template/content/docs/guides-first-page.md +64 -0
  17. package/dist/recipes/starlight/template/content/docs/installation.md +54 -0
  18. package/dist/recipes/starlight/template/litro.recipe.json +7 -0
  19. package/dist/recipes/starlight/template/nitro.config.ts +57 -0
  20. package/dist/recipes/starlight/template/package.json +26 -0
  21. package/dist/recipes/starlight/template/pages/blog/[slug].ts +125 -0
  22. package/dist/recipes/starlight/template/pages/blog/index.ts +114 -0
  23. package/dist/recipes/starlight/template/pages/blog/tags/[tag].ts +110 -0
  24. package/dist/recipes/starlight/template/pages/docs/[slug].ts +147 -0
  25. package/dist/recipes/starlight/template/pages/index.ts +135 -0
  26. package/dist/recipes/starlight/template/public/styles/starlight.css +215 -0
  27. package/dist/recipes/starlight/template/server/middleware/vite-dev.ts +29 -0
  28. package/dist/recipes/starlight/template/server/routes/[...].ts +57 -0
  29. package/dist/recipes/starlight/template/server/starlight.config.js +29 -0
  30. package/dist/recipes/starlight/template/src/components/sl-aside.ts +91 -0
  31. package/dist/recipes/starlight/template/src/components/sl-badge.ts +76 -0
  32. package/dist/recipes/starlight/template/src/components/sl-card-grid.ts +34 -0
  33. package/dist/recipes/starlight/template/src/components/sl-card.ts +91 -0
  34. package/dist/recipes/starlight/template/src/components/sl-tab-item.ts +35 -0
  35. package/dist/recipes/starlight/template/src/components/sl-tabs.ts +108 -0
  36. package/dist/recipes/starlight/template/src/components/starlight-header.ts +152 -0
  37. package/dist/recipes/starlight/template/src/components/starlight-page.ts +168 -0
  38. package/dist/recipes/starlight/template/src/components/starlight-sidebar.ts +125 -0
  39. package/dist/recipes/starlight/template/src/components/starlight-toc.ts +133 -0
  40. package/dist/recipes/starlight/template/src/date-utils.ts +20 -0
  41. package/dist/recipes/starlight/template/src/extract-headings.ts +68 -0
  42. package/dist/recipes/starlight/template/src/route-meta.ts +16 -0
  43. package/dist/recipes/starlight/template/tsconfig.json +14 -0
  44. package/dist/recipes/starlight/template/vite.config.ts +19 -0
  45. package/dist/src/scaffold.test.js +134 -0
  46. package/dist/src/scaffold.test.js.map +1 -1
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +1 -1
  49. package/recipes/starlight/recipe.config.ts +11 -0
  50. package/recipes/starlight/template/_data/metadata.js +10 -0
  51. package/recipes/starlight/template/app.ts +18 -0
  52. package/recipes/starlight/template/content/blog/.11tydata.json +1 -0
  53. package/recipes/starlight/template/content/blog/release-notes.md +44 -0
  54. package/recipes/starlight/template/content/blog/welcome.md +44 -0
  55. package/recipes/starlight/template/content/docs/.11tydata.json +1 -0
  56. package/recipes/starlight/template/content/docs/configuration.md +77 -0
  57. package/recipes/starlight/template/content/docs/getting-started.md +53 -0
  58. package/recipes/starlight/template/content/docs/guides-deploying.md +79 -0
  59. package/recipes/starlight/template/content/docs/guides-first-page.md +64 -0
  60. package/recipes/starlight/template/content/docs/installation.md +54 -0
  61. package/recipes/starlight/template/litro.recipe.json +7 -0
  62. package/recipes/starlight/template/nitro.config.ts +57 -0
  63. package/recipes/starlight/template/package.json +26 -0
  64. package/recipes/starlight/template/pages/blog/[slug].ts +125 -0
  65. package/recipes/starlight/template/pages/blog/index.ts +114 -0
  66. package/recipes/starlight/template/pages/blog/tags/[tag].ts +110 -0
  67. package/recipes/starlight/template/pages/docs/[slug].ts +147 -0
  68. package/recipes/starlight/template/pages/index.ts +135 -0
  69. package/recipes/starlight/template/public/styles/starlight.css +215 -0
  70. package/recipes/starlight/template/server/middleware/vite-dev.ts +29 -0
  71. package/recipes/starlight/template/server/routes/[...].ts +57 -0
  72. package/recipes/starlight/template/server/starlight.config.js +29 -0
  73. package/recipes/starlight/template/src/components/sl-aside.ts +91 -0
  74. package/recipes/starlight/template/src/components/sl-badge.ts +76 -0
  75. package/recipes/starlight/template/src/components/sl-card-grid.ts +34 -0
  76. package/recipes/starlight/template/src/components/sl-card.ts +91 -0
  77. package/recipes/starlight/template/src/components/sl-tab-item.ts +35 -0
  78. package/recipes/starlight/template/src/components/sl-tabs.ts +108 -0
  79. package/recipes/starlight/template/src/components/starlight-header.ts +152 -0
  80. package/recipes/starlight/template/src/components/starlight-page.ts +168 -0
  81. package/recipes/starlight/template/src/components/starlight-sidebar.ts +125 -0
  82. package/recipes/starlight/template/src/components/starlight-toc.ts +133 -0
  83. package/recipes/starlight/template/src/date-utils.ts +20 -0
  84. package/recipes/starlight/template/src/extract-headings.ts +68 -0
  85. package/recipes/starlight/template/src/route-meta.ts +16 -0
  86. package/recipes/starlight/template/tsconfig.json +14 -0
  87. package/recipes/starlight/template/vite.config.ts +19 -0
  88. package/src/scaffold.test.ts +148 -0
@@ -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;
@@ -0,0 +1,91 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * <sl-card title="Feature" description="Short desc" href="/docs/feature">
6
+ * Renders as an <a> when `href` is set, otherwise a <div>.
7
+ * Rotating accent color per nth-card via CSS counter.
8
+ * </sl-card>
9
+ */
10
+ @customElement('sl-card')
11
+ export class SlCard extends LitElement {
12
+ static override properties = {
13
+ title: { type: String },
14
+ description: { type: String },
15
+ icon: { type: String },
16
+ href: { type: String },
17
+ };
18
+
19
+ static override styles = css`
20
+ :host {
21
+ display: block;
22
+ counter-increment: card;
23
+ }
24
+
25
+ .card {
26
+ display: block;
27
+ padding: 1.25rem 1.5rem;
28
+ border: 1px solid var(--sl-color-border, #e8e8e8);
29
+ border-radius: var(--sl-border-radius, 0.375rem);
30
+ background-color: var(--sl-color-bg, #fff);
31
+ border-top: 4px solid;
32
+ text-decoration: none;
33
+ color: inherit;
34
+ transition: box-shadow 0.15s ease, transform 0.15s ease;
35
+ }
36
+
37
+ /* Rotate accent colors using counter — cycles through 4 variants */
38
+ :host(:nth-child(4n+1)) .card { border-top-color: var(--sl-color-accent, #7c3aed); }
39
+ :host(:nth-child(4n+2)) .card { border-top-color: var(--sl-color-note, #1d4ed8); }
40
+ :host(:nth-child(4n+3)) .card { border-top-color: var(--sl-color-tip, #15803d); }
41
+ :host(:nth-child(4n+0)) .card { border-top-color: var(--sl-color-caution, #b45309); }
42
+
43
+ a.card:hover {
44
+ box-shadow: var(--sl-shadow-md, 0 4px 16px rgba(0,0,0,.12));
45
+ transform: translateY(-2px);
46
+ }
47
+
48
+ .card-icon {
49
+ font-size: 1.75rem;
50
+ margin-bottom: 0.5rem;
51
+ }
52
+
53
+ .card-title {
54
+ font-size: var(--sl-text-lg, 1.125rem);
55
+ font-weight: 600;
56
+ color: var(--sl-color-text, #23262f);
57
+ margin: 0 0 0.4rem;
58
+ }
59
+
60
+ .card-desc {
61
+ font-size: var(--sl-text-sm, 0.875rem);
62
+ color: var(--sl-color-gray-4, #757575);
63
+ margin: 0;
64
+ line-height: 1.6;
65
+ }
66
+
67
+ .card-slot {
68
+ margin-top: 0.75rem;
69
+ }
70
+ `;
71
+
72
+ title = '';
73
+ description = '';
74
+ icon = '';
75
+ href = '';
76
+
77
+ override render() {
78
+ const inner = html`
79
+ ${this.icon ? html`<div class="card-icon">${this.icon}</div>` : ''}
80
+ <p class="card-title">${this.title}</p>
81
+ ${this.description ? html`<p class="card-desc">${this.description}</p>` : ''}
82
+ <div class="card-slot"><slot></slot></div>
83
+ `;
84
+
85
+ return this.href
86
+ ? html`<a class="card" href="${this.href}">${inner}</a>`
87
+ : html`<div class="card">${inner}</div>`;
88
+ }
89
+ }
90
+
91
+ export default SlCard;
@@ -0,0 +1,35 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * <sl-tab-item label="Tab Label">
6
+ * A single tab panel managed by a parent <sl-tabs> element.
7
+ * Hidden automatically when not selected.
8
+ * </sl-tab-item>
9
+ */
10
+ @customElement('sl-tab-item')
11
+ export class SlTabItem extends LitElement {
12
+ static override properties = {
13
+ label: { type: String },
14
+ selected: { type: Boolean, reflect: true },
15
+ };
16
+
17
+ static override styles = css`
18
+ :host {
19
+ display: block;
20
+ }
21
+
22
+ :host(:not([selected])) {
23
+ display: none;
24
+ }
25
+ `;
26
+
27
+ label = '';
28
+ selected = false;
29
+
30
+ override render() {
31
+ return html`<slot></slot>`;
32
+ }
33
+ }
34
+
35
+ export default SlTabItem;