diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc8f2a5..788111b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,8 +121,57 @@ jobs: # 1% tolerance at threshold 0.1 accommodates resvg native binary variation across architectures. run: bun run .github/scripts/compare-og.mjs /tmp/og-ref/og-x64.png /tmp/og-arm64.png - publish: + demo-probe: needs: verify + runs-on: [self-hosted, Linux, X64] + timeout-minutes: 20 + steps: + - name: checkout + uses: actions/checkout@v4 + with: + submodules: false + fetch-depth: 1 + + - name: setup-bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.2.x + no-cache: true + + - name: install + run: bun install --frozen-lockfile + + - name: build + run: bun run build + + - name: install-demo + working-directory: examples/demo + run: bun install --frozen-lockfile + + - name: build-demo + working-directory: examples/demo + env: + VITE_BASE: /vBrand/ + run: bun run build + + - name: playwright-install + run: npx playwright install chromium + + - name: test-e2e + run: bun run test:e2e + + - name: probe-verdict-gate + if: always() + run: | + if [ -f /tmp/vbrand-probe-results.json ]; then + node scripts/probe-verdict-gate.mjs /tmp/vbrand-probe-results.json + else + echo 'UNGROUNDED-CLAIM: 0 probes, 0 bugs, 0/3 axes covered' + exit 1 + fi + + publish: + needs: [verify, demo-probe] if: startsWith(github.ref, 'refs/tags/v') runs-on: [self-hosted, Linux, X64] steps: diff --git a/.gitignore b/.gitignore index bcd2588..4ae3a2f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ vbrand/.cache/ .playwright-cli/ .vbrand/ examples/demo/test-results/ +/test-results/ src/ssr/tailwind.css src/ssr/tailwind-bundle.ts diff --git a/.interface-design/system.md b/.interface-design/system.md index a2909f8..afc0146 100644 --- a/.interface-design/system.md +++ b/.interface-design/system.md @@ -42,7 +42,8 @@ Table uses `borderCollapse: 'collapse'` with a 2px bottom border on thead, 1px o ## Component Patterns ### Badge -Confidence pill: white text on `CONFIDENCE_COLOR[level]` background; `borderRadius: 4`, `padding: '2px 8px'`, `fontSize: 11`, `fontWeight: 700`, `letterSpacing: 1`. +Confidence pill: white text on `CONFIDENCE_COLOR[level]` background; `borderRadius: '4px'`, `padding: '2px 8px'`, `fontSize: 11`, `fontWeight: 700`, `letterSpacing: 1`. +Note: borderRadius is always expressed as a string `'4px'` throughout the codebase (not numeric `4`). ### FieldRow Table row with `CONFIDENCE_BG[confidence]` background. Columns: field name (monospace), badge, value (word-break), source/reason (muted). diff --git a/examples/demo/index.html b/examples/demo/index.html index 4b21907..e3ce446 100644 --- a/examples/demo/index.html +++ b/examples/demo/index.html @@ -3,8 +3,9 @@ - vBrand 0.4.0-alpha.4 - adaptive themed demo - + vBrand - adaptive themed demo + + diff --git a/examples/demo/package.json b/examples/demo/package.json index 24fcda3..59bb39d 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -1,6 +1,6 @@ { "name": "@booga/vbrand-demo", - "version": "0.4.0-alpha.4.2", + "version": "0.4.0-alpha.5", "private": true, "type": "module", "scripts": { diff --git a/examples/demo/playwright.config.ts b/examples/demo/playwright.config.ts index ee41794..2db8a65 100644 --- a/examples/demo/playwright.config.ts +++ b/examples/demo/playwright.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ retries: process.env['CI'] ? 2 : 0, reporter: [ ['list'], + ['json', { outputFile: '/tmp/vbrand-probe-results.json' }], [path.join(__dirname, 'tests/runtime-probe/verdict-reporter.ts')], ], use: { diff --git a/examples/demo/postcss.config.js b/examples/demo/postcss.config.js index 867fba4..82e84b9 100644 --- a/examples/demo/postcss.config.js +++ b/examples/demo/postcss.config.js @@ -1,8 +1,19 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 bvasilenko -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); + +function hasPackage(name) { + try { + require.resolve(name); + return true; + } catch { + return false; + } +} + +const plugins = { tailwindcss: {} }; +if (hasPackage('autoprefixer')) plugins.autoprefixer = {}; + +export default { plugins }; diff --git a/examples/demo/public/cms-fixtures/payload-collections.json b/examples/demo/public/cms-fixtures/payload-collections.json new file mode 100644 index 0000000..5922798 --- /dev/null +++ b/examples/demo/public/cms-fixtures/payload-collections.json @@ -0,0 +1,30 @@ +{ + "collections": [ + { + "slug": "pages", + "fields": [ + "landing.hero.heading", + "landing.hero.eyebrow", + "landing.hero.description", + "landing.hero.primaryCta.label", + "landing.features.heading", + "landing.cta.heading", + "landing.cta.description", + "landing.cta.primaryCta.label", + "landing.footer.copyright", + "marketing.hero.heading", + "marketing.hero.eyebrow", + "marketing.hero.description", + "marketing.testimonials.heading", + "marketing.pricing.heading", + "marketing.cta.primaryCta.label", + "docs.sidebar.heading", + "docs.article.title", + "docs.toc.heading", + "dashboard.sidebar.heading", + "dashboard.stats.heading", + "dashboard.grid.heading" + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo/public/cms-fixtures/payload-pages.json b/examples/demo/public/cms-fixtures/payload-pages.json new file mode 100644 index 0000000..b8c3504 --- /dev/null +++ b/examples/demo/public/cms-fixtures/payload-pages.json @@ -0,0 +1,134 @@ +{ + "docs": [ + { + "slug": "stripe", + "content": { + "landing.hero.heading": "Stripe", + "landing.hero.eyebrow": "Financial infrastructure to grow your revenue.", + "landing.hero.description": "Payments and billing infrastructure for internet businesses", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Stripe", + "landing.cta.heading": "Build with Stripe", + "landing.cta.description": "Payments and billing infrastructure for internet businesses", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Stripe", + "marketing.hero.heading": "Stripe", + "marketing.hero.eyebrow": "Financial infrastructure to grow your revenue.", + "marketing.hero.description": "Payments and billing infrastructure for internet businesses", + "marketing.testimonials.heading": "What teams say about Stripe", + "marketing.pricing.heading": "Simple pricing for Stripe brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Stripe", + "docs.article.title": "Stripe brand guide", + "docs.toc.heading": "Stripe on this page", + "dashboard.sidebar.heading": "Stripe", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "slug": "vercel", + "content": { + "landing.hero.heading": "Vercel", + "landing.hero.eyebrow": "Build and deploy on the AI Cloud.", + "landing.hero.description": "Frontend cloud platform for deploying and scaling web applications", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Vercel", + "landing.cta.heading": "Build with Vercel", + "landing.cta.description": "Frontend cloud platform for deploying and scaling web applications", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Vercel", + "marketing.hero.heading": "Vercel", + "marketing.hero.eyebrow": "Build and deploy on the AI Cloud.", + "marketing.hero.description": "Frontend cloud platform for deploying and scaling web applications", + "marketing.testimonials.heading": "What teams say about Vercel", + "marketing.pricing.heading": "Simple pricing for Vercel brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Vercel", + "docs.article.title": "Vercel brand guide", + "docs.toc.heading": "Vercel on this page", + "dashboard.sidebar.heading": "Vercel", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "slug": "linear", + "content": { + "landing.hero.heading": "Linear", + "landing.hero.eyebrow": "The product development system for teams and agents.", + "landing.hero.description": "Issue tracking and project management for high-performance software teams", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Linear", + "landing.cta.heading": "Build with Linear", + "landing.cta.description": "Issue tracking and project management for high-performance software teams", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Linear", + "marketing.hero.heading": "Linear", + "marketing.hero.eyebrow": "The product development system for teams and agents.", + "marketing.hero.description": "Issue tracking and project management for high-performance software teams", + "marketing.testimonials.heading": "What teams say about Linear", + "marketing.pricing.heading": "Simple pricing for Linear brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Linear", + "docs.article.title": "Linear brand guide", + "docs.toc.heading": "Linear on this page", + "dashboard.sidebar.heading": "Linear", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "slug": "notion", + "content": { + "landing.hero.heading": "Notion", + "landing.hero.eyebrow": "The AI workspace that works for you.", + "landing.hero.description": "All-in-one workspace for notes, wikis, databases, and project management", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Notion", + "landing.cta.heading": "Build with Notion", + "landing.cta.description": "All-in-one workspace for notes, wikis, databases, and project management", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Notion", + "marketing.hero.heading": "Notion", + "marketing.hero.eyebrow": "The AI workspace that works for you.", + "marketing.hero.description": "All-in-one workspace for notes, wikis, databases, and project management", + "marketing.testimonials.heading": "What teams say about Notion", + "marketing.pricing.heading": "Simple pricing for Notion brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Notion", + "docs.article.title": "Notion brand guide", + "docs.toc.heading": "Notion on this page", + "dashboard.sidebar.heading": "Notion", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "slug": "github", + "content": { + "landing.hero.heading": "GitHub", + "landing.hero.eyebrow": "The future of building happens together.", + "landing.hero.description": "Software development and collaboration platform for over 100 million developers", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines GitHub", + "landing.cta.heading": "Build with GitHub", + "landing.cta.description": "Software development and collaboration platform for over 100 million developers", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 GitHub", + "marketing.hero.heading": "GitHub", + "marketing.hero.eyebrow": "The future of building happens together.", + "marketing.hero.description": "Software development and collaboration platform for over 100 million developers", + "marketing.testimonials.heading": "What teams say about GitHub", + "marketing.pricing.heading": "Simple pricing for GitHub brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "GitHub", + "docs.article.title": "GitHub brand guide", + "docs.toc.heading": "GitHub on this page", + "dashboard.sidebar.heading": "GitHub", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + } + ] +} \ No newline at end of file diff --git a/examples/demo/public/cms-fixtures/sanity-pages.json b/examples/demo/public/cms-fixtures/sanity-pages.json new file mode 100644 index 0000000..fe66d53 --- /dev/null +++ b/examples/demo/public/cms-fixtures/sanity-pages.json @@ -0,0 +1,149 @@ +{ + "result": [ + { + "_id": "page.stripe", + "slug": { + "current": "stripe" + }, + "content": { + "landing.hero.heading": "Stripe", + "landing.hero.eyebrow": "Financial infrastructure to grow your revenue.", + "landing.hero.description": "Payments and billing infrastructure for internet businesses", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Stripe", + "landing.cta.heading": "Build with Stripe", + "landing.cta.description": "Payments and billing infrastructure for internet businesses", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Stripe", + "marketing.hero.heading": "Stripe", + "marketing.hero.eyebrow": "Financial infrastructure to grow your revenue.", + "marketing.hero.description": "Payments and billing infrastructure for internet businesses", + "marketing.testimonials.heading": "What teams say about Stripe", + "marketing.pricing.heading": "Simple pricing for Stripe brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Stripe", + "docs.article.title": "Stripe brand guide", + "docs.toc.heading": "Stripe on this page", + "dashboard.sidebar.heading": "Stripe", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "_id": "page.vercel", + "slug": { + "current": "vercel" + }, + "content": { + "landing.hero.heading": "Vercel", + "landing.hero.eyebrow": "Build and deploy on the AI Cloud.", + "landing.hero.description": "Frontend cloud platform for deploying and scaling web applications", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Vercel", + "landing.cta.heading": "Build with Vercel", + "landing.cta.description": "Frontend cloud platform for deploying and scaling web applications", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Vercel", + "marketing.hero.heading": "Vercel", + "marketing.hero.eyebrow": "Build and deploy on the AI Cloud.", + "marketing.hero.description": "Frontend cloud platform for deploying and scaling web applications", + "marketing.testimonials.heading": "What teams say about Vercel", + "marketing.pricing.heading": "Simple pricing for Vercel brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Vercel", + "docs.article.title": "Vercel brand guide", + "docs.toc.heading": "Vercel on this page", + "dashboard.sidebar.heading": "Vercel", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "_id": "page.linear", + "slug": { + "current": "linear" + }, + "content": { + "landing.hero.heading": "Linear", + "landing.hero.eyebrow": "The product development system for teams and agents.", + "landing.hero.description": "Issue tracking and project management for high-performance software teams", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Linear", + "landing.cta.heading": "Build with Linear", + "landing.cta.description": "Issue tracking and project management for high-performance software teams", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Linear", + "marketing.hero.heading": "Linear", + "marketing.hero.eyebrow": "The product development system for teams and agents.", + "marketing.hero.description": "Issue tracking and project management for high-performance software teams", + "marketing.testimonials.heading": "What teams say about Linear", + "marketing.pricing.heading": "Simple pricing for Linear brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Linear", + "docs.article.title": "Linear brand guide", + "docs.toc.heading": "Linear on this page", + "dashboard.sidebar.heading": "Linear", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "_id": "page.notion", + "slug": { + "current": "notion" + }, + "content": { + "landing.hero.heading": "Notion", + "landing.hero.eyebrow": "The AI workspace that works for you.", + "landing.hero.description": "All-in-one workspace for notes, wikis, databases, and project management", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Notion", + "landing.cta.heading": "Build with Notion", + "landing.cta.description": "All-in-one workspace for notes, wikis, databases, and project management", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Notion", + "marketing.hero.heading": "Notion", + "marketing.hero.eyebrow": "The AI workspace that works for you.", + "marketing.hero.description": "All-in-one workspace for notes, wikis, databases, and project management", + "marketing.testimonials.heading": "What teams say about Notion", + "marketing.pricing.heading": "Simple pricing for Notion brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Notion", + "docs.article.title": "Notion brand guide", + "docs.toc.heading": "Notion on this page", + "dashboard.sidebar.heading": "Notion", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + }, + { + "_id": "page.github", + "slug": { + "current": "github" + }, + "content": { + "landing.hero.heading": "GitHub", + "landing.hero.eyebrow": "The future of building happens together.", + "landing.hero.description": "Software development and collaboration platform for over 100 million developers", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines GitHub", + "landing.cta.heading": "Build with GitHub", + "landing.cta.description": "Software development and collaboration platform for over 100 million developers", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 GitHub", + "marketing.hero.heading": "GitHub", + "marketing.hero.eyebrow": "The future of building happens together.", + "marketing.hero.description": "Software development and collaboration platform for over 100 million developers", + "marketing.testimonials.heading": "What teams say about GitHub", + "marketing.pricing.heading": "Simple pricing for GitHub brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "GitHub", + "docs.article.title": "GitHub brand guide", + "docs.toc.heading": "GitHub on this page", + "dashboard.sidebar.heading": "GitHub", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + } + ] +} \ No newline at end of file diff --git a/examples/demo/public/cms-fixtures/sanity-schema.json b/examples/demo/public/cms-fixtures/sanity-schema.json new file mode 100644 index 0000000..9c0efa2 --- /dev/null +++ b/examples/demo/public/cms-fixtures/sanity-schema.json @@ -0,0 +1,30 @@ +{ + "types": [ + { + "name": "page", + "fields": [ + "landing.hero.heading", + "landing.hero.eyebrow", + "landing.hero.description", + "landing.hero.primaryCta.label", + "landing.features.heading", + "landing.cta.heading", + "landing.cta.description", + "landing.cta.primaryCta.label", + "landing.footer.copyright", + "marketing.hero.heading", + "marketing.hero.eyebrow", + "marketing.hero.description", + "marketing.testimonials.heading", + "marketing.pricing.heading", + "marketing.cta.primaryCta.label", + "docs.sidebar.heading", + "docs.article.title", + "docs.toc.heading", + "dashboard.sidebar.heading", + "dashboard.stats.heading", + "dashboard.grid.heading" + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo/public/cms-fixtures/strapi-content-types.json b/examples/demo/public/cms-fixtures/strapi-content-types.json new file mode 100644 index 0000000..defcfc0 --- /dev/null +++ b/examples/demo/public/cms-fixtures/strapi-content-types.json @@ -0,0 +1,30 @@ +{ + "contentTypes": [ + { + "uid": "api::page.page", + "attributes": [ + "landing.hero.heading", + "landing.hero.eyebrow", + "landing.hero.description", + "landing.hero.primaryCta.label", + "landing.features.heading", + "landing.cta.heading", + "landing.cta.description", + "landing.cta.primaryCta.label", + "landing.footer.copyright", + "marketing.hero.heading", + "marketing.hero.eyebrow", + "marketing.hero.description", + "marketing.testimonials.heading", + "marketing.pricing.heading", + "marketing.cta.primaryCta.label", + "docs.sidebar.heading", + "docs.article.title", + "docs.toc.heading", + "dashboard.sidebar.heading", + "dashboard.stats.heading", + "dashboard.grid.heading" + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo/public/cms-fixtures/strapi-pages.json b/examples/demo/public/cms-fixtures/strapi-pages.json new file mode 100644 index 0000000..1fdc3a0 --- /dev/null +++ b/examples/demo/public/cms-fixtures/strapi-pages.json @@ -0,0 +1,149 @@ +{ + "data": [ + { + "id": 1, + "attributes": { + "slug": "stripe", + "content": { + "landing.hero.heading": "Stripe", + "landing.hero.eyebrow": "Financial infrastructure to grow your revenue.", + "landing.hero.description": "Payments and billing infrastructure for internet businesses", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Stripe", + "landing.cta.heading": "Build with Stripe", + "landing.cta.description": "Payments and billing infrastructure for internet businesses", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Stripe", + "marketing.hero.heading": "Stripe", + "marketing.hero.eyebrow": "Financial infrastructure to grow your revenue.", + "marketing.hero.description": "Payments and billing infrastructure for internet businesses", + "marketing.testimonials.heading": "What teams say about Stripe", + "marketing.pricing.heading": "Simple pricing for Stripe brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Stripe", + "docs.article.title": "Stripe brand guide", + "docs.toc.heading": "Stripe on this page", + "dashboard.sidebar.heading": "Stripe", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + } + }, + { + "id": 2, + "attributes": { + "slug": "vercel", + "content": { + "landing.hero.heading": "Vercel", + "landing.hero.eyebrow": "Build and deploy on the AI Cloud.", + "landing.hero.description": "Frontend cloud platform for deploying and scaling web applications", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Vercel", + "landing.cta.heading": "Build with Vercel", + "landing.cta.description": "Frontend cloud platform for deploying and scaling web applications", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Vercel", + "marketing.hero.heading": "Vercel", + "marketing.hero.eyebrow": "Build and deploy on the AI Cloud.", + "marketing.hero.description": "Frontend cloud platform for deploying and scaling web applications", + "marketing.testimonials.heading": "What teams say about Vercel", + "marketing.pricing.heading": "Simple pricing for Vercel brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Vercel", + "docs.article.title": "Vercel brand guide", + "docs.toc.heading": "Vercel on this page", + "dashboard.sidebar.heading": "Vercel", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + } + }, + { + "id": 3, + "attributes": { + "slug": "linear", + "content": { + "landing.hero.heading": "Linear", + "landing.hero.eyebrow": "The product development system for teams and agents.", + "landing.hero.description": "Issue tracking and project management for high-performance software teams", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Linear", + "landing.cta.heading": "Build with Linear", + "landing.cta.description": "Issue tracking and project management for high-performance software teams", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Linear", + "marketing.hero.heading": "Linear", + "marketing.hero.eyebrow": "The product development system for teams and agents.", + "marketing.hero.description": "Issue tracking and project management for high-performance software teams", + "marketing.testimonials.heading": "What teams say about Linear", + "marketing.pricing.heading": "Simple pricing for Linear brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Linear", + "docs.article.title": "Linear brand guide", + "docs.toc.heading": "Linear on this page", + "dashboard.sidebar.heading": "Linear", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + } + }, + { + "id": 4, + "attributes": { + "slug": "notion", + "content": { + "landing.hero.heading": "Notion", + "landing.hero.eyebrow": "The AI workspace that works for you.", + "landing.hero.description": "All-in-one workspace for notes, wikis, databases, and project management", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines Notion", + "landing.cta.heading": "Build with Notion", + "landing.cta.description": "All-in-one workspace for notes, wikis, databases, and project management", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 Notion", + "marketing.hero.heading": "Notion", + "marketing.hero.eyebrow": "The AI workspace that works for you.", + "marketing.hero.description": "All-in-one workspace for notes, wikis, databases, and project management", + "marketing.testimonials.heading": "What teams say about Notion", + "marketing.pricing.heading": "Simple pricing for Notion brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "Notion", + "docs.article.title": "Notion brand guide", + "docs.toc.heading": "Notion on this page", + "dashboard.sidebar.heading": "Notion", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + } + }, + { + "id": 5, + "attributes": { + "slug": "github", + "content": { + "landing.hero.heading": "GitHub", + "landing.hero.eyebrow": "The future of building happens together.", + "landing.hero.description": "Software development and collaboration platform for over 100 million developers", + "landing.hero.primaryCta.label": "Get started", + "landing.features.heading": "What defines GitHub", + "landing.cta.heading": "Build with GitHub", + "landing.cta.description": "Software development and collaboration platform for over 100 million developers", + "landing.cta.primaryCta.label": "Get started", + "landing.footer.copyright": "Copyright 2026 GitHub", + "marketing.hero.heading": "GitHub", + "marketing.hero.eyebrow": "The future of building happens together.", + "marketing.hero.description": "Software development and collaboration platform for over 100 million developers", + "marketing.testimonials.heading": "What teams say about GitHub", + "marketing.pricing.heading": "Simple pricing for GitHub brand ops", + "marketing.cta.primaryCta.label": "Get started", + "docs.sidebar.heading": "GitHub", + "docs.article.title": "GitHub brand guide", + "docs.toc.heading": "GitHub on this page", + "dashboard.sidebar.heading": "GitHub", + "dashboard.stats.heading": "Brand stats", + "dashboard.grid.heading": "Color palette" + } + } + } + ] +} \ No newline at end of file diff --git a/examples/demo/public/favicon.svg b/examples/demo/public/favicon.svg new file mode 100644 index 0000000..de02f3c --- /dev/null +++ b/examples/demo/public/favicon.svg @@ -0,0 +1,4 @@ + + + V + diff --git a/examples/demo/public/stacks/astro.html b/examples/demo/public/stacks/astro.html new file mode 100644 index 0000000..c934b81 --- /dev/null +++ b/examples/demo/public/stacks/astro.html @@ -0,0 +1 @@ +vBrand Astro preview

