Files
zeekayandHanzo Dev 7aadd4b1e3 the mapping is @hanzo/design's, not a copy per brand
tokens.css was generated from a table in this repo that said which semantic token
each theme key answers for. Every brand package would have carried that same
table, and so would every app that resolves its brand at runtime — three copies
of one rule, which is how a brand comes to look like itself in one surface and
not another.

@hanzo/design owns the token names, so it owns the mapping (themeToTokens, 0.5.1).
This file now only decides where the result is written and under which selectors.

The generated tokens.css is byte-identical, which is the point: nothing about the
look changed, only how many places state the rule.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 19:21:40 -07:00

62 lines
2.4 KiB
JavaScript

// brand.json's `theme` PROJECTED onto @hanzo/design's semantic tokens.
//
// The look is already declared — brand.json carries a light and a dark theme, and
// has for as long as this package has existed. What was missing was the bridge to
// the layer that renders it, so every surface fell back to Hanzo's monochrome and
// a brand could not look like itself.
//
// It is GENERATED, never authored. A hand-written tokens.css would be a second
// copy of the palette, and the whole lesson of the placeholder mark is that a
// second copy is a thing that silently stops agreeing.
//
// Only the tokens a BRAND legitimately owns are emitted — ground, ink, accent,
// the edges drawn on them. Radius, type ramp, spacing, motion and z stay
// @hanzo/design's: they are the system's grammar, not a brand's voice, and a
// brand that redefines them is a fork wearing a stylesheet.
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { themeToTokens } from '@hanzo/design'
const brand = JSON.parse(readFileSync(join(process.cwd(), 'brand.json'), 'utf8')).brand
const { theme } = brand
if (!theme?.dark || !theme?.light) {
console.error('REFUSED: brand.json declares no light+dark theme to project')
process.exit(1)
}
// The MAPPING is @hanzo/design's — it owns the semantic token names, a brand owns
// the values, and neither carries a copy of the other. This file only decides
// where the result is written and under which selectors.
function block(t) {
return Object.entries(themeToTokens(t)).map(([name, value]) => ` ${name}:${value};`).join('\n')
}
const css = `/* GENERATED from brand.json by scripts/tokens.mjs — do not edit.
Run \`pnpm tokens\`. The palette lives in brand.json; this is its projection
onto @hanzo/design's semantic tokens.
Import AFTER @hanzo/design/styles.css:
@import "@hanzo/design/styles.css";
@import "@luxfi/brand/tokens.css";
Only what a brand owns is here — ground, ink, accent, edges. The radius scale,
the type ramp, spacing, motion and z stay @hanzo/design's. */
:root{
${block(theme.dark)}
}
@media (prefers-color-scheme: light){
:root:not([data-theme="dark"]){
${block(theme.light).split('\n').map((l) => ' ' + l).join('\n')}
}
}
[data-theme="light"]{
${block(theme.light)}
}
`
writeFileSync(join(process.cwd(), 'tokens.css'), css)
const n = css.match(/^\s+--/gm)?.length ?? 0
console.log(`>> tokens.css — ${n} declarations from brand.json's theme`)