brand-intent 0.8.0 → 0.9.0
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.
- package/package.json +2 -2
- package/parser/brandParser.ts +34 -1
- package/parser/identityParser.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brand-intent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "An open file format specification for making brand intent machine-readable.",
|
|
5
5
|
"author": "Christophe Stoll <christophe@stoll.studio>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
36
|
"release": "./scripts/release.sh",
|
|
37
|
-
"test": "tsx tests/brandParser.test.ts && tsx tests/realBrands.test.ts"
|
|
37
|
+
"test": "tsx tests/brandParser.test.ts && tsx tests/identityParser.test.ts && tsx tests/realBrands.test.ts"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"tsx": "^4.19.0"
|
package/parser/brandParser.ts
CHANGED
|
@@ -91,6 +91,12 @@ export interface ParsedAssetDef {
|
|
|
91
91
|
lottieSrc?: string;
|
|
92
92
|
animated?: boolean;
|
|
93
93
|
loop?: boolean;
|
|
94
|
+
/** Path to a glTF/GLB 3D model. When present, the asset renders as a live 3D object; `src` remains the 2D fallback. */
|
|
95
|
+
gltfSrc?: string;
|
|
96
|
+
/** Procedural generator id (e.g. "mobius", "spiral", "ribbon"). Renders as live geometry; `src` is the 2D fallback. */
|
|
97
|
+
generator?: string;
|
|
98
|
+
/** Default parameters for the procedural generator, keyed by param name. Declared via the indented `generator-params` sub-block. */
|
|
99
|
+
generatorParams?: Record<string, number>;
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
export interface ParsedBadgeStyle {
|
|
@@ -312,6 +318,7 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
312
318
|
const videosRaw: Array<{ id: string; props: Record<string, string> }> = [];
|
|
313
319
|
const illustrationsRaw: Array<{ id: string; props: Record<string, string> }> = [];
|
|
314
320
|
let currentAssetId = '';
|
|
321
|
+
let currentSubBlock: string | null = null;
|
|
315
322
|
const labelRaw: Record<string, string> = {};
|
|
316
323
|
const ctaRaw: Record<string, string> = {};
|
|
317
324
|
|
|
@@ -569,19 +576,33 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
569
576
|
continue;
|
|
570
577
|
}
|
|
571
578
|
if ((activeBlock === 'logo' || activeBlock === 'photo' || activeBlock === 'video' || activeBlock === 'illustration') && currentAssetId) {
|
|
579
|
+
const indent = raw.match(/^(\s*)/)?.[1].length ?? 0;
|
|
572
580
|
const colonIdx = raw.indexOf(':');
|
|
573
581
|
if (colonIdx !== -1) {
|
|
574
582
|
const key = raw.slice(0, colonIdx).trim();
|
|
575
583
|
const value = raw.slice(colonIdx + 1).trim();
|
|
576
584
|
const arr = activeBlock === 'logo' ? logosRaw : activeBlock === 'photo' ? photosRaw : activeBlock === 'video' ? videosRaw : illustrationsRaw;
|
|
577
585
|
const last = arr[arr.length - 1];
|
|
578
|
-
if (last && last.id === currentAssetId)
|
|
586
|
+
if (last && last.id === currentAssetId) {
|
|
587
|
+
// 4+ space indent after a bare sub-block keyword (e.g. generator-params) → nested param.
|
|
588
|
+
// Store under "subBlock.key" so toAssetDef can split it out.
|
|
589
|
+
if (indent >= 4 && currentSubBlock) {
|
|
590
|
+
last.props[`${currentSubBlock}.${key}`] = value;
|
|
591
|
+
} else {
|
|
592
|
+
last.props[key] = value;
|
|
593
|
+
currentSubBlock = null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
579
596
|
} else {
|
|
580
597
|
const trimmed = raw.trim();
|
|
581
598
|
if (trimmed === 'animated' || trimmed === 'loop') {
|
|
582
599
|
const arr = activeBlock === 'logo' ? logosRaw : activeBlock === 'photo' ? photosRaw : activeBlock === 'video' ? videosRaw : illustrationsRaw;
|
|
583
600
|
const last = arr[arr.length - 1];
|
|
584
601
|
if (last && last.id === currentAssetId) last.props[trimmed] = 'true';
|
|
602
|
+
currentSubBlock = null;
|
|
603
|
+
} else if (trimmed === 'generator-params') {
|
|
604
|
+
// Open a nested sub-block — subsequent deeper-indented key: value lines attach here.
|
|
605
|
+
currentSubBlock = 'generator-params';
|
|
585
606
|
}
|
|
586
607
|
}
|
|
587
608
|
continue;
|
|
@@ -986,6 +1007,18 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
986
1007
|
if (raw.props['lottie']) def.lottieSrc = raw.props['lottie'];
|
|
987
1008
|
if (raw.props['animated'] === 'true') def.animated = true;
|
|
988
1009
|
if (raw.props['loop'] === 'true') def.loop = true;
|
|
1010
|
+
if (raw.props['gltf']) def.gltfSrc = raw.props['gltf'];
|
|
1011
|
+
if (raw.props['generator']) def.generator = raw.props['generator'];
|
|
1012
|
+
// generator-params are stored as "generator-params.KEY" by the sub-block parser.
|
|
1013
|
+
const paramEntries: [string, number][] = [];
|
|
1014
|
+
for (const [k, v] of Object.entries(raw.props)) {
|
|
1015
|
+
if (k.startsWith('generator-params.')) {
|
|
1016
|
+
const paramKey = k.slice('generator-params.'.length);
|
|
1017
|
+
const n = parseFloat(v);
|
|
1018
|
+
if (!Number.isNaN(n)) paramEntries.push([paramKey, n]);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (paramEntries.length > 0) def.generatorParams = Object.fromEntries(paramEntries);
|
|
989
1022
|
return def;
|
|
990
1023
|
};
|
|
991
1024
|
if (logosRaw.length > 0 || photosRaw.length > 0 || videosRaw.length > 0 || illustrationsRaw.length > 0) {
|
package/parser/identityParser.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface ParsedIdentity {
|
|
|
26
26
|
tagline?: string;
|
|
27
27
|
/** Competitive positioning — what makes this brand the only one of its kind */
|
|
28
28
|
positioning?: string;
|
|
29
|
+
/** Brand purpose — why the brand exists, the inner motivation (distinct from promise, mission, vision) */
|
|
30
|
+
purpose?: string;
|
|
29
31
|
/** Organizational mission — what the brand does, for whom */
|
|
30
32
|
mission?: string;
|
|
31
33
|
/** Aspirational future state */
|
|
@@ -109,6 +111,7 @@ export function parseIdentityFile(content: string, fileName: string): ParsedIden
|
|
|
109
111
|
let promise = '';
|
|
110
112
|
let tagline = '';
|
|
111
113
|
let positioning = '';
|
|
114
|
+
let purpose = '';
|
|
112
115
|
let mission = '';
|
|
113
116
|
let vision = '';
|
|
114
117
|
let archetype = '';
|
|
@@ -195,6 +198,7 @@ export function parseIdentityFile(content: string, fileName: string): ParsedIden
|
|
|
195
198
|
if (continuationKey === 'essence') essence += ' ' + continuation;
|
|
196
199
|
else if (continuationKey === 'promise') promise += ' ' + continuation;
|
|
197
200
|
else if (continuationKey === 'positioning') positioning += ' ' + continuation;
|
|
201
|
+
else if (continuationKey === 'purpose') purpose += ' ' + continuation;
|
|
198
202
|
else if (continuationKey === 'mission') mission += ' ' + continuation;
|
|
199
203
|
else if (continuationKey === 'vision') vision += ' ' + continuation;
|
|
200
204
|
continue;
|
|
@@ -300,6 +304,10 @@ export function parseIdentityFile(content: string, fileName: string): ParsedIden
|
|
|
300
304
|
positioning = val;
|
|
301
305
|
continuationKey = 'positioning';
|
|
302
306
|
break;
|
|
307
|
+
case 'purpose':
|
|
308
|
+
purpose = val;
|
|
309
|
+
continuationKey = 'purpose';
|
|
310
|
+
break;
|
|
303
311
|
case 'mission':
|
|
304
312
|
mission = val;
|
|
305
313
|
continuationKey = 'mission';
|
|
@@ -425,6 +433,7 @@ export function parseIdentityFile(content: string, fileName: string): ParsedIden
|
|
|
425
433
|
// Optional fields — only include if present
|
|
426
434
|
if (tagline) result.tagline = tagline;
|
|
427
435
|
if (positioning) result.positioning = positioning;
|
|
436
|
+
if (purpose) result.purpose = purpose;
|
|
428
437
|
if (mission) result.mission = mission;
|
|
429
438
|
if (vision) result.vision = vision;
|
|
430
439
|
if (archetype) result.archetype = archetype;
|