branding-engine 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +433 -73
  2. package/bin/cli.mjs +8 -5
  3. package/examples/severino-labs/README.md +95 -0
  4. package/examples/severino-labs/brand.json +30 -0
  5. package/examples/severino-labs/generated/cards/social-card.png +0 -0
  6. package/examples/severino-labs/generated/severino-labs/README.md +49 -0
  7. package/examples/severino-labs/generated/severino-labs/icons/apple-touch-icon.png +0 -0
  8. package/examples/severino-labs/generated/severino-labs/icons/favicon-192.png +0 -0
  9. package/examples/severino-labs/generated/severino-labs/icons/favicon-32.png +0 -0
  10. package/examples/severino-labs/generated/severino-labs/icons/favicon.ico +0 -0
  11. package/examples/severino-labs/generated/severino-labs/icons/favicon.svg +1 -0
  12. package/examples/severino-labs/generated/severino-labs/mark/mark-1024.png +0 -0
  13. package/examples/severino-labs/generated/severino-labs/mark/mark-512.png +0 -0
  14. package/examples/severino-labs/generated/severino-labs/mark/mark-transparent-dark.png +0 -0
  15. package/examples/severino-labs/generated/severino-labs/mark/mark-transparent-light.png +0 -0
  16. package/examples/severino-labs/generated/severino-labs/mark/mark.svg +1 -0
  17. package/examples/severino-labs/generated/severino-labs/sheet/overview.png +0 -0
  18. package/examples/severino-labs/generated/severino-labs/sheet/palette.png +0 -0
  19. package/examples/severino-labs/generated/severino-labs/sheet/sheet-mark.png +0 -0
  20. package/examples/severino-labs/generated/severino-labs/sheet/type-specimen.png +0 -0
  21. package/examples/severino-labs/generated/severino-labs/web/head.html +6 -0
  22. package/examples/severino-labs/generated/severino-labs/web/site.webmanifest +19 -0
  23. package/examples/severino-labs/generated/severino-labs/web/tokens.css +7 -0
  24. package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-caps-dark.png +0 -0
  25. package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-caps-light.png +0 -0
  26. package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-caps.svg +1 -0
  27. package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-dark.png +0 -0
  28. package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-light.png +0 -0
  29. package/examples/severino-labs/generated/severino-labs/wordmark/wordmark.svg +1 -0
  30. package/examples/severino-labs/studio.jpg +0 -0
  31. package/index.mjs +1 -0
  32. package/package.json +13 -6
  33. package/src/build.mjs +8 -6
  34. package/src/lib/extract-glyphs.mjs +65 -0
  35. package/src/lib/glyphs.mjs +6 -19
  36. package/src/lib/identity.mjs +11 -0
  37. package/src/lib/mark.mjs +28 -8
  38. package/src/lib/render.mjs +11 -2
  39. package/src/lib/wordmark.mjs +6 -1
  40. package/src/make-mark.mjs +2 -0
  41. package/src/make-sheet.mjs +2 -0
  42. package/src/make-web.mjs +2 -0
  43. package/src/make-wordmark.mjs +3 -1
  44. package/src/site.mjs +3 -1
  45. package/requirements.txt +0 -3
  46. package/src/lib/extract-glyphs.py +0 -75
package/README.md CHANGED
@@ -1,129 +1,489 @@
1
1
  # branding-engine
2
2
 
