@loworbitstudio/visor-theme-engine 0.9.0 → 0.11.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.
@@ -1,4 +1,4 @@
1
- import { c as GeneratedPrimitives, i as SemanticTokens, R as ResolvedThemeConfig } from '../types-0hotGz1u.js';
1
+ import { c as GeneratedPrimitives, i as SemanticTokens, R as ResolvedThemeConfig } from '../types-Dwc1V0Nc.js';
2
2
 
3
3
  /**
4
4
  * Adapter types for the Visor theme engine.
@@ -6,14 +6,18 @@ import {
6
6
  buildVisorFontUrl,
7
7
  fontStack,
8
8
  generateDarkCss,
9
+ generateHairlineDecls,
10
+ generateIntentDecls,
9
11
  generateLightCss,
10
12
  generatePrimitivesCss,
11
13
  generateShadeScale,
14
+ generateSpaceAliasDecls,
15
+ generateTextScaleAliasDecls,
12
16
  header,
13
17
  parseColor,
14
18
  resolveThemeFonts,
15
19
  sectionComment
16
- } from "../chunk-BEFH5BTX.js";
20
+ } from "../chunk-43TVIXUS.js";
17
21
 
18
22
  // src/adapters/layers.ts
19
23
  var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
@@ -589,9 +593,46 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
589
593
  lines.push(sectionComment2("Fumadocs bridge: light"));
590
594
  lines.push(block(`html:not(.dark) ${scopeClass}`, generateFumadocsBridgeDecls(input.tokens, "light")));
591
595
  lines.push("");
592
- const layered = wrapInLayer("visor-adaptive", lines.join("\n").trim());
596
+ const semanticLines = [];
597
+ semanticLines.push("\n/* \u2500\u2500 Layer: Semantic aliases (VI-451) \u2500\u2500 */");
598
+ semanticLines.push(sectionComment2("Discrete: Text size aliases (--text-N)"));
599
+ semanticLines.push(block(scopeClass, generateTextScaleAliasDecls()));
600
+ semanticLines.push("");
601
+ semanticLines.push(sectionComment2("Discrete: Space aliases (--space-N)"));
602
+ semanticLines.push(block(scopeClass, generateSpaceAliasDecls(input.config)));
603
+ semanticLines.push("");
604
+ semanticLines.push(sectionComment2("Intent aliases (light)"));
605
+ semanticLines.push(block(`html:not(.dark) ${scopeClass}`, generateIntentDecls(input.tokens, "light")));
606
+ semanticLines.push("");
607
+ semanticLines.push(sectionComment2("Hairline aliases (light)"));
608
+ semanticLines.push(block(`html:not(.dark) ${scopeClass}`, generateHairlineDecls(input.tokens, "light")));
609
+ semanticLines.push("");
610
+ semanticLines.push(sectionComment2("Intent aliases (dark) \u2014 manual toggle"));
611
+ semanticLines.push(block(`.dark ${scopeClass}`, generateIntentDecls(input.tokens, "dark")));
612
+ semanticLines.push("");
613
+ semanticLines.push(sectionComment2("Hairline aliases (dark) \u2014 manual toggle"));
614
+ semanticLines.push(block(`.dark ${scopeClass}`, generateHairlineDecls(input.tokens, "dark")));
615
+ semanticLines.push("");
616
+ semanticLines.push(sectionComment2("Intent aliases (dark) \u2014 prefers-color-scheme"));
617
+ {
618
+ const inner = block(`${scopeClass}:not(.light)`, generateIntentDecls(input.tokens, "dark"));
619
+ semanticLines.push(`@media (prefers-color-scheme: dark) {
620
+ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
621
+ }`);
622
+ }
623
+ semanticLines.push("");
624
+ semanticLines.push(sectionComment2("Hairline aliases (dark) \u2014 prefers-color-scheme"));
625
+ {
626
+ const inner = block(`${scopeClass}:not(.light)`, generateHairlineDecls(input.tokens, "dark"));
627
+ semanticLines.push(`@media (prefers-color-scheme: dark) {
628
+ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
629
+ }`);
630
+ }
631
+ semanticLines.push("");
632
+ const adaptiveLayer = wrapInLayer("visor-adaptive", lines.join("\n").trim());
633
+ const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
593
634
  const head = fontLines.length > 0 ? fontLines.join("\n") + "\n" : "";
