executable-stories-demo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { fileURLToPath } from \"node:url\";\n\nimport {\n ReportGenerator,\n canonicalizeRun,\n copyMarkdownAssets,\n type Attachment,\n type RawRun,\n type TestCaseResult,\n type TestRunResult,\n} from \"executable-stories-formatters\";\n\nexport interface DemoCta {\n primary: string;\n url: string;\n}\n\nexport interface DemoScenarioConfig {\n order?: string[];\n}\n\nexport type DemoTemplate = \"splash\" | \"dashboard\";\n\nexport type DemoStatsMode = \"test\" | \"capability\" | \"off\";\n\nexport interface DemoStatsConfig {\n mode?: DemoStatsMode;\n}\n\nexport interface DemoFeaturedConfig {\n /** Slug of a story (e.g. \"stories/checkout/happy-path\") to feature inline on the landing page. */\n scenario?: string;\n}\n\nexport interface DemoBrandingConfig {\n /** Site-relative path or absolute URL to a logo. */\n logo?: string;\n /** Site-relative path or absolute URL to an OG/Twitter card image. */\n ogImage?: string;\n /** Site-relative path or absolute URL to a favicon. */\n favicon?: string;\n /** Optional CSS color string that overrides the active theme's --demo-accent. */\n accent?: string;\n}\n\nexport interface DemoSeoConfig {\n /** Defaults to productName. */\n title?: string;\n /** Defaults to tagline. */\n description?: string;\n /** Twitter handle including @, e.g. \"@acme\". */\n twitter?: string;\n /** Canonical URL for og:url and link rel=canonical. */\n canonical?: string;\n}\n\nexport type DemoSection =\n | { kind: \"feature-grid\"; heading?: string; items: Array<{ title: string; body: string }> }\n | { kind: \"narrative\"; heading?: string; eyebrow?: string; body: string; media?: string }\n | { kind: \"quote\"; quote: string; attribution?: string };\n\nexport interface DemoConfig {\n productName?: string;\n tagline?: string;\n theme?: string;\n template?: DemoTemplate;\n cta?: DemoCta;\n scenarios?: DemoScenarioConfig;\n stats?: DemoStatsConfig;\n featured?: DemoFeaturedConfig;\n branding?: DemoBrandingConfig;\n seo?: DemoSeoConfig;\n sections?: DemoSection[];\n}\n\nexport interface InitDemoOptions {\n targetDir?: string;\n force?: boolean;\n productName?: string;\n}\n\nexport interface InitDemoResult {\n targetDir: string;\n configPath: string;\n}\n\nexport interface BuildDemoOptions {\n input: string;\n siteDir: string;\n configPath?: string;\n allowMissingAssets?: boolean;\n assetsBaseUrl?: string;\n assetsDir?: string;\n strict?: boolean;\n}\n\nexport interface DemoPage {\n title: string;\n slug: string;\n file: string;\n}\n\nexport interface BuildDemoResult {\n pages: DemoPage[];\n manifestPath: string;\n storiesDir: string;\n}\n\nexport interface PreviewDemoOptions {\n siteDir: string;\n mode?: \"dev\" | \"preview\" | \"build\";\n}\n\ninterface ResolvedConfig {\n productName: string;\n tagline: string;\n theme: string;\n template: DemoTemplate;\n cta: DemoCta;\n scenarios: { order: string[] };\n stats: { mode: DemoStatsMode };\n featured: { scenario?: string };\n branding: DemoBrandingConfig;\n seo: DemoSeoConfig;\n sections: DemoSection[];\n}\n\nconst DEFAULT_CONFIG = {\n productName: \"Product Demo\",\n tagline: \"Executable stories turned into product walkthroughs.\",\n theme: \"default\",\n template: \"splash\" as DemoTemplate,\n cta: {\n primary: \"Get Started\",\n url: \"/\",\n },\n scenarios: {\n order: [] as string[],\n },\n // Splash audiences = customers/prospects; capability framing reads better than test counts.\n // Dashboard mode flips to \"test\" by default in loadConfig.\n stats: {\n mode: \"capability\" as DemoStatsMode,\n },\n} as const;\nconst SUPPORTED_THEMES = new Set([\n \"default\",\n \"corporate\",\n \"terminal\",\n \"minimal\",\n \"dashboard\",\n \"playful\",\n]);\nconst TEST_EXTENSIONS = [\n \".test.ts\",\n \".test.tsx\",\n \".spec.ts\",\n \".spec.tsx\",\n \".test.js\",\n \".spec.js\",\n \".story.test.ts\",\n \".story.spec.ts\",\n];\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport function initDemo(options: InitDemoOptions = {}): InitDemoResult {\n const targetDir = path.resolve(options.targetDir ?? \"./demo-site\");\n const force = options.force ?? false;\n\n if (fs.existsSync(targetDir)) {\n const entries = fs.readdirSync(targetDir);\n if (entries.length > 0 && !force) {\n throw new Error(\n `Directory \\\"${targetDir}\\\" already exists and is not empty. Use --force to overwrite.`,\n );\n }\n }\n\n const templateDir = path.resolve(\n __dirname,\n \"..\",\n \"templates\",\n \"astro-demo-starlight\",\n );\n\n if (!fs.existsSync(templateDir)) {\n throw new Error(`Template directory not found at ${templateDir}`);\n }\n\n copyDirRecursive(templateDir, targetDir);\n\n const productName = options.productName ?? toTitleCase(path.basename(targetDir));\n const config: DemoConfig = {\n productName,\n tagline: DEFAULT_CONFIG.tagline,\n theme: DEFAULT_CONFIG.theme,\n template: DEFAULT_CONFIG.template,\n cta: { ...DEFAULT_CONFIG.cta },\n scenarios: { order: [] },\n stats: { mode: DEFAULT_CONFIG.stats.mode },\n seo: {\n title: productName,\n description: DEFAULT_CONFIG.tagline,\n },\n };\n\n const configPath = path.join(targetDir, \"demo.config.json\");\n fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf8\");\n\n return { targetDir, configPath };\n}\n\nexport async function buildDemo(options: BuildDemoOptions): Promise<BuildDemoResult> {\n const siteDir = path.resolve(options.siteDir);\n const inputPath = path.resolve(options.input);\n const configPath = path.resolve(options.configPath ?? path.join(siteDir, \"demo.config.json\"));\n const astroConfigPath = path.join(siteDir, \"astro.config.mjs\");\n\n if (!fs.existsSync(siteDir)) {\n throw new Error(\n `Demo site directory not found: ${siteDir}. Run \\\"executable-stories-demo init <dir>\\\" first.`,\n );\n }\n\n if (!fs.existsSync(astroConfigPath)) {\n throw new Error(\n `astro.config.mjs not found in ${siteDir}. This directory is not a valid demo site scaffold.`,\n );\n }\n\n if (!fs.existsSync(inputPath)) {\n throw new Error(`Input run file not found: ${inputPath}`);\n }\n\n const config = loadConfig(configPath);\n const run = loadRun(inputPath);\n\n const docsDir = path.join(siteDir, \"src\", \"content\", \"docs\");\n const storiesDir = path.join(docsDir, \"stories\");\n const strict = options.strict ?? false;\n const allowMissingAssets = strict ? false : options.allowMissingAssets ?? true;\n const assetsDir = resolveAssetsDir(siteDir, options.assetsDir);\n const assetsBaseUrl = normalizeAssetsBaseUrl(options.assetsBaseUrl);\n\n fs.mkdirSync(storiesDir, { recursive: true });\n fs.mkdirSync(assetsDir, { recursive: true });\n\n const generator = new ReportGenerator({\n formats: [\"astro\"],\n outputDir: storiesDir,\n output: {\n mode: \"colocated\",\n colocatedStyle: \"mirrored\",\n },\n astro: {\n assetsDir,\n assetsBaseUrl,\n markdown: {\n title: `${config.productName} Stories`,\n },\n },\n assetMode: \"copy\",\n allowMissingAssets,\n });\n\n const output = await generator.generate(run);\n const astroFiles = output.get(\"astro\") ?? [];\n appendAttachmentsToPages({\n astroFiles,\n run,\n storiesDir,\n assetsDir,\n assetsBaseUrl,\n allowMissingAssets,\n });\n\n const pages = toPages(astroFiles, docsDir, config.scenarios.order);\n writeLandingPage(path.join(docsDir, \"index.mdx\"), config, pages, run, docsDir, assetsBaseUrl);\n applyThemeToAstroConfig(siteDir, config.theme);\n\n const manifest = {\n generatedAt: new Date().toISOString(),\n input: toPosix(inputPath),\n config: toPosix(configPath),\n productName: config.productName,\n theme: config.theme,\n assets: {\n dir: toPosix(assetsDir),\n baseUrl: assetsBaseUrl,\n allowMissing: allowMissingAssets,\n strict,\n },\n stats: {\n scenarios: run.testCases.length,\n passed: run.testCases.filter((tc) => tc.status === \"passed\").length,\n failed: run.testCases.filter((tc) => tc.status === \"failed\").length,\n skipped: run.testCases.filter((tc) => tc.status === \"skipped\").length,\n pending: run.testCases.filter((tc) => tc.status === \"pending\").length,\n },\n pages,\n };\n\n const manifestPath = path.join(siteDir, \"demo-manifest.json\");\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`, \"utf8\");\n\n return {\n pages,\n manifestPath,\n storiesDir,\n };\n}\n\nexport function previewDemo(options: PreviewDemoOptions): void {\n const siteDir = path.resolve(options.siteDir);\n const mode = options.mode ?? \"dev\";\n\n const command = mode === \"build\" ? \"build\" : mode === \"preview\" ? \"preview\" : \"dev\";\n const result = spawnSync(\"pnpm\", [command], {\n cwd: siteDir,\n stdio: \"inherit\",\n shell: process.platform === \"win32\",\n });\n\n if (result.status !== 0) {\n throw new Error(`pnpm ${command} failed with exit code ${result.status ?? 1}`);\n }\n}\n\nfunction loadConfig(configPath: string): ResolvedConfig {\n const userConfig = fs.existsSync(configPath)\n ? (JSON.parse(fs.readFileSync(configPath, \"utf8\")) as DemoConfig)\n : {};\n const theme = normalizeTheme(userConfig.theme);\n const template: DemoTemplate =\n userConfig.template === \"dashboard\" ? \"dashboard\" : DEFAULT_CONFIG.template;\n const statsMode: DemoStatsMode = (() => {\n const m = userConfig.stats?.mode;\n if (m === \"test\" || m === \"capability\" || m === \"off\") return m;\n // Dashboard audiences are engineering — test framing fits.\n return template === \"dashboard\" ? \"test\" : DEFAULT_CONFIG.stats.mode;\n })();\n\n return {\n productName: userConfig.productName ?? DEFAULT_CONFIG.productName,\n tagline: userConfig.tagline ?? DEFAULT_CONFIG.tagline,\n theme,\n template,\n cta: {\n primary: userConfig.cta?.primary ?? DEFAULT_CONFIG.cta.primary,\n url: userConfig.cta?.url ?? DEFAULT_CONFIG.cta.url,\n },\n scenarios: {\n order: userConfig.scenarios?.order ?? [...DEFAULT_CONFIG.scenarios.order],\n },\n stats: { mode: statsMode },\n featured: {\n scenario: userConfig.featured?.scenario,\n },\n branding: {\n logo: userConfig.branding?.logo,\n ogImage: userConfig.branding?.ogImage,\n favicon: userConfig.branding?.favicon,\n accent: userConfig.branding?.accent,\n },\n seo: {\n title: userConfig.seo?.title,\n description: userConfig.seo?.description,\n twitter: userConfig.seo?.twitter,\n canonical: userConfig.seo?.canonical,\n },\n sections: userConfig.sections ?? [],\n };\n}\n\nfunction loadRun(inputPath: string): TestRunResult {\n const payload = JSON.parse(fs.readFileSync(inputPath, \"utf8\")) as unknown;\n\n if (isRawLikePayload(payload)) {\n return canonicalizeRun(payload as RawRun);\n }\n\n return payload as TestRunResult;\n}\n\nfunction isRawLikePayload(payload: unknown): boolean {\n if (!payload || typeof payload !== \"object\") return false;\n\n const maybeCases = (payload as { testCases?: Array<{ status?: unknown }> }).testCases;\n const firstStatus = maybeCases?.[0]?.status;\n\n return firstStatus === \"pass\" || firstStatus === \"fail\" || firstStatus === \"skip\";\n}\n\nfunction appendAttachmentsToPages(args: {\n astroFiles: string[];\n run: TestRunResult;\n storiesDir: string;\n assetsDir: string;\n assetsBaseUrl: string;\n allowMissingAssets: boolean;\n}): void {\n const byPage = groupAttachmentsByPage(args.run.testCases, args.storiesDir, \"index\");\n\n for (const filePath of args.astroFiles) {\n const attachments = byPage.get(toPosix(path.resolve(filePath)));\n if (!attachments || attachments.length === 0) continue;\n\n const markdownDir = path.dirname(filePath);\n const unique = dedupeAttachments(attachments);\n const rendered = renderAttachmentSection(unique, markdownDir, args.run.projectRoot);\n if (rendered.length === 0) continue;\n\n const original = fs.readFileSync(filePath, \"utf8\");\n const appended = `${original.trimEnd()}\\n\\n${rendered}\\n`;\n\n const copied = copyMarkdownAssets({\n markdown: appended,\n markdownDir,\n assetsDir: args.assetsDir,\n assetsBaseUrl: args.assetsBaseUrl,\n allowMissing: args.allowMissingAssets,\n });\n\n fs.writeFileSync(filePath, copied.markdown, \"utf8\");\n }\n}\n\nfunction groupAttachmentsByPage(\n testCases: TestCaseResult[],\n storiesDir: string,\n outputName: string,\n): Map<string, Attachment[]> {\n const grouped = new Map<string, Attachment[]>();\n const baseDir = toPosix(path.resolve(storiesDir));\n\n for (const tc of testCases) {\n if (tc.attachments.length === 0) continue;\n\n const outputPath = computeStoryOutputPath(tc.sourceFile, baseDir, outputName);\n const existing = grouped.get(outputPath) ?? [];\n existing.push(...tc.attachments);\n grouped.set(outputPath, existing);\n }\n\n return grouped;\n}\n\nfunction computeStoryOutputPath(sourceFile: string, baseOutputDir: string, outputName: string): string {\n if (sourceFile === \"unknown\") {\n return toPosix(path.join(baseOutputDir, `${outputName}.md`));\n }\n\n const normalizedSource = toPosix(sourceFile);\n const dirOfSource = path.posix.dirname(normalizedSource);\n let baseName = path.posix.basename(normalizedSource);\n\n for (const extension of TEST_EXTENSIONS) {\n if (baseName.endsWith(extension)) {\n baseName = baseName.slice(0, -extension.length);\n break;\n }\n }\n\n const fileName = `${baseName}.${outputName}.md`;\n return toPosix(path.posix.join(baseOutputDir, dirOfSource, fileName));\n}\n\nfunction dedupeAttachments(attachments: Attachment[]): Attachment[] {\n const seen = new Set<string>();\n const deduped: Attachment[] = [];\n\n for (const attachment of attachments) {\n const key = `${attachment.name}|${attachment.mediaType}|${attachment.contentEncoding}|${attachment.body}`;\n if (seen.has(key)) continue;\n seen.add(key);\n deduped.push(attachment);\n }\n\n return deduped;\n}\n\nfunction renderAttachmentSection(\n attachments: Attachment[],\n markdownDir: string,\n projectRoot: string,\n): string {\n const lines: string[] = [\"## Media\", \"\"];\n\n for (const attachment of attachments) {\n const source = resolveAttachmentSource(attachment, markdownDir, projectRoot);\n if (!source) continue;\n\n const label = attachment.name || \"Attachment\";\n if (attachment.mediaType.startsWith(\"video/\")) {\n lines.push(`### ${label}`);\n lines.push(\"\");\n lines.push(`<video controls preload=\\\"metadata\\\" src=\\\"${source}\\\"></video>`);\n lines.push(\"\");\n continue;\n }\n\n if (attachment.mediaType.startsWith(\"image/\")) {\n lines.push(`### ${label}`);\n lines.push(\"\");\n lines.push(`![${label}](${source})`);\n lines.push(\"\");\n continue;\n }\n\n lines.push(`- [${label}](${source})`);\n }\n\n if (lines.length <= 2) return \"\";\n return lines.join(\"\\n\");\n}\n\nfunction resolveAttachmentSource(\n attachment: Attachment,\n markdownDir: string,\n projectRoot: string,\n): string | undefined {\n if (!attachment.body) return undefined;\n\n if (attachment.contentEncoding === \"BASE64\") {\n return `data:${attachment.mediaType};base64,${attachment.body}`;\n }\n\n const body = attachment.body.trim();\n if (\n body.startsWith(\"http://\") ||\n body.startsWith(\"https://\") ||\n body.startsWith(\"data:\") ||\n body.startsWith(\"#\")\n ) {\n return body;\n }\n\n if (path.isAbsolute(body) && fs.existsSync(body)) {\n return toPosix(path.relative(markdownDir, body));\n }\n\n const candidateFromProject = path.resolve(projectRoot, body);\n if (fs.existsSync(candidateFromProject)) {\n return toPosix(path.relative(markdownDir, candidateFromProject));\n }\n\n return body;\n}\n\nfunction toPages(astroFiles: string[], docsDir: string, orderedSlugs: string[]): DemoPage[] {\n const pages = astroFiles.map((absPath) => {\n const rel = toPosix(path.relative(docsDir, absPath));\n const withoutExt = rel.replace(/\\.md$/, \"\");\n const slug = withoutExt;\n const title = toTitleCase(normalizePageName(path.basename(withoutExt)));\n return {\n title,\n slug,\n file: rel,\n };\n });\n\n if (orderedSlugs.length === 0) return pages;\n\n const rank = new Map(orderedSlugs.map((slug, index) => [slug, index]));\n return [...pages].sort((a, b) => {\n const ar = rank.get(a.slug);\n const br = rank.get(b.slug);\n\n if (ar === undefined && br === undefined) return a.slug.localeCompare(b.slug);\n if (ar === undefined) return 1;\n if (br === undefined) return -1;\n return ar - br;\n });\n}\n\nfunction normalizePageName(fileBase: string): string {\n return fileBase\n .replace(/\\.story\\.index$/i, \"\")\n .replace(/\\.index$/i, \"\")\n .replace(/\\.story$/i, \"\");\n}\n\nfunction applyThemeToAstroConfig(siteDir: string, requestedTheme: string): void {\n const configPath = path.join(siteDir, \"astro.config.mjs\");\n if (!fs.existsSync(configPath)) return;\n\n const theme = normalizeTheme(requestedTheme);\n const config = fs.readFileSync(configPath, \"utf8\");\n const updated = config.replace(\n /'\\.\\/src\\/styles\\/themes\\/[^']+\\.css'/,\n `'./src/styles/themes/${theme}.css'`,\n );\n\n fs.writeFileSync(configPath, updated, \"utf8\");\n}\n\nfunction normalizeTheme(requestedTheme: string | undefined): string {\n if (!requestedTheme) return DEFAULT_CONFIG.theme;\n return SUPPORTED_THEMES.has(requestedTheme) ? requestedTheme : DEFAULT_CONFIG.theme;\n}\n\ntype PageStatus = \"passed\" | \"failed\" | \"skipped\" | \"pending\";\n\ninterface RunStats {\n total: number;\n passed: number;\n failed: number;\n skipped: number;\n}\n\nfunction computeStats(run: TestRunResult): RunStats {\n return {\n total: run.testCases.length,\n passed: run.testCases.filter((tc) => tc.status === \"passed\").length,\n failed: run.testCases.filter((tc) => tc.status === \"failed\").length,\n skipped: run.testCases.filter((tc) => tc.status === \"skipped\").length,\n };\n}\n\nfunction renderHead(config: ResolvedConfig): string[] {\n const seoTitle = config.seo.title ?? config.productName;\n const seoDesc = config.seo.description ?? config.tagline;\n const ogImage = config.branding.ogImage;\n const canonical = config.seo.canonical;\n const twitter = config.seo.twitter;\n\n const tags: Array<{ tag: string; attrs: Record<string, string> }> = [\n { tag: \"meta\", attrs: { property: \"og:type\", content: \"website\" } },\n { tag: \"meta\", attrs: { property: \"og:title\", content: seoTitle } },\n { tag: \"meta\", attrs: { property: \"og:description\", content: seoDesc } },\n { tag: \"meta\", attrs: { name: \"twitter:card\", content: ogImage ? \"summary_large_image\" : \"summary\" } },\n { tag: \"meta\", attrs: { name: \"twitter:title\", content: seoTitle } },\n { tag: \"meta\", attrs: { name: \"twitter:description\", content: seoDesc } },\n ];\n if (ogImage) {\n tags.push({ tag: \"meta\", attrs: { property: \"og:image\", content: ogImage } });\n tags.push({ tag: \"meta\", attrs: { name: \"twitter:image\", content: ogImage } });\n }\n if (canonical) {\n tags.push({ tag: \"meta\", attrs: { property: \"og:url\", content: canonical } });\n tags.push({ tag: \"link\", attrs: { rel: \"canonical\", href: canonical } });\n }\n if (twitter) {\n tags.push({ tag: \"meta\", attrs: { name: \"twitter:site\", content: twitter } });\n }\n if (config.branding.favicon) {\n tags.push({ tag: \"link\", attrs: { rel: \"icon\", href: config.branding.favicon } });\n }\n\n const lines = [\"head:\"];\n for (const { tag, attrs } of tags) {\n lines.push(` - tag: ${tag}`);\n lines.push(` attrs:`);\n for (const [k, v] of Object.entries(attrs)) {\n lines.push(` ${k}: ${yamlString(v)}`);\n }\n }\n return lines;\n}\n\nfunction renderStats(mode: DemoStatsMode, stats: RunStats): string[] {\n if (mode === \"off\") return [];\n\n if (mode === \"capability\") {\n // Customer-facing framing: total = capabilities verified, no fail count up top.\n const verified = stats.passed;\n const inProgress = stats.failed + stats.skipped;\n const items: string[] = [\n ` <ul class=\"demo-stats\" aria-label=\"Coverage summary\">`,\n ` <li class=\"demo-stat\" data-tone=\"total\"><span class=\"demo-stat__value\">${stats.total}</span><span class=\"demo-stat__label\">Scenarios</span></li>`,\n ` <li class=\"demo-stat\" data-tone=\"pass\"><span class=\"demo-stat__value\">${verified}</span><span class=\"demo-stat__label\">Verified</span></li>`,\n ];\n if (inProgress > 0) {\n items.push(\n ` <li class=\"demo-stat\" data-tone=\"pending\"><span class=\"demo-stat__value\">${inProgress}</span><span class=\"demo-stat__label\">In progress</span></li>`,\n );\n }\n items.push(` </ul>`);\n return [\"\", ...items];\n }\n\n // Test mode: original engineering dashboard.\n return [\n \"\",\n ` <ul class=\"demo-stats\" aria-label=\"Test results\">`,\n ` <li class=\"demo-stat\" data-tone=\"total\"><span class=\"demo-stat__value\">${stats.total}</span><span class=\"demo-stat__label\">Scenarios</span></li>`,\n ` <li class=\"demo-stat\" data-tone=\"pass\"><span class=\"demo-stat__value\">${stats.passed}</span><span class=\"demo-stat__label\">Passed</span></li>`,\n ` <li class=\"demo-stat\" data-tone=\"fail\"><span class=\"demo-stat__value\">${stats.failed}</span><span class=\"demo-stat__label\">Failed</span></li>`,\n ` <li class=\"demo-stat\" data-tone=\"skip\"><span class=\"demo-stat__value\">${stats.skipped}</span><span class=\"demo-stat__label\">Skipped</span></li>`,\n ` </ul>`,\n ];\n}\n\nfunction renderStoryList(\n pages: DemoPage[],\n statusBySlug: Map<string, PageStatus>,\n heading: string,\n): string[] {\n const lines: string[] = [\n \"\",\n ` <section class=\"demo-section\">`,\n ` <h2 class=\"demo-section-heading\">${escapeHtml(heading)}</h2>`,\n ];\n if (pages.length === 0) {\n lines.push(\n ` <div class=\"demo-empty\">No stories generated yet. Run <code>executable-stories-demo build</code>.</div>`,\n );\n } else {\n lines.push(` <ol class=\"demo-stories\">`);\n pages.forEach((page, index) => {\n const status = statusBySlug.get(page.slug) ?? \"pending\";\n const indexLabel = String(index + 1).padStart(2, \"0\");\n const title = escapeHtml(page.title);\n const statusLabel = escapeHtml(status);\n const href = toAstroUrl(page.slug);\n lines.push(\n ` <li><a class=\"demo-story\" data-status=\"${statusLabel}\" href=\"${href}\">` +\n `<span class=\"demo-story__index\">${indexLabel}</span>` +\n `<span class=\"demo-story__title\">${title}</span>` +\n `<span class=\"demo-story__status\">${statusLabel}</span></a></li>`,\n );\n });\n lines.push(` </ol>`);\n }\n lines.push(` </section>`);\n return lines;\n}\n\nfunction findFeaturedMedia(\n featuredSlug: string,\n pages: DemoPage[],\n run: TestRunResult,\n docsDir: string,\n assetsBaseUrl: string,\n):\n | { kind: \"video\" | \"image\"; src: string; alt: string; storyHref: string; storyTitle: string }\n | undefined {\n const target = pages.find((p) => p.slug === featuredSlug);\n if (!target) return undefined;\n\n const storiesDir = path.join(docsDir, \"stories\");\n const baseDir = toPosix(path.resolve(storiesDir));\n\n for (const tc of run.testCases) {\n const out = computeStoryOutputPath(tc.sourceFile, baseDir, \"index\");\n const rel = toPosix(path.posix.relative(toPosix(path.resolve(docsDir)), out));\n const slug = rel.replace(/\\.md$/, \"\");\n if (slug !== featuredSlug) continue;\n\n for (const att of tc.attachments) {\n const isVideo = att.mediaType.startsWith(\"video/\");\n const isImage = att.mediaType.startsWith(\"image/\");\n if (!isVideo && !isImage) continue;\n // Best-effort src: data URI for embedded, otherwise assume it lives under assetsBaseUrl\n // by name (the attachment pipeline already copies + hashes files into assetsDir).\n let src: string | undefined;\n if (att.contentEncoding === \"BASE64\") {\n src = `data:${att.mediaType};base64,${att.body}`;\n } else if (att.body) {\n const body = att.body.trim();\n if (body.startsWith(\"http\") || body.startsWith(\"data:\") || body.startsWith(\"/\")) {\n src = body;\n } else {\n src = `${assetsBaseUrl}/${path.posix.basename(body)}`;\n }\n }\n if (!src) continue;\n return {\n kind: isVideo ? \"video\" : \"image\",\n src,\n alt: att.name || target.title,\n storyHref: toAstroUrl(target.slug),\n storyTitle: target.title,\n };\n }\n }\n\n return undefined;\n}\n\nfunction renderFeatured(\n featured: NonNullable<ReturnType<typeof findFeaturedMedia>>,\n): string[] {\n const lines: string[] = [\n \"\",\n ` <section class=\"demo-featured\" aria-labelledby=\"demo-featured-title\">`,\n ` <span class=\"demo-featured__eyebrow\">Watch first</span>`,\n ` <h2 id=\"demo-featured-title\" class=\"demo-featured__title\">${escapeHtml(featured.storyTitle)}</h2>`,\n ` <div class=\"demo-featured__media\">`,\n ];\n if (featured.kind === \"video\") {\n lines.push(\n ` <video class=\"demo-featured__video\" controls preload=\"metadata\" src=\"${escapeHtml(featured.src)}\" aria-label=\"${escapeHtml(featured.alt)}\"></video>`,\n );\n } else {\n lines.push(\n ` <img class=\"demo-featured__image\" src=\"${escapeHtml(featured.src)}\" alt=\"${escapeHtml(featured.alt)}\" />`,\n );\n }\n lines.push(` </div>`);\n lines.push(\n ` <a class=\"demo-featured__link\" href=\"${escapeHtml(featured.storyHref)}\">Read the full scenario →</a>`,\n );\n lines.push(` </section>`);\n return lines;\n}\n\nfunction renderSections(sections: DemoSection[]): string[] {\n if (sections.length === 0) return [];\n const lines: string[] = [];\n\n for (const section of sections) {\n lines.push(\"\");\n if (section.kind === \"feature-grid\") {\n lines.push(` <section class=\"demo-section\">`);\n if (section.heading) {\n lines.push(` <h2 class=\"demo-section-heading\">${escapeHtml(section.heading)}</h2>`);\n }\n lines.push(` <ul class=\"demo-feature-grid\">`);\n for (const item of section.items) {\n lines.push(` <li class=\"demo-feature\">`);\n lines.push(` <h3 class=\"demo-feature__title\">${escapeHtml(item.title)}</h3>`);\n lines.push(` <p class=\"demo-feature__body\">${escapeHtml(item.body)}</p>`);\n lines.push(` </li>`);\n }\n lines.push(` </ul>`);\n lines.push(` </section>`);\n } else if (section.kind === \"narrative\") {\n lines.push(` <section class=\"demo-narrative\">`);\n lines.push(` <div class=\"demo-narrative__copy\">`);\n if (section.eyebrow) {\n lines.push(` <span class=\"demo-narrative__eyebrow\">${escapeHtml(section.eyebrow)}</span>`);\n }\n if (section.heading) {\n lines.push(` <h2 class=\"demo-narrative__heading\">${escapeHtml(section.heading)}</h2>`);\n }\n lines.push(` <p class=\"demo-narrative__body\">${escapeHtml(section.body)}</p>`);\n lines.push(` </div>`);\n if (section.media) {\n lines.push(` <div class=\"demo-narrative__media\">`);\n lines.push(` <img src=\"${escapeHtml(section.media)}\" alt=\"\" />`);\n lines.push(` </div>`);\n }\n lines.push(` </section>`);\n } else if (section.kind === \"quote\") {\n lines.push(` <figure class=\"demo-quote\">`);\n lines.push(` <blockquote class=\"demo-quote__body\">${escapeHtml(section.quote)}</blockquote>`);\n if (section.attribution) {\n lines.push(` <figcaption class=\"demo-quote__attribution\">${escapeHtml(section.attribution)}</figcaption>`);\n }\n lines.push(` </figure>`);\n }\n }\n\n return lines;\n}\n\nfunction writeLandingPage(\n indexPath: string,\n config: ResolvedConfig,\n pages: DemoPage[],\n run: TestRunResult,\n docsDir: string,\n assetsBaseUrl: string,\n): void {\n const stats = computeStats(run);\n const statusBySlug = buildStatusBySlug(run, docsDir);\n const ctaHref = config.cta.url || \"/\";\n const ctaLabel = escapeHtml(config.cta.primary);\n const productName = escapeHtml(config.productName);\n const tagline = escapeHtml(config.tagline);\n const isSplash = config.template === \"splash\";\n\n const frontmatter: string[] = [\n \"---\",\n `title: ${yamlString(config.productName)}`,\n `description: ${yamlString(config.tagline)}`,\n \"template: splash\",\n ];\n // SEO/OG meta via Starlight's frontmatter `head:` block.\n for (const line of renderHead(config)) frontmatter.push(line);\n frontmatter.push(\"---\", \"\");\n\n const accentVar = config.branding.accent\n ? ` style=\"--demo-accent-override: ${cssColorEscape(config.branding.accent)};\"`\n : \"\";\n\n const lines: string[] = [\n ...frontmatter,\n `{/* Generated by executable-stories-demo. Edit demo.config.json, not this file. */}`,\n `{/* template=${config.template} theme=${config.theme} stats=${config.stats.mode} */}`,\n \"\",\n `<div class=\"demo-landing not-content\" data-template=\"${config.template}\"${accentVar}>`,\n \"\",\n ` <section class=\"demo-hero\">`,\n ];\n\n if (config.branding.logo) {\n lines.push(\n ` <img class=\"demo-hero__logo\" src=\"${escapeHtml(config.branding.logo)}\" alt=\"${escapeHtml(config.productName + \" logo\")}\" />`,\n );\n }\n lines.push(\n ` <span class=\"demo-hero__eyebrow\">${escapeHtml(isSplash ? \"Product demo\" : \"Executable stories\")}</span>`,\n ` <h1 class=\"demo-hero__title\">${productName}</h1>`,\n ` <p class=\"demo-hero__tagline\">${tagline}</p>`,\n ` <a class=\"demo-hero__cta\" href=\"${escapeHtml(ctaHref)}\">${ctaLabel}</a>`,\n ` </section>`,\n );\n\n // Featured scenario (splash mode only — and only if config.featured.scenario resolves to a real story).\n if (isSplash && config.featured.scenario) {\n const featured = findFeaturedMedia(\n config.featured.scenario,\n pages,\n run,\n docsDir,\n assetsBaseUrl,\n );\n if (featured) lines.push(...renderFeatured(featured));\n }\n\n // Stats — capability framing in splash, test framing in dashboard, hidden if mode=off.\n for (const line of renderStats(config.stats.mode, stats)) lines.push(line);\n\n // Custom sections (splash only — dashboard stays focused on the test run).\n if (isSplash) {\n for (const line of renderSections(config.sections)) lines.push(line);\n }\n\n // Story list (always, but heading copy depends on mode).\n const storyHeading = isSplash ? \"Scenarios\" : \"Stories\";\n for (const line of renderStoryList(pages, statusBySlug, storyHeading)) {\n lines.push(line);\n }\n\n lines.push(\"\");\n lines.push(\"</div>\");\n\n fs.writeFileSync(indexPath, `${lines.join(\"\\n\")}\\n`, \"utf8\");\n}\n\n/**\n * YAML-safe scalar quote: `\"foo\"` for any value that contains characters YAML\n * would interpret. Used for frontmatter values we generate from user config.\n */\nfunction yamlString(value: string): string {\n return `\"${value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"`;\n}\n\n/** Inline CSS values get a tighter sanitisation than HTML attributes. */\nfunction cssColorEscape(value: string): string {\n // Strip anything that isn't a plausible CSS value character to neutralise\n // injection via style attributes. Keep alnum + common color punctuation.\n return value.replace(/[^a-zA-Z0-9#%(),.\\-\\s/]/g, \"\");\n}\n\nfunction buildStatusBySlug(run: TestRunResult, docsDir: string): Map<string, PageStatus> {\n const storiesDir = path.join(docsDir, \"stories\");\n const baseDir = toPosix(path.resolve(storiesDir));\n const absDocsDir = toPosix(path.resolve(docsDir));\n const result = new Map<string, PageStatus>();\n\n for (const tc of run.testCases) {\n const outputPath = computeStoryOutputPath(tc.sourceFile, baseDir, \"index\");\n const relative = toPosix(path.posix.relative(absDocsDir, outputPath));\n const slug = relative.replace(/\\.md$/, \"\");\n const current = result.get(slug);\n result.set(slug, mergePageStatus(current, tc.status as PageStatus));\n }\n\n return result;\n}\n\nfunction mergePageStatus(current: PageStatus | undefined, incoming: PageStatus): PageStatus {\n if (!current) return incoming;\n if (current === \"failed\" || incoming === \"failed\") return \"failed\";\n if (current === \"passed\" || incoming === \"passed\") return \"passed\";\n if (current === \"skipped\" && incoming === \"skipped\") return \"skipped\";\n return current;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n// Astro Starlight's content loader sanitizes slug segments by stripping\n// non-alphanumeric/-/_ characters and lower-casing. Mirror that so the\n// landing-page links resolve to the pages Astro actually emits.\nfunction toAstroUrl(pageSlug: string): string {\n const sanitized = pageSlug\n .split(\"/\")\n .map((segment) => segment.toLowerCase().replace(/[^a-z0-9_-]/g, \"\"))\n .filter(Boolean)\n .join(\"/\");\n return sanitized.length === 0 ? \"/\" : `/${sanitized}/`;\n}\n\nfunction copyDirRecursive(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n const entries = fs.readdirSync(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n copyDirRecursive(srcPath, destPath);\n } else {\n fs.copyFileSync(srcPath, destPath);\n }\n }\n}\n\nfunction toTitleCase(value: string): string {\n return value\n .replace(/[-_]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .replace(/\\b\\w/g, (match) => match.toUpperCase());\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join(\"/\");\n}\n\nfunction normalizeAssetsBaseUrl(value: string | undefined): string {\n const base = value ?? \"/demo-assets\";\n if (!base.startsWith(\"/\")) return `/${base.replace(/\\/+$/, \"\")}`;\n return base.replace(/\\/+$/, \"\") || \"/demo-assets\";\n}\n\nfunction resolveAssetsDir(siteDir: string, value: string | undefined): string {\n if (!value) return path.join(siteDir, \"public\", \"demo-assets\");\n if (path.isAbsolute(value)) return value;\n return path.join(siteDir, value);\n}\n"],"mappings":"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","names":["relative"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "executable-stories-demo",
3
+ "version": "0.1.0",
4
+ "description": "Publish Astro product demo sites from executable stories runs",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "templates",
19
+ "README.md",
20
+ "bin"
21
+ ],
22
+ "bin": {
23
+ "executable-stories-demo": "./bin/executable-stories-demo.js",
24
+ "intent": "./bin/intent.js"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "type-check": "tsc --noEmit",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest"
31
+ },
32
+ "dependencies": {
33
+ "executable-stories-formatters": "workspace:*"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^25.6.0",
37
+ "tsup": "^8.5.1",
38
+ "typescript": "~6.0.3",
39
+ "vitest": "^4.1.5"
40
+ },
41
+ "engines": {
42
+ "node": ">=22"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/jagreehal/executable-stories.git",
47
+ "directory": "packages/executable-stories-demo"
48
+ },
49
+ "keywords": [
50
+ "executable-stories",
51
+ "playwright",
52
+ "astro",
53
+ "demo",
54
+ "publishing"
55
+ ]
56
+ }
@@ -0,0 +1,26 @@
1
+ // @ts-check
2
+ import starlight from '@astrojs/starlight';
3
+ import astroMermaid from 'astro-mermaid';
4
+ import { defineConfig } from 'astro/config';
5
+
6
+ export default defineConfig({
7
+ integrations: [
8
+ astroMermaid(),
9
+ starlight({
10
+ title: 'Product Demo',
11
+ description: 'Generated demo site from executable stories.',
12
+ sidebar: [
13
+ { label: 'Home', slug: 'index' },
14
+ {
15
+ label: 'Stories',
16
+ autogenerate: { directory: 'stories' },
17
+ },
18
+ { label: 'Themes', slug: 'themes' },
19
+ ],
20
+ customCss: [
21
+ './src/styles/global.css',
22
+ './src/styles/themes/default.css',
23
+ ],
24
+ }),
25
+ ],
26
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "executable-stories-demo-site",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "astro dev",
8
+ "build": "astro build",
9
+ "preview": "astro preview"
10
+ },
11
+ "dependencies": {
12
+ "@astrojs/starlight": "^0.38.3",
13
+ "@fontsource-variable/bricolage-grotesque": "^5.2.5",
14
+ "@fontsource-variable/literata": "^5.2.5",
15
+ "@fontsource/albert-sans": "^5.2.5",
16
+ "@fontsource/archivo": "^5.2.5",
17
+ "@fontsource/figtree": "^5.2.5",
18
+ "@fontsource/funnel-sans": "^5.2.5",
19
+ "@fontsource/jetbrains-mono": "^5.2.5",
20
+ "@fontsource/vt323": "^5.2.5",
21
+ "@fontsource/work-sans": "^5.2.5",
22
+ "astro": "^6.1.8",
23
+ "astro-mermaid": "^2.0.1",
24
+ "mermaid": "^11.14.0"
25
+ }
26
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ title: Product Demo
3
+ description: Generated demo site from executable stories.
4
+ template: splash
5
+ ---
6
+
7
+ <div class="demo-landing not-content" data-template="splash">
8
+
9
+ <section class="demo-hero">
10
+ <span class="demo-hero__eyebrow">Product demo</span>
11
+ <h1 class="demo-hero__title">Product Demo</h1>
12
+ <p class="demo-hero__tagline">Your Playwright runs, published as a product demo. Run the build command below to populate this page.</p>
13
+ <a class="demo-hero__cta" href="/">Get started</a>
14
+ </section>
15
+
16
+ <section class="demo-section">
17
+ <h2 class="demo-section-heading">Next step</h2>
18
+ <div class="demo-empty">
19
+ Generate story pages with <code>executable-stories-demo build --input reports/raw-run.json --site .</code>
20
+ </div>
21
+ </section>
22
+
23
+ </div>
@@ -0,0 +1,190 @@
1
+ ---
2
+ title: Themes
3
+ description: Six built-in themes for executable-stories-demo. Pick one in demo.config.json.
4
+ ---
5
+
6
+ Each theme is a single CSS file that swaps the `--demo-*` design tokens. To
7
+ switch, edit `demo.config.json`:
8
+
9
+ ```json
10
+ {
11
+ "theme": "default"
12
+ }
13
+ ```
14
+
15
+ Then rebuild (`executable-stories-demo build`). Only one theme is active per
16
+ site; this page previews each palette so you can choose without scaffolding
17
+ six sites.
18
+
19
+ <style>{`
20
+ .theme-grid {
21
+ display: grid;
22
+ grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
23
+ gap: 1.5rem;
24
+ margin-block: 2rem;
25
+ padding: 0;
26
+ list-style: none;
27
+ }
28
+ .theme-grid li { margin: 0; max-width: none; }
29
+ .theme-card {
30
+ border: 1px solid var(--sl-color-gray-5, #e5e7eb);
31
+ border-radius: 10px;
32
+ overflow: hidden;
33
+ display: grid;
34
+ grid-template-rows: auto auto auto;
35
+ }
36
+ .theme-card__sample {
37
+ padding: 1.5rem 1.25rem;
38
+ min-height: 6.5rem;
39
+ display: grid;
40
+ align-content: center;
41
+ gap: 0.35rem;
42
+ }
43
+ .theme-card__name {
44
+ font-size: 1.6rem;
45
+ font-weight: 700;
46
+ letter-spacing: -0.02em;
47
+ margin: 0;
48
+ }
49
+ .theme-card__voice {
50
+ font-size: 0.85rem;
51
+ margin: 0;
52
+ opacity: 0.75;
53
+ }
54
+ .theme-card__swatches {
55
+ display: grid;
56
+ grid-template-columns: repeat(4, 1fr);
57
+ gap: 1px;
58
+ background: rgba(0,0,0,0.08);
59
+ }
60
+ .theme-card__swatch {
61
+ aspect-ratio: 4 / 3;
62
+ display: grid;
63
+ align-content: end;
64
+ padding: 0.4rem 0.5rem;
65
+ font-family: ui-monospace, "SF Mono", monospace;
66
+ font-size: 0.65rem;
67
+ letter-spacing: 0.04em;
68
+ }
69
+ .theme-card__meta {
70
+ padding: 0.85rem 1.25rem;
71
+ font-family: ui-monospace, "SF Mono", monospace;
72
+ font-size: 0.78rem;
73
+ background: var(--sl-color-bg-nav, #fafafa);
74
+ color: var(--sl-color-text-accent);
75
+ }
76
+ .theme-card__meta b { font-weight: 600; }
77
+ `}</style>
78
+
79
+ <ul class="theme-grid">
80
+
81
+ <li>
82
+ <article class="theme-card">
83
+ <div class="theme-card__sample" style="background: oklch(0.985 0.012 85); color: oklch(0.18 0.015 60); font-family: 'Literata', Georgia, serif;">
84
+ <h3 class="theme-card__name">default</h3>
85
+ <p class="theme-card__voice">Editorial warmth. Magazine-leaning serif display, bottle-green accent.</p>
86
+ </div>
87
+ <div class="theme-card__swatches">
88
+ <div class="theme-card__swatch" style="background: oklch(0.985 0.012 85); color: oklch(0.18 0.015 60);">bg</div>
89
+ <div class="theme-card__swatch" style="background: oklch(0.965 0.018 85); color: oklch(0.18 0.015 60);">surf</div>
90
+ <div class="theme-card__swatch" style="background: oklch(0.42 0.12 150); color: white;">accent</div>
91
+ <div class="theme-card__swatch" style="background: oklch(0.18 0.015 60); color: oklch(0.985 0.012 85);">ink</div>
92
+ </div>
93
+ <div class="theme-card__meta"><b>theme:</b> "default" — Literata + Work Sans</div>
94
+ </article>
95
+ </li>
96
+
97
+ <li>
98
+ <article class="theme-card">
99
+ <div class="theme-card__sample" style="background: oklch(0.99 0.005 245); color: oklch(0.20 0.025 260); font-family: Archivo, system-ui, sans-serif; font-weight: 800; letter-spacing: -0.025em;">
100
+ <h3 class="theme-card__name">corporate</h3>
101
+ <p class="theme-card__voice" style="font-weight: 400; letter-spacing: 0;">Institutional grotesk. High density, sharp 1-px borders, steel-blue accent.</p>
102
+ </div>
103
+ <div class="theme-card__swatches">
104
+ <div class="theme-card__swatch" style="background: oklch(0.99 0.005 245); color: oklch(0.20 0.025 260);">bg</div>
105
+ <div class="theme-card__swatch" style="background: oklch(0.975 0.008 245); color: oklch(0.20 0.025 260);">surf</div>
106
+ <div class="theme-card__swatch" style="background: oklch(0.38 0.14 255); color: white;">accent</div>
107
+ <div class="theme-card__swatch" style="background: oklch(0.20 0.025 260); color: oklch(0.99 0.005 245);">ink</div>
108
+ </div>
109
+ <div class="theme-card__meta"><b>theme:</b> "corporate" — Archivo only · forced light</div>
110
+ </article>
111
+ </li>
112
+
113
+ <li>
114
+ <article class="theme-card">
115
+ <div class="theme-card__sample" style="background: oklch(0.96 0.045 88); color: oklch(0.22 0.04 20); font-family: 'Bricolage Grotesque', system-ui, sans-serif; font-weight: 800;">
116
+ <h3 class="theme-card__name">playful</h3>
117
+ <p class="theme-card__voice" style="font-weight: 500;">Risograph poster. Persimmon + inky-blue spots, hard-shadow CTAs.</p>
118
+ </div>
119
+ <div class="theme-card__swatches">
120
+ <div class="theme-card__swatch" style="background: oklch(0.96 0.045 88); color: oklch(0.22 0.04 20);">bg</div>
121
+ <div class="theme-card__swatch" style="background: oklch(0.93 0.055 88); color: oklch(0.22 0.04 20);">surf</div>
122
+ <div class="theme-card__swatch" style="background: oklch(0.64 0.22 30); color: white;">accent</div>
123
+ <div class="theme-card__swatch" style="background: oklch(0.40 0.18 265); color: white;">spot</div>
124
+ </div>
125
+ <div class="theme-card__meta"><b>theme:</b> "playful" — Bricolage + Figtree · forced light</div>
126
+ </article>
127
+ </li>
128
+
129
+ <li>
130
+ <article class="theme-card">
131
+ <div class="theme-card__sample" style="background: oklch(0.15 0.02 150); color: oklch(0.84 0.17 140); font-family: 'VT323', 'JetBrains Mono', monospace; text-shadow: 0 0 12px rgba(132,210,82,0.4);">
132
+ <h3 class="theme-card__name" style="font-weight: 400;">terminal_</h3>
133
+ <p class="theme-card__voice" style="font-family: 'JetBrains Mono', monospace; font-size: 0.78rem;">Phosphor CRT. Near-black bg, blinking cursor on the hero title.</p>
134
+ </div>
135
+ <div class="theme-card__swatches">
136
+ <div class="theme-card__swatch" style="background: oklch(0.15 0.02 150); color: oklch(0.84 0.17 140);">bg</div>
137
+ <div class="theme-card__swatch" style="background: oklch(0.19 0.025 150); color: oklch(0.84 0.17 140);">surf</div>
138
+ <div class="theme-card__swatch" style="background: oklch(0.82 0.18 90); color: oklch(0.15 0.02 150);">accent</div>
139
+ <div class="theme-card__swatch" style="background: oklch(0.84 0.17 140); color: oklch(0.15 0.02 150);">ink</div>
140
+ </div>
141
+ <div class="theme-card__meta"><b>theme:</b> "terminal" — VT323 + JetBrains Mono · forced dark</div>
142
+ </article>
143
+ </li>
144
+
145
+ <li>
146
+ <article class="theme-card">
147
+ <div class="theme-card__sample" style="background: oklch(0.16 0.012 250); color: oklch(0.93 0.01 240); font-family: 'Funnel Sans', system-ui, sans-serif; font-weight: 600; background-image: linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px); background-size: 2rem 2rem;">
148
+ <h3 class="theme-card__name">dashboard</h3>
149
+ <p class="theme-card__voice" style="font-weight: 400;">Ops room. Slate surfaces, teal accent, status pills, faint grid backdrop.</p>
150
+ </div>
151
+ <div class="theme-card__swatches">
152
+ <div class="theme-card__swatch" style="background: oklch(0.16 0.012 250); color: oklch(0.93 0.01 240);">bg</div>
153
+ <div class="theme-card__swatch" style="background: oklch(0.20 0.014 250); color: oklch(0.93 0.01 240);">surf</div>
154
+ <div class="theme-card__swatch" style="background: oklch(0.76 0.14 195); color: oklch(0.15 0.015 250);">accent</div>
155
+ <div class="theme-card__swatch" style="background: oklch(0.76 0.17 145); color: oklch(0.15 0.015 250);">pass</div>
156
+ </div>
157
+ <div class="theme-card__meta"><b>theme:</b> "dashboard" — Funnel Sans + JetBrains Mono · forced dark</div>
158
+ </article>
159
+ </li>
160
+
161
+ <li>
162
+ <article class="theme-card">
163
+ <div class="theme-card__sample" style="background: oklch(0.985 0 0); color: oklch(0.14 0 0); font-family: 'Albert Sans', system-ui, sans-serif; font-weight: 300; letter-spacing: -0.035em;">
164
+ <h3 class="theme-card__name" style="font-weight: 300;">minimal</h3>
165
+ <p class="theme-card__voice" style="font-weight: 300; letter-spacing: 0;">Aggressive restraint. 300/700 weight contrast, single oxidised-orange dot.</p>
166
+ </div>
167
+ <div class="theme-card__swatches">
168
+ <div class="theme-card__swatch" style="background: oklch(0.985 0 0); color: oklch(0.14 0 0);">bg</div>
169
+ <div class="theme-card__swatch" style="background: oklch(0.94 0.04 35); color: oklch(0.30 0.14 35);">accent-low</div>
170
+ <div class="theme-card__swatch" style="background: oklch(0.56 0.18 35); color: white;">accent</div>
171
+ <div class="theme-card__swatch" style="background: oklch(0.14 0 0); color: oklch(0.985 0 0);">ink</div>
172
+ </div>
173
+ <div class="theme-card__meta"><b>theme:</b> "minimal" — Albert Sans only · forced light</div>
174
+ </article>
175
+ </li>
176
+
177
+ </ul>
178
+
179
+ ## Choosing a theme
180
+
181
+ Use the table to pick by audience and tone:
182
+
183
+ | Theme | Best for | Light/Dark |
184
+ |---|---|---|
185
+ | `default` | Developer relations, content-heavy product pages | Adaptive |
186
+ | `corporate` | Enterprise sales decks, compliance documentation | Light only |
187
+ | `playful` | Consumer products, design-tool demos, launch sites | Light only |
188
+ | `terminal` | DevTools, CLIs, infrastructure products | Dark only |
189
+ | `dashboard` | Observability, analytics, ops platforms | Dark only |
190
+ | `minimal` | Brand-led product pages where the work speaks for itself | Light only |
@@ -0,0 +1,7 @@
1
+ import { defineCollection } from 'astro:content';
2
+ import { docsLoader } from '@astrojs/starlight/loaders';
3
+ import { docsSchema } from '@astrojs/starlight/schema';
4
+
5
+ export const collections = {
6
+ docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
7
+ };