@vizejs/vite-plugin-musea 0.0.1-alpha.11
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/README.md +56 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +222 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1512 -0
- package/dist/index.js.map +1 -0
- package/dist/vrt-BfuTRv-J.d.ts +217 -0
- package/dist/vrt-BfuTRv-J.d.ts.map +1 -0
- package/dist/vrt-DRwtnkE5.js +773 -0
- package/dist/vrt-DRwtnkE5.js.map +1 -0
- package/dist/vrt.d.ts +2 -0
- package/dist/vrt.js +3 -0
- package/package.json +76 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["tokensPath: string","dirPath: string","categories: TokenCategory[]","tokens: Record<string, unknown>","prefix: string[]","obj: Record<string, unknown>","tokens: Record<string, DesignToken>","subcategories: TokenCategory[]","value: unknown","raw: Record<string, unknown>","name: string","token: DesignToken","category: TokenCategory","level: number","config: StyleDictionaryConfig","content: string","filename: string","native: NativeBinding | null","options: MuseaOptions","config: ResolvedConfig","server: ViteDevServer | null","mainPlugin: Plugin","filePath: string","info: ArtFileInfo","file: string","include: string[]","exclude: string[]","root: string","filepath: string","pattern: string","files: string[]","dir: string","basePath: string","art: ArtFileInfo","variantComponentName: string","variantName: string","artFiles: Map<string, ArtFileInfo>","outDir: string","str: string","variant: ArtVariant"],"sources":["../src/style-dictionary.ts","../src/index.ts"],"sourcesContent":["/**\n * Style Dictionary integration for Musea.\n * Generates design token documentation from Style Dictionary format.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Design token value.\n */\nexport interface DesignToken {\n value: string | number;\n type?: string;\n description?: string;\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Token category (e.g., colors, spacing, typography).\n */\nexport interface TokenCategory {\n name: string;\n tokens: Record<string, DesignToken>;\n subcategories?: TokenCategory[];\n}\n\n/**\n * Style Dictionary output format.\n */\nexport interface StyleDictionaryOutput {\n categories: TokenCategory[];\n metadata: {\n name: string;\n version?: string;\n generatedAt: string;\n };\n}\n\n/**\n * Configuration for Style Dictionary integration.\n */\nexport interface StyleDictionaryConfig {\n /**\n * Path to tokens JSON/JS file or directory.\n */\n tokensPath: string;\n\n /**\n * Output format for documentation.\n * @default 'html'\n */\n outputFormat?: \"html\" | \"json\" | \"markdown\";\n\n /**\n * Output directory for generated documentation.\n * @default '.vize/tokens'\n */\n outputDir?: string;\n\n /**\n * Custom token transformations.\n */\n transforms?: TokenTransform[];\n}\n\n/**\n * Token transformation function.\n */\nexport type TokenTransform = (token: DesignToken, path: string[]) => DesignToken;\n\n/**\n * Parse Style Dictionary tokens file.\n */\nexport async function parseTokens(tokensPath: string): Promise<TokenCategory[]> {\n const absolutePath = path.resolve(tokensPath);\n const stat = await fs.promises.stat(absolutePath);\n\n if (stat.isDirectory()) {\n return parseTokenDirectory(absolutePath);\n }\n\n const content = await fs.promises.readFile(absolutePath, \"utf-8\");\n const tokens = JSON.parse(content);\n return flattenTokens(tokens);\n}\n\n/**\n * Parse tokens from a directory.\n */\nasync function parseTokenDirectory(dirPath: string): Promise<TokenCategory[]> {\n const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });\n const categories: TokenCategory[] = [];\n\n for (const entry of entries) {\n if (entry.isFile() && (entry.name.endsWith(\".json\") || entry.name.endsWith(\".tokens.json\"))) {\n const filePath = path.join(dirPath, entry.name);\n const content = await fs.promises.readFile(filePath, \"utf-8\");\n const tokens = JSON.parse(content);\n const categoryName = path\n .basename(entry.name, path.extname(entry.name))\n .replace(\".tokens\", \"\");\n\n categories.push({\n name: formatCategoryName(categoryName),\n tokens: extractTokens(tokens),\n subcategories: extractSubcategories(tokens),\n });\n }\n }\n\n return categories;\n}\n\n/**\n * Flatten nested token structure into categories.\n */\nfunction flattenTokens(tokens: Record<string, unknown>, prefix: string[] = []): TokenCategory[] {\n const categories: TokenCategory[] = [];\n\n for (const [key, value] of Object.entries(tokens)) {\n if (isTokenValue(value)) {\n // This is a token leaf node\n continue;\n }\n\n if (typeof value === \"object\" && value !== null) {\n const categoryTokens = extractTokens(value as Record<string, unknown>);\n const subcategories = flattenTokens(value as Record<string, unknown>, [...prefix, key]);\n\n if (Object.keys(categoryTokens).length > 0 || subcategories.length > 0) {\n categories.push({\n name: formatCategoryName(key),\n tokens: categoryTokens,\n subcategories: subcategories.length > 0 ? subcategories : undefined,\n });\n }\n }\n }\n\n return categories;\n}\n\n/**\n * Extract token values from an object.\n */\nfunction extractTokens(obj: Record<string, unknown>): Record<string, DesignToken> {\n const tokens: Record<string, DesignToken> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n if (isTokenValue(value)) {\n tokens[key] = normalizeToken(value as Record<string, unknown>);\n }\n }\n\n return tokens;\n}\n\n/**\n * Extract subcategories from an object.\n */\nfunction extractSubcategories(obj: Record<string, unknown>): TokenCategory[] | undefined {\n const subcategories: TokenCategory[] = [];\n\n for (const [key, value] of Object.entries(obj)) {\n if (!isTokenValue(value) && typeof value === \"object\" && value !== null) {\n const categoryTokens = extractTokens(value as Record<string, unknown>);\n const nested = extractSubcategories(value as Record<string, unknown>);\n\n if (Object.keys(categoryTokens).length > 0 || (nested && nested.length > 0)) {\n subcategories.push({\n name: formatCategoryName(key),\n tokens: categoryTokens,\n subcategories: nested,\n });\n }\n }\n }\n\n return subcategories.length > 0 ? subcategories : undefined;\n}\n\n/**\n * Check if a value is a token definition.\n */\nfunction isTokenValue(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const obj = value as Record<string, unknown>;\n return \"value\" in obj && (typeof obj.value === \"string\" || typeof obj.value === \"number\");\n}\n\n/**\n * Normalize token to DesignToken interface.\n */\nfunction normalizeToken(raw: Record<string, unknown>): DesignToken {\n return {\n value: raw.value as string | number,\n type: raw.type as string | undefined,\n description: raw.description as string | undefined,\n attributes: raw.attributes as Record<string, unknown> | undefined,\n };\n}\n\n/**\n * Format category name for display.\n */\nfunction formatCategoryName(name: string): string {\n return name\n .replace(/[-_]/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .split(\" \")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\" \");\n}\n\n/**\n * Generate HTML documentation for tokens.\n */\nexport function generateTokensHtml(categories: TokenCategory[]): string {\n const renderToken = (name: string, token: DesignToken): string => {\n const isColor =\n typeof token.value === \"string\" &&\n (token.value.startsWith(\"#\") ||\n token.value.startsWith(\"rgb\") ||\n token.value.startsWith(\"hsl\") ||\n token.type === \"color\");\n\n return `\n <div class=\"token\">\n <div class=\"token-preview\">\n ${isColor ? `<div class=\"color-swatch\" style=\"background: ${token.value}\"></div>` : \"\"}\n </div>\n <div class=\"token-info\">\n <div class=\"token-name\">${name}</div>\n <div class=\"token-value\">${token.value}</div>\n ${token.description ? `<div class=\"token-description\">${token.description}</div>` : \"\"}\n </div>\n </div>\n `;\n };\n\n const renderCategory = (category: TokenCategory, level: number = 2): string => {\n const heading = `h${Math.min(level, 6)}`;\n let html = `<${heading}>${category.name}</${heading}>`;\n html += '<div class=\"tokens-grid\">';\n\n for (const [name, token] of Object.entries(category.tokens)) {\n html += renderToken(name, token);\n }\n\n html += \"</div>\";\n\n if (category.subcategories) {\n for (const sub of category.subcategories) {\n html += renderCategory(sub, level + 1);\n }\n }\n\n return html;\n };\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Design Tokens - Musea</title>\n <style>\n :root {\n --musea-bg: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-accent: #a34828;\n --musea-border: #3a3530;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, sans-serif;\n background: var(--musea-bg);\n color: var(--musea-text);\n line-height: 1.6;\n padding: 2rem;\n }\n h1 { margin-bottom: 2rem; color: var(--musea-accent); }\n h2 { margin: 2rem 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--musea-border); }\n h3, h4, h5, h6 { margin: 1.5rem 0 0.75rem; }\n .tokens-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 1rem;\n margin-bottom: 1.5rem;\n }\n .token {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: 8px;\n padding: 1rem;\n display: flex;\n gap: 1rem;\n align-items: center;\n }\n .token-preview {\n flex-shrink: 0;\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .color-swatch {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n border: 1px solid var(--musea-border);\n }\n .token-info {\n flex: 1;\n min-width: 0;\n }\n .token-name {\n font-weight: 600;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.875rem;\n }\n .token-value {\n color: var(--musea-text-muted);\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.75rem;\n word-break: break-all;\n }\n .token-description {\n color: var(--musea-text-muted);\n font-size: 0.75rem;\n margin-top: 0.25rem;\n }\n </style>\n</head>\n<body>\n <h1>Design Tokens</h1>\n ${categories.map((cat) => renderCategory(cat)).join(\"\")}\n</body>\n</html>`;\n}\n\n/**\n * Generate Markdown documentation for tokens.\n */\nexport function generateTokensMarkdown(categories: TokenCategory[]): string {\n const renderCategory = (category: TokenCategory, level: number = 2): string => {\n const heading = \"#\".repeat(level);\n let md = `\\n${heading} ${category.name}\\n\\n`;\n\n if (Object.keys(category.tokens).length > 0) {\n md += \"| Token | Value | Description |\\n\";\n md += \"|-------|-------|-------------|\\n\";\n\n for (const [name, token] of Object.entries(category.tokens)) {\n const desc = token.description || \"-\";\n md += `| \\`${name}\\` | \\`${token.value}\\` | ${desc} |\\n`;\n }\n md += \"\\n\";\n }\n\n if (category.subcategories) {\n for (const sub of category.subcategories) {\n md += renderCategory(sub, level + 1);\n }\n }\n\n return md;\n };\n\n let markdown = \"# Design Tokens\\n\\n\";\n markdown += `> Generated by Musea on ${new Date().toISOString()}\\n`;\n\n for (const category of categories) {\n markdown += renderCategory(category);\n }\n\n return markdown;\n}\n\n/**\n * Style Dictionary plugin for Musea.\n */\nexport async function processStyleDictionary(\n config: StyleDictionaryConfig,\n): Promise<StyleDictionaryOutput> {\n const categories = await parseTokens(config.tokensPath);\n const outputDir = config.outputDir ?? \".vize/tokens\";\n const outputFormat = config.outputFormat ?? \"html\";\n\n // Ensure output directory exists\n await fs.promises.mkdir(outputDir, { recursive: true });\n\n // Generate documentation\n let content: string;\n let filename: string;\n\n switch (outputFormat) {\n case \"html\":\n content = generateTokensHtml(categories);\n filename = \"tokens.html\";\n break;\n case \"markdown\":\n content = generateTokensMarkdown(categories);\n filename = \"tokens.md\";\n break;\n case \"json\":\n default:\n content = JSON.stringify({ categories }, null, 2);\n filename = \"tokens.json\";\n }\n\n const outputPath = path.join(outputDir, filename);\n await fs.promises.writeFile(outputPath, content, \"utf-8\");\n\n console.log(`[musea] Generated token documentation: ${outputPath}`);\n\n return {\n categories,\n metadata: {\n name: path.basename(config.tokensPath),\n generatedAt: new Date().toISOString(),\n },\n };\n}\n\nexport default processStyleDictionary;\n","/**\n * Vite plugin for Musea - Component gallery for Vue components.\n *\n * @example\n * ```ts\n * import { defineConfig } from 'vite';\n * import { vize } from '@vizejs/vite-plugin';\n * import { musea } from '@vizejs/vite-plugin-musea';\n *\n * export default defineConfig({\n * plugins: [vize(), musea()],\n * });\n * ```\n */\n\nimport type { Plugin, ViteDevServer, ResolvedConfig } from \"vite\";\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { MuseaOptions, ArtFileInfo, ArtMetadata, ArtVariant, CsfOutput } from \"./types.js\";\n\nexport type {\n MuseaOptions,\n ArtFileInfo,\n ArtMetadata,\n ArtVariant,\n CsfOutput,\n VrtOptions,\n ViewportConfig,\n} from \"./types.js\";\n\nexport {\n MuseaVrtRunner,\n generateVrtReport,\n generateVrtJsonReport,\n type VrtResult,\n type VrtSummary,\n} from \"./vrt.js\";\n\nexport {\n processStyleDictionary,\n parseTokens,\n generateTokensHtml,\n generateTokensMarkdown,\n type DesignToken,\n type TokenCategory,\n type StyleDictionaryConfig,\n type StyleDictionaryOutput,\n} from \"./style-dictionary.js\";\n\n// Virtual module prefixes\nconst VIRTUAL_MUSEA_PREFIX = \"\\0musea:\";\nconst VIRTUAL_GALLERY = \"\\0musea-gallery\";\nconst VIRTUAL_MANIFEST = \"\\0musea-manifest\";\n\n// Native binding types\ninterface NativeBinding {\n parseArt: (\n source: string,\n options?: { filename?: string },\n ) => {\n filename: string;\n metadata: {\n title: string;\n description?: string;\n component?: string;\n category?: string;\n tags: string[];\n status: string;\n order?: number;\n };\n variants: Array<{\n name: string;\n template: string;\n is_default: boolean;\n skip_vrt: boolean;\n }>;\n has_script_setup: boolean;\n has_script: boolean;\n style_count: number;\n };\n artToCsf: (\n source: string,\n options?: { filename?: string },\n ) => {\n code: string;\n filename: string;\n };\n}\n\n// Lazy-load native binding\nlet native: NativeBinding | null = null;\n\nfunction loadNative(): NativeBinding {\n if (native) return native;\n\n const require = createRequire(import.meta.url);\n try {\n native = require(\"@vizejs/native\") as NativeBinding;\n return native;\n } catch (e) {\n throw new Error(\n `Failed to load @vizejs/native. Make sure it's installed and built:\\n${String(e)}`,\n );\n }\n}\n\n/**\n * Create Musea Vite plugin.\n */\nexport function musea(options: MuseaOptions = {}): Plugin[] {\n const include = options.include ?? [\"**/*.art.vue\"];\n const exclude = options.exclude ?? [\"node_modules/**\", \"dist/**\"];\n const basePath = options.basePath ?? \"/__musea__\";\n const storybookCompat = options.storybookCompat ?? false;\n const storybookOutDir = options.storybookOutDir ?? \".storybook/stories\";\n\n let config: ResolvedConfig;\n let server: ViteDevServer | null = null;\n const artFiles = new Map<string, ArtFileInfo>();\n\n // Main plugin\n const mainPlugin: Plugin = {\n name: \"vite-plugin-musea\",\n enforce: \"pre\",\n\n config() {\n // Add Vue alias for runtime template compilation\n // This is needed because variant templates are compiled at runtime\n return {\n resolve: {\n alias: {\n vue: \"vue/dist/vue.esm-bundler.js\",\n },\n },\n };\n },\n\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n },\n\n configureServer(devServer) {\n server = devServer;\n\n // Gallery UI route\n devServer.middlewares.use(basePath, async (req, res, next) => {\n if (req.url === \"/\" || req.url === \"/index.html\") {\n const html = generateGalleryHtml(basePath);\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n return;\n }\n next();\n });\n\n // Preview module route - serves the JavaScript module for a specific variant\n devServer.middlewares.use(`${basePath}/preview-module`, async (req, res, _next) => {\n const url = new URL(req.url || \"\", `http://localhost`);\n const artPath = url.searchParams.get(\"art\");\n const variantName = url.searchParams.get(\"variant\");\n\n if (!artPath || !variantName) {\n res.statusCode = 400;\n res.end(\"Missing art or variant parameter\");\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found\");\n return;\n }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) {\n res.statusCode = 404;\n res.end(\"Variant not found\");\n return;\n }\n\n const variantComponentName = toPascalCase(variant.name);\n const moduleCode = generatePreviewModule(art, variantComponentName, variant.name);\n\n // Transform the module through Vite to resolve imports\n try {\n const result = await devServer.transformRequest(\n `virtual:musea-preview:${artPath}:${variantName}`,\n );\n if (result) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(result.code);\n return;\n }\n } catch {\n // Fall through to manual response\n }\n\n // Fallback: serve the module directly (imports won't be resolved)\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(moduleCode);\n });\n\n // VRT preview route - renders a single variant for screenshot\n devServer.middlewares.use(`${basePath}/preview`, async (req, res, _next) => {\n const url = new URL(req.url || \"\", `http://localhost`);\n const artPath = url.searchParams.get(\"art\");\n const variantName = url.searchParams.get(\"variant\");\n\n if (!artPath || !variantName) {\n res.statusCode = 400;\n res.end(\"Missing art or variant parameter\");\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found\");\n return;\n }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) {\n res.statusCode = 404;\n res.end(\"Variant not found\");\n return;\n }\n\n const rawHtml = generatePreviewHtml(art, variant, basePath);\n // Transform HTML through Vite to properly resolve module imports\n const html = await devServer.transformIndexHtml(\n `${basePath}/preview?art=${encodeURIComponent(artPath)}&variant=${encodeURIComponent(variantName)}`,\n rawHtml,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n });\n\n // Art module route - serves transformed art file as ES module\n devServer.middlewares.use(`${basePath}/art`, async (req, res, next) => {\n const url = new URL(req.url || \"\", \"http://localhost\");\n const artPath = decodeURIComponent(url.pathname.slice(1)); // Remove leading /\n\n if (!artPath) {\n next();\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found: \" + artPath);\n return;\n }\n\n // Transform through Vite for proper imports\n try {\n const virtualId = `virtual:musea-art:${artPath}`;\n const result = await devServer.transformRequest(virtualId);\n if (result) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(result.code);\n } else {\n // Fallback: generate and serve the module directly\n const moduleCode = generateArtModule(art, artPath);\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n }\n } catch (err) {\n console.error(\"[musea] Failed to transform art module:\", err);\n // Fallback if transform fails\n const moduleCode = generateArtModule(art, artPath);\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n }\n });\n\n // API endpoints\n devServer.middlewares.use(`${basePath}/api`, async (req, res, next) => {\n // GET /api/arts - List all arts\n if (req.url === \"/arts\" && req.method === \"GET\") {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(Array.from(artFiles.values())));\n return;\n }\n\n // GET /api/arts/:path - Get single art\n if (req.url?.startsWith(\"/arts/\") && req.method === \"GET\") {\n const artPath = decodeURIComponent(req.url.slice(6));\n const art = artFiles.get(artPath);\n if (art) {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(art));\n } else {\n res.statusCode = 404;\n res.end(JSON.stringify({ error: \"Art not found\" }));\n }\n return;\n }\n\n next();\n });\n\n // Watch for Art file changes\n devServer.watcher.on(\"change\", async (file) => {\n if (file.endsWith(\".art.vue\") && shouldProcess(file, include, exclude, config.root)) {\n await processArtFile(file);\n console.log(`[musea] Reloaded: ${path.relative(config.root, file)}`);\n }\n });\n\n devServer.watcher.on(\"add\", async (file) => {\n if (file.endsWith(\".art.vue\") && shouldProcess(file, include, exclude, config.root)) {\n await processArtFile(file);\n console.log(`[musea] Added: ${path.relative(config.root, file)}`);\n }\n });\n\n devServer.watcher.on(\"unlink\", (file) => {\n if (artFiles.has(file)) {\n artFiles.delete(file);\n console.log(`[musea] Removed: ${path.relative(config.root, file)}`);\n }\n });\n },\n\n async buildStart() {\n // Scan for Art files\n const files = await scanArtFiles(config.root, include, exclude);\n\n console.log(`[musea] Found ${files.length} art files`);\n\n for (const file of files) {\n await processArtFile(file);\n }\n\n // Generate Storybook CSF if enabled\n if (storybookCompat) {\n await generateStorybookFiles(artFiles, config.root, storybookOutDir);\n }\n },\n\n resolveId(id) {\n if (id === VIRTUAL_GALLERY) {\n return VIRTUAL_GALLERY;\n }\n if (id === VIRTUAL_MANIFEST) {\n return VIRTUAL_MANIFEST;\n }\n // Handle virtual:musea-preview: prefix for preview modules\n if (id.startsWith(\"virtual:musea-preview:\")) {\n return \"\\0musea-preview:\" + id.slice(\"virtual:musea-preview:\".length);\n }\n // Handle virtual:musea-art: prefix for preview modules\n if (id.startsWith(\"virtual:musea-art:\")) {\n const artPath = id.slice(\"virtual:musea-art:\".length);\n if (artFiles.has(artPath)) {\n return \"\\0musea-art:\" + artPath;\n }\n }\n if (id.endsWith(\".art.vue\")) {\n const resolved = path.resolve(config.root, id);\n if (artFiles.has(resolved)) {\n return VIRTUAL_MUSEA_PREFIX + resolved;\n }\n }\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_GALLERY) {\n return generateGalleryModule(basePath);\n }\n if (id === VIRTUAL_MANIFEST) {\n return generateManifestModule(artFiles);\n }\n // Handle \\0musea-preview: prefix for preview modules\n if (id.startsWith(\"\\0musea-preview:\")) {\n const rest = id.slice(\"\\0musea-preview:\".length);\n const lastColonIndex = rest.lastIndexOf(\":\");\n if (lastColonIndex !== -1) {\n const artPath = rest.slice(0, lastColonIndex);\n const variantName = rest.slice(lastColonIndex + 1);\n const art = artFiles.get(artPath);\n if (art) {\n const variantComponentName = toPascalCase(variantName);\n return generatePreviewModule(art, variantComponentName, variantName);\n }\n }\n }\n // Handle \\0musea-art: prefix for preview modules\n if (id.startsWith(\"\\0musea-art:\")) {\n const artPath = id.slice(\"\\0musea-art:\".length);\n const art = artFiles.get(artPath);\n if (art) {\n return generateArtModule(art, artPath);\n }\n }\n if (id.startsWith(VIRTUAL_MUSEA_PREFIX)) {\n const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length);\n const art = artFiles.get(realPath);\n if (art) {\n return generateArtModule(art, realPath);\n }\n }\n return null;\n },\n\n async handleHotUpdate(ctx) {\n const { file } = ctx;\n if (file.endsWith(\".art.vue\") && artFiles.has(file)) {\n await processArtFile(file);\n\n // Invalidate virtual modules\n const virtualId = VIRTUAL_MUSEA_PREFIX + file;\n const modules = server?.moduleGraph.getModulesByFile(virtualId);\n if (modules) {\n return [...modules];\n }\n }\n return undefined;\n },\n };\n\n // Helper functions scoped to this plugin instance\n\n async function processArtFile(filePath: string): Promise<void> {\n try {\n const source = await fs.promises.readFile(filePath, \"utf-8\");\n const binding = loadNative();\n const parsed = binding.parseArt(source, { filename: filePath });\n\n const info: ArtFileInfo = {\n path: filePath,\n metadata: {\n title: parsed.metadata.title,\n description: parsed.metadata.description,\n component: parsed.metadata.component,\n category: parsed.metadata.category,\n tags: parsed.metadata.tags,\n status: parsed.metadata.status as \"draft\" | \"ready\" | \"deprecated\",\n order: parsed.metadata.order,\n },\n variants: parsed.variants.map((v) => ({\n name: v.name,\n template: v.template,\n isDefault: v.is_default,\n skipVrt: v.skip_vrt,\n })),\n hasScriptSetup: parsed.has_script_setup,\n hasScript: parsed.has_script,\n styleCount: parsed.style_count,\n };\n\n artFiles.set(filePath, info);\n } catch (e) {\n console.error(`[musea] Failed to process ${filePath}:`, e);\n }\n }\n\n return [mainPlugin];\n}\n\n// Utility functions\n\nfunction shouldProcess(file: string, include: string[], exclude: string[], root: string): boolean {\n const relative = path.relative(root, file);\n\n // Check exclude patterns\n for (const pattern of exclude) {\n if (matchGlob(relative, pattern)) {\n return false;\n }\n }\n\n // Check include patterns\n for (const pattern of include) {\n if (matchGlob(relative, pattern)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction matchGlob(filepath: string, pattern: string): boolean {\n // Simple glob matching (supports * and **)\n // Escape . first, then replace glob patterns\n const regex = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \".*\")\n .replace(/\\*(?!\\*)/g, \"[^/]*\");\n\n return new RegExp(`^${regex}$`).test(filepath);\n}\n\nasync function scanArtFiles(root: string, include: string[], exclude: string[]): Promise<string[]> {\n const files: string[] = [];\n\n async function scan(dir: string): Promise<void> {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relative = path.relative(root, fullPath);\n\n // Check exclude\n let excluded = false;\n for (const pattern of exclude) {\n if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {\n excluded = true;\n break;\n }\n }\n\n if (excluded) continue;\n\n if (entry.isDirectory()) {\n await scan(fullPath);\n } else if (entry.isFile() && entry.name.endsWith(\".art.vue\")) {\n // Check include\n for (const pattern of include) {\n if (matchGlob(relative, pattern)) {\n files.push(fullPath);\n break;\n }\n }\n }\n }\n }\n\n await scan(root);\n return files;\n}\n\nfunction generateGalleryHtml(basePath: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Musea - Component Gallery</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-bg-elevated: #2d2a27;\n --musea-accent: #a34828;\n --musea-accent-hover: #c45a32;\n --musea-accent-subtle: rgba(163, 72, 40, 0.15);\n --musea-text: #e6e9f0;\n --musea-text-secondary: #c4c9d4;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n --musea-border-subtle: #2a2725;\n --musea-success: #4ade80;\n --musea-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);\n --musea-radius-sm: 6px;\n --musea-radius-md: 8px;\n --musea-radius-lg: 12px;\n --musea-transition: 0.15s ease;\n }\n\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n }\n\n /* Header */\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 0 1.5rem;\n height: 56px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 100;\n }\n\n .header-left {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n }\n\n .logo {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 1.125rem;\n font-weight: 700;\n color: var(--musea-accent);\n text-decoration: none;\n }\n\n .logo-svg {\n width: 32px;\n height: 32px;\n flex-shrink: 0;\n }\n\n .logo-icon svg {\n width: 16px;\n height: 16px;\n color: white;\n }\n\n .header-subtitle {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n font-weight: 500;\n padding-left: 1.5rem;\n border-left: 1px solid var(--musea-border);\n }\n\n .search-container {\n position: relative;\n width: 280px;\n }\n\n .search-input {\n width: 100%;\n background: var(--musea-bg-tertiary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-md);\n padding: 0.5rem 0.75rem 0.5rem 2.25rem;\n color: var(--musea-text);\n font-size: 0.8125rem;\n outline: none;\n transition: border-color var(--musea-transition), background var(--musea-transition);\n }\n\n .search-input::placeholder {\n color: var(--musea-text-muted);\n }\n\n .search-input:focus {\n border-color: var(--musea-accent);\n background: var(--musea-bg-elevated);\n }\n\n .search-icon {\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--musea-text-muted);\n pointer-events: none;\n }\n\n /* Layout */\n .main {\n display: grid;\n grid-template-columns: 260px 1fr;\n min-height: calc(100vh - 56px);\n }\n\n /* Sidebar */\n .sidebar {\n background: var(--musea-bg-secondary);\n border-right: 1px solid var(--musea-border);\n overflow-y: auto;\n overflow-x: hidden;\n }\n\n .sidebar::-webkit-scrollbar {\n width: 6px;\n }\n\n .sidebar::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .sidebar::-webkit-scrollbar-thumb {\n background: var(--musea-border);\n border-radius: 3px;\n }\n\n .sidebar-section {\n padding: 0.75rem;\n }\n\n .category-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.625rem 0.75rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--musea-text-muted);\n cursor: pointer;\n user-select: none;\n border-radius: var(--musea-radius-sm);\n transition: background var(--musea-transition);\n }\n\n .category-header:hover {\n background: var(--musea-bg-tertiary);\n }\n\n .category-icon {\n width: 16px;\n height: 16px;\n transition: transform var(--musea-transition);\n }\n\n .category-header.collapsed .category-icon {\n transform: rotate(-90deg);\n }\n\n .category-count {\n margin-left: auto;\n background: var(--musea-bg-tertiary);\n padding: 0.125rem 0.375rem;\n border-radius: 4px;\n font-size: 0.625rem;\n }\n\n .art-list {\n list-style: none;\n margin-top: 0.25rem;\n }\n\n .art-item {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.5rem 0.75rem 0.5rem 1.75rem;\n border-radius: var(--musea-radius-sm);\n cursor: pointer;\n font-size: 0.8125rem;\n color: var(--musea-text-secondary);\n transition: all var(--musea-transition);\n position: relative;\n }\n\n .art-item::before {\n content: '';\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--musea-border);\n transition: background var(--musea-transition);\n }\n\n .art-item:hover {\n background: var(--musea-bg-tertiary);\n color: var(--musea-text);\n }\n\n .art-item:hover::before {\n background: var(--musea-text-muted);\n }\n\n .art-item.active {\n background: var(--musea-accent-subtle);\n color: var(--musea-accent-hover);\n }\n\n .art-item.active::before {\n background: var(--musea-accent);\n }\n\n .art-variant-count {\n margin-left: auto;\n font-size: 0.6875rem;\n color: var(--musea-text-muted);\n opacity: 0;\n transition: opacity var(--musea-transition);\n }\n\n .art-item:hover .art-variant-count {\n opacity: 1;\n }\n\n /* Content */\n .content {\n background: var(--musea-bg-primary);\n overflow-y: auto;\n }\n\n .content-inner {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n }\n\n .content-header {\n margin-bottom: 2rem;\n }\n\n .content-title {\n font-size: 1.5rem;\n font-weight: 700;\n margin-bottom: 0.5rem;\n }\n\n .content-description {\n color: var(--musea-text-muted);\n font-size: 0.9375rem;\n max-width: 600px;\n }\n\n .content-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n margin-top: 1rem;\n }\n\n .meta-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.625rem;\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-sm);\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n }\n\n .meta-tag svg {\n width: 12px;\n height: 12px;\n }\n\n /* Gallery Grid */\n .gallery {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.25rem;\n }\n\n /* Variant Card */\n .variant-card {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-lg);\n overflow: hidden;\n transition: all var(--musea-transition);\n }\n\n .variant-card:hover {\n border-color: var(--musea-text-muted);\n box-shadow: var(--musea-shadow);\n transform: translateY(-2px);\n }\n\n .variant-preview {\n aspect-ratio: 16 / 10;\n background: var(--musea-bg-tertiary);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n overflow: hidden;\n }\n\n .variant-preview iframe {\n width: 100%;\n height: 100%;\n border: none;\n background: white;\n }\n\n .variant-preview-placeholder {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n text-align: center;\n padding: 1rem;\n }\n\n .variant-preview-code {\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n background: var(--musea-bg-primary);\n padding: 1rem;\n overflow: auto;\n max-height: 100%;\n width: 100%;\n }\n\n .variant-info {\n padding: 1rem;\n border-top: 1px solid var(--musea-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .variant-name {\n font-weight: 600;\n font-size: 0.875rem;\n }\n\n .variant-badge {\n font-size: 0.625rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n padding: 0.1875rem 0.5rem;\n border-radius: 4px;\n background: var(--musea-accent-subtle);\n color: var(--musea-accent);\n }\n\n .variant-actions {\n display: flex;\n gap: 0.5rem;\n }\n\n .variant-action-btn {\n width: 28px;\n height: 28px;\n border: none;\n background: var(--musea-bg-tertiary);\n border-radius: var(--musea-radius-sm);\n color: var(--musea-text-muted);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all var(--musea-transition);\n }\n\n .variant-action-btn:hover {\n background: var(--musea-bg-elevated);\n color: var(--musea-text);\n }\n\n .variant-action-btn svg {\n width: 14px;\n height: 14px;\n }\n\n /* Empty State */\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 400px;\n text-align: center;\n padding: 2rem;\n }\n\n .empty-state-icon {\n width: 80px;\n height: 80px;\n background: var(--musea-bg-secondary);\n border-radius: var(--musea-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 1.5rem;\n }\n\n .empty-state-icon svg {\n width: 40px;\n height: 40px;\n color: var(--musea-text-muted);\n }\n\n .empty-state-title {\n font-size: 1.125rem;\n font-weight: 600;\n margin-bottom: 0.5rem;\n }\n\n .empty-state-text {\n color: var(--musea-text-muted);\n font-size: 0.875rem;\n max-width: 300px;\n }\n\n /* Loading */\n .loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n color: var(--musea-text-muted);\n gap: 0.75rem;\n }\n\n .loading-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--musea-border);\n border-top-color: var(--musea-accent);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .main {\n grid-template-columns: 1fr;\n }\n .sidebar {\n display: none;\n }\n .header-subtitle {\n display: none;\n }\n }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div class=\"header-left\">\n <a href=\"${basePath}\" class=\"logo\">\n <svg class=\"logo-svg\" width=\"32\" height=\"32\" viewBox=\"0 0 200 200\" fill=\"none\">\n <defs>\n <linearGradient id=\"metal-grad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"20%\">\n <stop offset=\"0%\" stop-color=\"#f0f2f5\"/>\n <stop offset=\"50%\" stop-color=\"#9ca3b0\"/>\n <stop offset=\"100%\" stop-color=\"#e07048\"/>\n </linearGradient>\n <linearGradient id=\"metal-grad-dark\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"30%\">\n <stop offset=\"0%\" stop-color=\"#d0d4dc\"/>\n <stop offset=\"60%\" stop-color=\"#6b7280\"/>\n <stop offset=\"100%\" stop-color=\"#c45530\"/>\n </linearGradient>\n </defs>\n <g transform=\"translate(40, 40)\">\n <g transform=\"skewX(-12)\">\n <path d=\"M 100 0 L 60 120 L 105 30 L 100 0 Z\" fill=\"url(#metal-grad-dark)\" stroke=\"#4b5563\" stroke-width=\"0.5\"/>\n <path d=\"M 30 0 L 60 120 L 80 20 L 30 0 Z\" fill=\"url(#metal-grad)\" stroke-width=\"0.5\" stroke-opacity=\"0.4\"/>\n </g>\n </g>\n <g transform=\"translate(110, 120)\">\n <line x1=\"5\" y1=\"10\" x2=\"5\" y2=\"50\" stroke=\"#e07048\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <line x1=\"60\" y1=\"10\" x2=\"60\" y2=\"50\" stroke=\"#e07048\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <path d=\"M 0 10 L 32.5 0 L 65 10\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <rect x=\"15\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"36\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"23\" y=\"35\" width=\"18\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.6\"/>\n </g>\n </svg>\n Musea\n </a>\n <span class=\"header-subtitle\">Component Gallery</span>\n </div>\n <div class=\"search-container\">\n <svg class=\"search-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input type=\"text\" class=\"search-input\" placeholder=\"Search components...\" id=\"search\">\n </div>\n </header>\n\n <main class=\"main\">\n <aside class=\"sidebar\" id=\"sidebar\">\n <div class=\"loading\">\n <div class=\"loading-spinner\"></div>\n Loading...\n </div>\n </aside>\n <section class=\"content\" id=\"content\">\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants and documentation</div>\n </div>\n </section>\n </main>\n\n <script type=\"module\">\n const basePath = '${basePath}';\n let arts = [];\n let selectedArt = null;\n let searchQuery = '';\n\n async function loadArts() {\n try {\n const res = await fetch(basePath + '/api/arts');\n arts = await res.json();\n renderSidebar();\n } catch (e) {\n console.error('Failed to load arts:', e);\n document.getElementById('sidebar').innerHTML = '<div class=\"loading\">Failed to load</div>';\n }\n }\n\n function renderSidebar() {\n const sidebar = document.getElementById('sidebar');\n const categories = {};\n\n const filtered = searchQuery\n ? arts.filter(a => a.metadata.title.toLowerCase().includes(searchQuery.toLowerCase()))\n : arts;\n\n for (const art of filtered) {\n const cat = art.metadata.category || 'Components';\n if (!categories[cat]) categories[cat] = [];\n categories[cat].push(art);\n }\n\n if (Object.keys(categories).length === 0) {\n sidebar.innerHTML = '<div class=\"sidebar-section\"><div class=\"loading\">No components found</div></div>';\n return;\n }\n\n let html = '';\n for (const [category, items] of Object.entries(categories)) {\n html += '<div class=\"sidebar-section\">';\n html += '<div class=\"category-header\" data-category=\"' + category + '\">';\n html += '<svg class=\"category-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"m9 18 6-6-6-6\"/></svg>';\n html += '<span>' + category + '</span>';\n html += '<span class=\"category-count\">' + items.length + '</span>';\n html += '</div>';\n html += '<ul class=\"art-list\" data-category=\"' + category + '\">';\n for (const art of items) {\n const active = selectedArt?.path === art.path ? 'active' : '';\n const variantCount = art.variants?.length || 0;\n html += '<li class=\"art-item ' + active + '\" data-path=\"' + art.path + '\">';\n html += '<span>' + escapeHtml(art.metadata.title) + '</span>';\n html += '<span class=\"art-variant-count\">' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n html += '</li>';\n }\n html += '</ul>';\n html += '</div>';\n }\n\n sidebar.innerHTML = html;\n\n sidebar.querySelectorAll('.art-item').forEach(item => {\n item.addEventListener('click', () => {\n const artPath = item.dataset.path;\n selectedArt = arts.find(a => a.path === artPath);\n renderSidebar();\n renderContent();\n });\n });\n\n sidebar.querySelectorAll('.category-header').forEach(header => {\n header.addEventListener('click', () => {\n header.classList.toggle('collapsed');\n const list = sidebar.querySelector('.art-list[data-category=\"' + header.dataset.category + '\"]');\n if (list) list.style.display = header.classList.contains('collapsed') ? 'none' : 'block';\n });\n });\n }\n\n function renderContent() {\n const content = document.getElementById('content');\n if (!selectedArt) {\n content.innerHTML = \\`\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants</div>\n </div>\n \\`;\n return;\n }\n\n const meta = selectedArt.metadata;\n const tags = meta.tags || [];\n const variantCount = selectedArt.variants?.length || 0;\n\n let html = '<div class=\"content-inner\">';\n html += '<div class=\"content-header\">';\n html += '<h1 class=\"content-title\">' + escapeHtml(meta.title) + '</h1>';\n if (meta.description) {\n html += '<p class=\"content-description\">' + escapeHtml(meta.description) + '</p>';\n }\n html += '<div class=\"content-meta\">';\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/></svg>' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n if (meta.category) {\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>' + escapeHtml(meta.category) + '</span>';\n }\n for (const tag of tags) {\n html += '<span class=\"meta-tag\">#' + escapeHtml(tag) + '</span>';\n }\n html += '</div>';\n html += '</div>';\n\n html += '<div class=\"gallery\">';\n for (const variant of selectedArt.variants) {\n const previewUrl = basePath + '/preview?art=' + encodeURIComponent(selectedArt.path) + '&variant=' + encodeURIComponent(variant.name);\n\n html += '<div class=\"variant-card\">';\n html += '<div class=\"variant-preview\">';\n html += '<iframe src=\"' + previewUrl + '\" loading=\"lazy\" title=\"' + escapeHtml(variant.name) + '\"></iframe>';\n html += '</div>';\n html += '<div class=\"variant-info\">';\n html += '<div>';\n html += '<span class=\"variant-name\">' + escapeHtml(variant.name) + '</span>';\n if (variant.isDefault) html += ' <span class=\"variant-badge\">Default</span>';\n html += '</div>';\n html += '<div class=\"variant-actions\">';\n html += '<button class=\"variant-action-btn\" title=\"Open in new tab\" onclick=\"window.open(\\\\'' + previewUrl + '\\\\', \\\\'_blank\\\\')\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/></svg></button>';\n html += '</div>';\n html += '</div>';\n html += '</div>';\n }\n html += '</div>';\n html += '</div>';\n\n content.innerHTML = html;\n }\n\n function escapeHtml(str) {\n if (!str) return '';\n return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n }\n\n // Search\n document.getElementById('search').addEventListener('input', (e) => {\n searchQuery = e.target.value;\n renderSidebar();\n });\n\n // Keyboard shortcut for search\n document.addEventListener('keydown', (e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n e.preventDefault();\n document.getElementById('search').focus();\n }\n });\n\n loadArts();\n </script>\n</body>\n</html>`;\n}\n\nfunction generateGalleryModule(basePath: string): string {\n return `\nexport const basePath = '${basePath}';\nexport async function loadArts() {\n const res = await fetch(basePath + '/api/arts');\n return res.json();\n}\n`;\n}\n\nfunction generatePreviewModule(\n art: ArtFileInfo,\n variantComponentName: string,\n variantName: string,\n): string {\n const artModuleId = `virtual:musea-art:${art.path}`;\n const escapedVariantName = escapeTemplate(variantName);\n\n return `\nimport { createApp } from 'vue';\nimport * as artModule from '${artModuleId}';\n\nconst container = document.getElementById('app');\n\nasync function mount() {\n try {\n // Get the specific variant component\n const VariantComponent = artModule['${variantComponentName}'];\n\n if (!VariantComponent) {\n throw new Error('Variant component \"${variantComponentName}\" not found in art module');\n }\n\n // Create and mount the app\n const app = createApp(VariantComponent);\n container.innerHTML = '';\n container.className = 'musea-variant';\n app.mount(container);\n\n console.log('[musea-preview] Mounted variant: ${escapedVariantName}');\n } catch (error) {\n console.error('[musea-preview] Failed to mount:', error);\n container.innerHTML = \\`\n <div class=\"musea-error\">\n <div class=\"musea-error-title\">Failed to render component</div>\n <div>\\${error.message}</div>\n <pre>\\${error.stack || ''}</pre>\n </div>\n \\`;\n }\n}\n\nmount();\n`;\n}\n\nfunction generateManifestModule(artFiles: Map<string, ArtFileInfo>): string {\n const arts = Array.from(artFiles.values());\n return `export const arts = ${JSON.stringify(arts, null, 2)};`;\n}\n\nfunction generateArtModule(art: ArtFileInfo, filePath: string): string {\n const componentPath = art.metadata.component;\n\n // Resolve component path relative to art file location\n let resolvedComponentPath = componentPath;\n if (componentPath && !path.isAbsolute(componentPath)) {\n const artDir = path.dirname(filePath);\n resolvedComponentPath = path.resolve(artDir, componentPath);\n }\n\n // Extract component name from path (e.g., './Button.vue' -> 'Button')\n const componentName = componentPath ? path.basename(componentPath, \".vue\") : null;\n\n let code = `\n// Auto-generated module for: ${path.basename(filePath)}\nimport { defineComponent, h } from 'vue';\n`;\n\n if (resolvedComponentPath && componentName) {\n code += `import ${componentName} from '${resolvedComponentPath}';\\n`;\n }\n\n code += `\nexport const metadata = ${JSON.stringify(art.metadata)};\nexport const variants = ${JSON.stringify(art.variants)};\n`;\n\n // Generate variant components\n for (const variant of art.variants) {\n const variantComponentName = toPascalCase(variant.name);\n // Escape the template for use in a JS string\n const escapedTemplate = variant.template\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/`/g, \"\\\\`\")\n .replace(/\\$/g, \"\\\\$\");\n\n // Wrap template with the variant container\n const fullTemplate = `<div class=\"musea-variant\" data-variant=\"${variant.name}\">${escapedTemplate}</div>`;\n\n if (componentName) {\n code += `\nexport const ${variantComponentName} = {\n name: '${variantComponentName}',\n components: { ${componentName} },\n template: \\`${fullTemplate}\\`,\n};\n`;\n } else {\n code += `\nexport const ${variantComponentName} = {\n name: '${variantComponentName}',\n template: \\`${fullTemplate}\\`,\n};\n`;\n }\n }\n\n // Default export\n const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];\n if (defaultVariant) {\n code += `\nexport default ${toPascalCase(defaultVariant.name)};\n`;\n }\n\n return code;\n}\n\nasync function generateStorybookFiles(\n artFiles: Map<string, ArtFileInfo>,\n root: string,\n outDir: string,\n): Promise<void> {\n const binding = loadNative();\n const outputDir = path.resolve(root, outDir);\n\n // Ensure output directory exists\n await fs.promises.mkdir(outputDir, { recursive: true });\n\n for (const [filePath, _art] of artFiles) {\n try {\n const source = await fs.promises.readFile(filePath, \"utf-8\");\n const csf = binding.artToCsf(source, { filename: filePath });\n\n const outputPath = path.join(outputDir, csf.filename);\n await fs.promises.writeFile(outputPath, csf.code, \"utf-8\");\n\n console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);\n } catch (e) {\n console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);\n }\n }\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[\\s\\-_]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n\nfunction escapeTemplate(str: string): string {\n return str.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\").replace(/\\n/g, \"\\\\n\");\n}\n\nfunction generatePreviewHtml(art: ArtFileInfo, variant: ArtVariant, basePath: string): string {\n // Create a unique module URL for each variant to avoid caching issues\n const previewModuleUrl = `${basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n html, body {\n width: 100%;\n height: 100%;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #ffffff;\n }\n .musea-variant {\n padding: 1.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .musea-error {\n color: #dc2626;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n padding: 1rem;\n font-size: 0.875rem;\n max-width: 400px;\n }\n .musea-error-title {\n font-weight: 600;\n margin-bottom: 0.5rem;\n }\n .musea-error pre {\n font-family: monospace;\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-all;\n margin-top: 0.5rem;\n padding: 0.5rem;\n background: #fff;\n border-radius: 4px;\n }\n .musea-loading {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n color: #6b7280;\n font-size: 0.875rem;\n }\n .musea-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid #e5e7eb;\n border-top-color: #3b82f6;\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n @keyframes spin { to { transform: rotate(360deg); } }\n </style>\n</head>\n<body>\n <div id=\"app\" class=\"musea-variant\" data-art=\"${escapeHtml(art.path)}\" data-variant=\"${escapeHtml(variant.name)}\">\n <div class=\"musea-loading\">\n <div class=\"musea-spinner\"></div>\n Loading component...\n </div>\n </div>\n <script type=\"module\" src=\"${previewModuleUrl}\"></script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nexport default musea;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Musea plugin options.
|
|
4
|
+
*/
|
|
5
|
+
interface MuseaOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Glob patterns to include Art files.
|
|
8
|
+
* @default ['**\/*.art.vue']
|
|
9
|
+
*/
|
|
10
|
+
include?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Glob patterns to exclude.
|
|
13
|
+
* @default ['node_modules/**', 'dist/**']
|
|
14
|
+
*/
|
|
15
|
+
exclude?: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Base path for Musea gallery UI.
|
|
18
|
+
* @default '/__musea__'
|
|
19
|
+
*/
|
|
20
|
+
basePath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Enable Storybook CSF output.
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
storybookCompat?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Storybook output directory (when storybookCompat is true).
|
|
28
|
+
* @default '.storybook/stories'
|
|
29
|
+
*/
|
|
30
|
+
storybookOutDir?: string;
|
|
31
|
+
/**
|
|
32
|
+
* VRT (Visual Regression Testing) configuration.
|
|
33
|
+
*/
|
|
34
|
+
vrt?: VrtOptions;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* VRT configuration options.
|
|
38
|
+
*/
|
|
39
|
+
interface VrtOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Snapshot storage directory.
|
|
42
|
+
* @default '.vize/snapshots'
|
|
43
|
+
*/
|
|
44
|
+
snapshotDir?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Pixel difference threshold for comparison.
|
|
47
|
+
* @default 100
|
|
48
|
+
*/
|
|
49
|
+
threshold?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Viewports to capture.
|
|
52
|
+
* @default [{ width: 1280, height: 720 }, { width: 375, height: 667 }]
|
|
53
|
+
*/
|
|
54
|
+
viewports?: ViewportConfig[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Viewport configuration.
|
|
58
|
+
*/
|
|
59
|
+
interface ViewportConfig {
|
|
60
|
+
/** Width in pixels */
|
|
61
|
+
width: number;
|
|
62
|
+
/** Height in pixels */
|
|
63
|
+
height: number;
|
|
64
|
+
/** Device scale factor */
|
|
65
|
+
deviceScaleFactor?: number;
|
|
66
|
+
/** Viewport name for identification */
|
|
67
|
+
name?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Art file metadata.
|
|
71
|
+
*/
|
|
72
|
+
interface ArtMetadata {
|
|
73
|
+
title: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
component?: string;
|
|
76
|
+
category?: string;
|
|
77
|
+
tags: string[];
|
|
78
|
+
status: "draft" | "ready" | "deprecated";
|
|
79
|
+
order?: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Art variant definition.
|
|
83
|
+
*/
|
|
84
|
+
interface ArtVariant {
|
|
85
|
+
name: string;
|
|
86
|
+
template: string;
|
|
87
|
+
isDefault: boolean;
|
|
88
|
+
skipVrt: boolean;
|
|
89
|
+
args?: Record<string, unknown>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parsed Art file information.
|
|
93
|
+
*/
|
|
94
|
+
interface ArtFileInfo {
|
|
95
|
+
/** Absolute file path */
|
|
96
|
+
path: string;
|
|
97
|
+
/** Art metadata */
|
|
98
|
+
metadata: ArtMetadata;
|
|
99
|
+
/** Variant definitions */
|
|
100
|
+
variants: ArtVariant[];
|
|
101
|
+
/** Whether file has script setup */
|
|
102
|
+
hasScriptSetup: boolean;
|
|
103
|
+
/** Whether file has regular script */
|
|
104
|
+
hasScript: boolean;
|
|
105
|
+
/** Number of style blocks */
|
|
106
|
+
styleCount: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* CSF output from Art transformation.
|
|
110
|
+
*/
|
|
111
|
+
interface CsfOutput {
|
|
112
|
+
/** Generated CSF code */
|
|
113
|
+
code: string;
|
|
114
|
+
/** Suggested filename */
|
|
115
|
+
filename: string;
|
|
116
|
+
} //#endregion
|
|
117
|
+
//#region src/vrt.d.ts
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=types.d.ts.map
|
|
120
|
+
/**
|
|
121
|
+
* VRT test result for a single variant.
|
|
122
|
+
*/
|
|
123
|
+
interface VrtResult {
|
|
124
|
+
artPath: string;
|
|
125
|
+
variantName: string;
|
|
126
|
+
viewport: ViewportConfig;
|
|
127
|
+
passed: boolean;
|
|
128
|
+
snapshotPath: string;
|
|
129
|
+
currentPath?: string;
|
|
130
|
+
diffPath?: string;
|
|
131
|
+
diffPercentage?: number;
|
|
132
|
+
diffPixels?: number;
|
|
133
|
+
totalPixels?: number;
|
|
134
|
+
error?: string;
|
|
135
|
+
isNew?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* VRT summary for reporting.
|
|
139
|
+
*/
|
|
140
|
+
interface VrtSummary {
|
|
141
|
+
total: number;
|
|
142
|
+
passed: number;
|
|
143
|
+
failed: number;
|
|
144
|
+
new: number;
|
|
145
|
+
skipped: number;
|
|
146
|
+
duration: number;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Pixel comparison options.
|
|
150
|
+
*/
|
|
151
|
+
interface PixelCompareOptions {
|
|
152
|
+
/** Threshold for color difference (0-1). Default: 0.1 */
|
|
153
|
+
threshold?: number;
|
|
154
|
+
/** Include anti-aliasing in diff. Default: false */
|
|
155
|
+
includeAA?: boolean;
|
|
156
|
+
/** Alpha channel comparison. Default: true */
|
|
157
|
+
alpha?: boolean;
|
|
158
|
+
/** Diff highlight color */
|
|
159
|
+
diffColor?: {
|
|
160
|
+
r: number;
|
|
161
|
+
g: number;
|
|
162
|
+
b: number;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* VRT runner using Playwright.
|
|
167
|
+
*/
|
|
168
|
+
declare class MuseaVrtRunner {
|
|
169
|
+
private options;
|
|
170
|
+
private browser;
|
|
171
|
+
private startTime;
|
|
172
|
+
constructor(options?: VrtOptions);
|
|
173
|
+
/**
|
|
174
|
+
* Initialize Playwright browser.
|
|
175
|
+
*/
|
|
176
|
+
init(): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Close browser and cleanup.
|
|
179
|
+
*/
|
|
180
|
+
close(): Promise<void>;
|
|
181
|
+
/**
|
|
182
|
+
* Run VRT tests for all Art files.
|
|
183
|
+
*/
|
|
184
|
+
runAllTests(artFiles: ArtFileInfo[], baseUrl: string): Promise<VrtResult[]>;
|
|
185
|
+
/**
|
|
186
|
+
* Capture screenshot and compare with baseline.
|
|
187
|
+
*/
|
|
188
|
+
captureAndCompare(art: ArtFileInfo, variantName: string, viewport: ViewportConfig, baseUrl: string): Promise<VrtResult>;
|
|
189
|
+
/**
|
|
190
|
+
* Update baseline snapshots with current screenshots.
|
|
191
|
+
*/
|
|
192
|
+
updateBaselines(results: VrtResult[]): Promise<number>;
|
|
193
|
+
/**
|
|
194
|
+
* Get VRT summary statistics.
|
|
195
|
+
*/
|
|
196
|
+
getSummary(results: VrtResult[]): VrtSummary;
|
|
197
|
+
/**
|
|
198
|
+
* Build URL for variant preview.
|
|
199
|
+
*/
|
|
200
|
+
private buildVariantUrl;
|
|
201
|
+
/**
|
|
202
|
+
* Compare two PNG images and generate a diff image.
|
|
203
|
+
* Returns pixel difference statistics.
|
|
204
|
+
*/
|
|
205
|
+
private compareImages;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Generate VRT report in HTML format.
|
|
209
|
+
* Uses Musea design language for consistency.
|
|
210
|
+
*/
|
|
211
|
+
declare function generateVrtReport(results: VrtResult[], summary: VrtSummary): string;
|
|
212
|
+
/**
|
|
213
|
+
* Generate VRT JSON report for CI integration.
|
|
214
|
+
*/
|
|
215
|
+
declare function generateVrtJsonReport(results: VrtResult[], summary: VrtSummary): string; //#endregion
|
|
216
|
+
export { ArtFileInfo, ArtMetadata, ArtVariant, CsfOutput, MuseaOptions, MuseaVrtRunner as MuseaVrtRunner$1, PixelCompareOptions, ViewportConfig, VrtOptions, VrtResult, VrtSummary, generateVrtJsonReport as generateVrtJsonReport$1, generateVrtReport as generateVrtReport$1 };
|
|
217
|
+
//# sourceMappingURL=vrt-BfuTRv-J.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vrt-BfuTRv-J.d.ts","names":[],"sources":["../src/types.ts","../src/vrt.ts"],"sourcesContent":null,"mappings":";;;;UAGiB,YAAA;EAAA;;;;EAwCA,OAAA,CAAA,EAAA,MAAU,EAAA;;;;AAuB3B;;;;AAcA;;;;AAaA;;;;EAWiB;;;;EAMK,eAAA,CAAA,EAAA,MAAA;;;;EAYL,GAAA,CAAA,EArFT,UAqFkB;;;;;UA/ET,UAAA;;AC7BjB;;;;EAkBiB;;;;EAYA,SAAA,CAAA,EAAA,MAAA;;;;AAcjB;EAA2B,SAAA,CAAA,EDEb,cCFa,EAAA;;;;;AAsC4C,UD9BtD,cAAA,CC8BsD;EAAS;EAAV,KA2B7D,EAAA,MAAA;EAAW;EAEQ,MAEf,EAAA,MAAA;EAAS;EAAV,iBAmGqB,CAAA,EAAA,MAAA;EAAS;EAAY,IAqBhC,CAAA,EAAA,MAAA;;AAAwB;;;UDvK7B,WAAA;;EC+RD,WAAA,CAAA,EAAA,MAAiB;EAAA,SAAA,CAAA,EAAA,MAAA;EAAA,QAAU,CAAA,EAAA,MAAA;EAAS,IAAa,EAAA,MAAA,EAAA;EAAU,MAAA,EAAA,OAAA,GAAA,OAAA,GAAA,YAAA;;;;AA0d3E;;AAA+C,UD5uB9B,UAAA,CC4uB8B;EAAS,IAAa,EAAA,MAAA;EAAU,QAAA,EAAA,MAAA;;;SDvuBtE;;;;;UAMQ,WAAA;;;;YAIL;;YAEA;;;;;;;;;;;UAYK,SAAA;;;;;;;;;;;;AA/EA,UC7BA,SAAA,CD6BU;;;YC1Bf;EDiDK,MAAA,EAAA,OAAA;;;;EAcA,cAAW,CAAA,EAAA,MAAA;;;;EAaX,KAAA,CAAA,EAAA,OAAU;;;;AAW3B;AAA4B,UCxEX,UAAA,CDwEW;EAAA,KAIhB,EAAA,MAAA;EAAW,MAEX,EAAA,MAAA;EAAU,MAAA,EAAA,MAAA;;;;AAYtB;;;;UC9EiB,mBAAA;;;EA9BA;;;;EAkBA;;;;IAYA,CAAA,EAAA,MAAA;;;;AAcjB;;AAKuB,cALV,cAAA,CAKU;EAAe,QActB,OAAA;EAAO,QASN,OAAA;EAAO,QAUM,SAAA;EAAW,WAA8B,CAAA,OAAA,CAAA,EAjChD,UAiCgD;EAAS;;;EA6BpD,IAEf,CAAA,CAAA,EAlDG,OAkDH,CAAA,IAAA,CAAA;EAAS;;;EAmGgC,KAqBhC,CAAA,CAAA,EAjKL,OAiKK,CAAA,IAAA,CAAA;EAAS;AAAe;;wBAvJhB,iCAAiC,QAAQ;;;AA+QvE;EAAiC,iBAAA,CAAA,GAAA,EApPxB,WAoPwB,EAAA,WAAA,EAAA,MAAA,EAAA,QAAA,EAlPnB,cAkPmB,EAAA,OAAA,EAAA,MAAA,CAAA,EAhP5B,OAgP4B,CAhPpB,SAgPoB,CAAA;EAAA;;AAA0C;2BA7I1C,cAAc;;;AAumB/C;EAAqC,UAAA,CAAA,OAAA,EAllBf,SAklBe,EAAA,CAAA,EAllBD,UAklBC;EAAA;;AAA0C;;;;;;;;;;;;iBA1d/D,iBAAA,UAA2B,sBAAsB;;;;iBA0djD,qBAAA,UAA+B,sBAAsB"}
|