a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: doc versioning & theming for docsite

+752 -115
+2
.gitignore
··· 22 22 *.njsproj 23 23 *.sln 24 24 *.sw? 25 + 26 + **/.vitepress/cache/
+12 -14
cli/src/commands/css-docs.ts
··· 1 1 import { mkdir, readFile, writeFile } from "node:fs/promises"; 2 2 import path from "node:path"; 3 3 import { echo } from "../console/echo"; 4 + import { trackVersion } from "../versioning/tracker.js"; 4 5 5 6 type CSSComment = { selector: string; comment: string }; 7 + 6 8 type CSSVariable = { name: string; value: string; category: string }; 9 + 7 10 type ElementCoverage = { element: string; covered: boolean }; 8 11 9 12 /** 10 - * Extract CSS doc comments from CSS file 11 - * Parses block comments and associates them with selectors 13 + * Extract CSS doc comments from CSS file by parsing block comments and associatint them with selectors 12 14 */ 13 15 function extractCSSComments(cssContent: string): CSSComment[] { 14 16 const comments: CSSComment[] = []; ··· 71 73 } 72 74 73 75 /** 74 - * Extract CSS custom properties (variables) from :root 75 - * Groups them by category based on naming conventions 76 + * Extract CSS custom properties (variables) from :root, grouping them by category based on naming conventions 76 77 */ 77 78 function extractCSSVariables(cssContent: string): CSSVariable[] { 78 79 const variables: CSSVariable[] = []; ··· 96 97 const match = trimmed.match(/^(--[a-z0-9-]+)\s*:\s*([^;]+);/); 97 98 if (match) { 98 99 const [, name, value] = match; 99 - const category = categorizeCSSVariable(name); 100 + const category = categorizeCSSVar(name); 100 101 variables.push({ name, value: value.trim(), category }); 101 102 } 102 103 } ··· 108 109 /** 109 110 * Categorize CSS variable by name prefix 110 111 */ 111 - function categorizeCSSVariable(name: string): string { 112 + function categorizeCSSVar(name: string): string { 112 113 if (name.startsWith("--font")) return "Typography"; 113 114 if (name.startsWith("--line-height")) return "Typography"; 114 115 if (name.startsWith("--space")) return "Spacing"; ··· 121 122 return "Other"; 122 123 } 123 124 124 - /** 125 - * Validate CSS element coverage 126 - * Checks which HTML elements have styling defined 127 - */ 128 125 function validateElementCoverage(cssContent: string): ElementCoverage[] { 129 126 const elementsToCheck = [ 130 127 "html", ··· 274 271 return lines.join("\n"); 275 272 } 276 273 277 - /** 278 - * Group HTML elements by category for better organization 279 - */ 280 274 function groupElementsByCategory(elements: string[]): Record<string, string[]> { 281 275 const categories: Record<string, string[]> = { 282 276 "Document Structure": [], ··· 365 359 366 360 /** 367 361 * CSS documentation command implementation 362 + * 368 363 * Generates semantics.md from base.css 369 364 */ 370 365 export async function cssDocsCommand(): Promise<void> { ··· 399 394 const markdown = generateSemanticsDocs(comments, variables, coverage); 400 395 401 396 await mkdir(outputDir, { recursive: true }); 402 - await writeFile(outputPath, markdown, "utf8"); 397 + 398 + echo.info("\nTracking version..."); 399 + const versionedContent = await trackVersion(outputPath, markdown); 400 + await writeFile(outputPath, versionedContent, "utf8"); 403 401 404 402 echo.success(`\nCSS documentation generated: docs/css/semantics.md\n`); 405 403 echo.label("Summary:");
+3 -1
cli/src/commands/docs.ts
··· 2 2 import path from "node:path"; 3 3 import ts from "typescript"; 4 4 import { echo } from "../console/echo.js"; 5 + import { trackVersion } from "../versioning/tracker.js"; 5 6 6 7 type Member = { name: string; type: string; docs?: string }; 7 8 ··· 253 254 const markdown = generateMD(entries, moduleName, moduleDocs); 254 255 255 256 const outputPath = path.join(outputDir, `${moduleName}.md`); 256 - await writeFile(outputPath, markdown, "utf8"); 257 + const versionedContent = await trackVersion(outputPath, markdown); 258 + await writeFile(outputPath, versionedContent, "utf8"); 257 259 258 260 echo.ok(` Generated: ${relativePath} -> api/${moduleName}.md`); 259 261 }
+114
cli/src/versioning/differ.ts
··· 1 + import { createHash } from "node:crypto"; 2 + 3 + /** 4 + * Represents a section extracted from markdown 5 + */ 6 + export type Section = { heading: string; content: string; hash: string }; 7 + 8 + /** 9 + * Result of diffing two sets of sections 10 + */ 11 + export type SectionDiff = { added: number; removed: number; edited: number }; 12 + 13 + /** 14 + * Extract all ## and ### headings from markdown content 15 + */ 16 + export function extractSections(markdown: string): Section[] { 17 + const lines = markdown.split("\n"); 18 + const sections: Section[] = []; 19 + let currentSection: { heading: string; lines: string[] } | undefined = undefined; 20 + 21 + for (const line of lines) { 22 + const trimmed = line.trim(); 23 + 24 + if (trimmed.startsWith("## ") || trimmed.startsWith("### ")) { 25 + if (currentSection) { 26 + const content = currentSection.lines.join("\n").trim(); 27 + sections.push({ heading: currentSection.heading, content, hash: hashContent(content) }); 28 + } 29 + 30 + currentSection = { heading: trimmed, lines: [] }; 31 + } else if (currentSection) { 32 + currentSection.lines.push(line); 33 + } 34 + } 35 + 36 + if (currentSection) { 37 + const content = currentSection.lines.join("\n").trim(); 38 + sections.push({ heading: currentSection.heading, content, hash: hashContent(content) }); 39 + } 40 + 41 + return sections; 42 + } 43 + 44 + /** 45 + * Compare two sets of sections (matched by heading text) and calculate the diff 46 + */ 47 + export function diffSections(oldSections: Section[], newSections: Section[]): SectionDiff { 48 + const oldMap = new Map(oldSections.map((s) => [s.heading, s])); 49 + const newMap = new Map(newSections.map((s) => [s.heading, s])); 50 + 51 + let added = 0; 52 + let removed = 0; 53 + let edited = 0; 54 + 55 + for (const [heading, newSection] of newMap) { 56 + const oldSection = oldMap.get(heading); 57 + 58 + if (!oldSection) { 59 + added++; 60 + } else if (oldSection.hash !== newSection.hash) { 61 + edited++; 62 + } 63 + } 64 + 65 + // Find removed sections 66 + for (const heading of oldMap.keys()) { 67 + if (!newMap.has(heading)) { 68 + removed++; 69 + } 70 + } 71 + 72 + return { added, removed, edited }; 73 + } 74 + 75 + /** 76 + * Hash content using SHA-256 77 + */ 78 + function hashContent(content: string): string { 79 + return createHash("sha256").update(content).digest("hex"); 80 + } 81 + 82 + /** 83 + * Extract section headings as a simple string array 84 + */ 85 + export function extractHeadings(markdown: string): string[] { 86 + return extractSections(markdown).map((s) => s.heading); 87 + } 88 + 89 + /** 90 + * Hash entire markdown content (without frontmatter) 91 + */ 92 + export function hashMarkdown(markdown: string): string { 93 + const withoutFrontmatter = stripFrontmatter(markdown); 94 + return hashContent(withoutFrontmatter); 95 + } 96 + 97 + /** 98 + * Remove YAML frontmatter from markdown 99 + */ 100 + function stripFrontmatter(markdown: string): string { 101 + const lines = markdown.split("\n"); 102 + 103 + if (lines[0]?.trim() !== "---") { 104 + return markdown; 105 + } 106 + 107 + for (let i = 1; i < lines.length; i++) { 108 + if (lines[i].trim() === "---") { 109 + return lines.slice(i + 1).join("\n"); 110 + } 111 + } 112 + 113 + return markdown; 114 + }
+73
cli/src/versioning/storage.ts
··· 1 + import { mkdir, readFile, writeFile } from "node:fs/promises"; 2 + import path from "node:path"; 3 + 4 + /** 5 + * Version history entry for a single version 6 + */ 7 + export type VersionHistoryEntry = { 8 + version: string; 9 + date: string; 10 + hash: string; 11 + added: number; 12 + removed: number; 13 + edited: number; 14 + }; 15 + 16 + /** 17 + * Complete metadata for a single document 18 + */ 19 + export type DocMetadata = { 20 + version: string; 21 + updated: string; 22 + hash: string; 23 + sections: string[]; 24 + history: VersionHistoryEntry[]; 25 + }; 26 + 27 + /** 28 + * Complete metadata storage for all documents by mapping relative file paths to their metadata 29 + */ 30 + export type VersionsMetadata = Record<string, DocMetadata>; 31 + 32 + const METADATA_PATH = path.join(process.cwd(), "..", "docs", ".versions.json"); 33 + 34 + /** 35 + * Load the complete versions metadata from disk 36 + */ 37 + export async function loadMetadata(): Promise<VersionsMetadata> { 38 + try { 39 + const content = await readFile(METADATA_PATH, "utf8"); 40 + return JSON.parse(content); 41 + } catch (error) { 42 + if ((error as NodeJS.ErrnoException).code === "ENOENT") { 43 + return {}; 44 + } 45 + throw error; 46 + } 47 + } 48 + 49 + /** 50 + * Save the complete versions metadata to disk & creates the docs directory if it doesn't exist 51 + */ 52 + export async function saveMetadata(metadata: VersionsMetadata): Promise<void> { 53 + const docsDir = path.dirname(METADATA_PATH); 54 + await mkdir(docsDir, { recursive: true }); 55 + await writeFile(METADATA_PATH, JSON.stringify(metadata, null, 2), "utf8"); 56 + } 57 + 58 + /** 59 + * Get metadata for a specific document 60 + */ 61 + export async function getDocMetadata(relativePath: string): Promise<DocMetadata | undefined> { 62 + const metadata = await loadMetadata(); 63 + return metadata[relativePath]; 64 + } 65 + 66 + /** 67 + * Update metadata for a specific document or creates a new entry if document hasn't been versioned yet 68 + */ 69 + export async function updateDocMetadata(relativePath: string, docMeta: DocMetadata): Promise<void> { 70 + const metadata = await loadMetadata(); 71 + metadata[relativePath] = docMeta; 72 + await saveMetadata(metadata); 73 + }
+107
cli/src/versioning/tracker.ts
··· 1 + import path from "node:path"; 2 + import { diffSections, extractHeadings, extractSections, hashMarkdown } from "./differ.js"; 3 + import type { DocMetadata } from "./storage.js"; 4 + import { getDocMetadata, updateDocMetadata } from "./storage.js"; 5 + 6 + /** 7 + * Track version for a generated documentation file 8 + * Compares with previous version, calculates diff, bumps version, adds frontmatter 9 + * 10 + * @param filePath Absolute path to the documentation file 11 + * @param content Generated markdown content (without frontmatter) 12 + * @returns Content with frontmatter prepended 13 + */ 14 + export async function trackVersion(filePath: string, content: string): Promise<string> { 15 + const docsDir = path.join(process.cwd(), "..", "docs"); 16 + const relPath = path.relative(docsDir, filePath); 17 + 18 + const prevMeta = await getDocMetadata(relPath); 19 + 20 + const newSections = extractSections(content); 21 + const newHash = hashMarkdown(content); 22 + const today = new Date().toISOString().split("T")[0]; 23 + 24 + if (!prevMeta) { 25 + const initialMeta: DocMetadata = { 26 + version: "1.0", 27 + updated: today, 28 + hash: newHash, 29 + sections: extractHeadings(content), 30 + history: [{ version: "1.0", date: today, hash: newHash, added: newSections.length, removed: 0, edited: 0 }], 31 + }; 32 + 33 + await updateDocMetadata(relPath, initialMeta); 34 + return addFrontmatter(content, "1.0", today); 35 + } 36 + 37 + if (prevMeta.hash === newHash) { 38 + return addFrontmatter(content, prevMeta.version, prevMeta.updated); 39 + } 40 + 41 + const oldSections = extractSections(prevMeta.sections.map((h) => `${h}\n\nContent`).join("\n\n")); 42 + const diff = diffSections(oldSections, newSections); 43 + 44 + const newVersion = calculateVersionBump(diff, prevMeta.version); 45 + 46 + const newMeta: DocMetadata = { 47 + version: newVersion, 48 + updated: today, 49 + hash: newHash, 50 + sections: extractHeadings(content), 51 + history: [...prevMeta.history, { 52 + version: newVersion, 53 + date: today, 54 + hash: newHash, 55 + added: diff.added, 56 + removed: diff.removed, 57 + edited: diff.edited, 58 + }], 59 + }; 60 + 61 + await updateDocMetadata(relPath, newMeta); 62 + return addFrontmatter(content, newVersion, today); 63 + } 64 + 65 + /** 66 + * Calculate version bump based on diff 67 + * 68 + * Rules: 69 + * - Any sections removed → Major bump 70 + * - Any sections added → Major bump 71 + * - ≥4 sections edited → Major bump 72 + * - 1-3 sections edited → Minor bump 73 + * - No changes → No bump 74 + */ 75 + function calculateVersionBump(diff: { added: number; removed: number; edited: number }, current: string): string { 76 + const [major, minor] = current.split(".").map(Number); 77 + 78 + if (diff.removed > 0) { 79 + return `${major + 1}.0`; 80 + } 81 + 82 + if (diff.added > 0) { 83 + return `${major + 1}.0`; 84 + } 85 + 86 + if (diff.edited >= 4) { 87 + return `${major + 1}.0`; 88 + } 89 + 90 + if (diff.edited > 0) { 91 + return `${major}.${minor + 1}`; 92 + } 93 + 94 + return current; 95 + } 96 + 97 + /** 98 + * Add frontmatter to markdown content 99 + */ 100 + function addFrontmatter(content: string, version: string, date: string): string { 101 + return `--- 102 + version: ${version} 103 + updated: ${date} 104 + --- 105 + 106 + ${content}`; 107 + }
+158
docs/.versions.json
··· 1 + { 2 + "css/semantics.md": { 3 + "version": "1.0", 4 + "updated": "2025-10-18", 5 + "hash": "5771ebe5cced18431c542d5ac24f2e483ae0a2cd6368d3de30e62cbc17b05326", 6 + "sections": [ 7 + "## CSS Custom Properties", 8 + "### Typography", 9 + "### Spacing", 10 + "### Layout", 11 + "### Colors", 12 + "### Effects", 13 + "## Element Coverage", 14 + "### Styled Elements", 15 + "### Unstyled Elements", 16 + "## Documentation Comments", 17 + "### `:root`", 18 + "### `@media (prefers-color-scheme: dark)`", 19 + "### `*, *::before, *::after`", 20 + "### `html`", 21 + "### `body`", 22 + "### `h1, h2, h3, h4, h5, h6`", 23 + "### `h1`", 24 + "### `p`", 25 + "### `h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p`", 26 + "### `a`", 27 + "### `em`", 28 + "### `mark`", 29 + "### `sub, sup`", 30 + "### `small`", 31 + "### `ul, ol`", 32 + "### `li`", 33 + "### `li > ul, li > ol`", 34 + "### `dl`", 35 + "### `p:has(small)`", 36 + "### `p small`", 37 + "### `@media (max-width: 767px)`", 38 + "### `blockquote`", 39 + "### `cite`", 40 + "### `code`", 41 + "### `kbd`", 42 + "### `samp`", 43 + "### `var`", 44 + "### `pre`", 45 + "### `hr`", 46 + "### `table`", 47 + "### `thead`", 48 + "### `td`", 49 + "### `tbody tr:nth-child(even)`", 50 + "### `tbody tr:hover`", 51 + "### `form`", 52 + "### `fieldset`", 53 + "### `label`", 54 + "### `textarea`", 55 + "### `input[type=\"checkbox\"],`", 56 + "### `input[type=\"file\"]`", 57 + "### `input[type=\"range\"]`", 58 + "### `progress, meter`", 59 + "### `input[type=\"reset\"]`", 60 + "### `img`", 61 + "### `figure`", 62 + "### `video, audio`", 63 + "### `canvas, svg`", 64 + "### `iframe`", 65 + "### `article, section`", 66 + "### `aside`", 67 + "### `header`", 68 + "### `nav`", 69 + "### `details`", 70 + "### `.sr-only`", 71 + "### `@media print`", 72 + "### `@media (max-width: 768px)`", 73 + "### `@media (max-width: 480px)`" 74 + ], 75 + "history": [ 76 + { 77 + "version": "1.0", 78 + "date": "2025-10-18", 79 + "hash": "5771ebe5cced18431c542d5ac24f2e483ae0a2cd6368d3de30e62cbc17b05326", 80 + "added": 67, 81 + "removed": 0, 82 + "edited": 0 83 + } 84 + ] 85 + }, 86 + "api/binder.md": { 87 + "version": "1.0", 88 + "updated": "2025-10-18", 89 + "hash": "be8aac3c45149a3777d89f58e232935acb3720e6aa36f697eb11e58b416e0d09", 90 + "sections": ["## mount"], 91 + "history": [ 92 + { 93 + "version": "1.0", 94 + "date": "2025-10-18", 95 + "hash": "be8aac3c45149a3777d89f58e232935acb3720e6aa36f697eb11e58b416e0d09", 96 + "added": 1, 97 + "removed": 0, 98 + "edited": 0 99 + } 100 + ] 101 + }, 102 + "api/dom.md": { 103 + "version": "1.0", 104 + "updated": "2025-10-18", 105 + "hash": "72a59e5fa86de7b102089a7f84ff3d7c7674ab025dd75a4fb19264e50a7e32ab", 106 + "sections": [ 107 + "## walkDOM", 108 + "## hasVoltAttribute", 109 + "## getVoltAttributes", 110 + "## setText", 111 + "## setHTML", 112 + "## toggleClass", 113 + "## parseClassBinding" 114 + ], 115 + "history": [ 116 + { 117 + "version": "1.0", 118 + "date": "2025-10-18", 119 + "hash": "72a59e5fa86de7b102089a7f84ff3d7c7674ab025dd75a4fb19264e50a7e32ab", 120 + "added": 7, 121 + "removed": 0, 122 + "edited": 0 123 + } 124 + ] 125 + }, 126 + "api/evaluator.md": { 127 + "version": "1.0", 128 + "updated": "2025-10-18", 129 + "hash": "fc8aa3ad16b62bdb2d16f86a476070a1acacb944b407b8c5edd033cb44fbf7f4", 130 + "sections": ["## Scope", "## evaluate"], 131 + "history": [ 132 + { 133 + "version": "1.0", 134 + "date": "2025-10-18", 135 + "hash": "fc8aa3ad16b62bdb2d16f86a476070a1acacb944b407b8c5edd033cb44fbf7f4", 136 + "added": 2, 137 + "removed": 0, 138 + "edited": 0 139 + } 140 + ] 141 + }, 142 + "api/signal.md": { 143 + "version": "1.0", 144 + "updated": "2025-10-18", 145 + "hash": "fcd3af42730ec609900fb5d1355b100ef3ab2506d9dd00396d6a582a363409e4", 146 + "sections": ["## Signal", "## ComputedSignal", "## signal", "## computed", "## effect"], 147 + "history": [ 148 + { 149 + "version": "1.0", 150 + "date": "2025-10-18", 151 + "hash": "fcd3af42730ec609900fb5d1355b100ef3ab2506d9dd00396d6a582a363409e4", 152 + "added": 5, 153 + "removed": 0, 154 + "edited": 0 155 + } 156 + ] 157 + } 158 + }
+13 -19
docs/.vitepress/config.ts
··· 1 - import { defineConfig } from 'vitepress' 1 + import { defineConfig } from "vitepress"; 2 2 3 3 // https://vitepress.dev/reference/site-config 4 4 export default defineConfig({ 5 5 title: "Volt.js", 6 6 description: "A reactive, hypermedia framework.", 7 + appearance: "dark", 7 8 themeConfig: { 8 - // https://vitepress.dev/reference/default-theme-config 9 - nav: [ 10 - { text: 'Home', link: '/' }, 11 - { text: 'Examples', link: '/markdown-examples' } 12 - ], 13 - 9 + nav: [{ text: "Home", link: "/" }, { text: "CSS", link: "/css/volt-css" }, { text: "API", link: "/api-examples" }], 14 10 sidebar: [ 11 + { text: "Getting Started", items: [{ text: "Introduction", link: "/" }] }, 15 12 { 16 - text: 'Examples', 17 - items: [ 18 - { text: 'Markdown Examples', link: '/markdown-examples' }, 19 - { text: 'Runtime API Examples', link: '/api-examples' } 20 - ] 21 - } 13 + text: "CSS", 14 + collapsed: false, 15 + items: [{ text: "Volt CSS", link: "/css/volt-css" }, { text: "CSS Reference", link: "/css/semantics" }], 16 + }, 17 + { text: "API Reference", collapsed: false, items: [{ text: "Runtime API", link: "/api-examples" }] }, 18 + { text: "Examples", collapsed: true, items: [{ text: "Markdown Examples", link: "/markdown-examples" }] }, 22 19 ], 23 - 24 - socialLinks: [ 25 - { icon: 'github', link: 'https://github.com/vuejs/vitepress' } 26 - ] 27 - } 28 - }) 20 + socialLinks: [{ icon: "github", link: "https://github.com/stormlightlabs/volt" }], 21 + }, 22 + });
+8 -11
docs/.vitepress/theme/index.ts
··· 1 1 // https://vitepress.dev/guide/custom-theme 2 - import { h } from 'vue' 3 - import type { Theme } from 'vitepress' 4 - import DefaultTheme from 'vitepress/theme' 5 - import './style.css' 2 + import type { Theme } from "vitepress"; 3 + import DefaultTheme from "vitepress/theme"; 4 + import { h } from "vue"; 5 + import "./style.css"; 6 6 7 7 export default { 8 8 extends: DefaultTheme, 9 + // https://vitepress.dev/guide/extending-default-theme#layout-slots 9 10 Layout: () => { 10 - return h(DefaultTheme.Layout, null, { 11 - // https://vitepress.dev/guide/extending-default-theme#layout-slots 12 - }) 11 + return h(DefaultTheme.Layout, null, {}); 13 12 }, 14 - enhanceApp({ app, router, siteData }) { 15 - // ... 16 - } 17 - } satisfies Theme 13 + enhanceApp({ app, router, siteData }) {}, 14 + } satisfies Theme;
+73 -70
docs/.vitepress/theme/style.css
··· 4 4 */ 5 5 6 6 /** 7 - * Colors 8 - * 9 - * Each colors have exact same color scale system with 3 levels of solid 10 - * colors with different brightness, and 1 soft color. 11 - * 12 - * - `XXX-1`: The most solid color used mainly for colored text. It must 13 - * satisfy the contrast ratio against when used on top of `XXX-soft`. 14 - * 15 - * - `XXX-2`: The color used mainly for hover state of the button. 16 - * 17 - * - `XXX-3`: The color for solid background, such as bg color of the button. 18 - * It must satisfy the contrast ratio with pure white (#ffffff) text on 19 - * top of it. 20 - * 21 - * - `XXX-soft`: The color used for subtle background such as custom container 22 - * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors 23 - * on top of it. 24 - * 25 - * The soft color must be semi transparent alpha channel. This is crucial 26 - * because it allows adding multiple "soft" colors on top of each other 27 - * to create a accent, such as when having inline code block inside 28 - * custom containers. 29 - * 30 - * - `default`: The color used purely for subtle indication without any 31 - * special meanings attached to it such as bg color for menu hover state. 32 - * 33 - * - `brand`: Used for primary brand colors, such as link text, button with 34 - * brand theme, etc. 35 - * 36 - * - `tip`: Used to indicate useful information. The default theme uses the 37 - * brand color for this by default. 38 - * 39 - * - `warning`: Used to indicate warning to the users. Used in custom 40 - * container, badges, etc. 41 - * 42 - * - `danger`: Used to show error, or dangerous message to the users. Used 43 - * in custom container, badges, etc. 44 - * -------------------------------------------------------------------------- */ 7 + * Seattle SuperSonics Color Palette 8 + * - Emerald Green (#173F35) - Primary 9 + * - Red (#9E2A2F) - Danger 10 + * - Yellow (#FFA300) - Secondary/Warning 11 + * - Bronze (#8B634B) - Accent 12 + **/ 13 + :root { 14 + /* SuperSonics Emerald Green - Primary Brand */ 15 + --vp-c-sonics-green-1: #2a6b5d; 16 + --vp-c-sonics-green-2: #1f5248; 17 + --vp-c-sonics-green-3: #173F35; 18 + --vp-c-sonics-green-soft: rgba(23, 63, 53, 0.14); 45 19 20 + /* SuperSonics Yellow - Secondary/Warning */ 21 + --vp-c-sonics-yellow-1: #ffbb33; 22 + --vp-c-sonics-yellow-2: #ffad1a; 23 + --vp-c-sonics-yellow-3: #FFA300; 24 + --vp-c-sonics-yellow-soft: rgba(255, 163, 0, 0.14); 25 + 26 + /* SuperSonics Red - Danger */ 27 + --vp-c-sonics-red-1: #c24348; 28 + --vp-c-sonics-red-2: #b03338; 29 + --vp-c-sonics-red-3: #9E2A2F; 30 + --vp-c-sonics-red-soft: rgba(158, 42, 47, 0.14); 31 + 32 + /* SuperSonics Bronze - Accent */ 33 + --vp-c-sonics-bronze-1: #a3836b; 34 + --vp-c-sonics-bronze-2: #97735a; 35 + --vp-c-sonics-bronze-3: #8B634B; 36 + --vp-c-sonics-bronze-soft: rgba(139, 99, 75, 0.14); 37 + } 38 + 39 + /** 40 + * Dark Theme Colors 41 + **/ 46 42 :root { 47 43 --vp-c-default-1: var(--vp-c-gray-1); 48 44 --vp-c-default-2: var(--vp-c-gray-2); 49 45 --vp-c-default-3: var(--vp-c-gray-3); 50 46 --vp-c-default-soft: var(--vp-c-gray-soft); 51 47 52 - --vp-c-brand-1: var(--vp-c-indigo-1); 53 - --vp-c-brand-2: var(--vp-c-indigo-2); 54 - --vp-c-brand-3: var(--vp-c-indigo-3); 55 - --vp-c-brand-soft: var(--vp-c-indigo-soft); 48 + /* Use Emerald Green as primary brand color */ 49 + --vp-c-brand-1: var(--vp-c-sonics-green-1); 50 + --vp-c-brand-2: var(--vp-c-sonics-green-2); 51 + --vp-c-brand-3: var(--vp-c-sonics-green-3); 52 + --vp-c-brand-soft: var(--vp-c-sonics-green-soft); 56 53 57 - --vp-c-tip-1: var(--vp-c-brand-1); 58 - --vp-c-tip-2: var(--vp-c-brand-2); 59 - --vp-c-tip-3: var(--vp-c-brand-3); 60 - --vp-c-tip-soft: var(--vp-c-brand-soft); 54 + /* Tips use Bronze accent */ 55 + --vp-c-tip-1: var(--vp-c-sonics-bronze-1); 56 + --vp-c-tip-2: var(--vp-c-sonics-bronze-2); 57 + --vp-c-tip-3: var(--vp-c-sonics-bronze-3); 58 + --vp-c-tip-soft: var(--vp-c-sonics-bronze-soft); 61 59 62 - --vp-c-warning-1: var(--vp-c-yellow-1); 63 - --vp-c-warning-2: var(--vp-c-yellow-2); 64 - --vp-c-warning-3: var(--vp-c-yellow-3); 65 - --vp-c-warning-soft: var(--vp-c-yellow-soft); 60 + /* Warnings use Yellow */ 61 + --vp-c-warning-1: var(--vp-c-sonics-yellow-1); 62 + --vp-c-warning-2: var(--vp-c-sonics-yellow-2); 63 + --vp-c-warning-3: var(--vp-c-sonics-yellow-3); 64 + --vp-c-warning-soft: var(--vp-c-sonics-yellow-soft); 66 65 67 - --vp-c-danger-1: var(--vp-c-red-1); 68 - --vp-c-danger-2: var(--vp-c-red-2); 69 - --vp-c-danger-3: var(--vp-c-red-3); 70 - --vp-c-danger-soft: var(--vp-c-red-soft); 66 + /* Danger uses Red */ 67 + --vp-c-danger-1: var(--vp-c-sonics-red-1); 68 + --vp-c-danger-2: var(--vp-c-sonics-red-2); 69 + --vp-c-danger-3: var(--vp-c-sonics-red-3); 70 + --vp-c-danger-soft: var(--vp-c-sonics-red-soft); 71 + } 72 + 73 + .dark { 74 + /* Enhance dark mode background */ 75 + --vp-c-bg: #0d1117; 76 + --vp-c-bg-soft: #161b22; 77 + --vp-c-bg-mute: #1c2128; 71 78 } 72 79 73 80 /** 74 - * Component: Button 75 - * -------------------------------------------------------------------------- */ 76 - 81 + * Button 82 + **/ 77 83 :root { 78 84 --vp-button-brand-border: transparent; 79 85 --vp-button-brand-text: var(--vp-c-white); ··· 87 93 } 88 94 89 95 /** 90 - * Component: Home 91 - * -------------------------------------------------------------------------- */ 92 - 96 + * Home 97 + **/ 93 98 :root { 94 99 --vp-home-hero-name-color: transparent; 95 100 --vp-home-hero-name-background: -webkit-linear-gradient( 96 101 120deg, 97 - #bd34fe 30%, 98 - #41d1ff 102 + #FFA300 30%, 103 + #2a6b5d 99 104 ); 100 105 101 106 --vp-home-hero-image-background-image: linear-gradient( 102 107 -45deg, 103 - #bd34fe 50%, 104 - #47caff 50% 108 + #FFA300 50%, 109 + #173F35 50% 105 110 ); 106 111 --vp-home-hero-image-filter: blur(44px); 107 112 } ··· 119 124 } 120 125 121 126 /** 122 - * Component: Custom Block 123 - * -------------------------------------------------------------------------- */ 124 - 127 + * Custom Block 128 + **/ 125 129 :root { 126 130 --vp-custom-block-tip-border: transparent; 127 131 --vp-custom-block-tip-text: var(--vp-c-text-1); ··· 130 134 } 131 135 132 136 /** 133 - * Component: Algolia 134 - * -------------------------------------------------------------------------- */ 135 - 137 + * Algolia 138 + **/ 136 139 .DocSearch { 137 140 --docsearch-primary-color: var(--vp-c-brand-1) !important; 138 141 }
+18
docs/api/binder.md
··· 1 + --- 2 + version: 1.0 3 + updated: 2025-10-18 4 + --- 5 + 6 + # binder 7 + 8 + Binder system for mounting and managing Volt.js bindings 9 + 10 + ## mount 11 + 12 + Mount Volt.js on a root element and its descendants. 13 + Binds all data-x-* attributes to the provided scope. 14 + Returns a cleanup function to unmount and dispose all bindings. 15 + 16 + ```typescript 17 + export function mount(root: Element, scope: Scope): CleanupFunction 18 + ```
+67
docs/api/dom.md
··· 1 + --- 2 + version: 1.0 3 + updated: 2025-10-18 4 + --- 5 + 6 + # dom 7 + 8 + DOM utility functions 9 + 10 + ## walkDOM 11 + 12 + Walk the DOM tree and collect all elements with data-x-* attributes. 13 + Returns elements in document order (parent before children). 14 + 15 + ```typescript 16 + export function walkDOM(root: Element): Element[] 17 + ``` 18 + 19 + ## hasVoltAttribute 20 + 21 + Check if an element has any data-x-* attributes. 22 + 23 + ```typescript 24 + export function hasVoltAttribute(element: Element): boolean 25 + ``` 26 + 27 + ## getVoltAttributes 28 + 29 + Get all data-x-* attributes from an element. 30 + 31 + ```typescript 32 + export function getVoltAttributes(element: Element): Map<string, string> 33 + ``` 34 + 35 + ## setText 36 + 37 + Set the text content of an element safely. 38 + 39 + ```typescript 40 + export function setText(element: Element, value: unknown): void 41 + ``` 42 + 43 + ## setHTML 44 + 45 + Set the HTML content of an element safely. 46 + Note: This trusts the input HTML and should only be used with sanitized content. 47 + 48 + ```typescript 49 + export function setHTML(element: Element, value: string): void 50 + ``` 51 + 52 + ## toggleClass 53 + 54 + Add or remove a CSS class from an element. 55 + 56 + ```typescript 57 + export function toggleClass(element: Element, className: string, add: boolean): void 58 + ``` 59 + 60 + ## parseClassBinding 61 + 62 + Parse a class binding expression. 63 + Supports both string values ("active") and object notation ({active: true}). 64 + 65 + ```typescript 66 + export function parseClassBinding(value: unknown): Map<string, boolean> 67 + ```
+29
docs/api/evaluator.md
··· 1 + --- 2 + version: 1.0 3 + updated: 2025-10-18 4 + --- 5 + 6 + # evaluator 7 + 8 + Safe expression evaluation of simple expressions without using eval() for bindings 9 + 10 + ## Scope 11 + 12 + Safe expression evaluation of simple expressions without using eval() for bindings 13 + 14 + ```typescript 15 + Record<string, unknown> 16 + ``` 17 + 18 + ## evaluate 19 + 20 + Evaluate a simple expression against a scope object. 21 + Supports: 22 + - Property access: "count", "user.name", "items.length" 23 + - Simple literals: "true", "false", "null", "undefined" 24 + - Numbers: "42", "3.14" 25 + - Strings: "'hello'", '"world"' 26 + 27 + ```typescript 28 + export function evaluate(expression: string, scope: Scope): unknown 29 + ```
+70
docs/api/signal.md
··· 1 + --- 2 + version: 1.0 3 + updated: 2025-10-18 4 + --- 5 + 6 + # signal 7 + 8 + A reactive primitive that notifies subscribers when its value changes. 9 + 10 + ## Signal 11 + 12 + A reactive primitive that notifies subscribers when its value changes. 13 + 14 + ## ComputedSignal 15 + 16 + A computed signal that derives its value from other signals. 17 + 18 + ## signal 19 + 20 + Creates a new signal with the given initial value. 21 + Signals are reactive primitives that automatically notify subscribers when changed. 22 + 23 + ```typescript 24 + export function signal<T>(initialValue: T): Signal<T> 25 + ``` 26 + 27 + **Example:** 28 + 29 + ```typescript 30 + const count = signal(0); 31 + count.subscribe(value => console.log('Count:', value)); 32 + count.set(1); // Logs: Count: 1 33 + ``` 34 + 35 + ## computed 36 + 37 + Creates a computed signal that derives its value from other signals. 38 + The computation function is re-run whenever any of its dependencies change. 39 + 40 + ```typescript 41 + export function computed<T>( compute: () => T, dependencies: Array<Signal<unknown> | ComputedSignal<unknown>>, ): ComputedSignal<T> 42 + ``` 43 + 44 + **Example:** 45 + 46 + ```typescript 47 + const count = signal(5); 48 + const doubled = computed(() => count.get() * 2, [count]); 49 + doubled.get(); // 10 50 + count.set(10); 51 + doubled.get(); // 20 52 + ``` 53 + 54 + ## effect 55 + 56 + Creates a side effect that runs when dependencies change. 57 + Effects run immediately on creation and whenever dependencies update. 58 + 59 + ```typescript 60 + export function effect( effectFunction: () => void | (() => void), dependencies: Array<Signal<unknown> | ComputedSignal<unknown>>, ): () => void 61 + ``` 62 + 63 + **Example:** 64 + 65 + ```typescript 66 + const count = signal(0); 67 + const cleanup = effect(() => { 68 + console.log('Count changed:', count.get()); 69 + }, [count]); 70 + ```
+5
docs/css/semantics.md
··· 1 + --- 2 + version: 1.0 3 + updated: 2025-10-18 4 + --- 5 + 1 6 # Volt CSS Semantics 2 7 3 8 Auto-generated documentation from base.css