brand-intent 0.26.0 → 0.28.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brand-intent",
3
- "version": "0.26.0",
3
+ "version": "0.28.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",
@@ -57,20 +57,6 @@ export interface ParsedBrand {
57
57
  locale: string;
58
58
  /** Named font primitives: name → font definition. First entry is the brand default. */
59
59
  fonts?: Record<string, ParsedFontDef>;
60
- /** Token source type: 'figma' | 'inline' */
61
- tokensSource: 'figma' | 'inline';
62
- /** Figma file key (only when tokensSource === 'figma') */
63
- figmaFileKey?: string;
64
- /** Figma collection name (only when tokensSource === 'figma') */
65
- figmaCollection?: string;
66
- /** Path to the runtime tokens file (relative to /src) */
67
- tokensFile: string;
68
- /** Path to app icon asset (relative to /public), used for PWA icon */
69
- appIcon?: string;
70
- /** Path to favicon asset (relative to /public), used for browser tab icon */
71
- favicon?: string;
72
- /** Path to OG image asset (relative to /public), used for social sharing previews */
73
- ogImage?: string;
74
60
  /** Core brand color primitives: name → hex (e.g. primary, secondary, accent1, accent2, white).
75
61
  * Always present when any colors are declared — grouped-syntax brands get a flat projection
76
62
  * generated from all entries, so legacy consumers keep working unchanged. */
@@ -103,11 +89,11 @@ export interface ParsedBrand {
103
89
  themePairs?: Record<string, ThemePair>;
104
90
  /** Non-fatal color findings: rule violations, rejected family pairs, reach config hints. */
105
91
  colorWarnings?: string[];
106
- /** Inline themes: theme name → slot name → resolved hex color (only when tokensSource === 'inline') */
92
+ /** Inline themes: theme name → slot name → resolved hex color */
107
93
  themes?: Record<string, Record<string, string>>;
108
94
  /** Print annotations from inline # comments: slot name → annotation text */
109
95
  themeDescriptions?: Record<string, string>;
110
- /** Spacing scale (only when tokensSource === 'inline') */
96
+ /** Spacing scale */
111
97
  spacing?: { unit: string; xs: number; s: number; m: number; l: number; xl: number };
112
98
  /** Divider style definitions: name → divider config */
113
99
  dividers?: Record<string, ParsedDividerDef>;
@@ -450,10 +436,7 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
450
436
  return { slot, value, line: lineNum };
451
437
  };
452
438
 
453
- const result: Partial<ParsedBrand> = {
454
- tokensFile: 'tokens.json',
455
- tokensSource: 'figma',
456
- };
439
+ const result: Partial<ParsedBrand> = {};
457
440
 
458
441
  type ActiveBlock = 'none' | 'brand-colors' | 'state-colors' | 'color-bounds' | 'color-adherence' | 'color-rules' | 'tones' | 'levels' | 'color-families' | 'theme' | 'spacing' | 'divider' | 'typography' | 'font' | 'logo' | 'photo' | 'video' | 'illustration' | 'label' | 'cta' | 'icons' | 'counter' | 'framing' | 'camera' | 'photo-look' | 'windows';
459
442
  let activeBlock: ActiveBlock = 'none';
@@ -1164,19 +1147,6 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
1164
1147
  case 'name': result.name = value; break;
1165
1148
  case 'language': result.language = value; break;
1166
1149
  case 'locale': result.locale = value; break;
1167
- case 'tokens':
1168
- if (value === 'figma' || value === 'inline') {
1169
- result.tokensSource = value;
1170
- } else {
1171
- console.warn(`${fileName}:${lineNum}: unknown tokens source "${value}", defaulting to "figma"`);
1172
- }
1173
- break;
1174
- case 'figma-file-key': result.figmaFileKey = value; break;
1175
- case 'figma-collection': result.figmaCollection = value; break;
1176
- case 'tokens-file': result.tokensFile = value; break;
1177
- case 'app-icon': result.appIcon = value; break;
1178
- case 'favicon': result.favicon = value; break;
1179
- case 'og-image': result.ogImage = value; break;
1180
1150
  case 'spacing':
1181
1151
  activeBlock = 'spacing';
1182
1152
  break;
@@ -40,6 +40,8 @@ export interface ParsedStoryPage {
40
40
  composition?: string;
41
41
  /** Illustration ids; implementations pick one per story date, the same one every time for that date. */
42
42
  illustrations?: string[];
43
+ /** Photo id; overrides the purpose's `photo:` default for this page. */
44
+ photo?: string;
43
45
  /** Page topic: what this page adds to the story. */
44
46
  topic?: string;
45
47
  /** Fixed slot texts, may contain `{variable}`s. A slot the page doesn't name is open. */
@@ -192,10 +194,11 @@ export function parseStoryFile(content: string, fileName: string): ParsedStory {
192
194
  // Page lines (2-space)
193
195
  if (currentPage) {
194
196
  if (indent !== 2) throw new Error(`${fileName}:${lineNum}: unexpected indent in page (page keys use 2 spaces)`);
195
- const read = readKey(line, lineNum, currentPage, ['purpose', 'composition', 'illustration'], true);
197
+ const read = readKey(line, lineNum, currentPage, ['purpose', 'composition', 'illustration', 'photo'], true);
196
198
  if (read && 'multi' in read) { multiKey = read.multi; multiIndent = 4; }
197
199
  else if (read?.key === 'purpose') currentPage.purpose = read.value;
198
200
  else if (read?.key === 'illustration') currentPage.illustrations = read.value.split(',').map(id => id.trim()).filter(Boolean);
201
+ else if (read?.key === 'photo') currentPage.photo = read.value;
199
202
  else if (read) currentPage.composition = read.value;
200
203
  continue;
201
204
  }
@@ -281,7 +284,7 @@ export function parseStoryFile(content: string, fileName: string): ParsedStory {
281
284
  * empty when the story fits. Variables count at their longest form for `maxLength`. */
282
285
  export function checkStory(
283
286
  story: ParsedStory,
284
- brand: { purposes: ParsedPurpose[]; compositions: string[]; formats: string[]; illustrations?: string[] },
287
+ brand: { purposes: ParsedPurpose[]; compositions: string[]; formats: string[]; illustrations?: string[]; photos?: string[] },
285
288
  fileName: string,
286
289
  ): string[] {
287
290
  const problems: string[] = [];
@@ -314,6 +317,7 @@ export function checkStory(
314
317
  for (const id of page.illustrations ?? []) {
315
318
  if (brand.illustrations && !brand.illustrations.includes(id)) say(`${where}: illustration "${id}" does not exist`);
316
319
  }
320
+ if (page.photo && brand.photos && !brand.photos.includes(page.photo)) say(`${where}: photo "${page.photo}" does not exist`);
317
321
  checkTexts(where, purpose, page.texts);
318
322
  });
319
323
  story.examples.forEach((ex, i) => {
@@ -6,7 +6,6 @@
6
6
  { "include": "#comment" },
7
7
  { "include": "#id-field" },
8
8
  { "include": "#name-field" },
9
- { "include": "#tokens-enum" },
10
9
  { "include": "#brand-colors-header" },
11
10
  { "include": "#state-colors-header" },
12
11
  { "include": "#color-bounds-header" },
@@ -80,13 +79,6 @@
80
79
  "2": { "name": "string.unquoted.brand-intent-brand" }
81
80
  }
82
81
  },
83
- "tokens-enum": {
84
- "match": "^(tokens:)\\s+(figma|inline)",
85
- "captures": {
86
- "1": { "name": "keyword.control.brand-intent-brand" },
87
- "2": { "name": "entity.name.tag.brand-intent-brand" }
88
- }
89
- },
90
82
  "brand-colors-header": {
91
83
  "match": "^(brand-colors)\\s*$",
92
84
  "captures": {
@@ -535,7 +527,7 @@
535
527
  }
536
528
  },
537
529
  "string-field": {
538
- "match": "^(language|locale|app-icon|favicon|og-image|figma-file-key|figma-collection|tokens-file|spacing):?\\s*(.+)?",
530
+ "match": "^(language|locale|spacing):?\\s*(.+)?",
539
531
  "captures": {
540
532
  "1": { "name": "support.type.property-name.brand-intent-brand" },
541
533
  "2": { "name": "string.unquoted.brand-intent-brand" }
@@ -543,7 +535,7 @@
543
535
  },
544
536
  "keyword-partial": {
545
537
  "comment": "Fallback: color the keyword token on incomplete lines (while typing)",
546
- "match": "^(id:|name:|language:|locale:|app-icon:|favicon:|og-image:|tokens:|figma-file-key:|figma-collection:|tokens-file:|spacing:|label:|cta:|color-mode:|color-strategy:|color-adherence:|brand-colors|state-colors|color-bounds|color-rules|color-families|theme|divider|font|typography|logo|photo|video|illustration|icons|counter|framing|windows)(?=\\s|$)",
538
+ "match": "^(id:|name:|language:|locale:|spacing:|label:|cta:|color-mode:|color-strategy:|color-adherence:|brand-colors|state-colors|color-bounds|color-rules|color-families|theme|divider|font|typography|logo|photo|video|illustration|icons|counter|framing|windows)(?=\\s|$)",
547
539
  "captures": {
548
540
  "1": { "name": "keyword.control.brand-intent-brand" }
549
541
  }
@@ -37,7 +37,7 @@
37
37
  }
38
38
  },
39
39
  "reference": {
40
- "match": "^\\s+(purpose|composition|illustration)(:)\\s+([\\w:, -]+)",
40
+ "match": "^\\s+(purpose|composition|illustration|photo)(:)\\s+([\\w:, -]+)",
41
41
  "captures": {
42
42
  "1": { "name": "support.type.property-name.brand-intent-story" },
43
43
  "2": { "name": "punctuation.separator.brand-intent-story" },
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  {
69
- "match": "^\\s+(purpose|composition|illustration|topic|primary|secondary|detail|meta|cta|label)(?=:|\\s|$)",
69
+ "match": "^\\s+(purpose|composition|illustration|photo|topic|primary|secondary|detail|meta|cta|label)(?=:|\\s|$)",
70
70
  "captures": {
71
71
  "1": { "name": "support.type.property-name.brand-intent-story" }
72
72
  }