brand-intent 0.16.0 → 0.17.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
package/parser/brandParser.ts
CHANGED
|
@@ -116,6 +116,27 @@ export interface ParsedBrand {
|
|
|
116
116
|
framings?: ParsedFramingDef[];
|
|
117
117
|
/** Camera defaults (`camera` block). Absent → rear camera, no grid. */
|
|
118
118
|
camera?: ParsedCameraDef;
|
|
119
|
+
/** Photo treatment (`photo-look` block). Absent → photos stay as taken. */
|
|
120
|
+
photoLook?: ParsedPhotoLookDef;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type PhotoLookKey = 'saturation' | 'contrast' | 'warmth' | 'fade' | 'mono' | 'duotone';
|
|
124
|
+
|
|
125
|
+
export interface ParsedPhotoLookDef {
|
|
126
|
+
/** -100..100, 0 = unchanged. */
|
|
127
|
+
saturation: number;
|
|
128
|
+
/** -100..100, 0 = unchanged. */
|
|
129
|
+
contrast: number;
|
|
130
|
+
/** -100 (cool) .. 100 (warm), 0 = unchanged. */
|
|
131
|
+
warmth: number;
|
|
132
|
+
/** Lifted black point, 0..100. */
|
|
133
|
+
fade: number;
|
|
134
|
+
/** Black-and-white. */
|
|
135
|
+
mono: boolean;
|
|
136
|
+
/** Map luminance onto two colors (resolved hex), shadows → highlights. Excludes `mono`. */
|
|
137
|
+
duotone?: { shadow: string; highlight: string };
|
|
138
|
+
/** Keys the person cannot change. All others are starting values. */
|
|
139
|
+
fixed: PhotoLookKey[];
|
|
119
140
|
}
|
|
120
141
|
|
|
121
142
|
export interface ParsedCameraDef {
|
|
@@ -380,7 +401,7 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
380
401
|
tokensSource: 'figma',
|
|
381
402
|
};
|
|
382
403
|
|
|
383
|
-
type ActiveBlock = 'none' | 'brand-colors' | 'state-colors' | 'color-bounds' | 'color-adherence' | 'color-rules' | 'color-families' | 'theme' | 'spacing' | 'divider' | 'typography' | 'font' | 'logo' | 'photo' | 'video' | 'illustration' | 'label' | 'cta' | 'icons' | 'counter' | 'framing' | 'camera';
|
|
404
|
+
type ActiveBlock = 'none' | 'brand-colors' | 'state-colors' | 'color-bounds' | 'color-adherence' | 'color-rules' | 'color-families' | 'theme' | 'spacing' | 'divider' | 'typography' | 'font' | 'logo' | 'photo' | 'video' | 'illustration' | 'label' | 'cta' | 'icons' | 'counter' | 'framing' | 'camera' | 'photo-look';
|
|
384
405
|
let activeBlock: ActiveBlock = 'none';
|
|
385
406
|
let currentThemeName = '';
|
|
386
407
|
let currentDividerName = '';
|
|
@@ -429,6 +450,8 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
429
450
|
let counterDeclared = false;
|
|
430
451
|
const cameraRaw: Record<string, { value: string; line: number }> = {};
|
|
431
452
|
let cameraDeclared = false;
|
|
453
|
+
const photoLookRaw: Record<string, { value: string; line: number }> = {};
|
|
454
|
+
let photoLookDeclared = false;
|
|
432
455
|
const framingsRaw: Array<{ id: string; line: number; props: Record<string, { value: string; line: number }> }> = [];
|
|
433
456
|
|
|
434
457
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -817,6 +840,18 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
817
840
|
cameraRaw[entry.slice(0, colonIdx).trim()] = { value: entry.slice(colonIdx + 1).trim(), line: lineNum };
|
|
818
841
|
continue;
|
|
819
842
|
}
|
|
843
|
+
if (activeBlock === 'photo-look') {
|
|
844
|
+
const trimmed = raw.trim();
|
|
845
|
+
if (trimmed.startsWith('#')) continue;
|
|
846
|
+
// `# …` is a comment, but `#RGB` / `#RRGGBB` is a duotone color
|
|
847
|
+
const commentIdx = trimmed.search(/\s+#(?![0-9a-f]{3}(?:[0-9a-f]{3}(?:[0-9a-f]{2})?)?(?:\s|$))/i);
|
|
848
|
+
const entry = commentIdx !== -1 ? trimmed.slice(0, commentIdx) : trimmed;
|
|
849
|
+
const colonIdx = entry.indexOf(':');
|
|
850
|
+
// Bare flag (`mono`) or KEY: VALUE
|
|
851
|
+
if (colonIdx === -1) photoLookRaw[entry.trim()] = { value: '', line: lineNum };
|
|
852
|
+
else photoLookRaw[entry.slice(0, colonIdx).trim()] = { value: entry.slice(colonIdx + 1).trim(), line: lineNum };
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
820
855
|
if (activeBlock === 'framing') {
|
|
821
856
|
const trimmed = raw.trim();
|
|
822
857
|
if (trimmed.startsWith('#')) continue;
|
|
@@ -897,6 +932,14 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
897
932
|
continue;
|
|
898
933
|
}
|
|
899
934
|
|
|
935
|
+
// photo-look — photo treatment (saturation)
|
|
936
|
+
if (raw.trim() === 'photo-look') {
|
|
937
|
+
if (photoLookDeclared) throw new Error(`${fileName}:${lineNum}: duplicate photo-look block`);
|
|
938
|
+
photoLookDeclared = true;
|
|
939
|
+
activeBlock = 'photo-look';
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
|
|
900
943
|
// framing <id> — photo framing module (horizon position)
|
|
901
944
|
const framingMatch = raw.match(/^framing\s+(\S+)\s*$/);
|
|
902
945
|
if (framingMatch) {
|
|
@@ -1582,6 +1625,60 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
1582
1625
|
result.camera = camera;
|
|
1583
1626
|
}
|
|
1584
1627
|
|
|
1628
|
+
// Finalise photo treatment
|
|
1629
|
+
if (photoLookDeclared) {
|
|
1630
|
+
const keys: PhotoLookKey[] = ['saturation', 'contrast', 'warmth', 'fade', 'mono', 'duotone'];
|
|
1631
|
+
for (const [key, { line }] of Object.entries(photoLookRaw)) {
|
|
1632
|
+
if (key !== 'fixed' && !keys.includes(key as PhotoLookKey)) throw new Error(`${fileName}:${line}: unknown photo-look property "${key}"`);
|
|
1633
|
+
}
|
|
1634
|
+
const int = (key: string, min: number, max: number): number => {
|
|
1635
|
+
const p = photoLookRaw[key];
|
|
1636
|
+
if (!p) return 0;
|
|
1637
|
+
const n = Number(p.value);
|
|
1638
|
+
if (!/^[+-]?\d+$/.test(p.value) || n < min || n > max) {
|
|
1639
|
+
throw new Error(`${fileName}:${p.line}: photo-look ${key} must be an integer from ${min} to ${max}, got "${p.value}"`);
|
|
1640
|
+
}
|
|
1641
|
+
return n;
|
|
1642
|
+
};
|
|
1643
|
+
const look: ParsedPhotoLookDef = {
|
|
1644
|
+
saturation: int('saturation', -100, 100),
|
|
1645
|
+
contrast: int('contrast', -100, 100),
|
|
1646
|
+
warmth: int('warmth', -100, 100),
|
|
1647
|
+
fade: int('fade', 0, 100),
|
|
1648
|
+
mono: false,
|
|
1649
|
+
fixed: [],
|
|
1650
|
+
};
|
|
1651
|
+
const mono = photoLookRaw['mono'];
|
|
1652
|
+
if (mono) {
|
|
1653
|
+
if (mono.value !== '') throw new Error(`${fileName}:${mono.line}: photo-look mono is a flag and takes no value`);
|
|
1654
|
+
look.mono = true;
|
|
1655
|
+
}
|
|
1656
|
+
const duo = photoLookRaw['duotone'];
|
|
1657
|
+
if (duo) {
|
|
1658
|
+
if (mono) throw new Error(`${fileName}:${duo.line}: photo-look cannot be both mono and duotone`);
|
|
1659
|
+
const refs = duo.value.split(/\s+/);
|
|
1660
|
+
if (refs.length !== 2) throw new Error(`${fileName}:${duo.line}: photo-look duotone must be "SHADOW HIGHLIGHT", got "${duo.value}"`);
|
|
1661
|
+
const [shadow, highlight] = refs.map(ref => {
|
|
1662
|
+
if (ref.startsWith('$') && !brandColors[ref.slice(1)]) {
|
|
1663
|
+
throw new Error(`${fileName}:${duo.line}: photo-look duotone references unknown brand color "${ref}"`);
|
|
1664
|
+
}
|
|
1665
|
+
const hex = normalizeHex(ref.startsWith('$') ? brandColors[ref.slice(1)] : ref);
|
|
1666
|
+
if (!hex) throw new Error(`${fileName}:${duo.line}: photo-look duotone colors must be $ref or #HEX, got "${ref}"`);
|
|
1667
|
+
return hex;
|
|
1668
|
+
});
|
|
1669
|
+
look.duotone = { shadow, highlight };
|
|
1670
|
+
}
|
|
1671
|
+
const fixed = photoLookRaw['fixed'];
|
|
1672
|
+
if (fixed) {
|
|
1673
|
+
for (const key of fixed.value.split(/\s+/)) {
|
|
1674
|
+
if (!keys.includes(key as PhotoLookKey)) throw new Error(`${fileName}:${fixed.line}: photo-look fixed lists unknown key "${key}"`);
|
|
1675
|
+
if (!photoLookRaw[key]) throw new Error(`${fileName}:${fixed.line}: photo-look fixed lists "${key}", which is not set`);
|
|
1676
|
+
look.fixed.push(key as PhotoLookKey);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
result.photoLook = look;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1585
1682
|
// Finalise framing modules
|
|
1586
1683
|
if (framingsRaw.length > 0) {
|
|
1587
1684
|
const known = new Set(['label', 'horizon', 'description']);
|
package/parser/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ export {
|
|
|
24
24
|
DEFAULT_COLOR_RULES,
|
|
25
25
|
} from './colorSystem';
|
|
26
26
|
export type { ReachConfig } from './colorSystem';
|
|
27
|
-
export type { ParsedBrand, ParsedFontDef, ParsedTypographyDef, ParsedDividerDef, BrandColorPrintSpec, ParsedAssetDef, ParsedBadgeStyle, LogoAlign, ParsedCounterDef, CounterFormat, CounterShape, ParsedFramingDef, ParsedCameraDef } from './brandParser';
|
|
27
|
+
export type { ParsedBrand, ParsedFontDef, ParsedTypographyDef, ParsedDividerDef, BrandColorPrintSpec, ParsedAssetDef, ParsedBadgeStyle, LogoAlign, ParsedCounterDef, CounterFormat, CounterShape, ParsedFramingDef, ParsedCameraDef, ParsedPhotoLookDef, PhotoLookKey } from './brandParser';
|
|
28
28
|
|
|
29
29
|
export { parseIconRef, parseIconSource, resolveIconRef, parseListItem, phosphorName, PHOSPHOR_WEIGHTS } from './icons';
|
|
30
30
|
export type { IconRef, IconSource, ParsedIconDef, ResolvedIcon, PhosphorWeight } from './icons';
|
|
@@ -399,8 +399,8 @@
|
|
|
399
399
|
}
|
|
400
400
|
},
|
|
401
401
|
"icons-counter-header": {
|
|
402
|
-
"comment": "Bare `icons` / `counter` block headers.",
|
|
403
|
-
"match": "^(icons|counter)\\s*$",
|
|
402
|
+
"comment": "Bare `icons` / `counter` / `camera` / `photo-look` block headers.",
|
|
403
|
+
"match": "^(icons|counter|camera|photo-look)\\s*$",
|
|
404
404
|
"captures": {
|
|
405
405
|
"1": { "name": "keyword.control.brand-intent-brand" }
|
|
406
406
|
}
|