@loworbitstudio/visor-theme-engine 0.11.0 → 0.13.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-KFTTL3XP.js} +218 -36
- package/dist/index.d.ts +203 -3
- package/dist/index.js +229 -3
- package/dist/{types-Dwc1V0Nc.d.ts → types-CSO2avFQ.d.ts} +118 -1
- package/package.json +1 -1
- package/src/visor-theme.schema.json +81 -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-CSO2avFQ.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-KFTTL3XP.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,180 @@ 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
|
+
"animated"
|
|
807
|
+
];
|
|
808
|
+
|
|
809
|
+
// src/brand/pipeline.ts
|
|
810
|
+
function cssUrl(value) {
|
|
811
|
+
return `url("${value}")`;
|
|
812
|
+
}
|
|
813
|
+
function staticDeclsFor(r) {
|
|
814
|
+
const decls = [];
|
|
815
|
+
decls.push(`--brand-${r.variant}-light: ${cssUrl(r.light)};`);
|
|
816
|
+
decls.push(`--brand-${r.variant}-dark: ${cssUrl(r.dark)};`);
|
|
817
|
+
if (r.clearSpace !== null) {
|
|
818
|
+
decls.push(`--brand-${r.variant}-clear-space: ${r.clearSpace};`);
|
|
819
|
+
}
|
|
820
|
+
if (r.aspectRatio !== null) {
|
|
821
|
+
decls.push(`--brand-${r.variant}-aspect-ratio: ${r.aspectRatio};`);
|
|
822
|
+
}
|
|
823
|
+
return decls;
|
|
824
|
+
}
|
|
825
|
+
function modeDecl(r, mode) {
|
|
826
|
+
return `--brand-${r.variant}: ${cssUrl(r[mode])};`;
|
|
827
|
+
}
|
|
828
|
+
function block(selector, decls) {
|
|
829
|
+
if (decls.length === 0) return "";
|
|
830
|
+
return [`${selector} {`, ...decls.map((d) => ` ${d}`), "}"].join("\n");
|
|
831
|
+
}
|
|
832
|
+
function generateBrandCSS(resolutions, scope) {
|
|
833
|
+
if (resolutions.length === 0) return "";
|
|
834
|
+
const baseSelector = scope ? scope : ":root";
|
|
835
|
+
const lightSelector = scope ? `html:not(.dark) ${scope}` : ":root";
|
|
836
|
+
const darkSelector = scope ? `.dark ${scope}` : ".dark";
|
|
837
|
+
const pcsSelector = scope ? `${scope}:not(.light)` : ":root:not(.light)";
|
|
838
|
+
const lines = [];
|
|
839
|
+
const staticDecls = resolutions.flatMap(staticDeclsFor);
|
|
840
|
+
lines.push("/* --- Brand: forced-mode aliases + tokens --- */");
|
|
841
|
+
lines.push(block(baseSelector, staticDecls));
|
|
842
|
+
lines.push("");
|
|
843
|
+
const lightDecls = resolutions.map((r) => modeDecl(r, "light"));
|
|
844
|
+
lines.push("/* --- Brand: variants (light) --- */");
|
|
845
|
+
lines.push(block(lightSelector, lightDecls));
|
|
846
|
+
lines.push("");
|
|
847
|
+
const darkDecls = resolutions.map((r) => modeDecl(r, "dark"));
|
|
848
|
+
lines.push("/* --- Brand: variants (dark) \u2014 manual toggle --- */");
|
|
849
|
+
lines.push(block(darkSelector, darkDecls));
|
|
850
|
+
lines.push("");
|
|
851
|
+
lines.push("/* --- Brand: variants (dark) \u2014 prefers-color-scheme --- */");
|
|
852
|
+
const inner = block(pcsSelector, darkDecls);
|
|
853
|
+
lines.push(
|
|
854
|
+
`@media (prefers-color-scheme: dark) {
|
|
855
|
+
${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
856
|
+
}`
|
|
857
|
+
);
|
|
858
|
+
return lines.join("\n").trim();
|
|
859
|
+
}
|
|
860
|
+
function resolveThemeBrand(brand, options) {
|
|
861
|
+
const effective = brand ?? DEFAULT_VISOR_BRAND;
|
|
862
|
+
const scope = options?.scope ?? "";
|
|
863
|
+
const source = resolveBrandSource(effective);
|
|
864
|
+
const org = effective.org ?? null;
|
|
865
|
+
const cdnBase = effective["cdn-overrides"]?.["visor-brands"] ?? null;
|
|
866
|
+
const slotOptions = { source, org, cdnBase };
|
|
867
|
+
const warnings = [];
|
|
868
|
+
const variants = [];
|
|
869
|
+
const custom = [];
|
|
870
|
+
for (const variant of BRAND_VARIANTS) {
|
|
871
|
+
const slot = effective[variant];
|
|
872
|
+
if (!slot) continue;
|
|
873
|
+
const resolution = resolveBrandSlot(variant, slot, slotOptions);
|
|
874
|
+
variants.push(resolution);
|
|
875
|
+
if (resolution.guidance) warnings.push(resolution.guidance);
|
|
876
|
+
}
|
|
877
|
+
if (effective.custom) {
|
|
878
|
+
for (const [key, slot] of Object.entries(effective.custom)) {
|
|
879
|
+
const resolution = resolveBrandSlot(key, slot, slotOptions);
|
|
880
|
+
custom.push(resolution);
|
|
881
|
+
if (resolution.guidance) warnings.push(resolution.guidance);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const css = generateBrandCSS([...variants, ...custom], scope);
|
|
885
|
+
return { variants, custom, css, warnings };
|
|
886
|
+
}
|
|
887
|
+
|
|
714
888
|
// src/color.ts
|
|
715
889
|
function normalizeHex(hex) {
|
|
716
890
|
let color = hex.replace(/^#/, "");
|
|
@@ -1199,7 +1373,7 @@ function sectionComment(label) {
|
|
|
1199
1373
|
return `
|
|
1200
1374
|
/* --- ${label} --- */`;
|
|
1201
1375
|
}
|
|
1202
|
-
function
|
|
1376
|
+
function block2(selector, declarations) {
|
|
1203
1377
|
if (declarations.length === 0) return "";
|
|
1204
1378
|
return [selector + " {", ...declarations.map((d) => ` ${d}`), "}", ""].join(
|
|
1205
1379
|
"\n"
|
|
@@ -1356,20 +1530,20 @@ function generatePrimitivesCss(primitives, config, options) {
|
|
|
1356
1530
|
const host = options?.scopePrefix ?? ":root";
|
|
1357
1531
|
lines.push(sectionComment("Primitive: Colors"));
|
|
1358
1532
|
lines.push(
|
|
1359
|
-
|
|
1533
|
+
block2(host, [generateColorPrimitives(primitives)])
|
|
1360
1534
|
);
|
|
1361
1535
|
lines.push(sectionComment("Primitive: Spacing"));
|
|
1362
|
-
lines.push(
|
|
1536
|
+
lines.push(block2(host, generateSpacingPrimitives(config)));
|
|
1363
1537
|
lines.push(sectionComment("Primitive: Border Radius"));
|
|
1364
|
-
lines.push(
|
|
1538
|
+
lines.push(block2(host, generateRadiusPrimitives(config)));
|
|
1365
1539
|
lines.push(sectionComment("Primitive: Typography"));
|
|
1366
|
-
lines.push(
|
|
1540
|
+
lines.push(block2(host, generateTypographyPrimitives(config, options?.aliasedFamilies)));
|
|
1367
1541
|
lines.push(sectionComment("Primitive: Shadows"));
|
|
1368
|
-
lines.push(
|
|
1542
|
+
lines.push(block2(host, generateShadowPrimitives(config)));
|
|
1369
1543
|
lines.push(sectionComment("Primitive: Motion"));
|
|
1370
|
-
lines.push(
|
|
1544
|
+
lines.push(block2(host, generateMotionPrimitives(config)));
|
|
1371
1545
|
lines.push(sectionComment("Primitive: Miscellaneous"));
|
|
1372
|
-
lines.push(
|
|
1546
|
+
lines.push(block2(host, generateMiscPrimitives()));
|
|
1373
1547
|
return header("Visor Theme \u2014 Primitives") + lines.join("\n");
|
|
1374
1548
|
}
|
|
1375
1549
|
function hairlineDeclName(name) {
|
|
@@ -1381,32 +1555,32 @@ function generateSemanticCss(tokens) {
|
|
|
1381
1555
|
const textDecls = Object.entries(tokens.text).map(
|
|
1382
1556
|
([name, { light }]) => `--text-${name}: ${light};`
|
|
1383
1557
|
);
|
|
1384
|
-
lines.push(
|
|
1558
|
+
lines.push(block2(":root", textDecls));
|
|
1385
1559
|
lines.push(sectionComment("Semantic: Surface"));
|
|
1386
1560
|
const surfaceDecls = Object.entries(tokens.surface).map(
|
|
1387
1561
|
([name, { light }]) => `--surface-${name}: ${light};`
|
|
1388
1562
|
);
|
|
1389
|
-
lines.push(
|
|
1563
|
+
lines.push(block2(":root", surfaceDecls));
|
|
1390
1564
|
lines.push(sectionComment("Semantic: Border"));
|
|
1391
1565
|
const borderDecls = Object.entries(tokens.border).map(
|
|
1392
1566
|
([name, { light }]) => `--border-${name}: ${light};`
|
|
1393
1567
|
);
|
|
1394
|
-
lines.push(
|
|
1568
|
+
lines.push(block2(":root", borderDecls));
|
|
1395
1569
|
lines.push(sectionComment("Semantic: Interactive"));
|
|
1396
1570
|
const interactiveDecls = Object.entries(tokens.interactive).map(
|
|
1397
1571
|
([name, { light }]) => `--interactive-${name}: ${light};`
|
|
1398
1572
|
);
|
|
1399
|
-
lines.push(
|
|
1573
|
+
lines.push(block2(":root", interactiveDecls));
|
|
1400
1574
|
lines.push(sectionComment("Semantic: Intent (aliases)"));
|
|
1401
1575
|
const intentDecls = Object.entries(tokens.intent).map(
|
|
1402
1576
|
([name, { light }]) => `--${name}: ${light};`
|
|
1403
1577
|
);
|
|
1404
|
-
lines.push(
|
|
1578
|
+
lines.push(block2(":root", intentDecls));
|
|
1405
1579
|
lines.push(sectionComment("Semantic: Hairline (aliases)"));
|
|
1406
1580
|
const hairlineDecls = Object.entries(tokens.hairline).map(
|
|
1407
1581
|
([name, { light }]) => `${hairlineDeclName(name)}: ${light};`
|
|
1408
1582
|
);
|
|
1409
|
-
lines.push(
|
|
1583
|
+
lines.push(block2(":root", hairlineDecls));
|
|
1410
1584
|
return header("Visor Theme \u2014 Semantic") + lines.join("\n");
|
|
1411
1585
|
}
|
|
1412
1586
|
var TEXT_SCALE_ALIASES = [
|
|
@@ -1474,17 +1648,17 @@ function generateLightCss(tokens, options) {
|
|
|
1474
1648
|
const { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls } = buildAdaptiveDecls(tokens, "light");
|
|
1475
1649
|
const host = options?.scopePrefix ?? ":root";
|
|
1476
1650
|
lines.push(sectionComment("Adaptive: Text (light)"));
|
|
1477
|
-
lines.push(
|
|
1651
|
+
lines.push(block2(host, textDecls));
|
|
1478
1652
|
lines.push(sectionComment("Adaptive: Surface (light)"));
|
|
1479
|
-
lines.push(
|
|
1653
|
+
lines.push(block2(host, surfaceDecls));
|
|
1480
1654
|
lines.push(sectionComment("Adaptive: Border (light)"));
|
|
1481
|
-
lines.push(
|
|
1655
|
+
lines.push(block2(host, borderDecls));
|
|
1482
1656
|
lines.push(sectionComment("Adaptive: Interactive (light)"));
|
|
1483
|
-
lines.push(
|
|
1657
|
+
lines.push(block2(host, interactiveDecls));
|
|
1484
1658
|
lines.push(sectionComment("Adaptive: Intent aliases (light)"));
|
|
1485
|
-
lines.push(
|
|
1659
|
+
lines.push(block2(host, intentDecls));
|
|
1486
1660
|
lines.push(sectionComment("Adaptive: Hairline aliases (light)"));
|
|
1487
|
-
lines.push(
|
|
1661
|
+
lines.push(block2(host, hairlineDecls));
|
|
1488
1662
|
return header("Visor Theme \u2014 Light") + lines.join("\n");
|
|
1489
1663
|
}
|
|
1490
1664
|
function generateDarkCss(tokens, options) {
|
|
@@ -1495,23 +1669,23 @@ function generateDarkCss(tokens, options) {
|
|
|
1495
1669
|
const darkSelector = darkSelectors.join(",\n");
|
|
1496
1670
|
const prefersSelector = prefix ? `${prefix}:not(.light):not(.theme-light):not([data-theme="light"])` : ':root:not(.light):not(.theme-light):not([data-theme="light"])';
|
|
1497
1671
|
lines.push(sectionComment("Adaptive: Text (dark) \u2014 manual toggle"));
|
|
1498
|
-
lines.push(
|
|
1672
|
+
lines.push(block2(darkSelector, textDecls));
|
|
1499
1673
|
lines.push(sectionComment("Adaptive: Surface (dark) \u2014 manual toggle"));
|
|
1500
|
-
lines.push(
|
|
1674
|
+
lines.push(block2(darkSelector, surfaceDecls));
|
|
1501
1675
|
lines.push(sectionComment("Adaptive: Border (dark) \u2014 manual toggle"));
|
|
1502
|
-
lines.push(
|
|
1676
|
+
lines.push(block2(darkSelector, borderDecls));
|
|
1503
1677
|
lines.push(sectionComment("Adaptive: Interactive (dark) \u2014 manual toggle"));
|
|
1504
|
-
lines.push(
|
|
1678
|
+
lines.push(block2(darkSelector, interactiveDecls));
|
|
1505
1679
|
lines.push(sectionComment("Adaptive: Intent aliases (dark) \u2014 manual toggle"));
|
|
1506
|
-
lines.push(
|
|
1680
|
+
lines.push(block2(darkSelector, intentDecls));
|
|
1507
1681
|
lines.push(sectionComment("Adaptive: Hairline aliases (dark) \u2014 manual toggle"));
|
|
1508
|
-
lines.push(
|
|
1682
|
+
lines.push(block2(darkSelector, hairlineDecls));
|
|
1509
1683
|
lines.push(
|
|
1510
1684
|
sectionComment("Adaptive: Text (dark) \u2014 prefers-color-scheme")
|
|
1511
1685
|
);
|
|
1512
1686
|
lines.push(
|
|
1513
1687
|
`@media (prefers-color-scheme: dark) {
|
|
1514
|
-
${
|
|
1688
|
+
${block2(prefersSelector, textDecls)}}`
|
|
1515
1689
|
);
|
|
1516
1690
|
lines.push("");
|
|
1517
1691
|
lines.push(
|
|
@@ -1519,7 +1693,7 @@ ${block(prefersSelector, textDecls)}}`
|
|
|
1519
1693
|
);
|
|
1520
1694
|
lines.push(
|
|
1521
1695
|
`@media (prefers-color-scheme: dark) {
|
|
1522
|
-
${
|
|
1696
|
+
${block2(prefersSelector, surfaceDecls)}}`
|
|
1523
1697
|
);
|
|
1524
1698
|
lines.push("");
|
|
1525
1699
|
lines.push(
|
|
@@ -1527,7 +1701,7 @@ ${block(prefersSelector, surfaceDecls)}}`
|
|
|
1527
1701
|
);
|
|
1528
1702
|
lines.push(
|
|
1529
1703
|
`@media (prefers-color-scheme: dark) {
|
|
1530
|
-
${
|
|
1704
|
+
${block2(prefersSelector, borderDecls)}}`
|
|
1531
1705
|
);
|
|
1532
1706
|
lines.push("");
|
|
1533
1707
|
lines.push(
|
|
@@ -1535,7 +1709,7 @@ ${block(prefersSelector, borderDecls)}}`
|
|
|
1535
1709
|
);
|
|
1536
1710
|
lines.push(
|
|
1537
1711
|
`@media (prefers-color-scheme: dark) {
|
|
1538
|
-
${
|
|
1712
|
+
${block2(prefersSelector, interactiveDecls)}}`
|
|
1539
1713
|
);
|
|
1540
1714
|
lines.push("");
|
|
1541
1715
|
lines.push(
|
|
@@ -1543,7 +1717,7 @@ ${block(prefersSelector, interactiveDecls)}}`
|
|
|
1543
1717
|
);
|
|
1544
1718
|
lines.push(
|
|
1545
1719
|
`@media (prefers-color-scheme: dark) {
|
|
1546
|
-
${
|
|
1720
|
+
${block2(prefersSelector, intentDecls)}}`
|
|
1547
1721
|
);
|
|
1548
1722
|
lines.push("");
|
|
1549
1723
|
lines.push(
|
|
@@ -1551,7 +1725,7 @@ ${block(prefersSelector, intentDecls)}}`
|
|
|
1551
1725
|
);
|
|
1552
1726
|
lines.push(
|
|
1553
1727
|
`@media (prefers-color-scheme: dark) {
|
|
1554
|
-
${
|
|
1728
|
+
${block2(prefersSelector, hairlineDecls)}}`
|
|
1555
1729
|
);
|
|
1556
1730
|
lines.push("");
|
|
1557
1731
|
return header("Visor Theme \u2014 Dark") + lines.join("\n");
|
|
@@ -1618,6 +1792,14 @@ export {
|
|
|
1618
1792
|
generatePreloadLinks,
|
|
1619
1793
|
generateStylesheetLinks,
|
|
1620
1794
|
resolveThemeFonts,
|
|
1795
|
+
VISOR_BRANDS_CDN,
|
|
1796
|
+
VISOR_DEFAULT_BRAND_PATH,
|
|
1797
|
+
DEFAULT_VISOR_BRAND,
|
|
1798
|
+
buildVisorBrandUrl,
|
|
1799
|
+
resolveBrandSlot,
|
|
1800
|
+
resolveBrandSource,
|
|
1801
|
+
BRAND_VARIANTS,
|
|
1802
|
+
resolveThemeBrand,
|
|
1621
1803
|
normalizeHex,
|
|
1622
1804
|
isValidHex,
|
|
1623
1805
|
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-CSO2avFQ.js';
|
|
2
|
+
export { o as BRAND_VARIANTS, p as BrandVariant, q as ColorFormat, r as FontSource, s as RGBA, t as SemanticTokenValue } from './types-CSO2avFQ.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,86 @@ 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
|
+
animated: {
|
|
635
|
+
$ref: "#/$defs/brandSlot"
|
|
636
|
+
},
|
|
637
|
+
custom: {
|
|
638
|
+
type: "object",
|
|
639
|
+
description: "Operator-defined slots, addressed by key.",
|
|
640
|
+
additionalProperties: {
|
|
641
|
+
$ref: "#/$defs/brandSlot"
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
"if": {
|
|
646
|
+
properties: {
|
|
647
|
+
source: {
|
|
648
|
+
"const": "visor-brands"
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
required: [
|
|
652
|
+
"source"
|
|
653
|
+
]
|
|
654
|
+
},
|
|
655
|
+
then: {
|
|
656
|
+
anyOf: [
|
|
657
|
+
{
|
|
658
|
+
required: [
|
|
659
|
+
"org"
|
|
660
|
+
]
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
required: [
|
|
664
|
+
"cdn-overrides"
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
]
|
|
668
|
+
}
|
|
669
|
+
},
|
|
504
670
|
spacing: {
|
|
505
671
|
type: "object",
|
|
506
672
|
description: "Spacing configuration.",
|
|
@@ -667,6 +833,40 @@ var $defs = {
|
|
|
667
833
|
}
|
|
668
834
|
]
|
|
669
835
|
},
|
|
836
|
+
brandSlot: {
|
|
837
|
+
type: "object",
|
|
838
|
+
description: "A single brand-variant declaration. Resolves to light + dark asset URLs; clearSpace and aspectRatio are tokenized per variant (VI-470, Q6).",
|
|
839
|
+
additionalProperties: false,
|
|
840
|
+
properties: {
|
|
841
|
+
slug: {
|
|
842
|
+
type: "string",
|
|
843
|
+
description: "CDN/asset-set slug. Required when source is visor-brands (Phase 2+)."
|
|
844
|
+
},
|
|
845
|
+
formats: {
|
|
846
|
+
type: "array",
|
|
847
|
+
items: {
|
|
848
|
+
type: "string"
|
|
849
|
+
},
|
|
850
|
+
description: "Preferred asset formats, first wins (e.g. [\"svg\"], [\"svg\", \"png\"])."
|
|
851
|
+
},
|
|
852
|
+
light: {
|
|
853
|
+
type: "string",
|
|
854
|
+
description: "Explicit light-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
855
|
+
},
|
|
856
|
+
dark: {
|
|
857
|
+
type: "string",
|
|
858
|
+
description: "Explicit dark-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
859
|
+
},
|
|
860
|
+
clearSpace: {
|
|
861
|
+
type: "string",
|
|
862
|
+
description: "Tokenized safe-zone padding enforced by <Logo> (e.g. \"0.5rem\"). Emitted as --brand-{variant}-clear-space."
|
|
863
|
+
},
|
|
864
|
+
aspectRatio: {
|
|
865
|
+
type: "string",
|
|
866
|
+
description: "Tokenized locked aspect ratio (e.g. \"3 / 1\"); else derived from the SVG viewBox. Emitted as --brand-{variant}-aspect-ratio."
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
},
|
|
670
870
|
textSlotOverride: {
|
|
671
871
|
type: "object",
|
|
672
872
|
description: "Per-slot override for one Material TextTheme entry.",
|
|
@@ -1021,4 +1221,4 @@ declare function cleanFontValue(val: string): string;
|
|
|
1021
1221
|
*/
|
|
1022
1222
|
declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
|
|
1023
1223
|
|
|
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 };
|
|
1224
|
+
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-KFTTL3XP.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,55 @@ 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
|
+
animated: { $ref: "#/$defs/brandSlot" },
|
|
461
|
+
custom: {
|
|
462
|
+
type: "object",
|
|
463
|
+
description: "Operator-defined slots, addressed by key.",
|
|
464
|
+
additionalProperties: { $ref: "#/$defs/brandSlot" }
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
if: {
|
|
468
|
+
properties: { source: { const: "visor-brands" } },
|
|
469
|
+
required: ["source"]
|
|
470
|
+
},
|
|
471
|
+
then: {
|
|
472
|
+
anyOf: [
|
|
473
|
+
{ required: ["org"] },
|
|
474
|
+
{ required: ["cdn-overrides"] }
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
},
|
|
421
478
|
spacing: {
|
|
422
479
|
type: "object",
|
|
423
480
|
description: "Spacing configuration.",
|
|
@@ -532,6 +589,38 @@ var visor_theme_schema_default = {
|
|
|
532
589
|
{ pattern: "^oklch\\(" }
|
|
533
590
|
]
|
|
534
591
|
},
|
|
592
|
+
brandSlot: {
|
|
593
|
+
type: "object",
|
|
594
|
+
description: "A single brand-variant declaration. Resolves to light + dark asset URLs; clearSpace and aspectRatio are tokenized per variant (VI-470, Q6).",
|
|
595
|
+
additionalProperties: false,
|
|
596
|
+
properties: {
|
|
597
|
+
slug: {
|
|
598
|
+
type: "string",
|
|
599
|
+
description: "CDN/asset-set slug. Required when source is visor-brands (Phase 2+)."
|
|
600
|
+
},
|
|
601
|
+
formats: {
|
|
602
|
+
type: "array",
|
|
603
|
+
items: { type: "string" },
|
|
604
|
+
description: 'Preferred asset formats, first wins (e.g. ["svg"], ["svg", "png"]).'
|
|
605
|
+
},
|
|
606
|
+
light: {
|
|
607
|
+
type: "string",
|
|
608
|
+
description: "Explicit light-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
609
|
+
},
|
|
610
|
+
dark: {
|
|
611
|
+
type: "string",
|
|
612
|
+
description: "Explicit dark-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
613
|
+
},
|
|
614
|
+
clearSpace: {
|
|
615
|
+
type: "string",
|
|
616
|
+
description: 'Tokenized safe-zone padding enforced by <Logo> (e.g. "0.5rem"). Emitted as --brand-{variant}-clear-space.'
|
|
617
|
+
},
|
|
618
|
+
aspectRatio: {
|
|
619
|
+
type: "string",
|
|
620
|
+
description: 'Tokenized locked aspect ratio (e.g. "3 / 1"); else derived from the SVG viewBox. Emitted as --brand-{variant}-aspect-ratio.'
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
},
|
|
535
624
|
textSlotOverride: {
|
|
536
625
|
type: "object",
|
|
537
626
|
description: "Per-slot override for one Material TextTheme entry.",
|
|
@@ -567,6 +656,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
567
656
|
"colors",
|
|
568
657
|
"colors-dark",
|
|
569
658
|
"typography",
|
|
659
|
+
"brand",
|
|
570
660
|
"spacing",
|
|
571
661
|
"radius",
|
|
572
662
|
"shadows",
|
|
@@ -607,6 +697,22 @@ var KNOWN_SHADOW_KEYS = /* @__PURE__ */ new Set(["xs", "sm", "md", "lg", "xl"]);
|
|
|
607
697
|
var KNOWN_STROKE_WIDTH_KEYS = /* @__PURE__ */ new Set(["thin", "regular", "medium", "thick"]);
|
|
608
698
|
var KNOWN_MOTION_KEYS = /* @__PURE__ */ new Set(["duration-fast", "duration-normal", "duration-slow", "easing", "easing-overshoot"]);
|
|
609
699
|
var KNOWN_OVERRIDES_KEYS = /* @__PURE__ */ new Set(["light", "dark"]);
|
|
700
|
+
var KNOWN_BRAND_KEYS = /* @__PURE__ */ new Set([
|
|
701
|
+
"org",
|
|
702
|
+
"source",
|
|
703
|
+
"cdn-overrides",
|
|
704
|
+
"logo",
|
|
705
|
+
"brandmark",
|
|
706
|
+
"wordmark",
|
|
707
|
+
"monochrome",
|
|
708
|
+
"favicon",
|
|
709
|
+
"animated",
|
|
710
|
+
"custom"
|
|
711
|
+
]);
|
|
712
|
+
var KNOWN_BRAND_STANDARD_SLOTS = ["logo", "brandmark", "wordmark", "monochrome", "favicon", "animated"];
|
|
713
|
+
var KNOWN_BRAND_SLOT_KEYS = /* @__PURE__ */ new Set(["slug", "formats", "light", "dark", "clearSpace", "aspectRatio"]);
|
|
714
|
+
var KNOWN_BRAND_SOURCES = /* @__PURE__ */ new Set(["visor-brands", "local"]);
|
|
715
|
+
var KNOWN_BRAND_CDN_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["visor-brands"]);
|
|
610
716
|
function checkUnknownKeys(obj, errors) {
|
|
611
717
|
for (const key of Object.keys(obj)) {
|
|
612
718
|
if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
|
|
@@ -702,6 +808,46 @@ function checkUnknownKeys(obj, errors) {
|
|
|
702
808
|
}
|
|
703
809
|
}
|
|
704
810
|
}
|
|
811
|
+
if (typeof obj.brand === "object" && obj.brand !== null) {
|
|
812
|
+
const brand = obj.brand;
|
|
813
|
+
for (const key of Object.keys(brand)) {
|
|
814
|
+
if (!KNOWN_BRAND_KEYS.has(key)) {
|
|
815
|
+
errors.push(`Unknown key 'brand.${key}'. Valid keys: ${[...KNOWN_BRAND_KEYS].join(", ")}`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (typeof brand["cdn-overrides"] === "object" && brand["cdn-overrides"] !== null) {
|
|
819
|
+
for (const key of Object.keys(brand["cdn-overrides"])) {
|
|
820
|
+
if (!KNOWN_BRAND_CDN_OVERRIDE_KEYS.has(key)) {
|
|
821
|
+
errors.push(`Unknown key 'brand.cdn-overrides.${key}'. Valid keys: ${[...KNOWN_BRAND_CDN_OVERRIDE_KEYS].join(", ")}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
for (const slot of KNOWN_BRAND_STANDARD_SLOTS) {
|
|
826
|
+
const slotObj = brand[slot];
|
|
827
|
+
if (typeof slotObj === "object" && slotObj !== null) {
|
|
828
|
+
for (const key of Object.keys(slotObj)) {
|
|
829
|
+
if (!KNOWN_BRAND_SLOT_KEYS.has(key)) {
|
|
830
|
+
errors.push(`Unknown key 'brand.${slot}.${key}'. Valid keys: ${[...KNOWN_BRAND_SLOT_KEYS].join(", ")}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (typeof brand.custom === "object" && brand.custom !== null) {
|
|
836
|
+
const custom = brand.custom;
|
|
837
|
+
for (const slotName of Object.keys(custom)) {
|
|
838
|
+
const slotObj = custom[slotName];
|
|
839
|
+
if (typeof slotObj !== "object" || slotObj === null) {
|
|
840
|
+
errors.push(`'brand.custom.${slotName}' must be an object with optional slug/formats/light/dark/clearSpace/aspectRatio fields`);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
for (const key of Object.keys(slotObj)) {
|
|
844
|
+
if (!KNOWN_BRAND_SLOT_KEYS.has(key)) {
|
|
845
|
+
errors.push(`Unknown key 'brand.custom.${slotName}.${key}'. Valid keys: ${[...KNOWN_BRAND_SLOT_KEYS].join(", ")}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
705
851
|
if (typeof obj.spacing === "object" && obj.spacing !== null) {
|
|
706
852
|
for (const key of Object.keys(obj.spacing)) {
|
|
707
853
|
if (!KNOWN_SPACING_KEYS.has(key)) {
|
|
@@ -857,6 +1003,60 @@ function validateConfig(config) {
|
|
|
857
1003
|
}
|
|
858
1004
|
}
|
|
859
1005
|
}
|
|
1006
|
+
if (obj.brand !== void 0) {
|
|
1007
|
+
if (typeof obj.brand !== "object" || obj.brand === null) {
|
|
1008
|
+
errors.push("'brand' must be an object");
|
|
1009
|
+
} else {
|
|
1010
|
+
const brand = obj.brand;
|
|
1011
|
+
if (brand.source !== void 0 && !KNOWN_BRAND_SOURCES.has(brand.source)) {
|
|
1012
|
+
errors.push(`'brand.source' must be one of: ${[...KNOWN_BRAND_SOURCES].join(", ")}`);
|
|
1013
|
+
}
|
|
1014
|
+
const brandCdn = brand["cdn-overrides"];
|
|
1015
|
+
const visorBrandsOverride = brandCdn?.["visor-brands"];
|
|
1016
|
+
if (visorBrandsOverride !== void 0 && typeof visorBrandsOverride !== "string") {
|
|
1017
|
+
errors.push("'brand.cdn-overrides.visor-brands' must be a string URL");
|
|
1018
|
+
}
|
|
1019
|
+
if (typeof visorBrandsOverride === "string" && visorBrandsOverride.length === 0) {
|
|
1020
|
+
errors.push("'brand.cdn-overrides.visor-brands' must not be empty");
|
|
1021
|
+
}
|
|
1022
|
+
const orgOptional = typeof visorBrandsOverride === "string" && visorBrandsOverride.length > 0;
|
|
1023
|
+
if (brand.source === "visor-brands" && !orgOptional && !brand.org) {
|
|
1024
|
+
errors.push("'brand.org' is required when brand.source is 'visor-brands' (unless brand.cdn-overrides.visor-brands is set)");
|
|
1025
|
+
}
|
|
1026
|
+
const allSlots = [];
|
|
1027
|
+
for (const slot of KNOWN_BRAND_STANDARD_SLOTS) {
|
|
1028
|
+
if (typeof brand[slot] === "object" && brand[slot] !== null) {
|
|
1029
|
+
allSlots.push(brand[slot]);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (typeof brand.custom === "object" && brand.custom !== null) {
|
|
1033
|
+
for (const slot of Object.values(brand.custom)) {
|
|
1034
|
+
if (typeof slot === "object" && slot !== null) allSlots.push(slot);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
for (const slot of allSlots) {
|
|
1038
|
+
if (slot.formats !== void 0) {
|
|
1039
|
+
if (!Array.isArray(slot.formats) || !slot.formats.every((f) => typeof f === "string")) {
|
|
1040
|
+
errors.push(`'brand.<slot>.formats' must be an array of format strings (e.g., ["svg", "png"])`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (typeof brand.animated === "object" && brand.animated !== null) {
|
|
1045
|
+
const animated = brand.animated;
|
|
1046
|
+
if (Array.isArray(animated.formats) && !animated.formats.every(
|
|
1047
|
+
(f) => typeof f === "string" && f.toLowerCase() === "svg"
|
|
1048
|
+
)) {
|
|
1049
|
+
errors.push("'brand.animated.formats' must be SVG-only (the animated slot accepts self-contained animated SVGs only)");
|
|
1050
|
+
}
|
|
1051
|
+
for (const mode of ["light", "dark"]) {
|
|
1052
|
+
const p = animated[mode];
|
|
1053
|
+
if (typeof p === "string" && !p.toLowerCase().endsWith(".svg")) {
|
|
1054
|
+
errors.push(`'brand.animated.${mode}' must be an .svg path (the animated slot is SVG-only)`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
860
1060
|
if (obj.overrides !== void 0) {
|
|
861
1061
|
if (typeof obj.overrides !== "object" || obj.overrides === null) {
|
|
862
1062
|
errors.push("'overrides' must be an object");
|
|
@@ -917,6 +1117,23 @@ var DEFAULTS = {
|
|
|
917
1117
|
easing: "cubic-bezier(0.4, 0, 0.2, 1)"
|
|
918
1118
|
}
|
|
919
1119
|
};
|
|
1120
|
+
function resolveBrand(brand) {
|
|
1121
|
+
if (!brand) return DEFAULT_VISOR_BRAND;
|
|
1122
|
+
return {
|
|
1123
|
+
org: brand.org ?? DEFAULT_VISOR_BRAND.org,
|
|
1124
|
+
source: brand.source ?? DEFAULT_VISOR_BRAND.source,
|
|
1125
|
+
...brand["cdn-overrides"] && { "cdn-overrides": brand["cdn-overrides"] },
|
|
1126
|
+
logo: brand.logo ?? DEFAULT_VISOR_BRAND.logo,
|
|
1127
|
+
brandmark: brand.brandmark ?? DEFAULT_VISOR_BRAND.brandmark,
|
|
1128
|
+
wordmark: brand.wordmark ?? DEFAULT_VISOR_BRAND.wordmark,
|
|
1129
|
+
monochrome: brand.monochrome ?? DEFAULT_VISOR_BRAND.monochrome,
|
|
1130
|
+
favicon: brand.favicon ?? DEFAULT_VISOR_BRAND.favicon,
|
|
1131
|
+
// animated is optional with no Visor default (D2): pass through only when a
|
|
1132
|
+
// theme declares it, so undeclared themes emit no --brand-animated.
|
|
1133
|
+
...brand.animated && { animated: brand.animated },
|
|
1134
|
+
...brand.custom && { custom: brand.custom }
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
920
1137
|
function resolveConfig(config) {
|
|
921
1138
|
const colors = config.colors;
|
|
922
1139
|
const originalColors = {};
|
|
@@ -997,6 +1214,7 @@ function resolveConfig(config) {
|
|
|
997
1214
|
},
|
|
998
1215
|
slots: config.typography?.slots ?? {}
|
|
999
1216
|
},
|
|
1217
|
+
brand: resolveBrand(config.brand),
|
|
1000
1218
|
spacing: {
|
|
1001
1219
|
base: config.spacing?.base ?? DEFAULTS.spacing.base
|
|
1002
1220
|
},
|
|
@@ -1208,7 +1426,7 @@ var SEMANTIC_SURFACE_MAP = {
|
|
|
1208
1426
|
// VI-478: status soft tints (BL-193) — alpha overlays, semantically distinct
|
|
1209
1427
|
// from the OPAQUE `surface-{status}-subtle` above (do NOT alias them together).
|
|
1210
1428
|
// Default to a color-mix of the status color so they track the theme; themes
|
|
1211
|
-
// pin exact values via overrides (blacklight-
|
|
1429
|
+
// pin exact values via overrides (blacklight-pro: success @10%,
|
|
1212
1430
|
// warning/error @12%).
|
|
1213
1431
|
"success-soft": {
|
|
1214
1432
|
light: { constant: "color-mix(in srgb, var(--color-success-500) 10%, transparent)" },
|
|
@@ -1305,7 +1523,7 @@ var SEMANTIC_INTERACTIVE_MAP = {
|
|
|
1305
1523
|
// VI-478: brand-derived alpha-overlay helpers (BL-193). `soft`/`glow` are
|
|
1306
1524
|
// alpha overlays that track the theme's primary via color-mix (distinct from
|
|
1307
1525
|
// any opaque surface); `strong` is a solid lightened-brand emphasis color.
|
|
1308
|
-
// Themes pin exact values via overrides — e.g. blacklight-
|
|
1526
|
+
// Themes pin exact values via overrides — e.g. blacklight-pro sets
|
|
1309
1527
|
// soft @12% / glow @32% / strong #FFD050.
|
|
1310
1528
|
"primary-soft": {
|
|
1311
1529
|
light: { constant: "color-mix(in srgb, var(--color-primary-500) 12%, transparent)" },
|
|
@@ -2997,12 +3215,17 @@ function extractFromCSS(files, name = "extracted-theme") {
|
|
|
2997
3215
|
return { config, tokens, unmapped, warnings };
|
|
2998
3216
|
}
|
|
2999
3217
|
export {
|
|
3218
|
+
BRAND_VARIANTS,
|
|
3219
|
+
DEFAULT_VISOR_BRAND,
|
|
3000
3220
|
FONT_WEIGHT_ALIASES,
|
|
3001
3221
|
SEMANTIC_MAP,
|
|
3002
3222
|
TAILWIND_GRAY,
|
|
3223
|
+
VISOR_BRANDS_CDN,
|
|
3224
|
+
VISOR_DEFAULT_BRAND_PATH,
|
|
3003
3225
|
VISOR_FONTS_CDN,
|
|
3004
3226
|
applyOverrides,
|
|
3005
3227
|
assignSemanticTokens,
|
|
3228
|
+
buildVisorBrandUrl,
|
|
3006
3229
|
buildVisorFontUrl,
|
|
3007
3230
|
clampToSrgb,
|
|
3008
3231
|
cleanFontValue,
|
|
@@ -3042,8 +3265,11 @@ export {
|
|
|
3042
3265
|
parseHsla,
|
|
3043
3266
|
parseOklch,
|
|
3044
3267
|
parseRgba,
|
|
3268
|
+
resolveBrandSlot,
|
|
3269
|
+
resolveBrandSource,
|
|
3045
3270
|
resolveConfig,
|
|
3046
3271
|
resolveFont,
|
|
3272
|
+
resolveThemeBrand,
|
|
3047
3273
|
resolveThemeFonts,
|
|
3048
3274
|
rgbToHex,
|
|
3049
3275
|
serializeColor,
|
|
@@ -119,6 +119,111 @@ 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" | "animated";
|
|
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
|
+
/**
|
|
189
|
+
* Animated lockup. Optional and SVG-only (D2/D3): the asset must be a
|
|
190
|
+
* self-contained animated SVG (inlined `<style>`/@keyframes or SMIL) so it
|
|
191
|
+
* animates inside `<img>`. Stock themes omit this — absent → no
|
|
192
|
+
* `--brand-animated` emitted. Reduced-motion consumers fall back to `logo`.
|
|
193
|
+
*/
|
|
194
|
+
animated?: BrandSlot;
|
|
195
|
+
/** Operator-defined slots, addressed by key. */
|
|
196
|
+
custom?: Record<string, BrandSlot>;
|
|
197
|
+
}
|
|
198
|
+
/** A single resolved brand variant — light + dark asset URLs plus its tokens. */
|
|
199
|
+
interface BrandResolution {
|
|
200
|
+
/** The variant slot this resolution covers ("logo", "brandmark", or a custom key). */
|
|
201
|
+
variant: string;
|
|
202
|
+
/** Where this asset comes from. */
|
|
203
|
+
source: BrandSource;
|
|
204
|
+
/** Resolved light-mode asset URL/path. */
|
|
205
|
+
light: string;
|
|
206
|
+
/** Resolved dark-mode asset URL/path. */
|
|
207
|
+
dark: string;
|
|
208
|
+
/** Tokenized safe-zone padding (null when not declared). */
|
|
209
|
+
clearSpace: string | null;
|
|
210
|
+
/** Tokenized locked aspect ratio (null when not declared). */
|
|
211
|
+
aspectRatio: string | null;
|
|
212
|
+
/** Human-readable guidance for assets needing manual setup (e.g. local files). */
|
|
213
|
+
guidance: string | null;
|
|
214
|
+
}
|
|
215
|
+
/** Result of resolving all brand assets for a theme. */
|
|
216
|
+
interface ThemeBrandResult {
|
|
217
|
+
/** Resolved standard-slot variants in declaration order. */
|
|
218
|
+
variants: BrandResolution[];
|
|
219
|
+
/** Resolved custom-slot variants in declaration order. */
|
|
220
|
+
custom: BrandResolution[];
|
|
221
|
+
/** Mode-scoped `--brand-*` CSS custom property declarations (no @layer wrapper). */
|
|
222
|
+
css: string;
|
|
223
|
+
/** Warnings for assets needing manual setup. */
|
|
224
|
+
warnings: string[];
|
|
225
|
+
}
|
|
226
|
+
|
|
122
227
|
/**
|
|
123
228
|
* Types for the Visor Theme Engine
|
|
124
229
|
*
|
|
@@ -240,6 +345,13 @@ interface VisorThemeConfig {
|
|
|
240
345
|
*/
|
|
241
346
|
slots?: Partial<Record<MaterialTextSlot, TextSlotOverride>>;
|
|
242
347
|
};
|
|
348
|
+
/**
|
|
349
|
+
* Brand-asset declarations (VI-470). Structured like `typography` — per-slot
|
|
350
|
+
* logo/brandmark/wordmark/etc. entries resolved to asset URLs and emitted as
|
|
351
|
+
* mode-scoped `--brand-*` CSS vars in a dedicated `visor-brand` cascade layer.
|
|
352
|
+
* Omitted → the Visor default brand (stock themes are not logo-less).
|
|
353
|
+
*/
|
|
354
|
+
brand?: VisorBrand;
|
|
243
355
|
spacing?: {
|
|
244
356
|
base?: number;
|
|
245
357
|
};
|
|
@@ -358,6 +470,11 @@ interface ResolvedThemeConfig {
|
|
|
358
470
|
*/
|
|
359
471
|
slots: Partial<Record<MaterialTextSlot, TextSlotOverride>>;
|
|
360
472
|
};
|
|
473
|
+
/**
|
|
474
|
+
* Resolved brand block (VI-470). Always present — falls back to the Visor
|
|
475
|
+
* default brand when the source config omits `brand` (D3).
|
|
476
|
+
*/
|
|
477
|
+
brand: VisorBrand;
|
|
361
478
|
spacing: {
|
|
362
479
|
base: number;
|
|
363
480
|
};
|
|
@@ -434,4 +551,4 @@ interface ThemeData {
|
|
|
434
551
|
output: ThemeOutput;
|
|
435
552
|
}
|
|
436
553
|
|
|
437
|
-
export type
|
|
554
|
+
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.13.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,55 @@
|
|
|
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
|
+
"animated": { "$ref": "#/$defs/brandSlot" },
|
|
257
|
+
"custom": {
|
|
258
|
+
"type": "object",
|
|
259
|
+
"description": "Operator-defined slots, addressed by key.",
|
|
260
|
+
"additionalProperties": { "$ref": "#/$defs/brandSlot" }
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"if": {
|
|
264
|
+
"properties": { "source": { "const": "visor-brands" } },
|
|
265
|
+
"required": ["source"]
|
|
266
|
+
},
|
|
267
|
+
"then": {
|
|
268
|
+
"anyOf": [
|
|
269
|
+
{ "required": ["org"] },
|
|
270
|
+
{ "required": ["cdn-overrides"] }
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
},
|
|
225
274
|
"spacing": {
|
|
226
275
|
"type": "object",
|
|
227
276
|
"description": "Spacing configuration.",
|
|
@@ -336,6 +385,38 @@
|
|
|
336
385
|
{ "pattern": "^oklch\\(" }
|
|
337
386
|
]
|
|
338
387
|
},
|
|
388
|
+
"brandSlot": {
|
|
389
|
+
"type": "object",
|
|
390
|
+
"description": "A single brand-variant declaration. Resolves to light + dark asset URLs; clearSpace and aspectRatio are tokenized per variant (VI-470, Q6).",
|
|
391
|
+
"additionalProperties": false,
|
|
392
|
+
"properties": {
|
|
393
|
+
"slug": {
|
|
394
|
+
"type": "string",
|
|
395
|
+
"description": "CDN/asset-set slug. Required when source is visor-brands (Phase 2+)."
|
|
396
|
+
},
|
|
397
|
+
"formats": {
|
|
398
|
+
"type": "array",
|
|
399
|
+
"items": { "type": "string" },
|
|
400
|
+
"description": "Preferred asset formats, first wins (e.g. [\"svg\"], [\"svg\", \"png\"])."
|
|
401
|
+
},
|
|
402
|
+
"light": {
|
|
403
|
+
"type": "string",
|
|
404
|
+
"description": "Explicit light-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
405
|
+
},
|
|
406
|
+
"dark": {
|
|
407
|
+
"type": "string",
|
|
408
|
+
"description": "Explicit dark-mode asset filename or public/-relative path (overrides the inferred name)."
|
|
409
|
+
},
|
|
410
|
+
"clearSpace": {
|
|
411
|
+
"type": "string",
|
|
412
|
+
"description": "Tokenized safe-zone padding enforced by <Logo> (e.g. \"0.5rem\"). Emitted as --brand-{variant}-clear-space."
|
|
413
|
+
},
|
|
414
|
+
"aspectRatio": {
|
|
415
|
+
"type": "string",
|
|
416
|
+
"description": "Tokenized locked aspect ratio (e.g. \"3 / 1\"); else derived from the SVG viewBox. Emitted as --brand-{variant}-aspect-ratio."
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
},
|
|
339
420
|
"textSlotOverride": {
|
|
340
421
|
"type": "object",
|
|
341
422
|
"description": "Per-slot override for one Material TextTheme entry.",
|