@loworbitstudio/visor-theme-engine 0.4.1 → 0.4.2
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 +10 -6
- package/dist/adapters/index.js +16 -13
- package/dist/{chunk-G4B57FB3.js → chunk-SXT2KY6D.js} +9 -6
- package/dist/index.d.ts +16 -2
- package/dist/index.js +106 -55
- package/dist/{types-gAlkt__C.d.ts → types-ljcTtODU.d.ts} +15 -0
- package/package.json +1 -1
- package/src/visor-theme.schema.json +12 -0
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as GeneratedPrimitives, i as SemanticTokens, R as ResolvedThemeConfig } from '../types-
|
|
1
|
+
import { c as GeneratedPrimitives, i as SemanticTokens, R as ResolvedThemeConfig } from '../types-ljcTtODU.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter types for the Visor theme engine.
|
|
@@ -139,8 +139,10 @@ declare function deckAdapter(input: AdapterInput, options?: DeckAdapterOptions):
|
|
|
139
139
|
/**
|
|
140
140
|
* Generate docs-site CSS for a theme.
|
|
141
141
|
*
|
|
142
|
-
* Output is class-scoped (.{slug}-theme)
|
|
143
|
-
*
|
|
142
|
+
* Output is class-scoped (.{slug}-theme) and wrapped in @layer visor-adaptive
|
|
143
|
+
* so consumer overrides (unlayered) and visor-core's own visor-adaptive layer
|
|
144
|
+
* cascade correctly. Font @import / @font-face statements stay outside the
|
|
145
|
+
* layer block per CSS spec (VI-312).
|
|
144
146
|
*/
|
|
145
147
|
declare function docsAdapter(input: AdapterInput, options?: DocsAdapterOptions): string;
|
|
146
148
|
|
|
@@ -177,9 +179,11 @@ declare function flutterAdapter(input: AdapterInput, options?: FlutterAdapterOpt
|
|
|
177
179
|
/**
|
|
178
180
|
* CSS @layer utilities for adapter output.
|
|
179
181
|
*
|
|
180
|
-
* Establishes a specificity ordering so theme overrides work
|
|
181
|
-
*
|
|
182
|
-
*
|
|
182
|
+
* Establishes a specificity ordering so theme overrides work without
|
|
183
|
+
* !important. Both adapter output (here) and visor-core's emitted CSS
|
|
184
|
+
* (packages/tokens/src/generate/generate-css.ts) declare this same layer
|
|
185
|
+
* order — defense in depth, so whichever stylesheet loads first establishes
|
|
186
|
+
* the cascade.
|
|
183
187
|
*/
|
|
184
188
|
/** Layer order declaration — must appear before any @layer blocks. */
|
|
185
189
|
declare const LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
|
package/dist/adapters/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
parseColor,
|
|
12
12
|
resolveThemeFonts,
|
|
13
13
|
sectionComment
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-SXT2KY6D.js";
|
|
15
15
|
|
|
16
16
|
// src/adapters/layers.ts
|
|
17
17
|
var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
|
|
@@ -418,6 +418,7 @@ function docsAdapter(input, options) {
|
|
|
418
418
|
const slug = toKebabCase2(input.config.name);
|
|
419
419
|
const scopeClass = `.${slug}-theme`;
|
|
420
420
|
const includeFontImports = options?.includeFontImports ?? true;
|
|
421
|
+
const fontLines = [];
|
|
421
422
|
const lines = [];
|
|
422
423
|
if (includeFontImports && input.config.typography) {
|
|
423
424
|
const fontResult = resolveThemeFonts(input.config.typography);
|
|
@@ -426,8 +427,8 @@ function docsAdapter(input, options) {
|
|
|
426
427
|
for (const font of fontSlots) {
|
|
427
428
|
if (font && font.source === "google-fonts" && font.cssUrl && !seenUrls.has(font.cssUrl)) {
|
|
428
429
|
seenUrls.add(font.cssUrl);
|
|
429
|
-
|
|
430
|
-
|
|
430
|
+
fontLines.push(`@import url("${font.cssUrl}");`);
|
|
431
|
+
fontLines.push("");
|
|
431
432
|
}
|
|
432
433
|
}
|
|
433
434
|
const scale = input.config.typography?.scale ?? 1;
|
|
@@ -437,17 +438,17 @@ function docsAdapter(input, options) {
|
|
|
437
438
|
seenFamilies.add(font.family);
|
|
438
439
|
for (const weight of font.weights) {
|
|
439
440
|
const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
441
|
+
fontLines.push("@font-face {");
|
|
442
|
+
fontLines.push(` font-family: "${font.family}";`);
|
|
443
|
+
fontLines.push(` src: url("${url}") format("woff2");`);
|
|
444
|
+
fontLines.push(` font-weight: ${weight};`);
|
|
445
|
+
fontLines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
|
|
446
|
+
fontLines.push(` font-display: ${font.display};`);
|
|
446
447
|
if (scale !== 1) {
|
|
447
|
-
|
|
448
|
+
fontLines.push(` size-adjust: ${Math.round(scale * 100)}%;`);
|
|
448
449
|
}
|
|
449
|
-
|
|
450
|
-
|
|
450
|
+
fontLines.push("}");
|
|
451
|
+
fontLines.push("");
|
|
451
452
|
}
|
|
452
453
|
}
|
|
453
454
|
}
|
|
@@ -562,7 +563,9 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
|
562
563
|
lines.push(sectionComment2("Fumadocs bridge: light"));
|
|
563
564
|
lines.push(block(`html:not(.dark) ${scopeClass}`, generateFumadocsBridgeDecls(input.tokens, "light")));
|
|
564
565
|
lines.push("");
|
|
565
|
-
|
|
566
|
+
const layered = wrapInLayer("visor-adaptive", lines.join("\n").trim());
|
|
567
|
+
const head = fontLines.length > 0 ? fontLines.join("\n") + "\n" : "";
|
|
568
|
+
return head + LAYER_ORDER + "\n\n" + layered + "\n";
|
|
566
569
|
}
|
|
567
570
|
|
|
568
571
|
// src/flutter/color-to-dart.ts
|
|
@@ -964,8 +964,11 @@ var LIGHTNESS_TARGETS = {
|
|
|
964
964
|
200: 0.87,
|
|
965
965
|
300: 0.78,
|
|
966
966
|
400: 0.65,
|
|
967
|
-
500
|
|
968
|
-
//
|
|
967
|
+
// 500 is the reference midpoint (Tailwind gray-500's OKLCH L ≈ 0.55).
|
|
968
|
+
// At the anchor shade the input L is used directly; this value seeds the
|
|
969
|
+
// interpolation math in computeLightness() for inputs that don't land at
|
|
970
|
+
// the reference midpoint.
|
|
971
|
+
500: 0.55,
|
|
969
972
|
600: 0.45,
|
|
970
973
|
700: 0.38,
|
|
971
974
|
800: 0.3,
|
|
@@ -1012,20 +1015,20 @@ function computeLightness(step, inputL, anchorShade) {
|
|
|
1012
1015
|
if (step === anchorShade) {
|
|
1013
1016
|
return inputL;
|
|
1014
1017
|
}
|
|
1015
|
-
const anchorTarget =
|
|
1018
|
+
const anchorTarget = LIGHTNESS_TARGETS[anchorShade];
|
|
1016
1019
|
const stepTarget = LIGHTNESS_TARGETS[step];
|
|
1017
1020
|
if (Math.abs(anchorTarget - inputL) < 0.01) {
|
|
1018
1021
|
return stepTarget;
|
|
1019
1022
|
}
|
|
1020
1023
|
if (step < anchorShade) {
|
|
1021
|
-
const anchorDefaultL =
|
|
1024
|
+
const anchorDefaultL = LIGHTNESS_TARGETS[anchorShade];
|
|
1022
1025
|
const rawRange = 0.97 - anchorDefaultL;
|
|
1023
1026
|
const newRange = 0.97 - inputL;
|
|
1024
1027
|
if (rawRange <= 0) return stepTarget;
|
|
1025
1028
|
const t = (stepTarget - anchorDefaultL) / rawRange;
|
|
1026
1029
|
return inputL + t * newRange;
|
|
1027
1030
|
} else {
|
|
1028
|
-
const anchorDefaultL =
|
|
1031
|
+
const anchorDefaultL = LIGHTNESS_TARGETS[anchorShade];
|
|
1029
1032
|
const rawRange = anchorDefaultL - 0.14;
|
|
1030
1033
|
const newRange = inputL - 0.14;
|
|
1031
1034
|
if (rawRange <= 0) return stepTarget;
|
|
@@ -1043,7 +1046,7 @@ function generateShadeScale(color, role) {
|
|
|
1043
1046
|
for (const step of steps) {
|
|
1044
1047
|
const targetL = computeLightness(step, inputL, anchorShade);
|
|
1045
1048
|
let targetC = inputC * CHROMA_MULTIPLIERS[step];
|
|
1046
|
-
if (role === "neutral") {
|
|
1049
|
+
if (role === "neutral" && step !== anchorShade) {
|
|
1047
1050
|
targetC = Math.min(targetC, maxNeutralChroma);
|
|
1048
1051
|
}
|
|
1049
1052
|
scale[step] = oklchToHex(targetL, targetC, inputH);
|
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, R as ResolvedThemeConfig, c as GeneratedPrimitives, d as ThemeOutput, e as ThemeData, f as VisorThemeConfig, g as FullShadeScale, C as ColorRole, S as SelectiveShadeScale, h as RGB, P as ParsedColor, O as OKLCH, i as SemanticTokens, j as ShadeStep } from './types-
|
|
2
|
-
export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-
|
|
1
|
+
import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, R as ResolvedThemeConfig, c as GeneratedPrimitives, d as ThemeOutput, e as ThemeData, f as VisorThemeConfig, g as FullShadeScale, C as ColorRole, S as SelectiveShadeScale, h as RGB, P as ParsedColor, O as OKLCH, i as SemanticTokens, j as ShadeStep } from './types-ljcTtODU.js';
|
|
2
|
+
export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-ljcTtODU.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -556,6 +556,20 @@ var properties = {
|
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
558
|
}
|
|
559
|
+
},
|
|
560
|
+
migrate: {
|
|
561
|
+
type: "object",
|
|
562
|
+
description: "Migration metadata consumed by `visor migrate` commands. Does not affect CSS generation or theme application.",
|
|
563
|
+
additionalProperties: false,
|
|
564
|
+
properties: {
|
|
565
|
+
"token-substitution": {
|
|
566
|
+
type: "object",
|
|
567
|
+
description: "§3.1 V7-primitive → Visor-semantic substitution table. Maps V7 CSS custom property names (with -- prefix) to Visor semantic token names. Consumed by `visor migrate token-substitution`.",
|
|
568
|
+
additionalProperties: {
|
|
569
|
+
type: "string"
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
559
573
|
}
|
|
560
574
|
};
|
|
561
575
|
var $defs = {
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
rgbToHex,
|
|
33
33
|
rgbToOklch,
|
|
34
34
|
serializeColor
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-SXT2KY6D.js";
|
|
36
36
|
|
|
37
37
|
// src/pipeline.ts
|
|
38
38
|
import { parse as parseYaml } from "yaml";
|
|
@@ -339,6 +339,18 @@ var visor_theme_schema_default = {
|
|
|
339
339
|
additionalProperties: { type: "string" }
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
|
+
},
|
|
343
|
+
migrate: {
|
|
344
|
+
type: "object",
|
|
345
|
+
description: "Migration metadata consumed by `visor migrate` commands. Does not affect CSS generation or theme application.",
|
|
346
|
+
additionalProperties: false,
|
|
347
|
+
properties: {
|
|
348
|
+
"token-substitution": {
|
|
349
|
+
type: "object",
|
|
350
|
+
description: "\xA73.1 V7-primitive \u2192 Visor-semantic substitution table. Maps V7 CSS custom property names (with -- prefix) to Visor semantic token names. Consumed by `visor migrate token-substitution`.",
|
|
351
|
+
additionalProperties: { type: "string" }
|
|
352
|
+
}
|
|
353
|
+
}
|
|
342
354
|
}
|
|
343
355
|
},
|
|
344
356
|
$defs: {
|
|
@@ -839,13 +851,17 @@ var SEMANTIC_TEXT_MAP = {
|
|
|
839
851
|
light: { role: "neutral", shade: 900 },
|
|
840
852
|
dark: { role: "neutral", shade: 50 }
|
|
841
853
|
},
|
|
854
|
+
// VI-346: rebalanced to fixed-L shades (700/300, 600/400) so AA contrast holds
|
|
855
|
+
// by default for any reasonable input neutral. Shade 500 is brand-anchored
|
|
856
|
+
// (variable L = input itself) and therefore forbidden for any text level that
|
|
857
|
+
// must clear AA. See docs/token-rules.md and SEMANTIC_TEXT_MAP rationale.
|
|
842
858
|
secondary: {
|
|
843
|
-
light: { role: "neutral", shade:
|
|
844
|
-
dark: { role: "neutral", shade:
|
|
859
|
+
light: { role: "neutral", shade: 700 },
|
|
860
|
+
dark: { role: "neutral", shade: 300 }
|
|
845
861
|
},
|
|
846
862
|
tertiary: {
|
|
847
|
-
light: { role: "neutral", shade:
|
|
848
|
-
dark: { role: "neutral", shade:
|
|
863
|
+
light: { role: "neutral", shade: 600 },
|
|
864
|
+
dark: { role: "neutral", shade: 400 }
|
|
849
865
|
},
|
|
850
866
|
disabled: {
|
|
851
867
|
light: { role: "neutral", shade: 300 },
|
|
@@ -976,6 +992,29 @@ var SEMANTIC_SURFACE_MAP = {
|
|
|
976
992
|
"info-default": {
|
|
977
993
|
light: { role: "info", shade: 500 },
|
|
978
994
|
dark: { role: "info", shade: 500 }
|
|
995
|
+
},
|
|
996
|
+
// 5-tier ordinal elevation scale — deepest (0) to highest (4)
|
|
997
|
+
// Light mode: BO-10 near-white ramp (white → neutral-300).
|
|
998
|
+
// Dark mode: deep neutral ramp (neutral-950 → neutral-600).
|
|
999
|
+
"elev-0": {
|
|
1000
|
+
light: { constant: "#ffffff" },
|
|
1001
|
+
dark: { role: "neutral", shade: 950 }
|
|
1002
|
+
},
|
|
1003
|
+
"elev-1": {
|
|
1004
|
+
light: { role: "neutral", shade: 50 },
|
|
1005
|
+
dark: { role: "neutral", shade: 900 }
|
|
1006
|
+
},
|
|
1007
|
+
"elev-2": {
|
|
1008
|
+
light: { role: "neutral", shade: 100 },
|
|
1009
|
+
dark: { role: "neutral", shade: 800 }
|
|
1010
|
+
},
|
|
1011
|
+
"elev-3": {
|
|
1012
|
+
light: { role: "neutral", shade: 200 },
|
|
1013
|
+
dark: { role: "neutral", shade: 700 }
|
|
1014
|
+
},
|
|
1015
|
+
"elev-4": {
|
|
1016
|
+
light: { role: "neutral", shade: 300 },
|
|
1017
|
+
dark: { role: "neutral", shade: 600 }
|
|
979
1018
|
}
|
|
980
1019
|
};
|
|
981
1020
|
var SEMANTIC_BORDER_MAP = {
|
|
@@ -1826,35 +1865,75 @@ function colorToRgb(color) {
|
|
|
1826
1865
|
const parsed = parseColor(color);
|
|
1827
1866
|
return parsed ? parsed.rgb : [0, 0, 0];
|
|
1828
1867
|
}
|
|
1868
|
+
var STANDARD_TEXT_TOKENS = ["primary", "secondary", "tertiary"];
|
|
1869
|
+
var STATUS_TEXT_TOKENS = ["error", "warning", "success", "info"];
|
|
1829
1870
|
function checkContrastWarnings(config, issues) {
|
|
1830
1871
|
const resolved = resolveConfig(config);
|
|
1872
|
+
const lightPrimitives = generatePrimitives(resolved);
|
|
1873
|
+
const darkPrimitives = generateDarkPrimitives(resolved, lightPrimitives);
|
|
1874
|
+
const tokens = applyOverrides(
|
|
1875
|
+
assignSemanticTokens(lightPrimitives, darkPrimitives, resolved),
|
|
1876
|
+
resolved.overrides
|
|
1877
|
+
);
|
|
1831
1878
|
const lightBg = resolved.colors.background;
|
|
1832
1879
|
const lightSurface = resolved.colors.surface;
|
|
1833
1880
|
const primary = resolved.colors.primary;
|
|
1834
1881
|
const lightBgRgb = colorToRgb(lightBg);
|
|
1835
1882
|
const lightSurfaceRgb = colorToRgb(lightSurface);
|
|
1836
|
-
const
|
|
1837
|
-
const
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
);
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
);
|
|
1883
|
+
const darkBg = resolved["colors-dark"]?.background ?? "#0a0a0a";
|
|
1884
|
+
const darkSurface = resolved["colors-dark"]?.surface ?? "#171717";
|
|
1885
|
+
const darkPrimary = resolved["colors-dark"]?.primary ?? primary;
|
|
1886
|
+
const darkBgRgb = colorToRgb(darkBg);
|
|
1887
|
+
const darkSurfaceRgb = colorToRgb(darkSurface);
|
|
1888
|
+
const textTokensToCheck = [...STANDARD_TEXT_TOKENS, ...STATUS_TEXT_TOKENS];
|
|
1889
|
+
for (const tokenName of textTokensToCheck) {
|
|
1890
|
+
const tokenValue = tokens.text[tokenName];
|
|
1891
|
+
if (!tokenValue) continue;
|
|
1892
|
+
const fullName = `text-${tokenName}`;
|
|
1893
|
+
const lightOnBg = getContrastRatio(tokenValue.light, lightBg, lightBgRgb);
|
|
1894
|
+
if (lightOnBg < CONTRAST_TEXT_AA) {
|
|
1895
|
+
issues.push(
|
|
1896
|
+
issue(
|
|
1897
|
+
"warning",
|
|
1898
|
+
"WCAG_CONTRAST",
|
|
1899
|
+
`Light mode: ${fullName} on background has contrast ratio ${lightOnBg.toFixed(2)}:1 (needs >= ${CONTRAST_TEXT_AA}:1)`,
|
|
1900
|
+
"colors.background"
|
|
1901
|
+
)
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
const lightOnSurface = getContrastRatio(tokenValue.light, lightSurface, lightSurfaceRgb);
|
|
1905
|
+
if (lightOnSurface < CONTRAST_TEXT_AA) {
|
|
1906
|
+
issues.push(
|
|
1907
|
+
issue(
|
|
1908
|
+
"warning",
|
|
1909
|
+
"WCAG_CONTRAST",
|
|
1910
|
+
`Light mode: ${fullName} on surface has contrast ratio ${lightOnSurface.toFixed(2)}:1 (needs >= ${CONTRAST_TEXT_AA}:1)`,
|
|
1911
|
+
"colors.surface"
|
|
1912
|
+
)
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
const darkOnBg = getContrastRatio(tokenValue.dark, darkBg, darkBgRgb);
|
|
1916
|
+
if (darkOnBg < CONTRAST_TEXT_AA) {
|
|
1917
|
+
issues.push(
|
|
1918
|
+
issue(
|
|
1919
|
+
"warning",
|
|
1920
|
+
"WCAG_CONTRAST",
|
|
1921
|
+
`Dark mode: ${fullName} on background has contrast ratio ${darkOnBg.toFixed(2)}:1 (needs >= ${CONTRAST_TEXT_AA}:1)`,
|
|
1922
|
+
"colors-dark.background"
|
|
1923
|
+
)
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
const darkOnSurface = getContrastRatio(tokenValue.dark, darkSurface, darkSurfaceRgb);
|
|
1927
|
+
if (darkOnSurface < CONTRAST_TEXT_AA) {
|
|
1928
|
+
issues.push(
|
|
1929
|
+
issue(
|
|
1930
|
+
"warning",
|
|
1931
|
+
"WCAG_CONTRAST",
|
|
1932
|
+
`Dark mode: ${fullName} on surface has contrast ratio ${darkOnSurface.toFixed(2)}:1 (needs >= ${CONTRAST_TEXT_AA}:1)`,
|
|
1933
|
+
"colors-dark.surface"
|
|
1934
|
+
)
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1858
1937
|
}
|
|
1859
1938
|
const primaryOnBg = getContrastRatio(primary, lightBg, lightBgRgb);
|
|
1860
1939
|
if (primaryOnBg < CONTRAST_INTERACTIVE_AA) {
|
|
@@ -1878,34 +1957,6 @@ function checkContrastWarnings(config, issues) {
|
|
|
1878
1957
|
)
|
|
1879
1958
|
);
|
|
1880
1959
|
}
|
|
1881
|
-
const darkBg = resolved["colors-dark"]?.background ?? "#0a0a0a";
|
|
1882
|
-
const darkSurface = resolved["colors-dark"]?.surface ?? "#171717";
|
|
1883
|
-
const darkPrimary = resolved["colors-dark"]?.primary ?? primary;
|
|
1884
|
-
const darkBgRgb = colorToRgb(darkBg);
|
|
1885
|
-
const darkSurfaceRgb = colorToRgb(darkSurface);
|
|
1886
|
-
const textLight = "#f9fafb";
|
|
1887
|
-
const textOnDarkBg = getContrastRatio(textLight, darkBg, darkBgRgb);
|
|
1888
|
-
if (textOnDarkBg < CONTRAST_TEXT_AA) {
|
|
1889
|
-
issues.push(
|
|
1890
|
-
issue(
|
|
1891
|
-
"warning",
|
|
1892
|
-
"WCAG_CONTRAST",
|
|
1893
|
-
`Dark mode: text-primary on background has contrast ratio ${textOnDarkBg.toFixed(2)}:1 (needs >= ${CONTRAST_TEXT_AA}:1)`,
|
|
1894
|
-
"colors-dark.background"
|
|
1895
|
-
)
|
|
1896
|
-
);
|
|
1897
|
-
}
|
|
1898
|
-
const textOnDarkSurface = getContrastRatio(textLight, darkSurface, darkSurfaceRgb);
|
|
1899
|
-
if (textOnDarkSurface < CONTRAST_TEXT_AA) {
|
|
1900
|
-
issues.push(
|
|
1901
|
-
issue(
|
|
1902
|
-
"warning",
|
|
1903
|
-
"WCAG_CONTRAST",
|
|
1904
|
-
`Dark mode: text-primary on surface has contrast ratio ${textOnDarkSurface.toFixed(2)}:1 (needs >= ${CONTRAST_TEXT_AA}:1)`,
|
|
1905
|
-
"colors-dark.surface"
|
|
1906
|
-
)
|
|
1907
|
-
);
|
|
1908
|
-
}
|
|
1909
1960
|
const darkPrimaryOnBg = getContrastRatio(darkPrimary, darkBg, darkBgRgb);
|
|
1910
1961
|
if (darkPrimaryOnBg < CONTRAST_INTERACTIVE_AA) {
|
|
1911
1962
|
issues.push(
|
|
@@ -247,6 +247,21 @@ interface VisorThemeConfig {
|
|
|
247
247
|
light?: Record<string, string>;
|
|
248
248
|
dark?: Record<string, string>;
|
|
249
249
|
};
|
|
250
|
+
/**
|
|
251
|
+
* Migration metadata — optional, additive, consumed by `visor migrate` commands.
|
|
252
|
+
* Does not affect CSS generation or theme application.
|
|
253
|
+
*/
|
|
254
|
+
migrate?: {
|
|
255
|
+
/**
|
|
256
|
+
* §3.1 V7-primitive → Visor-semantic substitution table.
|
|
257
|
+
* Maps V7 CSS custom property names (without `var()`) to Visor semantic
|
|
258
|
+
* token names. Consumed by `visor migrate token-substitution`.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* { "--panel": "--surface-card", "--text": "--text-primary" }
|
|
262
|
+
*/
|
|
263
|
+
"token-substitution"?: Record<string, string>;
|
|
264
|
+
};
|
|
250
265
|
}
|
|
251
266
|
/** Config with all defaults resolved */
|
|
252
267
|
interface ResolvedThemeConfig {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Theme engine for the Visor design system — shade generation, token mapping, font resolution, and import/export for .visor.yaml themes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -299,6 +299,18 @@
|
|
|
299
299
|
"additionalProperties": { "type": "string" }
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
|
+
},
|
|
303
|
+
"migrate": {
|
|
304
|
+
"type": "object",
|
|
305
|
+
"description": "Migration metadata consumed by `visor migrate` commands. Does not affect CSS generation or theme application.",
|
|
306
|
+
"additionalProperties": false,
|
|
307
|
+
"properties": {
|
|
308
|
+
"token-substitution": {
|
|
309
|
+
"type": "object",
|
|
310
|
+
"description": "§3.1 V7-primitive → Visor-semantic substitution table. Maps V7 CSS custom property names (with -- prefix) to Visor semantic token names. Consumed by `visor migrate token-substitution`.",
|
|
311
|
+
"additionalProperties": { "type": "string" }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
302
314
|
}
|
|
303
315
|
},
|
|
304
316
|
"$defs": {
|