@loworbitstudio/visor-theme-engine 0.19.0 → 0.20.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 +5 -1
- package/dist/adapters/index.js +15 -24
- package/dist/{chunk-32DC5DAR.js → chunk-JDYLDGUN.js} +120 -20
- package/dist/index.d.ts +133 -4
- package/dist/index.js +57 -2
- package/dist/{types-DF5StphJ.d.ts → types-BG-YT4JW.d.ts} +12 -0
- package/package.json +1 -1
- package/src/visor-theme.schema.json +1 -1
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-
|
|
1
|
+
import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-BG-YT4JW.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter types for the Visor theme engine.
|
|
@@ -37,6 +37,10 @@ interface NextJSAdapterOptions extends AdapterOptions {
|
|
|
37
37
|
* `@layer visor-base` origination block that binds theme tokens to the page
|
|
38
38
|
* root (default: true). Set false for consumers that ship Tailwind preflight
|
|
39
39
|
* or their own reset. See VI-616.
|
|
40
|
+
*
|
|
41
|
+
* VI-638: the base block also carries `font-size: var(--font-size-base)`,
|
|
42
|
+
* which is how `typography.scale` reaches the page. A consumer that declines
|
|
43
|
+
* it must bind the base size itself — on `body`, not `:root`.
|
|
40
44
|
*/
|
|
41
45
|
includeBaseLayer?: boolean;
|
|
42
46
|
}
|
package/dist/adapters/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
collectBrandPassthrough,
|
|
9
9
|
fontStack,
|
|
10
10
|
generateDarkCss,
|
|
11
|
+
generateFontSizeDecls,
|
|
12
|
+
generateFontWeightDecls,
|
|
11
13
|
generateHairlineDecls,
|
|
12
14
|
generateIntentDecls,
|
|
13
15
|
generateLightCss,
|
|
@@ -21,7 +23,7 @@ import {
|
|
|
21
23
|
resolveThemeBrand,
|
|
22
24
|
resolveThemeFonts,
|
|
23
25
|
sectionComment
|
|
24
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-JDYLDGUN.js";
|
|
25
27
|
|
|
26
28
|
// src/adapters/brand-passthrough.ts
|
|
27
29
|
var SENTINEL_COLOR = "#ff00ff";
|
|
@@ -212,7 +214,11 @@ function nextjsAdapter(input, options) {
|
|
|
212
214
|
baseLines.push(
|
|
213
215
|
block(scopePrefix ?? "body", [
|
|
214
216
|
"font-family: var(--font-body);",
|
|
215
|
-
|
|
217
|
+
// VI-638: the scaled ramp is the single mechanism that sets type size,
|
|
218
|
+
// and the base step is what the page inherits. This binding lives on
|
|
219
|
+
// `body` (or the body-class scope), never `:root` — the ramp is
|
|
220
|
+
// expressed in `rem`, so a scaled root would multiply it a second time.
|
|
221
|
+
"font-size: var(--font-size-base, 1rem);",
|
|
216
222
|
"color: var(--text-primary);",
|
|
217
223
|
"background: var(--surface-page, var(--surface-background));"
|
|
218
224
|
])
|
|
@@ -537,23 +543,8 @@ function generateTypographyDecls(config, aliases) {
|
|
|
537
543
|
decls.push(`--font-heading: ${fontStack(headingFamily, aliases)};`);
|
|
538
544
|
decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
|
|
539
545
|
decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
sm: 14,
|
|
543
|
-
base: 16,
|
|
544
|
-
lg: 18,
|
|
545
|
-
xl: 20,
|
|
546
|
-
"2xl": 24,
|
|
547
|
-
"3xl": 30,
|
|
548
|
-
"4xl": 36
|
|
549
|
-
};
|
|
550
|
-
for (const [name, px] of Object.entries(fontSizes)) {
|
|
551
|
-
decls.push(`--font-size-${name}: ${px / 16}rem; /* ${px}px */`);
|
|
552
|
-
}
|
|
553
|
-
decls.push(`--font-weight-normal: ${config.typography.body.weight};`);
|
|
554
|
-
decls.push("--font-weight-medium: 500;");
|
|
555
|
-
decls.push(`--font-weight-semibold: ${config.typography.heading.weight};`);
|
|
556
|
-
decls.push("--font-weight-bold: 700;");
|
|
546
|
+
decls.push(...generateFontSizeDecls(config.typography.scale));
|
|
547
|
+
decls.push(...generateFontWeightDecls(config.typography));
|
|
557
548
|
const lineHeights = {
|
|
558
549
|
none: 1,
|
|
559
550
|
tight: 1.25,
|
|
@@ -686,7 +677,6 @@ function docsAdapter(input, options) {
|
|
|
686
677
|
fontLines.push("");
|
|
687
678
|
}
|
|
688
679
|
}
|
|
689
|
-
const scale = input.config.typography?.scale ?? 1;
|
|
690
680
|
const emittedFamilies = /* @__PURE__ */ new Set();
|
|
691
681
|
for (const font of fontSlots) {
|
|
692
682
|
if (font && font.source === "visor-fonts" && !emittedFamilies.has(font.family)) {
|
|
@@ -700,9 +690,6 @@ function docsAdapter(input, options) {
|
|
|
700
690
|
fontLines.push(` font-weight: ${weight};`);
|
|
701
691
|
fontLines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
|
|
702
692
|
fontLines.push(` font-display: ${font.display};`);
|
|
703
|
-
if (scale !== 1) {
|
|
704
|
-
fontLines.push(` size-adjust: ${Math.round(scale * 100)}%;`);
|
|
705
|
-
}
|
|
706
693
|
fontLines.push("}");
|
|
707
694
|
fontLines.push("");
|
|
708
695
|
}
|
|
@@ -712,7 +699,11 @@ function docsAdapter(input, options) {
|
|
|
712
699
|
lines.push("\n/* \u2500\u2500 Section 1: Shared tokens (mode-independent) \u2500\u2500 */");
|
|
713
700
|
const sharedDecls = [
|
|
714
701
|
"min-height: 100vh;",
|
|
715
|
-
|
|
702
|
+
// VI-638: the scaled ramp is the single mechanism that sets type size, and
|
|
703
|
+
// the base step is what the page inherits. Safe here because the scope is a
|
|
704
|
+
// class on a wrapper, never `:root` — a scaled root would multiply the
|
|
705
|
+
// rem-expressed ramp a second time.
|
|
706
|
+
"font-size: var(--font-size-base, 1rem);",
|
|
716
707
|
// BO-56: pin UA chrome to the brand's single mode (adaptive emits nothing).
|
|
717
708
|
...singleMode ? [`color-scheme: ${singleMode};`] : [],
|
|
718
709
|
`background: var(--surface-page, var(--surface-background));`,
|
|
@@ -1928,6 +1928,99 @@ function applyOverrides(tokens, overrides) {
|
|
|
1928
1928
|
return result;
|
|
1929
1929
|
}
|
|
1930
1930
|
|
|
1931
|
+
// src/font-weights.ts
|
|
1932
|
+
var WEIGHT_RAMP_TARGETS = {
|
|
1933
|
+
medium: 500,
|
|
1934
|
+
semibold: 600,
|
|
1935
|
+
bold: 700
|
|
1936
|
+
};
|
|
1937
|
+
var SLOTS = ["heading", "display", "body", "mono"];
|
|
1938
|
+
function loadedWeights(typography) {
|
|
1939
|
+
const set = /* @__PURE__ */ new Set();
|
|
1940
|
+
for (const slot of SLOTS) {
|
|
1941
|
+
const cfg = typography[slot];
|
|
1942
|
+
if (!cfg) continue;
|
|
1943
|
+
if (cfg.weights?.length) {
|
|
1944
|
+
for (const w of cfg.weights) set.add(w);
|
|
1945
|
+
} else if (typeof cfg.weight === "number") {
|
|
1946
|
+
set.add(cfg.weight);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return [...set].sort((a, b) => a - b);
|
|
1950
|
+
}
|
|
1951
|
+
function declaresWeights(typography) {
|
|
1952
|
+
return SLOTS.some((slot) => Boolean(typography[slot]?.weights?.length));
|
|
1953
|
+
}
|
|
1954
|
+
function matchLoadedWeight(desired, loaded) {
|
|
1955
|
+
if (loaded.length === 0) return desired;
|
|
1956
|
+
if (loaded.includes(desired)) return desired;
|
|
1957
|
+
const ascending = [...loaded].sort((a, b) => a - b);
|
|
1958
|
+
const lighter = ascending.filter((w) => w < desired).reverse();
|
|
1959
|
+
const heavier = ascending.filter((w) => w > desired);
|
|
1960
|
+
if (desired >= 400 && desired <= 500) {
|
|
1961
|
+
const upToFive = heavier.filter((w) => w <= 500);
|
|
1962
|
+
const aboveFive = heavier.filter((w) => w > 500);
|
|
1963
|
+
return upToFive[0] ?? lighter[0] ?? aboveFive[0];
|
|
1964
|
+
}
|
|
1965
|
+
if (desired < 400) {
|
|
1966
|
+
return lighter[0] ?? heavier[0];
|
|
1967
|
+
}
|
|
1968
|
+
return heavier[0] ?? lighter[0];
|
|
1969
|
+
}
|
|
1970
|
+
function resolveWeightRamp(typography) {
|
|
1971
|
+
const bodyWeight = typography.body?.weight ?? 400;
|
|
1972
|
+
if (!declaresWeights(typography)) {
|
|
1973
|
+
return {
|
|
1974
|
+
normal: bodyWeight,
|
|
1975
|
+
medium: WEIGHT_RAMP_TARGETS.medium,
|
|
1976
|
+
semibold: WEIGHT_RAMP_TARGETS.semibold,
|
|
1977
|
+
bold: WEIGHT_RAMP_TARGETS.bold
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
const loaded = loadedWeights(typography);
|
|
1981
|
+
return {
|
|
1982
|
+
normal: matchLoadedWeight(bodyWeight, loaded),
|
|
1983
|
+
medium: matchLoadedWeight(WEIGHT_RAMP_TARGETS.medium, loaded),
|
|
1984
|
+
semibold: matchLoadedWeight(WEIGHT_RAMP_TARGETS.semibold, loaded),
|
|
1985
|
+
bold: matchLoadedWeight(WEIGHT_RAMP_TARGETS.bold, loaded)
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
function resolveRoleWeight(declared, typography) {
|
|
1989
|
+
if (!declaresWeights(typography)) return declared;
|
|
1990
|
+
return matchLoadedWeight(declared, loadedWeights(typography));
|
|
1991
|
+
}
|
|
1992
|
+
function generateFontWeightDecls(typography) {
|
|
1993
|
+
const ramp = resolveWeightRamp(typography);
|
|
1994
|
+
const decls = [
|
|
1995
|
+
`--font-weight-normal: ${ramp.normal};`,
|
|
1996
|
+
`--font-weight-medium: ${ramp.medium};`,
|
|
1997
|
+
`--font-weight-semibold: ${ramp.semibold};`,
|
|
1998
|
+
`--font-weight-bold: ${ramp.bold};`,
|
|
1999
|
+
// Role primitives. These are why `semibold` was pinned in the first place:
|
|
2000
|
+
// packages/tokens defined `--weight-heading: var(--font-weight-semibold)`
|
|
2001
|
+
// and the engine emitted no per-theme value, so hijacking `semibold` was
|
|
2002
|
+
// the only channel a theme's heading weight had. Each role has its own
|
|
2003
|
+
// primitive now, and the semantic `--weight-*` roles resolve through them.
|
|
2004
|
+
//
|
|
2005
|
+
// Emitted as `--font-weight-*` primitives rather than the bare `--weight-*`
|
|
2006
|
+
// names on purpose: the semantic names are defined in @layer visor-semantic,
|
|
2007
|
+
// which wins over visor-primitives, so a theme writing them directly would
|
|
2008
|
+
// be overridden by the tokens package on a `:root`-scoped consumer.
|
|
2009
|
+
`--font-weight-heading: ${resolveRoleWeight(typography.heading.weight, typography)};`,
|
|
2010
|
+
`--font-weight-body: ${resolveRoleWeight(typography.body.weight, typography)};`,
|
|
2011
|
+
`--font-weight-display: ${resolveRoleWeight(typography.display.weight, typography)};`,
|
|
2012
|
+
// Retained: `--weight-display` has no competing definition in the tokens
|
|
2013
|
+
// package and predates this change, so consumers already reference it.
|
|
2014
|
+
`--weight-display: ${resolveRoleWeight(typography.display.weight, typography)};`
|
|
2015
|
+
];
|
|
2016
|
+
if (declaresWeights(typography)) {
|
|
2017
|
+
for (const weight of loadedWeights(typography)) {
|
|
2018
|
+
decls.push(`--font-weight-${weight}: ${weight};`);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return decls;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1931
2024
|
// src/fonts/theme-alias.ts
|
|
1932
2025
|
var EMPTY_ALIASES = /* @__PURE__ */ new Map();
|
|
1933
2026
|
function aliasFamily(family, themeSlug) {
|
|
@@ -2024,33 +2117,32 @@ function generateShadowPrimitives(config) {
|
|
|
2024
2117
|
`--shadow-xl: ${config.shadows.xl};`
|
|
2025
2118
|
];
|
|
2026
2119
|
}
|
|
2120
|
+
var FONT_SIZE_RAMP_PX = {
|
|
2121
|
+
xs: 12,
|
|
2122
|
+
sm: 14,
|
|
2123
|
+
base: 16,
|
|
2124
|
+
lg: 18,
|
|
2125
|
+
xl: 20,
|
|
2126
|
+
"2xl": 24,
|
|
2127
|
+
"3xl": 30,
|
|
2128
|
+
"4xl": 36
|
|
2129
|
+
};
|
|
2130
|
+
function generateFontSizeDecls(scale) {
|
|
2131
|
+
return Object.entries(FONT_SIZE_RAMP_PX).map(([name, px]) => {
|
|
2132
|
+
const scaled = px * scale;
|
|
2133
|
+
const rem = Number((scaled / 16).toFixed(5));
|
|
2134
|
+
return `--font-size-${name}: ${rem}rem; /* ${Number(scaled.toFixed(2))}px */`;
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2027
2137
|
function generateTypographyPrimitives(config, aliases = EMPTY_ALIASES) {
|
|
2028
2138
|
const decls = [];
|
|
2029
|
-
const scale = config.typography.scale;
|
|
2030
|
-
decls.push(`font-size: ${scale === 1 ? "1rem" : `${scale}rem`};`);
|
|
2031
2139
|
decls.push(`--font-heading: ${fontStack(config.typography.heading.family, aliases)};`);
|
|
2032
2140
|
decls.push(`--font-display: ${fontStack(config.typography.display.family, aliases)};`);
|
|
2033
2141
|
decls.push(`--font-sans: ${fontStack(config.typography.body.family, aliases)};`);
|
|
2034
2142
|
decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
|
|
2035
2143
|
decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
sm: 14,
|
|
2039
|
-
base: 16,
|
|
2040
|
-
lg: 18,
|
|
2041
|
-
xl: 20,
|
|
2042
|
-
"2xl": 24,
|
|
2043
|
-
"3xl": 30,
|
|
2044
|
-
"4xl": 36
|
|
2045
|
-
};
|
|
2046
|
-
for (const [name, px] of Object.entries(fontSizes)) {
|
|
2047
|
-
decls.push(`--font-size-${name}: ${px / 16}rem; /* ${px}px */`);
|
|
2048
|
-
}
|
|
2049
|
-
decls.push(`--font-weight-normal: ${config.typography.body.weight};`);
|
|
2050
|
-
decls.push("--font-weight-medium: 500;");
|
|
2051
|
-
decls.push(`--font-weight-semibold: ${config.typography.heading.weight};`);
|
|
2052
|
-
decls.push("--font-weight-bold: 700;");
|
|
2053
|
-
decls.push(`--weight-display: ${config.typography.display.weight};`);
|
|
2144
|
+
decls.push(...generateFontSizeDecls(config.typography.scale));
|
|
2145
|
+
decls.push(...generateFontWeightDecls(config.typography));
|
|
2054
2146
|
const lineHeights = {
|
|
2055
2147
|
none: 1,
|
|
2056
2148
|
tight: 1.25,
|
|
@@ -2464,11 +2556,19 @@ export {
|
|
|
2464
2556
|
collectBrandPassthrough,
|
|
2465
2557
|
hasBrandPassthrough,
|
|
2466
2558
|
applyOverrides,
|
|
2559
|
+
WEIGHT_RAMP_TARGETS,
|
|
2560
|
+
loadedWeights,
|
|
2561
|
+
declaresWeights,
|
|
2562
|
+
matchLoadedWeight,
|
|
2563
|
+
resolveWeightRamp,
|
|
2564
|
+
resolveRoleWeight,
|
|
2565
|
+
generateFontWeightDecls,
|
|
2467
2566
|
aliasFamily,
|
|
2468
2567
|
fontStack,
|
|
2469
2568
|
header,
|
|
2470
2569
|
sectionComment,
|
|
2471
2570
|
block2 as block,
|
|
2571
|
+
generateFontSizeDecls,
|
|
2472
2572
|
generatePrimitivesCss,
|
|
2473
2573
|
generateSemanticCss,
|
|
2474
2574
|
generateTextScaleAliasDecls,
|
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, 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-
|
|
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 COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-
|
|
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-BG-YT4JW.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 COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-BG-YT4JW.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -585,7 +585,7 @@ var properties = {
|
|
|
585
585
|
},
|
|
586
586
|
scale: {
|
|
587
587
|
type: "number",
|
|
588
|
-
description: "Type scale multiplier applied to the font-size ramp. Default: 1."
|
|
588
|
+
description: "Type scale multiplier applied to the font-size ramp: every --font-size-* step is multiplied by it, and the page inherits the scaled --font-size-base. Default: 1. CSS adapters only — the Flutter adapter emits Material 3 slot sizes verbatim and ignores this field."
|
|
589
589
|
},
|
|
590
590
|
"text-on-light": {
|
|
591
591
|
type: "string",
|
|
@@ -1922,6 +1922,135 @@ declare function generateDarkCss(tokens: SemanticTokens, options?: {
|
|
|
1922
1922
|
}): string;
|
|
1923
1923
|
declare function generateFullBundleCss(primitives: GeneratedPrimitives, tokens: SemanticTokens, config: ResolvedThemeConfig): string;
|
|
1924
1924
|
|
|
1925
|
+
/**
|
|
1926
|
+
* Font-weight resolution (VI-639).
|
|
1927
|
+
*
|
|
1928
|
+
* The named weight ramp used to be three literals and one role leak:
|
|
1929
|
+
*
|
|
1930
|
+
* ```
|
|
1931
|
+
* --font-weight-normal: <body.weight>
|
|
1932
|
+
* --font-weight-medium: 500 // literal
|
|
1933
|
+
* --font-weight-semibold: <heading.weight> // a different concept entirely
|
|
1934
|
+
* --font-weight-bold: 700 // literal
|
|
1935
|
+
* ```
|
|
1936
|
+
*
|
|
1937
|
+
* `semibold` is not a heading. Pinning it there collapsed it onto `medium` on
|
|
1938
|
+
* every theme whose heading weight is 500 (the Blacklight family), onto
|
|
1939
|
+
* `normal` where it is 400 (knowmentum), and inverted the ramp where it is
|
|
1940
|
+
* above `bold` (strata emitted `semibold: 800` over `bold: 700`). Meanwhile the
|
|
1941
|
+
* literals ignored the `weights` array a theme actually loaded, so a theme
|
|
1942
|
+
* declaring `[200, 400, 600, 900]` got tokens pointing at faces it never
|
|
1943
|
+
* fetched — which the browser then silently substituted.
|
|
1944
|
+
*
|
|
1945
|
+
* Resolution here follows the CSS font-matching algorithm's weight-selection
|
|
1946
|
+
* order (CSS Fonts 4 §5.2) rather than naive nearest-numeric. The browser is
|
|
1947
|
+
* already performing exactly this substitution at render time, so an emitted
|
|
1948
|
+
* token becomes the weight that was *already rendering* — which is what makes
|
|
1949
|
+
* this change visually neutral everywhere except where the token was wrong.
|
|
1950
|
+
*/
|
|
1951
|
+
/** Canonical targets for the named ramp above `normal`. */
|
|
1952
|
+
declare const WEIGHT_RAMP_TARGETS: {
|
|
1953
|
+
readonly medium: 500;
|
|
1954
|
+
readonly semibold: 600;
|
|
1955
|
+
readonly bold: 700;
|
|
1956
|
+
};
|
|
1957
|
+
/** The typography slots that can declare a family, weight and weights array. */
|
|
1958
|
+
declare const SLOTS: readonly ["heading", "display", "body", "mono"];
|
|
1959
|
+
type WeightedSlot = {
|
|
1960
|
+
weight?: number;
|
|
1961
|
+
weights?: number[];
|
|
1962
|
+
} | undefined;
|
|
1963
|
+
type SlottedTypography = Partial<Record<(typeof SLOTS)[number], WeightedSlot>>;
|
|
1964
|
+
/**
|
|
1965
|
+
* The weights a theme actually loads: each slot's explicit `weights` array, or
|
|
1966
|
+
* its single `weight` when it declares none — mirroring how the font pipeline
|
|
1967
|
+
* resolves the set it fetches.
|
|
1968
|
+
*/
|
|
1969
|
+
declare function loadedWeights(typography: SlottedTypography): number[];
|
|
1970
|
+
/**
|
|
1971
|
+
* Whether any slot declares an explicit `weights` array.
|
|
1972
|
+
*
|
|
1973
|
+
* Resolution is gated on this. A theme that declares one has told us what it
|
|
1974
|
+
* loads, so we can hold every emitted token to that set. A theme that hasn't
|
|
1975
|
+
* keeps the historical emission untouched — snapping against a set inferred
|
|
1976
|
+
* from three `weight` fields would move tokens on a claim the theme never made
|
|
1977
|
+
* (and says nothing at all about a system font stack, which has whatever the OS
|
|
1978
|
+
* has). `validateFontWeights` warns about those instead.
|
|
1979
|
+
*/
|
|
1980
|
+
declare function declaresWeights(typography: SlottedTypography): boolean;
|
|
1981
|
+
/**
|
|
1982
|
+
* Pick the weight a browser would actually use for `desired`, given the faces
|
|
1983
|
+
* the theme loaded — CSS Fonts 4 §5.2 weight-matching order:
|
|
1984
|
+
*
|
|
1985
|
+
* - exact match wins;
|
|
1986
|
+
* - desired 400–500: heavier faces up to 500 ascending, then lighter descending,
|
|
1987
|
+
* then heavier than 500 ascending;
|
|
1988
|
+
* - desired below 400: lighter descending, then heavier ascending;
|
|
1989
|
+
* - desired above 500: heavier ascending, then lighter descending.
|
|
1990
|
+
*
|
|
1991
|
+
* Returns `desired` unchanged when the theme loads nothing (nothing to match
|
|
1992
|
+
* against, and the system font has its own ideas).
|
|
1993
|
+
*/
|
|
1994
|
+
declare function matchLoadedWeight(desired: number, loaded: readonly number[]): number;
|
|
1995
|
+
/** The four named ramp steps a theme emits, resolved against what it loaded. */
|
|
1996
|
+
interface ResolvedWeightRamp {
|
|
1997
|
+
normal: number;
|
|
1998
|
+
medium: number;
|
|
1999
|
+
semibold: number;
|
|
2000
|
+
bold: number;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Resolve the named ramp.
|
|
2004
|
+
*
|
|
2005
|
+
* `normal` tracks the theme's body weight rather than a hard 400 — it is the
|
|
2006
|
+
* ramp's zero point, and Blacklight's `body.weight: 300` is a deliberate
|
|
2007
|
+
* choice, not a rounding error. It is still coerced to a loaded face (strata
|
|
2008
|
+
* declares `body.weight: 450` and loads `[400, 700, 900]`).
|
|
2009
|
+
*
|
|
2010
|
+
* `medium / semibold / bold` resolve the canonical 500 / 600 / 700 targets.
|
|
2011
|
+
* Names can still share a value on a family that cannot separate them — two
|
|
2012
|
+
* loaded faces cannot back four distinct names — but the result is now the
|
|
2013
|
+
* truth about what renders rather than a number nothing will honour.
|
|
2014
|
+
*/
|
|
2015
|
+
declare function resolveWeightRamp(typography: SlottedTypography & {
|
|
2016
|
+
body?: WeightedSlot;
|
|
2017
|
+
}): ResolvedWeightRamp;
|
|
2018
|
+
/**
|
|
2019
|
+
* Resolve a role weight (`--weight-heading`, `--weight-body`,
|
|
2020
|
+
* `--weight-display`) to a loaded face. Roles carry theme intent — what this
|
|
2021
|
+
* theme's headings weigh — and are the channel components should read when they
|
|
2022
|
+
* mean "the heading weight" rather than "a step above medium".
|
|
2023
|
+
*/
|
|
2024
|
+
declare function resolveRoleWeight(declared: number, typography: SlottedTypography): number;
|
|
2025
|
+
/**
|
|
2026
|
+
* Emit the shared weight declarations: the named ramp, the role tokens, and the
|
|
2027
|
+
* discrete ladder.
|
|
2028
|
+
*
|
|
2029
|
+
* The ladder (`--font-weight-300` … `--font-weight-800`, one per loaded face)
|
|
2030
|
+
* is what makes the `weights` array authoritative instead of decorative. Before
|
|
2031
|
+
* it, a theme could load five faces and reach four of them by name at best —
|
|
2032
|
+
* Blacklight's 800 was unreachable from CSS, so BL-987 had to write a literal.
|
|
2033
|
+
* It follows the `--text-N` / `--space-N` discrete-alias precedent from VI-451.
|
|
2034
|
+
*/
|
|
2035
|
+
declare function generateFontWeightDecls(typography: {
|
|
2036
|
+
heading: {
|
|
2037
|
+
weight: number;
|
|
2038
|
+
weights?: number[];
|
|
2039
|
+
};
|
|
2040
|
+
display: {
|
|
2041
|
+
weight: number;
|
|
2042
|
+
weights?: number[];
|
|
2043
|
+
};
|
|
2044
|
+
body: {
|
|
2045
|
+
weight: number;
|
|
2046
|
+
weights?: number[];
|
|
2047
|
+
};
|
|
2048
|
+
mono: {
|
|
2049
|
+
weight?: number;
|
|
2050
|
+
weights?: number[];
|
|
2051
|
+
};
|
|
2052
|
+
}): string[];
|
|
2053
|
+
|
|
1925
2054
|
/**
|
|
1926
2055
|
* Theme Extraction Engine
|
|
1927
2056
|
*
|
|
@@ -1997,4 +2126,4 @@ declare function cleanFontValue(val: string): string;
|
|
|
1997
2126
|
*/
|
|
1998
2127
|
declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
|
|
1999
2128
|
|
|
2000
|
-
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 };
|
|
2129
|
+
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, type ResolvedWeightRamp, 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, WEIGHT_RAMP_TARGETS, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, compositeOverBackground, declaresWeights, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFontWeightDecls, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, getLuminance, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, loadedWeights, lookupFontWeightAlias, lookupGoogleFont, matchLoadedWeight, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveRoleWeight, resolveThemeBrand, resolveThemeFonts, resolveWeightRamp, rgbToHex, serializeBrandStrategy, serializeColor, validate, validateBrandStrategy, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
VISOR_BRANDS_CDN,
|
|
10
10
|
VISOR_DEFAULT_BRAND_PATH,
|
|
11
11
|
VISOR_FONTS_CDN,
|
|
12
|
+
WEIGHT_RAMP_TARGETS,
|
|
12
13
|
allComponentTokenNames,
|
|
13
14
|
applyOverrides,
|
|
14
15
|
buildVisorBrandUrl,
|
|
@@ -17,7 +18,9 @@ import {
|
|
|
17
18
|
collectBrandPassthrough,
|
|
18
19
|
componentTokenName,
|
|
19
20
|
compositeOverBackground,
|
|
21
|
+
declaresWeights,
|
|
20
22
|
generateDarkCss,
|
|
23
|
+
generateFontWeightDecls,
|
|
21
24
|
generateFullBundleCss,
|
|
22
25
|
generateLightCss,
|
|
23
26
|
generatePreloadLinks,
|
|
@@ -34,8 +37,10 @@ import {
|
|
|
34
37
|
hexToRgb,
|
|
35
38
|
isValidColor,
|
|
36
39
|
isValidHex,
|
|
40
|
+
loadedWeights,
|
|
37
41
|
lookupFontWeightAlias,
|
|
38
42
|
lookupGoogleFont,
|
|
43
|
+
matchLoadedWeight,
|
|
39
44
|
normalizeHex,
|
|
40
45
|
oklchToHex,
|
|
41
46
|
parseColor,
|
|
@@ -47,13 +52,15 @@ import {
|
|
|
47
52
|
resolveBrandSource,
|
|
48
53
|
resolveComponentBindings,
|
|
49
54
|
resolveFont,
|
|
55
|
+
resolveRoleWeight,
|
|
50
56
|
resolveThemeBrand,
|
|
51
57
|
resolveThemeFonts,
|
|
58
|
+
resolveWeightRamp,
|
|
52
59
|
rgbToHex,
|
|
53
60
|
rgbToOklch,
|
|
54
61
|
serializeColor,
|
|
55
62
|
validateComponentBindings
|
|
56
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-JDYLDGUN.js";
|
|
57
64
|
|
|
58
65
|
// src/fonts/validate-coverage.ts
|
|
59
66
|
var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
|
|
@@ -793,7 +800,7 @@ var visor_theme_schema_default = {
|
|
|
793
800
|
},
|
|
794
801
|
scale: {
|
|
795
802
|
type: "number",
|
|
796
|
-
description: "Type scale multiplier applied to the font-size ramp. Default: 1."
|
|
803
|
+
description: "Type scale multiplier applied to the font-size ramp: every --font-size-* step is multiplied by it, and the page inherits the scaled --font-size-base. Default: 1. CSS adapters only \u2014 the Flutter adapter emits Material 3 slot sizes verbatim and ignores this field."
|
|
797
804
|
},
|
|
798
805
|
"text-on-light": {
|
|
799
806
|
type: "string",
|
|
@@ -3137,6 +3144,46 @@ function checkTypeScaleCoherence(config, issues) {
|
|
|
3137
3144
|
}
|
|
3138
3145
|
}
|
|
3139
3146
|
}
|
|
3147
|
+
function checkFontWeightCoverage(config, issues) {
|
|
3148
|
+
const typography = config.typography;
|
|
3149
|
+
if (!typography) return;
|
|
3150
|
+
const HOSTED = /* @__PURE__ */ new Set(["visor-fonts", "google-fonts", "fontshare"]);
|
|
3151
|
+
const slots = ["heading", "display", "body", "mono"];
|
|
3152
|
+
if (!declaresWeights(typography)) {
|
|
3153
|
+
const hosted = slots.filter((slot) => {
|
|
3154
|
+
const source = typography[slot]?.source;
|
|
3155
|
+
return typeof source === "string" && HOSTED.has(source);
|
|
3156
|
+
});
|
|
3157
|
+
if (hosted.length > 0) {
|
|
3158
|
+
issues.push(
|
|
3159
|
+
issue(
|
|
3160
|
+
"warning",
|
|
3161
|
+
"FONT_WEIGHTS_UNDECLARED",
|
|
3162
|
+
`typography.${hosted[0]} loads from '${typography[hosted[0]].source}' but no slot declares a 'weights' array. Named weight tokens fall back to the canonical 500/600/700 literals, which may not be faces this theme fetched. Declare 'weights' to make them resolve against what you actually load.`,
|
|
3163
|
+
`typography.${hosted[0]}.weights`
|
|
3164
|
+
)
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
const loaded = loadedWeights(typography);
|
|
3170
|
+
const ramp = resolveWeightRamp(typography);
|
|
3171
|
+
const collapsed = [
|
|
3172
|
+
["medium", "normal"],
|
|
3173
|
+
["semibold", "medium"],
|
|
3174
|
+
["bold", "semibold"]
|
|
3175
|
+
].filter(([heavier, lighter]) => ramp[heavier] === ramp[lighter]).map(([heavier, lighter]) => `${heavier} = ${lighter} (${ramp[heavier]})`);
|
|
3176
|
+
if (collapsed.length > 0) {
|
|
3177
|
+
issues.push(
|
|
3178
|
+
issue(
|
|
3179
|
+
"warning",
|
|
3180
|
+
"FONT_WEIGHT_RAMP_COLLAPSED",
|
|
3181
|
+
`Named weight steps share a value because this theme loads only [${loaded.join(", ")}]: ${collapsed.join(", ")}. Asking for the heavier name renders no differently from the lighter one. This is what the browser already does \u2014 load an intermediate face, or use the discrete ladder (--font-weight-${loaded.join(" / --font-weight-")}) where a real step is needed.`,
|
|
3182
|
+
"typography"
|
|
3183
|
+
)
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3140
3187
|
function checkLetterSpacing(config, issues) {
|
|
3141
3188
|
const ls = config.typography?.["letter-spacing"];
|
|
3142
3189
|
if (!ls) return;
|
|
@@ -3643,6 +3690,7 @@ function validate(config, options) {
|
|
|
3643
3690
|
const typedConfig = config;
|
|
3644
3691
|
checkCompleteness(typedConfig, errors);
|
|
3645
3692
|
checkTypeScaleCoherence(typedConfig, errors);
|
|
3693
|
+
checkFontWeightCoverage(typedConfig, warnings);
|
|
3646
3694
|
checkLetterSpacing(typedConfig, errors);
|
|
3647
3695
|
checkMotionEasing(typedConfig, errors);
|
|
3648
3696
|
const durationIssues = [];
|
|
@@ -4216,6 +4264,7 @@ export {
|
|
|
4216
4264
|
VISOR_BRANDS_CDN,
|
|
4217
4265
|
VISOR_DEFAULT_BRAND_PATH,
|
|
4218
4266
|
VISOR_FONTS_CDN,
|
|
4267
|
+
WEIGHT_RAMP_TARGETS,
|
|
4219
4268
|
allComponentTokenNames,
|
|
4220
4269
|
applyOverrides,
|
|
4221
4270
|
assignSemanticTokens,
|
|
@@ -4228,10 +4277,12 @@ export {
|
|
|
4228
4277
|
collectBrandPassthrough,
|
|
4229
4278
|
componentTokenName,
|
|
4230
4279
|
compositeOverBackground,
|
|
4280
|
+
declaresWeights,
|
|
4231
4281
|
exportTheme,
|
|
4232
4282
|
extractFromCSS,
|
|
4233
4283
|
formatFontCoverageError,
|
|
4234
4284
|
generateDarkCss,
|
|
4285
|
+
generateFontWeightDecls,
|
|
4235
4286
|
generateFullBundleCss,
|
|
4236
4287
|
generateLightCss,
|
|
4237
4288
|
generatePreloadLinks,
|
|
@@ -4255,8 +4306,10 @@ export {
|
|
|
4255
4306
|
isValidColor,
|
|
4256
4307
|
isValidHex,
|
|
4257
4308
|
isVisorThemeConfig,
|
|
4309
|
+
loadedWeights,
|
|
4258
4310
|
lookupFontWeightAlias,
|
|
4259
4311
|
lookupGoogleFont,
|
|
4312
|
+
matchLoadedWeight,
|
|
4260
4313
|
normalizeHex,
|
|
4261
4314
|
oklchToHex,
|
|
4262
4315
|
parseCSSDeclarations,
|
|
@@ -4272,8 +4325,10 @@ export {
|
|
|
4272
4325
|
resolveComponentBindings,
|
|
4273
4326
|
resolveConfig,
|
|
4274
4327
|
resolveFont,
|
|
4328
|
+
resolveRoleWeight,
|
|
4275
4329
|
resolveThemeBrand,
|
|
4276
4330
|
resolveThemeFonts,
|
|
4331
|
+
resolveWeightRamp,
|
|
4277
4332
|
rgbToHex,
|
|
4278
4333
|
serializeBrandStrategy,
|
|
4279
4334
|
serializeColor,
|
|
@@ -678,6 +678,12 @@ interface VisorThemeConfig {
|
|
|
678
678
|
info?: string;
|
|
679
679
|
};
|
|
680
680
|
typography?: {
|
|
681
|
+
/**
|
|
682
|
+
* Type-scale multiplier applied to the `--font-size-*` ramp (VI-638).
|
|
683
|
+
* Every step is multiplied by it, and the page inherits the scaled
|
|
684
|
+
* `--font-size-base`. CSS adapters only — the Flutter adapter emits
|
|
685
|
+
* Material 3 slot sizes verbatim. Default: 1.
|
|
686
|
+
*/
|
|
681
687
|
scale?: number;
|
|
682
688
|
/**
|
|
683
689
|
* VI-375: text color placed on a LIGHT interactive background. Auto-picked
|
|
@@ -867,6 +873,12 @@ interface ResolvedThemeConfig {
|
|
|
867
873
|
};
|
|
868
874
|
"colors-dark"?: VisorThemeConfig["colors-dark"];
|
|
869
875
|
typography: {
|
|
876
|
+
/**
|
|
877
|
+
* Type-scale multiplier applied to the `--font-size-*` ramp (VI-638).
|
|
878
|
+
* Every step is multiplied by it, and the page inherits the scaled
|
|
879
|
+
* `--font-size-base`. CSS adapters only — the Flutter adapter emits
|
|
880
|
+
* Material 3 slot sizes verbatim. Default: 1.
|
|
881
|
+
*/
|
|
870
882
|
scale: number;
|
|
871
883
|
/**
|
|
872
884
|
* VI-375: resolved default text color for LIGHT interactive backgrounds.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
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",
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
},
|
|
188
188
|
"scale": {
|
|
189
189
|
"type": "number",
|
|
190
|
-
"description": "Type scale multiplier applied to the font-size ramp. Default: 1."
|
|
190
|
+
"description": "Type scale multiplier applied to the font-size ramp: every --font-size-* step is multiplied by it, and the page inherits the scaled --font-size-base. Default: 1. CSS adapters only — the Flutter adapter emits Material 3 slot sizes verbatim and ignores this field."
|
|
191
191
|
},
|
|
192
192
|
"text-on-light": {
|
|
193
193
|
"type": "string",
|