feat(branding): per-app banner + per-brand default theme via brand.json #104

Merged
padreug merged 2 commits from feat/brand-banner-and-default-theme into dev 2026-06-15 20:15:49 +00:00
Owner

Two independent brand-kit extensions (one commit each), both white-label-only — the default/ brand is unchanged, so existing deployments render identically.

1. Optional per-app banner (8f2c401)

A brand may ship a wide banner (logo + wordmark in one image) that replaces the logo + app-name pair in a standalone's header. Events is the first consumer.

  • resolveAppBanner(app?) mirrors the existing @brand-app-logo chain — per-standalone override (branding/<dep>/icons/<app>/banner.{svg,png}) → brand-global (branding/<dep>/banner.{svg,png}) — but is optional: returns null instead of throwing, so brands without a banner keep logo + name.
  • brandAppBannerAliasEntry() always registers @brand-app-banner (falling back to the logo) so the static import resolves; rendering is gated by the VITE_APP_BANNER build flag.
  • EventsPage.vue renders the banner when the flag is set, else logo + name.

2. Per-brand default theme + palette (d4d088f)

Lets a deployer set the in-app color scheme a fresh visitor sees (e.g. cfaun → darkmatter light) without forking. Two optional brand.json fields — theme (light|dark|system) and palette (one of PALETTES) — distinct from themeColor (PWA chrome only).

  • vite-branding.ts surfaces them as VITE_BRAND_THEME / VITE_BRAND_PALETTE at module load, so the default applies app-wide (hub + every standalone) with no per-config wiring.
  • The theme-provider reads them as the initial value; a user's stored localStorage choice still wins and persists.
  • Splits the catppuccin = bare-:root invariant (now BASE_PALETTE) from the configurable default — without this a non-catppuccin brand default would strip the data-theme attribute and silently render catppuccin.

Notes

  • brand.json schema, banner resolution chain, and an outline-SVG-text-to-paths caveat (browsers lack designer fonts) are documented in branding/README.md.
  • Verified locally against a throwaway branding/oyez/ kit: events standalone renders the per-app "Oyez!" banner and loads darkmatter-light by default.

🤖 Generated with Claude Code

Two independent brand-kit extensions (one commit each), both white-label-only — the `default/` brand is unchanged, so existing deployments render identically. ## 1. Optional per-app banner (`8f2c401`) A brand may ship a wide banner (logo + wordmark in one image) that replaces the logo + app-name pair in a standalone's header. **Events** is the first consumer. - `resolveAppBanner(app?)` mirrors the existing `@brand-app-logo` chain — per-standalone override (`branding/<dep>/icons/<app>/banner.{svg,png}`) → brand-global (`branding/<dep>/banner.{svg,png}`) — but is **optional**: returns `null` instead of throwing, so brands without a banner keep logo + name. - `brandAppBannerAliasEntry()` always registers `@brand-app-banner` (falling back to the logo) so the static import resolves; rendering is gated by the `VITE_APP_BANNER` build flag. - `EventsPage.vue` renders the banner when the flag is set, else logo + name. ## 2. Per-brand default theme + palette (`d4d088f`) Lets a deployer set the in-app color scheme a fresh visitor sees (e.g. cfaun → darkmatter light) without forking. Two optional `brand.json` fields — `theme` (`light|dark|system`) and `palette` (one of `PALETTES`) — distinct from `themeColor` (PWA chrome only). - `vite-branding.ts` surfaces them as `VITE_BRAND_THEME` / `VITE_BRAND_PALETTE` at module load, so the default applies app-wide (hub + every standalone) with no per-config wiring. - The theme-provider reads them as the **initial** value; a user's stored `localStorage` choice still wins and persists. - Splits the `catppuccin` = bare-`:root` invariant (now `BASE_PALETTE`) from the configurable default — without this a non-catppuccin brand default would strip the `data-theme` attribute and silently render catppuccin. ## Notes - `brand.json` schema, banner resolution chain, and an **outline-SVG-text-to-paths** caveat (browsers lack designer fonts) are documented in `branding/README.md`. - Verified locally against a throwaway `branding/oyez/` kit: events standalone renders the per-app "Oyez!" banner and loads darkmatter-light by default. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A brand may ship a wide banner (logo + wordmark in one image) that
replaces the brand-kit logo + app-name pair in a standalone's header.
Events is the first consumer.

Banners are optional and resolve at build time, mirroring the existing
@brand-app-logo chain:

- resolveAppBanner(app?) checks per-standalone override first
  (branding/<dep>/icons/<app>/banner.{svg,png}) then the brand's primary
  banner (branding/<dep>/banner.{svg,png}); returns null when absent
  instead of throwing, so brands without a banner keep logo + name.
- brandAppBannerAliasEntry() always registers the @brand-app-banner
  alias (falling back to the logo) so the static import resolves; whether
  it renders is gated by the VITE_APP_BANNER build flag.
- EventsPage renders the banner when the flag is set, else logo + name.

Deployers override per-standalone without touching the component. SVG
banners must have their text outlined to paths (browsers lack designer
fonts) — documented in branding/README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lets a deployer set the in-app color scheme a fresh visitor sees (e.g.
cfaun → darkmatter light) without forking. Two optional brand.json
fields, `theme` (light|dark|system) and `palette` (one of PALETTES),
distinct from `themeColor` which is PWA chrome only.

- vite-branding.ts surfaces them as VITE_BRAND_THEME / VITE_BRAND_PALETTE
  at module load, so the default applies app-wide (hub + all standalones)
  with no per-config wiring.
- theme-provider reads them as the INITIAL value of theme/palette; a
  user's stored choice in localStorage still wins and persists.
- Splits the catppuccin = bare `:root` invariant (now BASE_PALETTE, used
  by applyPalette to drop data-theme) from the configurable default.
  Without this, a non-catppuccin brand default would strip the
  data-theme attribute and silently render catppuccin instead.

Unset → the app's built-ins (dark + catppuccin), unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
padreug deleted branch feat/brand-banner-and-default-theme 2026-06-15 20:15:50 +00:00
Sign in to join this conversation.
No description provided.