594
- return head + LAYER_ORDER + "\n\n" + layered + "\n";
635
+ return head + LAYER_ORDER + "\n\n" + semanticLayer + "\n\n" + adaptiveLayer + "\n";
595
636
  }
596
637
 
597
638
  // src/flutter/color-to-dart.ts
@@ -1324,6 +1324,9 @@ function generateMotionPrimitives(config) {
1324
1324
  decls.push(
1325
1325
  "--motion-easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);"
1326
1326
  );
1327
+ if (config.motion["easing-overshoot"]) {
1328
+ decls.push(`--motion-easing-overshoot: ${config.motion["easing-overshoot"]};`);
1329
+ }
1327
1330
  return decls;
1328
1331
  }
1329
1332
  function generateMiscPrimitives() {
@@ -1369,6 +1372,9 @@ function generatePrimitivesCss(primitives, config, options) {
1369
1372
  lines.push(block(host, generateMiscPrimitives()));
1370
1373
  return header("Visor Theme \u2014 Primitives") + lines.join("\n");
1371
1374
  }
1375
+ function hairlineDeclName(name) {
1376
+ return name === "default" ? "--hairline" : `--hairline-${name}`;
1377
+ }
1372
1378
  function generateSemanticCss(tokens) {
1373
1379
  const lines = [];
1374
1380
  lines.push(sectionComment("Semantic: Text"));
@@ -1391,8 +1397,61 @@ function generateSemanticCss(tokens) {
1391
1397
  ([name, { light }]) => `--interactive-${name}: ${light};`
1392
1398
  );
1393
1399
  lines.push(block(":root", interactiveDecls));
1400
+ lines.push(sectionComment("Semantic: Intent (aliases)"));
1401
+ const intentDecls = Object.entries(tokens.intent).map(
1402
+ ([name, { light }]) => `--${name}: ${light};`
1403
+ );
1404
+ lines.push(block(":root", intentDecls));
1405
+ lines.push(sectionComment("Semantic: Hairline (aliases)"));
1406
+ const hairlineDecls = Object.entries(tokens.hairline).map(
1407
+ ([name, { light }]) => `${hairlineDeclName(name)}: ${light};`
1408
+ );
1409
+ lines.push(block(":root", hairlineDecls));
1394
1410
  return header("Visor Theme \u2014 Semantic") + lines.join("\n");
1395
1411
  }
1412
+ var TEXT_SCALE_ALIASES = [
1413
+ 11,
1414
+ 13,
1415
+ 14,
1416
+ 16,
1417
+ 20,
1418
+ 24,
1419
+ 32,
1420
+ 40,
1421
+ 48
1422
+ ];
1423
+ function generateTextScaleAliasDecls() {
1424
+ return TEXT_SCALE_ALIASES.map((px) => `--text-${px}: ${px}px;`);
1425
+ }
1426
+ var SPACE_ALIAS_MULTIPLIERS = [
1427
+ 1,
1428
+ 2,
1429
+ 3,
1430
+ 4,
1431
+ 5,
1432
+ 6,
1433
+ 8,
1434
+ 10,
1435
+ 12,
1436
+ 16
1437
+ ];
1438
+ function generateSpaceAliasDecls(config) {
1439
+ const base = config.spacing.base;
1440
+ return SPACE_ALIAS_MULTIPLIERS.map((m) => {
1441
+ const px = base * m;
1442
+ return `--space-${m}: ${px}px;`;
1443
+ });
1444
+ }
1445
+ function generateIntentDecls(tokens, mode) {
1446
+ return Object.entries(tokens.intent).map(
1447
+ ([name, value]) => `--${name}: ${value[mode]};`
1448
+ );
1449
+ }
1450
+ function generateHairlineDecls(tokens, mode) {
1451
+ return Object.entries(tokens.hairline).map(
1452
+ ([name, value]) => `${hairlineDeclName(name)}: ${value[mode]};`
1453
+ );
1454
+ }
1396
1455
  function buildAdaptiveDecls(tokens, theme) {
1397
1456
  const textDecls = Object.entries(tokens.text).map(
1398
1457
  ([name, values]) => `--text-${name}: ${values[theme]};`
@@ -1406,11 +1465,13 @@ function buildAdaptiveDecls(tokens, theme) {
1406
1465
  const interactiveDecls = Object.entries(tokens.interactive).map(
1407
1466
  ([name, values]) => `--interactive-${name}: ${values[theme]};`
1408
1467
  );
1409
- return { textDecls, surfaceDecls, borderDecls, interactiveDecls };
1468
+ const intentDecls = generateIntentDecls(tokens, theme);
1469
+ const hairlineDecls = generateHairlineDecls(tokens, theme);
1470
+ return { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls };
1410
1471
  }
1411
1472
  function generateLightCss(tokens, options) {
1412
1473
  const lines = [];
1413
- const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "light");
1474
+ const { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls } = buildAdaptiveDecls(tokens, "light");
1414
1475
  const host = options?.scopePrefix ?? ":root";
1415
1476
  lines.push(sectionComment("Adaptive: Text (light)"));
1416
1477
  lines.push(block(host, textDecls));
@@ -1420,11 +1481,15 @@ function generateLightCss(tokens, options) {
1420
1481
  lines.push(block(host, borderDecls));
1421
1482
  lines.push(sectionComment("Adaptive: Interactive (light)"));
1422
1483
  lines.push(block(host, interactiveDecls));
1484
+ lines.push(sectionComment("Adaptive: Intent aliases (light)"));
1485
+ lines.push(block(host, intentDecls));
1486
+ lines.push(sectionComment("Adaptive: Hairline aliases (light)"));
1487
+ lines.push(block(host, hairlineDecls));
1423
1488
  return header("Visor Theme \u2014 Light") + lines.join("\n");
1424
1489
  }
1425
1490
  function generateDarkCss(tokens, options) {
1426
1491
  const lines = [];
1427
- const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "dark");
1492
+ const { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls } = buildAdaptiveDecls(tokens, "dark");
1428
1493
  const prefix = options?.scopePrefix;
1429
1494
  const darkSelectors = prefix ? [`${prefix}.dark`, `${prefix}.theme-dark`, `${prefix}[data-theme="dark"]`] : [".dark", ".theme-dark", '[data-theme="dark"]'];
1430
1495
  const darkSelector = darkSelectors.join(",\n");
@@ -1437,6 +1502,10 @@ function generateDarkCss(tokens, options) {
1437
1502
  lines.push(block(darkSelector, borderDecls));
1438
1503
  lines.push(sectionComment("Adaptive: Interactive (dark) \u2014 manual toggle"));
1439
1504
  lines.push(block(darkSelector, interactiveDecls));
1505
+ lines.push(sectionComment("Adaptive: Intent aliases (dark) \u2014 manual toggle"));
1506
+ lines.push(block(darkSelector, intentDecls));
1507
+ lines.push(sectionComment("Adaptive: Hairline aliases (dark) \u2014 manual toggle"));
1508
+ lines.push(block(darkSelector, hairlineDecls));
1440
1509
  lines.push(
1441
1510
  sectionComment("Adaptive: Text (dark) \u2014 prefers-color-scheme")
1442
1511
  );
@@ -1467,6 +1536,22 @@ ${block(prefersSelector, borderDecls)}}`
1467
1536
  lines.push(
1468
1537
  `@media (prefers-color-scheme: dark) {
1469
1538
  ${block(prefersSelector, interactiveDecls)}}`
1539
+ );
1540
+ lines.push("");
1541
+ lines.push(
1542
+ sectionComment("Adaptive: Intent aliases (dark) \u2014 prefers-color-scheme")
1543
+ );
1544
+ lines.push(
1545
+ `@media (prefers-color-scheme: dark) {
1546
+ ${block(prefersSelector, intentDecls)}}`
1547
+ );
1548
+ lines.push("");
1549
+ lines.push(
1550
+ sectionComment("Adaptive: Hairline aliases (dark) \u2014 prefers-color-scheme")
1551
+ );
1552
+ lines.push(
1553
+ `@media (prefers-color-scheme: dark) {
1554
+ ${block(prefersSelector, hairlineDecls)}}`
1470
1555
  );
1471
1556
  lines.push("");
1472
1557
  return header("Visor Theme \u2014 Dark") + lines.join("\n");
@@ -1561,6 +1646,10 @@ export {
1561
1646
  sectionComment,
1562
1647
  generatePrimitivesCss,
1563
1648
  generateSemanticCss,
1649
+ generateTextScaleAliasDecls,
1650
+ generateSpaceAliasDecls,
1651
+ generateIntentDecls,
1652
+ generateHairlineDecls,
1564
1653
  generateLightCss,
1565
1654
  generateDarkCss,
1566
1655
  generateFullBundleCss
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, R as ResolvedThemeConfig, c as GeneratedPrimitives, d as ThemeOutput, e as ThemeData, f as VisorThemeConfig, g as FullShadeScale, C as ColorRole, S as SelectiveShadeScale, h as RGB, P as ParsedColor, O as OKLCH, i as SemanticTokens, j as ShadeStep } from './types-0hotGz1u.js';
2
- export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-0hotGz1u.js';
1
+ import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, R as ResolvedThemeConfig, c as GeneratedPrimitives, d as ThemeOutput, e as ThemeData, f as VisorThemeConfig, g as FullShadeScale, C as ColorRole, S as SelectiveShadeScale, h as RGB, P as ParsedColor, O as OKLCH, i as SemanticTokens, j as ShadeStep } from './types-Dwc1V0Nc.js';
2
+ export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-Dwc1V0Nc.js';
3
3
 
4
4
  /**
5
5
  * Font resolver — maps font family names to loadable font resources.
@@ -898,6 +898,8 @@ declare const SEMANTIC_MAP: {
898
898
  surface: Record<string, SemanticMapping>;
899
899
  border: Record<string, SemanticMapping>;
900
900
  interactive: Record<string, SemanticMapping>;
901
+ intent: Record<string, SemanticMapping>;
902
+ hairline: Record<string, SemanticMapping>;
901
903
  };
902
904
 
903
905
  /**
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  rgbToHex,
35
35
  rgbToOklch,
36
36
  serializeColor
37
- } from "./chunk-BEFH5BTX.js";
37
+ } from "./chunk-43TVIXUS.js";
38
38
 
39
39
  // src/fonts/validate-coverage.ts
40
40
  var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
@@ -605,7 +605,7 @@ var KNOWN_SPACING_KEYS = /* @__PURE__ */ new Set(["base"]);
605
605
  var KNOWN_RADIUS_KEYS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "pill"]);
606
606
  var KNOWN_SHADOW_KEYS = /* @__PURE__ */ new Set(["xs", "sm", "md", "lg", "xl"]);
607
607
  var KNOWN_STROKE_WIDTH_KEYS = /* @__PURE__ */ new Set(["thin", "regular", "medium", "thick"]);
608
- var KNOWN_MOTION_KEYS = /* @__PURE__ */ new Set(["duration-fast", "duration-normal", "duration-slow", "easing"]);
608
+ var KNOWN_MOTION_KEYS = /* @__PURE__ */ new Set(["duration-fast", "duration-normal", "duration-slow", "easing", "easing-overshoot"]);
609
609
  var KNOWN_OVERRIDES_KEYS = /* @__PURE__ */ new Set(["light", "dark"]);
610
610
  function checkUnknownKeys(obj, errors) {
611
611
  for (const key of Object.keys(obj)) {
@@ -1024,7 +1024,10 @@ function resolveConfig(config) {
1024
1024
  "duration-fast": config.motion?.["duration-fast"] ?? DEFAULTS.motion["duration-fast"],
1025
1025
  "duration-normal": config.motion?.["duration-normal"] ?? DEFAULTS.motion["duration-normal"],
1026
1026
  "duration-slow": config.motion?.["duration-slow"] ?? DEFAULTS.motion["duration-slow"],
1027
- easing: config.motion?.easing ?? DEFAULTS.motion.easing
1027
+ easing: config.motion?.easing ?? DEFAULTS.motion.easing,
1028
+ // VI-451 (drive-by): pass through opt-in bouncy easing when set; absent
1029
+ // themes leave this undefined and fall through to --motion-easing-spring.
1030
+ ...config.motion?.["easing-overshoot"] !== void 0 ? { "easing-overshoot": config.motion["easing-overshoot"] } : {}
1028
1031
  },
1029
1032
  overrides: config.overrides,
1030
1033
  originalColors,
@@ -1057,6 +1060,11 @@ var SEMANTIC_TEXT_MAP = {
1057
1060
  light: { role: "neutral", shade: 600 },
1058
1061
  dark: { role: "neutral", shade: 400 }
1059
1062
  },
1063
+ // VI-451: deemphasized but readable — slots between tertiary and disabled.
1064
+ muted: {
1065
+ light: { role: "neutral", shade: 500 },
1066
+ dark: { role: "neutral", shade: 500 }
1067
+ },
1060
1068
  disabled: {
1061
1069
  light: { role: "neutral", shade: 300 },
1062
1070
  dark: { role: "neutral", shade: 600 }
@@ -1117,6 +1125,16 @@ var SEMANTIC_SURFACE_MAP = {
1117
1125
  light: { role: "neutral", shade: 100 },
1118
1126
  dark: { role: "neutral", shade: 700 }
1119
1127
  },
1128
+ // VI-451: deeper than page — used by admin-ui chrome (deepest backdrop).
1129
+ screen: {
1130
+ light: { role: "neutral", shade: 50 },
1131
+ dark: { role: "neutral", shade: 950 }
1132
+ },
1133
+ // VI-451: singular elevation alias, distinct from elev-0..4. Defaults to mid (elev-2).
1134
+ elev: {
1135
+ light: { role: "neutral", shade: 100 },
1136
+ dark: { role: "neutral", shade: 800 }
1137
+ },
1120
1138
  overlay: {
1121
1139
  light: { role: "neutral", shade: 900 },
1122
1140
  dark: { role: "neutral", shade: 950 }
@@ -1187,6 +1205,23 @@ var SEMANTIC_SURFACE_MAP = {
1187
1205
  light: { role: "info", shade: 500 },
1188
1206
  dark: { role: "info", shade: 500 }
1189
1207
  },
1208
+ // VI-478: status soft tints (BL-193) — alpha overlays, semantically distinct
1209
+ // from the OPAQUE `surface-{status}-subtle` above (do NOT alias them together).
1210
+ // Default to a color-mix of the status color so they track the theme; themes
1211
+ // pin exact values via overrides (blacklight-underground: success @10%,
1212
+ // warning/error @12%).
1213
+ "success-soft": {
1214
+ light: { constant: "color-mix(in srgb, var(--color-success-500) 10%, transparent)" },
1215
+ dark: { constant: "color-mix(in srgb, var(--color-success-500) 10%, transparent)" }
1216
+ },
1217
+ "warning-soft": {
1218
+ light: { constant: "color-mix(in srgb, var(--color-warning-500) 12%, transparent)" },
1219
+ dark: { constant: "color-mix(in srgb, var(--color-warning-500) 12%, transparent)" }
1220
+ },
1221
+ "error-soft": {
1222
+ light: { constant: "color-mix(in srgb, var(--color-error-500) 12%, transparent)" },
1223
+ dark: { constant: "color-mix(in srgb, var(--color-error-500) 12%, transparent)" }
1224
+ },
1190
1225
  // 5-tier ordinal elevation scale — deepest (0) to highest (4)
1191
1226
  // Light mode: BO-10 near-white ramp (white → neutral-300).
1192
1227
  // Dark mode: deep neutral ramp (neutral-950 → neutral-600).
@@ -1267,6 +1302,23 @@ var SEMANTIC_INTERACTIVE_MAP = {
1267
1302
  light: { constant: "#ffffff" },
1268
1303
  dark: { constant: "#ffffff" }
1269
1304
  },
1305
+ // VI-478: brand-derived alpha-overlay helpers (BL-193). `soft`/`glow` are
1306
+ // alpha overlays that track the theme's primary via color-mix (distinct from
1307
+ // any opaque surface); `strong` is a solid lightened-brand emphasis color.
1308
+ // Themes pin exact values via overrides — e.g. blacklight-underground sets
1309
+ // soft @12% / glow @32% / strong #FFD050.
1310
+ "primary-soft": {
1311
+ light: { constant: "color-mix(in srgb, var(--color-primary-500) 12%, transparent)" },
1312
+ dark: { constant: "color-mix(in srgb, var(--color-primary-500) 12%, transparent)" }
1313
+ },
1314
+ "primary-glow": {
1315
+ light: { constant: "color-mix(in srgb, var(--color-primary-500) 32%, transparent)" },
1316
+ dark: { constant: "color-mix(in srgb, var(--color-primary-500) 32%, transparent)" }
1317
+ },
1318
+ "primary-strong": {
1319
+ light: { role: "primary", shade: 600 },
1320
+ dark: { role: "primary", shade: 400 }
1321
+ },
1270
1322
  // Secondary action
1271
1323
  "secondary-bg": {
1272
1324
  light: { constant: "#ffffff" },
@@ -1311,11 +1363,56 @@ var SEMANTIC_INTERACTIVE_MAP = {
1311
1363
  dark: { role: "neutral", shade: 700 }
1312
1364
  }
1313
1365
  };
1366
+ var SEMANTIC_INTENT_MAP = {
1367
+ primary: {
1368
+ light: { role: "primary", shade: 500 },
1369
+ dark: { role: "primary", shade: 500 }
1370
+ },
1371
+ // Text color paired with --primary backgrounds. Default white; themes whose
1372
+ // primary fails AA on white pin to a graphite via overrides (entr does this).
1373
+ "primary-text": {
1374
+ light: { constant: "#ffffff" },
1375
+ dark: { constant: "#ffffff" }
1376
+ },
1377
+ accent: {
1378
+ light: { role: "accent", shade: 500 },
1379
+ dark: { role: "accent", shade: 500 }
1380
+ },
1381
+ success: {
1382
+ light: { role: "success", shade: 500 },
1383
+ dark: { role: "success", shade: 500 }
1384
+ },
1385
+ warning: {
1386
+ light: { role: "warning", shade: 500 },
1387
+ dark: { role: "warning", shade: 500 }
1388
+ },
1389
+ // shadcn naming — aliases the `error` role.
1390
+ destructive: {
1391
+ light: { role: "error", shade: 500 },
1392
+ dark: { role: "error", shade: 500 }
1393
+ },
1394
+ info: {
1395
+ light: { role: "info", shade: 500 },
1396
+ dark: { role: "info", shade: 500 }
1397
+ }
1398
+ };
1399
+ var SEMANTIC_HAIRLINE_MAP = {
1400
+ default: {
1401
+ light: { constant: "rgba(0, 0, 0, 0.06)" },
1402
+ dark: { constant: "rgba(255, 255, 255, 0.06)" }
1403
+ },
1404
+ strong: {
1405
+ light: { constant: "rgba(0, 0, 0, 0.10)" },
1406
+ dark: { constant: "rgba(255, 255, 255, 0.10)" }
1407
+ }
1408
+ };
1314
1409
  var SEMANTIC_MAP = {
1315
1410
  text: SEMANTIC_TEXT_MAP,
1316
1411
  surface: SEMANTIC_SURFACE_MAP,
1317
1412
  border: SEMANTIC_BORDER_MAP,
1318
- interactive: SEMANTIC_INTERACTIVE_MAP
1413
+ interactive: SEMANTIC_INTERACTIVE_MAP,
1414
+ intent: SEMANTIC_INTENT_MAP,
1415
+ hairline: SEMANTIC_HAIRLINE_MAP
1319
1416
  };
1320
1417
 
1321
1418
  // src/assign.ts
@@ -1357,6 +1454,8 @@ function assignSemanticTokens(lightPrimitives, darkPrimitives, config) {
1357
1454
  const surface = {};
1358
1455
  const border = {};
1359
1456
  const interactive = {};
1457
+ const intent = {};
1458
+ const hairline = {};
1360
1459
  for (const [name, mapping] of Object.entries(SEMANTIC_MAP.text)) {
1361
1460
  text[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1362
1461
  }
@@ -1369,7 +1468,13 @@ function assignSemanticTokens(lightPrimitives, darkPrimitives, config) {
1369
1468
  for (const [name, mapping] of Object.entries(SEMANTIC_MAP.interactive)) {
1370
1469
  interactive[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1371
1470
  }
1372
- return { text, surface, border, interactive };
1471
+ for (const [name, mapping] of Object.entries(SEMANTIC_MAP.intent)) {
1472
+ intent[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1473
+ }
1474
+ for (const [name, mapping] of Object.entries(SEMANTIC_MAP.hairline)) {
1475
+ hairline[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1476
+ }
1477
+ return { text, surface, border, interactive, intent, hairline };
1373
1478
  }
1374
1479
 
1375
1480
  // src/overrides.ts
@@ -1377,9 +1482,13 @@ var TOKEN_CATEGORIES = [
1377
1482
  { prefix: "text-", key: "text" },
1378
1483
  { prefix: "surface-", key: "surface" },
1379
1484
  { prefix: "border-", key: "border" },
1380
- { prefix: "interactive-", key: "interactive" }
1485
+ { prefix: "interactive-", key: "interactive" },
1486
+ { prefix: "hairline-", key: "hairline" }
1381
1487
  ];
1382
1488
  function findToken(key, tokens) {
1489
+ if (key === "hairline" && "default" in tokens.hairline) {
1490
+ return { group: tokens.hairline, name: "default" };
1491
+ }
1383
1492
  for (const { prefix, key: groupKey } of TOKEN_CATEGORIES) {
1384
1493
  if (key.startsWith(prefix)) {
1385
1494
  const name = key.slice(prefix.length);
@@ -1388,6 +1497,9 @@ function findToken(key, tokens) {
1388
1497
  }
1389
1498
  }
1390
1499
  }
1500
+ if (key in tokens.intent) {
1501
+ return { group: tokens.intent, name: key };
1502
+ }
1391
1503
  return null;
1392
1504
  }
1393
1505
  function applyOverrides(tokens, overrides) {
@@ -1396,9 +1508,11 @@ function applyOverrides(tokens, overrides) {
1396
1508
  text: { ...tokens.text },
1397
1509
  surface: { ...tokens.surface },
1398
1510
  border: { ...tokens.border },
1399
- interactive: { ...tokens.interactive }
1511
+ interactive: { ...tokens.interactive },
1512
+ intent: { ...tokens.intent },
1513
+ hairline: { ...tokens.hairline }
1400
1514
  };
1401
- for (const group of ["text", "surface", "border", "interactive"]) {
1515
+ for (const group of ["text", "surface", "border", "interactive", "intent", "hairline"]) {
1402
1516
  for (const [name, value] of Object.entries(result[group])) {
1403
1517
  result[group][name] = { ...value };
1404
1518
  }
@@ -1615,10 +1729,13 @@ function exportTheme(primitives, config) {
1615
1729
  const motion = {};
1616
1730
  for (const [key, defaultVal] of Object.entries(DEFAULT_MOTION)) {
1617
1731
  const val = config.motion[key];
1618
- if (val !== defaultVal) {
1732
+ if (val !== void 0 && val !== defaultVal) {
1619
1733
  motion[key] = val;
1620
1734
  }
1621
1735
  }
1736
+ if (config.motion["easing-overshoot"] !== void 0) {
1737
+ motion["easing-overshoot"] = config.motion["easing-overshoot"];
1738
+ }
1622
1739
  if (Object.keys(motion).length > 0) {
1623
1740
  output.motion = motion;
1624
1741
  }
@@ -268,6 +268,12 @@ interface VisorThemeConfig {
268
268
  "duration-normal"?: string;
269
269
  "duration-slow"?: string;
270
270
  easing?: string;
271
+ /**
272
+ * VI-451 (drive-by): opt-in tier-2 easing for bouncy entrances
273
+ * (marker pops, scale-in entrances). Emitted as `--motion-easing-overshoot`
274
+ * when set; absent themes fall through to the default `--motion-easing-spring`.
275
+ */
276
+ "easing-overshoot"?: string;
271
277
  };
272
278
  overrides?: {
273
279
  light?: Record<string, string>;
@@ -380,6 +386,8 @@ interface ResolvedThemeConfig {
380
386
  "duration-normal": string;
381
387
  "duration-slow": string;
382
388
  easing: string;
389
+ /** VI-451 (drive-by): opt-in bouncy easing for marker pops / scale-in entrances. */
390
+ "easing-overshoot"?: string;
383
391
  };
384
392
  overrides?: {
385
393
  light?: Record<string, string>;
@@ -408,6 +416,8 @@ interface SemanticTokens {
408
416
  surface: Record<string, SemanticTokenValue>;
409
417
  border: Record<string, SemanticTokenValue>;
410
418
  interactive: Record<string, SemanticTokenValue>;
419
+ intent: Record<string, SemanticTokenValue>;
420
+ hairline: Record<string, SemanticTokenValue>;
411
421
  }
412
422
  interface ThemeOutput {
413
423
  primitivesCss: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor-theme-engine",
3
- "version": "0.9.0",
3
+ "version": "0.11.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",