@aravindc26/velu 0.3.0 → 0.5.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/package.json +1 -1
- package/schema/velu.schema.json +63 -0
- package/src/build.ts +322 -91
- package/src/cli.ts +1 -0
- package/src/themes.ts +358 -0
- package/src/validate.ts +4 -0
package/package.json
CHANGED
package/schema/velu.schema.json
CHANGED
|
@@ -10,6 +10,69 @@
|
|
|
10
10
|
"type": "string",
|
|
11
11
|
"description": "Path or URL to the JSON schema for editor validation."
|
|
12
12
|
},
|
|
13
|
+
"theme": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Named theme preset that controls the overall look and feel.",
|
|
16
|
+
"enum": ["violet", "maple", "palm", "willow", "linden", "almond", "aspen"],
|
|
17
|
+
"default": "violet"
|
|
18
|
+
},
|
|
19
|
+
"colors": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "Override the theme's accent colors.",
|
|
22
|
+
"properties": {
|
|
23
|
+
"primary": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Primary brand color (hex). Used as accent in both light and dark modes unless overridden.",
|
|
26
|
+
"pattern": "^#[0-9a-fA-F]{6}$"
|
|
27
|
+
},
|
|
28
|
+
"light": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Accent color for light mode (hex). Overrides primary in light mode.",
|
|
31
|
+
"pattern": "^#[0-9a-fA-F]{6}$"
|
|
32
|
+
},
|
|
33
|
+
"dark": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Accent color for dark mode (hex). Overrides primary in dark mode.",
|
|
36
|
+
"pattern": "^#[0-9a-fA-F]{6}$"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": false
|
|
40
|
+
},
|
|
41
|
+
"appearance": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Controls light/dark mode behavior.",
|
|
44
|
+
"enum": ["system", "light", "dark"],
|
|
45
|
+
"default": "system"
|
|
46
|
+
},
|
|
47
|
+
"styling": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"description": "Fine-grained styling options.",
|
|
50
|
+
"properties": {
|
|
51
|
+
"codeblocks": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"description": "Code block styling options.",
|
|
54
|
+
"properties": {
|
|
55
|
+
"theme": {
|
|
56
|
+
"description": "Shiki theme for syntax highlighting. Use a string for a single theme, or an object with light/dark keys.",
|
|
57
|
+
"oneOf": [
|
|
58
|
+
{ "type": "string" },
|
|
59
|
+
{
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"light": { "type": "string" },
|
|
63
|
+
"dark": { "type": "string" }
|
|
64
|
+
},
|
|
65
|
+
"required": ["light", "dark"],
|
|
66
|
+
"additionalProperties": false
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"additionalProperties": false
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": false
|
|
75
|
+
},
|
|
13
76
|
"navigation": {
|
|
14
77
|
"type": "object",
|
|
15
78
|
"description": "Defines the site navigation hierarchy: tabs → groups → pages.",
|
package/src/build.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, rmSync } from "node:fs";
|
|
2
2
|
import { resolve, join, dirname } from "node:path";
|
|
3
|
+
import { generateThemeCss, type ThemeConfig, type VeluColors, type VeluStyling } from "./themes.js";
|
|
3
4
|
|
|
4
5
|
// ── Types (used only by build.ts for page copying) ─────────────────────────────
|
|
5
6
|
|
|
@@ -17,6 +18,10 @@ interface VeluTab {
|
|
|
17
18
|
|
|
18
19
|
interface VeluConfig {
|
|
19
20
|
$schema?: string;
|
|
21
|
+
theme?: string;
|
|
22
|
+
colors?: VeluColors;
|
|
23
|
+
appearance?: "system" | "light" | "dark";
|
|
24
|
+
styling?: VeluStyling;
|
|
20
25
|
navigation: {
|
|
21
26
|
tabs?: VeluTab[];
|
|
22
27
|
groups?: VeluGroup[];
|
|
@@ -132,6 +137,10 @@ export interface VeluTab {
|
|
|
132
137
|
|
|
133
138
|
export interface VeluConfig {
|
|
134
139
|
$schema?: string;
|
|
140
|
+
theme?: string;
|
|
141
|
+
colors?: { primary?: string; light?: string; dark?: string };
|
|
142
|
+
appearance?: 'system' | 'light' | 'dark';
|
|
143
|
+
styling?: { codeblocks?: { theme?: string | { light: string; dark: string } } };
|
|
135
144
|
navigation: {
|
|
136
145
|
tabs?: VeluTab[];
|
|
137
146
|
groups?: VeluGroup[];
|
|
@@ -296,9 +305,31 @@ export function getTabSidebarMap(): Record<string, string[]> {
|
|
|
296
305
|
writeFileSync(join(outDir, "src", "lib", "velu.ts"), veluLib, "utf-8");
|
|
297
306
|
console.log("📚 Generated config module");
|
|
298
307
|
|
|
299
|
-
// ── 4. Generate
|
|
308
|
+
// ── 4. Generate theme CSS ─────────────────────────────────────────────────
|
|
309
|
+
const themeCss = generateThemeCss({
|
|
310
|
+
theme: config.theme,
|
|
311
|
+
colors: config.colors,
|
|
312
|
+
appearance: config.appearance,
|
|
313
|
+
styling: config.styling,
|
|
314
|
+
});
|
|
315
|
+
writeFileSync(join(outDir, "src", "styles", "velu-theme.css"), themeCss, "utf-8");
|
|
316
|
+
console.log(`🎨 Generated theme: ${config.theme || "mint"}`);
|
|
317
|
+
|
|
318
|
+
// ── 5. Generate site config ────────────────────────────────────────────────
|
|
319
|
+
// Build expressiveCode config for code block themes
|
|
320
|
+
let expressiveCodeConfig = "";
|
|
321
|
+
if (config.styling?.codeblocks?.theme) {
|
|
322
|
+
const cbt = config.styling.codeblocks.theme;
|
|
323
|
+
if (typeof cbt === "string") {
|
|
324
|
+
expressiveCodeConfig = `\n expressiveCode: { themes: ['${cbt}'] },`;
|
|
325
|
+
} else {
|
|
326
|
+
expressiveCodeConfig = `\n expressiveCode: { themes: ['${cbt.dark}', '${cbt.light}'] },`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
300
330
|
const astroConfig = `import { defineConfig } from 'astro/config';
|
|
301
331
|
import starlight from '@astrojs/starlight';
|
|
332
|
+
import { ion } from 'starlight-ion-theme';
|
|
302
333
|
import { getSidebar } from './src/lib/velu.ts';
|
|
303
334
|
|
|
304
335
|
export default defineConfig({
|
|
@@ -306,11 +337,12 @@ export default defineConfig({
|
|
|
306
337
|
integrations: [
|
|
307
338
|
starlight({
|
|
308
339
|
title: 'Velu Docs',
|
|
340
|
+
plugins: [ion()],
|
|
309
341
|
components: {
|
|
310
|
-
Header: './src/components/Header.astro',
|
|
311
342
|
Sidebar: './src/components/Sidebar.astro',
|
|
343
|
+
PageTitle: './src/components/PageTitle.astro',
|
|
312
344
|
},
|
|
313
|
-
customCss: ['./src/styles/tabs.css']
|
|
345
|
+
customCss: ['./src/styles/velu-theme.css', './src/styles/tabs.css'],${expressiveCodeConfig}
|
|
314
346
|
sidebar: getSidebar(),
|
|
315
347
|
}),
|
|
316
348
|
],
|
|
@@ -319,64 +351,26 @@ export default defineConfig({
|
|
|
319
351
|
writeFileSync(join(outDir, "_config.mjs"), astroConfig, "utf-8");
|
|
320
352
|
console.log("⚙️ Generated site config");
|
|
321
353
|
|
|
322
|
-
// ──
|
|
323
|
-
const
|
|
324
|
-
import
|
|
325
|
-
import
|
|
354
|
+
// ── 6. Generate Sidebar.astro — tabs at top + filtered content ─────────────
|
|
355
|
+
const sidebarComponent = `---
|
|
356
|
+
import MobileMenuFooter from 'virtual:starlight/components/MobileMenuFooter';
|
|
357
|
+
import SidebarSublist from '@astrojs/starlight/components/SidebarSublist.astro';
|
|
358
|
+
import { getTabs, getTabSidebarMap } from '../lib/velu.ts';
|
|
326
359
|
|
|
327
360
|
const tabs = getTabs();
|
|
361
|
+
const tabSidebarMap = getTabSidebarMap();
|
|
328
362
|
const currentPath = Astro.url.pathname;
|
|
329
363
|
|
|
330
364
|
function isTabActive(tab: any, path: string): boolean {
|
|
331
365
|
if (tab.href) return false;
|
|
332
366
|
if (tab.pathPrefix === '__default__') {
|
|
333
367
|
const otherPrefixes = tabs
|
|
334
|
-
.filter((t) => t.pathPrefix && t.pathPrefix !== '__default__' && !t.href)
|
|
335
|
-
.map((t) => t.pathPrefix);
|
|
336
|
-
return !otherPrefixes.some((p) => path.startsWith('/' + p + '/'));
|
|
368
|
+
.filter((t: any) => t.pathPrefix && t.pathPrefix !== '__default__' && !t.href)
|
|
369
|
+
.map((t: any) => t.pathPrefix);
|
|
370
|
+
return !otherPrefixes.some((p: string) => path.startsWith('/' + p + '/'));
|
|
337
371
|
}
|
|
338
372
|
return path.startsWith('/' + tab.pathPrefix + '/');
|
|
339
373
|
}
|
|
340
|
-
---
|
|
341
|
-
|
|
342
|
-
<Default {...Astro.props}>
|
|
343
|
-
<slot />
|
|
344
|
-
</Default>
|
|
345
|
-
|
|
346
|
-
<nav class="velu-tabs">
|
|
347
|
-
<div class="velu-tabs-inner">
|
|
348
|
-
{tabs.map((tab) => {
|
|
349
|
-
if (tab.href) {
|
|
350
|
-
return (
|
|
351
|
-
<a href={tab.href} class="velu-tab" target="_blank" rel="noopener noreferrer">
|
|
352
|
-
{tab.label}
|
|
353
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 17L17 7M17 7H7M17 7V17"/></svg>
|
|
354
|
-
</a>
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
const active = isTabActive(tab, currentPath);
|
|
358
|
-
const href = tab.firstPage ? '/' + tab.firstPage + '/' : '/';
|
|
359
|
-
return (
|
|
360
|
-
<a href={href} class:list={['velu-tab', { active }]}>
|
|
361
|
-
{tab.label}
|
|
362
|
-
</a>
|
|
363
|
-
);
|
|
364
|
-
})}
|
|
365
|
-
</div>
|
|
366
|
-
</nav>
|
|
367
|
-
`;
|
|
368
|
-
writeFileSync(join(outDir, "src", "components", "Header.astro"), headerComponent, "utf-8");
|
|
369
|
-
console.log("🧩 Generated header component");
|
|
370
|
-
|
|
371
|
-
// ── 6. Generate Sidebar.astro — reads filter map from velu.ts ─────────────
|
|
372
|
-
const sidebarComponent = `---
|
|
373
|
-
import MobileMenuFooter from 'virtual:starlight/components/MobileMenuFooter';
|
|
374
|
-
import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro';
|
|
375
|
-
import SidebarSublist from '@astrojs/starlight/components/SidebarSublist.astro';
|
|
376
|
-
import { getTabSidebarMap } from '../lib/velu.ts';
|
|
377
|
-
|
|
378
|
-
const tabSidebarMap = getTabSidebarMap();
|
|
379
|
-
const currentPath = Astro.url.pathname;
|
|
380
374
|
|
|
381
375
|
function getActivePrefix(path: string): string {
|
|
382
376
|
const prefixes = Object.keys(tabSidebarMap).filter(p => p !== '__default__');
|
|
@@ -396,9 +390,29 @@ const filteredSidebar = sidebar.filter((entry: any) => {
|
|
|
396
390
|
});
|
|
397
391
|
---
|
|
398
392
|
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
393
|
+
<div class="velu-sidebar-tabs">
|
|
394
|
+
{tabs.map((tab) => {
|
|
395
|
+
if (tab.href) {
|
|
396
|
+
return (
|
|
397
|
+
<a href={tab.href} class="velu-sidebar-tab" target="_blank" rel="noopener noreferrer">
|
|
398
|
+
{tab.icon && <span class="velu-sidebar-tab-icon" data-icon={tab.icon} />}
|
|
399
|
+
<span>{tab.label}</span>
|
|
400
|
+
<svg class="velu-external-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 17L17 7M17 7H7M17 7V17"/></svg>
|
|
401
|
+
</a>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const active = isTabActive(tab, currentPath);
|
|
405
|
+
const href = tab.firstPage ? '/' + tab.firstPage + '/' : '/';
|
|
406
|
+
return (
|
|
407
|
+
<a href={href} class:list={['velu-sidebar-tab', { active }]}>
|
|
408
|
+
{tab.icon && <span class="velu-sidebar-tab-icon" data-icon={tab.icon} />}
|
|
409
|
+
<span>{tab.label}</span>
|
|
410
|
+
</a>
|
|
411
|
+
);
|
|
412
|
+
})}
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<SidebarSublist sublist={filteredSidebar} />
|
|
402
416
|
|
|
403
417
|
<div class="md:sl-hidden">
|
|
404
418
|
<MobileMenuFooter />
|
|
@@ -407,75 +421,291 @@ const filteredSidebar = sidebar.filter((entry: any) => {
|
|
|
407
421
|
writeFileSync(join(outDir, "src", "components", "Sidebar.astro"), sidebarComponent, "utf-8");
|
|
408
422
|
console.log("📋 Generated sidebar component");
|
|
409
423
|
|
|
410
|
-
// ── 7. Generate
|
|
411
|
-
const
|
|
424
|
+
// ── 7. Generate PageTitle.astro — title row with copy page button ────────
|
|
425
|
+
const pageTitleComponent = `---
|
|
426
|
+
const currentUrl = Astro.url.href;
|
|
427
|
+
const title = Astro.locals.starlightRoute.entry.data.title;
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
<div class="velu-title-row">
|
|
431
|
+
<h1 id="_top">{title}</h1>
|
|
432
|
+
<div class="velu-copy-page-container">
|
|
433
|
+
<div class="velu-copy-split-btn">
|
|
434
|
+
<button class="velu-copy-main-btn" data-action="direct-copy">
|
|
435
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
436
|
+
<span class="velu-copy-label">Copy page</span>
|
|
437
|
+
</button>
|
|
438
|
+
<span class="velu-copy-sep"></span>
|
|
439
|
+
<button class="velu-copy-caret-btn" aria-expanded="false" aria-haspopup="true">
|
|
440
|
+
<svg class="velu-copy-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
|
441
|
+
</button>
|
|
442
|
+
</div>
|
|
443
|
+
<div class="velu-copy-dropdown" hidden>
|
|
444
|
+
<button class="velu-copy-option" data-action="copy">
|
|
445
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
446
|
+
<div>
|
|
447
|
+
<div class="velu-copy-option-title">Copy page</div>
|
|
448
|
+
<div class="velu-copy-option-desc">Copy page as Markdown for LLMs</div>
|
|
449
|
+
</div>
|
|
450
|
+
</button>
|
|
451
|
+
<a class="velu-copy-option" href={\`https://chatgpt.com/?prompt=Read+from+\${encodeURIComponent(currentUrl)}+so+I+can+ask+questions+about+it.\`} target="_blank" rel="noopener noreferrer">
|
|
452
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/></svg>
|
|
453
|
+
<div>
|
|
454
|
+
<div class="velu-copy-option-title">Open in ChatGPT <span class="velu-external-arrow">↗</span></div>
|
|
455
|
+
<div class="velu-copy-option-desc">Ask questions about this page</div>
|
|
456
|
+
</div>
|
|
457
|
+
</a>
|
|
458
|
+
<a class="velu-copy-option" href={\`https://claude.ai/new?q=Read+from+\${encodeURIComponent(currentUrl)}+so+I+can+ask+questions+about+it.\`} target="_blank" rel="noopener noreferrer">
|
|
459
|
+
<svg width="18" height="18" viewBox="0 0 200 200" style="overflow:visible" fill="currentColor"><path d="m50.228 170.321 50.357-28.257.843-2.463-.843-1.361h-2.462l-8.426-.518-28.775-.778-24.952-1.037-24.175-1.296-6.092-1.297L0 125.796l.583-3.759 5.12-3.434 7.324.648 16.202 1.101 24.304 1.685 17.629 1.037 26.118 2.722h4.148l.583-1.685-1.426-1.037-1.101-1.037-25.147-17.045-27.22-18.017-14.258-10.37-7.713-5.25-3.888-4.925-1.685-10.758 7-7.713 9.397.649 2.398.648 9.527 7.323 20.35 15.75L94.817 91.9l3.889 3.24 1.555-1.102.195-.777-1.75-2.917-14.453-26.118-15.425-26.572-6.87-11.018-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0 82.05 1.426l4.472 3.888 6.61 15.101 10.694 23.786 16.591 32.34 4.861 9.592 2.592 8.879.973 2.722h1.685v-1.556l1.36-18.211 2.528-22.36 2.463-28.776.843-8.1 4.018-9.722 7.971-5.25 6.222 2.981 5.12 7.324-.713 4.73-3.046 19.768-5.962 30.98-3.889 20.739h2.268l2.593-2.593 10.499-13.934 17.628-22.036 7.778-8.749 9.073-9.657 5.833-4.601h11.018l8.1 12.055-3.628 12.443-11.342 14.388-9.398 12.184-13.48 18.147-8.426 14.518.778 1.166 2.01-.194 30.46-6.481 16.462-2.982 19.637-3.37 8.88 4.148.971 4.213-3.5 8.62-20.998 5.184-24.628 4.926-36.682 8.685-.454.324.519.648 16.526 1.555 7.065.389h17.304l32.21 2.398 8.426 5.574 5.055 6.805-.843 5.184-12.962 6.611-17.498-4.148-40.83-9.721-14-3.5h-1.944v1.167l11.666 11.406 21.387 19.314 26.767 24.887 1.36 6.157-3.434 4.86-3.63-.518-23.526-17.693-9.073-7.972-20.545-17.304h-1.36v1.814l4.73 6.935 25.017 37.59 1.296 11.536-1.814 3.76-6.481 2.268-7.13-1.297-14.647-20.544-15.1-23.138-12.185-20.739-1.49.843-7.194 77.448-3.37 3.953-7.778 2.981-6.48-4.925-3.436-7.972 3.435-15.749 4.148-20.544 3.37-16.333 3.046-20.285 1.815-6.74-.13-.454-1.49.194-15.295 20.999-23.267 31.433-18.406 19.702-4.407 1.75-7.648-3.954.713-7.064 4.277-6.286 25.47-32.405 15.36-20.092 9.917-11.6-.065-1.686h-.583L44.07 198.125l-12.055 1.555-5.185-4.86.648-7.972 2.463-2.593 20.35-13.999-.064.065Z"/></svg>
|
|
460
|
+
<div>
|
|
461
|
+
<div class="velu-copy-option-title">Open in Claude <span class="velu-external-arrow">↗</span></div>
|
|
462
|
+
<div class="velu-copy-option-desc">Ask questions about this page</div>
|
|
463
|
+
</div>
|
|
464
|
+
</a>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
<script is:inline>
|
|
470
|
+
(function init() {
|
|
471
|
+
var caretBtn = document.querySelector('.velu-copy-caret-btn');
|
|
472
|
+
var mainBtn = document.querySelector('.velu-copy-main-btn');
|
|
473
|
+
var dropdown = document.querySelector('.velu-copy-dropdown');
|
|
474
|
+
var label = document.querySelector('.velu-copy-label');
|
|
475
|
+
if (!caretBtn || !mainBtn || !dropdown) return;
|
|
476
|
+
|
|
477
|
+
function doCopy() {
|
|
478
|
+
if (label) label.textContent = 'Copying...';
|
|
479
|
+
var titleEl = document.querySelector('#_top');
|
|
480
|
+
var article = document.querySelector('.sl-markdown-content') || document.querySelector('.content-panel') || document.querySelector('main');
|
|
481
|
+
var text = '';
|
|
482
|
+
if (titleEl) text = '# ' + titleEl.textContent + '\\n\\n';
|
|
483
|
+
if (article) text += article.innerText;
|
|
484
|
+
if (text) {
|
|
485
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
486
|
+
if (label) label.textContent = 'Copied!';
|
|
487
|
+
setTimeout(function() { if (label) label.textContent = 'Copy page'; }, 1500);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
dropdown.hidden = true;
|
|
491
|
+
caretBtn.setAttribute('aria-expanded', 'false');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
mainBtn.onclick = function(e) { e.stopPropagation(); doCopy(); };
|
|
495
|
+
|
|
496
|
+
caretBtn.onclick = function(e) {
|
|
497
|
+
e.stopPropagation();
|
|
498
|
+
var open = dropdown.hidden;
|
|
499
|
+
dropdown.hidden = !open;
|
|
500
|
+
caretBtn.setAttribute('aria-expanded', String(open));
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
document.addEventListener('click', function() {
|
|
504
|
+
dropdown.hidden = true;
|
|
505
|
+
caretBtn.setAttribute('aria-expanded', 'false');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
dropdown.onclick = function(e) { e.stopPropagation(); };
|
|
509
|
+
|
|
510
|
+
var copyOpt = dropdown.querySelector('[data-action="copy"]');
|
|
511
|
+
if (copyOpt) {
|
|
512
|
+
copyOpt.onclick = function() { doCopy(); };
|
|
513
|
+
}
|
|
514
|
+
})();
|
|
515
|
+
</script>
|
|
516
|
+
`;
|
|
517
|
+
writeFileSync(join(outDir, "src", "components", "PageTitle.astro"), pageTitleComponent, "utf-8");
|
|
518
|
+
console.log("📋 Generated page title component");
|
|
519
|
+
|
|
520
|
+
// ── 8. Generate tabs.css ──────────────────────────────────────────────────
|
|
521
|
+
const tabsCss = `/* ── Velu sidebar tabs ─────────────────────────────────────────────────── */
|
|
412
522
|
|
|
413
523
|
:root {
|
|
414
|
-
--sl-
|
|
524
|
+
--sl-sidebar-width: 16rem;
|
|
415
525
|
}
|
|
416
526
|
|
|
417
|
-
|
|
418
|
-
.page > header.header {
|
|
527
|
+
.velu-sidebar-tabs {
|
|
419
528
|
display: flex;
|
|
420
529
|
flex-direction: column;
|
|
421
|
-
|
|
530
|
+
gap: 0.15rem;
|
|
531
|
+
padding: 0.35rem;
|
|
532
|
+
margin-bottom: 1rem;
|
|
533
|
+
background-color: var(--sl-color-bg);
|
|
534
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
535
|
+
border-radius: 0.5rem;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.velu-sidebar-tab {
|
|
539
|
+
display: flex;
|
|
540
|
+
align-items: center;
|
|
541
|
+
gap: 0.5rem;
|
|
542
|
+
padding: 0.45rem 0.65rem;
|
|
543
|
+
font-size: var(--sl-text-sm);
|
|
544
|
+
font-weight: 600;
|
|
545
|
+
color: var(--sl-color-gray-3);
|
|
546
|
+
text-decoration: none;
|
|
547
|
+
border-radius: 0.375rem;
|
|
548
|
+
transition: color 0.15s, background-color 0.15s;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.velu-sidebar-tab:hover {
|
|
552
|
+
color: var(--sl-color-white);
|
|
553
|
+
background-color: var(--sl-color-gray-6);
|
|
422
554
|
}
|
|
423
555
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
flex: 1;
|
|
556
|
+
.velu-sidebar-tab.active {
|
|
557
|
+
color: var(--sl-color-white);
|
|
558
|
+
background-color: var(--sl-color-gray-6);
|
|
428
559
|
}
|
|
429
560
|
|
|
430
|
-
|
|
561
|
+
:root[data-theme='light'] .velu-sidebar-tab.active {
|
|
562
|
+
color: var(--sl-color-white);
|
|
563
|
+
background-color: var(--sl-color-gray-7);
|
|
564
|
+
}
|
|
431
565
|
|
|
432
|
-
.velu-
|
|
566
|
+
.velu-external-icon {
|
|
567
|
+
opacity: 0.4;
|
|
433
568
|
flex-shrink: 0;
|
|
434
|
-
|
|
435
|
-
margin-inline: calc(-1 * var(--sl-nav-pad-x));
|
|
436
|
-
padding-inline: var(--sl-nav-pad-x);
|
|
437
|
-
background: var(--sl-color-bg-nav);
|
|
569
|
+
margin-inline-start: auto;
|
|
438
570
|
}
|
|
439
571
|
|
|
440
|
-
|
|
572
|
+
/* ── Copy page button ─────────────────────────────────────────────────── */
|
|
573
|
+
|
|
574
|
+
.velu-title-row {
|
|
441
575
|
display: flex;
|
|
442
|
-
|
|
443
|
-
|
|
576
|
+
align-items: flex-start;
|
|
577
|
+
justify-content: space-between;
|
|
578
|
+
gap: 1rem;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.velu-title-row h1 {
|
|
582
|
+
margin: 0;
|
|
444
583
|
}
|
|
445
584
|
|
|
446
|
-
.velu-
|
|
585
|
+
.velu-copy-page-container {
|
|
586
|
+
position: relative;
|
|
587
|
+
flex-shrink: 0;
|
|
588
|
+
margin-top: 0.35rem;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.velu-copy-split-btn {
|
|
447
592
|
display: inline-flex;
|
|
448
593
|
align-items: center;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
594
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
595
|
+
border-radius: 999px;
|
|
596
|
+
background: var(--sl-color-bg-nav);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.velu-copy-main-btn {
|
|
600
|
+
display: inline-flex;
|
|
601
|
+
align-items: center;
|
|
602
|
+
gap: 0.4rem;
|
|
603
|
+
padding: 0.35rem 0.5rem 0.35rem 0.75rem;
|
|
604
|
+
font-size: var(--sl-text-xs);
|
|
452
605
|
font-weight: 500;
|
|
453
606
|
color: var(--sl-color-gray-3);
|
|
607
|
+
background: none;
|
|
608
|
+
border: none;
|
|
609
|
+
cursor: pointer;
|
|
610
|
+
transition: color 0.15s;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.velu-copy-main-btn:hover {
|
|
614
|
+
color: var(--sl-color-white);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.velu-copy-sep {
|
|
618
|
+
width: 1px;
|
|
619
|
+
height: 14px;
|
|
620
|
+
background-color: var(--sl-color-gray-5);
|
|
621
|
+
flex-shrink: 0;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.velu-copy-caret-btn {
|
|
625
|
+
display: inline-flex;
|
|
626
|
+
align-items: center;
|
|
627
|
+
padding: 0.35rem 0.5rem;
|
|
628
|
+
background: none;
|
|
629
|
+
border: none;
|
|
630
|
+
color: var(--sl-color-gray-3);
|
|
631
|
+
cursor: pointer;
|
|
632
|
+
transition: color 0.15s;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.velu-copy-caret-btn:hover {
|
|
636
|
+
color: var(--sl-color-white);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.velu-copy-chevron {
|
|
640
|
+
transition: transform 0.15s;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.velu-copy-caret-btn[aria-expanded='true'] .velu-copy-chevron {
|
|
644
|
+
transform: rotate(180deg);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.velu-copy-dropdown {
|
|
648
|
+
position: absolute;
|
|
649
|
+
right: 0;
|
|
650
|
+
top: calc(100% + 0.35rem);
|
|
651
|
+
z-index: 100;
|
|
652
|
+
min-width: 16rem;
|
|
653
|
+
padding: 0.35rem;
|
|
654
|
+
background: var(--sl-color-bg-nav);
|
|
655
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
656
|
+
border-radius: 0.5rem;
|
|
657
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.velu-copy-option {
|
|
661
|
+
display: flex;
|
|
662
|
+
align-items: flex-start;
|
|
663
|
+
gap: 0.5rem;
|
|
664
|
+
width: 100%;
|
|
665
|
+
padding: 0.5rem 0.6rem;
|
|
666
|
+
font: inherit;
|
|
667
|
+
font-size: var(--sl-text-sm);
|
|
668
|
+
color: var(--sl-color-gray-2);
|
|
669
|
+
text-align: left;
|
|
454
670
|
text-decoration: none;
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
671
|
+
background: none;
|
|
672
|
+
border: none;
|
|
673
|
+
border-radius: 0.35rem;
|
|
674
|
+
cursor: pointer;
|
|
675
|
+
transition: background-color 0.15s;
|
|
458
676
|
}
|
|
459
677
|
|
|
460
|
-
.velu-
|
|
461
|
-
color: var(--sl-color-gray-1);
|
|
678
|
+
.velu-copy-option:hover {
|
|
462
679
|
background-color: var(--sl-color-gray-6);
|
|
463
680
|
}
|
|
464
681
|
|
|
465
|
-
.velu-
|
|
466
|
-
|
|
467
|
-
|
|
682
|
+
.velu-copy-option svg {
|
|
683
|
+
flex-shrink: 0;
|
|
684
|
+
opacity: 0.7;
|
|
685
|
+
margin-top: 0.15rem;
|
|
686
|
+
overflow: visible;
|
|
468
687
|
}
|
|
469
688
|
|
|
470
|
-
.velu-
|
|
689
|
+
.velu-copy-option-title {
|
|
690
|
+
font-weight: 500;
|
|
691
|
+
line-height: 1.3;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.velu-copy-option-desc {
|
|
695
|
+
font-size: var(--sl-text-xs);
|
|
696
|
+
color: var(--sl-color-gray-3);
|
|
697
|
+
line-height: 1.3;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.velu-external-arrow {
|
|
701
|
+
font-size: 0.75em;
|
|
471
702
|
opacity: 0.5;
|
|
472
|
-
flex-shrink: 0;
|
|
473
703
|
}
|
|
474
704
|
`;
|
|
475
705
|
writeFileSync(join(outDir, "src", "styles", "tabs.css"), tabsCss, "utf-8");
|
|
476
706
|
console.log("🎨 Generated tabs.css");
|
|
477
707
|
|
|
478
|
-
// ──
|
|
708
|
+
// ── 9. Static boilerplate ─────────────────────────────────────────────────
|
|
479
709
|
const astroPkg = {
|
|
480
710
|
name: "velu-docs-site",
|
|
481
711
|
version: "0.0.1",
|
|
@@ -487,9 +717,10 @@ const filteredSidebar = sidebar.filter((entry: any) => {
|
|
|
487
717
|
preview: "astro preview",
|
|
488
718
|
},
|
|
489
719
|
dependencies: {
|
|
490
|
-
astro: "^5.
|
|
491
|
-
"@astrojs/starlight": "^0.
|
|
720
|
+
astro: "^5.12.0",
|
|
721
|
+
"@astrojs/starlight": "^0.35.0",
|
|
492
722
|
sharp: "^0.33.0",
|
|
723
|
+
"starlight-ion-theme": "^2.3.0",
|
|
493
724
|
},
|
|
494
725
|
};
|
|
495
726
|
writeFileSync(join(outDir, "package.json"), JSON.stringify(astroPkg, null, 2) + "\n", "utf-8");
|
|
@@ -513,7 +744,7 @@ const filteredSidebar = sidebar.filter((entry: any) => {
|
|
|
513
744
|
"utf-8"
|
|
514
745
|
);
|
|
515
746
|
|
|
516
|
-
// ──
|
|
747
|
+
// ── 10. Generate _server.mjs — programmatic dev/build/preview ───────────────
|
|
517
748
|
const serverScript = `import { dev, build, preview } from 'astro';
|
|
518
749
|
import { watch } from 'node:fs';
|
|
519
750
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync } from 'node:fs';
|
|
@@ -623,7 +854,7 @@ if (command === 'dev') {
|
|
|
623
854
|
`;
|
|
624
855
|
writeFileSync(join(outDir, "_server.mjs"), serverScript, "utf-8");
|
|
625
856
|
|
|
626
|
-
// ──
|
|
857
|
+
// ── 11. Generate .gitignore ──────────────────────────────────────────────
|
|
627
858
|
writeFileSync(
|
|
628
859
|
join(outDir, ".gitignore"),
|
|
629
860
|
`.astro/\nnode_modules/\ndist/\n`,
|
package/src/cli.ts
CHANGED
package/src/themes.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
interface ColorSet {
|
|
4
|
+
accentLow: string;
|
|
5
|
+
accent: string;
|
|
6
|
+
accentHigh: string;
|
|
7
|
+
white: string;
|
|
8
|
+
gray1: string;
|
|
9
|
+
gray2: string;
|
|
10
|
+
gray3: string;
|
|
11
|
+
gray4: string;
|
|
12
|
+
gray5: string;
|
|
13
|
+
gray6: string;
|
|
14
|
+
gray7: string;
|
|
15
|
+
black: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ThemePreset {
|
|
19
|
+
dark: ColorSet;
|
|
20
|
+
light: ColorSet;
|
|
21
|
+
font?: string;
|
|
22
|
+
fontMono?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface VeluColors {
|
|
26
|
+
primary?: string;
|
|
27
|
+
light?: string;
|
|
28
|
+
dark?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface VeluStyling {
|
|
32
|
+
codeblocks?: {
|
|
33
|
+
theme?: string | { light: string; dark: string };
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ThemeConfig {
|
|
38
|
+
theme?: string;
|
|
39
|
+
colors?: VeluColors;
|
|
40
|
+
appearance?: "system" | "light" | "dark";
|
|
41
|
+
styling?: VeluStyling;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Color utilities ──────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
47
|
+
const h = hex.replace("#", "");
|
|
48
|
+
return [
|
|
49
|
+
parseInt(h.substring(0, 2), 16),
|
|
50
|
+
parseInt(h.substring(2, 4), 16),
|
|
51
|
+
parseInt(h.substring(4, 6), 16),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function rgbToHex(r: number, g: number, b: number): string {
|
|
56
|
+
const clamp = (v: number) => Math.round(Math.max(0, Math.min(255, v)));
|
|
57
|
+
return (
|
|
58
|
+
"#" +
|
|
59
|
+
[clamp(r), clamp(g), clamp(b)]
|
|
60
|
+
.map((c) => c.toString(16).padStart(2, "0"))
|
|
61
|
+
.join("")
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function mixColors(hex1: string, hex2: string, weight: number): string {
|
|
66
|
+
const [r1, g1, b1] = hexToRgb(hex1);
|
|
67
|
+
const [r2, g2, b2] = hexToRgb(hex2);
|
|
68
|
+
return rgbToHex(
|
|
69
|
+
r1 * weight + r2 * (1 - weight),
|
|
70
|
+
g1 * weight + g2 * (1 - weight),
|
|
71
|
+
b1 * weight + b2 * (1 - weight)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function deriveAccentPalette(primary: string): { dark: Pick<ColorSet, "accentLow" | "accent" | "accentHigh">; light: Pick<ColorSet, "accentLow" | "accent" | "accentHigh"> } {
|
|
76
|
+
return {
|
|
77
|
+
dark: {
|
|
78
|
+
accentLow: mixColors(primary, "#000000", 0.3),
|
|
79
|
+
accent: mixColors(primary, "#ffffff", 0.2),
|
|
80
|
+
accentHigh: mixColors(primary, "#ffffff", 0.8),
|
|
81
|
+
},
|
|
82
|
+
light: {
|
|
83
|
+
accentLow: mixColors(primary, "#ffffff", 0.15),
|
|
84
|
+
accent: primary,
|
|
85
|
+
accentHigh: mixColors(primary, "#000000", 0.55),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Gray palettes ────────────────────────────────────────────────────────────
|
|
91
|
+
// Starlight convention: gray-1 = strongest foreground, gray-7 = subtlest
|
|
92
|
+
// In dark mode: gray-1 is light, gray-7 is dark
|
|
93
|
+
// In light mode: gray-1 is dark, gray-7 is light
|
|
94
|
+
// --sl-color-white = foreground extreme, --sl-color-black = background extreme
|
|
95
|
+
|
|
96
|
+
const GRAY_SLATE = {
|
|
97
|
+
dark: {
|
|
98
|
+
white: "#ffffff",
|
|
99
|
+
gray1: "#eceef2",
|
|
100
|
+
gray2: "#c0c2c7",
|
|
101
|
+
gray3: "#888b96",
|
|
102
|
+
gray4: "#545861",
|
|
103
|
+
gray5: "#353841",
|
|
104
|
+
gray6: "#24272f",
|
|
105
|
+
gray7: "#17181c",
|
|
106
|
+
black: "#13141a",
|
|
107
|
+
},
|
|
108
|
+
light: {
|
|
109
|
+
white: "#13141a",
|
|
110
|
+
gray1: "#17181c",
|
|
111
|
+
gray2: "#24272f",
|
|
112
|
+
gray3: "#545861",
|
|
113
|
+
gray4: "#888b96",
|
|
114
|
+
gray5: "#c0c2c7",
|
|
115
|
+
gray6: "#eceef2",
|
|
116
|
+
gray7: "#f5f6f8",
|
|
117
|
+
black: "#ffffff",
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const GRAY_ZINC = {
|
|
122
|
+
dark: {
|
|
123
|
+
white: "#ffffff",
|
|
124
|
+
gray1: "#ececef",
|
|
125
|
+
gray2: "#bfc0c4",
|
|
126
|
+
gray3: "#878890",
|
|
127
|
+
gray4: "#53545c",
|
|
128
|
+
gray5: "#34353b",
|
|
129
|
+
gray6: "#23242a",
|
|
130
|
+
gray7: "#17171a",
|
|
131
|
+
black: "#121214",
|
|
132
|
+
},
|
|
133
|
+
light: {
|
|
134
|
+
white: "#121214",
|
|
135
|
+
gray1: "#17171a",
|
|
136
|
+
gray2: "#23242a",
|
|
137
|
+
gray3: "#53545c",
|
|
138
|
+
gray4: "#878890",
|
|
139
|
+
gray5: "#bfc0c4",
|
|
140
|
+
gray6: "#ececef",
|
|
141
|
+
gray7: "#f5f5f7",
|
|
142
|
+
black: "#ffffff",
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const GRAY_STONE = {
|
|
147
|
+
dark: {
|
|
148
|
+
white: "#ffffff",
|
|
149
|
+
gray1: "#eeeceb",
|
|
150
|
+
gray2: "#c3bfbb",
|
|
151
|
+
gray3: "#8c8680",
|
|
152
|
+
gray4: "#585550",
|
|
153
|
+
gray5: "#383532",
|
|
154
|
+
gray6: "#272421",
|
|
155
|
+
gray7: "#1a1816",
|
|
156
|
+
black: "#141210",
|
|
157
|
+
},
|
|
158
|
+
light: {
|
|
159
|
+
white: "#141210",
|
|
160
|
+
gray1: "#1a1816",
|
|
161
|
+
gray2: "#272421",
|
|
162
|
+
gray3: "#585550",
|
|
163
|
+
gray4: "#8c8680",
|
|
164
|
+
gray5: "#c3bfbb",
|
|
165
|
+
gray6: "#eeeceb",
|
|
166
|
+
gray7: "#f7f6f5",
|
|
167
|
+
black: "#ffffff",
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// ── Theme presets ────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
const THEMES: Record<string, ThemePreset> = {
|
|
174
|
+
violet: {
|
|
175
|
+
dark: {
|
|
176
|
+
accentLow: "#1e1b4b",
|
|
177
|
+
accent: "#818cf8",
|
|
178
|
+
accentHigh: "#e0e7ff",
|
|
179
|
+
...GRAY_SLATE.dark,
|
|
180
|
+
},
|
|
181
|
+
light: {
|
|
182
|
+
accentLow: "#e0e7ff",
|
|
183
|
+
accent: "#4f46e5",
|
|
184
|
+
accentHigh: "#1e1b4b",
|
|
185
|
+
...GRAY_SLATE.light,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
maple: {
|
|
190
|
+
dark: {
|
|
191
|
+
accentLow: "#2e1065",
|
|
192
|
+
accent: "#a78bfa",
|
|
193
|
+
accentHigh: "#ede9fe",
|
|
194
|
+
...GRAY_ZINC.dark,
|
|
195
|
+
},
|
|
196
|
+
light: {
|
|
197
|
+
accentLow: "#ede9fe",
|
|
198
|
+
accent: "#7c3aed",
|
|
199
|
+
accentHigh: "#2e1065",
|
|
200
|
+
...GRAY_ZINC.light,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
palm: {
|
|
205
|
+
dark: {
|
|
206
|
+
accentLow: "#0c2d44",
|
|
207
|
+
accent: "#38bdf8",
|
|
208
|
+
accentHigh: "#e0f2fe",
|
|
209
|
+
...GRAY_SLATE.dark,
|
|
210
|
+
},
|
|
211
|
+
light: {
|
|
212
|
+
accentLow: "#e0f2fe",
|
|
213
|
+
accent: "#0369a1",
|
|
214
|
+
accentHigh: "#0c2d44",
|
|
215
|
+
...GRAY_SLATE.light,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
willow: {
|
|
220
|
+
dark: {
|
|
221
|
+
accentLow: "#292524",
|
|
222
|
+
accent: "#a8a29e",
|
|
223
|
+
accentHigh: "#fafaf9",
|
|
224
|
+
...GRAY_STONE.dark,
|
|
225
|
+
},
|
|
226
|
+
light: {
|
|
227
|
+
accentLow: "#f5f5f4",
|
|
228
|
+
accent: "#57534e",
|
|
229
|
+
accentHigh: "#1c1917",
|
|
230
|
+
...GRAY_STONE.light,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
linden: {
|
|
235
|
+
dark: {
|
|
236
|
+
accentLow: "#052e16",
|
|
237
|
+
accent: "#4ade80",
|
|
238
|
+
accentHigh: "#dcfce7",
|
|
239
|
+
...GRAY_ZINC.dark,
|
|
240
|
+
},
|
|
241
|
+
light: {
|
|
242
|
+
accentLow: "#dcfce7",
|
|
243
|
+
accent: "#16a34a",
|
|
244
|
+
accentHigh: "#052e16",
|
|
245
|
+
...GRAY_ZINC.light,
|
|
246
|
+
},
|
|
247
|
+
font: "'JetBrains Mono', 'Fira Code', 'Courier New', monospace",
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
almond: {
|
|
251
|
+
dark: {
|
|
252
|
+
accentLow: "#451a03",
|
|
253
|
+
accent: "#fbbf24",
|
|
254
|
+
accentHigh: "#fef3c7",
|
|
255
|
+
...GRAY_STONE.dark,
|
|
256
|
+
},
|
|
257
|
+
light: {
|
|
258
|
+
accentLow: "#fef3c7",
|
|
259
|
+
accent: "#b45309",
|
|
260
|
+
accentHigh: "#451a03",
|
|
261
|
+
...GRAY_STONE.light,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
aspen: {
|
|
266
|
+
dark: {
|
|
267
|
+
accentLow: "#1e1b4b",
|
|
268
|
+
accent: "#818cf8",
|
|
269
|
+
accentHigh: "#e0e7ff",
|
|
270
|
+
...GRAY_SLATE.dark,
|
|
271
|
+
},
|
|
272
|
+
light: {
|
|
273
|
+
accentLow: "#e0e7ff",
|
|
274
|
+
accent: "#4f46e5",
|
|
275
|
+
accentHigh: "#1e1b4b",
|
|
276
|
+
...GRAY_SLATE.light,
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// ── CSS generator ────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
function colorSetToCss(colors: ColorSet): string {
|
|
284
|
+
return [
|
|
285
|
+
` --sl-color-accent-low: ${colors.accentLow};`,
|
|
286
|
+
` --sl-color-accent: ${colors.accent};`,
|
|
287
|
+
` --sl-color-accent-high: ${colors.accentHigh};`,
|
|
288
|
+
` --sl-color-white: ${colors.white};`,
|
|
289
|
+
` --sl-color-gray-1: ${colors.gray1};`,
|
|
290
|
+
` --sl-color-gray-2: ${colors.gray2};`,
|
|
291
|
+
` --sl-color-gray-3: ${colors.gray3};`,
|
|
292
|
+
` --sl-color-gray-4: ${colors.gray4};`,
|
|
293
|
+
` --sl-color-gray-5: ${colors.gray5};`,
|
|
294
|
+
` --sl-color-gray-6: ${colors.gray6};`,
|
|
295
|
+
` --sl-color-gray-7: ${colors.gray7};`,
|
|
296
|
+
` --sl-color-black: ${colors.black};`,
|
|
297
|
+
].join("\n");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function generateThemeCss(config: ThemeConfig): string {
|
|
301
|
+
const themeName = config.theme || "violet";
|
|
302
|
+
const preset = THEMES[themeName] || THEMES["violet"];
|
|
303
|
+
|
|
304
|
+
const darkColors: ColorSet = { ...preset.dark };
|
|
305
|
+
const lightColors: ColorSet = { ...preset.light };
|
|
306
|
+
|
|
307
|
+
// Apply color overrides
|
|
308
|
+
if (config.colors) {
|
|
309
|
+
const { primary, light, dark } = config.colors;
|
|
310
|
+
|
|
311
|
+
const lightAccent = light || primary;
|
|
312
|
+
const darkAccent = dark || primary;
|
|
313
|
+
|
|
314
|
+
if (lightAccent) {
|
|
315
|
+
const palette = deriveAccentPalette(lightAccent);
|
|
316
|
+
lightColors.accentLow = palette.light.accentLow;
|
|
317
|
+
lightColors.accent = palette.light.accent;
|
|
318
|
+
lightColors.accentHigh = palette.light.accentHigh;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (darkAccent) {
|
|
322
|
+
const palette = deriveAccentPalette(darkAccent);
|
|
323
|
+
darkColors.accentLow = palette.dark.accentLow;
|
|
324
|
+
darkColors.accent = palette.dark.accent;
|
|
325
|
+
darkColors.accentHigh = palette.dark.accentHigh;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const lines: string[] = [];
|
|
330
|
+
lines.push(`/* Velu Theme: ${themeName} */`);
|
|
331
|
+
lines.push("");
|
|
332
|
+
|
|
333
|
+
// Dark mode (Starlight default)
|
|
334
|
+
lines.push(":root {");
|
|
335
|
+
lines.push(colorSetToCss(darkColors));
|
|
336
|
+
if (preset.font) {
|
|
337
|
+
lines.push(` --sl-font: ${preset.font};`);
|
|
338
|
+
}
|
|
339
|
+
if (preset.fontMono) {
|
|
340
|
+
lines.push(` --sl-font-mono: ${preset.fontMono};`);
|
|
341
|
+
}
|
|
342
|
+
lines.push("}");
|
|
343
|
+
lines.push("");
|
|
344
|
+
|
|
345
|
+
// Light mode
|
|
346
|
+
lines.push(":root[data-theme='light'] {");
|
|
347
|
+
lines.push(colorSetToCss(lightColors));
|
|
348
|
+
lines.push("}");
|
|
349
|
+
lines.push("");
|
|
350
|
+
|
|
351
|
+
return lines.join("\n");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function getThemeNames(): string[] {
|
|
355
|
+
return Object.keys(THEMES);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export { generateThemeCss, getThemeNames, THEMES, ThemeConfig, VeluColors, VeluStyling };
|
package/src/validate.ts
CHANGED
|
@@ -21,6 +21,10 @@ interface VeluTab {
|
|
|
21
21
|
|
|
22
22
|
interface VeluConfig {
|
|
23
23
|
$schema?: string;
|
|
24
|
+
theme?: string;
|
|
25
|
+
colors?: { primary?: string; light?: string; dark?: string };
|
|
26
|
+
appearance?: "system" | "light" | "dark";
|
|
27
|
+
styling?: { codeblocks?: { theme?: string | { light: string; dark: string } } };
|
|
24
28
|
navigation: {
|
|
25
29
|
tabs?: VeluTab[];
|
|
26
30
|
groups?: VeluGroup[];
|