@loworbitstudio/visor-theme-engine 0.9.0 → 0.10.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 }
@@ -1311,11 +1329,56 @@ var SEMANTIC_INTERACTIVE_MAP = {
1311
1329
  dark: { role: "neutral", shade: 700 }
1312
1330
  }
1313
1331
  };
1332
+ var SEMANTIC_INTENT_MAP = {
1333
+ primary: {
1334
+ light: { role: "primary", shade: 500 },
1335
+ dark: { role: "primary", shade: 500 }
1336
+ },
1337
+ // Text color paired with --primary backgrounds. Default white; themes whose
1338
+ // primary fails AA on white pin to a graphite via overrides (entr does this).
1339
+ "primary-text": {
1340
+ light: { constant: "#ffffff" },
1341
+ dark: { constant: "#ffffff" }
1342
+ },
1343
+ accent: {
1344
+ light: { role: "accent", shade: 500 },
1345
+ dark: { role: "accent", shade: 500 }
1346
+ },
1347
+ success: {
1348
+ light: { role: "success", shade: 500 },
1349
+ dark: { role: "success", shade: 500 }
1350
+ },
1351
+ warning: {
1352
+ light: { role: "warning", shade: 500 },
1353
+ dark: { role: "warning", shade: 500 }
1354
+ },
1355
+ // shadcn naming — aliases the `error` role.
1356
+ destructive: {
1357
+ light: { role: "error", shade: 500 },
1358
+ dark: { role: "error", shade: 500 }
1359
+ },
1360
+ info: {
1361
+ light: { role: "info", shade: 500 },
1362
+ dark: { role: "info", shade: 500 }
1363
+ }
1364
+ };
1365
+ var SEMANTIC_HAIRLINE_MAP = {
1366
+ default: {
1367
+ light: { constant: "rgba(0, 0, 0, 0.06)" },
1368
+ dark: { constant: "rgba(255, 255, 255, 0.06)" }
1369
+ },
1370
+ strong: {
1371
+ light: { constant: "rgba(0, 0, 0, 0.10)" },
1372
+ dark: { constant: "rgba(255, 255, 255, 0.10)" }
1373
+ }
1374
+ };
1314
1375
  var SEMANTIC_MAP = {
1315
1376
  text: SEMANTIC_TEXT_MAP,
1316
1377
  surface: SEMANTIC_SURFACE_MAP,
1317
1378
  border: SEMANTIC_BORDER_MAP,
1318
- interactive: SEMANTIC_INTERACTIVE_MAP
1379
+ interactive: SEMANTIC_INTERACTIVE_MAP,
1380
+ intent: SEMANTIC_INTENT_MAP,
1381
+ hairline: SEMANTIC_HAIRLINE_MAP
1319
1382
  };
1320
1383
 
1321
1384
  // src/assign.ts
@@ -1357,6 +1420,8 @@ function assignSemanticTokens(lightPrimitives, darkPrimitives, config) {
1357
1420
  const surface = {};
1358
1421
  const border = {};
1359
1422
  const interactive = {};
1423
+ const intent = {};
1424
+ const hairline = {};
1360
1425
  for (const [name, mapping] of Object.entries(SEMANTIC_MAP.text)) {
1361
1426
  text[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1362
1427
  }
@@ -1369,7 +1434,13 @@ function assignSemanticTokens(lightPrimitives, darkPrimitives, config) {
1369
1434
  for (const [name, mapping] of Object.entries(SEMANTIC_MAP.interactive)) {
1370
1435
  interactive[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1371
1436
  }
1372
- return { text, surface, border, interactive };
1437
+ for (const [name, mapping] of Object.entries(SEMANTIC_MAP.intent)) {
1438
+ intent[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1439
+ }
1440
+ for (const [name, mapping] of Object.entries(SEMANTIC_MAP.hairline)) {
1441
+ hairline[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
1442
+ }
1443
+ return { text, surface, border, interactive, intent, hairline };
1373
1444
  }
1374
1445
 
1375
1446
  // src/overrides.ts
@@ -1377,9 +1448,13 @@ var TOKEN_CATEGORIES = [
1377
1448
  { prefix: "text-", key: "text" },
1378
1449
  { prefix: "surface-", key: "surface" },
1379
1450
  { prefix: "border-", key: "border" },
1380
- { prefix: "interactive-", key: "interactive" }
1451
+ { prefix: "interactive-", key: "interactive" },
1452
+ { prefix: "hairline-", key: "hairline" }
1381
1453
  ];
1382
1454
  function findToken(key, tokens) {
1455
+ if (key === "hairline" && "default" in tokens.hairline) {
1456
+ return { group: tokens.hairline, name: "default" };
1457
+ }
1383
1458
  for (const { prefix, key: groupKey } of TOKEN_CATEGORIES) {
1384
1459
  if (key.startsWith(prefix)) {
1385
1460
  const name = key.slice(prefix.length);
@@ -1388,6 +1463,9 @@ function findToken(key, tokens) {
1388
1463
  }
1389
1464
  }
1390
1465
  }
1466
+ if (key in tokens.intent) {
1467
+ return { group: tokens.intent, name: key };
1468
+ }
1391
1469
  return null;
1392
1470
  }
1393
1471
  function applyOverrides(tokens, overrides) {
@@ -1396,9 +1474,11 @@ function applyOverrides(tokens, overrides) {
1396
1474
  text: { ...tokens.text },
1397
1475
  surface: { ...tokens.surface },
1398
1476
  border: { ...tokens.border },
1399
- interactive: { ...tokens.interactive }
1477
+ interactive: { ...tokens.interactive },
1478
+ intent: { ...tokens.intent },
1479
+ hairline: { ...tokens.hairline }
1400
1480
  };
1401
- for (const group of ["text", "surface", "border", "interactive"]) {
1481
+ for (const group of ["text", "surface", "border", "interactive", "intent", "hairline"]) {
1402
1482
  for (const [name, value] of Object.entries(result[group])) {
1403
1483
  result[group][name] = { ...value };
1404
1484
  }
@@ -1615,10 +1695,13 @@ function exportTheme(primitives, config) {
1615
1695
  const motion = {};
1616
1696
  for (const [key, defaultVal] of Object.entries(DEFAULT_MOTION)) {
1617
1697
  const val = config.motion[key];
1618
- if (val !== defaultVal) {
1698
+ if (val !== void 0 && val !== defaultVal) {
1619
1699
  motion[key] = val;
1620
1700
  }
1621
1701
  }
1702
+ if (config.motion["easing-overshoot"] !== void 0) {
1703
+ motion["easing-overshoot"] = config.motion["easing-overshoot"];
1704
+ }
1622
1705
  if (Object.keys(motion).length > 0) {
1623
1706
  output.motion = motion;
1624
1707
  }
@@ -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.10.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",