@loworbitstudio/visor-theme-engine 0.15.1 → 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/dist/adapters/index.d.ts +1 -1
- package/dist/fowt.d.ts +46 -1
- package/dist/fowt.js +22 -1
- package/dist/index.d.ts +551 -4
- package/dist/index.js +649 -1
- package/dist/{types-zug1_eLX.d.ts → types-DqsWwFVZ.d.ts} +232 -1
- package/package.json +1 -1
- package/src/visor-theme.schema.json +225 -1
package/dist/index.js
CHANGED
|
@@ -202,6 +202,397 @@ function validateFontCoverage(css) {
|
|
|
202
202
|
return { errors };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// src/brand-strategy/types.ts
|
|
206
|
+
var DEFAULT_BRAND_STRATEGY_TONE_STATES = [
|
|
207
|
+
"error",
|
|
208
|
+
"success",
|
|
209
|
+
"warning",
|
|
210
|
+
"info",
|
|
211
|
+
"empty",
|
|
212
|
+
"loading",
|
|
213
|
+
"validation-warning"
|
|
214
|
+
];
|
|
215
|
+
var DEFAULT_BRAND_STRATEGY_SURFACES = [
|
|
216
|
+
"manifest",
|
|
217
|
+
"cli",
|
|
218
|
+
"component-metadata"
|
|
219
|
+
];
|
|
220
|
+
var BRAND_VISIBILITIES = ["public", "private"];
|
|
221
|
+
var GOVERNS_WILDCARD = "*";
|
|
222
|
+
|
|
223
|
+
// src/brand-strategy/validate.ts
|
|
224
|
+
function error(code, message, path) {
|
|
225
|
+
const issue2 = { severity: "error", code, message };
|
|
226
|
+
if (path !== void 0) issue2.path = path;
|
|
227
|
+
return issue2;
|
|
228
|
+
}
|
|
229
|
+
function isObject(value) {
|
|
230
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
231
|
+
}
|
|
232
|
+
function isNonEmptyString(value) {
|
|
233
|
+
return typeof value === "string" && value.length > 0;
|
|
234
|
+
}
|
|
235
|
+
function isStringArray(value) {
|
|
236
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
237
|
+
}
|
|
238
|
+
function normalizeTokenRef(ref) {
|
|
239
|
+
return ref.startsWith("--") ? ref.slice(2) : ref;
|
|
240
|
+
}
|
|
241
|
+
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
242
|
+
"positioning",
|
|
243
|
+
"essence",
|
|
244
|
+
"personality",
|
|
245
|
+
"archetype",
|
|
246
|
+
"pillars",
|
|
247
|
+
"voice",
|
|
248
|
+
"tone",
|
|
249
|
+
"lexicon",
|
|
250
|
+
"core",
|
|
251
|
+
"visibility",
|
|
252
|
+
// Phase 2 wave-1 (VI-541) — all optional.
|
|
253
|
+
"messaging",
|
|
254
|
+
"taglines",
|
|
255
|
+
"boilerplate",
|
|
256
|
+
"colorUsage",
|
|
257
|
+
"accessibility"
|
|
258
|
+
]);
|
|
259
|
+
var GOVERNS_KEYS = /* @__PURE__ */ new Set(["tokens", "components", "surfaces"]);
|
|
260
|
+
function checkBrandStrategyStructure(block) {
|
|
261
|
+
const errors = [];
|
|
262
|
+
if (!isObject(block)) {
|
|
263
|
+
errors.push(error("BRAND_STRATEGY_STRUCTURAL", "'brand-strategy' must be an object", "brand-strategy"));
|
|
264
|
+
return errors;
|
|
265
|
+
}
|
|
266
|
+
for (const key of Object.keys(block)) {
|
|
267
|
+
if (!TOP_LEVEL_KEYS.has(key)) {
|
|
268
|
+
errors.push(
|
|
269
|
+
error(
|
|
270
|
+
"BRAND_STRATEGY_UNKNOWN_KEY",
|
|
271
|
+
`Unknown key 'brand-strategy.${key}'. Valid keys: ${[...TOP_LEVEL_KEYS].join(", ")}`,
|
|
272
|
+
`brand-strategy.${key}`
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const positioning = block.positioning;
|
|
278
|
+
if (!isObject(positioning)) {
|
|
279
|
+
errors.push(error("BRAND_STRATEGY_POSITIONING", "'brand-strategy.positioning' is required and must be an object", "brand-strategy.positioning"));
|
|
280
|
+
} else {
|
|
281
|
+
for (const field of ["onliness", "category", "differentiation"]) {
|
|
282
|
+
if (!isNonEmptyString(positioning[field])) {
|
|
283
|
+
errors.push(error("BRAND_STRATEGY_POSITIONING", `'brand-strategy.positioning.${field}' is required and must be a non-empty string`, `brand-strategy.positioning.${field}`));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (!isStringArray(block.essence) || block.essence.length === 0) {
|
|
288
|
+
errors.push(error("BRAND_STRATEGY_ESSENCE", "'brand-strategy.essence' is required and must be a non-empty array of strings", "brand-strategy.essence"));
|
|
289
|
+
}
|
|
290
|
+
if (!Array.isArray(block.personality) || block.personality.length === 0) {
|
|
291
|
+
errors.push(error("BRAND_STRATEGY_PERSONALITY", "'brand-strategy.personality' is required and must be a non-empty array", "brand-strategy.personality"));
|
|
292
|
+
} else {
|
|
293
|
+
block.personality.forEach((trait, i) => {
|
|
294
|
+
if (!isObject(trait) || !isNonEmptyString(trait.trait) || !isNonEmptyString(trait.not)) {
|
|
295
|
+
errors.push(error("BRAND_STRATEGY_PERSONALITY", `'brand-strategy.personality[${i}]' must have non-empty 'trait' and 'not' strings`, `brand-strategy.personality[${i}]`));
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const archetype = block.archetype;
|
|
300
|
+
if (!isObject(archetype)) {
|
|
301
|
+
errors.push(error("BRAND_STRATEGY_ARCHETYPE", "'brand-strategy.archetype' is required and must be an object", "brand-strategy.archetype"));
|
|
302
|
+
} else {
|
|
303
|
+
if (!isNonEmptyString(archetype.primary)) {
|
|
304
|
+
errors.push(error("BRAND_STRATEGY_ARCHETYPE", "'brand-strategy.archetype.primary' is required and must be a non-empty string", "brand-strategy.archetype.primary"));
|
|
305
|
+
}
|
|
306
|
+
for (const field of ["secondary", "tertiary"]) {
|
|
307
|
+
if (archetype[field] !== void 0 && !isNonEmptyString(archetype[field])) {
|
|
308
|
+
errors.push(error("BRAND_STRATEGY_ARCHETYPE", `'brand-strategy.archetype.${field}' must be a non-empty string when present`, `brand-strategy.archetype.${field}`));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!Array.isArray(block.pillars) || block.pillars.length === 0) {
|
|
313
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", "'brand-strategy.pillars' is required and must be a non-empty array", "brand-strategy.pillars"));
|
|
314
|
+
} else {
|
|
315
|
+
block.pillars.forEach((pillar, i) => {
|
|
316
|
+
errors.push(...checkPillarStructure(pillar, i));
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
const voice = block.voice;
|
|
320
|
+
if (!isObject(voice) || !Array.isArray(voice.traits) || voice.traits.length === 0) {
|
|
321
|
+
errors.push(error("BRAND_STRATEGY_VOICE", "'brand-strategy.voice' is required and must have a non-empty 'traits' array", "brand-strategy.voice"));
|
|
322
|
+
} else {
|
|
323
|
+
voice.traits.forEach((trait, i) => {
|
|
324
|
+
if (!isObject(trait) || !isNonEmptyString(trait.name) || !isNonEmptyString(trait.do) || !isNonEmptyString(trait.dont)) {
|
|
325
|
+
errors.push(error("BRAND_STRATEGY_VOICE", `'brand-strategy.voice.traits[${i}]' must have non-empty 'name', 'do', and 'dont' strings`, `brand-strategy.voice.traits[${i}]`));
|
|
326
|
+
} else if (trait.example !== void 0 && !isNonEmptyString(trait.example)) {
|
|
327
|
+
errors.push(error("BRAND_STRATEGY_VOICE", `'brand-strategy.voice.traits[${i}].example' must be a non-empty string when present`, `brand-strategy.voice.traits[${i}].example`));
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const tone = block.tone;
|
|
332
|
+
if (!isObject(tone) || Object.keys(tone).length === 0) {
|
|
333
|
+
errors.push(error("BRAND_STRATEGY_TONE", "'brand-strategy.tone' is required and must be a non-empty object keyed by UI state", "brand-strategy.tone"));
|
|
334
|
+
} else {
|
|
335
|
+
for (const [state, entry] of Object.entries(tone)) {
|
|
336
|
+
if (!isObject(entry) || !isNonEmptyString(entry.feeling) || !isNonEmptyString(entry.example)) {
|
|
337
|
+
errors.push(error("BRAND_STRATEGY_TONE", `'brand-strategy.tone.${state}' must have non-empty 'feeling' and 'example' strings`, `brand-strategy.tone.${state}`));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (!Array.isArray(block.lexicon) || block.lexicon.length === 0) {
|
|
342
|
+
errors.push(error("BRAND_STRATEGY_LEXICON", "'brand-strategy.lexicon' is required and must be a non-empty array", "brand-strategy.lexicon"));
|
|
343
|
+
} else {
|
|
344
|
+
block.lexicon.forEach((entry, i) => {
|
|
345
|
+
if (!isObject(entry) || !isNonEmptyString(entry.use) || !isNonEmptyString(entry.avoid)) {
|
|
346
|
+
errors.push(error("BRAND_STRATEGY_LEXICON", `'brand-strategy.lexicon[${i}]' must have non-empty 'use' and 'avoid' strings`, `brand-strategy.lexicon[${i}]`));
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (!isStringArray(block.core) || block.core.length === 0) {
|
|
351
|
+
errors.push(error("BRAND_STRATEGY_CORE", "'brand-strategy.core' is required and must be a non-empty array of strings", "brand-strategy.core"));
|
|
352
|
+
}
|
|
353
|
+
if (!isNonEmptyString(block.visibility) || !BRAND_VISIBILITIES.includes(block.visibility)) {
|
|
354
|
+
errors.push(error("BRAND_STRATEGY_VISIBILITY", `'brand-strategy.visibility' is required and must be one of: ${BRAND_VISIBILITIES.join(", ")}`, "brand-strategy.visibility"));
|
|
355
|
+
}
|
|
356
|
+
errors.push(...checkPhase2Structure(block));
|
|
357
|
+
return errors;
|
|
358
|
+
}
|
|
359
|
+
function checkPhase2Structure(block) {
|
|
360
|
+
const errors = [];
|
|
361
|
+
if (block.messaging !== void 0) {
|
|
362
|
+
const messaging = block.messaging;
|
|
363
|
+
if (!isObject(messaging) || !isNonEmptyString(messaging.roof)) {
|
|
364
|
+
errors.push(error("BRAND_STRATEGY_MESSAGING", "'brand-strategy.messaging' must be an object with a non-empty 'roof' string", "brand-strategy.messaging"));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (block.taglines !== void 0) {
|
|
368
|
+
if (!isStringArray(block.taglines) || block.taglines.length === 0) {
|
|
369
|
+
errors.push(error("BRAND_STRATEGY_TAGLINES", "'brand-strategy.taglines' must be a non-empty array of strings when present", "brand-strategy.taglines"));
|
|
370
|
+
} else if (!block.taglines.every(isNonEmptyString)) {
|
|
371
|
+
errors.push(error("BRAND_STRATEGY_TAGLINES", "'brand-strategy.taglines' entries must be non-empty strings", "brand-strategy.taglines"));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (block.boilerplate !== void 0) {
|
|
375
|
+
const boilerplate = block.boilerplate;
|
|
376
|
+
if (!isObject(boilerplate) || !isNonEmptyString(boilerplate.short) || !isNonEmptyString(boilerplate.long)) {
|
|
377
|
+
errors.push(error("BRAND_STRATEGY_BOILERPLATE", "'brand-strategy.boilerplate' must be an object with non-empty 'short' and 'long' strings", "brand-strategy.boilerplate"));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (block.colorUsage !== void 0) {
|
|
381
|
+
const colorUsage = block.colorUsage;
|
|
382
|
+
if (!isObject(colorUsage) || !Array.isArray(colorUsage.pairings) || colorUsage.pairings.length === 0) {
|
|
383
|
+
errors.push(error("BRAND_STRATEGY_COLOR_USAGE", "'brand-strategy.colorUsage' must be an object with a non-empty 'pairings' array", "brand-strategy.colorUsage"));
|
|
384
|
+
} else {
|
|
385
|
+
colorUsage.pairings.forEach((pairing, i) => {
|
|
386
|
+
if (!isObject(pairing) || !isNonEmptyString(pairing.use) || !isNonEmptyString(pairing.with) || !isNonEmptyString(pairing.rule)) {
|
|
387
|
+
errors.push(error("BRAND_STRATEGY_COLOR_USAGE", `'brand-strategy.colorUsage.pairings[${i}]' must have non-empty 'use', 'with', and 'rule' strings`, `brand-strategy.colorUsage.pairings[${i}]`));
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (block.accessibility !== void 0) {
|
|
393
|
+
const accessibility = block.accessibility;
|
|
394
|
+
if (!isObject(accessibility)) {
|
|
395
|
+
errors.push(error("BRAND_STRATEGY_ACCESSIBILITY", "'brand-strategy.accessibility' must be an object", "brand-strategy.accessibility"));
|
|
396
|
+
} else {
|
|
397
|
+
if (!isNonEmptyString(accessibility.standard)) {
|
|
398
|
+
errors.push(error("BRAND_STRATEGY_ACCESSIBILITY", "'brand-strategy.accessibility.standard' is required and must be a non-empty string", "brand-strategy.accessibility.standard"));
|
|
399
|
+
}
|
|
400
|
+
if (!isNonEmptyString(accessibility.intent)) {
|
|
401
|
+
errors.push(error("BRAND_STRATEGY_ACCESSIBILITY", "'brand-strategy.accessibility.intent' is required and must be a non-empty string", "brand-strategy.accessibility.intent"));
|
|
402
|
+
}
|
|
403
|
+
if (!Array.isArray(accessibility.contrast) || accessibility.contrast.length === 0) {
|
|
404
|
+
errors.push(error("BRAND_STRATEGY_ACCESSIBILITY", "'brand-strategy.accessibility.contrast' must be a non-empty array", "brand-strategy.accessibility.contrast"));
|
|
405
|
+
} else {
|
|
406
|
+
accessibility.contrast.forEach((target, i) => {
|
|
407
|
+
if (!isObject(target) || !isNonEmptyString(target.context) || !isNonEmptyString(target.ratio)) {
|
|
408
|
+
errors.push(error("BRAND_STRATEGY_ACCESSIBILITY", `'brand-strategy.accessibility.contrast[${i}]' must have non-empty 'context' and 'ratio' strings`, `brand-strategy.accessibility.contrast[${i}]`));
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return errors;
|
|
415
|
+
}
|
|
416
|
+
function checkPillarStructure(pillar, index) {
|
|
417
|
+
const path = `brand-strategy.pillars[${index}]`;
|
|
418
|
+
if (!isObject(pillar)) {
|
|
419
|
+
return [error("BRAND_STRATEGY_PILLARS", `'${path}' must be an object`, path)];
|
|
420
|
+
}
|
|
421
|
+
const errors = [];
|
|
422
|
+
if (!isNonEmptyString(pillar.id)) {
|
|
423
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", `'${path}.id' is required and must be a non-empty string`, `${path}.id`));
|
|
424
|
+
}
|
|
425
|
+
if (!isNonEmptyString(pillar.statement)) {
|
|
426
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", `'${path}.statement' is required and must be a non-empty string`, `${path}.statement`));
|
|
427
|
+
}
|
|
428
|
+
const governs = pillar.governs;
|
|
429
|
+
if (!isObject(governs)) {
|
|
430
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", `'${path}.governs' is required and must be an object`, `${path}.governs`));
|
|
431
|
+
} else {
|
|
432
|
+
for (const key of Object.keys(governs)) {
|
|
433
|
+
if (!GOVERNS_KEYS.has(key)) {
|
|
434
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `Unknown key '${path}.governs.${key}'. Valid keys: ${[...GOVERNS_KEYS].join(", ")}`, `${path}.governs.${key}`));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (const key of GOVERNS_KEYS) {
|
|
438
|
+
const list = governs[key];
|
|
439
|
+
if (list === void 0) continue;
|
|
440
|
+
if (!isStringArray(list)) {
|
|
441
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `'${path}.governs.${key}' must be an array of strings`, `${path}.governs.${key}`));
|
|
442
|
+
} else if (list.length === 0) {
|
|
443
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `'${path}.governs.${key}' must not be empty when present`, `${path}.governs.${key}`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (governs.tokens === void 0 && governs.components === void 0 && governs.surfaces === void 0) {
|
|
447
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `'${path}.governs' must declare at least one of: ${[...GOVERNS_KEYS].join(", ")}`, `${path}.governs`));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (pillar.proof !== void 0) {
|
|
451
|
+
if (!isStringArray(pillar.proof) || pillar.proof.length === 0) {
|
|
452
|
+
errors.push(error("BRAND_STRATEGY_PROOF", `'${path}.proof' must be a non-empty array of strings when present`, `${path}.proof`));
|
|
453
|
+
} else if (!pillar.proof.every(isNonEmptyString)) {
|
|
454
|
+
errors.push(error("BRAND_STRATEGY_PROOF", `'${path}.proof' entries must be non-empty strings`, `${path}.proof`));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return errors;
|
|
458
|
+
}
|
|
459
|
+
function checkBrandStrategyCoherence(strategy, ctx = {}) {
|
|
460
|
+
const errors = [];
|
|
461
|
+
const surfaces = ctx.surfaces ?? new Set(DEFAULT_BRAND_STRATEGY_SURFACES);
|
|
462
|
+
const states = ctx.states ?? new Set(DEFAULT_BRAND_STRATEGY_TONE_STATES);
|
|
463
|
+
strategy.pillars.forEach((pillar, i) => {
|
|
464
|
+
const path = `brand-strategy.pillars[${i}].governs`;
|
|
465
|
+
if (ctx.tokens && pillar.governs.tokens) {
|
|
466
|
+
for (const ref of pillar.governs.tokens) {
|
|
467
|
+
if (ref === GOVERNS_WILDCARD) continue;
|
|
468
|
+
if (!ctx.tokens.has(normalizeTokenRef(ref))) {
|
|
469
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS_TOKEN", `Pillar '${pillar.id}' governs token '${ref}', which is not a known Visor token`, `${path}.tokens`));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (pillar.governs.components) {
|
|
474
|
+
for (const ref of pillar.governs.components) {
|
|
475
|
+
if (ref === GOVERNS_WILDCARD) continue;
|
|
476
|
+
if (ctx.components && !ctx.components.has(ref)) {
|
|
477
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS_COMPONENT", `Pillar '${pillar.id}' governs component '${ref}', which is not a known Visor component`, `${path}.components`));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (pillar.governs.surfaces) {
|
|
482
|
+
for (const ref of pillar.governs.surfaces) {
|
|
483
|
+
if (!surfaces.has(ref)) {
|
|
484
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS_SURFACE", `Pillar '${pillar.id}' governs surface '${ref}', which is not a recognized meta-surface (${[...surfaces].join(", ")})`, `${path}.surfaces`));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
for (const state of Object.keys(strategy.tone)) {
|
|
490
|
+
if (!states.has(state)) {
|
|
491
|
+
errors.push(error("BRAND_STRATEGY_TONE_STATE", `tone key '${state}' does not map to a recognized UI state (${[...states].join(", ")})`, `brand-strategy.tone.${state}`));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return errors;
|
|
495
|
+
}
|
|
496
|
+
function validateBrandStrategy(block, ctx = {}) {
|
|
497
|
+
const errors = checkBrandStrategyStructure(block);
|
|
498
|
+
const warnings = [];
|
|
499
|
+
if (errors.length > 0) {
|
|
500
|
+
return { valid: false, errors, warnings };
|
|
501
|
+
}
|
|
502
|
+
for (const issue2 of checkBrandStrategyCoherence(block, ctx)) {
|
|
503
|
+
(issue2.severity === "error" ? errors : warnings).push(issue2);
|
|
504
|
+
}
|
|
505
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/brand-strategy/serialize.ts
|
|
509
|
+
function compactGoverns(governs) {
|
|
510
|
+
const out = {};
|
|
511
|
+
if (governs.tokens) out.tokens = [...governs.tokens];
|
|
512
|
+
if (governs.components) out.components = [...governs.components];
|
|
513
|
+
if (governs.surfaces) out.surfaces = [...governs.surfaces];
|
|
514
|
+
return out;
|
|
515
|
+
}
|
|
516
|
+
function serializePillar(p) {
|
|
517
|
+
const pillar = {
|
|
518
|
+
id: p.id,
|
|
519
|
+
statement: p.statement,
|
|
520
|
+
governs: compactGoverns(p.governs)
|
|
521
|
+
};
|
|
522
|
+
if (p.proof !== void 0) pillar.proof = [...p.proof];
|
|
523
|
+
return pillar;
|
|
524
|
+
}
|
|
525
|
+
function serializeBrandStrategy(strategy) {
|
|
526
|
+
if (strategy.visibility === "private") {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
const archetype = { primary: strategy.archetype.primary };
|
|
530
|
+
if (strategy.archetype.secondary !== void 0) archetype.secondary = strategy.archetype.secondary;
|
|
531
|
+
if (strategy.archetype.tertiary !== void 0) archetype.tertiary = strategy.archetype.tertiary;
|
|
532
|
+
const tone = {};
|
|
533
|
+
for (const [state, entry] of Object.entries(strategy.tone)) {
|
|
534
|
+
tone[state] = { feeling: entry.feeling, example: entry.example };
|
|
535
|
+
}
|
|
536
|
+
const serialized = {
|
|
537
|
+
positioning: {
|
|
538
|
+
onliness: strategy.positioning.onliness,
|
|
539
|
+
category: strategy.positioning.category,
|
|
540
|
+
differentiation: strategy.positioning.differentiation
|
|
541
|
+
},
|
|
542
|
+
essence: [...strategy.essence],
|
|
543
|
+
personality: strategy.personality.map((p) => ({ trait: p.trait, not: p.not })),
|
|
544
|
+
archetype,
|
|
545
|
+
pillars: strategy.pillars.map(serializePillar),
|
|
546
|
+
voice: {
|
|
547
|
+
traits: strategy.voice.traits.map((t) => {
|
|
548
|
+
const trait = {
|
|
549
|
+
name: t.name,
|
|
550
|
+
do: t.do,
|
|
551
|
+
dont: t.dont
|
|
552
|
+
};
|
|
553
|
+
if (t.example !== void 0) trait.example = t.example;
|
|
554
|
+
return trait;
|
|
555
|
+
})
|
|
556
|
+
},
|
|
557
|
+
tone,
|
|
558
|
+
lexicon: strategy.lexicon.map((l) => ({ use: l.use, avoid: l.avoid })),
|
|
559
|
+
core: [...strategy.core],
|
|
560
|
+
visibility: strategy.visibility
|
|
561
|
+
};
|
|
562
|
+
if (strategy.messaging !== void 0) {
|
|
563
|
+
serialized.messaging = { roof: strategy.messaging.roof };
|
|
564
|
+
}
|
|
565
|
+
if (strategy.taglines !== void 0) {
|
|
566
|
+
serialized.taglines = [...strategy.taglines];
|
|
567
|
+
}
|
|
568
|
+
if (strategy.boilerplate !== void 0) {
|
|
569
|
+
serialized.boilerplate = {
|
|
570
|
+
short: strategy.boilerplate.short,
|
|
571
|
+
long: strategy.boilerplate.long
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
if (strategy.colorUsage !== void 0) {
|
|
575
|
+
serialized.colorUsage = {
|
|
576
|
+
pairings: strategy.colorUsage.pairings.map((p) => ({
|
|
577
|
+
use: p.use,
|
|
578
|
+
with: p.with,
|
|
579
|
+
rule: p.rule
|
|
580
|
+
}))
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (strategy.accessibility !== void 0) {
|
|
584
|
+
serialized.accessibility = {
|
|
585
|
+
standard: strategy.accessibility.standard,
|
|
586
|
+
contrast: strategy.accessibility.contrast.map((c) => ({
|
|
587
|
+
context: c.context,
|
|
588
|
+
ratio: c.ratio
|
|
589
|
+
})),
|
|
590
|
+
intent: strategy.accessibility.intent
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
return serialized;
|
|
594
|
+
}
|
|
595
|
+
|
|
205
596
|
// src/pipeline.ts
|
|
206
597
|
import { parse as parseYaml } from "yaml";
|
|
207
598
|
|
|
@@ -230,7 +621,7 @@ var visor_theme_schema_default = {
|
|
|
230
621
|
},
|
|
231
622
|
label: {
|
|
232
623
|
type: "string",
|
|
233
|
-
description: "Human-readable display name for the theme (e.g. '
|
|
624
|
+
description: "Human-readable display name for the theme (e.g. 'My Brand Pro'). Overrides the name-derived label in the docs theme switcher. Optional."
|
|
234
625
|
},
|
|
235
626
|
"default-mode": {
|
|
236
627
|
type: "string",
|
|
@@ -501,6 +892,69 @@ var visor_theme_schema_default = {
|
|
|
501
892
|
]
|
|
502
893
|
}
|
|
503
894
|
},
|
|
895
|
+
"brand-strategy": {
|
|
896
|
+
type: "object",
|
|
897
|
+
description: "Brand-strategy block (VI-505) \u2014 positioning, personality, pillars, voice, and tone as validated, serializable data. Sibling to `brand` (assets): different lifecycle and consumer. NOT expanded into derived CSS values; coherence-checked (pillars govern real tokens/components/surfaces; tone keys map to real UI states) and serialized to the agent manifest under `brand_strategy`.",
|
|
898
|
+
additionalProperties: false,
|
|
899
|
+
required: ["positioning", "essence", "personality", "archetype", "pillars", "voice", "tone", "lexicon", "core", "visibility"],
|
|
900
|
+
properties: {
|
|
901
|
+
positioning: { $ref: "#/$defs/brandPositioning" },
|
|
902
|
+
essence: {
|
|
903
|
+
type: "array",
|
|
904
|
+
items: { type: "string" },
|
|
905
|
+
minItems: 1,
|
|
906
|
+
description: "2\u20133 internal-facing core words (Aaker essence)."
|
|
907
|
+
},
|
|
908
|
+
personality: {
|
|
909
|
+
type: "array",
|
|
910
|
+
items: { $ref: "#/$defs/brandPersonalityTrait" },
|
|
911
|
+
minItems: 1,
|
|
912
|
+
description: "Brand-as-person traits, each sharpened by its antonym."
|
|
913
|
+
},
|
|
914
|
+
archetype: { $ref: "#/$defs/brandArchetype" },
|
|
915
|
+
pillars: {
|
|
916
|
+
type: "array",
|
|
917
|
+
items: { $ref: "#/$defs/brandPillar" },
|
|
918
|
+
minItems: 1,
|
|
919
|
+
description: "Strategic pillars \u2014 essence words made operational, each governing real tokens/components/surfaces."
|
|
920
|
+
},
|
|
921
|
+
voice: { $ref: "#/$defs/brandVoice" },
|
|
922
|
+
tone: {
|
|
923
|
+
type: "object",
|
|
924
|
+
description: "Voice flexed per UI state. Keys must be recognized UI states.",
|
|
925
|
+
minProperties: 1,
|
|
926
|
+
propertyNames: { enum: ["error", "success", "warning", "info", "empty", "loading", "validation-warning"] },
|
|
927
|
+
additionalProperties: { $ref: "#/$defs/brandToneEntry" }
|
|
928
|
+
},
|
|
929
|
+
lexicon: {
|
|
930
|
+
type: "array",
|
|
931
|
+
items: { $ref: "#/$defs/brandLexiconEntry" },
|
|
932
|
+
minItems: 1,
|
|
933
|
+
description: "Words to use and the ones to avoid."
|
|
934
|
+
},
|
|
935
|
+
core: {
|
|
936
|
+
type: "array",
|
|
937
|
+
items: { type: "string" },
|
|
938
|
+
minItems: 1,
|
|
939
|
+
description: "Aaker core/extended \u2014 the immutable subset, as section names."
|
|
940
|
+
},
|
|
941
|
+
visibility: {
|
|
942
|
+
type: "string",
|
|
943
|
+
enum: ["public", "private"],
|
|
944
|
+
description: "Client brands are private and are omitted from the public agent manifest."
|
|
945
|
+
},
|
|
946
|
+
messaging: { $ref: "#/$defs/brandMessaging" },
|
|
947
|
+
taglines: {
|
|
948
|
+
type: "array",
|
|
949
|
+
items: { type: "string", minLength: 1 },
|
|
950
|
+
minItems: 1,
|
|
951
|
+
description: "Phase 2 wave-1 (VI-541). Permanent, brand-level signature line(s) \u2014 who the brand is, not what it sells this quarter."
|
|
952
|
+
},
|
|
953
|
+
boilerplate: { $ref: "#/$defs/brandBoilerplate" },
|
|
954
|
+
colorUsage: { $ref: "#/$defs/brandColorUsage" },
|
|
955
|
+
accessibility: { $ref: "#/$defs/brandAccessibility" }
|
|
956
|
+
}
|
|
957
|
+
},
|
|
504
958
|
spacing: {
|
|
505
959
|
type: "object",
|
|
506
960
|
description: "Spacing configuration.",
|
|
@@ -668,6 +1122,167 @@ var visor_theme_schema_default = {
|
|
|
668
1122
|
description: "Letter spacing in logical pixels (Flutter TextStyle.letterSpacing). Material defaults include negative values, e.g. -0.25 for displayLarge."
|
|
669
1123
|
}
|
|
670
1124
|
}
|
|
1125
|
+
},
|
|
1126
|
+
brandPositioning: {
|
|
1127
|
+
type: "object",
|
|
1128
|
+
description: "Positioning \u2014 the onliness, category, and differentiation wedge.",
|
|
1129
|
+
additionalProperties: false,
|
|
1130
|
+
required: ["onliness", "category", "differentiation"],
|
|
1131
|
+
properties: {
|
|
1132
|
+
onliness: { type: "string", minLength: 1, description: `The single sentence that passes Neumeier's "only" test.` },
|
|
1133
|
+
category: { type: "string", minLength: 1, description: 'The category the brand competes in (e.g. "design system").' },
|
|
1134
|
+
differentiation: { type: "string", minLength: 1, description: "What sets the brand apart within that category." }
|
|
1135
|
+
}
|
|
1136
|
+
},
|
|
1137
|
+
brandPersonalityTrait: {
|
|
1138
|
+
type: "object",
|
|
1139
|
+
description: "A personality trait sharpened by its antonym (brand-as-person).",
|
|
1140
|
+
additionalProperties: false,
|
|
1141
|
+
required: ["trait", "not"],
|
|
1142
|
+
properties: {
|
|
1143
|
+
trait: { type: "string", minLength: 1 },
|
|
1144
|
+
not: { type: "string", minLength: 1, description: "The antonym \u2014 what the trait is not." }
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
brandArchetype: {
|
|
1148
|
+
type: "object",
|
|
1149
|
+
description: "Brand archetype assignment (Pearson & Mark). Primary required; secondary/tertiary optional.",
|
|
1150
|
+
additionalProperties: false,
|
|
1151
|
+
required: ["primary"],
|
|
1152
|
+
properties: {
|
|
1153
|
+
primary: { type: "string", minLength: 1 },
|
|
1154
|
+
secondary: { type: "string", minLength: 1 },
|
|
1155
|
+
tertiary: { type: "string", minLength: 1 }
|
|
1156
|
+
}
|
|
1157
|
+
},
|
|
1158
|
+
brandGoverns: {
|
|
1159
|
+
type: "object",
|
|
1160
|
+
description: 'What a pillar governs \u2014 design tokens, registry components, and/or meta-surfaces. At least one list is expected; use "*" for all of a namespace.',
|
|
1161
|
+
additionalProperties: false,
|
|
1162
|
+
minProperties: 1,
|
|
1163
|
+
properties: {
|
|
1164
|
+
tokens: { type: "array", items: { type: "string" }, minItems: 1, description: 'Semantic token refs (with or without the leading --), or "*".' },
|
|
1165
|
+
components: { type: "array", items: { type: "string" }, minItems: 1, description: 'Registry component names, or "*" for all.' },
|
|
1166
|
+
surfaces: { type: "array", items: { type: "string", enum: ["manifest", "cli", "component-metadata"] }, minItems: 1, description: "Meta-surfaces a pillar may govern." }
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
brandPillar: {
|
|
1170
|
+
type: "object",
|
|
1171
|
+
description: "A strategic pillar \u2014 an essence word made operational.",
|
|
1172
|
+
additionalProperties: false,
|
|
1173
|
+
required: ["id", "statement", "governs"],
|
|
1174
|
+
properties: {
|
|
1175
|
+
id: { type: "string", minLength: 1 },
|
|
1176
|
+
statement: { type: "string", minLength: 1, description: "The pillar's claim in one line." },
|
|
1177
|
+
governs: { $ref: "#/$defs/brandGoverns" },
|
|
1178
|
+
proof: {
|
|
1179
|
+
type: "array",
|
|
1180
|
+
items: { type: "string", minLength: 1 },
|
|
1181
|
+
minItems: 1,
|
|
1182
|
+
description: "Phase 2 wave-1 (VI-541). Reasons-to-believe \u2014 checkable evidence backing this pillar's claim (the message-house foundation)."
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
},
|
|
1186
|
+
brandVoice: {
|
|
1187
|
+
type: "object",
|
|
1188
|
+
description: "Voice \u2014 fixed across the brand; never flexes.",
|
|
1189
|
+
additionalProperties: false,
|
|
1190
|
+
required: ["traits"],
|
|
1191
|
+
properties: {
|
|
1192
|
+
traits: { type: "array", items: { $ref: "#/$defs/brandVoiceTrait" }, minItems: 1 }
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
brandVoiceTrait: {
|
|
1196
|
+
type: "object",
|
|
1197
|
+
description: "A fixed voice trait with a worked example.",
|
|
1198
|
+
additionalProperties: false,
|
|
1199
|
+
required: ["name", "do", "dont"],
|
|
1200
|
+
properties: {
|
|
1201
|
+
name: { type: "string", minLength: 1 },
|
|
1202
|
+
do: { type: "string", minLength: 1, description: "What to do." },
|
|
1203
|
+
dont: { type: "string", minLength: 1, description: "What not to do." },
|
|
1204
|
+
example: { type: "string", minLength: 1, description: "A worked example sentence." }
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1207
|
+
brandToneEntry: {
|
|
1208
|
+
type: "object",
|
|
1209
|
+
description: "A single tone entry, keyed (in `tone`) to a recognized UI state.",
|
|
1210
|
+
additionalProperties: false,
|
|
1211
|
+
required: ["feeling", "example"],
|
|
1212
|
+
properties: {
|
|
1213
|
+
feeling: { type: "string", minLength: 1, description: "The feeling the copy should evoke in this state." },
|
|
1214
|
+
example: { type: "string", minLength: 1, description: "A worked example message for this state." }
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
brandLexiconEntry: {
|
|
1218
|
+
type: "object",
|
|
1219
|
+
description: "A lexicon pairing \u2014 the word to use and the one to avoid.",
|
|
1220
|
+
additionalProperties: false,
|
|
1221
|
+
required: ["use", "avoid"],
|
|
1222
|
+
properties: {
|
|
1223
|
+
use: { type: "string", minLength: 1 },
|
|
1224
|
+
avoid: { type: "string", minLength: 1 }
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
brandMessaging: {
|
|
1228
|
+
type: "object",
|
|
1229
|
+
description: "Phase 2 wave-1 (VI-541). Message-house roof \u2014 the single umbrella message above the pillars.",
|
|
1230
|
+
additionalProperties: false,
|
|
1231
|
+
required: ["roof"],
|
|
1232
|
+
properties: {
|
|
1233
|
+
roof: { type: "string", minLength: 1, description: "One overarching statement the pillars support." }
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
brandBoilerplate: {
|
|
1237
|
+
type: "object",
|
|
1238
|
+
description: 'Phase 2 wave-1 (VI-541). Reusable "about us" copy \u2014 short and long forms.',
|
|
1239
|
+
additionalProperties: false,
|
|
1240
|
+
required: ["short", "long"],
|
|
1241
|
+
properties: {
|
|
1242
|
+
short: { type: "string", minLength: 1 },
|
|
1243
|
+
long: { type: "string", minLength: 1 }
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
brandColorPairing: {
|
|
1247
|
+
type: "object",
|
|
1248
|
+
description: "A color-pairing rule expressed as brand intent (not a computed value).",
|
|
1249
|
+
additionalProperties: false,
|
|
1250
|
+
required: ["use", "with", "rule"],
|
|
1251
|
+
properties: {
|
|
1252
|
+
use: { type: "string", minLength: 1, description: "The token or role being used (e.g. --primary)." },
|
|
1253
|
+
with: { type: "string", minLength: 1, description: "What it pairs against (token, role, or surface)." },
|
|
1254
|
+
rule: { type: "string", minLength: 1, description: "The intent \u2014 when and how the pairing is allowed." }
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
brandColorUsage: {
|
|
1258
|
+
type: "object",
|
|
1259
|
+
description: "Phase 2 wave-1 (VI-541). Color-usage intent \u2014 the brand's allowed pairings.",
|
|
1260
|
+
additionalProperties: false,
|
|
1261
|
+
required: ["pairings"],
|
|
1262
|
+
properties: {
|
|
1263
|
+
pairings: { type: "array", items: { $ref: "#/$defs/brandColorPairing" }, minItems: 1 }
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
brandContrastTarget: {
|
|
1267
|
+
type: "object",
|
|
1268
|
+
description: "A contrast target expressed as brand intent (a WCAG 2.1 AA threshold).",
|
|
1269
|
+
additionalProperties: false,
|
|
1270
|
+
required: ["context", "ratio"],
|
|
1271
|
+
properties: {
|
|
1272
|
+
context: { type: "string", minLength: 1, description: "The text/UI context the target applies to." },
|
|
1273
|
+
ratio: { type: "string", minLength: 1, description: 'The minimum contrast ratio (e.g. "4.5:1").' }
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
brandAccessibility: {
|
|
1277
|
+
type: "object",
|
|
1278
|
+
description: "Phase 2 wave-1 (VI-541). Accessibility intent \u2014 the standard and its contrast targets (authored as intent; live computation is the render surface's concern).",
|
|
1279
|
+
additionalProperties: false,
|
|
1280
|
+
required: ["standard", "contrast", "intent"],
|
|
1281
|
+
properties: {
|
|
1282
|
+
standard: { type: "string", minLength: 1, description: 'The conformance standard. Visor targets "WCAG 2.1 AA".' },
|
|
1283
|
+
contrast: { type: "array", items: { $ref: "#/$defs/brandContrastTarget" }, minItems: 1 },
|
|
1284
|
+
intent: { type: "string", minLength: 1, description: "How the brand applies the standard (intent, not computed results)." }
|
|
1285
|
+
}
|
|
671
1286
|
}
|
|
672
1287
|
}
|
|
673
1288
|
};
|
|
@@ -683,6 +1298,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
683
1298
|
"colors-dark",
|
|
684
1299
|
"typography",
|
|
685
1300
|
"brand",
|
|
1301
|
+
"brand-strategy",
|
|
686
1302
|
"spacing",
|
|
687
1303
|
"radius",
|
|
688
1304
|
"shadows",
|
|
@@ -1102,6 +1718,11 @@ function validateConfig(config) {
|
|
|
1102
1718
|
}
|
|
1103
1719
|
}
|
|
1104
1720
|
}
|
|
1721
|
+
if (obj["brand-strategy"] !== void 0) {
|
|
1722
|
+
for (const iss of checkBrandStrategyStructure(obj["brand-strategy"])) {
|
|
1723
|
+
errors.push(iss.path ? `${iss.message} (at ${iss.path})` : iss.message);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1105
1726
|
if (obj.overrides !== void 0) {
|
|
1106
1727
|
if (typeof obj.overrides !== "object" || obj.overrides === null) {
|
|
1107
1728
|
errors.push("'overrides' must be an object");
|
|
@@ -1735,6 +2356,18 @@ var SEMANTIC_MAP = {
|
|
|
1735
2356
|
intent: SEMANTIC_INTENT_MAP,
|
|
1736
2357
|
hairline: SEMANTIC_HAIRLINE_MAP
|
|
1737
2358
|
};
|
|
2359
|
+
function getKnownTokenRefs() {
|
|
2360
|
+
const refs = /* @__PURE__ */ new Set();
|
|
2361
|
+
for (const key of Object.keys(SEMANTIC_TEXT_MAP)) refs.add(`text-${key}`);
|
|
2362
|
+
for (const key of Object.keys(SEMANTIC_SURFACE_MAP)) refs.add(`surface-${key}`);
|
|
2363
|
+
for (const key of Object.keys(SEMANTIC_BORDER_MAP)) refs.add(`border-${key}`);
|
|
2364
|
+
for (const key of Object.keys(SEMANTIC_INTERACTIVE_MAP)) refs.add(`interactive-${key}`);
|
|
2365
|
+
for (const key of Object.keys(SEMANTIC_INTENT_MAP)) refs.add(key);
|
|
2366
|
+
for (const key of Object.keys(SEMANTIC_HAIRLINE_MAP)) {
|
|
2367
|
+
refs.add(key === "default" ? "hairline" : `hairline-${key}`);
|
|
2368
|
+
}
|
|
2369
|
+
return refs;
|
|
2370
|
+
}
|
|
1738
2371
|
|
|
1739
2372
|
// src/assign.ts
|
|
1740
2373
|
function lookupShade(primitives, role, shade) {
|
|
@@ -2862,6 +3495,12 @@ function validate(config, options) {
|
|
|
2862
3495
|
const resolved = resolveConfig(typedConfig);
|
|
2863
3496
|
checkResolvedCompleteness(resolved, errors);
|
|
2864
3497
|
}
|
|
3498
|
+
const brandStrategy = typedConfig["brand-strategy"];
|
|
3499
|
+
if (brandStrategy) {
|
|
3500
|
+
for (const iss of checkBrandStrategyCoherence(brandStrategy, { tokens: getKnownTokenRefs() })) {
|
|
3501
|
+
(iss.severity === "error" ? errors : warnings).push(iss);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
2865
3504
|
if (errors.length === 0) {
|
|
2866
3505
|
checkContrastWarnings(typedConfig, warnings);
|
|
2867
3506
|
checkColorSimilarity(typedConfig, warnings);
|
|
@@ -3368,8 +4007,12 @@ function extractFromCSS(files, name = "extracted-theme") {
|
|
|
3368
4007
|
}
|
|
3369
4008
|
export {
|
|
3370
4009
|
BRAND_VARIANTS,
|
|
4010
|
+
BRAND_VISIBILITIES,
|
|
4011
|
+
DEFAULT_BRAND_STRATEGY_SURFACES,
|
|
4012
|
+
DEFAULT_BRAND_STRATEGY_TONE_STATES,
|
|
3371
4013
|
DEFAULT_VISOR_BRAND,
|
|
3372
4014
|
FONT_WEIGHT_ALIASES,
|
|
4015
|
+
GOVERNS_WILDCARD,
|
|
3373
4016
|
SEMANTIC_MAP,
|
|
3374
4017
|
TAILWIND_GRAY,
|
|
3375
4018
|
VISOR_BRANDS_CDN,
|
|
@@ -3379,6 +4022,8 @@ export {
|
|
|
3379
4022
|
assignSemanticTokens,
|
|
3380
4023
|
buildVisorBrandUrl,
|
|
3381
4024
|
buildVisorFontUrl,
|
|
4025
|
+
checkBrandStrategyCoherence,
|
|
4026
|
+
checkBrandStrategyStructure,
|
|
3382
4027
|
clampToSrgb,
|
|
3383
4028
|
cleanFontValue,
|
|
3384
4029
|
collectBrandPassthrough,
|
|
@@ -3400,6 +4045,7 @@ export {
|
|
|
3400
4045
|
generateThemeDataFromConfig,
|
|
3401
4046
|
generateThemeFromConfig,
|
|
3402
4047
|
getContrastRatio,
|
|
4048
|
+
getKnownTokenRefs,
|
|
3403
4049
|
googleFontsCatalog,
|
|
3404
4050
|
hasBrandPassthrough,
|
|
3405
4051
|
hexToOklch,
|
|
@@ -3426,8 +4072,10 @@ export {
|
|
|
3426
4072
|
resolveThemeBrand,
|
|
3427
4073
|
resolveThemeFonts,
|
|
3428
4074
|
rgbToHex,
|
|
4075
|
+
serializeBrandStrategy,
|
|
3429
4076
|
serializeColor,
|
|
3430
4077
|
validate,
|
|
4078
|
+
validateBrandStrategy,
|
|
3431
4079
|
validateConfig,
|
|
3432
4080
|
validateFontCoverage,
|
|
3433
4081
|
visor_theme_schema_default as visorThemeSchema
|