mirror of
https://github.com/hanzoai/brand.git
synced 2026-10-23 03:05:28 +00:00
title() answers a brand's title or a Hanzo product's own name: PRODUCTS
holds the one product that has one, team → "Hanzo Team" (hanzo.team),
so @hanzo/app's app({ brand }) reads a name from here instead of keeping
its own brand() and wordmark(). An id that names neither throws.
The package publishes from CI now. Its gate could not pass on a fresh
checkout — pnpm gen read @hanzo/tokens from a sibling ../ui checkout, and
the declaration build died on TypeScript 6's baseUrl deprecation — so it
went out from a laptop. @hanzo/tokens is a dev dependency, tsconfig states
its types and the deprecation, and publish.yml reads the npm token from
KMS. styles/variables.css is regenerated from @hanzo/tokens 1.1.1, which
is what every build already wrote.
143 lines
7.2 KiB
Markdown
143 lines
7.2 KiB
Markdown
# @hanzo/brand
|
|
|
|
Official brand assets, design system, AND the fleet white-label registry for
|
|
Hanzo. Published as `@hanzo/brand` on npm. Exports the multi-brand registry +
|
|
host resolver (hanzo / lux / zoo / pars + white-label tenants), colors,
|
|
typography, themes, design tokens, logo components, and CSS variables.
|
|
|
|
## How this ships
|
|
|
|
One way, and it runs on our own stack:
|
|
|
|
push -> github.com/hanzoai/brand (a mirror)
|
|
-> git.hanzo.ai/hanzoai/brand CANONICAL
|
|
.hanzo/workflows/sync-from-github.yml pulls the mirror, every 10m
|
|
.hanzo/workflows/publish.yml publishes @hanzo/brand to npm
|
|
.hanzo/workflows/deploy.yml builds ghcr.io/hanzoai/brand
|
|
-> hanzoai/universe crs/brand.yaml names the tag that is live
|
|
-> hanzoai/operator reconciles the App
|
|
-> hanzoai/static behind hanzoai/ingress serves brand.hanzo.ai
|
|
|
|
**git.hanzo.ai is canonical; GitHub is a mirror.** `.github/workflows/` is empty.
|
|
Every build, check, publish and the sync itself lives under `.hanzo/workflows/`,
|
|
which the forge reads. It uses GitHub Actions syntax, so a workflow moves between
|
|
the two by changing directory and nothing else.
|
|
|
|
The sync is a PULL: the forge runner reaches both ends, so nothing needs a
|
|
credential aimed into the forge, and a public repo needs none at all. It
|
|
fast-forwards only — a divergence fails loudly instead of force-pushing a side.
|
|
|
|
`publish.yml` is the SOLE publisher of `@hanzo/brand`. It fires when `version` in
|
|
`package.json` changes on `main`, skips a version already on the registry, and
|
|
runs `pnpm test` first — one command that builds `dist/` and then gates on the
|
|
suite. `dist/` is gitignored, so publishing without it ships a package whose every
|
|
subpath 404s. Needs `NPM_TOKEN` as a forge secret.
|
|
|
|
### The site
|
|
|
|
`index.html` is the whole site: one self-contained page, inline CSS, no scripts
|
|
and no subresources. `Dockerfile` copies it into `ghcr.io/hanzoai/static` — no
|
|
build stage, because there is nothing to build. No GitHub Pages and no Cloudflare
|
|
Pages: the repo used to push this page to a `gh-pages` branch, Pages never built
|
|
it, and `brand.hanzo.ai` has answered 404 from our own ingress ever since.
|
|
|
|
A build never deploys itself. `deploy.yml` publishes
|
|
`ghcr.io/hanzoai/brand:<sha>`; a human then sets `spec.image.tag` in
|
|
`hanzoai/universe` `infra/k8s/operator/crs/brand.yaml` and adds `- brand.yaml` to
|
|
that directory's `kustomization.yaml`. The CR is inert until both are done, which
|
|
is deliberate: an App promoted with an empty tag takes the host down instead of
|
|
leaving it alone.
|
|
|
|
Order: publish an image -> set the tag -> add the line -> confirm the pod is
|
|
Running.
|
|
|
|
## Stack
|
|
- TypeScript 5, tsup (CJS + ESM + DTS output)
|
|
- Runtime dep: `@hanzo/logo` (the ONE home of the Hanzo block-H geometry —
|
|
consumed via `MARK_PATHS` / `MARK_VIEWBOX`; never re-typed here)
|
|
- React is an OPTIONAL peer (only the logo components need it)
|
|
- Outputs: `dist/index.{js,mjs,d.ts}`, plus `./loader`, `./hero`
|
|
|
|
## White-label registry (the multi-brand layer)
|
|
ONE home for "who is this brand?" across every surface. A `lux.network` /
|
|
`zoo.ngo` / `pars.network` host resolves to ITS brand + mark — NEVER Hanzo's.
|
|
That non-cross-contamination is the white-label invariant (test/registry.test.mjs).
|
|
|
|
```
|
|
src/brand-id.ts # BrandId + HOST_BRANDS suffix table + brandFromHost(host)
|
|
# the pure resolver (no React, no data). Exact suffix /
|
|
# dot-boundary match — lookalike hosts can't impersonate.
|
|
src/marks.ts # BRAND_MARKS: per-brand inline mark geometry. Hanzo mark =
|
|
# @hanzo/logo MARK_PATHS. renderMarkSVG(id) string helper.
|
|
src/registry.ts # BrandIdentity + BRANDS registry + getBrand(host) +
|
|
# toBrandContract() (projects to @hanzo/id's BrandContract).
|
|
```
|
|
|
|
Key API:
|
|
```ts
|
|
import { getBrand, brandFromHost, BRANDS, BrandLogo, renderMarkSVG, toBrandContract } from '@hanzo/brand'
|
|
|
|
getBrand('lux.network') // -> BrandIdentity { id:'lux', name:'Lux', mark, ... }
|
|
brandFromHost('zoo.ngo') // -> 'zoo'
|
|
<BrandLogo /> // React: auto-resolves the mark by window.location.hostname
|
|
<BrandLogo brand="lux" /> // React: explicit brand mark
|
|
renderMarkSVG('pars') // framework-agnostic inline SVG string
|
|
toBrandContract(BRANDS.zoo) // -> the @hanzo/id runtime BrandContract shape
|
|
```
|
|
|
|
## What a wordmark reads: `title`
|
|
`title(id)` (`@hanzo/brand/registry`, also the root) is the one answer to "what
|
|
does this host's wordmark say": a brand's `title` ("Hanzo AI", "Lux Network",
|
|
"Zoo"), or a Hanzo product's that carries a name of its own (`PRODUCTS`:
|
|
`team` → "Hanzo Team", hanzo.team). `@hanzo/app`'s `app({ brand })` reads it.
|
|
A product names the brand it belongs to and never repeats a brand's title; an
|
|
id that names neither throws, so a host's typo never prints another brand.
|
|
|
|
## Release
|
|
`.github/workflows/publish.yml` publishes when `package.json` is ahead of npm,
|
|
with `NPM_TOKEN` read from KMS through `KMS_CLIENT_ID`/`KMS_CLIENT_SECRET`. The
|
|
gate is the release: `pnpm install --frozen-lockfile`, `pnpm type-check`,
|
|
`pnpm test` (which builds and regenerates `styles/variables.css` from
|
|
`@hanzo/tokens`, a dev dependency). No laptop publishes.
|
|
|
|
## Structure
|
|
```
|
|
src/
|
|
index.ts # Main entry - re-exports all modules, auto-applies theme in browser
|
|
brand-id.ts # BrandId, HOST_BRANDS, brandFromHost, isKnownBrandHost, normalizeHost
|
|
marks.ts # BRAND_MARKS (hanzo/lux/zoo/pars marks), renderMarkSVG
|
|
registry.ts # BrandIdentity, BRANDS, getBrand, toBrandContract, BrandContract
|
|
logos.tsx # HanzoLogo/HanzoLogoSVG/HanzoFavicon (consume @hanzo/logo) + BrandLogo
|
|
colors.ts # Monochrome palette: Hanzo Black (#0a0a0b) ↔ Hanzo White (#fff), neutral scale, status semantics
|
|
typography.ts # Font families (Zen, Zen Mono), sizes, weights, line heights
|
|
themes.ts # Light/dark theme definitions, applyTheme() helper
|
|
tokens.ts # Design tokens (spacing, radius, shadows, transitions)
|
|
config.ts # Company info, social links, domains, SEO defaults
|
|
hero.ts, cards.ts, cli.ts # README hero + social-card toolkit + `hanzo-brand` CLI
|
|
utils.ts, docs.ts
|
|
brand.json # Hanzo runtime BrandContract (id portal fetches this; kept in
|
|
# sync with the registry Hanzo entry by a consistency test)
|
|
test/ # node:test suites run against the BUILT dist (pnpm test)
|
|
styles/variables.css
|
|
```
|
|
|
|
## Commands
|
|
```bash
|
|
pnpm install # install dev deps
|
|
pnpm build # tsup (CJS + ESM + DTS)
|
|
pnpm type-check # tsc --noEmit (the real static gate)
|
|
pnpm test # build + node --test (19 tests: white-label invariant, suffix
|
|
# and homoglyph confusion, brand.json consistency)
|
|
```
|
|
|
|
## Notes
|
|
- `@hanzo/logo` is the single source of the Hanzo mark; this package NEVER
|
|
re-types the block-H path data (marks.ts / logos.tsx both consume it).
|
|
- The registry's Hanzo entry and `brand.json` are kept in sync by
|
|
test/brand-json-consistency.test.mjs.
|
|
- `pnpm lint` is currently a no-op error (no ESLint config in-repo); use
|
|
`type-check`.
|
|
- Auto-applies dark/light theme on import in browser environments.
|
|
- Published with public access to npm registry.
|
|
- `CLAUDE.md`, `AGENTS.md`, `QWEN.md`, `GEMINI.md` are all symlinks to this file.
|