3
- Generate a complete, self-consistent brand kit from one accent color and a
4
- monogram: a favicon set, a vector mark, wordmark lockups, social cards, a brand
5
- sheet, and drop-in web tokens. The mark and wordmark are real font outlines
6
- (pure vector, no raster source); only the social cards and brand sheet render
7
- live text via a headless browser.
3
+ [![npm version](https://img.shields.io/npm/v/branding-engine.svg)](https://www.npmjs.com/package/branding-engine)
4
+ [![ci](https://github.com/joeseverino/branding-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/joeseverino/branding-engine/actions/workflows/ci.yml)
5
+ [![node](https://img.shields.io/node/v/branding-engine.svg)](https://nodejs.org)
6
+ [![license: MIT](https://img.shields.io/npm/l/branding-engine.svg)](./LICENSE)
8
7
 
9
- One monogram, one accent color, every brand surface.
8
+ Generate a consistent brand kit from a compact alphanumeric mark and one accent
9
+ color. Outputs include favicons, vector and raster marks, wordmark lockups,
10
+ brand sheets, social cards, manifests, and CSS tokens.
10
11
 
11
- ## Install
12
+ The mark and wordmark use real font outlines. The common website path requires
13
+ only Node.js; browser rendering is optional.
12
14
 
13
- ```sh
15
+ ## Example
16
+
17
+ Illustrative input using a non-production sample palette:
18
+
19
+ ```json
20
+ {
21
+ "name": "Severino Labs",
22
+ "identity": {
23
+ "slug": "severino-labs",
24
+ "color": "#6D5EF7",
25
+ "deep": "#352A8A",
26
+ "onColor": "#FFFFFF",
27
+ "glyph": "SL",
28
+ "wordmark": "Severino Labs"
29
+ },
30
+ "portrait": "./studio.jpg",
31
+ "cardPalette": {
32
+ "accent": "#9B8CFF",
33
+ "textSoft": "#E3DEFF",
34
+ "textMuted": "#B7AFE8"
35
+ },
36
+ "cards": [
37
+ {
38
+ "file": "social-card.png",
39
+ "width": 1200,
40
+ "height": 630,
41
+ "photoWidth": 420,
42
+ "eyebrow": "Severino Labs",
43
+ "name": "Brand systems, generated.",
44
+ "tagline": "Marks, wordmarks, sheets, web assets, and social cards from one config.",
45
+ "meta": "Illustrative branding-engine example",
46
+ "url": "github.com/joeseverino/branding-engine"
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ Generated mark:
53
+
54
+ ![Severino Labs generated mark](./examples/severino-labs/generated/severino-labs/mark/mark-512.png)
55
+
56
+ Generated wordmark:
57
+
58
+ ![Severino Labs generated wordmark](./examples/severino-labs/generated/severino-labs/wordmark/wordmark-light.png)
59
+
60
+ Generated brand sheet:
61
+
62
+ ![Severino Labs generated brand sheet](./examples/severino-labs/generated/severino-labs/sheet/overview.png)
63
+
64
+ Generated social card:
65
+
66
+ ![Severino Labs generated social card](./examples/severino-labs/generated/cards/social-card.png)
67
+
68
+ The complete input and committed generated output are in
69
+ [`examples/severino-labs`](./examples/severino-labs/).
70
+
71
+ ## Requirements
72
+
73
+ - Node.js 18 or newer
74
+ - `sharp`, OpenType.js, and the WOFF2 decoder, installed automatically
75
+ - Optional: `@playwright/test` plus Chromium for brand sheets and social cards
76
+
77
+ Install:
78
+
79
+ ```bash
14
80
  npm install branding-engine
15
81
  ```
16
82
 
17
- `sharp` (used everywhere) installs automatically. `@playwright/test` is an
18
- **optional** dependency: it's only needed for the brand sheet and social cards.
19
- The mark, wordmark, and icon generation path needs neither Playwright nor Python.
20
- To enable sheets and cards:
83
+ For a project-local CLI installation:
84
+
85
+ ```bash
86
+ npm install --save-dev branding-engine
87
+ npx branding-engine --help
88
+ ```
89
+
90
+ The package can also be installed globally with
91
+ `npm install --global branding-engine`, though project-local installation keeps
92
+ the version reproducible for collaborators and CI.
93
+
94
+ For sheets and social cards:
95
+
96
+ ```bash
97
+ npm install --save-dev @playwright/test
98
+ npx playwright install chromium
99
+ ```
100
+
101
+ ## Glyph Rules
102
+
103
+ `glyph` is the compact mark rendered inside the tile.
104
+
105
+ - Accepts 1-3 ASCII letters or digits
106
+ - Lowercase letters are normalized to uppercase
107
+ - Spaces, punctuation, symbols, and strings longer than three characters fail
108
+ - Layout dynamically adjusts by character count and caps narrow marks by height
109
+
110
+ Valid:
111
+
112
+ ```text
113
+ A
114
+ AC
115
+ A3X
116
+ 7
117
+ R2
118
+ ```
119
+
120
+ Invalid:
21
121
 
22
- ```sh
23
- npm i -D @playwright/test && npx playwright install chromium
122
+ ```text
123
+ ABCD
124
+ A C
125
+ A-C
126
+ @
24
127
  ```
25
128
 
26
- ## Plug into a site (Astro, Eleventy, plain HTML)
129
+ ## Quick Start: Add Branding to a Website
27
130
 
28
- The fastest path for a website: scaffold a config, set your color, generate a
29
- favicon set into `public/`. No headless browser, no python.
131
+ Use `init` and `generate` when a site needs favicons, a manifest, and CSS
132
+ tokens in its public directory.
30
133
 
31
- ```sh
32
- npm i -D branding-engine
33
- npx branding-engine init # writes brand.config.json + a `brand` npm script
34
- # edit accent, glyph, name in brand.config.json, then:
35
- npm run brand # generates into public/, prints the <head> snippet
134
+ ```bash
135
+ npm install --save-dev branding-engine
136
+ npx branding-engine init
36
137
  ```
37
138
 
38
- `brand.config.json` is flat:
139
+ Edit the generated `brand.config.json`:
39
140
 
40
141
  ```json
41
- { "name": "My Site", "accent": "#2563EB", "glyph": "MS", "wordmark": "My Site" }
142
+ {
143
+ "name": "My Site",
144
+ "accent": "#2563EB",
145
+ "deep": "#173B8F",
146
+ "onColor": "#FFFFFF",
147
+ "glyph": "MS"
148
+ }
149
+ ```
150
+
151
+ Then generate the files:
152
+
153
+ ```bash
154
+ npm run brand
42
155
  ```
43
156
 
44
- `generate` writes these to `public/` at the root paths a static site expects, and
45
- prints the `<head>` block to paste in (its `theme-color` reflects your accent):
157
+ Default output:
46
158
 
47
159
  ```text
48
- public/favicon.ico favicon.svg favicon-32.png favicon-192.png
49
- public/apple-touch-icon.png site.webmanifest brand-tokens.css
160
+ public/
161
+ ├── apple-touch-icon.png
162
+ ├── brand-tokens.css
163
+ ├── favicon-32.png
164
+ ├── favicon-192.png
165
+ ├── favicon.ico
166
+ ├── favicon.svg
167
+ └── site.webmanifest
50
168
  ```
51
169
 
52
- Commit those files; they are deterministic, so re-running `npm run brand` after a
53
- color change reproduces them exactly. Optional flags: `--public <dir>`,
54
- `--config <file>`.
170
+ The command also prints the `<head>` links to add to the site.
55
171
 
56
- ## CLI
172
+ ### Website Config Reference
57
173
 
58
- ```sh
59
- # A one-off kit (mark, wordmark, sheet, web) from an accent + initials
60
- branding-engine kit acme ff5733 AC "Acme Corp"
174
+ | Field | Required | Description |
175
+ |---|---:|---|
176
+ | `name` | yes | Application name used in `site.webmanifest` |
177
+ | `accent` | yes | Six-digit hex accent, with or without `#` |
178
+ | `glyph` | yes | One to three alphanumeric mark characters |
179
+ | `deep` | no | Dark palette shade; derived from `accent` when omitted |
180
+ | `onColor` | no | Glyph color on the accent; defaults to `#FFFFFF` |
61
181
 
62
- # Just the lean, browser-free pieces (what a website needs)
63
- branding-engine kit acme ff5733 AC "Acme Corp" --only mark,wordmark,web
182
+ Options:
64
183
 
65
- # A whole brand (primary identity + surfaces + cards) from a config
66
- branding-engine build --config ./brand --out ./kits
184
+ ```bash
185
+ branding-engine generate --config path/to/brand.config.json --public path/to/public
186
+ ```
187
+
188
+ Generated files are deterministic and intended to be committed with the site.
189
+
190
+ ### Astro
191
+
192
+ Astro serves files from `public/` at the site root, so the default generator
193
+ paths work without customization:
194
+
195
+ ```bash
196
+ npm install --save-dev branding-engine
197
+ npx branding-engine init
198
+ npm run brand
199
+ ```
200
+
201
+ In your shared layout, add the generated links and tokens:
202
+
203
+ ```astro
204
+ <html lang="en">
205
+ <head>
206
+ <link rel="icon" href="/favicon.ico" sizes="any" />
207
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
208
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
209
+ <link rel="manifest" href="/site.webmanifest" />
210
+ <link rel="stylesheet" href="/brand-tokens.css" />
211
+ <meta name="theme-color" content="#2563EB" />
212
+ </head>
213
+ <body><slot /></body>
214
+ </html>
215
+ ```
216
+
217
+ To regenerate before every production build, add it to the existing build
218
+ script:
219
+
220
+ ```json
221
+ {
222
+ "scripts": {
223
+ "brand": "branding-engine generate",
224
+ "build": "npm run brand && astro build"
225
+ }
226
+ }
227
+ ```
228
+
229
+ ### Plain HTML or Static Site
230
+
231
+ If the repository already publishes a `public/` directory, use the same
232
+ default commands as Astro. If the repository root itself is deployed:
233
+
234
+ ```bash
235
+ npx branding-engine generate --public .
236
+ ```
237
+
238
+ Add the links printed by the command to the page `<head>`, plus the token
239
+ stylesheet:
240
+
241
+ ```html
242
+ <link rel="stylesheet" href="/brand-tokens.css" />
67
243
  ```
68
244
 
69
- Flags: `--config <dir|brand.json>`, `--out <dir>`, `--font <file>`,
70
- `--only mark,wordmark,sheet,web,cards` (`mark` includes the favicon set).
245
+ The generated CSS variables can then be used from any stylesheet:
71
246
 
72
- ## Brand config
247
+ ```css
248
+ .button {
249
+ color: var(--brand-on-accent);
250
+ background: var(--brand-accent);
251
+ }
252
+ ```
73
253
 
74
- `build` reads a `brand.json` (and an optional sibling `surfaces.json`). Paths
75
- inside it (`font`, `portrait`) are resolved relative to the config's directory;
76
- omit `font` to use the bundled Inter.
254
+ ## Full Brand Kit
77
255
 
78
- ```jsonc
256
+ Use `build` for a reusable config-driven kit:
257
+
258
+ ```bash
259
+ branding-engine build --config ./brand --out ./kits
260
+ ```
261
+
262
+ `--config` accepts either a `brand.json` path or a directory containing
263
+ `brand.json`. An optional `surfaces.json` can live beside it.
264
+
265
+ Minimal `brand.json`:
266
+
267
+ ```json
79
268
  {
80
269
  "name": "Acme",
81
- "wordmarkWeight": 700, // wordmark text weight (mark uses `weight`, default 800)
82
270
  "identity": {
83
271
  "slug": "acme",
84
- "color": "#1E3A8A", // accent
85
- "deep": "#14245C", // optional; falls back to a darkened accent
86
- "onColor": "#ffffff", // optional; color on the accent
272
+ "color": "#1E3A8A",
87
273
  "glyph": "AC",
88
274
  "wordmark": "Acme Corp"
275
+ }
276
+ }
277
+ ```
278
+
279
+ Expanded `brand.json`:
280
+
281
+ ```json
282
+ {
283
+ "name": "Acme",
284
+ "font": "./AcmeSans.ttf",
285
+ "weight": 800,
286
+ "wordmarkWeight": 700,
287
+ "identity": {
288
+ "slug": "acme",
289
+ "color": "#1E3A8A",
290
+ "deep": "#14245C",
291
+ "onColor": "#FFFFFF",
292
+ "glyph": "A3C",
293
+ "wordmark": "Acme Corp"
89
294
  },
90
- "cardPalette": { "accent": "#5B82D6", "textSoft": "#DDE6FB", "textMuted": "#A9C0E8" },
91
- "cards": [ /* { file, width, height, photoWidth, eyebrow, name, tagline, meta, url } */ ]
295
+ "portrait": "./portrait.jpg",
296
+ "cardPalette": {
297
+ "accent": "#5B82D6",
298
+ "textSoft": "#DDE6FB",
299
+ "textMuted": "#A9C0E8"
300
+ },
301
+ "cards": [
302
+ {
303
+ "file": "social-card.png",
304
+ "width": 1200,
305
+ "height": 630,
306
+ "photoWidth": 420,
307
+ "eyebrow": "Acme Corp",
308
+ "name": "Acme",
309
+ "tagline": "Built for what comes next.",
310
+ "meta": "Brand systems and engineering",
311
+ "url": "acme.example"
312
+ }
313
+ ]
92
314
  }
93
315
  ```
94
316
 
95
- `surfaces.json` lists other surfaces that inherit the font and glyph and override
96
- only color (and optionally wordmark):
317
+ ### Full Config Reference
318
+
319
+ | Field | Required | Description |
320
+ |---|---:|---|
321
+ | `name` | no | Human-readable brand name used in logs and fallbacks |
322
+ | `identity` | yes | Primary brand identity object |
323
+ | `identity.slug` | yes | Output directory name |
324
+ | `identity.color` | yes | Six-digit accent color |
325
+ | `identity.glyph` | yes | One to three alphanumeric mark characters |
326
+ | `identity.wordmark` | no | Text used for wordmark lockups and sheet title |
327
+ | `identity.deep` | no | Curated dark shade |
328
+ | `identity.onColor` | no | Glyph color on accent |
329
+ | `font` | no | Font path relative to `brand.json`; defaults to bundled Inter |
330
+ | `weight` | no | Mark font weight; defaults to `800` |
331
+ | `wordmarkWeight` | no | Wordmark font weight; defaults to `700` |
332
+ | `surfaces` | no | Inline additional surfaces; `surfaces.json` takes precedence |
333
+ | `portrait` | for cards | JPEG path relative to `brand.json` |
334
+ | `cardPalette` | for cards | Card accent and supporting text colors |
335
+ | `cards` | no | Social-card definitions rendered to `<out>/cards/` |
336
+
337
+ Additional surfaces inherit the primary glyph unless they override it:
97
338
 
98
339
  ```json
99
- { "support": { "color": "#1f4d57", "wordmark": "Acme Support" } }
340
+ {
341
+ "support": {
342
+ "color": "#1F4D57",
343
+ "wordmark": "Acme Support"
344
+ },
345
+ "labs": {
346
+ "color": "#7C3AED",
347
+ "glyph": "A3",
348
+ "wordmark": "Acme Labs"
349
+ }
350
+ }
351
+ ```
352
+
353
+ ## One-Off Kit
354
+
355
+ Create a kit without a config file:
356
+
357
+ ```bash
358
+ branding-engine kit acme ff5733 AC "Acme Corp"
359
+ ```
360
+
361
+ Three-character example:
362
+
363
+ ```bash
364
+ branding-engine kit prism 635bff P3X "Prism Works" \
365
+ --only mark,wordmark,web \
366
+ --out ./kits
367
+ ```
368
+
369
+ Syntax:
370
+
371
+ ```text
372
+ branding-engine kit <slug> <hex> <glyph> ["Wordmark"] [options]
373
+ ```
374
+
375
+ ## Stages
376
+
377
+ Select stages with a comma-separated `--only` value:
378
+
379
+ ```bash
380
+ branding-engine build \
381
+ --config ./brand.json \
382
+ --out ./kits \
383
+ --only mark,wordmark,web
384
+ ```
385
+
386
+ | Stage | Browser needed | Output |
387
+ |---|---:|---|
388
+ | `mark` | no | Favicons, vector mark, PNG marks, transparent variants |
389
+ | `wordmark` | no | Vector and PNG title-case/all-caps lockups |
390
+ | `web` | no | CSS tokens, web manifest, and `<head>` snippet |
391
+ | `sheet` | yes | Brand overview poster, sections, and generated kit README |
392
+ | `cards` | yes | Configured social-card PNGs |
393
+
394
+ Without `--only`, all stages run.
395
+
396
+ ## Output Layout
397
+
398
+ Each kit is written under `<out>/<slug>/`:
399
+
400
+ ```text
401
+ <out>/<slug>/
402
+ ├── icons/
403
+ ├── mark/
404
+ ├── sheet/
405
+ ├── web/
406
+ └── wordmark/
100
407
  ```
101
408
 
409
+ Social cards are written to `<out>/cards/`.
410
+
102
411
  ## Programmatic API
103
412
 
104
413
  ```js
105
- import { buildBrand, buildKit, markSvg, wordmarkSvg } from 'branding-engine';
106
-
107
- await buildKit({ slug: 'acme', hex: '#ff5733', glyph: 'AC', wordmark: 'Acme', only: 'mark,wordmark', outDir: 'public/brand' });
108
- const svg = markSvg({ size: 64, bg: '#ff5733', glyph: 'AC' }); // pure string, no I/O
414
+ import {
415
+ buildBrand,
416
+ buildKit,
417
+ generateSite,
418
+ markSvg,
419
+ normalizeGlyph,
420
+ wordmarkSvg,
421
+ } from 'branding-engine';
422
+
423
+ await buildKit({
424
+ slug: 'acme',
425
+ hex: '#FF5733',
426
+ glyph: 'a3x',
427
+ wordmark: 'Acme',
428
+ only: 'mark,wordmark,web',
429
+ outDir: 'public/brand',
430
+ });
431
+
432
+ const glyph = normalizeGlyph('a3x'); // "A3X"
433
+ const mark = markSvg({ size: 64, bg: '#FF5733', glyph });
434
+ const lockup = wordmarkSvg({
435
+ tileHex: '#FF5733',
436
+ text: 'Acme',
437
+ glyph,
438
+ });
109
439
  ```
110
440
 
111
- ## Output
441
+ Main exports:
442
+
443
+ - `buildBrand(options)`
444
+ - `buildKit(options)`
445
+ - `initSite(options)`
446
+ - `generateSite(options)`
447
+ - `makeMark(options)`
448
+ - `makeWordmark(options)`
449
+ - `makeSheet(options)`
450
+ - `makeWeb(options)`
451
+ - `makeCards(options)`
452
+ - `markSvg(options)`
453
+ - `wordmarkSvg(options)`
454
+ - `normalizeGlyph(glyph)`
455
+ - `renderCard(browser, options)`
456
+ - `launchBrowser()`
112
457
 
113
- Each kit is `<out>/<slug>/`:
458
+ ## Fonts and Glyph Extraction
114
459
 
115
- - `icons/`: `favicon.svg/.ico`, `favicon-32/192.png`, `apple-touch-icon.png`
116
- - `mark/`: `mark.svg`, `mark-512/1024.png`, transparent light/dark
117
- - `wordmark/`: `wordmark.svg` + light/dark PNGs, plus all-caps `wordmark-caps.*`
118
- - `sheet/`: `overview.png` poster + section images, and a `README.md`
119
- - `web/`: `tokens.css`, `site.webmanifest`, `head.html`
460
+ Bundled Inter caches include uppercase letters and digits for marks, plus
461
+ uppercase, lowercase, digits, and spaces for wordmarks.
120
462
 
121
- Social cards land in `<out>/cards/`.
463
+ Custom fonts and missing characters are extracted entirely in Node with
464
+ OpenType.js and a WebAssembly WOFF2 decoder. No Python, fonttools, native
465
+ binding, or system font utility is required. Supported input formats are TTF,
466
+ OTF, WOFF, and WOFF2.
467
+
468
+ Extracted caches are written under `.brand-cache/`, or the directory specified
469
+ by `BRAND_CACHE_DIR`. The installed package is never modified. If a variable
470
+ font cannot be instantiated at the requested `weight`, the build exits with the
471
+ font filename and parser error; use a static font file or another supported
472
+ variable font.
473
+
474
+ ## Errors
475
+
476
+ The CLI exits nonzero with an actionable message for invalid glyphs, invalid
477
+ colors, missing configs, unavailable font glyphs, or missing optional browser
478
+ dependencies.
479
+
480
+ Example:
481
+
482
+ ```text
483
+ Invalid glyph: "ABCD". Expected 1-3 letters or digits, e.g. A, AC, or A3X.
484
+ ```
122
485
 
123
- ## Fonts and python
486
+ ## License
124
487
 
125
- The bundled Inter ships with prebuilt glyph outlines, so the default font needs
126
- **no python**. A custom font (or a glyph outside the bundled set) is extracted on
127
- demand and requires `python3` + `fonttools` (`pip install -r requirements.txt`);
128
- the extracted cache is written under `.brand-cache/` (or `$BRAND_CACHE_DIR`),
129
- never into the installed package.
488
+ MIT. The bundled Inter font includes its own notice under
489
+ `assets/fonts/inter/NOTICE.md`.
package/bin/cli.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  // branding-engine init scaffold a site brand.config + npm script
4
4
  // branding-engine generate [--public <dir>] [--config <file>] favicons + manifest + tokens -> public/
5
5
  // branding-engine build [--config <dir|brand.json>] [--out <dir>] [--only a,b]
6
- // branding-engine kit <slug> <hex> <initials> ["Wordmark"] [--font f] [--out d] [--only a,b]
6
+ // branding-engine kit <slug> <hex> <glyph> ["Wordmark"] [--font f] [--out d] [--only a,b]
7
7
  // Stages for --only: mark, wordmark, sheet, web, cards (mark includes favicons).
8
8
  import { buildBrand, buildKit } from '../src/build.mjs';
9
9
  import { generateSite, initSite } from '../src/site.mjs';
@@ -29,17 +29,20 @@ const USAGE =
29
29
  ' branding-engine init scaffold brand.config.json + a `brand` npm script\n' +
30
30
  ' branding-engine generate [--public <dir>] [--config <file>] favicons + manifest + tokens -> public/\n' +
31
31
  ' branding-engine build [--config <dir|brand.json>] [--out <dir>] [--only mark,wordmark,sheet,web,cards]\n' +
32
- ' branding-engine kit <slug> <hex> <initials> ["Wordmark"] [--font <file>] [--out <dir>] [--only ...]';
32
+ ' branding-engine kit <slug> <hex> <glyph> ["Wordmark"] [--font <file>] [--out <dir>] [--only ...]\n' +
33
+ ' <glyph> is 1-3 letters or digits, e.g. A, AC, or A3X.';
33
34
 
34
35
  const [cmd, ...rest] = process.argv.slice(2);
35
36
  const { pos, opt } = parse(rest);
36
37
 
37
38
  try {
38
- if (cmd === 'init') {
39
+ if (!cmd || cmd === '--help' || cmd === '-h') {
40
+ console.log(USAGE);
41
+ } else if (cmd === 'init') {
39
42
  const { created, headSnippet } = initSite({});
40
43
  if (created.length) console.log('Created:\n' + created.map((c) => ' ' + c).join('\n'));
41
44
  console.log('\nNext: edit brand.config.json (accent, glyph, name), then run `npm run brand`.');
42
- console.log('\nAdd this to your site <head> (or import public/brand-tokens.css for the CSS vars):\n');
45
+ console.log('\nAdd this to your site <head>:\n');
43
46
  console.log(headSnippet + '\n');
44
47
  } else if (cmd === 'generate') {
45
48
  const { written, publicDir, headSnippet } = await generateSite({ config: opt.config, publicDir: opt.public });
@@ -58,7 +61,7 @@ try {
58
61
  await buildKit({ slug, hex, glyph, wordmark, font: opt.font, outDir: opt.out, only: opt.only });
59
62
  } else {
60
63
  console.error(USAGE);
61
- process.exit(cmd ? 1 : 0);
64
+ process.exit(1);
62
65
  }
63
66
  } catch (err) {
64
67
  console.error(`\n${err.message}`);