brand-intent 0.3.2 → 0.4.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.3.2",
3
+ "version": "0.4.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",
@@ -22,6 +22,26 @@ export interface ParsedImageRegion {
22
22
  colEnd?: number; // 0-based column (undefined = full width)
23
23
  }
24
24
 
25
+ /** Anchor keywords for illustration placement. */
26
+ export type IllustrationAnchor =
27
+ | 'top-left' | 'top-center' | 'top-right'
28
+ | 'center-left' | 'center' | 'center-right'
29
+ | 'bottom-left' | 'bottom-center' | 'bottom-right';
30
+
31
+ /** Parsed illustration placement from `illustration:` line. */
32
+ export interface ParsedIllustration {
33
+ anchor: IllustrationAnchor;
34
+ fit: 'width' | 'height';
35
+ /** Scale as fraction 0–1 (e.g. 0.9 = 90% of canvas dimension). */
36
+ scale: number;
37
+ }
38
+
39
+ const ILLUSTRATION_ANCHORS = new Set<string>([
40
+ 'top-left', 'top-center', 'top-right',
41
+ 'center-left', 'center', 'center-right',
42
+ 'bottom-left', 'bottom-center', 'bottom-right',
43
+ ]);
44
+
25
45
  export interface ParsedComposition {
26
46
  /** Programmatic ID (from `id:` line, or fallback from first `#` line). */
27
47
  id: string;
@@ -44,6 +64,8 @@ export interface ParsedComposition {
44
64
  sticky: ParsedStickyRegion[];
45
65
  /** Flow regions — content-driven flow areas. */
46
66
  flow: ParsedFlowRegion | null;
67
+ /** Illustration placement (grid mode only for now). */
68
+ illustration?: ParsedIllustration;
47
69
  }
48
70
 
49
71
  /** A sticky (repetition) region — composed elements that don't participate in flow. */
@@ -179,6 +201,7 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
179
201
  const slots: ParsedSlot[] = [];
180
202
  const sticky: ParsedStickyRegion[] = [];
181
203
  let flow: ParsedFlowRegion | null = null;
204
+ let illustration: ParsedIllustration | undefined = undefined;
182
205
 
183
206
  /** Parse a fraction string like "2/3" → 0.667. */
184
207
  function parseFraction(s: string, ctx: string): number {
@@ -260,6 +283,23 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
260
283
  continue;
261
284
  }
262
285
 
286
+ // Illustration placement: "illustration: bottom-center fit-width 90%"
287
+ if (line.startsWith('illustration:')) {
288
+ const parts = line.slice(13).trim().split(/\s+/);
289
+ if (parts.length < 3) throw new Error(`${fileName}:${lineNum}: invalid illustration line — expected "illustration: <anchor> <fit-width|fit-height> <N%>"`);
290
+ const anchor = parts[0];
291
+ if (!ILLUSTRATION_ANCHORS.has(anchor)) throw new Error(`${fileName}:${lineNum}: invalid illustration anchor "${anchor}" (expected ${[...ILLUSTRATION_ANCHORS].join(', ')})`);
292
+ const fitStr = parts[1];
293
+ if (fitStr !== 'fit-width' && fitStr !== 'fit-height') throw new Error(`${fileName}:${lineNum}: invalid illustration fit "${fitStr}" (expected fit-width or fit-height)`);
294
+ const scaleStr = parts[2];
295
+ const scaleMatch = scaleStr.match(/^(\d+(?:\.\d+)?)%$/);
296
+ if (!scaleMatch) throw new Error(`${fileName}:${lineNum}: invalid illustration scale "${scaleStr}" (expected e.g. "90%")`);
297
+ const scale = parseFloat(scaleMatch[1]) / 100;
298
+ if (scale <= 0 || scale > 2) throw new Error(`${fileName}:${lineNum}: illustration scale out of range (expected 1%–200%)`);
299
+ illustration = { anchor: anchor as IllustrationAnchor, fit: fitStr === 'fit-width' ? 'width' : 'height', scale };
300
+ continue;
301
+ }
302
+
263
303
  // grid: is no longer used in layout files (format owns the grid). Silently skip.
264
304
  if (line.startsWith('grid:')) {
265
305
  console.warn(`${fileName}:${lineNum}: "grid:" in layout files is no longer needed — format defines the grid`);
@@ -512,5 +552,5 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
512
552
  // Validate required fields
513
553
  if (!image) image = 'full'; // default to full-bleed image
514
554
  if (!logo && !logoNone && !sticky.length) throw new Error(`${fileName}: missing "logo:" line (or use sticky: block)`);
515
- return { id, name, description, mode, image, imageExplicit, logo, logoNone: logoNone || undefined, icon, slots, sticky, flow };
555
+ return { id, name, description, mode, image, imageExplicit, logo, logoNone: logoNone || undefined, icon, slots, sticky, flow, illustration };
516
556
  }
package/parser/index.ts CHANGED
@@ -21,6 +21,8 @@ export type {
21
21
  ParsedStickyRegion,
22
22
  ParsedFlowItem,
23
23
  ParsedImageRegion,
24
+ ParsedIllustration,
25
+ IllustrationAnchor,
24
26
  GridYKeyword,
25
27
  } from './compositionParser';
26
28