docs(branding): brand kit contract + CLAUDE.md section

branding/README.md is the deployer contract:
- Directory layout, source format constraints (SVG > PNG ≥ 1024),
  brand.json schema, per-standalone override resolution order
- BRAND_DIR / BRAND_APP usage, generator pipeline walkthrough
- Pointer to issue #95 + the NixOS Phase 2 integration

webapp CLAUDE.md gains a Brand Kit section describing the moving
parts (vite-branding.ts, @brand alias, brandAssetsPlugin,
public/icons/ gitignore, per-app override path) so future sessions
on this repo know the convention without grepping.

Adds BRAND_DIR / BRAND_APP to the Environment Variables example.

Workspace ~/dev/CLAUDE.md note about "brand changes don't need
flake.lock bump" deferred to Phase 2 (server-deploy migration) —
that's when the workflow becomes reality.

Part of aiolabs/webapp#95.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-10 00:02:23 +02:00
commit 3dfed23b43
2 changed files with 162 additions and 0 deletions

110
branding/README.md Normal file
View file

@ -0,0 +1,110 @@
# Brand kit
This directory holds the **white-label brand kit** that drives the PWA's icons, manifest name/colors, and in-app `<img>` logo across the hub and every standalone (events, wallet, chat, market, …).
The committed `default/` is aiolabs's brand and is what unparameterized builds use. Downstream deployers add a sibling directory (e.g. `branding/cfaun/`) and point `BRAND_DIR` at it to ship a fully rebranded build with no fork required.
## Directory layout
```
branding/
README.md
default/ # aiolabs default, committed
logo.svg # preferred source (sharp at every size)
logo.png # fallback source (≥ 1024×1024 if PNG-only)
brand.json # { name, shortName?, themeColor?, backgroundColor? }
icons/ # optional per-standalone overrides
events/logo.svg
wallet/logo.png
cfaun/ # downstream deployer's brand (gitignored or in deploy repo)
logo.svg
brand.json
```
aiolabs's `default/` currently ships PNG-only (1024×1024). Replace with `logo.svg` when a vector source becomes available — produces sharper icons at every size and unlocks `favicon.svg`.
## Source formats
**SVG strongly preferred:**
- Crisp at every output size (192 / 512 maskable / 180 apple / 48 favicon)
- Enables sharp `favicon.svg` for modern browsers
- The in-app `@brand/logo` reference can be tinted via CSS (`currentColor`, filters)
**PNG accepted with constraints:**
- **≥ 1024×1024** — smaller sources produce blurry icons on high-DPI Android install screens
- **Square aspect ratio** — PWA icon canvas is square
- **Transparent background** — the generator applies maskable/apple background colors itself
- PNG-source deployments lose the `favicon.svg` benefit and the recolorable in-app logo
When both `logo.svg` and `logo.png` are present, SVG wins.
## brand.json schema
```jsonc
{
"name": "AIO", // required — drives PWA manifest name
"shortName": "AIO", // optional — PWA home-screen label; defaults to `name`
"themeColor": "#1f2937", // optional — PWA chrome color override (otherwise each standalone keeps its accent)
"backgroundColor": "#fff" // optional — PWA splash background
}
```
`themeColor` and `backgroundColor` are *overrides*, not defaults. When unset, each standalone's own accent applies (wallet yellow `#eab308`, chat green `#16a34a`, …) — so the default brand kit preserves the per-app visual identity, and a deployer who wants unified chrome adds the override.
## Per-standalone overrides
Place a logo at `branding/<dep>/icons/<app>/logo.{svg,png}` to override the brand's primary logo for a single standalone build.
Resolution at build time:
1. `branding/<dep>/icons/<app>/logo.svg`
2. `branding/<dep>/icons/<app>/logo.png`
3. `branding/<dep>/logo.svg`
4. `branding/<dep>/logo.png`
5. Build fails with a clear error pointing here.
`<app>` is set via `BRAND_APP` env var (the standalone build script sets this; deployers don't touch it directly).
## How to use
**Building with the default brand:**
```bash
pnpm build # main shell
pnpm build:events # events standalone
# … one per standalone
```
**Building with a deployer's brand:**
```bash
BRAND_DIR=branding/cfaun pnpm build:events
```
`BRAND_DIR` accepts relative paths (resolved from the webapp repo root) or absolute paths (used by the NixOS builder, which mounts the brand directory into the sandbox at a `/nix/store/...-branding` path).
**Regenerating icons explicitly:**
The Vite plugin auto-runs the generator on every build/dev start. To run it standalone:
```bash
pnpm generate-pwa-assets
```
Outputs land in `public/icons/` (gitignored).
## Build pipeline
1. `BRAND_DIR` is resolved (defaults to `./branding/default`).
2. `vite-branding.ts` reads `brand.json` and exposes `@brand/<file>` alias.
3. `brandAssetsPlugin()` (registered in every `vite.*.config.ts`) runs `scripts/generate-pwa-assets.mjs` once per build via `buildStart`.
4. The script stages the source logo into `public/icons/.brand-source.{svg,png}`, runs `pwa-assets-generator`, then deletes the staged source.
5. Vite copies `public/icons/` into `dist/icons/`. Manifest references `icons/<name>.png`. HTML `<link>` tags reference `/icons/<name>.{ico,png}`.
## Integration with NixOS deployment
See [aiolabs/webapp#95](https://git.atitlan.io/aiolabs/webapp/issues/95) for the full architecture. In short: `deploy/server-deploy/hosts/<host>/branding/` will hold per-host brands, and each host's `services/webapp.nix` will call `inputs.webapp.lib.mkWebapp { brandDir = ./../branding; }` once `lib.mkWebapp` is exposed from `flake.nix` (separate follow-up issue).
The architectural payoff: brand and code become independent axes. Logo changes ship via server-deploy commits + redeploys — no webapp release, no `flake.lock` bump.