@loworbitstudio/visor-theme-engine 0.11.0 → 0.12.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 +10 -3
- package/dist/adapters/index.js +7 -3
- package/dist/{chunk-43TVIXUS.js → chunk-B56A5DE6.js} +217 -36
- package/dist/index.d.ts +200 -3
- package/dist/index.js +208 -1
- package/dist/{types-Dwc1V0Nc.d.ts → types-BKEkyelS.d.ts} +111 -1
- package/package.json +1 -1
- package/src/visor-theme.schema.json +80 -0
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { g as GeneratedPrimitives, m as SemanticTokens, R as ResolvedThemeConfig } from '../types-BKEkyelS.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter types for the Visor theme engine.
|
|
@@ -195,8 +195,15 @@ declare function flutterAdapter(input: AdapterInput, options?: FlutterAdapterOpt
|
|
|
195
195
|
* order — defense in depth, so whichever stylesheet loads first establishes
|
|
196
196
|
* the cascade.
|
|
197
197
|
*/
|
|
198
|
-
/**
|
|
199
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Layer order declaration — must appear before any @layer blocks.
|
|
200
|
+
*
|
|
201
|
+
* `visor-brand` (VI-470) is ordered immediately after `visor-semantic`: brand
|
|
202
|
+
* asset vars (`--brand-*`) sit above semantic tokens so brand overrides stay
|
|
203
|
+
* cleanly separable, while still below `visor-adaptive` chrome and the
|
|
204
|
+
* `visor-bridge` framework layer.
|
|
205
|
+
*/
|
|
206
|
+
declare const LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-brand, visor-adaptive, visor-bridge;";
|
|
200
207
|
/**
|
|
201
208
|
* Wrap CSS content in a named @layer block.
|
|
202
209
|
*/
|
package/dist/adapters/index.js
CHANGED
|
@@ -15,12 +15,13 @@ import {
|
|
|
15
15
|
generateTextScaleAliasDecls,
|
|
16
16
|
header,
|
|
17
17
|
parseColor,
|
|
18
|
+
resolveThemeBrand,
|
|
18
19
|
resolveThemeFonts,
|
|
19
20
|
sectionComment
|
|
20
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-B56A5DE6.js";
|
|
21
22
|
|
|
22
23
|
// src/adapters/layers.ts
|
|
23
|
-
var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
|
|
24
|
+
var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-brand, visor-adaptive, visor-bridge;";
|
|
24
25
|
function wrapInLayer(layerName, css) {
|
|
25
26
|
const trimmed = css.trim();
|
|
26
27
|
if (!trimmed) return "";
|
|
@@ -629,10 +630,13 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
|
629
630
|
}`);
|
|
630
631
|
}
|
|
631
632
|
semanticLines.push("");
|
|
633
|
+
const brandResult = resolveThemeBrand(input.config.brand, { scope: scopeClass });
|
|
632
634
|
const adaptiveLayer = wrapInLayer("visor-adaptive", lines.join("\n").trim());
|
|
633
635
|
const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
|
|
636
|
+
const brandLayer = wrapInLayer("visor-brand", brandResult.css);
|
|
634
637
|
const head = fontLines.length > 0 ? fontLines.join("\n") + "\n" : "";
|
|
635
|
-
|
|
638
|
+
const layerBlocks = [semanticLayer, brandLayer, adaptiveLayer].filter(Boolean);
|
|
639
|
+
return head + LAYER_ORDER + "\n\n" + layerBlocks.join("\n\n") + "\n";
|
|
636
640
|
}
|
|
637
641
|
|
|
638
642
|
// src/flutter/color-to-dart.ts
|
|
@@ -217,11 +217,11 @@ function resolveFont(family, options = {}) {
|
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
if (explicitSource === "fontshare") {
|
|
220
|
-
const
|
|
220
|
+
const cssUrl2 = buildFontshareCssUrl(family, requestedWeights, italic, display);
|
|
221
221
|
return {
|
|
222
222
|
family,
|
|
223
223
|
source: "fontshare",
|
|
224
|
-
cssUrl,
|
|
224
|
+
cssUrl: cssUrl2,
|
|
225
225
|
weights: requestedWeights,
|
|
226
226
|
italic,
|
|
227
227
|
display,
|
|
@@ -256,7 +256,7 @@ function resolveFont(family, options = {}) {
|
|
|
256
256
|
const weights = availableWeights.length > 0 ? availableWeights : catalogEntry.weights;
|
|
257
257
|
const hasItalic = catalogEntry.styles.includes("italic");
|
|
258
258
|
const resolvedItalic = italic && hasItalic;
|
|
259
|
-
const
|
|
259
|
+
const cssUrl2 = buildGoogleFontsCssUrl(
|
|
260
260
|
catalogEntry.family,
|
|
261
261
|
weights,
|
|
262
262
|
resolvedItalic,
|
|
@@ -266,7 +266,7 @@ function resolveFont(family, options = {}) {
|
|
|
266
266
|
family: catalogEntry.family,
|
|
267
267
|
// Use canonical casing from catalog
|
|
268
268
|
source: "google-fonts",
|
|
269
|
-
cssUrl,
|
|
269
|
+
cssUrl: cssUrl2,
|
|
270
270
|
weights,
|
|
271
271
|
italic: resolvedItalic,
|
|
272
272
|
display,
|
|
@@ -711,6 +711,179 @@ function resolveThemeFonts(typography, options) {
|
|
|
711
711
|
};
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
+
// src/brand/resolve.ts
|
|
715
|
+
var VISOR_BRANDS_CDN = "https://brands.visor.design";
|
|
716
|
+
var DEFAULT_FORMAT = "svg";
|
|
717
|
+
var DEFAULT_BRAND_SOURCE = "visor-brands";
|
|
718
|
+
var VISOR_DEFAULT_BRAND_PATH = "/themes/visor/brand";
|
|
719
|
+
var DEFAULT_VISOR_BRAND = {
|
|
720
|
+
org: "low-orbit-studio",
|
|
721
|
+
source: "local",
|
|
722
|
+
logo: {
|
|
723
|
+
slug: "visor",
|
|
724
|
+
formats: ["svg"],
|
|
725
|
+
light: `${VISOR_DEFAULT_BRAND_PATH}/visor-logo-light.svg`,
|
|
726
|
+
dark: `${VISOR_DEFAULT_BRAND_PATH}/visor-logo-dark.svg`,
|
|
727
|
+
// Aspect ratios are the real SVG viewBoxes from the VI-469 asset set.
|
|
728
|
+
aspectRatio: "1269.97 / 540",
|
|
729
|
+
clearSpace: "0.5rem"
|
|
730
|
+
},
|
|
731
|
+
brandmark: {
|
|
732
|
+
slug: "visor",
|
|
733
|
+
formats: ["svg"],
|
|
734
|
+
// Single-file mark — same asset on light and dark surfaces.
|
|
735
|
+
light: `${VISOR_DEFAULT_BRAND_PATH}/visor-brandmark.svg`,
|
|
736
|
+
dark: `${VISOR_DEFAULT_BRAND_PATH}/visor-brandmark.svg`,
|
|
737
|
+
aspectRatio: "1 / 1",
|
|
738
|
+
clearSpace: "0.25rem"
|
|
739
|
+
},
|
|
740
|
+
wordmark: {
|
|
741
|
+
slug: "visor",
|
|
742
|
+
formats: ["svg"],
|
|
743
|
+
light: `${VISOR_DEFAULT_BRAND_PATH}/visor-wordmark-light.svg`,
|
|
744
|
+
dark: `${VISOR_DEFAULT_BRAND_PATH}/visor-wordmark-dark.svg`,
|
|
745
|
+
aspectRatio: "1100 / 316",
|
|
746
|
+
clearSpace: "0.5rem"
|
|
747
|
+
},
|
|
748
|
+
monochrome: {
|
|
749
|
+
slug: "visor",
|
|
750
|
+
formats: ["svg"],
|
|
751
|
+
// Single-file mark, tinted via mask-image + currentColor.
|
|
752
|
+
light: `${VISOR_DEFAULT_BRAND_PATH}/visor-monochrome.svg`,
|
|
753
|
+
dark: `${VISOR_DEFAULT_BRAND_PATH}/visor-monochrome.svg`,
|
|
754
|
+
aspectRatio: "2210 / 636",
|
|
755
|
+
clearSpace: "0.25rem"
|
|
756
|
+
},
|
|
757
|
+
favicon: {
|
|
758
|
+
slug: "visor",
|
|
759
|
+
formats: ["svg", "png"],
|
|
760
|
+
// The Visor symbol (brandmark) doubles as the favicon source; Phase 2's
|
|
761
|
+
// build step generates the sized .ico/.png set from it (§4.D).
|
|
762
|
+
light: `${VISOR_DEFAULT_BRAND_PATH}/visor-favicon.svg`,
|
|
763
|
+
dark: `${VISOR_DEFAULT_BRAND_PATH}/visor-favicon.svg`,
|
|
764
|
+
aspectRatio: "1 / 1"
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
function buildVisorBrandUrl(org, slug, variant, mode, format, cdnBase) {
|
|
768
|
+
const base = cdnBase ?? VISOR_BRANDS_CDN;
|
|
769
|
+
const orgSegment = org ? `/${org}` : "";
|
|
770
|
+
const modeSuffix = mode === "dark" ? "-dark" : "";
|
|
771
|
+
return `${base}${orgSegment}/${slug}/${variant}${modeSuffix}.${format}`;
|
|
772
|
+
}
|
|
773
|
+
function buildLocalBrandPath(slug, variant, mode, format, explicit) {
|
|
774
|
+
if (explicit) return explicit;
|
|
775
|
+
const modeSuffix = mode === "dark" ? "-dark" : "";
|
|
776
|
+
return `/themes/${slug}/brand/${variant}${modeSuffix}.${format}`;
|
|
777
|
+
}
|
|
778
|
+
function resolveBrandSlot(variant, slot, options) {
|
|
779
|
+
const source = options.source;
|
|
780
|
+
const format = slot.formats?.[0] ?? DEFAULT_FORMAT;
|
|
781
|
+
const slug = slot.slug ?? variant;
|
|
782
|
+
const clearSpace = slot.clearSpace ?? null;
|
|
783
|
+
const aspectRatio = slot.aspectRatio ?? null;
|
|
784
|
+
if (source === "local") {
|
|
785
|
+
const light2 = buildLocalBrandPath(slug, variant, "light", format, slot.light);
|
|
786
|
+
const dark2 = buildLocalBrandPath(slug, variant, "dark", format, slot.dark);
|
|
787
|
+
const guidance = slot.light || slot.dark ? null : `Brand variant "${variant}" is a local asset. Place the file(s) at public${light2}${light2 !== dark2 ? ` and public${dark2}` : ""} in your project, or set brand.${variant}.light / .dark to an explicit public/-relative path.`;
|
|
788
|
+
return { variant, source, light: light2, dark: dark2, clearSpace, aspectRatio, guidance };
|
|
789
|
+
}
|
|
790
|
+
const org = options.org ?? "";
|
|
791
|
+
const light = slot.light ?? buildVisorBrandUrl(org, slug, variant, "light", format, options.cdnBase);
|
|
792
|
+
const dark = slot.dark ?? buildVisorBrandUrl(org, slug, variant, "dark", format, options.cdnBase);
|
|
793
|
+
return { variant, source, light, dark, clearSpace, aspectRatio, guidance: null };
|
|
794
|
+
}
|
|
795
|
+
function resolveBrandSource(brand) {
|
|
796
|
+
return brand.source ?? DEFAULT_BRAND_SOURCE;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/brand/types.ts
|
|
800
|
+
var BRAND_VARIANTS = [
|
|
801
|
+
"logo",
|
|
802
|
+
"brandmark",
|
|
803
|
+
"wordmark",
|
|
804
|
+
"monochrome",
|
|
805
|
+
"favicon"
|
|
806
|
+
];
|
|
807
|
+
|
|
808
|
+
// src/brand/pipeline.ts
|
|
809
|
+
function cssUrl(value) {
|
|
810
|
+
return `url("${value}")`;
|
|
811
|
+
}
|
|
812
|
+
function staticDeclsFor(r) {
|
|
813
|
+
const decls = [];
|
|
814
|
+
decls.push(`--brand-${r.variant}-light: ${cssUrl(r.light)};`);
|
|
815
|
+
decls.push(`--brand-${r.variant}-dark: ${cssUrl(r.dark)};`);
|
|
816
|
+
if (r.clearSpace !== null) {
|
|
817
|
+
decls.push(`--brand-${r.variant}-clear-space: ${r.clearSpace};`);
|
|
818
|
+
}
|
|
819
|
+
if (r.aspectRatio !== null) {
|
|
820
|
+
decls.push(`--brand-${r.variant}-aspect-ratio: ${r.aspectRatio};`);
|
|
821
|
+
}
|
|
822
|
+
return decls;
|
|
823
|
+
}
|
|
824
|
+
function modeDecl(r, mode) {
|
|
825
|
+
return `--brand-${r.variant}: ${cssUrl(r[mode])};`;
|
|
826
|
+
}
|
|
827
|
+
function block(selector, decls) {
|
|
828
|
+
if (decls.length === 0) return "";
|
|
829
|
+
return [`${selector} {`, ...decls.map((d) => ` ${d}`), "}"].join("\n");
|
|
830
|
+
}
|
|
831
|
+
function generateBrandCSS(resolutions, scope) {
|
|
832
|
+
if (resolutions.length === 0) return "";
|
|
833
|
+
const baseSelector = scope ? scope : ":root";
|
|
834
|
+
const lightSelector = scope ? `html:not(.dark) ${scope}` : ":root";
|
|
835
|
+
const darkSelector = scope ? `.dark ${scope}` : ".dark";
|
|
836
|
+
const pcsSelector = scope ? `${scope}:not(.light)` : ":root:not(.light)";
|
|
837
|
+
const lines = [];
|
|
838
|
+
const staticDecls = resolutions.flatMap(staticDeclsFor);
|
|
839
|
+
lines.push("/* --- Brand: forced-mode aliases + tokens --- */");
|
|
840
|
+
lines.push(block(baseSelector, staticDecls));
|
|
841
|
+
lines.push("");
|
|
842
|
+
const lightDecls = resolutions.map((r) => modeDecl(r, "light"));
|
|
843
|
+
lines.push("/* --- Brand: variants (light) --- */");
|
|
844
|
+
lines.push(block(lightSelector, lightDecls));
|
|
845
|
+
lines.push("");
|
|
846
|
+
const darkDecls = resolutions.map((r) => modeDecl(r, "dark"));
|
|
847
|
+
lines.push("/* --- Brand: variants (dark) \u2014 manual toggle --- */");
|
|
848
|
+
lines.push(block(darkSelector, darkDecls));
|
|
849
|
+
lines.push("");
|
|
850
|
+
lines.push("/* --- Brand: variants (dark) \u2014 prefers-color-scheme --- */");
|
|
851
|
+
const inner = block(pcsSelector, darkDecls);
|
|
852
|
+
lines.push(
|
|
853
|
+
`@media (prefers-color-scheme: dark) {
|
|
854
|
+
${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
855
|
+
}`
|
|
856
|
+
);
|
|
857
|
+
return lines.join("\n").trim();
|
|
858
|
+
}
|
|
859
|
+
function resolveThemeBrand(brand, options) {
|
|
860
|
+
const effective = brand ?? DEFAULT_VISOR_BRAND;
|
|
861
|
+
const scope = options?.scope ?? "";
|
|
862
|
+
const source = resolveBrandSource(effective);
|
|
863
|
+
const org = effective.org ?? null;
|
|
864
|
+
const cdnBase = effective["cdn-overrides"]?.["visor-brands"] ?? null;
|
|
865
|
+
const slotOptions = { source, org, cdnBase };
|
|
866
|
+
const warnings = [];
|
|
867
|
+
const variants = [];
|
|
868
|
+
const custom = [];
|
|
869
|
+
for (const variant of BRAND_VARIANTS) {
|
|
870
|
+
const slot = effective[variant];
|
|
871
|
+
if (!slot) continue;
|
|
872
|
+
const resolution = resolveBrandSlot(variant, slot, slotOptions);
|
|
873
|
+
variants.push(resolution);
|
|
874
|
+
if (resolution.guidance) warnings.push(resolution.guidance);
|
|
875
|
+
}
|
|
876
|
+
if (effective.custom) {
|
|
877
|
+
for (const [key, slot] of Object.entries(effective.custom)) {
|
|
878
|
+
const resolution = resolveBrandSlot(key, slot, slotOptions);
|
|
879
|
+
custom.push(resolution);
|
|
880
|
+
if (resolution.guidance) warnings.push(resolution.guidance);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const css = generateBrandCSS([...variants, ...custom], scope);
|
|
884
|
+
return { variants, custom, css, warnings };
|
|
885
|
+
}
|
|
886
|
+
|
|
714
887
|
// src/color.ts
|
|
715
888
|
function normalizeHex(hex) {
|
|
716
889
|
let color = hex.replace(/^#/, "");
|
|
@@ -1199,7 +1372,7 @@ function sectionComment(label) {
|
|
|
1199
1372
|
return `
|
|
1200
1373
|
/* --- ${label} --- */`;
|
|
1201
1374
|
}
|
|
1202
|
-
function
|
|
1375
|
+
function block2(selector, declarations) {
|
|
1203
1376
|
if (declarations.length === 0) return "";
|
|
1204
1377
|
return [selector + " {", ...declarations.map((d) => ` ${d}`), "}", ""].join(
|
|
1205
1378
|
"\n"
|
|
@@ -1356,20 +1529,20 @@ function generatePrimitivesCss(primitives, config, options) {
|
|
|
1356
1529
|
const host = options?.scopePrefix ?? ":root";
|
|
1357
1530
|
lines.push(sectionComment("Primitive: Colors"));
|
|
1358
1531
|
lines.push(
|
|
1359
|
-
|
|
1532
|
+
block2(host, [generateColorPrimitives(primitives)])
|
|
1360
1533
|
);
|
|
1361
1534
|
lines.push(sectionComment("Primitive: Spacing"));
|
|
1362
|
-
lines.push(
|
|
1535
|
+
lines.push(block2(host, generateSpacingPrimitives(config)));
|
|
1363
1536
|
lines.push(sectionComment("Primitive: Border Radius"));
|
|
1364
|
-
lines.push(
|
|
1537
|
+
lines.push(block2(host, generateRadiusPrimitives(config)));
|
|
1365
1538
|
lines.push(sectionComment("Primitive: Typography"));
|
|
1366
|
-
lines.push(
|
|
1539
|
+
lines.push(block2(host, generateTypographyPrimitives(config, options?.aliasedFamilies)));
|
|
1367
1540
|
lines.push(sectionComment("Primitive: Shadows"));
|
|
1368
|
-
lines.push(
|
|
1541
|
+
lines.push(block2(host, generateShadowPrimitives(config)));
|
|
1369
1542
|
lines.push(sectionComment("Primitive: Motion"));
|
|
1370
|
-
lines.push(
|
|
1543
|
+
lines.push(block2(host, generateMotionPrimitives(config)));
|
|
1371
1544
|
lines.push(sectionComment("Primitive: Miscellaneous"));
|
|
1372
|
-
lines.push(
|
|
1545
|
+
lines.push(block2(host, generateMiscPrimitives()));
|
|
1373
1546
|
return header("Visor Theme \u2014 Primitives") + lines.join("\n");
|
|
1374
1547
|
}
|
|
1375
1548
|
function hairlineDeclName(name) {
|
|
@@ -1381,32 +1554,32 @@ function generateSemanticCss(tokens) {
|
|
|
1381
1554
|
const textDecls = Object.entries(tokens.text).map(
|
|
1382
1555
|
([name, { light }]) => `--text-${name}: ${light};`
|
|
1383
1556
|
);
|
|
1384
|
-
lines.push(
|
|
1557
|
+
lines.push(block2(":root", textDecls));
|
|
1385
1558
|
lines.push(sectionComment("Semantic: Surface"));
|
|
1386
1559
|
const surfaceDecls = Object.entries(tokens.surface).map(
|
|
1387
1560
|
([name, { light }]) => `--surface-${name}: ${light};`
|
|
1388
1561
|
);
|
|
1389
|
-
lines.push(
|
|
1562
|
+
lines.push(block2(":root", surfaceDecls));
|
|
1390
1563
|
lines.push(sectionComment("Semantic: Border"));
|
|
1391
1564
|
const borderDecls = Object.entries(tokens.border).map(
|
|
1392
1565
|
([name, { light }]) => `--border-${name}: ${light};`
|
|
1393
1566
|
);
|
|
1394
|
-
lines.push(
|
|
1567
|
+
lines.push(block2(":root", borderDecls));
|
|
1395
1568
|
lines.push(sectionComment("Semantic: Interactive"));
|
|
1396
1569
|
const interactiveDecls = Object.entries(tokens.interactive).map(
|
|
1397
1570
|
([name, { light }]) => `--interactive-${name}: ${light};`
|
|
1398
1571
|
);
|
|
1399
|
-
lines.push(
|
|
1572
|
+
lines.push(block2(":root", interactiveDecls));
|
|
1400
1573
|
lines.push(sectionComment("Semantic: Intent (aliases)"));
|
|
1401
1574
|
const intentDecls = Object.entries(tokens.intent).map(
|
|
1402
1575
|
([name, { light }]) => `--${name}: ${light};`
|
|
1403
1576
|
);
|
|
1404
|
-
lines.push(
|
|
1577
|
+
lines.push(block2(":root", intentDecls));
|
|
1405
1578
|
lines.push(sectionComment("Semantic: Hairline (aliases)"));
|
|
1406
1579
|
const hairlineDecls = Object.entries(tokens.hairline).map(
|
|
1407
1580
|
([name, { light }]) => `${hairlineDeclName(name)}: ${light};`
|
|
1408
1581
|
);
|
|
1409
|
-
lines.push(
|
|
1582
|
+
lines.push(block2(":root", hairlineDecls));
|
|
1410
1583
|
return header("Visor Theme \u2014 Semantic") + lines.join("\n");
|
|
1411
1584
|
}
|
|
1412
1585
|
var TEXT_SCALE_ALIASES = [
|
|
@@ -1474,17 +1647,17 @@ function generateLightCss(tokens, options) {
|
|
|
1474
1647
|
const { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls } = buildAdaptiveDecls(tokens, "light");
|
|
1475
1648
|
const host = options?.scopePrefix ?? ":root";
|
|
1476
1649
|
lines.push(sectionComment("Adaptive: Text (light)"));
|
|
1477
|
-
lines.push(
|
|
1650
|
+
lines.push(block2(host, textDecls));
|
|
1478
1651
|
lines.push(sectionComment("Adaptive: Surface (light)"));
|
|
1479
|
-
lines.push(
|
|
1652
|
+
lines.push(block2(host, surfaceDecls));
|
|
1480
1653
|
lines.push(sectionComment("Adaptive: Border (light)"));
|
|
1481
|
-
lines.push(
|
|
1654
|
+
lines.push(block2(host, borderDecls));
|
|
1482
1655
|
lines.push(sectionComment("Adaptive: Interactive (light)"));
|
|
1483
|
-
lines.push(
|
|
1656
|
+
lines.push(block2(host, interactiveDecls));
|
|
1484
1657
|
lines.push(sectionComment("Adaptive: Intent aliases (light)"));
|
|
1485
|
-
lines.push(
|
|
1658
|
+
lines.push(block2(host, intentDecls));
|
|
1486
1659
|
lines.push(sectionComment("Adaptive: Hairline aliases (light)"));
|
|
1487
|
-
lines.push(
|
|
1660
|
+
lines.push(block2(host, hairlineDecls));
|
|
1488
1661
|
return header("Visor Theme \u2014 Light") + lines.join("\n");
|
|
1489
1662
|
}
|
|
1490
1663
|
function generateDarkCss(tokens, options) {
|
|
@@ -1495,23 +1668,23 @@ function generateDarkCss(tokens, options) {
|
|
|
1495
1668
|
const darkSelector = darkSelectors.join(",\n");
|
|
1496
1669
|
const prefersSelector = prefix ? `${prefix}:not(.light):not(.theme-light):not([data-theme="light"])` : ':root:not(.light):not(.theme-light):not([data-theme="light"])';
|
|
1497
1670
|
lines.push(sectionComment("Adaptive: Text (dark) \u2014 manual toggle"));
|
|
1498
|
-
lines.push(
|
|
1671
|
+
lines.push(block2(darkSelector, textDecls));
|
|
1499
1672
|
lines.push(sectionComment("Adaptive: Surface (dark) \u2014 manual toggle"));
|
|
1500
|
-
lines.push(
|
|
1673
|
+
lines.push(block2(darkSelector, surfaceDecls));
|
|
1501
1674
|
lines.push(sectionComment("Adaptive: Border (dark) \u2014 manual toggle"));
|
|
1502
|
-
lines.push(
|
|
1675
|
+
lines.push(block2(darkSelector, borderDecls));
|
|
1503
1676
|
lines.push(sectionComment("Adaptive: Interactive (dark) \u2014 manual toggle"));
|
|
1504
|
-
lines.push(
|
|
1677
|
+
lines.push(block2(darkSelector, interactiveDecls));
|
|
1505
1678
|
lines.push(sectionComment("Adaptive: Intent aliases (dark) \u2014 manual toggle"));
|
|
1506
|
-
lines.push(
|
|
1679
|
+
lines.push(block2(darkSelector, intentDecls));
|
|
1507
1680
|
lines.push(sectionComment("Adaptive: Hairline aliases (dark) \u2014 manual toggle"));
|
|
1508
|
-
lines.push(
|
|
1681
|
+
lines.push(block2(darkSelector, hairlineDecls));
|
|
1509
1682
|
lines.push(
|
|
1510
1683
|
sectionComment("Adaptive: Text (dark) \u2014 prefers-color-scheme")
|
|
1511
1684
|
);
|
|
1512
1685
|
lines.push(
|
|
1513
1686
|
`@media (prefers-color-scheme: dark) {
|
|
1514
|
-
${
|
|
1687
|
+
${block2(prefersSelector, textDecls)}}`
|
|
1515
1688
|
);
|
|
1516
1689
|
lines.push("");
|
|
1517
1690
|
lines.push(
|
|
@@ -1519,7 +1692,7 @@ ${block(prefersSelector, textDecls)}}`
|
|
|
1519
1692
|
);
|
|
1520
1693
|
lines.push(
|
|
1521
1694
|
`@media (prefers-color-scheme: dark) {
|
|
1522
|
-
${
|
|
1695
|
+
${block2(prefersSelector, surfaceDecls)}}`
|
|
1523
1696
|
);
|
|
1524
1697
|
lines.push("");
|
|
1525
1698
|
lines.push(
|
|
@@ -1527,7 +1700,7 @@ ${block(prefersSelector, surfaceDecls)}}`
|
|
|
1527
1700
|
);
|
|
1528
1701
|
lines.push(
|
|
1529
1702
|
`@media (prefers-color-scheme: dark) {
|
|
1530
|
-
${
|
|
1703
|
+
${block2(prefersSelector, borderDecls)}}`
|
|
1531
1704
|
);
|
|
1532
1705
|
lines.push("");
|
|
1533
1706
|
lines.push(
|
|
@@ -1535,7 +1708,7 @@ ${block(prefersSelector, borderDecls)}}`
|
|
|
1535
1708
|
);
|
|
1536
1709
|
lines.push(
|
|
1537
1710
|
`@media (prefers-color-scheme: dark) {
|
|
1538
|
-
${
|
|
1711
|
+
${block2(prefersSelector, interactiveDecls)}}`
|
|
1539
1712
|
);
|
|
1540
1713
|
lines.push("");
|
|
1541
1714
|
lines.push(
|
|
@@ -1543,7 +1716,7 @@ ${block(prefersSelector, interactiveDecls)}}`
|
|
|
1543
1716
|
);
|
|
1544
1717
|
lines.push(
|
|
1545
1718
|
`@media (prefers-color-scheme: dark) {
|
|
1546
|
-
${
|
|
1719
|
+
${block2(prefersSelector, intentDecls)}}`
|
|
1547
1720
|
);
|
|
1548
1721
|
lines.push("");
|
|
1549
1722
|
lines.push(
|
|
@@ -1551,7 +1724,7 @@ ${block(prefersSelector, intentDecls)}}`
|
|
|
1551
1724
|
);
|
|
1552
1725
|
lines.push(
|
|
1553
1726
|
`@media (prefers-color-scheme: dark) {
|
|
1554
|
-
${
|
|
1727
|
+
${block2(prefersSelector, hairlineDecls)}}`
|
|
1555
1728
|
);
|
|
1556
1729
|
lines.push("");
|
|
1557
1730
|
return header("Visor Theme \u2014 Dark") + lines.join("\n");
|
|
@@ -1618,6 +1791,14 @@ export {
|
|
|
1618
1791
|
generatePreloadLinks,
|
|
1619
1792
|
generateStylesheetLinks,
|
|
1620
1793
|
resolveThemeFonts,
|
|
1794
|
+
VISOR_BRANDS_CDN,
|
|
1795
|
+
VISOR_DEFAULT_BRAND_PATH,
|
|
1796
|
+
DEFAULT_VISOR_BRAND,
|
|
1797
|
+
buildVisorBrandUrl,
|
|
1798
|
+
resolveBrandSlot,
|
|
1799
|
+
resolveBrandSource,
|
|
1800
|
+
BRAND_VARIANTS,
|
|
1801
|
+
resolveThemeBrand,
|
|
1621
1802
|
normalizeHex,
|
|
1622
1803
|
isValidHex,
|
|
1623
1804
|
hexToRgb,
|
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,
|
|
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, f as ThemeBrandResult, R as ResolvedThemeConfig, g as GeneratedPrimitives, h as ThemeOutput, i as ThemeData, j as VisorThemeConfig, k as FullShadeScale, C as ColorRole, S as SelectiveShadeScale, l as RGB, P as ParsedColor, O as OKLCH, m as SemanticTokens, n as ShadeStep } from './types-BKEkyelS.js';
|
|
2
|
+
export { o as BRAND_VARIANTS, p as BrandVariant, q as ColorFormat, r as FontSource, s as RGBA, t as SemanticTokenValue } from './types-BKEkyelS.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -150,6 +150,92 @@ interface FontCoverageResult {
|
|
|
150
150
|
declare function formatFontCoverageError(filename: string, declaredAt: string, family: string): string;
|
|
151
151
|
declare function validateFontCoverage(css: string): FontCoverageResult;
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Brand-asset resolver — maps a `brand` block's slots to loadable asset URLs.
|
|
155
|
+
*
|
|
156
|
+
* Mirrors `fonts/resolve.ts`:
|
|
157
|
+
* - `VISOR_BRANDS_CDN` is the default CDN base (parallels `VISOR_FONTS_CDN`).
|
|
158
|
+
* - `buildVisorBrandUrl()` constructs a CDN URL for a variant (Phase 2+).
|
|
159
|
+
* - `source: local` resolves to a path under the consuming app's `public/`.
|
|
160
|
+
* - `DEFAULT_VISOR_BRAND` is the Visor default brand stock themes fall back to.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/** Default CDN base for brand assets (Phase 2+; unused while source is `local`). */
|
|
164
|
+
declare const VISOR_BRANDS_CDN = "https://brands.visor.design";
|
|
165
|
+
/**
|
|
166
|
+
* Local public/ path prefix for the Visor default brand. VI-469 produces the
|
|
167
|
+
* SVGs at this canonical location (`packages/docs/public/themes/visor/brand/`),
|
|
168
|
+
* served from the docs app's `public/` root as `/themes/visor/brand/...`.
|
|
169
|
+
*/
|
|
170
|
+
declare const VISOR_DEFAULT_BRAND_PATH = "/themes/visor/brand";
|
|
171
|
+
/**
|
|
172
|
+
* The Visor default brand. Stock themes that omit a `brand` block resolve to
|
|
173
|
+
* this — they are not logo-less (D3). Declared as `source: local` so Phase 1
|
|
174
|
+
* resolves it to the bundled SVGs at `/themes/visor/brand/` (no CDN).
|
|
175
|
+
*
|
|
176
|
+
* Aspect ratios and clear-space are pinned per variant (Q6). These are the
|
|
177
|
+
* canonical Visor mark dimensions; per-theme `brand` blocks override any slot.
|
|
178
|
+
*/
|
|
179
|
+
declare const DEFAULT_VISOR_BRAND: VisorBrand;
|
|
180
|
+
/**
|
|
181
|
+
* Build a visor-brands CDN URL for a brand variant (Phase 2+).
|
|
182
|
+
*
|
|
183
|
+
* Default pattern: `https://brands.visor.design/{org}/{slug}/{variant}[-dark].{format}`.
|
|
184
|
+
* When `cdnBase` is provided, that base replaces `VISOR_BRANDS_CDN`. When `org`
|
|
185
|
+
* is empty (the CDN base already encodes the namespace), the org segment is
|
|
186
|
+
* dropped: `{cdnBase}/{slug}/{variant}[-dark].{format}`.
|
|
187
|
+
*
|
|
188
|
+
* Mirrors `buildVisorFontUrl`. Content-hashing (Q5) is a Phase 2 concern and is
|
|
189
|
+
* intentionally omitted here.
|
|
190
|
+
*/
|
|
191
|
+
declare function buildVisorBrandUrl(org: string, slug: string, variant: string, mode: "light" | "dark", format: string, cdnBase?: string | null): string;
|
|
192
|
+
/**
|
|
193
|
+
* Resolve a single brand slot to a `BrandResolution`.
|
|
194
|
+
*
|
|
195
|
+
* Resolution order (mirrors the fonts resolver):
|
|
196
|
+
* 1. `source: local` → public/ paths (Phase 1).
|
|
197
|
+
* 2. `source: visor-brands` → CDN URLs via `buildVisorBrandUrl` (Phase 2+).
|
|
198
|
+
*
|
|
199
|
+
* `org` / `source` / `cdnBase` fall back to the brand-block-wide defaults when
|
|
200
|
+
* the slot omits them.
|
|
201
|
+
*/
|
|
202
|
+
declare function resolveBrandSlot(variant: string, slot: BrandSlot, options: {
|
|
203
|
+
source: BrandSource;
|
|
204
|
+
org: string | null;
|
|
205
|
+
cdnBase?: string | null;
|
|
206
|
+
}): BrandResolution;
|
|
207
|
+
/** Resolve the brand-block-wide source default (slot-level `source` is not supported). */
|
|
208
|
+
declare function resolveBrandSource(brand: VisorBrand): BrandSource;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Theme brand pipeline — connects brand resolution to the .visor.yaml flow.
|
|
212
|
+
*
|
|
213
|
+
* Takes the `brand` block from a .visor.yaml file and produces:
|
|
214
|
+
* - Resolved brand assets (local public/ paths or CDN URLs).
|
|
215
|
+
* - Mode-scoped `--brand-{variant}` CSS custom properties, plus explicit
|
|
216
|
+
* `--brand-{variant}-light` / `-dark` forced-mode aliases (Q1).
|
|
217
|
+
* - Per-variant `--brand-{variant}-clear-space` / `-aspect-ratio` tokens (Q6).
|
|
218
|
+
* - Warnings for assets needing manual setup.
|
|
219
|
+
*
|
|
220
|
+
* The emitted CSS is a raw declaration body (no @layer wrapper). Adapters wrap
|
|
221
|
+
* it in `@layer visor-brand` (ordered after `visor-semantic`).
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Resolve all brand assets for a theme's `brand` configuration.
|
|
226
|
+
*
|
|
227
|
+
* Main entry point for the brand pipeline — called during .visor.yaml import.
|
|
228
|
+
* When `brand` is omitted, falls back to the Visor default brand (D3) so stock
|
|
229
|
+
* themes are never logo-less.
|
|
230
|
+
*
|
|
231
|
+
* @param brand The resolved `brand` block (or undefined → Visor default).
|
|
232
|
+
* @param options `scope` — optional scope selector (e.g. `.blackout-theme`)
|
|
233
|
+
* that prefixes the emitted selectors for class-scoped adapters.
|
|
234
|
+
*/
|
|
235
|
+
declare function resolveThemeBrand(brand: VisorBrand | undefined, options?: {
|
|
236
|
+
scope?: string;
|
|
237
|
+
}): ThemeBrandResult;
|
|
238
|
+
|
|
153
239
|
/**
|
|
154
240
|
* Import Pipeline
|
|
155
241
|
*
|
|
@@ -501,6 +587,83 @@ var properties = {
|
|
|
501
587
|
}
|
|
502
588
|
}
|
|
503
589
|
},
|
|
590
|
+
brand: {
|
|
591
|
+
type: "object",
|
|
592
|
+
description: "Brand-asset declarations (VI-470). Per-slot logo/brandmark/wordmark/etc. entries resolved to asset URLs and emitted as mode-scoped --brand-* CSS variables in a dedicated visor-brand cascade layer. Omitted → the Visor default brand (stock themes are not logo-less).",
|
|
593
|
+
additionalProperties: false,
|
|
594
|
+
properties: {
|
|
595
|
+
org: {
|
|
596
|
+
type: "string",
|
|
597
|
+
description: "CDN namespace for brand assets. Required when source is \"visor-brands\" (unless cdn-overrides.visor-brands is set)."
|
|
598
|
+
},
|
|
599
|
+
source: {
|
|
600
|
+
type: "string",
|
|
601
|
+
"enum": [
|
|
602
|
+
"visor-brands",
|
|
603
|
+
"local"
|
|
604
|
+
],
|
|
605
|
+
description: "Where assets resolve from. Default: \"visor-brands\". Phase 1 supports \"local\" (resolves to public/ paths); CDN resolution lands later."
|
|
606
|
+
},
|
|
607
|
+
"cdn-overrides": {
|
|
608
|
+
type: "object",
|
|
609
|
+
description: "Per-source CDN base URL override. Only visor-brands is supported.",
|
|
610
|
+
additionalProperties: false,
|
|
611
|
+
properties: {
|
|
612
|
+
"visor-brands": {
|
|
613
|
+
type: "string",
|
|
614
|
+
minLength: 1,
|
|
615
|
+
description: "CDN base URL that replaces brands.visor.design for source: visor-brands slots."
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
logo: {
|
|
620
|
+
$ref: "#/$defs/brandSlot"
|
|
621
|
+
},
|
|
622
|
+
brandmark: {
|
|
623
|
+
$ref: "#/$defs/brandSlot"
|
|
624
|
+
},
|
|
625
|
+
wordmark: {
|
|
626
|
+
$ref: "#/$defs/brandSlot"
|
|
627
|
+
},
|
|
628
|
+
monochrome: {
|
|
629
|
+
$ref: "#/$defs/brandSlot"
|
|
630
|
+
},
|
|
631
|
+
favicon: {
|
|
632
|
+
$ref: "#/$defs/brandSlot"
|
|
633
|
+
},
|
|
634
|
+
custom: {
|
|
635
|
+
type: "object",
|
|
636
|
+
description: "Operator-defined slots, addressed by key.",
|
|
637
|
+
additionalProperties: {
|
|
638
|
+
$ref: "#/$defs/brandSlot"
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
"if": {
|
|
643
|
+
properties: {
|
|
644
|
+
source: {
|
|
645
|
+
"const": "visor-brands"
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
required: [
|
|
649
|
+
"source"
|
|
650
|
+
]
|
|
651
|
+
},
|
|
652
|
+
then: {
|
|
653
|
+
anyOf: [
|
|
654
|
+
{
|
|
655
|
+
required: [
|
|
656
|
+
"org"
|
|
657
|
+
]
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
required: [
|
|
661
|
+
"cdn-overrides"
|
|
662
|
+
]
|
|
663
|
+
}
|
|
664
|
+
]
|
|
665
|
+
}
|
|
666
|
+
},
|
|
504
667
|
spacing: {
|
|
505
668
|
type: "object",
|
|
506
669
|
description: "Spacing configuration.",
|
|
@@ -667,6 +830,40 @@ var $defs = {
|
|
|
667
830
|
}
|
|
668
831
|
]
|
|
669
832
|
},
|
|
833
|
+
brandSlot: {
|
|
834
|
+
type: "object",
|
|
835
|
+
description: "A single brand-variant declaration. Resolves to light + dark asset URLs; clearSpace and aspectRatio are tokenized per variant (VI-470, Q6).",
|
|
836
|
+
additionalProperties: false,
|
|
837
|
+
properties: {
|
|
838
|
+
slug: {
|
|
839
|
+
type: "string",
|
|
840
|
+
description: "CDN/asset-set slug. Required when source is visor-brands (Phase 2+)."
|
|
841
|
+
},
|
|
842
|
+
formats: {
|
|
843
|
+
type: "array",
|
|
844
|
+
items: {
|
|
845
|
+
type: "string"
|
|
846
|
+
},
|
|
847
|
+
description: "Preferred asset formats, first wins (e.g. [\"svg\"], [\"svg\", \"png\"])."
|
|
848
|
+
},
|
|
849
|
+
light: {
|
|
850
|
+
type: "string",
|
|
851
|
+
description: "Explicit light-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
852
|
+
},
|
|
853
|
+
dark: {
|
|
854
|
+
type: "string",
|
|
855
|
+
description: "Explicit dark-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
856
|
+
},
|
|
857
|
+
clearSpace: {
|
|
858
|
+
type: "string",
|
|
859
|
+
description: "Tokenized safe-zone padding enforced by <Logo> (e.g. \"0.5rem\"). Emitted as --brand-{variant}-clear-space."
|
|
860
|
+
},
|
|
861
|
+
aspectRatio: {
|
|
862
|
+
type: "string",
|
|
863
|
+
description: "Tokenized locked aspect ratio (e.g. \"3 / 1\"); else derived from the SVG viewBox. Emitted as --brand-{variant}-aspect-ratio."
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
},
|
|
670
867
|
textSlotOverride: {
|
|
671
868
|
type: "object",
|
|
672
869
|
description: "Per-slot override for one Material TextTheme entry.",
|
|
@@ -1021,4 +1218,4 @@ declare function cleanFontValue(val: string): string;
|
|
|
1021
1218
|
*/
|
|
1022
1219
|
declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
|
|
1023
1220
|
|
|
1024
|
-
export { type CSSFile, ColorRole, type Confidence, 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, ShadeStep, TAILWIND_GRAY, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_FONTS_CDN, type ValidationIssue, type ValidationSeverity, VisorThemeConfig, VisorTypography, applyOverrides, assignSemanticTokens, buildVisorFontUrl, clampToSrgb, cleanFontValue, compositeOverBackground, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, googleFontsCatalog, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, lookupFontWeightAlias, lookupGoogleFont, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveConfig, resolveFont, resolveThemeFonts, rgbToHex, serializeColor, validate, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
|
|
1221
|
+
export { BrandResolution, BrandSlot, BrandSource, 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, ShadeStep, TAILWIND_GRAY, ThemeBrandResult, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_BRANDS_CDN, VISOR_DEFAULT_BRAND_PATH, VISOR_FONTS_CDN, type ValidationIssue, type ValidationSeverity, VisorBrand, VisorThemeConfig, VisorTypography, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, clampToSrgb, cleanFontValue, compositeOverBackground, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, googleFontsCatalog, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, lookupFontWeightAlias, lookupGoogleFont, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveThemeBrand, resolveThemeFonts, rgbToHex, serializeColor, validate, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BRAND_VARIANTS,
|
|
3
|
+
DEFAULT_VISOR_BRAND,
|
|
2
4
|
FONT_WEIGHT_ALIASES,
|
|
3
5
|
MATERIAL_TEXT_SLOTS,
|
|
4
6
|
TAILWIND_GRAY,
|
|
7
|
+
VISOR_BRANDS_CDN,
|
|
8
|
+
VISOR_DEFAULT_BRAND_PATH,
|
|
5
9
|
VISOR_FONTS_CDN,
|
|
10
|
+
buildVisorBrandUrl,
|
|
6
11
|
buildVisorFontUrl,
|
|
7
12
|
clampToSrgb,
|
|
8
13
|
compositeOverBackground,
|
|
@@ -29,12 +34,15 @@ import {
|
|
|
29
34
|
parseHsla,
|
|
30
35
|
parseOklch,
|
|
31
36
|
parseRgba,
|
|
37
|
+
resolveBrandSlot,
|
|
38
|
+
resolveBrandSource,
|
|
32
39
|
resolveFont,
|
|
40
|
+
resolveThemeBrand,
|
|
33
41
|
resolveThemeFonts,
|
|
34
42
|
rgbToHex,
|
|
35
43
|
rgbToOklch,
|
|
36
44
|
serializeColor
|
|
37
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-B56A5DE6.js";
|
|
38
46
|
|
|
39
47
|
// src/fonts/validate-coverage.ts
|
|
40
48
|
var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
|
|
@@ -418,6 +426,54 @@ var visor_theme_schema_default = {
|
|
|
418
426
|
}
|
|
419
427
|
}
|
|
420
428
|
},
|
|
429
|
+
brand: {
|
|
430
|
+
type: "object",
|
|
431
|
+
description: "Brand-asset declarations (VI-470). Per-slot logo/brandmark/wordmark/etc. entries resolved to asset URLs and emitted as mode-scoped --brand-* CSS variables in a dedicated visor-brand cascade layer. Omitted \u2192 the Visor default brand (stock themes are not logo-less).",
|
|
432
|
+
additionalProperties: false,
|
|
433
|
+
properties: {
|
|
434
|
+
org: {
|
|
435
|
+
type: "string",
|
|
436
|
+
description: 'CDN namespace for brand assets. Required when source is "visor-brands" (unless cdn-overrides.visor-brands is set).'
|
|
437
|
+
},
|
|
438
|
+
source: {
|
|
439
|
+
type: "string",
|
|
440
|
+
enum: ["visor-brands", "local"],
|
|
441
|
+
description: 'Where assets resolve from. Default: "visor-brands". Phase 1 supports "local" (resolves to public/ paths); CDN resolution lands later.'
|
|
442
|
+
},
|
|
443
|
+
"cdn-overrides": {
|
|
444
|
+
type: "object",
|
|
445
|
+
description: "Per-source CDN base URL override. Only visor-brands is supported.",
|
|
446
|
+
additionalProperties: false,
|
|
447
|
+
properties: {
|
|
448
|
+
"visor-brands": {
|
|
449
|
+
type: "string",
|
|
450
|
+
minLength: 1,
|
|
451
|
+
description: "CDN base URL that replaces brands.visor.design for source: visor-brands slots."
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
logo: { $ref: "#/$defs/brandSlot" },
|
|
456
|
+
brandmark: { $ref: "#/$defs/brandSlot" },
|
|
457
|
+
wordmark: { $ref: "#/$defs/brandSlot" },
|
|
458
|
+
monochrome: { $ref: "#/$defs/brandSlot" },
|
|
459
|
+
favicon: { $ref: "#/$defs/brandSlot" },
|
|
460
|
+
custom: {
|
|
461
|
+
type: "object",
|
|
462
|
+
description: "Operator-defined slots, addressed by key.",
|
|
463
|
+
additionalProperties: { $ref: "#/$defs/brandSlot" }
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
if: {
|
|
467
|
+
properties: { source: { const: "visor-brands" } },
|
|
468
|
+
required: ["source"]
|
|
469
|
+
},
|
|
470
|
+
then: {
|
|
471
|
+
anyOf: [
|
|
472
|
+
{ required: ["org"] },
|
|
473
|
+
{ required: ["cdn-overrides"] }
|
|
474
|
+
]
|
|
475
|
+
}
|
|
476
|
+
},
|
|
421
477
|
spacing: {
|
|
422
478
|
type: "object",
|
|
423
479
|
description: "Spacing configuration.",
|
|
@@ -532,6 +588,38 @@ var visor_theme_schema_default = {
|
|
|
532
588
|
{ pattern: "^oklch\\(" }
|
|
533
589
|
]
|
|
534
590
|
},
|
|
591
|
+
brandSlot: {
|
|
592
|
+
type: "object",
|
|
593
|
+
description: "A single brand-variant declaration. Resolves to light + dark asset URLs; clearSpace and aspectRatio are tokenized per variant (VI-470, Q6).",
|
|
594
|
+
additionalProperties: false,
|
|
595
|
+
properties: {
|
|
596
|
+
slug: {
|
|
597
|
+
type: "string",
|
|
598
|
+
description: "CDN/asset-set slug. Required when source is visor-brands (Phase 2+)."
|
|
599
|
+
},
|
|
600
|
+
formats: {
|
|
601
|
+
type: "array",
|
|
602
|
+
items: { type: "string" },
|
|
603
|
+
description: 'Preferred asset formats, first wins (e.g. ["svg"], ["svg", "png"]).'
|
|
604
|
+
},
|
|
605
|
+
light: {
|
|
606
|
+
type: "string",
|
|
607
|
+
description: "Explicit light-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
608
|
+
},
|
|
609
|
+
dark: {
|
|
610
|
+
type: "string",
|
|
611
|
+
description: "Explicit dark-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
612
|
+
},
|
|
613
|
+
clearSpace: {
|
|
614
|
+
type: "string",
|
|
615
|
+
description: 'Tokenized safe-zone padding enforced by <Logo> (e.g. "0.5rem"). Emitted as --brand-{variant}-clear-space.'
|
|
616
|
+
},
|
|
617
|
+
aspectRatio: {
|
|
618
|
+
type: "string",
|
|
619
|
+
description: 'Tokenized locked aspect ratio (e.g. "3 / 1"); else derived from the SVG viewBox. Emitted as --brand-{variant}-aspect-ratio.'
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
},
|
|
535
623
|
textSlotOverride: {
|
|
536
624
|
type: "object",
|
|
537
625
|
description: "Per-slot override for one Material TextTheme entry.",
|
|
@@ -567,6 +655,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
567
655
|
"colors",
|
|
568
656
|
"colors-dark",
|
|
569
657
|
"typography",
|
|
658
|
+
"brand",
|
|
570
659
|
"spacing",
|
|
571
660
|
"radius",
|
|
572
661
|
"shadows",
|
|
@@ -607,6 +696,21 @@ var KNOWN_SHADOW_KEYS = /* @__PURE__ */ new Set(["xs", "sm", "md", "lg", "xl"]);
|
|
|
607
696
|
var KNOWN_STROKE_WIDTH_KEYS = /* @__PURE__ */ new Set(["thin", "regular", "medium", "thick"]);
|
|
608
697
|
var KNOWN_MOTION_KEYS = /* @__PURE__ */ new Set(["duration-fast", "duration-normal", "duration-slow", "easing", "easing-overshoot"]);
|
|
609
698
|
var KNOWN_OVERRIDES_KEYS = /* @__PURE__ */ new Set(["light", "dark"]);
|
|
699
|
+
var KNOWN_BRAND_KEYS = /* @__PURE__ */ new Set([
|
|
700
|
+
"org",
|
|
701
|
+
"source",
|
|
702
|
+
"cdn-overrides",
|
|
703
|
+
"logo",
|
|
704
|
+
"brandmark",
|
|
705
|
+
"wordmark",
|
|
706
|
+
"monochrome",
|
|
707
|
+
"favicon",
|
|
708
|
+
"custom"
|
|
709
|
+
]);
|
|
710
|
+
var KNOWN_BRAND_STANDARD_SLOTS = ["logo", "brandmark", "wordmark", "monochrome", "favicon"];
|
|
711
|
+
var KNOWN_BRAND_SLOT_KEYS = /* @__PURE__ */ new Set(["slug", "formats", "light", "dark", "clearSpace", "aspectRatio"]);
|
|
712
|
+
var KNOWN_BRAND_SOURCES = /* @__PURE__ */ new Set(["visor-brands", "local"]);
|
|
713
|
+
var KNOWN_BRAND_CDN_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["visor-brands"]);
|
|
610
714
|
function checkUnknownKeys(obj, errors) {
|
|
611
715
|
for (const key of Object.keys(obj)) {
|
|
612
716
|
if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
|
|
@@ -702,6 +806,46 @@ function checkUnknownKeys(obj, errors) {
|
|
|
702
806
|
}
|
|
703
807
|
}
|
|
704
808
|
}
|
|
809
|
+
if (typeof obj.brand === "object" && obj.brand !== null) {
|
|
810
|
+
const brand = obj.brand;
|
|
811
|
+
for (const key of Object.keys(brand)) {
|
|
812
|
+
if (!KNOWN_BRAND_KEYS.has(key)) {
|
|
813
|
+
errors.push(`Unknown key 'brand.${key}'. Valid keys: ${[...KNOWN_BRAND_KEYS].join(", ")}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (typeof brand["cdn-overrides"] === "object" && brand["cdn-overrides"] !== null) {
|
|
817
|
+
for (const key of Object.keys(brand["cdn-overrides"])) {
|
|
818
|
+
if (!KNOWN_BRAND_CDN_OVERRIDE_KEYS.has(key)) {
|
|
819
|
+
errors.push(`Unknown key 'brand.cdn-overrides.${key}'. Valid keys: ${[...KNOWN_BRAND_CDN_OVERRIDE_KEYS].join(", ")}`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
for (const slot of KNOWN_BRAND_STANDARD_SLOTS) {
|
|
824
|
+
const slotObj = brand[slot];
|
|
825
|
+
if (typeof slotObj === "object" && slotObj !== null) {
|
|
826
|
+
for (const key of Object.keys(slotObj)) {
|
|
827
|
+
if (!KNOWN_BRAND_SLOT_KEYS.has(key)) {
|
|
828
|
+
errors.push(`Unknown key 'brand.${slot}.${key}'. Valid keys: ${[...KNOWN_BRAND_SLOT_KEYS].join(", ")}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (typeof brand.custom === "object" && brand.custom !== null) {
|
|
834
|
+
const custom = brand.custom;
|
|
835
|
+
for (const slotName of Object.keys(custom)) {
|
|
836
|
+
const slotObj = custom[slotName];
|
|
837
|
+
if (typeof slotObj !== "object" || slotObj === null) {
|
|
838
|
+
errors.push(`'brand.custom.${slotName}' must be an object with optional slug/formats/light/dark/clearSpace/aspectRatio fields`);
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
for (const key of Object.keys(slotObj)) {
|
|
842
|
+
if (!KNOWN_BRAND_SLOT_KEYS.has(key)) {
|
|
843
|
+
errors.push(`Unknown key 'brand.custom.${slotName}.${key}'. Valid keys: ${[...KNOWN_BRAND_SLOT_KEYS].join(", ")}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
705
849
|
if (typeof obj.spacing === "object" && obj.spacing !== null) {
|
|
706
850
|
for (const key of Object.keys(obj.spacing)) {
|
|
707
851
|
if (!KNOWN_SPACING_KEYS.has(key)) {
|
|
@@ -857,6 +1001,46 @@ function validateConfig(config) {
|
|
|
857
1001
|
}
|
|
858
1002
|
}
|
|
859
1003
|
}
|
|
1004
|
+
if (obj.brand !== void 0) {
|
|
1005
|
+
if (typeof obj.brand !== "object" || obj.brand === null) {
|
|
1006
|
+
errors.push("'brand' must be an object");
|
|
1007
|
+
} else {
|
|
1008
|
+
const brand = obj.brand;
|
|
1009
|
+
if (brand.source !== void 0 && !KNOWN_BRAND_SOURCES.has(brand.source)) {
|
|
1010
|
+
errors.push(`'brand.source' must be one of: ${[...KNOWN_BRAND_SOURCES].join(", ")}`);
|
|
1011
|
+
}
|
|
1012
|
+
const brandCdn = brand["cdn-overrides"];
|
|
1013
|
+
const visorBrandsOverride = brandCdn?.["visor-brands"];
|
|
1014
|
+
if (visorBrandsOverride !== void 0 && typeof visorBrandsOverride !== "string") {
|
|
1015
|
+
errors.push("'brand.cdn-overrides.visor-brands' must be a string URL");
|
|
1016
|
+
}
|
|
1017
|
+
if (typeof visorBrandsOverride === "string" && visorBrandsOverride.length === 0) {
|
|
1018
|
+
errors.push("'brand.cdn-overrides.visor-brands' must not be empty");
|
|
1019
|
+
}
|
|
1020
|
+
const orgOptional = typeof visorBrandsOverride === "string" && visorBrandsOverride.length > 0;
|
|
1021
|
+
if (brand.source === "visor-brands" && !orgOptional && !brand.org) {
|
|
1022
|
+
errors.push("'brand.org' is required when brand.source is 'visor-brands' (unless brand.cdn-overrides.visor-brands is set)");
|
|
1023
|
+
}
|
|
1024
|
+
const allSlots = [];
|
|
1025
|
+
for (const slot of KNOWN_BRAND_STANDARD_SLOTS) {
|
|
1026
|
+
if (typeof brand[slot] === "object" && brand[slot] !== null) {
|
|
1027
|
+
allSlots.push(brand[slot]);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (typeof brand.custom === "object" && brand.custom !== null) {
|
|
1031
|
+
for (const slot of Object.values(brand.custom)) {
|
|
1032
|
+
if (typeof slot === "object" && slot !== null) allSlots.push(slot);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
for (const slot of allSlots) {
|
|
1036
|
+
if (slot.formats !== void 0) {
|
|
1037
|
+
if (!Array.isArray(slot.formats) || !slot.formats.every((f) => typeof f === "string")) {
|
|
1038
|
+
errors.push(`'brand.<slot>.formats' must be an array of format strings (e.g., ["svg", "png"])`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
860
1044
|
if (obj.overrides !== void 0) {
|
|
861
1045
|
if (typeof obj.overrides !== "object" || obj.overrides === null) {
|
|
862
1046
|
errors.push("'overrides' must be an object");
|
|
@@ -917,6 +1101,20 @@ var DEFAULTS = {
|
|
|
917
1101
|
easing: "cubic-bezier(0.4, 0, 0.2, 1)"
|
|
918
1102
|
}
|
|
919
1103
|
};
|
|
1104
|
+
function resolveBrand(brand) {
|
|
1105
|
+
if (!brand) return DEFAULT_VISOR_BRAND;
|
|
1106
|
+
return {
|
|
1107
|
+
org: brand.org ?? DEFAULT_VISOR_BRAND.org,
|
|
1108
|
+
source: brand.source ?? DEFAULT_VISOR_BRAND.source,
|
|
1109
|
+
...brand["cdn-overrides"] && { "cdn-overrides": brand["cdn-overrides"] },
|
|
1110
|
+
logo: brand.logo ?? DEFAULT_VISOR_BRAND.logo,
|
|
1111
|
+
brandmark: brand.brandmark ?? DEFAULT_VISOR_BRAND.brandmark,
|
|
1112
|
+
wordmark: brand.wordmark ?? DEFAULT_VISOR_BRAND.wordmark,
|
|
1113
|
+
monochrome: brand.monochrome ?? DEFAULT_VISOR_BRAND.monochrome,
|
|
1114
|
+
favicon: brand.favicon ?? DEFAULT_VISOR_BRAND.favicon,
|
|
1115
|
+
...brand.custom && { custom: brand.custom }
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
920
1118
|
function resolveConfig(config) {
|
|
921
1119
|
const colors = config.colors;
|
|
922
1120
|
const originalColors = {};
|
|
@@ -997,6 +1195,7 @@ function resolveConfig(config) {
|
|
|
997
1195
|
},
|
|
998
1196
|
slots: config.typography?.slots ?? {}
|
|
999
1197
|
},
|
|
1198
|
+
brand: resolveBrand(config.brand),
|
|
1000
1199
|
spacing: {
|
|
1001
1200
|
base: config.spacing?.base ?? DEFAULTS.spacing.base
|
|
1002
1201
|
},
|
|
@@ -2997,12 +3196,17 @@ function extractFromCSS(files, name = "extracted-theme") {
|
|
|
2997
3196
|
return { config, tokens, unmapped, warnings };
|
|
2998
3197
|
}
|
|
2999
3198
|
export {
|
|
3199
|
+
BRAND_VARIANTS,
|
|
3200
|
+
DEFAULT_VISOR_BRAND,
|
|
3000
3201
|
FONT_WEIGHT_ALIASES,
|
|
3001
3202
|
SEMANTIC_MAP,
|
|
3002
3203
|
TAILWIND_GRAY,
|
|
3204
|
+
VISOR_BRANDS_CDN,
|
|
3205
|
+
VISOR_DEFAULT_BRAND_PATH,
|
|
3003
3206
|
VISOR_FONTS_CDN,
|
|
3004
3207
|
applyOverrides,
|
|
3005
3208
|
assignSemanticTokens,
|
|
3209
|
+
buildVisorBrandUrl,
|
|
3006
3210
|
buildVisorFontUrl,
|
|
3007
3211
|
clampToSrgb,
|
|
3008
3212
|
cleanFontValue,
|
|
@@ -3042,8 +3246,11 @@ export {
|
|
|
3042
3246
|
parseHsla,
|
|
3043
3247
|
parseOklch,
|
|
3044
3248
|
parseRgba,
|
|
3249
|
+
resolveBrandSlot,
|
|
3250
|
+
resolveBrandSource,
|
|
3045
3251
|
resolveConfig,
|
|
3046
3252
|
resolveFont,
|
|
3253
|
+
resolveThemeBrand,
|
|
3047
3254
|
resolveThemeFonts,
|
|
3048
3255
|
rgbToHex,
|
|
3049
3256
|
serializeColor,
|
|
@@ -119,6 +119,104 @@ interface GoogleFontEntry {
|
|
|
119
119
|
category: string;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Brand-asset resolution types for the Visor theme engine.
|
|
124
|
+
*
|
|
125
|
+
* Models the fonts subsystem (`packages/theme-engine/src/fonts/`): a per-theme
|
|
126
|
+
* `brand` block declares logo/brandmark/wordmark/etc. slots, the resolver maps
|
|
127
|
+
* each to loadable asset URLs (Phase 1: local public/ paths; Phase 2+: CDN),
|
|
128
|
+
* and the pipeline emits `--brand-{variant}` CSS custom properties into a
|
|
129
|
+
* dedicated `visor-brand` cascade layer.
|
|
130
|
+
*
|
|
131
|
+
* Phase 1 (VI-470) supports `source: local` only — no CDN. The `visor-brands`
|
|
132
|
+
* source value is reserved in the type union so the schema stays
|
|
133
|
+
* forward-compatible (parallels the fonts `visor-fonts` source).
|
|
134
|
+
*/
|
|
135
|
+
/** Where a brand asset is loaded from. Phase 1 resolves only `local`. */
|
|
136
|
+
type BrandSource = "visor-brands" | "local";
|
|
137
|
+
/**
|
|
138
|
+
* Standard brand variant slots. A fixed set covers the common lockups; custom
|
|
139
|
+
* operator-defined slots are addressed by key through the `custom` map.
|
|
140
|
+
*/
|
|
141
|
+
type BrandVariant = "logo" | "brandmark" | "wordmark" | "monochrome" | "favicon";
|
|
142
|
+
/** Ordered list of the standard brand variant slots. */
|
|
143
|
+
declare const BRAND_VARIANTS: readonly BrandVariant[];
|
|
144
|
+
/**
|
|
145
|
+
* A single brand-slot declaration in `.visor.yaml`. Each slot may declare a
|
|
146
|
+
* `slug` (CDN namespace, Phase 2+), an explicit per-mode `light`/`dark`
|
|
147
|
+
* filename or path, plus the tokenized `clearSpace` (safe-zone) and
|
|
148
|
+
* `aspectRatio` enforced by `<Logo>` (Q6).
|
|
149
|
+
*/
|
|
150
|
+
interface BrandSlot {
|
|
151
|
+
/** CDN/asset-set slug. Required when `source: visor-brands` (Phase 2+). */
|
|
152
|
+
slug?: string;
|
|
153
|
+
/** Preferred asset formats, first wins (e.g. `["svg"]`, `["svg", "png"]`). */
|
|
154
|
+
formats?: string[];
|
|
155
|
+
/** Explicit light-mode asset filename or path (overrides the inferred name). */
|
|
156
|
+
light?: string;
|
|
157
|
+
/** Explicit dark-mode asset filename or path (overrides the inferred name). */
|
|
158
|
+
dark?: string;
|
|
159
|
+
/** Tokenized safe-zone padding enforced by `<Logo>` (Q6), e.g. "0.5rem". */
|
|
160
|
+
clearSpace?: string;
|
|
161
|
+
/** Tokenized locked aspect ratio (Q6), e.g. "3 / 1"; else derived from viewBox. */
|
|
162
|
+
aspectRatio?: string;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The `brand` block from a `.visor.yaml` file. Structured like `typography`:
|
|
166
|
+
* shared `org`/`source`/`cdn-overrides` defaults plus the standard slots and
|
|
167
|
+
* an optional `custom` map.
|
|
168
|
+
*/
|
|
169
|
+
interface VisorBrand {
|
|
170
|
+
/** CDN namespace; required when `source: visor-brands` unless cdn-overrides is set. */
|
|
171
|
+
org?: string;
|
|
172
|
+
/** Where assets resolve from. Phase 1 supports `local` only. Default: `visor-brands`. */
|
|
173
|
+
source?: BrandSource;
|
|
174
|
+
/** Per-source CDN base URL overrides (Phase 2+). Only `visor-brands` is supported. */
|
|
175
|
+
"cdn-overrides"?: {
|
|
176
|
+
"visor-brands"?: string;
|
|
177
|
+
};
|
|
178
|
+
/** Full lockup. */
|
|
179
|
+
logo?: BrandSlot;
|
|
180
|
+
/** Symbol only. */
|
|
181
|
+
brandmark?: BrandSlot;
|
|
182
|
+
/** Type only. */
|
|
183
|
+
wordmark?: BrandSlot;
|
|
184
|
+
/** Single-color mark (tinted via CSS mask-image + currentColor). */
|
|
185
|
+
monochrome?: BrandSlot;
|
|
186
|
+
/** Favicon source. */
|
|
187
|
+
favicon?: BrandSlot;
|
|
188
|
+
/** Operator-defined slots, addressed by key. */
|
|
189
|
+
custom?: Record<string, BrandSlot>;
|
|
190
|
+
}
|
|
191
|
+
/** A single resolved brand variant — light + dark asset URLs plus its tokens. */
|
|
192
|
+
interface BrandResolution {
|
|
193
|
+
/** The variant slot this resolution covers ("logo", "brandmark", or a custom key). */
|
|
194
|
+
variant: string;
|
|
195
|
+
/** Where this asset comes from. */
|
|
196
|
+
source: BrandSource;
|
|
197
|
+
/** Resolved light-mode asset URL/path. */
|
|
198
|
+
light: string;
|
|
199
|
+
/** Resolved dark-mode asset URL/path. */
|
|
200
|
+
dark: string;
|
|
201
|
+
/** Tokenized safe-zone padding (null when not declared). */
|
|
202
|
+
clearSpace: string | null;
|
|
203
|
+
/** Tokenized locked aspect ratio (null when not declared). */
|
|
204
|
+
aspectRatio: string | null;
|
|
205
|
+
/** Human-readable guidance for assets needing manual setup (e.g. local files). */
|
|
206
|
+
guidance: string | null;
|
|
207
|
+
}
|
|
208
|
+
/** Result of resolving all brand assets for a theme. */
|
|
209
|
+
interface ThemeBrandResult {
|
|
210
|
+
/** Resolved standard-slot variants in declaration order. */
|
|
211
|
+
variants: BrandResolution[];
|
|
212
|
+
/** Resolved custom-slot variants in declaration order. */
|
|
213
|
+
custom: BrandResolution[];
|
|
214
|
+
/** Mode-scoped `--brand-*` CSS custom property declarations (no @layer wrapper). */
|
|
215
|
+
css: string;
|
|
216
|
+
/** Warnings for assets needing manual setup. */
|
|
217
|
+
warnings: string[];
|
|
218
|
+
}
|
|
219
|
+
|
|
122
220
|
/**
|
|
123
221
|
* Types for the Visor Theme Engine
|
|
124
222
|
*
|
|
@@ -240,6 +338,13 @@ interface VisorThemeConfig {
|
|
|
240
338
|
*/
|
|
241
339
|
slots?: Partial<Record<MaterialTextSlot, TextSlotOverride>>;
|
|
242
340
|
};
|
|
341
|
+
/**
|
|
342
|
+
* Brand-asset declarations (VI-470). Structured like `typography` — per-slot
|
|
343
|
+
* logo/brandmark/wordmark/etc. entries resolved to asset URLs and emitted as
|
|
344
|
+
* mode-scoped `--brand-*` CSS vars in a dedicated `visor-brand` cascade layer.
|
|
345
|
+
* Omitted → the Visor default brand (stock themes are not logo-less).
|
|
346
|
+
*/
|
|
347
|
+
brand?: VisorBrand;
|
|
243
348
|
spacing?: {
|
|
244
349
|
base?: number;
|
|
245
350
|
};
|
|
@@ -358,6 +463,11 @@ interface ResolvedThemeConfig {
|
|
|
358
463
|
*/
|
|
359
464
|
slots: Partial<Record<MaterialTextSlot, TextSlotOverride>>;
|
|
360
465
|
};
|
|
466
|
+
/**
|
|
467
|
+
* Resolved brand block (VI-470). Always present — falls back to the Visor
|
|
468
|
+
* default brand when the source config omits `brand` (D3).
|
|
469
|
+
*/
|
|
470
|
+
brand: VisorBrand;
|
|
361
471
|
spacing: {
|
|
362
472
|
base: number;
|
|
363
473
|
};
|
|
@@ -434,4 +544,4 @@ interface ThemeData {
|
|
|
434
544
|
output: ThemeOutput;
|
|
435
545
|
}
|
|
436
546
|
|
|
437
|
-
export type
|
|
547
|
+
export { type BrandSlot as B, type ColorRole as C, type FontResolveOptions as F, type GoogleFontEntry as G, type OKLCH as O, type ParsedColor as P, type ResolvedThemeConfig as R, type SelectiveShadeScale as S, type ThemeFontResult as T, type VisorTypography as V, type FontResolution as a, type FontDisplayStrategy as b, type VisorBrand as c, type BrandSource as d, type BrandResolution as e, type ThemeBrandResult as f, type GeneratedPrimitives as g, type ThemeOutput as h, type ThemeData as i, type VisorThemeConfig as j, type FullShadeScale as k, type RGB as l, type SemanticTokens as m, type ShadeStep as n, BRAND_VARIANTS as o, type BrandVariant as p, type ColorFormat as q, type FontSource as r, type RGBA as s, type SemanticTokenValue as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|
|
@@ -222,6 +222,54 @@
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
},
|
|
225
|
+
"brand": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"description": "Brand-asset declarations (VI-470). Per-slot logo/brandmark/wordmark/etc. entries resolved to asset URLs and emitted as mode-scoped --brand-* CSS variables in a dedicated visor-brand cascade layer. Omitted → the Visor default brand (stock themes are not logo-less).",
|
|
228
|
+
"additionalProperties": false,
|
|
229
|
+
"properties": {
|
|
230
|
+
"org": {
|
|
231
|
+
"type": "string",
|
|
232
|
+
"description": "CDN namespace for brand assets. Required when source is \"visor-brands\" (unless cdn-overrides.visor-brands is set)."
|
|
233
|
+
},
|
|
234
|
+
"source": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"enum": ["visor-brands", "local"],
|
|
237
|
+
"description": "Where assets resolve from. Default: \"visor-brands\". Phase 1 supports \"local\" (resolves to public/ paths); CDN resolution lands later."
|
|
238
|
+
},
|
|
239
|
+
"cdn-overrides": {
|
|
240
|
+
"type": "object",
|
|
241
|
+
"description": "Per-source CDN base URL override. Only visor-brands is supported.",
|
|
242
|
+
"additionalProperties": false,
|
|
243
|
+
"properties": {
|
|
244
|
+
"visor-brands": {
|
|
245
|
+
"type": "string",
|
|
246
|
+
"minLength": 1,
|
|
247
|
+
"description": "CDN base URL that replaces brands.visor.design for source: visor-brands slots."
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"logo": { "$ref": "#/$defs/brandSlot" },
|
|
252
|
+
"brandmark": { "$ref": "#/$defs/brandSlot" },
|
|
253
|
+
"wordmark": { "$ref": "#/$defs/brandSlot" },
|
|
254
|
+
"monochrome": { "$ref": "#/$defs/brandSlot" },
|
|
255
|
+
"favicon": { "$ref": "#/$defs/brandSlot" },
|
|
256
|
+
"custom": {
|
|
257
|
+
"type": "object",
|
|
258
|
+
"description": "Operator-defined slots, addressed by key.",
|
|
259
|
+
"additionalProperties": { "$ref": "#/$defs/brandSlot" }
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"if": {
|
|
263
|
+
"properties": { "source": { "const": "visor-brands" } },
|
|
264
|
+
"required": ["source"]
|
|
265
|
+
},
|
|
266
|
+
"then": {
|
|
267
|
+
"anyOf": [
|
|
268
|
+
{ "required": ["org"] },
|
|
269
|
+
{ "required": ["cdn-overrides"] }
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
},
|
|
225
273
|
"spacing": {
|
|
226
274
|
"type": "object",
|
|
227
275
|
"description": "Spacing configuration.",
|
|
@@ -336,6 +384,38 @@
|
|
|
336
384
|
{ "pattern": "^oklch\\(" }
|
|
337
385
|
]
|
|
338
386
|
},
|
|
387
|
+
"brandSlot": {
|
|
388
|
+
"type": "object",
|
|
389
|
+
"description": "A single brand-variant declaration. Resolves to light + dark asset URLs; clearSpace and aspectRatio are tokenized per variant (VI-470, Q6).",
|
|
390
|
+
"additionalProperties": false,
|
|
391
|
+
"properties": {
|
|
392
|
+
"slug": {
|
|
393
|
+
"type": "string",
|
|
394
|
+
"description": "CDN/asset-set slug. Required when source is visor-brands (Phase 2+)."
|
|
395
|
+
},
|
|
396
|
+
"formats": {
|
|
397
|
+
"type": "array",
|
|
398
|
+
"items": { "type": "string" },
|
|
399
|
+
"description": "Preferred asset formats, first wins (e.g. [\"svg\"], [\"svg\", \"png\"])."
|
|
400
|
+
},
|
|
401
|
+
"light": {
|
|
402
|
+
"type": "string",
|
|
403
|
+
"description": "Explicit light-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
404
|
+
},
|
|
405
|
+
"dark": {
|
|
406
|
+
"type": "string",
|
|
407
|
+
"description": "Explicit dark-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
408
|
+
},
|
|
409
|
+
"clearSpace": {
|
|
410
|
+
"type": "string",
|
|
411
|
+
"description": "Tokenized safe-zone padding enforced by <Logo> (e.g. \"0.5rem\"). Emitted as --brand-{variant}-clear-space."
|
|
412
|
+
},
|
|
413
|
+
"aspectRatio": {
|
|
414
|
+
"type": "string",
|
|
415
|
+
"description": "Tokenized locked aspect ratio (e.g. \"3 / 1\"); else derived from the SVG viewBox. Emitted as --brand-{variant}-aspect-ratio."
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
},
|
|
339
419
|
"textSlotOverride": {
|
|
340
420
|
"type": "object",
|
|
341
421
|
"description": "Per-slot override for one Material TextTheme entry.",
|