feat(branding): optional per-app banner replacing logo + name in header
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>
This commit is contained in:
parent
138f9905bf
commit
8f2c401e00
5 changed files with 119 additions and 7 deletions
|
|
@ -92,6 +92,46 @@ export function brandAppLogoAliasEntry(app?: string) {
|
|||
} as const
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional brand banner — a wide lockup (logo + wordmark in one image)
|
||||
* that replaces the logo + app-name pair in a standalone's header.
|
||||
*
|
||||
* Resolution mirrors {@link resolveAppLogo} (per-standalone override
|
||||
* first, then the brand's primary banner), but a banner is OPTIONAL:
|
||||
* returns `null` when none is found instead of throwing. Brands that
|
||||
* don't ship a banner keep the default logo + name rendering.
|
||||
*/
|
||||
export function resolveAppBanner(app?: string): string | null {
|
||||
const candidates: string[] = []
|
||||
if (app) {
|
||||
candidates.push(
|
||||
join(BRAND_DIR, 'icons', app, 'banner.svg'),
|
||||
join(BRAND_DIR, 'icons', app, 'banner.png'),
|
||||
)
|
||||
}
|
||||
candidates.push(
|
||||
join(BRAND_DIR, 'banner.svg'),
|
||||
join(BRAND_DIR, 'banner.png'),
|
||||
)
|
||||
return candidates.find((p) => existsSync(p)) ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Standalone-aware brand-banner alias entry, the banner sibling of
|
||||
* {@link brandAppLogoAliasEntry}. Always registers the
|
||||
* `@brand-app-banner` alias so the static `import '@brand-app-banner?url'`
|
||||
* in the component resolves cleanly — when the active brand has no
|
||||
* banner it falls back to the resolved logo, which the component never
|
||||
* renders (it gates on the `VITE_APP_BANNER` flag instead).
|
||||
*/
|
||||
export function brandAppBannerAliasEntry(app?: string) {
|
||||
const resolved = resolveAppBanner(app) ?? resolveAppLogo(app)
|
||||
return {
|
||||
find: /^@brand-app-banner(\?.*)?$/,
|
||||
replacement: `${resolved}$1`,
|
||||
} as const
|
||||
}
|
||||
|
||||
/**
|
||||
* PWA manifest name for a standalone. Combines the brand name with the
|
||||
* app's own label, or returns the bare brand when no label is given.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue