77 - logos/png/*.png (all rasters at all configured sizes)
88 - logos/favicon/*.png (favicon sizes)
99 - logos/favicon/favicon.ico (multi-resolution legacy favicon)
10+ - tokens/brand.css (palette as :root CSS custom properties)
11+ - tokens/brand.json (palette as structured JSON)
1012
1113Usage:
1214 python scripts/build.py # Build everything
2022from __future__ import annotations
2123
2224import argparse
25+ import json
2326import os
2427import shutil
2528import subprocess
4144SVG_DIR = ASSETS_DIR / "svg"
4245PNG_DIR = ASSETS_DIR / "png"
4346FAVICON_DIR = ASSETS_DIR / "favicon"
47+ # Non-logo machine-readable exports (palette tokens) live one level up from
48+ # logos/ so consumers can grab just the tokens without the whole asset tree.
49+ TOKENS_DIR = ROOT / "tokens"
4450
4551
4652# ─── Config helpers ─────────────────────────────────────────────────
@@ -374,28 +380,81 @@ def build_favicons(cfg: dict) -> list[Path]:
374380 return written
375381
376382
383+ # ─── Token export (CSS + JSON) ──────────────────────────────────────
384+
385+ def _css_var (prefix : str , key : str ) -> str :
386+ """Map a palette key to a CSS custom-property name: mid_highs → --ap-mid-highs."""
387+ return f"--{ prefix } -{ key .replace ('_' , '-' )} "
388+
389+
390+ def write_tokens (cfg : dict ) -> list [Path ]:
391+ """Export the palette as tokens/brand.css (:root vars) + tokens/brand.json.
392+
393+ Sourced straight from [colors.*] so consumers reference the brand
394+ instead of hand-copying hexes. tomllib preserves insertion order, so
395+ output is deterministic.
396+ """
397+ prefix = cfg ["tokens" ]["css_prefix" ]
398+ spectrum = cfg ["colors" ]["spectrum" ]
399+ neutrals = cfg ["colors" ]["neutrals" ]
400+ semantics = cfg ["colors" ]["semantics" ]
401+
402+ # CSS
403+ lines = [
404+ "/* Audiophore brand color tokens — generated from brand.toml by scripts/build.py." ,
405+ " * Do not edit by hand; run `make tokens`. */" ,
406+ ":root {" ,
407+ " /* Spectrum — each maps to an FFT band (high → low). */" ,
408+ ]
409+ lines += [f" { _css_var (prefix , k )} : { v } ;" for k , v in spectrum .items ()]
410+ lines .append (" /* Neutrals. */" )
411+ lines += [f" { _css_var (prefix , k )} : { v } ;" for k , v in neutrals .items ()]
412+ lines .append (" /* Semantic aliases. */" )
413+ lines += [
414+ f" { _css_var (prefix , k )} : var({ _css_var (prefix , target )} );"
415+ for k , target in semantics .items ()
416+ ]
417+ lines .append ("}" )
418+ css = "\n " .join (lines ) + "\n "
419+
420+ # JSON
421+ data = {
422+ "meta" : {"name" : cfg ["meta" ]["name" ], "slug" : cfg ["meta" ]["slug" ]},
423+ "spectrum" : dict (spectrum ),
424+ "neutrals" : dict (neutrals ),
425+ "semantics" : {k : neutrals .get (t ) or spectrum .get (t ) for k , t in semantics .items ()},
426+ }
427+ js = json .dumps (data , indent = 2 ) + "\n "
428+
429+ css_path = TOKENS_DIR / "brand.css"
430+ json_path = TOKENS_DIR / "brand.json"
431+ css_path .write_text (css )
432+ json_path .write_text (js )
433+ return [css_path , json_path ]
434+
435+
377436# ─── Cleanup and CLI ────────────────────────────────────────────────
378437
379438def clean ():
380439 """Remove all generated assets."""
381- for d in (SVG_DIR , PNG_DIR , FAVICON_DIR ):
440+ for d in (SVG_DIR , PNG_DIR , FAVICON_DIR , TOKENS_DIR ):
382441 if d .exists ():
383442 shutil .rmtree (d )
384443 print (f" removed { d .relative_to (ROOT )} " )
385- for d in (SVG_DIR , PNG_DIR , FAVICON_DIR ):
444+ for d in (SVG_DIR , PNG_DIR , FAVICON_DIR , TOKENS_DIR ):
386445 d .mkdir (parents = True , exist_ok = True )
387446
388447
389448def ensure_dirs ():
390- for d in (SVG_DIR , PNG_DIR , FAVICON_DIR ):
449+ for d in (SVG_DIR , PNG_DIR , FAVICON_DIR , TOKENS_DIR ):
391450 d .mkdir (parents = True , exist_ok = True )
392451
393452
394453def main ():
395454 parser = argparse .ArgumentParser (description = __doc__ .strip ().split ("\n " )[0 ])
396455 parser .add_argument (
397456 "targets" , nargs = "*" ,
398- choices = ["all" , "symbol" , "wordmark" , "lockup" , "png" , "favicon" , []],
457+ choices = ["all" , "symbol" , "wordmark" , "lockup" , "png" , "favicon" , "tokens" , []],
399458 default = [],
400459 help = "Which assets to build (default: all)" ,
401460 )
@@ -411,7 +470,7 @@ def main():
411470
412471 targets = set (args .targets ) if args .targets else {"all" }
413472 if "all" in targets :
414- targets = {"symbol" , "wordmark" , "lockup" , "png" , "favicon" }
473+ targets = {"symbol" , "wordmark" , "lockup" , "png" , "favicon" , "tokens" }
415474
416475 if "symbol" in targets :
417476 print ("Building symbol SVGs…" )
@@ -433,6 +492,10 @@ def main():
433492 print ("Building favicons…" )
434493 for p in build_favicons (cfg ):
435494 print (f" { p .relative_to (ROOT )} " )
495+ if "tokens" in targets :
496+ print ("Exporting color tokens…" )
497+ for p in write_tokens (cfg ):
498+ print (f" { p .relative_to (ROOT )} " )
436499
437500 print ("\n Done." )
438501
0 commit comments