vBrand stack preview artefact

Generated by astro StackRuntime.bootstrapMarkup.

Shared island preview content
\ No newline at end of file diff --git a/examples/demo/public/stacks/next.html b/examples/demo/public/stacks/next.html new file mode 100644 index 0000000..58731d6 --- /dev/null +++ b/examples/demo/public/stacks/next.html @@ -0,0 +1 @@ +vBrand Next preview

vBrand stack preview artefact

Generated by next StackRuntime.bootstrapMarkup.

Shared island preview content
\ No newline at end of file diff --git a/examples/demo/public/stacks/vite.html b/examples/demo/public/stacks/vite.html new file mode 100644 index 0000000..e30af96 --- /dev/null +++ b/examples/demo/public/stacks/vite.html @@ -0,0 +1 @@ +vBrand Vite preview

vBrand stack preview artefact

Generated by vite StackRuntime.bootstrapMarkup.

Shared island preview content
\ No newline at end of file diff --git a/examples/demo/src/app.tsx b/examples/demo/src/app.tsx index a002317..9e3c607 100644 --- a/examples/demo/src/app.tsx +++ b/examples/demo/src/app.tsx @@ -21,6 +21,7 @@ export function App() { const base = viteBase(); const route = parseRoute(window.location.search, window.location.pathname, base); const brandLabel = brandParamToString(route.brandParams); + const fixtureSlug = route.brandParams.type === 'fixture' ? route.brandParams.handle : undefined; const [brand, setBrand] = useState(null); const [meta, setMeta] = useState(DEFAULT_META); @@ -67,6 +68,8 @@ export function App() { currentBrand={brandLabel} currentTemplate={route.templateId} currentMode={route.mode} + currentStack={route.stack} + currentCms={route.cms} isLoading={isLoading} dataViewHref={buildViewPath('data', base) + window.location.search + window.location.hash} onDataViewNavigate={() => { @@ -84,7 +87,7 @@ export function App() { {isLoading && } {!isLoading && error && } {!isLoading && !error && brand && activeTab === 'template' && ( - + )} {!isLoading && !error && brand && activeTab === 'data' && ( @@ -99,27 +102,47 @@ function TabBar({ activeTab, onTabChange }: { activeTab: ViewTab; onTabChange: ( { id: 'template', label: 'Template view' }, { id: 'data', label: 'Brand data' }, ]; + function setTabHover(el: HTMLButtonElement, active: boolean, selected: boolean) { + el.style.background = active ? 'rgba(99,102,241,0.06)' : 'transparent'; + if (!selected) el.style.color = active ? 'var(--color-primary, #6366f1)' : 'var(--color-neutral-500, #6b7280)'; + } return ( -
- {tabs.map((tab) => ( - - ))} +
+ {tabs.map((tab) => { + const selected = activeTab === tab.id; + return ( + + ); + })}
); } @@ -127,7 +150,7 @@ function TabBar({ activeTab, onTabChange }: { activeTab: ViewTab; onTabChange: ( function LoadingState({ label }: { label: string }) { return (
- {label} + {label} extracting brand signal
); @@ -146,15 +169,15 @@ function ErrorState({ error, brandLabel }: { error: string; brandLabel: string } const zodFormatted = formatZodError(error); return (
-
+
confidence: none
- {brandLabel} + {brandLabel} {zodFormatted ? (
-
{zodFormatted.summary}
+
{zodFormatted.summary}
    {zodFormatted.fields.map((f) => ( -
  • {f.path}: {f.message}
  • +
  • {f.path}: {f.message}
  • ))}
@@ -162,8 +185,8 @@ function ErrorState({ error, brandLabel }: { error: string; brandLabel: string }
{error}
)} {cors && ( -
-
cors limitation
+
+
cors limitation

Live URL extraction is browser-CORS-blocked on this hosted surface. Use the CLI locally:

@@ -173,7 +196,7 @@ function ErrorState({ error, brandLabel }: { error: string; brandLabel: string }
)} -
+
offline fixture: and json:<base64> load without network diff --git a/examples/demo/src/axis-ledger.tsx b/examples/demo/src/axis-ledger.tsx new file mode 100644 index 0000000..b00126a --- /dev/null +++ b/examples/demo/src/axis-ledger.tsx @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 bvasilenko +import React from 'react'; +import type { StackName, CmsName } from './router'; + +export interface AxisLedgerProps { + stack: StackName; + cms: CmsName; +} + +const LEDGER_AXES: ReadonlyArray<{ label: string; color: string; getValue: (p: AxisLedgerProps) => string }> = [ + { label: 'Stack', color: 'var(--color-primary, #6366f1)', getValue: (p) => p.stack }, + { label: 'CMS', color: '#22c55e', getValue: (p) => p.cms }, + { label: 'Deploy', color: '#eab308', getValue: () => 'gh-pages' }, +]; + +const LEDGER_SURFACE_STYLE: React.CSSProperties = { + border: '1px solid var(--color-neutral-200, #e5e7eb)', + borderLeft: '4px solid var(--color-primary, #6366f1)', + borderRadius: '4px', + padding: '12px', + background: 'var(--color-neutral-50, #f9fafb)', + fontFamily: 'system-ui, sans-serif', +}; + +export function AxisLedger({ stack, cms }: AxisLedgerProps) { + return ( +
+
+
+ axis ledger +
+ 7/9 flexed +
+
+ {LEDGER_AXES.map(({ label, color, getValue }) => ( + + {label} + {getValue({ stack, cms })} + + ))} +
+
+ ); +} diff --git a/examples/demo/src/cms-toggle.tsx b/examples/demo/src/cms-toggle.tsx new file mode 100644 index 0000000..44c69f9 --- /dev/null +++ b/examples/demo/src/cms-toggle.tsx @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 bvasilenko +import React from 'react'; +import { CMS_SUBSTRATE_REGISTRY } from '@booga/vbrand/cms'; +import type { CmsName } from './router'; + +interface CmsToggleProps { + cms: CmsName; + onChange: (cms: CmsName) => void; +} + +const CMS_COPY: Record = { + 'vbrand-standalone': { + label: 'vBrand', + source: 'canonical vBrand schema', + contract: 'schema as content', + }, + payload: { + label: 'Payload', + source: 'fixture-mocked Payload pages response', + contract: 'collections normalizer', + }, + sanity: { + label: 'Sanity', + source: 'fixture-mocked Sanity GROQ response', + contract: 'GROQ normalizer', + }, + strapi: { + label: 'Strapi', + source: 'fixture-mocked Strapi populated pages response', + contract: 'content-type normalizer', + }, +}; + +const CMS_ACCENT = '#22c55e'; +const CMS_HOVER_BG = 'rgba(34,197,94,0.06)'; + +const panelStyle: React.CSSProperties = { + border: '1px solid var(--color-neutral-200, #e5e7eb)', + borderLeft: `4px solid ${CMS_ACCENT}`, + borderRadius: '4px', + padding: '12px', + background: 'var(--color-neutral-50, #f9fafb)', + fontFamily: 'system-ui, sans-serif', +}; + +const eyebrowStyle: React.CSSProperties = { + margin: '0 0 8px', + fontSize: '0.6875rem', + fontWeight: 700, + textTransform: 'uppercase', + letterSpacing: '0.08em', + color: 'var(--color-neutral-400, #9ca3af)', +}; + +function focusRing(e: React.FocusEvent) { + e.currentTarget.style.outline = `2px solid ${CMS_ACCENT}`; + e.currentTarget.style.outlineOffset = '1px'; +} + +function clearFocusRing(e: React.FocusEvent) { + e.currentTarget.style.outline = ''; + e.currentTarget.style.outlineOffset = ''; +} + +function cmsButtonStyle(active: boolean): React.CSSProperties { + return { + flex: '1 1 110px', + minWidth: '110px', + padding: '8px', + border: active ? `1px solid ${CMS_ACCENT}` : '1px solid var(--color-neutral-200, #e5e7eb)', + borderLeft: active ? `4px solid ${CMS_ACCENT}` : '4px solid transparent', + borderRadius: '4px', + background: active ? '#f0fdf4' : 'transparent', + color: active ? CMS_ACCENT : 'var(--color-neutral-700, #374151)', + cursor: 'pointer', + textAlign: 'left', + transition: 'background 0.12s ease, border-color 0.12s ease, color 0.12s ease', + }; +} + +export function CmsToggle({ cms, onChange }: CmsToggleProps) { + return ( + + ); +} diff --git a/examples/demo/src/content-editor.tsx b/examples/demo/src/content-editor.tsx index 815b8cf..831bc68 100644 --- a/examples/demo/src/content-editor.tsx +++ b/examples/demo/src/content-editor.tsx @@ -42,6 +42,7 @@ export function ContentEditor({ return (