From a74b273a5797dcd25e1ee696683caedbe297aaa6 Mon Sep 17 00:00:00 2001 From: Boris Vasilenko Date: Thu, 2 Jul 2026 22:38:01 +0300 Subject: [PATCH 1/2] alpha.5: add stack + CMS + deploy axis modules, wire demo drawers, add Playwright axis-reachability probes and verdict gate --- .github/workflows/ci.yml | 51 ++- .gitignore | 1 + .interface-design/system.md | 3 +- examples/demo/index.html | 5 +- examples/demo/package.json | 2 +- examples/demo/playwright.config.ts | 1 + examples/demo/postcss.config.js | 23 +- .../cms-fixtures/payload-collections.json | 30 ++ .../public/cms-fixtures/payload-pages.json | 134 ++++++ .../public/cms-fixtures/sanity-pages.json | 149 ++++++ .../public/cms-fixtures/sanity-schema.json | 30 ++ .../cms-fixtures/strapi-content-types.json | 30 ++ .../public/cms-fixtures/strapi-pages.json | 149 ++++++ examples/demo/public/favicon.svg | 4 + examples/demo/public/stacks/astro.html | 1 + examples/demo/public/stacks/next.html | 1 + examples/demo/public/stacks/vite.html | 1 + examples/demo/src/app.tsx | 83 ++-- examples/demo/src/axis-ledger.tsx | 55 +++ examples/demo/src/cms-toggle.tsx | 125 +++++ examples/demo/src/content-editor.tsx | 1 + examples/demo/src/content-layers.ts | 11 + examples/demo/src/demo-layout-styles.ts | 69 +++ examples/demo/src/deploy-info.tsx | 63 +++ examples/demo/src/global.d.ts | 13 + examples/demo/src/main.jsx | 2 +- examples/demo/src/nav-bar.tsx | 195 ++++++-- examples/demo/src/park-notice.tsx | 113 +++-- examples/demo/src/render-area.tsx | 222 +++++++++ examples/demo/src/router.ts | 24 +- examples/demo/src/stack-toggle.tsx | 152 +++++++ examples/demo/src/template-view.tsx | 234 +++------- examples/demo/src/use-breakpoint.ts | 38 ++ examples/demo/src/use-cms-content.ts | 29 ++ examples/demo/src/use-composition.ts | 59 +++ examples/demo/tests/axis-ledger.test.tsx | 140 ++++++ examples/demo/tests/cms-toggle.test.tsx | 161 +++++++ examples/demo/tests/content-layers.test.ts | 200 ++++++++ .../demo/tests/demo-layout-styles.test.ts | 218 +++++++++ examples/demo/tests/deploy-info.test.tsx | 98 ++++ examples/demo/tests/nav-bar.test.tsx | 429 +++++++++++++++++- examples/demo/tests/park-notice.test.tsx | 169 ++----- examples/demo/tests/render-area.test.ts | 91 ++++ examples/demo/tests/router.test.ts | 241 +++++++++- .../runtime-probe/axis-reachability.test.ts | 81 ++++ .../demo/tests/runtime-probe/cms-axis.test.ts | 40 ++ .../demo-surface-version.test.ts | 43 ++ .../runtime-probe/density-toggle.test.ts | 5 +- .../tests/runtime-probe/deploy-axis.test.ts | 41 ++ .../tests/runtime-probe/github-color.test.ts | 4 +- .../tests/runtime-probe/stack-axis.test.ts | 129 ++++++ .../demo/tests/runtime-probe/verdict-logic.ts | 27 +- .../tests/runtime-probe/verdict-reporter.ts | 18 +- examples/demo/tests/stack-toggle.test.tsx | 142 ++++++ examples/demo/tests/use-breakpoint.test.ts | 279 ++++++++++++ examples/demo/tests/use-cms-content.test.tsx | 352 ++++++++++++++ examples/demo/tests/use-composition.test.ts | 372 +++++++++++++++ examples/demo/tests/verdict-logic.test.ts | 210 ++++++++- examples/demo/tests/verdict-reporter.test.ts | 262 +++++++++++ .../demo/tests/vite-html-transform.test.js | 309 +++++++++++++ examples/demo/vite-html-transform.js | 14 + examples/demo/vite.config.js | 19 +- package.json | 22 +- scripts/build-stack-artefacts.mjs | 29 ++ scripts/bundle-require-output-redirect.cjs | 30 ++ scripts/bundle-require-redirect-patterns.cjs | 40 ++ scripts/eye-test-screenshot-grid.mjs | 234 +--------- scripts/eye-test/axes.mjs | 8 + scripts/eye-test/cell-generator.mjs | 34 ++ scripts/eye-test/cell-generator.test.mjs | 144 ++++++ scripts/eye-test/cli.mjs | 17 + scripts/eye-test/diff-reporter.mjs | 47 ++ scripts/eye-test/grid-image.mjs | 171 +++++++ scripts/eye-test/page-capture.mjs | 97 ++++ scripts/eye-test/pipeline.mjs | 62 +++ scripts/eye-test/stack-snippets.mjs | 58 +++ scripts/eye-test/url-builder.mjs | 15 + scripts/eye-test/url-builder.test.mjs | 157 +++++++ scripts/generate-cms-fixtures.mjs | 24 + scripts/probe-verdict-gate.mjs | 75 +++ scripts/run-tsup.cjs | 9 + src/cms/content-tree.ts | 59 +++ src/cms/contract.test.ts | 380 ++++++++++++++++ src/cms/index.ts | 38 ++ src/cms/payload.ts | 32 ++ src/cms/sanity.ts | 32 ++ src/cms/strapi.ts | 32 ++ src/cms/types.ts | 18 + src/cms/vbrand-standalone.ts | 10 + src/deploy/bundle.ts | 66 +++ src/deploy/cloudflare-pages.ts | 5 + src/deploy/contract.test.ts | 373 +++++++++++++++ src/deploy/contract.ts | 32 ++ src/deploy/coolify.ts | 5 + src/deploy/custom-vps.ts | 5 + src/deploy/deferred.ts | 30 ++ src/deploy/fly.ts | 5 + src/deploy/gh-pages.ts | 59 +++ src/deploy/index.ts | 42 ++ src/deploy/metadata.ts | 42 ++ src/deploy/netlify.ts | 5 + src/deploy/types.ts | 13 + src/deploy/vercel.ts | 5 + src/index.ts | 36 ++ src/stacks/astro.ts | 21 + src/stacks/contract.test.ts | 258 +++++++++++ src/stacks/index.ts | 33 ++ src/stacks/mode-affinity.test.ts | 111 +++++ src/stacks/mode-affinity.ts | 23 + src/stacks/next.ts | 18 + src/stacks/preview-html.ts | 47 ++ src/stacks/types.ts | 13 + src/stacks/vite.ts | 17 + tests/repo-hygiene.test.ts | 170 +++++++ .../bundle-require-output-redirect.test.ts | 328 +++++++++++++ tests/scripts/eye-test/axes.test.ts | 97 ++++ tests/scripts/eye-test/cell-generator.test.ts | 118 +++++ tests/scripts/eye-test/cli.test.ts | 162 +++++++ tests/scripts/eye-test/diff-reporter.test.ts | 326 +++++++++++++ tests/scripts/eye-test/stack-snippets.test.ts | 237 ++++++++++ tests/scripts/eye-test/url-builder.test.ts | 158 +++++++ tests/scripts/probe-verdict-gate.test.ts | 257 +++++++++++ tsup.config.ts => tsup.config.mjs | 42 ++ vitest.config.ts | 8 + 124 files changed, 10502 insertions(+), 736 deletions(-) create mode 100644 examples/demo/public/cms-fixtures/payload-collections.json create mode 100644 examples/demo/public/cms-fixtures/payload-pages.json create mode 100644 examples/demo/public/cms-fixtures/sanity-pages.json create mode 100644 examples/demo/public/cms-fixtures/sanity-schema.json create mode 100644 examples/demo/public/cms-fixtures/strapi-content-types.json create mode 100644 examples/demo/public/cms-fixtures/strapi-pages.json create mode 100644 examples/demo/public/favicon.svg create mode 100644 examples/demo/public/stacks/astro.html create mode 100644 examples/demo/public/stacks/next.html create mode 100644 examples/demo/public/stacks/vite.html create mode 100644 examples/demo/src/axis-ledger.tsx create mode 100644 examples/demo/src/cms-toggle.tsx create mode 100644 examples/demo/src/content-layers.ts create mode 100644 examples/demo/src/demo-layout-styles.ts create mode 100644 examples/demo/src/deploy-info.tsx create mode 100644 examples/demo/src/global.d.ts create mode 100644 examples/demo/src/render-area.tsx create mode 100644 examples/demo/src/stack-toggle.tsx create mode 100644 examples/demo/src/use-breakpoint.ts create mode 100644 examples/demo/src/use-cms-content.ts create mode 100644 examples/demo/src/use-composition.ts create mode 100644 examples/demo/tests/axis-ledger.test.tsx create mode 100644 examples/demo/tests/cms-toggle.test.tsx create mode 100644 examples/demo/tests/content-layers.test.ts create mode 100644 examples/demo/tests/demo-layout-styles.test.ts create mode 100644 examples/demo/tests/deploy-info.test.tsx create mode 100644 examples/demo/tests/render-area.test.ts create mode 100644 examples/demo/tests/runtime-probe/axis-reachability.test.ts create mode 100644 examples/demo/tests/runtime-probe/cms-axis.test.ts create mode 100644 examples/demo/tests/runtime-probe/demo-surface-version.test.ts create mode 100644 examples/demo/tests/runtime-probe/deploy-axis.test.ts create mode 100644 examples/demo/tests/runtime-probe/stack-axis.test.ts create mode 100644 examples/demo/tests/stack-toggle.test.tsx create mode 100644 examples/demo/tests/use-breakpoint.test.ts create mode 100644 examples/demo/tests/use-cms-content.test.tsx create mode 100644 examples/demo/tests/use-composition.test.ts create mode 100644 examples/demo/tests/verdict-reporter.test.ts create mode 100644 examples/demo/tests/vite-html-transform.test.js create mode 100644 examples/demo/vite-html-transform.js create mode 100644 scripts/build-stack-artefacts.mjs create mode 100644 scripts/bundle-require-output-redirect.cjs create mode 100644 scripts/bundle-require-redirect-patterns.cjs create mode 100644 scripts/eye-test/axes.mjs create mode 100644 scripts/eye-test/cell-generator.mjs create mode 100644 scripts/eye-test/cell-generator.test.mjs create mode 100644 scripts/eye-test/cli.mjs create mode 100644 scripts/eye-test/diff-reporter.mjs create mode 100644 scripts/eye-test/grid-image.mjs create mode 100644 scripts/eye-test/page-capture.mjs create mode 100644 scripts/eye-test/pipeline.mjs create mode 100644 scripts/eye-test/stack-snippets.mjs create mode 100644 scripts/eye-test/url-builder.mjs create mode 100644 scripts/eye-test/url-builder.test.mjs create mode 100644 scripts/generate-cms-fixtures.mjs create mode 100644 scripts/probe-verdict-gate.mjs create mode 100644 scripts/run-tsup.cjs create mode 100644 src/cms/content-tree.ts create mode 100644 src/cms/contract.test.ts create mode 100644 src/cms/index.ts create mode 100644 src/cms/payload.ts create mode 100644 src/cms/sanity.ts create mode 100644 src/cms/strapi.ts create mode 100644 src/cms/types.ts create mode 100644 src/cms/vbrand-standalone.ts create mode 100644 src/deploy/bundle.ts create mode 100644 src/deploy/cloudflare-pages.ts create mode 100644 src/deploy/contract.test.ts create mode 100644 src/deploy/contract.ts create mode 100644 src/deploy/coolify.ts create mode 100644 src/deploy/custom-vps.ts create mode 100644 src/deploy/deferred.ts create mode 100644 src/deploy/fly.ts create mode 100644 src/deploy/gh-pages.ts create mode 100644 src/deploy/index.ts create mode 100644 src/deploy/metadata.ts create mode 100644 src/deploy/netlify.ts create mode 100644 src/deploy/types.ts create mode 100644 src/deploy/vercel.ts create mode 100644 src/stacks/astro.ts create mode 100644 src/stacks/contract.test.ts create mode 100644 src/stacks/index.ts create mode 100644 src/stacks/mode-affinity.test.ts create mode 100644 src/stacks/mode-affinity.ts create mode 100644 src/stacks/next.ts create mode 100644 src/stacks/preview-html.ts create mode 100644 src/stacks/types.ts create mode 100644 src/stacks/vite.ts create mode 100644 tests/scripts/bundle-require-output-redirect.test.ts create mode 100644 tests/scripts/eye-test/axes.test.ts create mode 100644 tests/scripts/eye-test/cell-generator.test.ts create mode 100644 tests/scripts/eye-test/cli.test.ts create mode 100644 tests/scripts/eye-test/diff-reporter.test.ts create mode 100644 tests/scripts/eye-test/stack-snippets.test.ts create mode 100644 tests/scripts/eye-test/url-builder.test.ts create mode 100644 tests/scripts/probe-verdict-gate.test.ts rename tsup.config.ts => tsup.config.mjs (69%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc8f2a5..4d3bd06 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 --with-deps 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 (