@loworbitstudio/visor-theme-engine 0.16.0 → 0.17.1
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/adapters/index.js +136 -77
- package/dist/{chunk-DQ256PSE.js → chunk-BUWBBUFG.js} +64 -2
- package/dist/index.d.ts +179 -5
- package/dist/index.js +243 -12
- package/dist/{types-BstIS9rL.d.ts → types-Cvm7vFwe.d.ts} +79 -4
- package/package.json +1 -1
- package/src/visor-theme.schema.json +83 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale,
|
|
2
|
-
export {
|
|
1
|
+
import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, C as ColorScheme, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale, p as ColorRole, q as SelectiveShadeScale, r as RGB, P as ParsedColor, O as OKLCH, s as SemanticTokens, t as ShadeStep } from './types-Cvm7vFwe.js';
|
|
2
|
+
export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as ColorFormat, Z as DEFAULT_BRAND_STRATEGY_SURFACES, _ as DEFAULT_BRAND_STRATEGY_TONE_STATES, $ as FontSource, a0 as GOVERNS_WILDCARD, a1 as RGBA, a2 as SemanticTokenValue } from './types-Cvm7vFwe.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -234,6 +234,7 @@ declare function resolveBrandSource(brand: VisorBrand): BrandSource;
|
|
|
234
234
|
*/
|
|
235
235
|
declare function resolveThemeBrand(brand: VisorBrand | undefined, options?: {
|
|
236
236
|
scope?: string;
|
|
237
|
+
colorScheme?: ColorScheme;
|
|
237
238
|
}): ThemeBrandResult;
|
|
238
239
|
|
|
239
240
|
/**
|
|
@@ -291,8 +292,11 @@ declare function validateBrandStrategy(block: unknown, ctx?: BrandStrategyContex
|
|
|
291
292
|
/**
|
|
292
293
|
* Serialize a validated brand strategy for the manifest. Returns `null` when
|
|
293
294
|
* `visibility: private`. Optional fields (`archetype.secondary`,
|
|
294
|
-
* `voice.traits[].example`, absent `governs` lists
|
|
295
|
-
*
|
|
295
|
+
* `voice.traits[].example`, absent `governs` lists, the Phase 2 wave-1 fields
|
|
296
|
+
* `pillars[].proof` / `messaging` / `taglines` / `boilerplate` / `colorUsage` /
|
|
297
|
+
* `accessibility`) are emitted only when present, so the agent reads a clean,
|
|
298
|
+
* predictable object. The Phase 2 fields are all PUBLIC — a private record
|
|
299
|
+
* never reaches this point, so visibility is enforced once, up top (D4).
|
|
296
300
|
*/
|
|
297
301
|
declare function serializeBrandStrategy(strategy: BrandStrategy): SerializedBrandStrategy | null;
|
|
298
302
|
|
|
@@ -387,6 +391,15 @@ var properties = {
|
|
|
387
391
|
],
|
|
388
392
|
description: "Default color mode when activating this theme. When set, the docs site forces this mode on theme switch (unless the user has a stored mode preference). Optional."
|
|
389
393
|
},
|
|
394
|
+
"color-scheme": {
|
|
395
|
+
type: "string",
|
|
396
|
+
"enum": [
|
|
397
|
+
"dark-only",
|
|
398
|
+
"light-only",
|
|
399
|
+
"adaptive"
|
|
400
|
+
],
|
|
401
|
+
description: "Brand constraint declaring which color modes the theme supports. 'dark-only'/'light-only' lock the brand to a single mode; 'adaptive' supports both. Complements 'default-mode' (the runtime default) — 'color-scheme' is authoritative for the brand-lock. Defaults to 'adaptive' when omitted. Optional."
|
|
402
|
+
},
|
|
390
403
|
colors: {
|
|
391
404
|
type: "object",
|
|
392
405
|
description: "Color definitions for light mode. Only primary is required — all others have sensible defaults.",
|
|
@@ -856,6 +869,27 @@ var properties = {
|
|
|
856
869
|
"private"
|
|
857
870
|
],
|
|
858
871
|
description: "Client brands are private and are omitted from the public agent manifest."
|
|
872
|
+
},
|
|
873
|
+
messaging: {
|
|
874
|
+
$ref: "#/$defs/brandMessaging"
|
|
875
|
+
},
|
|
876
|
+
taglines: {
|
|
877
|
+
type: "array",
|
|
878
|
+
items: {
|
|
879
|
+
type: "string",
|
|
880
|
+
minLength: 1
|
|
881
|
+
},
|
|
882
|
+
minItems: 1,
|
|
883
|
+
description: "Phase 2 wave-1 (VI-541). Permanent, brand-level signature line(s) — who the brand is, not what it sells this quarter."
|
|
884
|
+
},
|
|
885
|
+
boilerplate: {
|
|
886
|
+
$ref: "#/$defs/brandBoilerplate"
|
|
887
|
+
},
|
|
888
|
+
colorUsage: {
|
|
889
|
+
$ref: "#/$defs/brandColorUsage"
|
|
890
|
+
},
|
|
891
|
+
accessibility: {
|
|
892
|
+
$ref: "#/$defs/brandAccessibility"
|
|
859
893
|
}
|
|
860
894
|
}
|
|
861
895
|
},
|
|
@@ -1208,6 +1242,15 @@ var $defs = {
|
|
|
1208
1242
|
},
|
|
1209
1243
|
governs: {
|
|
1210
1244
|
$ref: "#/$defs/brandGoverns"
|
|
1245
|
+
},
|
|
1246
|
+
proof: {
|
|
1247
|
+
type: "array",
|
|
1248
|
+
items: {
|
|
1249
|
+
type: "string",
|
|
1250
|
+
minLength: 1
|
|
1251
|
+
},
|
|
1252
|
+
minItems: 1,
|
|
1253
|
+
description: "Phase 2 wave-1 (VI-541). Reasons-to-believe — checkable evidence backing this pillar's claim (the message-house foundation)."
|
|
1211
1254
|
}
|
|
1212
1255
|
}
|
|
1213
1256
|
},
|
|
@@ -1298,6 +1341,134 @@ var $defs = {
|
|
|
1298
1341
|
minLength: 1
|
|
1299
1342
|
}
|
|
1300
1343
|
}
|
|
1344
|
+
},
|
|
1345
|
+
brandMessaging: {
|
|
1346
|
+
type: "object",
|
|
1347
|
+
description: "Phase 2 wave-1 (VI-541). Message-house roof — the single umbrella message above the pillars.",
|
|
1348
|
+
additionalProperties: false,
|
|
1349
|
+
required: [
|
|
1350
|
+
"roof"
|
|
1351
|
+
],
|
|
1352
|
+
properties: {
|
|
1353
|
+
roof: {
|
|
1354
|
+
type: "string",
|
|
1355
|
+
minLength: 1,
|
|
1356
|
+
description: "One overarching statement the pillars support."
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
brandBoilerplate: {
|
|
1361
|
+
type: "object",
|
|
1362
|
+
description: "Phase 2 wave-1 (VI-541). Reusable \"about us\" copy — short and long forms.",
|
|
1363
|
+
additionalProperties: false,
|
|
1364
|
+
required: [
|
|
1365
|
+
"short",
|
|
1366
|
+
"long"
|
|
1367
|
+
],
|
|
1368
|
+
properties: {
|
|
1369
|
+
short: {
|
|
1370
|
+
type: "string",
|
|
1371
|
+
minLength: 1
|
|
1372
|
+
},
|
|
1373
|
+
long: {
|
|
1374
|
+
type: "string",
|
|
1375
|
+
minLength: 1
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
brandColorPairing: {
|
|
1380
|
+
type: "object",
|
|
1381
|
+
description: "A color-pairing rule expressed as brand intent (not a computed value).",
|
|
1382
|
+
additionalProperties: false,
|
|
1383
|
+
required: [
|
|
1384
|
+
"use",
|
|
1385
|
+
"with",
|
|
1386
|
+
"rule"
|
|
1387
|
+
],
|
|
1388
|
+
properties: {
|
|
1389
|
+
use: {
|
|
1390
|
+
type: "string",
|
|
1391
|
+
minLength: 1,
|
|
1392
|
+
description: "The token or role being used (e.g. --primary)."
|
|
1393
|
+
},
|
|
1394
|
+
"with": {
|
|
1395
|
+
type: "string",
|
|
1396
|
+
minLength: 1,
|
|
1397
|
+
description: "What it pairs against (token, role, or surface)."
|
|
1398
|
+
},
|
|
1399
|
+
rule: {
|
|
1400
|
+
type: "string",
|
|
1401
|
+
minLength: 1,
|
|
1402
|
+
description: "The intent — when and how the pairing is allowed."
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
},
|
|
1406
|
+
brandColorUsage: {
|
|
1407
|
+
type: "object",
|
|
1408
|
+
description: "Phase 2 wave-1 (VI-541). Color-usage intent — the brand's allowed pairings.",
|
|
1409
|
+
additionalProperties: false,
|
|
1410
|
+
required: [
|
|
1411
|
+
"pairings"
|
|
1412
|
+
],
|
|
1413
|
+
properties: {
|
|
1414
|
+
pairings: {
|
|
1415
|
+
type: "array",
|
|
1416
|
+
items: {
|
|
1417
|
+
$ref: "#/$defs/brandColorPairing"
|
|
1418
|
+
},
|
|
1419
|
+
minItems: 1
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
brandContrastTarget: {
|
|
1424
|
+
type: "object",
|
|
1425
|
+
description: "A contrast target expressed as brand intent (a WCAG 2.1 AA threshold).",
|
|
1426
|
+
additionalProperties: false,
|
|
1427
|
+
required: [
|
|
1428
|
+
"context",
|
|
1429
|
+
"ratio"
|
|
1430
|
+
],
|
|
1431
|
+
properties: {
|
|
1432
|
+
context: {
|
|
1433
|
+
type: "string",
|
|
1434
|
+
minLength: 1,
|
|
1435
|
+
description: "The text/UI context the target applies to."
|
|
1436
|
+
},
|
|
1437
|
+
ratio: {
|
|
1438
|
+
type: "string",
|
|
1439
|
+
minLength: 1,
|
|
1440
|
+
description: "The minimum contrast ratio (e.g. \"4.5:1\")."
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
brandAccessibility: {
|
|
1445
|
+
type: "object",
|
|
1446
|
+
description: "Phase 2 wave-1 (VI-541). Accessibility intent — the standard and its contrast targets (authored as intent; live computation is the render surface's concern).",
|
|
1447
|
+
additionalProperties: false,
|
|
1448
|
+
required: [
|
|
1449
|
+
"standard",
|
|
1450
|
+
"contrast",
|
|
1451
|
+
"intent"
|
|
1452
|
+
],
|
|
1453
|
+
properties: {
|
|
1454
|
+
standard: {
|
|
1455
|
+
type: "string",
|
|
1456
|
+
minLength: 1,
|
|
1457
|
+
description: "The conformance standard. Visor targets \"WCAG 2.1 AA\"."
|
|
1458
|
+
},
|
|
1459
|
+
contrast: {
|
|
1460
|
+
type: "array",
|
|
1461
|
+
items: {
|
|
1462
|
+
$ref: "#/$defs/brandContrastTarget"
|
|
1463
|
+
},
|
|
1464
|
+
minItems: 1
|
|
1465
|
+
},
|
|
1466
|
+
intent: {
|
|
1467
|
+
type: "string",
|
|
1468
|
+
minLength: 1,
|
|
1469
|
+
description: "How the brand applies the standard (intent, not computed results)."
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1301
1472
|
}
|
|
1302
1473
|
};
|
|
1303
1474
|
var visorTheme_schema = {
|
|
@@ -1427,6 +1598,7 @@ declare function clampToSrgb(L: number, C: number, H: number): RGB;
|
|
|
1427
1598
|
* Convert OKLCH to hex, with gamut clamping.
|
|
1428
1599
|
*/
|
|
1429
1600
|
declare function oklchToHex(L: number, C: number, H: number): string;
|
|
1601
|
+
declare function getLuminance(r: number, g: number, b: number): number;
|
|
1430
1602
|
declare function getContrastRatio(color1: string | ParsedColor, color2: string | ParsedColor, compositeBackground?: RGB): number;
|
|
1431
1603
|
/** Parse a hex color string into a ParsedColor. */
|
|
1432
1604
|
declare function parseHex(str: string): ParsedColor | null;
|
|
@@ -1603,9 +1775,11 @@ declare function generatePrimitivesCss(primitives: GeneratedPrimitives, config:
|
|
|
1603
1775
|
declare function generateSemanticCss(tokens: SemanticTokens): string;
|
|
1604
1776
|
declare function generateLightCss(tokens: SemanticTokens, options?: {
|
|
1605
1777
|
scopePrefix?: string;
|
|
1778
|
+
colorScheme?: ColorScheme;
|
|
1606
1779
|
}): string;
|
|
1607
1780
|
declare function generateDarkCss(tokens: SemanticTokens, options?: {
|
|
1608
1781
|
scopePrefix?: string;
|
|
1782
|
+
colorScheme?: ColorScheme;
|
|
1609
1783
|
}): string;
|
|
1610
1784
|
declare function generateFullBundleCss(primitives: GeneratedPrimitives, tokens: SemanticTokens, config: ResolvedThemeConfig): string;
|
|
1611
1785
|
|
|
@@ -1684,4 +1858,4 @@ declare function cleanFontValue(val: string): string;
|
|
|
1684
1858
|
*/
|
|
1685
1859
|
declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
|
|
1686
1860
|
|
|
1687
|
-
export { type BrandPassthrough, BrandResolution, BrandSlot, BrandSource, BrandStrategy, BrandStrategyContext, BrandStrategyIssue, BrandStrategyValidationResult, type CSSFile, ColorRole, type Confidence, DEFAULT_VISOR_BRAND, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, SerializedBrandStrategy, ShadeStep, TAILWIND_GRAY, ThemeBrandResult, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_BRANDS_CDN, VISOR_DEFAULT_BRAND_PATH, VISOR_FONTS_CDN, type ValidateOptions, type ValidationIssue, type ValidationSeverity, VisorBrand, VisorThemeConfig, VisorTypography, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, compositeOverBackground, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, lookupFontWeightAlias, lookupGoogleFont, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveThemeBrand, resolveThemeFonts, rgbToHex, serializeBrandStrategy, serializeColor, validate, validateBrandStrategy, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
|
|
1861
|
+
export { type BrandPassthrough, BrandResolution, BrandSlot, BrandSource, BrandStrategy, BrandStrategyContext, BrandStrategyIssue, BrandStrategyValidationResult, type CSSFile, ColorRole, type Confidence, DEFAULT_VISOR_BRAND, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, SerializedBrandStrategy, ShadeStep, TAILWIND_GRAY, ThemeBrandResult, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_BRANDS_CDN, VISOR_DEFAULT_BRAND_PATH, VISOR_FONTS_CDN, type ValidateOptions, type ValidationIssue, type ValidationSeverity, VisorBrand, VisorThemeConfig, VisorTypography, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, compositeOverBackground, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, getLuminance, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, lookupFontWeightAlias, lookupGoogleFont, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveThemeBrand, resolveThemeFonts, rgbToHex, serializeBrandStrategy, serializeColor, validate, validateBrandStrategy, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
rgbToHex,
|
|
47
47
|
rgbToOklch,
|
|
48
48
|
serializeColor
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-BUWBBUFG.js";
|
|
50
50
|
|
|
51
51
|
// src/fonts/validate-coverage.ts
|
|
52
52
|
var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
|
|
@@ -248,7 +248,13 @@ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
248
248
|
"tone",
|
|
249
249
|
"lexicon",
|
|
250
250
|
"core",
|
|
251
|
-
"visibility"
|
|
251
|
+
"visibility",
|
|
252
|
+
// Phase 2 wave-1 (VI-541) — all optional.
|
|
253
|
+
"messaging",
|
|
254
|
+
"taglines",
|
|
255
|
+
"boilerplate",
|
|
256
|
+
"colorUsage",
|
|
257
|
+
"accessibility"
|
|
252
258
|
]);
|
|
253
259
|
var GOVERNS_KEYS = /* @__PURE__ */ new Set(["tokens", "components", "surfaces"]);
|
|
254
260
|
function checkBrandStrategyStructure(block) {
|
|
@@ -347,6 +353,64 @@ function checkBrandStrategyStructure(block) {
|
|
|
347
353
|
if (!isNonEmptyString(block.visibility) || !BRAND_VISIBILITIES.includes(block.visibility)) {
|
|
348
354
|
errors.push(error("BRAND_STRATEGY_VISIBILITY", `'brand-strategy.visibility' is required and must be one of: ${BRAND_VISIBILITIES.join(", ")}`, "brand-strategy.visibility"));
|
|
349
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
|
+
}
|
|
350
414
|
return errors;
|
|
351
415
|
}
|
|
352
416
|
function checkPillarStructure(pillar, index) {
|
|
@@ -383,6 +447,13 @@ function checkPillarStructure(pillar, index) {
|
|
|
383
447
|
errors.push(error("BRAND_STRATEGY_GOVERNS", `'${path}.governs' must declare at least one of: ${[...GOVERNS_KEYS].join(", ")}`, `${path}.governs`));
|
|
384
448
|
}
|
|
385
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
|
+
}
|
|
386
457
|
return errors;
|
|
387
458
|
}
|
|
388
459
|
function checkBrandStrategyCoherence(strategy, ctx = {}) {
|
|
@@ -442,6 +513,15 @@ function compactGoverns(governs) {
|
|
|
442
513
|
if (governs.surfaces) out.surfaces = [...governs.surfaces];
|
|
443
514
|
return out;
|
|
444
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
|
+
}
|
|
445
525
|
function serializeBrandStrategy(strategy) {
|
|
446
526
|
if (strategy.visibility === "private") {
|
|
447
527
|
return null;
|
|
@@ -453,7 +533,7 @@ function serializeBrandStrategy(strategy) {
|
|
|
453
533
|
for (const [state, entry] of Object.entries(strategy.tone)) {
|
|
454
534
|
tone[state] = { feeling: entry.feeling, example: entry.example };
|
|
455
535
|
}
|
|
456
|
-
|
|
536
|
+
const serialized = {
|
|
457
537
|
positioning: {
|
|
458
538
|
onliness: strategy.positioning.onliness,
|
|
459
539
|
category: strategy.positioning.category,
|
|
@@ -462,11 +542,7 @@ function serializeBrandStrategy(strategy) {
|
|
|
462
542
|
essence: [...strategy.essence],
|
|
463
543
|
personality: strategy.personality.map((p) => ({ trait: p.trait, not: p.not })),
|
|
464
544
|
archetype,
|
|
465
|
-
pillars: strategy.pillars.map(
|
|
466
|
-
id: p.id,
|
|
467
|
-
statement: p.statement,
|
|
468
|
-
governs: compactGoverns(p.governs)
|
|
469
|
-
})),
|
|
545
|
+
pillars: strategy.pillars.map(serializePillar),
|
|
470
546
|
voice: {
|
|
471
547
|
traits: strategy.voice.traits.map((t) => {
|
|
472
548
|
const trait = {
|
|
@@ -483,6 +559,38 @@ function serializeBrandStrategy(strategy) {
|
|
|
483
559
|
core: [...strategy.core],
|
|
484
560
|
visibility: strategy.visibility
|
|
485
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;
|
|
486
594
|
}
|
|
487
595
|
|
|
488
596
|
// src/pipeline.ts
|
|
@@ -520,6 +628,11 @@ var visor_theme_schema_default = {
|
|
|
520
628
|
enum: ["light", "dark"],
|
|
521
629
|
description: "Default color mode when activating this theme. When set, the docs site forces this mode on theme switch (unless the user has a stored mode preference). Optional."
|
|
522
630
|
},
|
|
631
|
+
"color-scheme": {
|
|
632
|
+
type: "string",
|
|
633
|
+
enum: ["dark-only", "light-only", "adaptive"],
|
|
634
|
+
description: "Brand constraint declaring which color modes the theme supports. 'dark-only'/'light-only' lock the brand to a single mode; 'adaptive' supports both. Complements 'default-mode' (the runtime default) \u2014 'color-scheme' is authoritative for the brand-lock. Defaults to 'adaptive' when omitted. Optional."
|
|
635
|
+
},
|
|
523
636
|
colors: {
|
|
524
637
|
type: "object",
|
|
525
638
|
description: "Color definitions for light mode. Only primary is required \u2014 all others have sensible defaults.",
|
|
@@ -834,7 +947,17 @@ var visor_theme_schema_default = {
|
|
|
834
947
|
type: "string",
|
|
835
948
|
enum: ["public", "private"],
|
|
836
949
|
description: "Client brands are private and are omitted from the public agent manifest."
|
|
837
|
-
}
|
|
950
|
+
},
|
|
951
|
+
messaging: { $ref: "#/$defs/brandMessaging" },
|
|
952
|
+
taglines: {
|
|
953
|
+
type: "array",
|
|
954
|
+
items: { type: "string", minLength: 1 },
|
|
955
|
+
minItems: 1,
|
|
956
|
+
description: "Phase 2 wave-1 (VI-541). Permanent, brand-level signature line(s) \u2014 who the brand is, not what it sells this quarter."
|
|
957
|
+
},
|
|
958
|
+
boilerplate: { $ref: "#/$defs/brandBoilerplate" },
|
|
959
|
+
colorUsage: { $ref: "#/$defs/brandColorUsage" },
|
|
960
|
+
accessibility: { $ref: "#/$defs/brandAccessibility" }
|
|
838
961
|
}
|
|
839
962
|
},
|
|
840
963
|
spacing: {
|
|
@@ -1056,7 +1179,13 @@ var visor_theme_schema_default = {
|
|
|
1056
1179
|
properties: {
|
|
1057
1180
|
id: { type: "string", minLength: 1 },
|
|
1058
1181
|
statement: { type: "string", minLength: 1, description: "The pillar's claim in one line." },
|
|
1059
|
-
governs: { $ref: "#/$defs/brandGoverns" }
|
|
1182
|
+
governs: { $ref: "#/$defs/brandGoverns" },
|
|
1183
|
+
proof: {
|
|
1184
|
+
type: "array",
|
|
1185
|
+
items: { type: "string", minLength: 1 },
|
|
1186
|
+
minItems: 1,
|
|
1187
|
+
description: "Phase 2 wave-1 (VI-541). Reasons-to-believe \u2014 checkable evidence backing this pillar's claim (the message-house foundation)."
|
|
1188
|
+
}
|
|
1060
1189
|
}
|
|
1061
1190
|
},
|
|
1062
1191
|
brandVoice: {
|
|
@@ -1099,6 +1228,66 @@ var visor_theme_schema_default = {
|
|
|
1099
1228
|
use: { type: "string", minLength: 1 },
|
|
1100
1229
|
avoid: { type: "string", minLength: 1 }
|
|
1101
1230
|
}
|
|
1231
|
+
},
|
|
1232
|
+
brandMessaging: {
|
|
1233
|
+
type: "object",
|
|
1234
|
+
description: "Phase 2 wave-1 (VI-541). Message-house roof \u2014 the single umbrella message above the pillars.",
|
|
1235
|
+
additionalProperties: false,
|
|
1236
|
+
required: ["roof"],
|
|
1237
|
+
properties: {
|
|
1238
|
+
roof: { type: "string", minLength: 1, description: "One overarching statement the pillars support." }
|
|
1239
|
+
}
|
|
1240
|
+
},
|
|
1241
|
+
brandBoilerplate: {
|
|
1242
|
+
type: "object",
|
|
1243
|
+
description: 'Phase 2 wave-1 (VI-541). Reusable "about us" copy \u2014 short and long forms.',
|
|
1244
|
+
additionalProperties: false,
|
|
1245
|
+
required: ["short", "long"],
|
|
1246
|
+
properties: {
|
|
1247
|
+
short: { type: "string", minLength: 1 },
|
|
1248
|
+
long: { type: "string", minLength: 1 }
|
|
1249
|
+
}
|
|
1250
|
+
},
|
|
1251
|
+
brandColorPairing: {
|
|
1252
|
+
type: "object",
|
|
1253
|
+
description: "A color-pairing rule expressed as brand intent (not a computed value).",
|
|
1254
|
+
additionalProperties: false,
|
|
1255
|
+
required: ["use", "with", "rule"],
|
|
1256
|
+
properties: {
|
|
1257
|
+
use: { type: "string", minLength: 1, description: "The token or role being used (e.g. --primary)." },
|
|
1258
|
+
with: { type: "string", minLength: 1, description: "What it pairs against (token, role, or surface)." },
|
|
1259
|
+
rule: { type: "string", minLength: 1, description: "The intent \u2014 when and how the pairing is allowed." }
|
|
1260
|
+
}
|
|
1261
|
+
},
|
|
1262
|
+
brandColorUsage: {
|
|
1263
|
+
type: "object",
|
|
1264
|
+
description: "Phase 2 wave-1 (VI-541). Color-usage intent \u2014 the brand's allowed pairings.",
|
|
1265
|
+
additionalProperties: false,
|
|
1266
|
+
required: ["pairings"],
|
|
1267
|
+
properties: {
|
|
1268
|
+
pairings: { type: "array", items: { $ref: "#/$defs/brandColorPairing" }, minItems: 1 }
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
brandContrastTarget: {
|
|
1272
|
+
type: "object",
|
|
1273
|
+
description: "A contrast target expressed as brand intent (a WCAG 2.1 AA threshold).",
|
|
1274
|
+
additionalProperties: false,
|
|
1275
|
+
required: ["context", "ratio"],
|
|
1276
|
+
properties: {
|
|
1277
|
+
context: { type: "string", minLength: 1, description: "The text/UI context the target applies to." },
|
|
1278
|
+
ratio: { type: "string", minLength: 1, description: 'The minimum contrast ratio (e.g. "4.5:1").' }
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
brandAccessibility: {
|
|
1282
|
+
type: "object",
|
|
1283
|
+
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).",
|
|
1284
|
+
additionalProperties: false,
|
|
1285
|
+
required: ["standard", "contrast", "intent"],
|
|
1286
|
+
properties: {
|
|
1287
|
+
standard: { type: "string", minLength: 1, description: 'The conformance standard. Visor targets "WCAG 2.1 AA".' },
|
|
1288
|
+
contrast: { type: "array", items: { $ref: "#/$defs/brandContrastTarget" }, minItems: 1 },
|
|
1289
|
+
intent: { type: "string", minLength: 1, description: "How the brand applies the standard (intent, not computed results)." }
|
|
1290
|
+
}
|
|
1102
1291
|
}
|
|
1103
1292
|
}
|
|
1104
1293
|
};
|
|
@@ -1110,6 +1299,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
1110
1299
|
"group",
|
|
1111
1300
|
"label",
|
|
1112
1301
|
"default-mode",
|
|
1302
|
+
"color-scheme",
|
|
1113
1303
|
"colors",
|
|
1114
1304
|
"colors-dark",
|
|
1115
1305
|
"typography",
|
|
@@ -1390,6 +1580,12 @@ function validateConfig(config) {
|
|
|
1390
1580
|
errors.push("'default-mode' must be either 'dark' or 'light'");
|
|
1391
1581
|
}
|
|
1392
1582
|
}
|
|
1583
|
+
if (obj["color-scheme"] !== void 0) {
|
|
1584
|
+
const scheme = obj["color-scheme"];
|
|
1585
|
+
if (scheme !== "dark-only" && scheme !== "light-only" && scheme !== "adaptive") {
|
|
1586
|
+
errors.push("'color-scheme' must be one of: dark-only, light-only, adaptive");
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1393
1589
|
if (typeof obj.colors !== "object" || obj.colors === null) {
|
|
1394
1590
|
errors.push("'colors' is required and must be an object");
|
|
1395
1591
|
return { valid: false, errors };
|
|
@@ -1679,6 +1875,9 @@ function resolveConfig(config) {
|
|
|
1679
1875
|
name: config.name,
|
|
1680
1876
|
...config.label !== void 0 && { label: config.label },
|
|
1681
1877
|
...config["default-mode"] !== void 0 && { "default-mode": config["default-mode"] },
|
|
1878
|
+
// BO-55: always-resolved brand constraint — existing themes (no field) resolve
|
|
1879
|
+
// to 'adaptive', preserving back-compat.
|
|
1880
|
+
"color-scheme": config["color-scheme"] ?? "adaptive",
|
|
1682
1881
|
version: 1,
|
|
1683
1882
|
colors: {
|
|
1684
1883
|
primary: colors.primary,
|
|
@@ -2371,8 +2570,8 @@ ${validation.errors.map((e) => ` - ${e}`).join("\n")}`
|
|
|
2371
2570
|
const output = {
|
|
2372
2571
|
primitivesCss: generatePrimitivesCss(primitives, resolved),
|
|
2373
2572
|
semanticCss: generateSemanticCss(tokens),
|
|
2374
|
-
lightCss: generateLightCss(tokens),
|
|
2375
|
-
darkCss: generateDarkCss(tokens),
|
|
2573
|
+
lightCss: generateLightCss(tokens, { colorScheme: resolved["color-scheme"] }),
|
|
2574
|
+
darkCss: generateDarkCss(tokens, { colorScheme: resolved["color-scheme"] }),
|
|
2376
2575
|
fullBundleCss: generateFullBundleCss(primitives, tokens, resolved)
|
|
2377
2576
|
};
|
|
2378
2577
|
return { config: resolved, primitives, tokens, output };
|
|
@@ -3719,6 +3918,36 @@ function extractBackgroundSurface(declarations) {
|
|
|
3719
3918
|
}
|
|
3720
3919
|
return result;
|
|
3721
3920
|
}
|
|
3921
|
+
function detectExplicitColorScheme(files) {
|
|
3922
|
+
for (const file of files) {
|
|
3923
|
+
const content = file.content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
3924
|
+
const re = /(?<![\w-])color-scheme\s*:\s*([^;}]+)/gi;
|
|
3925
|
+
let m;
|
|
3926
|
+
while ((m = re.exec(content)) !== null) {
|
|
3927
|
+
const keywords = m[1].trim().toLowerCase().split(/\s+/).filter((k) => k.length > 0 && k !== "only");
|
|
3928
|
+
const hasDark = keywords.includes("dark");
|
|
3929
|
+
const hasLight = keywords.includes("light");
|
|
3930
|
+
if (hasDark && hasLight) return "adaptive";
|
|
3931
|
+
if (hasDark) return "dark-only";
|
|
3932
|
+
if (hasLight) return "light-only";
|
|
3933
|
+
if (keywords.includes("normal")) return "adaptive";
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
return null;
|
|
3937
|
+
}
|
|
3938
|
+
function detectColorScheme(files, bgSurface, warnings) {
|
|
3939
|
+
const explicit = detectExplicitColorScheme(files);
|
|
3940
|
+
const heuristic = bgSurface.dark.background && !bgSurface.light.background ? "dark-only" : "adaptive";
|
|
3941
|
+
if (explicit) {
|
|
3942
|
+
if (heuristic !== "adaptive" && heuristic !== explicit) {
|
|
3943
|
+
warnings.push(
|
|
3944
|
+
`color-scheme ambiguity: source CSS explicitly declares 'color-scheme: ${explicit}' but the background heuristic suggests '${heuristic}'. Using the explicit declaration \u2014 review the source if this is unexpected.`
|
|
3945
|
+
);
|
|
3946
|
+
}
|
|
3947
|
+
return explicit;
|
|
3948
|
+
}
|
|
3949
|
+
return heuristic;
|
|
3950
|
+
}
|
|
3722
3951
|
function extractFromCSS(files, name = "extracted-theme") {
|
|
3723
3952
|
const warnings = [];
|
|
3724
3953
|
const allDeclarations = [];
|
|
@@ -3792,6 +4021,7 @@ function extractFromCSS(files, name = "extracted-theme") {
|
|
|
3792
4021
|
if (Object.keys(darkColors).length > 0) {
|
|
3793
4022
|
config["colors-dark"] = darkColors;
|
|
3794
4023
|
}
|
|
4024
|
+
config["color-scheme"] = detectColorScheme(files, bgSurface, warnings);
|
|
3795
4025
|
if (typography.heading || typography.display || typography.body || typography.mono) {
|
|
3796
4026
|
config.typography = {};
|
|
3797
4027
|
if (typography.heading) config.typography.heading = typography.heading;
|
|
@@ -3862,6 +4092,7 @@ export {
|
|
|
3862
4092
|
generateThemeFromConfig,
|
|
3863
4093
|
getContrastRatio,
|
|
3864
4094
|
getKnownTokenRefs,
|
|
4095
|
+
getLuminance,
|
|
3865
4096
|
googleFontsCatalog,
|
|
3866
4097
|
hasBrandPassthrough,
|
|
3867
4098
|
hexToOklch,
|