rafters 0.0.78 → 0.0.79

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.
Files changed (2) hide show
  1. package/dist/index.js +915 -356
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -311,6 +311,32 @@ function toDTCG(tokens, options = {}) {
311
311
 
312
312
  // ../design-tokens/src/exporters/tailwind.ts
313
313
  var SHADOW_PART_SUFFIX = /-(offset-x|offset-y|blur|spread|color)$/;
314
+ var MOTION_NAMESPACE_PROPERTY = {
315
+ duration: "transition-duration",
316
+ ease: "transition-timing-function",
317
+ delay: "transition-delay",
318
+ // Extents are consumed inside transforms, so the utility publishes the chosen
319
+ // extent under a fixed name the consuming rule reads. The name comes from toy
320
+ // 9 (worktree-toy-motion-registry).
321
+ //
322
+ // THIS IS THE UTILITY-SIDE CONTRACT, and it is one of TWO (#2017). A CLASS
323
+ // picks an extent by naming a member (`extent-pop`) and the rule downstream
324
+ // reads `--rafters-consumed-extent` without knowing which member won.
325
+ // KEYFRAME BODIES DO NOT USE THIS ALIAS: they are generator-owned emission and
326
+ // reference the LEAF directly (`var(--rafters-extent-pop)`, see
327
+ // DEFAULT_KEYFRAME_DEFINITIONS). A shape is not a function of whatever extent
328
+ // the consuming class last selected. Do not merge the two contracts.
329
+ extent: "--rafters-consumed-extent",
330
+ period: "animation-duration"
331
+ };
332
+ var MOTION_NAMESPACE_NAMES = Object.keys(MOTION_NAMESPACE_PROPERTY);
333
+ var REDUCED_MOTION_ZEROED = /* @__PURE__ */ new Set(["duration", "delay"]);
334
+ var MOTION_NAMESPACE_TOKEN = new RegExp(`^rafters-(${MOTION_NAMESPACE_NAMES.join("|")})-(.+)$`);
335
+ function motionNamespaceParts(name) {
336
+ const match = MOTION_NAMESPACE_TOKEN.exec(name);
337
+ if (!match?.[1] || !match[2]) return null;
338
+ return { namespace: match[1], member: match[2] };
339
+ }
314
340
  function isShadowDecomposedPart(name) {
315
341
  return SHADOW_PART_SUFFIX.test(name);
316
342
  }
@@ -507,7 +533,7 @@ function generateThemeBlock(groups) {
507
533
  const value = tokenValueToCSS(token);
508
534
  if (value === null) continue;
509
535
  const key = token.name.replace(/^radius-/, "");
510
- const themeValue = value.replaceAll("var(--rafters-radius-base)", "var(--radius-base)").replaceAll("var(--rafters-radius-tl)", "var(--radius-tl)").replaceAll("var(--rafters-radius-tr)", "var(--radius-tr)").replaceAll("var(--rafters-radius-bl)", "var(--radius-bl)").replaceAll("var(--rafters-radius-br)", "var(--radius-br)");
536
+ const themeValue = value.replaceAll("var(--rafters-spacing-base)", "var(--spacing-base)").replaceAll("var(--rafters-radius-base)", "var(--radius-base)").replaceAll("var(--rafters-radius-tl)", "var(--radius-tl)").replaceAll("var(--rafters-radius-tr)", "var(--radius-tr)").replaceAll("var(--rafters-radius-bl)", "var(--radius-bl)").replaceAll("var(--rafters-radius-br)", "var(--radius-br)");
511
537
  lines.push(` --radius-${key}: ${themeValue};`);
512
538
  }
513
539
  lines.push("");
@@ -533,32 +559,26 @@ function generateThemeBlock(groups) {
533
559
  }
534
560
  lines.push("");
535
561
  }
562
+ const motionNamespaceLines = generateMotionNamespaceVars(groups.motion);
563
+ if (motionNamespaceLines) {
564
+ lines.push(motionNamespaceLines);
565
+ lines.push("");
566
+ }
536
567
  if (groups.motion.length > 0) {
537
568
  for (const token of groups.motion) {
538
569
  if (token.name.startsWith("motion-duration-") && token.name !== "motion-duration-base")
539
570
  continue;
540
571
  if (token.name.startsWith("motion-easing-")) continue;
572
+ if (motionNamespaceParts(token.name)) continue;
541
573
  const value = tokenValueToCSS(token);
542
574
  if (value === null) continue;
543
575
  lines.push(` --${token.name}: ${value};`);
544
576
  }
545
577
  lines.push("");
546
578
  }
547
- if (groups.motion.length > 0) {
548
- for (const token of groups.motion) {
549
- if (token.name.startsWith("motion-duration-") && token.name !== "motion-duration-base") {
550
- const key = token.name.replace("motion-duration-", "");
551
- const value = tokenValueToCSS(token);
552
- if (value === null) continue;
553
- lines.push(` --duration-${key}: ${value};`);
554
- }
555
- if (token.name.startsWith("motion-easing-")) {
556
- const key = token.name.replace("motion-easing-", "");
557
- const value = tokenValueToCSS(token);
558
- if (value === null) continue;
559
- lines.push(` --ease-${key}: ${value};`);
560
- }
561
- }
579
+ const bridgeLines = generateMotionBridgeVars(groups.motion);
580
+ if (bridgeLines) {
581
+ lines.push(bridgeLines);
562
582
  lines.push("");
563
583
  }
564
584
  if (groups.breakpoint.length > 0) {
@@ -574,7 +594,8 @@ function generateThemeBlock(groups) {
574
594
  for (const token of groups.focus) {
575
595
  const value = tokenValueToCSS(token);
576
596
  if (value === null) continue;
577
- lines.push(` --${token.name}: ${value};`);
597
+ const themeValue = value.replaceAll("var(--rafters-spacing-base)", "var(--spacing-base)").replaceAll("var(--rafters-focus-ring-width)", "var(--focus-ring-width)");
598
+ lines.push(` --${token.name}: ${themeValue};`);
578
599
  }
579
600
  lines.push("");
580
601
  }
@@ -594,18 +615,18 @@ function generateThemeBlock(groups) {
594
615
  return lines.join("\n");
595
616
  }
596
617
  var ARTICLE_ELEMENT_STYLES = [
597
- // Paragraphs
598
- ["p", "leading-relaxed mb-4"],
618
+ // Paragraphs -- composition for metrics, structural for spacing
619
+ ["p", "text-body-medium ts-body-medium leading-relaxed mb-4"],
599
620
  ["p:last-child", "mb-0"],
600
- // Headings
601
- ["h1", "text-4xl font-bold tracking-tight mb-4 mt-0 text-accent-foreground"],
602
- ["h2", "text-3xl font-semibold tracking-tight mb-3 mt-8 text-accent-foreground"],
621
+ // Headings -- compositions carry size/weight/tracking/leading/family
622
+ ["h1", "text-display-medium ts-display-medium mb-4 mt-0 text-accent-foreground"],
623
+ ["h2", "text-title-large ts-title-large mb-3 mt-8 text-accent-foreground"],
603
624
  ["h2:first-child", "mt-0"],
604
- ["h3", "text-2xl font-semibold mb-2 mt-6 text-accent-foreground"],
605
- ["h4", "text-xl font-semibold mb-2 mt-4 text-accent-foreground"],
606
- ["h5", "text-lg font-semibold mb-2 mt-4 text-accent-foreground"],
607
- ["h6", "text-base font-semibold mb-2 mt-4 text-accent-foreground"],
608
- // Lists
625
+ ["h3", "text-title-medium ts-title-medium mb-2 mt-6 text-accent-foreground"],
626
+ ["h4", "text-title-small ts-title-small mb-2 mt-4 text-accent-foreground"],
627
+ ["h5", "text-title-small ts-title-small mb-2 mt-4 text-accent-foreground"],
628
+ ["h6", "text-title-small ts-title-small mb-2 mt-4 text-accent-foreground"],
629
+ // Lists -- structural only (type metrics cascade from parent)
609
630
  ["ul", "list-disc pl-6 mb-4"],
610
631
  ["ol", "list-decimal pl-6 mb-4"],
611
632
  ["li", "mb-1"],
@@ -615,11 +636,11 @@ var ARTICLE_ELEMENT_STYLES = [
615
636
  ["a:hover", "text-primary/80"],
616
637
  // Blockquotes
617
638
  ["blockquote", "border-l-4 border-muted pl-4 italic my-4"],
618
- // Code
619
- ["code", "bg-muted px-1.5 py-0.5 rounded text-sm font-mono"],
620
- ["pre", "bg-muted p-4 rounded-lg overflow-x-auto my-4 text-sm font-mono"],
639
+ // Code -- composition for metrics + family, structural for bg/padding/radius
640
+ ["code", "text-code-small ts-code-small bg-muted px-1.5 py-0.5 rounded"],
641
+ ["pre", "text-code-large ts-code-large bg-muted p-4 rounded-lg overflow-x-auto my-4"],
621
642
  ["pre code", "bg-transparent p-0 rounded-none text-[inherit]"],
622
- ["kbd", "bg-muted border border-border rounded px-1.5 py-0.5 text-sm font-mono"],
643
+ ["kbd", "text-code-small ts-code-small bg-muted border border-border rounded px-1.5 py-0.5"],
623
644
  // Horizontal rules
624
645
  ["hr", "border-border my-8"],
625
646
  // Media
@@ -627,12 +648,12 @@ var ARTICLE_ELEMENT_STYLES = [
627
648
  ["video", "rounded-lg my-4 max-w-full h-auto"],
628
649
  // Tables
629
650
  ["table", "w-full my-4 border-collapse"],
630
- ["caption", "mt-2 text-sm text-muted-foreground text-left"],
651
+ ["caption", "text-label-small ts-label-small mt-2 text-muted-foreground text-left"],
631
652
  ["th", "border border-border px-3 py-2 text-left font-semibold"],
632
653
  ["td", "border border-border px-3 py-2"],
633
654
  // Figures
634
655
  ["figure", "my-4"],
635
- ["figcaption", "mt-2 text-sm text-muted-foreground"],
656
+ ["figcaption", "text-label-small ts-label-small mt-2 text-muted-foreground"],
636
657
  // Definition lists
637
658
  ["dl", "my-4"],
638
659
  ["dt", "font-semibold mt-2"],
@@ -643,7 +664,7 @@ var ARTICLE_ELEMENT_STYLES = [
643
664
  // Inline formatting
644
665
  ["strong,\n article b", "font-semibold"],
645
666
  ["mark", "bg-accent text-accent-foreground px-1 rounded"],
646
- ["small", "text-sm"],
667
+ ["small", "text-label-small ts-label-small"],
647
668
  ["sub", "text-xs align-sub"],
648
669
  ["sup", "text-xs align-super"],
649
670
  ["abbr[title]", "underline decoration-dotted underline-offset-4 cursor-help"],
@@ -703,12 +724,21 @@ function generateDepthUtilities(depthTokens) {
703
724
  }
704
725
  return lines.join("\n");
705
726
  }
706
- function generateTypographyCompositeUtilities(compositeTokens) {
707
- if (compositeTokens.length === 0) {
708
- return "";
709
- }
710
- const lines = [];
711
- const mappings = compositeTokens.map((t) => {
727
+ var NAMED_TRACKING = {
728
+ tighter: "-0.05em",
729
+ tight: "-0.025em",
730
+ normal: "0em",
731
+ wide: "0.025em",
732
+ wider: "0.05em",
733
+ widest: "0.1em"
734
+ };
735
+ function trackingRef(key) {
736
+ const named = NAMED_TRACKING[key];
737
+ if (named !== void 0) return named;
738
+ return `var(--letter-spacing-${key})`;
739
+ }
740
+ function parseComposites(compositeTokens) {
741
+ return compositeTokens.map((t) => {
712
742
  try {
713
743
  const parsed = JSON.parse(t.value);
714
744
  return { name: t.name, ...parsed };
@@ -716,40 +746,50 @@ function generateTypographyCompositeUtilities(compositeTokens) {
716
746
  return null;
717
747
  }
718
748
  }).filter((m3) => m3 !== null);
719
- for (const mapping of mappings) {
720
- lines.push(`@utility text-${mapping.name} {`);
721
- lines.push(` font-family: var(--font-${mapping.fontFamily});`);
722
- lines.push(` font-size: var(--font-size-${mapping.fontSize});`);
723
- lines.push(` font-weight: var(--font-weight-${mapping.fontWeight});`);
724
- lines.push(` line-height: var(--line-height-${mapping.lineHeight});`);
725
- const namedTrackingValues = {
726
- tighter: "-0.05em",
727
- tight: "-0.025em",
728
- normal: "0em",
729
- wide: "0.025em",
730
- wider: "0.05em",
731
- widest: "0.1em"
732
- };
733
- if (mapping.letterSpacing in namedTrackingValues) {
734
- lines.push(` letter-spacing: ${namedTrackingValues[mapping.letterSpacing]};`);
735
- } else {
736
- lines.push(` letter-spacing: var(--letter-spacing-${mapping.letterSpacing});`);
737
- }
738
- if (mapping.responsive) {
739
- for (const [breakpoint, overrides] of Object.entries(mapping.responsive)) {
749
+ }
750
+ function generateTypographyCompositeThemeInline(compositeTokens) {
751
+ const mappings = parseComposites(compositeTokens);
752
+ if (mappings.length === 0) return "";
753
+ const lines = [];
754
+ lines.push("@theme inline {");
755
+ for (const m3 of mappings) {
756
+ lines.push(` --text-${m3.name}: var(--font-size-${m3.fontSize});`);
757
+ lines.push(` --text-${m3.name}--line-height: var(--font-size-${m3.lineHeight}--line-height);`);
758
+ lines.push(` --text-${m3.name}--letter-spacing: ${trackingRef(m3.letterSpacing)};`);
759
+ lines.push(` --text-${m3.name}--font-weight: var(--font-weight-${m3.fontWeight});`);
760
+ lines.push(` --rafters-ts-${m3.name}: var(--font-${m3.fontFamily});`);
761
+ if (m3.responsive) {
762
+ for (const [bp, overrides] of Object.entries(m3.responsive)) {
740
763
  if (overrides.fontSize) {
741
- const bpWidth = breakpoint === "sm" ? "480px" : breakpoint === "md" ? "640px" : "1024px";
742
- lines.push(` @container (min-width: ${bpWidth}) {`);
743
- lines.push(` font-size: var(--font-size-${overrides.fontSize});`);
744
- lines.push(` line-height: var(--line-height-${overrides.fontSize});`);
745
- lines.push(" }");
764
+ lines.push(` --rafters-ts-${m3.name}-${bp}: var(--font-size-${overrides.fontSize});`);
765
+ lines.push(
766
+ ` --rafters-ts-${m3.name}-${bp}-leading: var(--font-size-${overrides.fontSize}--line-height);`
767
+ );
746
768
  }
747
769
  }
748
770
  }
749
- lines.push("}");
771
+ lines.push("");
750
772
  }
773
+ lines.push("}");
751
774
  return lines.join("\n");
752
775
  }
776
+ function generateTypographyCompositeUtility(compositeTokens) {
777
+ if (compositeTokens.length === 0) return "";
778
+ return [
779
+ "@utility ts-* {",
780
+ " font-family: --value(--rafters-ts-*);",
781
+ " text-transform: --value(--rafters-ts-*-transform);",
782
+ " @container (min-width: 640px) {",
783
+ " font-size: --value(--rafters-ts-*-md);",
784
+ " line-height: --value(--rafters-ts-*-md-leading);",
785
+ " }",
786
+ " @container (min-width: 1024px) {",
787
+ " font-size: --value(--rafters-ts-*-lg);",
788
+ " line-height: --value(--rafters-ts-*-lg-leading);",
789
+ " }",
790
+ "}"
791
+ ].join("\n");
792
+ }
753
793
  function generateMotionUtilities(motionTokens) {
754
794
  const semanticTokens = motionTokens.filter((t) => t.name.startsWith("motion-semantic-"));
755
795
  if (semanticTokens.length === 0) {
@@ -781,6 +821,82 @@ function generateMotionUtilities(motionTokens) {
781
821
  }
782
822
  return lines.join("\n");
783
823
  }
824
+ function generateMotionCellUtilities(motionTokens) {
825
+ const cellTokens = motionTokens.filter((t) => t.name.startsWith("motion-cell-"));
826
+ if (cellTokens.length === 0) return "";
827
+ const lines = [
828
+ "/* Motion cells -- one utility per animated (component, part, transition) */"
829
+ ];
830
+ for (const token of cellTokens) {
831
+ if (typeof token.value !== "string") continue;
832
+ let spec = null;
833
+ try {
834
+ spec = JSON.parse(token.value);
835
+ } catch {
836
+ spec = null;
837
+ }
838
+ lines.push(`@utility ${token.name.replace("motion-cell-", "animate-")} {`);
839
+ if (spec === null) {
840
+ lines.push(` animation: ${token.value};`);
841
+ } else {
842
+ lines.push(` animation-name: ${spec.keyframe};`);
843
+ lines.push(` animation-duration: var(--rafters-duration-${spec.durationTier});`);
844
+ lines.push(` animation-timing-function: var(--rafters-ease-${spec.curve});`);
845
+ }
846
+ lines.push(" @media (prefers-reduced-motion: reduce) {");
847
+ lines.push(" animation-duration: 0s;");
848
+ lines.push(" }");
849
+ lines.push("}");
850
+ }
851
+ return lines.join("\n");
852
+ }
853
+ function generateMotionNamespaceVars(motionTokens) {
854
+ const lines = [];
855
+ for (const token of motionTokens) {
856
+ if (!motionNamespaceParts(token.name)) continue;
857
+ const value = tokenValueToCSS(token);
858
+ if (value === null) continue;
859
+ lines.push(` --${token.name}: ${value};`);
860
+ }
861
+ if (lines.length === 0) return "";
862
+ return [
863
+ " /* The five motion namespaces -- system leaves, the values live here */",
864
+ ...lines
865
+ ].join("\n");
866
+ }
867
+ function generateMotionBridgeVars(motionTokens) {
868
+ const lines = [];
869
+ for (const token of motionTokens) {
870
+ if (token.name.startsWith("motion-duration-") && token.name !== "motion-duration-base") {
871
+ const key = token.name.replace("motion-duration-", "");
872
+ lines.push(` --duration-${key}: var(--rafters-duration-${key});`);
873
+ }
874
+ if (token.name.startsWith("motion-easing-")) {
875
+ const key = token.name.replace("motion-easing-", "");
876
+ lines.push(` --ease-${key}: var(--rafters-ease-${key});`);
877
+ }
878
+ }
879
+ return lines.join("\n");
880
+ }
881
+ function generateMotionNamespaceUtilities(motionTokens) {
882
+ const lines = ["/* The five motion namespaces -- one utility per member */"];
883
+ let emitted = 0;
884
+ for (const token of motionTokens) {
885
+ const parts = motionNamespaceParts(token.name);
886
+ if (!parts) continue;
887
+ const property = MOTION_NAMESPACE_PROPERTY[parts.namespace];
888
+ emitted++;
889
+ lines.push(`@utility ${parts.namespace}-${parts.member} {`);
890
+ lines.push(` ${property}: var(--${token.name});`);
891
+ if (REDUCED_MOTION_ZEROED.has(parts.namespace)) {
892
+ lines.push(" @media (prefers-reduced-motion: reduce) {");
893
+ lines.push(` ${property}: 0ms;`);
894
+ lines.push(" }");
895
+ }
896
+ lines.push("}");
897
+ }
898
+ return emitted === 0 ? "" : lines.join("\n");
899
+ }
784
900
  function overridePropertyToUtility(property, value) {
785
901
  switch (property) {
786
902
  case "fontFamily":
@@ -846,21 +962,38 @@ function tokensToTailwind(tokens, options = {}, typographyOverrides = []) {
846
962
  if (keyframes) {
847
963
  sections.push(keyframes);
848
964
  }
849
- const typographyUtilities = generateTypographyCompositeUtilities(groups["typography-composite"]);
850
- if (typographyUtilities) {
965
+ const typographyThemeInline = generateTypographyCompositeThemeInline(
966
+ groups["typography-composite"]
967
+ );
968
+ if (typographyThemeInline) {
851
969
  sections.push("");
852
- sections.push(typographyUtilities);
970
+ sections.push(typographyThemeInline);
971
+ }
972
+ const typographyTsUtility = generateTypographyCompositeUtility(groups["typography-composite"]);
973
+ if (typographyTsUtility) {
974
+ sections.push("");
975
+ sections.push(typographyTsUtility);
853
976
  }
854
977
  const depthUtilities = generateDepthUtilities(groups.depth);
855
978
  if (depthUtilities) {
856
979
  sections.push("");
857
980
  sections.push(depthUtilities);
858
981
  }
982
+ const namespaceUtilities = generateMotionNamespaceUtilities(groups.motion);
983
+ if (namespaceUtilities) {
984
+ sections.push("");
985
+ sections.push(namespaceUtilities);
986
+ }
859
987
  const motionUtilities = generateMotionUtilities(groups.motion);
860
988
  if (motionUtilities) {
861
989
  sections.push("");
862
990
  sections.push(motionUtilities);
863
991
  }
992
+ const cellUtilities = generateMotionCellUtilities(groups.motion);
993
+ if (cellUtilities) {
994
+ sections.push("");
995
+ sections.push(cellUtilities);
996
+ }
864
997
  const overrideCSS = generateTypographyOverrideCSS(typographyOverrides);
865
998
  if (overrideCSS) {
866
999
  sections.push("");
@@ -882,7 +1015,7 @@ async function registryToCompiled(registry2, options = {}) {
882
1015
  ${sourceDirectives}
883
1016
  ${themeBody}`;
884
1017
  const { execFileSync } = await import("child_process");
885
- const { mkdtempSync, writeFileSync: writeFileSync2, readFileSync: readFileSync3, rmSync } = await import("fs");
1018
+ const { mkdtempSync, writeFileSync: writeFileSync2, readFileSync: readFileSync4, rmSync } = await import("fs");
886
1019
  const { join: join15, dirname: dirname4 } = await import("path");
887
1020
  const { createRequire: createRequire2 } = await import("module");
888
1021
  const require2 = createRequire2(import.meta.url);
@@ -904,7 +1037,7 @@ ${themeBody}`;
904
1037
  args.push("--minify");
905
1038
  }
906
1039
  execFileSync("node", args, { stdio: "pipe", timeout: 3e4, cwd: pkgDir });
907
- return readFileSync3(tempOutput, "utf-8");
1040
+ return readFileSync4(tempOutput, "utf-8");
908
1041
  } catch (error47) {
909
1042
  const message = error47 instanceof Error ? error47.message : String(error47);
910
1043
  throw new Error(`Failed to compile CSS: ${message}`);
@@ -1050,6 +1183,10 @@ function deriveCandidates(themeCSS) {
1050
1183
  candidates.add(name);
1051
1184
  } else if (name.startsWith("animate-")) {
1052
1185
  candidates.add(name);
1186
+ } else if (name.startsWith("text-") && !name.includes("--")) {
1187
+ candidates.add(`text-${name.slice(5)}`);
1188
+ } else if (name.startsWith("rafters-ts-") && !name.endsWith("-transform")) {
1189
+ candidates.add(`ts-${name.slice(11)}`);
1053
1190
  }
1054
1191
  }
1055
1192
  const base = [...candidates];
@@ -1105,8 +1242,8 @@ ${themeBody}`;
1105
1242
  const args = [binPath, "-i", tempInput, "-o", tempOutput];
1106
1243
  if (minify) args.push("--minify");
1107
1244
  execFileSync("node", args, { stdio: "pipe", timeout: 6e4, cwd: pkgDir });
1108
- const { readFileSync: readFileSync3 } = await import("fs");
1109
- const raw = readFileSync3(tempOutput, "utf-8");
1245
+ const { readFileSync: readFileSync4 } = await import("fs");
1246
+ const raw = readFileSync4(tempOutput, "utf-8");
1110
1247
  return postProcessDocSheet(raw);
1111
1248
  } catch (error47) {
1112
1249
  const message = error47 instanceof Error ? error47.message : String(error47);
@@ -1306,10 +1443,12 @@ var DEFAULT_SYSTEM_CONFIG = {
1306
1443
  // 1.2 ratio
1307
1444
  fontFamily: "'Noto Sans Variable', sans-serif",
1308
1445
  monoFontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace",
1309
- // Rafters aesthetic overrides (happen to match pure math at baseSpacingUnit=4)
1446
+ // Rafters aesthetic overrides.
1447
+ // baseRadius and focusRingWidth have NO pin here -- they derive from
1448
+ // baseSpacingUnit (×1.5 and ÷2), and at base 4 the derivation already
1449
+ // produces 6 and 2. A pin would hide the derivation, which is the #2031
1450
+ // defect one layer down.
1310
1451
  baseFontSizeOverride: 16,
1311
- baseRadiusOverride: 6,
1312
- focusRingWidthOverride: 2,
1313
1452
  baseTransitionDurationOverride: 150
1314
1453
  };
1315
1454
  var COLOR_SCALE_POSITIONS = [
@@ -1325,42 +1464,6 @@ var COLOR_SCALE_POSITIONS = [
1325
1464
  "900",
1326
1465
  "950"
1327
1466
  ];
1328
- var SPACING_SCALE = [
1329
- "0",
1330
- "0.5",
1331
- "1",
1332
- "1.5",
1333
- "2",
1334
- "2.5",
1335
- "3",
1336
- "3.5",
1337
- "4",
1338
- "5",
1339
- "6",
1340
- "7",
1341
- "8",
1342
- "9",
1343
- "10",
1344
- "11",
1345
- "12",
1346
- "14",
1347
- "16",
1348
- "20",
1349
- "24",
1350
- "28",
1351
- "32",
1352
- "36",
1353
- "40",
1354
- "44",
1355
- "48",
1356
- "52",
1357
- "56",
1358
- "60",
1359
- "64",
1360
- "72",
1361
- "80",
1362
- "96"
1363
- ];
1364
1467
  var TYPOGRAPHY_SCALE = [
1365
1468
  "xs",
1366
1469
  "sm",
@@ -4468,7 +4571,7 @@ function range(color1, color2, options = {}) {
4468
4571
  let [r, options2] = [color1, color2];
4469
4572
  return range(...r.rangeArgs.colors, { ...r.rangeArgs.options, ...options2 });
4470
4573
  }
4471
- let { space, outputSpace, progression: progression2, premultiplied } = options;
4574
+ let { space, outputSpace, progression, premultiplied } = options;
4472
4575
  color1 = getColor(color1);
4473
4576
  color2 = getColor(color2);
4474
4577
  color1 = clone(color1);
@@ -4502,7 +4605,7 @@ function range(color1, color2, options = {}) {
4502
4605
  color2.coords = color2.coords.map((c4) => c4 * color2.alpha);
4503
4606
  }
4504
4607
  return Object.assign((p2) => {
4505
- p2 = progression2 ? progression2(p2) : p2;
4608
+ p2 = progression ? progression(p2) : p2;
4506
4609
  let coords = color1.coords.map((start, i) => {
4507
4610
  let end = color2.coords[i];
4508
4611
  return interpolate(start, end, p2);
@@ -21891,6 +21994,8 @@ var BindingSchema = external_exports.object({
21891
21994
  plugin: external_exports.string(),
21892
21995
  input: external_exports.unknown()
21893
21996
  });
21997
+ var OVERRIDE_KINDS = ["baseline", "preset", "designer"];
21998
+ var OverrideKindSchema = external_exports.enum(OVERRIDE_KINDS);
21894
21999
  var TokenSchema = external_exports.object({
21895
22000
  // Core token data
21896
22001
  name: external_exports.string(),
@@ -21929,7 +22034,13 @@ var TokenSchema = external_exports.object({
21929
22034
  // Why was this overridden
21930
22035
  reason: external_exports.string(),
21931
22036
  // Additional context (e.g. "Q1 marketing campaign", "accessibility audit")
21932
- context: external_exports.string().optional()
22037
+ context: external_exports.string().optional(),
22038
+ // Who attributed the value. The reason string is for humans; kind is what
22039
+ // machines branch on (preset application skips kind === 'designer').
22040
+ // Optional and never defaulted: absent means the provenance is unknown,
22041
+ // which is the honest state of every override written before this field
22042
+ // existed.
22043
+ kind: OverrideKindSchema.optional()
21933
22044
  }).nullable(),
21934
22045
  // Computed value from generation rule (before any override)
21935
22046
  // Stored so agents can see what the system WOULD produce vs what human chose
@@ -22614,7 +22725,7 @@ var DEFAULT_DURATION_DEFINITIONS = {
22614
22725
  },
22615
22726
  moderate: {
22616
22727
  range: [200, 300],
22617
- default: 200,
22728
+ default: 250,
22618
22729
  band: "communicative (~200-300ms)",
22619
22730
  meaning: "Dropdowns, tab switches, small reveals. The communicative window: fast enough to feel responsive, slow enough for the eye to track a trajectory and build a spatial model.",
22620
22731
  contexts: ["dropdowns", "tab-switches", "small-reveals"],
@@ -22622,7 +22733,7 @@ var DEFAULT_DURATION_DEFINITIONS = {
22622
22733
  },
22623
22734
  normal: {
22624
22735
  range: [300, 400],
22625
- default: 300,
22736
+ default: 350,
22626
22737
  band: "communicative, larger movement",
22627
22738
  meaning: "The workhorse -- modal entrances, toggles, standard state transitions. The communicative window for larger movement.",
22628
22739
  contexts: ["modals", "toggles", "state-changes"],
@@ -22630,7 +22741,7 @@ var DEFAULT_DURATION_DEFINITIONS = {
22630
22741
  },
22631
22742
  slow: {
22632
22743
  range: [400, 500],
22633
- default: 400,
22744
+ default: 500,
22634
22745
  band: "at the sluggish boundary",
22635
22746
  meaning: "Sheets, page transitions, large spatial movement where the user needs orientation. At the sluggish boundary -- the ceiling for anything but full-screen spatial transitions.",
22636
22747
  contexts: ["sheets", "page-transitions", "large-spatial-movement"],
@@ -22727,12 +22838,12 @@ var DEFAULT_KEYFRAME_DEFINITIONS = {
22727
22838
  contexts: ["sidebar-close", "panel-exit"]
22728
22839
  },
22729
22840
  "scale-in": {
22730
- css: (ctx) => `from { transform: scale(${ctx.scaleStart}); opacity: 0; } to { transform: scale(1); opacity: 1; }`,
22841
+ css: () => "from { transform: scale(var(--rafters-extent-pop)); opacity: 0; } to { transform: scale(1); opacity: 1; }",
22731
22842
  meaning: "Scale up while fading in",
22732
22843
  contexts: ["modal", "popover", "dialog"]
22733
22844
  },
22734
22845
  "scale-out": {
22735
- css: (ctx) => `from { transform: scale(1); opacity: 1; } to { transform: scale(${ctx.scaleStart}); opacity: 0; }`,
22846
+ css: () => "from { transform: scale(1); opacity: 1; } to { transform: scale(var(--rafters-extent-pop)); opacity: 0; }",
22736
22847
  meaning: "Scale down while fading out",
22737
22848
  contexts: ["modal-exit", "popover-close"]
22738
22849
  },
@@ -22889,6 +23000,56 @@ var DEFAULT_ANIMATION_DEFINITIONS = {
22889
23000
  contexts: ["input"]
22890
23001
  }
22891
23002
  };
23003
+ var DEFAULT_MOTION_CELL_ANIMATIONS = {
23004
+ "dialog-content-open": {
23005
+ keyframe: "scale-in",
23006
+ tier: "normal",
23007
+ curve: "enter",
23008
+ cell: { component: "dialog", part: "content", transition: "closed -> open" },
23009
+ meaning: "A dialog arriving: fade + zoom from the pop extent, on the arrival curve.",
23010
+ contexts: ["dialog", "modal", "alert-dialog"]
23011
+ },
23012
+ "dialog-content-close": {
23013
+ keyframe: "scale-out",
23014
+ tier: "moderate",
23015
+ curve: "exit",
23016
+ cell: { component: "dialog", part: "content", transition: "open -> closed" },
23017
+ meaning: "A dialog leaving: fade + zoom back to the pop extent, on the departure curve.",
23018
+ contexts: ["dialog", "modal", "alert-dialog"]
23019
+ },
23020
+ "popover-content-open": {
23021
+ keyframe: "scale-in",
23022
+ tier: "moderate",
23023
+ curve: "enter",
23024
+ cell: { component: "popover", part: "content", transition: "closed -> open" },
23025
+ meaning: "A popover arriving: smaller and nearer than a dialog, so one tier quicker.",
23026
+ contexts: ["popover", "anchored-popup"]
23027
+ },
23028
+ "popover-content-close": {
23029
+ keyframe: "scale-out",
23030
+ tier: "fast",
23031
+ curve: "exit",
23032
+ cell: { component: "popover", part: "content", transition: "open -> closed" },
23033
+ meaning: "A popover leaving: the user already chose to dismiss it.",
23034
+ contexts: ["popover", "anchored-popup"]
23035
+ },
23036
+ "dropdown-menu-content-open": {
23037
+ keyframe: "scale-in",
23038
+ tier: "moderate",
23039
+ curve: "enter",
23040
+ cell: { component: "dropdown-menu", part: "content", transition: "closed -> open" },
23041
+ meaning: "A menu arriving: same anchored-popup moment as popover, declared separately.",
23042
+ contexts: ["dropdown-menu", "menu", "anchored-popup"]
23043
+ },
23044
+ "dropdown-menu-content-close": {
23045
+ keyframe: "scale-out",
23046
+ tier: "fast",
23047
+ curve: "exit",
23048
+ cell: { component: "dropdown-menu", part: "content", transition: "open -> closed" },
23049
+ meaning: "A menu leaving, after a choice or a dismissal.",
23050
+ contexts: ["dropdown-menu", "menu", "anchored-popup"]
23051
+ }
23052
+ };
22892
23053
  var DEFAULT_MOTION_COMPOSITE_PRESETS = {
22893
23054
  "motion-fade-in": {
22894
23055
  durationTier: "fast",
@@ -22924,7 +23085,8 @@ var DEFAULT_MOTION_COMPOSITE_PRESETS = {
22924
23085
  var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
22925
23086
  hover: {
22926
23087
  properties: ["color", "background-color", "border-color"],
22927
- durationTier: "fast",
23088
+ travel: "none",
23089
+ band: "fast",
22928
23090
  curve: "standard",
22929
23091
  reducedMotion: null,
22930
23092
  category: "interaction",
@@ -22934,7 +23096,8 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
22934
23096
  },
22935
23097
  focus: {
22936
23098
  properties: ["box-shadow", "outline-color"],
22937
- durationTier: "micro",
23099
+ travel: "none",
23100
+ band: "micro",
22938
23101
  curve: "linear",
22939
23102
  reducedMotion: null,
22940
23103
  category: "interaction",
@@ -22944,7 +23107,8 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
22944
23107
  },
22945
23108
  press: {
22946
23109
  properties: ["transform", "color", "background-color"],
22947
- durationTier: "micro",
23110
+ travel: "none",
23111
+ band: "micro",
22948
23112
  curve: "spring-snappy",
22949
23113
  reducedMotion: { properties: ["color", "background-color"] },
22950
23114
  category: "interaction",
@@ -22954,18 +23118,25 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
22954
23118
  },
22955
23119
  toggle: {
22956
23120
  properties: ["color", "background-color", "transform"],
22957
- durationTier: "moderate",
22958
- curve: "spring-snappy",
23121
+ travel: "none",
23122
+ band: "moderate",
23123
+ // `standard`, not `spring-snappy`. The 30-site study found 29 of 30 sites
23124
+ // carry zero overshoot curves, and the one that does is the friendly
23125
+ // exemplar -- spring-snappy belongs to friendly and effectively nowhere
23126
+ // else. Efficient is the shipped default intent and is characterised as
23127
+ // zero-overshoot, so a spring here contradicts the intent it ships under.
23128
+ // Matches the recorded ruling: an efficient toggle is crisp; friendly is
23129
+ // the intent that springs a switch.
23130
+ curve: "standard",
22959
23131
  reducedMotion: { properties: ["color", "background-color"] },
22960
23132
  category: "interaction",
22961
- sizeReasoning: "A thumb travelling a track is a small, tracked movement -- moderate tier, snappy spring. Reduced motion drops the transform to a colour cross-fade.",
23133
+ sizeReasoning: "A thumb travelling a track is a small, tracked movement -- moderate tier at the standard curve. Reduced motion drops the transform to a colour cross-fade.",
22962
23134
  meaning: "Toggle/switch state change. Shows the new state.",
22963
23135
  contexts: ["switch", "toggle", "checkbox"]
22964
23136
  },
22965
23137
  "dropdown-in": {
22966
23138
  properties: ["opacity", "transform"],
22967
- durationTier: "moderate",
22968
- curve: "enter",
23139
+ travel: "short",
22969
23140
  reducedMotion: { properties: ["opacity"], ms: 100 },
22970
23141
  category: "enter",
22971
23142
  sizeReasoning: "A dropdown is small and travels a short distance -- moderate tier, one step below the modal, with the arrival curve.",
@@ -22974,8 +23145,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
22974
23145
  },
22975
23146
  "dropdown-out": {
22976
23147
  properties: ["opacity", "transform"],
22977
- durationTier: "fast",
22978
- curve: "exit",
23148
+ travel: "short",
22979
23149
  reducedMotion: { properties: ["opacity"], ms: 100 },
22980
23150
  category: "exit",
22981
23151
  sizeReasoning: "The exit of a small element -- fast tier (shorter than its moderate entrance) with the departure curve. The user already chose to dismiss it.",
@@ -22984,8 +23154,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
22984
23154
  },
22985
23155
  "modal-in": {
22986
23156
  properties: ["opacity", "transform"],
22987
- durationTier: "normal",
22988
- curve: "enter",
23157
+ travel: "medium",
22989
23158
  reducedMotion: { properties: ["opacity"], ms: 150 },
22990
23159
  category: "enter",
22991
23160
  sizeReasoning: "A modal is larger and travels farther than a dropdown -- normal tier, one step up, with the arrival curve. Size and distance produce the longer duration.",
@@ -22994,8 +23163,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
22994
23163
  },
22995
23164
  "modal-out": {
22996
23165
  properties: ["opacity", "transform"],
22997
- durationTier: "moderate",
22998
- curve: "exit",
23166
+ travel: "medium",
22999
23167
  reducedMotion: { properties: ["opacity"], ms: 150 },
23000
23168
  category: "exit",
23001
23169
  sizeReasoning: "The modal exit -- moderate tier (shorter than its normal entrance) with the departure curve.",
@@ -23004,8 +23172,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
23004
23172
  },
23005
23173
  "sheet-in": {
23006
23174
  properties: ["transform"],
23007
- durationTier: "normal",
23008
- curve: "spring-smooth",
23175
+ travel: "large",
23009
23176
  reducedMotion: { properties: ["opacity"], ms: 250 },
23010
23177
  category: "enter",
23011
23178
  sizeReasoning: "A sheet is a large spatial movement -- normal tier with the physical settle of a smooth spring, because the user must track it into place. Reduced motion becomes a cross-fade.",
@@ -23014,8 +23181,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
23014
23181
  },
23015
23182
  "sheet-out": {
23016
23183
  properties: ["transform"],
23017
- durationTier: "normal",
23018
- curve: "exit",
23184
+ travel: "large",
23019
23185
  reducedMotion: { properties: ["opacity"], ms: 250 },
23020
23186
  category: "exit",
23021
23187
  sizeReasoning: "The sheet exit -- normal tier (shorter than its slow entrance) with the departure curve.",
@@ -23024,8 +23190,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
23024
23190
  },
23025
23191
  expand: {
23026
23192
  properties: ["grid-template-rows", "opacity"],
23027
- durationTier: "normal",
23028
- curve: "enter",
23193
+ travel: "medium",
23029
23194
  reducedMotion: { properties: ["opacity"] },
23030
23195
  category: "enter",
23031
23196
  sizeReasoning: "Content unfolding to its natural height -- normal tier with the arrival curve. Transitions grid-template-rows (0fr->1fr), the transitionable stand-in for height:auto. Reduced motion snaps the rows and fades opacity.",
@@ -23034,8 +23199,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
23034
23199
  },
23035
23200
  collapse: {
23036
23201
  properties: ["grid-template-rows", "opacity"],
23037
- durationTier: "moderate",
23038
- curve: "exit",
23202
+ travel: "medium",
23039
23203
  reducedMotion: { properties: ["opacity"] },
23040
23204
  category: "exit",
23041
23205
  sizeReasoning: "Content folding away -- moderate tier (shorter than its normal expansion) with the departure curve. Reduced motion snaps the rows.",
@@ -23044,8 +23208,7 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
23044
23208
  },
23045
23209
  page: {
23046
23210
  properties: ["opacity", "transform"],
23047
- durationTier: "normal",
23048
- curve: "spring-smooth",
23211
+ travel: "large",
23049
23212
  reducedMotion: { properties: ["opacity"], ms: 200 },
23050
23213
  category: "enter",
23051
23214
  sizeReasoning: "A whole-view transition -- normal tier with the physical settle of a smooth spring, because the user reorients across a large distance.",
@@ -23053,11 +23216,95 @@ var DEFAULT_MOTION_SEMANTIC_MAPPINGS = {
23053
23216
  contexts: ["page-transition", "route-change", "view-switch"]
23054
23217
  }
23055
23218
  };
23056
- var DEFAULT_DELAY_DEFINITIONS = {
23057
- none: { step: "none" },
23058
- short: { step: -1 },
23059
- medium: { step: 0 },
23060
- long: { step: 1 }
23219
+ var DEFAULT_DELAY_NAMESPACE = {
23220
+ "hover-intent": {
23221
+ value: "200ms",
23222
+ provenance: "observed",
23223
+ note: "Observed in navigation-menu, which hardcoded it before #1995 routed both it and tooltip through the runtime accessor. Observed in working code, not tuned.",
23224
+ meaning: "How long a pointer must rest before the system believes the hover was meant. Long enough to survive a pass-through, short enough that a deliberate hover does not feel ignored.",
23225
+ contexts: ["tooltip", "hover-card", "navigation-menu"]
23226
+ },
23227
+ linger: {
23228
+ value: "300ms",
23229
+ provenance: "proposed",
23230
+ note: "PROPOSED. The grace window before a hovered surface closes, so a diagonal cursor path to a submenu does not dismiss it.",
23231
+ meaning: "How long a surface stays after the pointer leaves, so a near-miss is forgiven.",
23232
+ contexts: ["hover-card", "navigation-menu", "submenu"]
23233
+ },
23234
+ "choreo-step": {
23235
+ value: "50ms",
23236
+ provenance: "proposed",
23237
+ note: "PROPOSED. The offset between two parts of ONE surface moving together (panel then its content).",
23238
+ meaning: "The beat between choreographed parts of a single surface.",
23239
+ contexts: ["modal-content", "panel-content", "sequenced-parts"]
23240
+ },
23241
+ "stagger-step": {
23242
+ value: "0ms",
23243
+ provenance: "proposed",
23244
+ note: "PROPOSED, and zero is a value: efficient does not stagger lists. A non-zero stagger is a character choice a designer makes, not a default.",
23245
+ meaning: "The per-item offset when a list animates in.",
23246
+ contexts: ["staggered-lists", "sequential-elements"]
23247
+ },
23248
+ skip: {
23249
+ value: "300ms",
23250
+ provenance: "proposed",
23251
+ note: "PROPOSED. The warm-reopen window: reopen inside it and the entrance delay is skipped, because the user is already oriented.",
23252
+ meaning: "How long a just-closed surface stays warm enough to reopen without ceremony.",
23253
+ contexts: ["tooltip-reopen", "menu-reopen"]
23254
+ }
23255
+ };
23256
+ var DEFAULT_EXTENT_NAMESPACE = {
23257
+ pop: {
23258
+ value: "0.95",
23259
+ provenance: "proposed",
23260
+ note: "PROPOSED. The scale a surface enters from. Close to 1 so the entrance reads as arrival rather than as a zoom.",
23261
+ meaning: "How far a surface scales up as it arrives.",
23262
+ contexts: ["modal", "popover", "dialog"]
23263
+ },
23264
+ press: {
23265
+ value: "0.97",
23266
+ provenance: "proposed",
23267
+ note: "PROPOSED. Depression under a press. Smaller than `pop` because the finger is the evidence -- the motion only confirms it.",
23268
+ meaning: "How far a control depresses when pressed.",
23269
+ contexts: ["button", "toggle", "press-feedback"]
23270
+ },
23271
+ draw: {
23272
+ value: "1",
23273
+ provenance: "proposed",
23274
+ note: "PROPOSED. The completed fraction of a drawn indicator. 1 is full travel; a value below it is a deliberately incomplete stroke.",
23275
+ meaning: "How far an indicator draws along its track.",
23276
+ contexts: ["tabs-indicator", "underline", "progress-stroke"]
23277
+ }
23278
+ };
23279
+ var DEFAULT_PERIOD_NAMESPACE = {
23280
+ spin: {
23281
+ value: "1s",
23282
+ provenance: "baseline",
23283
+ note: "The shipped loop period of the spin animation.",
23284
+ meaning: "One full rotation of a working indicator.",
23285
+ contexts: ["loading", "spinner", "refresh"]
23286
+ },
23287
+ pulse: {
23288
+ value: "2s",
23289
+ provenance: "baseline",
23290
+ note: "The shipped loop period of the pulse animation.",
23291
+ meaning: "One breath of a skeleton or placeholder.",
23292
+ contexts: ["skeleton", "loading-placeholder"]
23293
+ },
23294
+ blink: {
23295
+ value: "1.25s",
23296
+ provenance: "baseline",
23297
+ note: "The shipped loop period of the caret-blink animation.",
23298
+ meaning: "One blink of a text caret.",
23299
+ contexts: ["input-caret", "text-cursor"]
23300
+ },
23301
+ shimmer: {
23302
+ value: "2s",
23303
+ provenance: "proposed",
23304
+ note: "PROPOSED. No shimmer animation ships yet; the period is here because the namespace is a vocabulary, not a list of what happens to exist.",
23305
+ meaning: "One sweep of a shimmer across a loading surface.",
23306
+ contexts: ["skeleton", "loading-placeholder"]
23307
+ }
23061
23308
  };
23062
23309
  var DEFAULT_FOCUS_CONFIGS = {
23063
23310
  default: {
@@ -23136,41 +23383,13 @@ var DEFAULT_RADIUS_DEFINITIONS = {
23136
23383
  contexts: ["avatars", "pill-buttons", "circular-elements"]
23137
23384
  }
23138
23385
  };
23139
- var DEFAULT_SPACING_MULTIPLIERS = {
23140
- "0": 0,
23141
- "0.5": 0.5,
23142
- "1": 1,
23143
- "1.5": 1.5,
23144
- "2": 2,
23145
- "2.5": 2.5,
23146
- "3": 3,
23147
- "3.5": 3.5,
23148
- "4": 4,
23149
- "5": 5,
23150
- "6": 6,
23151
- "7": 7,
23152
- "8": 8,
23153
- "9": 9,
23154
- "10": 10,
23155
- "11": 11,
23156
- "12": 12,
23157
- "14": 14,
23158
- "16": 16,
23159
- "20": 20,
23160
- "24": 24,
23161
- "28": 28,
23162
- "32": 32,
23163
- "36": 36,
23164
- "40": 40,
23165
- "44": 44,
23166
- "48": 48,
23167
- "52": 52,
23168
- "56": 56,
23169
- "60": 60,
23170
- "64": 64,
23171
- "72": 72,
23172
- "80": 80,
23173
- "96": 96
23386
+ var DEFAULT_SPACING_BOUNDS = {
23387
+ floor: 1,
23388
+ ceiling: 96
23389
+ };
23390
+ var DEFAULT_SHADOW_BOUNDS = {
23391
+ floor: 1,
23392
+ ceiling: 96
23174
23393
  };
23175
23394
  var DEFAULT_TYPOGRAPHY_SCALE = {
23176
23395
  xs: { step: -2, lineHeight: 1.5, letterSpacing: "0.025em" },
@@ -25272,25 +25491,23 @@ function generateDepthTokens(_config, depthDefs) {
25272
25491
  }
25273
25492
 
25274
25493
  // ../design-tokens/src/generators/focus.ts
25275
- function pxToRem(px) {
25276
- const rem = Math.round(px / 16 * 1e3) / 1e3;
25277
- return `${rem}rem`;
25278
- }
25279
25494
  function generateFocusTokens(config2, focusConfigs) {
25280
25495
  const tokens = [];
25281
25496
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
25282
- const { focusRingWidth } = config2;
25283
- const focusRingWidthRem = pxToRem(focusRingWidth);
25497
+ const { focusRingWidth, baseSpacingUnit } = config2;
25498
+ const focusDivisor = focusRingWidth > 0 ? Math.round(baseSpacingUnit / focusRingWidth * 1e3) / 1e3 : 2;
25499
+ const focusWidthValue = `calc(var(--rafters-spacing-base) / ${focusDivisor})`;
25284
25500
  tokens.push({
25285
25501
  name: "focus-ring-width",
25286
- value: focusRingWidthRem,
25502
+ value: focusWidthValue,
25287
25503
  category: "focus",
25288
25504
  namespace: "focus",
25289
- semanticMeaning: "Default focus ring width - WCAG 2.2 requires minimum 2px",
25505
+ semanticMeaning: "Default focus ring width - derives from spacing base",
25290
25506
  usageContext: ["focus-indicators", "keyboard-navigation"],
25291
25507
  accessibilityLevel: "AA",
25292
- focusRingWidth: focusRingWidthRem,
25293
- description: `Focus ring width ${focusRingWidthRem}. WCAG 2.2 requires minimum 2px for visibility.`,
25508
+ focusRingWidth: focusWidthValue,
25509
+ dependsOn: ["spacing-base"],
25510
+ description: `Focus ring width = spacing-base / ${focusDivisor} (${focusRingWidth}px at base ${baseSpacingUnit}). WCAG 2.2 requires minimum 2px.`,
25294
25511
  generatedAt: timestamp,
25295
25512
  containerQueryAware: false,
25296
25513
  userOverride: null,
@@ -25314,14 +25531,22 @@ function generateFocusTokens(config2, focusConfigs) {
25314
25531
  highContrastMode: "Highlight",
25315
25532
  userOverride: null
25316
25533
  });
25534
+ const focusVar = "var(--rafters-focus-ring-width)";
25535
+ const focusCalc = (px) => {
25536
+ const mult = px / focusRingWidth;
25537
+ if (mult === 0) return "0";
25538
+ if (mult === 1) return focusVar;
25539
+ if (mult === -1) return `calc(${focusVar} * -1)`;
25540
+ return `calc(${focusVar} * ${mult})`;
25541
+ };
25317
25542
  for (const [name, focusConfig] of Object.entries(focusConfigs)) {
25318
- const widthRem = pxToRem(focusConfig.width);
25319
- const offsetRem = pxToRem(focusConfig.offset);
25543
+ const widthVal = focusCalc(focusConfig.width);
25544
+ const offsetVal = focusCalc(focusConfig.offset);
25320
25545
  tokens.push({
25321
25546
  name: name === "default" ? "focus-ring" : `focus-ring-${name}`,
25322
25547
  value: JSON.stringify({
25323
- width: widthRem,
25324
- offset: offsetRem,
25548
+ width: widthVal,
25549
+ offset: offsetVal,
25325
25550
  style: focusConfig.style,
25326
25551
  color: "var(--ring)"
25327
25552
  }),
@@ -25329,13 +25554,13 @@ function generateFocusTokens(config2, focusConfigs) {
25329
25554
  namespace: "focus",
25330
25555
  semanticMeaning: focusConfig.meaning,
25331
25556
  usageContext: focusConfig.contexts,
25332
- focusRingWidth: widthRem,
25557
+ focusRingWidth: widthVal,
25333
25558
  focusRingColor: "var(--ring)",
25334
- focusRingOffset: offsetRem,
25559
+ focusRingOffset: offsetVal,
25335
25560
  focusRingStyle: focusConfig.style,
25336
25561
  dependsOn: ["ring", "focus-ring-width"],
25337
25562
  accessibilityLevel: focusConfig.width >= 2 ? "AA" : void 0,
25338
- description: `${focusConfig.meaning}. Width: ${widthRem}, Offset: ${offsetRem}.`,
25563
+ description: `${focusConfig.meaning}. Width: ${widthVal}, Offset: ${offsetVal}.`,
25339
25564
  generatedAt: timestamp,
25340
25565
  containerQueryAware: false,
25341
25566
  highContrastMode: "Highlight",
@@ -25348,7 +25573,7 @@ function generateFocusTokens(config2, focusConfigs) {
25348
25573
  ]
25349
25574
  }
25350
25575
  });
25351
- const outlineValue = `${widthRem} ${focusConfig.style} var(--ring)`;
25576
+ const outlineValue = `${widthVal} ${focusConfig.style} var(--ring)`;
25352
25577
  tokens.push({
25353
25578
  name: name === "default" ? "focus-outline" : `focus-outline-${name}`,
25354
25579
  value: outlineValue,
@@ -25356,20 +25581,21 @@ function generateFocusTokens(config2, focusConfigs) {
25356
25581
  namespace: "focus",
25357
25582
  semanticMeaning: `CSS outline shorthand for ${name} focus ring`,
25358
25583
  usageContext: ["css-outline-property"],
25359
- dependsOn: ["ring"],
25360
- description: `CSS outline value: ${outlineValue}. Use with outline-offset: ${offsetRem}.`,
25584
+ dependsOn: ["ring", "focus-ring-width"],
25585
+ description: `CSS outline value: ${outlineValue}. Use with outline-offset: ${offsetVal}.`,
25361
25586
  generatedAt: timestamp,
25362
25587
  containerQueryAware: false,
25363
25588
  userOverride: null
25364
25589
  });
25365
25590
  tokens.push({
25366
25591
  name: name === "default" ? "focus-offset" : `focus-offset-${name}`,
25367
- value: offsetRem,
25592
+ value: offsetVal,
25368
25593
  category: "focus",
25369
25594
  namespace: "focus",
25370
25595
  semanticMeaning: `Focus ring offset for ${name} style`,
25371
- focusRingOffset: offsetRem,
25372
- description: `Focus offset ${offsetRem} for ${name} focus style.`,
25596
+ focusRingOffset: offsetVal,
25597
+ dependsOn: ["focus-ring-width"],
25598
+ description: `Focus offset ${offsetVal} for ${name} focus style.`,
25373
25599
  generatedAt: timestamp,
25374
25600
  containerQueryAware: false,
25375
25601
  userOverride: null
@@ -25378,7 +25604,7 @@ function generateFocusTokens(config2, focusConfigs) {
25378
25604
  tokens.push({
25379
25605
  name: "focus-within-ring",
25380
25606
  value: JSON.stringify({
25381
- width: focusRingWidthRem,
25607
+ width: focusVar,
25382
25608
  offset: "0",
25383
25609
  style: "solid",
25384
25610
  color: "var(--ring)"
@@ -25387,11 +25613,11 @@ function generateFocusTokens(config2, focusConfigs) {
25387
25613
  namespace: "focus",
25388
25614
  semanticMeaning: "Focus ring for containers with focused descendants",
25389
25615
  usageContext: ["form-groups", "card-actions", "list-containers"],
25390
- focusRingWidth: focusRingWidthRem,
25616
+ focusRingWidth: focusVar,
25391
25617
  focusRingColor: "var(--ring)",
25392
25618
  focusRingOffset: "0",
25393
25619
  focusRingStyle: "solid",
25394
- dependsOn: ["ring"],
25620
+ dependsOn: ["ring", "focus-ring-width"],
25395
25621
  description: "Focus indicator for containers using :focus-within pseudo-class.",
25396
25622
  generatedAt: timestamp,
25397
25623
  containerQueryAware: false,
@@ -25401,13 +25627,13 @@ function generateFocusTokens(config2, focusConfigs) {
25401
25627
  never: ["Use as replacement for child focus indicators", "Apply to non-container elements"]
25402
25628
  }
25403
25629
  });
25404
- const highContrastWidth = focusRingWidth * 1.5;
25405
- const highContrastOffset = focusRingWidth;
25630
+ const hcWidthVal = `calc(${focusVar} * 1.5)`;
25631
+ const hcOffsetVal = focusVar;
25406
25632
  tokens.push({
25407
25633
  name: "focus-high-contrast",
25408
25634
  value: JSON.stringify({
25409
- width: pxToRem(highContrastWidth),
25410
- offset: pxToRem(highContrastOffset),
25635
+ width: hcWidthVal,
25636
+ offset: hcOffsetVal,
25411
25637
  style: "solid",
25412
25638
  color: "Highlight"
25413
25639
  }),
@@ -25415,10 +25641,11 @@ function generateFocusTokens(config2, focusConfigs) {
25415
25641
  namespace: "focus",
25416
25642
  semanticMeaning: "Focus ring for Windows High Contrast Mode",
25417
25643
  usageContext: ["high-contrast-mode", "forced-colors"],
25418
- focusRingWidth: pxToRem(highContrastWidth),
25419
- focusRingOffset: pxToRem(highContrastOffset),
25644
+ focusRingWidth: hcWidthVal,
25645
+ focusRingOffset: hcOffsetVal,
25420
25646
  focusRingStyle: "solid",
25421
25647
  highContrastMode: "Highlight",
25648
+ dependsOn: ["focus-ring-width"],
25422
25649
  description: "High contrast focus ring using system Highlight color.",
25423
25650
  generatedAt: timestamp,
25424
25651
  containerQueryAware: false,
@@ -25598,21 +25825,6 @@ function evaluateExpression(expression, options = {}) {
25598
25825
  }
25599
25826
 
25600
25827
  // ../math-utils/src/progressions.ts
25601
- var progression = (r, base, step) => base * ratioValue(r) ** step;
25602
- function generateSequence(r, base, count, options = {}) {
25603
- const { startStep = 0, includeZero = false } = options;
25604
- const ratio = ratioValue(r);
25605
- const result = [];
25606
- for (let i = 0; i < count; i++) {
25607
- if (i === 0 && includeZero) {
25608
- result.push(0);
25609
- } else {
25610
- const step = startStep + (includeZero ? i - 1 : i);
25611
- result.push(base * ratio ** step);
25612
- }
25613
- }
25614
- return result;
25615
- }
25616
25828
  function generateModularScale(r, base, steps2 = 5) {
25617
25829
  const ratio = ratioValue(r);
25618
25830
  const smaller = [];
@@ -25674,23 +25886,123 @@ function tryParseUnit(cssValue, registry2 = DEFAULT_UNITS) {
25674
25886
  }
25675
25887
  }
25676
25888
 
25889
+ // ../design-tokens/src/generators/motion-derivation.ts
25890
+ var BAND_ORDER = ["instant", "micro", "fast", "moderate", "normal", "slow"];
25891
+ var TRAVEL_BAND = {
25892
+ none: "fast",
25893
+ short: "moderate",
25894
+ medium: "normal",
25895
+ large: "slow"
25896
+ };
25897
+ var LARGE_TRAVEL_DISAGREEMENT = {
25898
+ derived: "slow",
25899
+ shipped: "normal",
25900
+ reason: "b864de01 places the sheet pair at normal deliberately -- the one pair without a shortened exit. Unresolved: either the model needs a term that justifies normal, or these move to slow."
25901
+ };
25902
+ function applyLargeTravelException(band, travel) {
25903
+ return travel === "large" ? LARGE_TRAVEL_DISAGREEMENT.shipped : band;
25904
+ }
25905
+ function shortenForExit(band) {
25906
+ const i = BAND_ORDER.indexOf(band);
25907
+ return BAND_ORDER[Math.max(0, i - 1)];
25908
+ }
25909
+ var INTENT_POSITION = {
25910
+ efficient: null,
25911
+ elegant: 1,
25912
+ friendly: null,
25913
+ technical: null,
25914
+ editorial: null
25915
+ };
25916
+ var LANDMARK_BANDS = /* @__PURE__ */ new Set(["instant", "micro", "fast"]);
25917
+ function deriveDuration(band, intent, durationDefs) {
25918
+ const def = durationDefs[band];
25919
+ if (def === void 0) {
25920
+ throw new Error(
25921
+ `motion derivation: unknown band "${band}". Known bands: ${BAND_ORDER.join(", ")}.`
25922
+ );
25923
+ }
25924
+ if (LANDMARK_BANDS.has(band)) return def.default;
25925
+ const position = INTENT_POSITION[intent];
25926
+ if (position === null) return def.default;
25927
+ const [min, max2] = def.range;
25928
+ return Math.round(min + (max2 - min) * position);
25929
+ }
25930
+ function deriveBand(category, travel, declaredBand) {
25931
+ if (category === "interaction") {
25932
+ if (declaredBand === void 0) {
25933
+ throw new Error(
25934
+ "motion derivation: an interaction mapping must declare its band -- it has no travel to derive from."
25935
+ );
25936
+ }
25937
+ return declaredBand;
25938
+ }
25939
+ const base = applyLargeTravelException(TRAVEL_BAND[travel], travel);
25940
+ if (category === "exit" && travel !== "large") return shortenForExit(base);
25941
+ return base;
25942
+ }
25943
+ function deriveCurve(category, travel, _intent, declaredCurve) {
25944
+ if (category === "exit") return "exit";
25945
+ if (category === "enter") return travel === "large" ? "spring-smooth" : "enter";
25946
+ return declaredCurve ?? "standard";
25947
+ }
25948
+
25677
25949
  // ../design-tokens/src/generators/motion.ts
25678
- function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, semanticMappings, keyframeDefs, animationDefs, compositePresets) {
25950
+ function requireDef(defs, key, kind, owner) {
25951
+ const def = defs[key];
25952
+ if (def === void 0) {
25953
+ throw new Error(
25954
+ `motion generator: ${owner} references unknown ${kind} "${key}". Known ${kind}s: ${Object.keys(defs).sort().join(", ")}.`
25955
+ );
25956
+ }
25957
+ return def;
25958
+ }
25959
+ function motionNamespaceTokenName(namespace, member) {
25960
+ return `rafters-${namespace}-${member}`;
25961
+ }
25962
+ function namespaceLeaf(input) {
25963
+ const { namespaceName, member, value, provenance, note, meaning, contexts, timestamp } = input;
25964
+ return {
25965
+ name: motionNamespaceTokenName(namespaceName, member),
25966
+ value,
25967
+ category: "motion",
25968
+ namespace: "motion",
25969
+ semanticMeaning: meaning,
25970
+ usageContext: contexts,
25971
+ dependsOn: [],
25972
+ description: `${namespaceName}-${member}: ${value} [provenance: ${provenance}] ${note} ${meaning}`,
25973
+ generatedAt: timestamp,
25974
+ containerQueryAware: false,
25975
+ // Mirrors the exporter's REDUCED_MOTION_ZEROED set: only duration and delay
25976
+ // are zeroed under prefers-reduced-motion. ease/extent are shaped BY a
25977
+ // duration, not zeroed themselves; period is exempt by law (loops slow,
25978
+ // never stop).
25979
+ reducedMotionAware: namespaceName === "duration" || namespaceName === "delay",
25980
+ userOverride: null,
25981
+ usagePatterns: {
25982
+ do: [`Use the generated utility \`${namespaceName}-${member}\``],
25983
+ never: [
25984
+ "Hardcode this value in a component",
25985
+ "Add a second name for the same idea -- one fast, everywhere, always"
25986
+ ]
25987
+ }
25988
+ };
25989
+ }
25990
+ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, extentDefs, periodDefs, semanticMappings, keyframeDefs, animationDefs, compositePresets, cellAnimations) {
25679
25991
  const tokens = [];
25680
25992
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
25681
25993
  const { baseTransitionDuration, progressionRatio } = config2;
25994
+ const intent = config2.intent ?? "efficient";
25682
25995
  const ratio = resolveRatio(progressionRatio);
25683
25996
  const ratioVal = ratioValue(ratio);
25684
- const computeStep = (base, step) => progression(ratio, base, step);
25685
25997
  tokens.push({
25686
25998
  name: "motion-duration-base",
25687
25999
  value: `${baseTransitionDuration}ms`,
25688
26000
  category: "motion",
25689
26001
  namespace: "motion",
25690
- semanticMeaning: "Legacy base transition duration. The perceptual duration scale (motion-duration-*) no longer derives from this; retained only as a reference value and delay-progression base.",
25691
- usageContext: ["calculation-reference", "delay-base"],
26002
+ semanticMeaning: "Legacy base transition duration. Nothing derives from this any more: the perceptual duration scale never did, and the ratio-stepped delay tokens that did were removed in #1991. Retained as a reference value only.",
26003
+ usageContext: ["calculation-reference"],
25692
26004
  progressionSystem: progressionRatio,
25693
- description: `Base duration (${baseTransitionDuration}ms). Delay tokens use ${progressionRatio} progression (ratio ${ratioVal}); duration tiers are perceptual RANGES a designer sets within and do not derive from this base.`,
26005
+ description: `Base duration (${baseTransitionDuration}ms). A reference value with no dependents: duration tiers are perceptual RANGES a designer sets within, and the delay namespace holds relationships rather than ratio steps.`,
25694
26006
  generatedAt: timestamp,
25695
26007
  containerQueryAware: false,
25696
26008
  reducedMotionAware: true,
@@ -25706,7 +26018,7 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25706
26018
  const def = durationDefs[scale2];
25707
26019
  if (!def) continue;
25708
26020
  const scaleIndex = MOTION_DURATION_SCALE.indexOf(scale2);
25709
- const durationMs = def.default;
26021
+ const durationMs = deriveDuration(scale2, intent, durationDefs);
25710
26022
  const [rangeMin, rangeMax] = def.range;
25711
26023
  const bandNote = def.band ? ` Band: ${def.band}.` : "";
25712
26024
  const rangeNote = rangeMin === rangeMax ? " Fixed." : ` Range: ${rangeMin}-${rangeMax}ms.`;
@@ -25759,40 +26071,64 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25759
26071
  }
25760
26072
  });
25761
26073
  }
25762
- for (const [name, def] of Object.entries(delayDefs)) {
25763
- let delayMs;
25764
- let mathRelationship;
25765
- if (def.step === "none") {
25766
- delayMs = 0;
25767
- mathRelationship = "0";
25768
- } else {
25769
- delayMs = Math.round(computeStep(baseTransitionDuration, def.step));
25770
- mathRelationship = def.step === 0 ? `${baseTransitionDuration}ms (base)` : `${baseTransitionDuration} \xD7 ${ratioVal}^${def.step}`;
26074
+ for (const scale2 of MOTION_DURATION_SCALE) {
26075
+ const def = durationDefs[scale2];
26076
+ if (!def) continue;
26077
+ const durationMs = deriveDuration(scale2, intent, durationDefs);
26078
+ tokens.push(
26079
+ namespaceLeaf({
26080
+ namespaceName: "duration",
26081
+ member: scale2,
26082
+ value: `${durationMs}ms`,
26083
+ provenance: "baseline",
26084
+ note: def.band ? `Efficient baseline. ${durationMs}ms within the ${def.band} band (${def.range[0]}-${def.range[1]}ms).` : `Efficient baseline. Fixed at ${durationMs}ms.`,
26085
+ meaning: def.meaning,
26086
+ contexts: def.contexts,
26087
+ timestamp
26088
+ })
26089
+ );
26090
+ }
26091
+ for (const curve of EASING_CURVES) {
26092
+ const def = easingDefs[curve];
26093
+ if (!def) continue;
26094
+ tokens.push(
26095
+ namespaceLeaf({
26096
+ namespaceName: "ease",
26097
+ member: curve,
26098
+ value: def.css,
26099
+ provenance: "baseline",
26100
+ note: "Efficient baseline curve.",
26101
+ meaning: def.meaning,
26102
+ contexts: def.contexts,
26103
+ timestamp
26104
+ })
26105
+ );
26106
+ }
26107
+ for (const [namespaceName, members] of [
26108
+ ["delay", delayDefs],
26109
+ ["extent", extentDefs],
26110
+ ["period", periodDefs]
26111
+ ]) {
26112
+ for (const [member, def] of Object.entries(members)) {
26113
+ tokens.push(
26114
+ namespaceLeaf({
26115
+ namespaceName,
26116
+ member,
26117
+ value: def.value,
26118
+ provenance: def.provenance,
26119
+ note: def.note,
26120
+ meaning: def.meaning,
26121
+ contexts: def.contexts,
26122
+ timestamp
26123
+ })
26124
+ );
25771
26125
  }
25772
- tokens.push({
25773
- name: `motion-delay-${name}`,
25774
- value: delayMs === 0 ? "0ms" : `${delayMs}ms`,
25775
- category: "motion",
25776
- namespace: "motion",
25777
- semanticMeaning: `${name.charAt(0).toUpperCase() + name.slice(1)} animation delay`,
25778
- usageContext: name === "none" ? ["immediate-response"] : name === "short" ? ["staggered-lists", "sequential-elements"] : name === "medium" ? ["modal-content", "after-transition"] : ["emphasis", "dramatic-reveals"],
25779
- delayMs,
25780
- mathRelationship,
25781
- dependsOn: def.step === "none" ? [] : ["motion-duration-base"],
25782
- description: `Delay ${name}: ${delayMs}ms. Based on duration progression.`,
25783
- generatedAt: timestamp,
25784
- containerQueryAware: false,
25785
- reducedMotionAware: true,
25786
- userOverride: null
25787
- });
25788
26126
  }
25789
26127
  const ratioValue2 = ratioVal;
25790
- const scaleStart = Math.round(1 / ratioValue2 ** 0.25 * 100) / 100;
25791
26128
  const pingScale = Math.round(ratioValue2 ** 3 * 10) / 10;
25792
26129
  const pulseOpacity = Math.round(1 / ratioValue2 ** 4 * 100) / 100;
25793
26130
  const bouncePercent = Math.round(100 / ratioValue2 ** 6);
25794
26131
  const keyframeContext = {
25795
- scaleStart,
25796
26132
  pingScale,
25797
26133
  pulseOpacity,
25798
26134
  bouncePercent
@@ -25822,15 +26158,18 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25822
26158
  durationRef = anim.duration.loopPeriod;
25823
26159
  durationDependency = [];
25824
26160
  } else {
25825
- const durationDef = durationDefs[anim.duration.tier];
25826
- if (!durationDef) continue;
26161
+ const durationDef = requireDef(
26162
+ durationDefs,
26163
+ anim.duration.tier,
26164
+ "duration tier",
26165
+ `animation "${name}"`
26166
+ );
25827
26167
  durationValue = `${durationDef.default}ms`;
25828
- durationRef = `var(--motion-duration-${anim.duration.tier})`;
25829
- durationDependency = [`motion-duration-${anim.duration.tier}`];
26168
+ durationRef = `var(--rafters-duration-${anim.duration.tier})`;
26169
+ durationDependency = [`rafters-duration-${anim.duration.tier}`];
25830
26170
  }
25831
- const easingDef = easingDefs[anim.curve];
25832
- if (!easingDef) continue;
25833
- const easingRef = `var(--motion-easing-${anim.curve})`;
26171
+ const easingDef = requireDef(easingDefs, anim.curve, "easing curve", `animation "${name}"`);
26172
+ const easingRef = `var(--rafters-ease-${anim.curve})`;
25834
26173
  const iterations = anim.iterations || "";
25835
26174
  const animValue = iterations ? `${anim.keyframe} ${durationRef} ${easingRef} ${iterations}` : `${anim.keyframe} ${durationRef} ${easingRef}`;
25836
26175
  tokens.push({
@@ -25848,7 +26187,7 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25848
26187
  dependsOn: [
25849
26188
  `motion-keyframe-${anim.keyframe}`,
25850
26189
  ...durationDependency,
25851
- `motion-easing-${anim.curve}`
26190
+ `rafters-ease-${anim.curve}`
25852
26191
  ],
25853
26192
  description: `Animation ${name}: ${anim.meaning}`,
25854
26193
  generatedAt: timestamp,
@@ -25857,10 +26196,57 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25857
26196
  userOverride: null
25858
26197
  });
25859
26198
  }
26199
+ for (const [name, cell] of Object.entries(cellAnimations)) {
26200
+ requireDef(keyframeDefs, cell.keyframe, "keyframe", `motion cell "${name}"`);
26201
+ requireDef(durationDefs, cell.tier, "duration tier", `motion cell "${name}"`);
26202
+ requireDef(easingDefs, cell.curve, "easing curve", `motion cell "${name}"`);
26203
+ const { component, part, transition } = cell.cell;
26204
+ tokens.push({
26205
+ name: `motion-cell-${name}`,
26206
+ value: JSON.stringify({
26207
+ keyframe: cell.keyframe,
26208
+ durationTier: cell.tier,
26209
+ curve: cell.curve
26210
+ }),
26211
+ category: "motion",
26212
+ namespace: "motion",
26213
+ semanticMeaning: cell.meaning,
26214
+ usageContext: cell.contexts,
26215
+ animationName: name,
26216
+ keyframeName: cell.keyframe,
26217
+ generateUtilityClass: true,
26218
+ dependsOn: [
26219
+ `motion-keyframe-${cell.keyframe}`,
26220
+ `rafters-duration-${cell.tier}`,
26221
+ `rafters-ease-${cell.curve}`
26222
+ ],
26223
+ description: `Motion cell ${component} / ${part} / ${transition}: ${cell.keyframe} over ${cell.tier} with ${cell.curve}. ${cell.meaning}`,
26224
+ generatedAt: timestamp,
26225
+ containerQueryAware: false,
26226
+ reducedMotionAware: true,
26227
+ userOverride: null,
26228
+ usagePatterns: {
26229
+ do: [`Apply class animate-${name} on the ${component} ${part} for "${transition}"`],
26230
+ never: [
26231
+ "Reuse this cell on a different component -- assignments come from motion.jsonl, one cell at a time",
26232
+ "Add motion-reduce:animate-none alongside it -- animation:none resets the shorthand and discards the zeroed duration"
26233
+ ]
26234
+ }
26235
+ });
26236
+ }
25860
26237
  for (const [name, comp] of Object.entries(compositePresets)) {
25861
- const durationDef = durationDefs[comp.durationTier];
25862
- const easingDef = easingDefs[comp.curve];
25863
- if (!durationDef || !easingDef) continue;
26238
+ const durationDef = requireDef(
26239
+ durationDefs,
26240
+ comp.durationTier,
26241
+ "duration tier",
26242
+ `composite preset "${name}"`
26243
+ );
26244
+ const easingDef = requireDef(
26245
+ easingDefs,
26246
+ comp.curve,
26247
+ "easing curve",
26248
+ `composite preset "${name}"`
26249
+ );
25864
26250
  const durationMs = durationDef.default;
25865
26251
  tokens.push({
25866
26252
  name,
@@ -25881,12 +26267,16 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25881
26267
  });
25882
26268
  }
25883
26269
  for (const [name, mapping] of Object.entries(semanticMappings)) {
26270
+ const durationTier = deriveBand(mapping.category, mapping.travel, mapping.band);
26271
+ const curve = deriveCurve(mapping.category, mapping.travel, intent, mapping.curve);
26272
+ requireDef(durationDefs, durationTier, "duration tier", `semantic motion "${name}"`);
26273
+ requireDef(easingDefs, curve, "easing curve", `semantic motion "${name}"`);
25884
26274
  tokens.push({
25885
26275
  name: `motion-semantic-${name}`,
25886
26276
  value: JSON.stringify({
25887
26277
  properties: mapping.properties,
25888
- durationTier: mapping.durationTier,
25889
- curve: mapping.curve,
26278
+ durationTier,
26279
+ curve,
25890
26280
  reducedMotion: mapping.reducedMotion
25891
26281
  }),
25892
26282
  category: "motion",
@@ -25895,8 +26285,8 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25895
26285
  usageContext: mapping.contexts,
25896
26286
  motionIntent: mapping.category === "enter" ? "enter" : mapping.category === "exit" ? "exit" : "transition",
25897
26287
  generateUtilityClass: true,
25898
- dependsOn: [`motion-duration-${mapping.durationTier}`, `motion-easing-${mapping.curve}`],
25899
- description: `Semantic motion motion-${name}: ${mapping.properties.join(", ")} over ${mapping.durationTier} with ${mapping.curve}. ${mapping.sizeReasoning}`,
26288
+ dependsOn: [`motion-duration-${durationTier}`, `motion-easing-${curve}`],
26289
+ description: `Semantic motion motion-${name}: ${mapping.properties.join(", ")} over ${durationTier} with ${curve}. ${mapping.sizeReasoning}`,
25900
26290
  generatedAt: timestamp,
25901
26291
  containerQueryAware: false,
25902
26292
  reducedMotionAware: true,
@@ -25913,12 +26303,13 @@ function generateMotionTokens(config2, durationDefs, easingDefs, delayDefs, sema
25913
26303
  ratio: progressionRatio,
25914
26304
  ratioValue: ratioVal,
25915
26305
  baseDuration: baseTransitionDuration,
25916
- note: "Duration tiers are perceptually derived literals (docs/MOTION.md); delay tokens use the workspace progression ratio from the base duration."
26306
+ note: "Duration tiers are perceptually derived literals (docs/MOTION.md) and the five motion namespaces are authored leaves. The ratio reaches exactly one place: the loop keyframes ping, pulse and bounce, whose shapes are computed from it.",
26307
+ ratioDrivenKeyframes: ["ping", "pulse", "bounce"]
25917
26308
  }),
25918
26309
  category: "motion",
25919
26310
  namespace: "motion",
25920
26311
  semanticMeaning: "Metadata about the motion system",
25921
- description: `Duration tiers are perceptual literals; delays use ${progressionRatio} progression from ${baseTransitionDuration}ms base.`,
26312
+ description: `Duration tiers are perceptual literals; the five motion namespaces (duration, ease, delay, extent, period) are authored leaves. The ${progressionRatio} progression drives the loop keyframes ping, pulse and bounce, and nothing else -- the ${baseTransitionDuration}ms base is recorded for reference and drives no value.`,
25922
26313
  generatedAt: timestamp,
25923
26314
  containerQueryAware: false,
25924
26315
  userOverride: null
@@ -25942,16 +26333,17 @@ function generateRadiusTokens(config2, radiusDefs) {
25942
26333
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
25943
26334
  const { baseRadius, progressionRatio } = config2;
25944
26335
  const ratioVal = ratioValue(resolveRatio(progressionRatio));
25945
- const baseRadiusRem = baseRadius / 16;
26336
+ const radiusMultiplier = Math.round(baseRadius / config2.baseSpacingUnit * 1e3) / 1e3;
25946
26337
  tokens.push({
25947
26338
  name: "radius-base",
25948
- value: `${baseRadiusRem}rem`,
26339
+ value: `calc(var(--rafters-spacing-base) * ${radiusMultiplier})`,
25949
26340
  category: "radius",
25950
26341
  namespace: "radius",
25951
- semanticMeaning: "Base border radius - all other radii derive from this value",
26342
+ semanticMeaning: "Base border radius - derives from spacing base",
25952
26343
  usageContext: ["calculation-reference"],
25953
26344
  progressionSystem: progressionRatio,
25954
- description: `Base radius (${baseRadiusRem}rem / ${baseRadius}px). Scale uses ${progressionRatio} progression (ratio ${ratioVal}).`,
26345
+ dependsOn: ["spacing-base"],
26346
+ description: `Base radius = spacing-base * ${radiusMultiplier} (${baseRadius}px at base ${config2.baseSpacingUnit}). Scale uses ${progressionRatio} progression (ratio ${ratioVal}).`,
25955
26347
  generatedAt: timestamp,
25956
26348
  containerQueryAware: false,
25957
26349
  userOverride: null,
@@ -25989,15 +26381,15 @@ function generateRadiusTokens(config2, radiusDefs) {
25989
26381
  mathRelationship = "infinite (9999px)";
25990
26382
  } else if (def.step === 0) {
25991
26383
  value = "var(--rafters-radius-base)";
25992
- mathRelationship = `${baseRadiusRem}rem (base)`;
26384
+ mathRelationship = `spacing-base * ${radiusMultiplier} (base)`;
25993
26385
  } else {
25994
26386
  const multiplier = Math.round(ratioVal ** def.step * 1e3) / 1e3;
25995
26387
  value = `calc(var(--rafters-radius-base) * ${multiplier})`;
25996
26388
  mathRelationship = `base \xD7 ${ratioVal}^${def.step} (\xD7${multiplier})`;
25997
26389
  }
25998
- const scaleName = scale2 === "DEFAULT" ? "radius" : `radius-${scale2}`;
26390
+ const scaleName2 = scale2 === "DEFAULT" ? "radius" : `radius-${scale2}`;
25999
26391
  tokens.push({
26000
- name: scaleName,
26392
+ name: scaleName2,
26001
26393
  value,
26002
26394
  category: "radius",
26003
26395
  namespace: "radius",
@@ -26186,40 +26578,69 @@ function generateSemanticTokens(_config) {
26186
26578
  };
26187
26579
  }
26188
26580
 
26581
+ // ../design-tokens/src/generators/progression.ts
26582
+ var NON_ADVANCING = 1;
26583
+ function progressionFrom(floor, ratioVal, ceiling) {
26584
+ if (!(ratioVal > NON_ADVANCING) || !(floor > 0) || !(ceiling >= floor)) return [floor];
26585
+ const out = [];
26586
+ const seen = /* @__PURE__ */ new Set();
26587
+ for (let n2 = 0; ; n2++) {
26588
+ const value = Math.round(floor * ratioVal ** n2);
26589
+ if (value > ceiling) break;
26590
+ if (!seen.has(value)) {
26591
+ seen.add(value);
26592
+ out.push(value);
26593
+ }
26594
+ }
26595
+ return out;
26596
+ }
26597
+ function nearestRung(target, scale2) {
26598
+ let best = scale2[0] ?? target;
26599
+ for (const v of scale2) {
26600
+ if (Math.abs(v - target) < Math.abs(best - target)) best = v;
26601
+ }
26602
+ return best;
26603
+ }
26604
+
26189
26605
  // ../design-tokens/src/generators/shadow.ts
26190
- function pxToRem2(px) {
26606
+ function pxToRem(px) {
26191
26607
  const rem = Math.round(px / 16 * 1e3) / 1e3;
26192
26608
  return `${rem}rem`;
26193
26609
  }
26194
26610
  var SHADOW_PARTS = ["offset-x", "offset-y", "blur", "spread", "color"];
26195
- function scalePx(multiplier, baseSpacing) {
26196
- return Math.round(multiplier * baseSpacing * 100) / 100;
26611
+ function shadowScale(ratioVal, bounds) {
26612
+ return progressionFrom(bounds.floor, ratioVal, bounds.ceiling);
26613
+ }
26614
+ function scalePx(multiplier, baseSpacing, scale2) {
26615
+ if (multiplier === 0) return 0;
26616
+ return nearestRung(multiplier * baseSpacing, scale2);
26197
26617
  }
26198
- function resolveShadowParts(def, baseSpacing) {
26618
+ function resolveShadowParts(def, baseSpacing, scale2) {
26199
26619
  return {
26200
26620
  // Shadows are vertical-only by design (material elevation model)
26201
26621
  "offset-x": "0rem",
26202
- "offset-y": pxToRem2(scalePx(def.yOffset, baseSpacing)),
26203
- blur: pxToRem2(scalePx(def.blur, baseSpacing)),
26204
- spread: pxToRem2(scalePx(def.spread, baseSpacing)),
26622
+ "offset-y": pxToRem(scalePx(def.yOffset, baseSpacing, scale2)),
26623
+ blur: pxToRem(scalePx(def.blur, baseSpacing, scale2)),
26624
+ spread: pxToRem(scalePx(def.spread, baseSpacing, scale2)),
26205
26625
  color: `rgb(0 0 0 / ${def.opacity})`
26206
26626
  };
26207
26627
  }
26208
- function generateInnerShadowValue(inner, baseSpacing) {
26209
- const y = pxToRem2(scalePx(inner.yOffset, baseSpacing));
26210
- const blur = pxToRem2(scalePx(inner.blur, baseSpacing));
26211
- const spread = pxToRem2(scalePx(inner.spread, baseSpacing));
26628
+ function generateInnerShadowValue(inner, baseSpacing, scale2) {
26629
+ const y = pxToRem(scalePx(inner.yOffset, baseSpacing, scale2));
26630
+ const blur = pxToRem(scalePx(inner.blur, baseSpacing, scale2));
26631
+ const spread = pxToRem(scalePx(inner.spread, baseSpacing, scale2));
26212
26632
  return `0 ${y} ${blur} ${spread} rgb(0 0 0 / ${inner.opacity})`;
26213
26633
  }
26214
26634
  function buildCompositeFromVars(prefix, innerValue) {
26215
26635
  const primary = SHADOW_PARTS.map((part) => `var(--rafters-${prefix}-${part})`).join(" ");
26216
26636
  return innerValue ? `${primary}, ${innerValue}` : primary;
26217
26637
  }
26218
- function generateShadowTokens(config2, shadowDefs) {
26638
+ function generateShadowTokens(config2, shadowDefs, bounds) {
26219
26639
  const tokens = [];
26220
26640
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
26221
26641
  const { baseSpacingUnit, progressionRatio } = config2;
26222
26642
  const ratioVal = ratioValue(resolveRatio(progressionRatio));
26643
+ const geometry = shadowScale(ratioVal, bounds);
26223
26644
  const baseSpacingRem = baseSpacingUnit / 16;
26224
26645
  tokens.push({
26225
26646
  name: "shadow-base-unit",
@@ -26239,10 +26660,10 @@ function generateShadowTokens(config2, shadowDefs) {
26239
26660
  const def = shadowDefs[scale2];
26240
26661
  if (!def) continue;
26241
26662
  const scaleIndex = SHADOW_SCALE.indexOf(scale2);
26242
- const scaleName = scale2 === "DEFAULT" ? "shadow" : `shadow-${scale2}`;
26663
+ const scaleName2 = scale2 === "DEFAULT" ? "shadow" : `shadow-${scale2}`;
26243
26664
  if (def.opacity === 0) {
26244
26665
  tokens.push({
26245
- name: scaleName,
26666
+ name: scaleName2,
26246
26667
  value: "none",
26247
26668
  category: "shadow",
26248
26669
  namespace: "shadow",
@@ -26262,10 +26683,10 @@ function generateShadowTokens(config2, shadowDefs) {
26262
26683
  });
26263
26684
  continue;
26264
26685
  }
26265
- const parts = resolveShadowParts(def, baseSpacingUnit);
26686
+ const parts = resolveShadowParts(def, baseSpacingUnit, geometry);
26266
26687
  const partDeps = [];
26267
26688
  for (const part of SHADOW_PARTS) {
26268
- const partName = `${scaleName}-${part}`;
26689
+ const partName = `${scaleName2}-${part}`;
26269
26690
  partDeps.push(partName);
26270
26691
  tokens.push({
26271
26692
  name: partName,
@@ -26282,10 +26703,10 @@ function generateShadowTokens(config2, shadowDefs) {
26282
26703
  userOverride: null
26283
26704
  });
26284
26705
  }
26285
- const innerValue = def.innerShadow && def.innerShadow.opacity > 0 ? generateInnerShadowValue(def.innerShadow, baseSpacingUnit) : null;
26286
- const compositeValue = buildCompositeFromVars(scaleName, innerValue);
26706
+ const innerValue = def.innerShadow && def.innerShadow.opacity > 0 ? generateInnerShadowValue(def.innerShadow, baseSpacingUnit, geometry) : null;
26707
+ const compositeValue = buildCompositeFromVars(scaleName2, innerValue);
26287
26708
  tokens.push({
26288
- name: scaleName,
26709
+ name: scaleName2,
26289
26710
  value: compositeValue,
26290
26711
  category: "shadow",
26291
26712
  namespace: "shadow",
@@ -26294,7 +26715,7 @@ function generateShadowTokens(config2, shadowDefs) {
26294
26715
  scalePosition: scaleIndex,
26295
26716
  progressionSystem: progressionRatio,
26296
26717
  dependsOn: partDeps,
26297
- description: `Shadow ${scale2}: ${def.meaning}. Composed from var() refs to ${scaleName}-* tokens.`,
26718
+ description: `Shadow ${scale2}: ${def.meaning}. Composed from var() refs to ${scaleName2}-* tokens.`,
26298
26719
  generatedAt: timestamp,
26299
26720
  containerQueryAware: false,
26300
26721
  userOverride: null,
@@ -26367,13 +26788,19 @@ function generateShadowTokens(config2, shadowDefs) {
26367
26788
  }
26368
26789
 
26369
26790
  // ../design-tokens/src/generators/spacing.ts
26370
- function generateSpacingTokens(config2, spacingMultipliers) {
26791
+ function spacingMultipliers(ratioVal, bounds) {
26792
+ return progressionFrom(bounds.floor, ratioVal, bounds.ceiling);
26793
+ }
26794
+ function scaleName(multiplier) {
26795
+ return String(multiplier);
26796
+ }
26797
+ function generateSpacingTokens(config2, bounds) {
26371
26798
  const tokens = [];
26372
26799
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
26373
26800
  const { baseSpacingUnit, progressionRatio } = config2;
26374
26801
  const ratio = resolveRatio(progressionRatio);
26375
26802
  const ratioVal = ratioValue(ratio);
26376
- const progression2 = generateSequence(ratio, baseSpacingUnit, 10, { includeZero: true });
26803
+ const multipliers = spacingMultipliers(ratioVal, bounds);
26377
26804
  const baseRem = baseSpacingUnit / 16;
26378
26805
  tokens.push({
26379
26806
  name: "spacing-base",
@@ -26398,11 +26825,10 @@ function generateSpacingTokens(config2, spacingMultipliers) {
26398
26825
  ]
26399
26826
  }
26400
26827
  });
26401
- for (const scale2 of SPACING_SCALE) {
26402
- const multiplier = spacingMultipliers[scale2];
26403
- if (multiplier === void 0) continue;
26828
+ for (const multiplier of [0, ...multipliers]) {
26829
+ const scale2 = scaleName(multiplier);
26404
26830
  const value = baseSpacingUnit * multiplier;
26405
- const scaleIndex = SPACING_SCALE.indexOf(scale2);
26831
+ const scaleIndex = multipliers.indexOf(multiplier) + 1;
26406
26832
  let meaning;
26407
26833
  let usageContext;
26408
26834
  if (multiplier === 0) {
@@ -26449,16 +26875,19 @@ function generateSpacingTokens(config2, spacingMultipliers) {
26449
26875
  }
26450
26876
  tokens.push({
26451
26877
  name: "spacing-progression",
26878
+ // The scale that actually shipped. Before #2031 this carried a `sample`
26879
+ // computed straight off the ratio while the tokens came from a table --
26880
+ // metadata describing a scale that did not exist.
26452
26881
  value: JSON.stringify({
26453
26882
  ratio: progressionRatio,
26454
26883
  ratioValue: ratioVal,
26455
26884
  baseUnit: baseSpacingUnit,
26456
- sample: progression2.map((v) => Math.round(v * 100) / 100)
26885
+ multipliers
26457
26886
  }),
26458
26887
  category: "spacing",
26459
26888
  namespace: "spacing",
26460
26889
  semanticMeaning: "Metadata about the spacing progression system",
26461
- description: `Spacing uses ${progressionRatio} progression (ratio ${ratioVal}) from base ${baseRem}rem. Sample values: ${progression2.slice(0, 5).map((v) => Math.round(v)).join(", ")}...`,
26890
+ description: `Spacing is ${baseSpacingUnit}px x ${progressionRatio} (${ratioVal})^n from position 0, rounded to whole pixels: ${multipliers.length} rungs, ${baseSpacingUnit}px to ${Math.round(baseSpacingUnit * (multipliers[multipliers.length - 1] ?? 1))}px.`,
26462
26891
  generatedAt: timestamp,
26463
26892
  containerQueryAware: false,
26464
26893
  userOverride: null,
@@ -26795,7 +27224,7 @@ function createGeneratorDefs(colorPaletteBases) {
26795
27224
  },
26796
27225
  {
26797
27226
  name: "spacing",
26798
- generate: (config2) => generateSpacingTokens(config2, DEFAULT_SPACING_MULTIPLIERS)
27227
+ generate: (config2) => generateSpacingTokens(config2, DEFAULT_SPACING_BOUNDS)
26799
27228
  },
26800
27229
  {
26801
27230
  name: "typography",
@@ -26816,7 +27245,7 @@ function createGeneratorDefs(colorPaletteBases) {
26816
27245
  },
26817
27246
  {
26818
27247
  name: "shadow",
26819
- generate: (config2) => generateShadowTokens(config2, DEFAULT_SHADOW_DEFINITIONS)
27248
+ generate: (config2) => generateShadowTokens(config2, DEFAULT_SHADOW_DEFINITIONS, DEFAULT_SHADOW_BOUNDS)
26820
27249
  },
26821
27250
  {
26822
27251
  name: "depth",
@@ -26828,11 +27257,14 @@ function createGeneratorDefs(colorPaletteBases) {
26828
27257
  config2,
26829
27258
  DEFAULT_DURATION_DEFINITIONS,
26830
27259
  DEFAULT_EASING_DEFINITIONS,
26831
- DEFAULT_DELAY_DEFINITIONS,
27260
+ DEFAULT_DELAY_NAMESPACE,
27261
+ DEFAULT_EXTENT_NAMESPACE,
27262
+ DEFAULT_PERIOD_NAMESPACE,
26832
27263
  DEFAULT_MOTION_SEMANTIC_MAPPINGS,
26833
27264
  DEFAULT_KEYFRAME_DEFINITIONS,
26834
27265
  DEFAULT_ANIMATION_DEFINITIONS,
26835
- DEFAULT_MOTION_COMPOSITE_PRESETS
27266
+ DEFAULT_MOTION_COMPOSITE_PRESETS,
27267
+ DEFAULT_MOTION_CELL_ANIMATIONS
26836
27268
  )
26837
27269
  },
26838
27270
  {
@@ -26871,7 +27303,10 @@ function generateBaseSystem(config2 = {}) {
26871
27303
  var UserOverrideSchema = external_exports.object({
26872
27304
  previousValue: external_exports.unknown(),
26873
27305
  reason: external_exports.string(),
26874
- context: external_exports.string().optional()
27306
+ context: external_exports.string().optional(),
27307
+ // Provenance. Optional by design: absent means unknown, which is what every
27308
+ // override written before this field existed actually is.
27309
+ kind: OverrideKindSchema.optional()
26875
27310
  });
26876
27311
  var BindingSchema2 = external_exports.object({
26877
27312
  plugin: external_exports.string(),
@@ -26916,7 +27351,8 @@ var TokenGraph = class {
26916
27351
  userOverride: {
26917
27352
  previousValue: existing?.value,
26918
27353
  reason: options.reason,
26919
- ...options.context ? { context: options.context } : {}
27354
+ ...options.context ? { context: options.context } : {},
27355
+ ...options.kind ? { kind: options.kind } : {}
26920
27356
  },
26921
27357
  ...existing?.binding ? { binding: existing.binding } : {}
26922
27358
  };
@@ -27482,7 +27918,7 @@ async function regenerateOutputs(registry2, input, hooks2 = {}) {
27482
27918
  written.push("rafters.standalone.css");
27483
27919
  }
27484
27920
  if (exports.documentation) {
27485
- const doc = await registryToDocumentation(registry2);
27921
+ const doc = await registryToDocumentation(registry2, { contentSources });
27486
27922
  await writeFile(join2(outputDir2, "rafters.documentation.css"), doc);
27487
27923
  written.push("rafters.documentation.css");
27488
27924
  }
@@ -27512,11 +27948,7 @@ var TokenRegistry = class {
27512
27948
  parsed.push(result.data);
27513
27949
  }
27514
27950
  for (const t of parsed) {
27515
- const override = t.userOverride ? {
27516
- previousValue: t.userOverride.previousValue,
27517
- reason: t.userOverride.reason,
27518
- ...t.userOverride.context ? { context: t.userOverride.context } : {}
27519
- } : void 0;
27951
+ const override = toNodeOverride(t.userOverride);
27520
27952
  if (t.binding && !override) continue;
27521
27953
  this.graph.seed(t.name, t.value, {
27522
27954
  ...override ? { userOverride: override } : {},
@@ -27540,11 +27972,7 @@ var TokenRegistry = class {
27540
27972
  }
27541
27973
  const t = result.data;
27542
27974
  this.metadata.set(t.name, t);
27543
- const override = t.userOverride ? {
27544
- previousValue: t.userOverride.previousValue,
27545
- reason: t.userOverride.reason,
27546
- ...t.userOverride.context ? { context: t.userOverride.context } : {}
27547
- } : void 0;
27975
+ const override = toNodeOverride(t.userOverride);
27548
27976
  if (t.binding && !override) {
27549
27977
  this.graph.bind(t.name, t.binding.plugin, t.binding.input);
27550
27978
  } else {
@@ -27620,6 +28048,15 @@ var TokenParseError = class extends Error {
27620
28048
  this.name = "TokenParseError";
27621
28049
  }
27622
28050
  };
28051
+ function toNodeOverride(field) {
28052
+ if (!field) return void 0;
28053
+ return {
28054
+ previousValue: field.previousValue,
28055
+ reason: field.reason,
28056
+ ...field.context ? { context: field.context } : {},
28057
+ ...field.kind ? { kind: field.kind } : {}
28058
+ };
28059
+ }
27623
28060
  function toUserOverrideField(override, baseValue) {
27624
28061
  const previousValue = override.previousValue ?? baseValue;
27625
28062
  const result = {
@@ -27627,6 +28064,7 @@ function toUserOverrideField(override, baseValue) {
27627
28064
  reason: override.reason
27628
28065
  };
27629
28066
  if (override.context) result.context = override.context;
28067
+ if (override.kind) result.kind = override.kind;
27630
28068
  return result;
27631
28069
  }
27632
28070
 
@@ -28467,15 +28905,37 @@ function log(event) {
28467
28905
  console.log(` ${event.suggestion}`);
28468
28906
  }
28469
28907
  break;
28470
- case "add:complete":
28471
- context.spinner?.succeed(
28472
- `Added ${event.installed} component${event.installed !== 1 ? "s" : ""}`
28473
- );
28474
- if (event.skipped > 0) {
28475
- console.log(` Skipped: ${event.skipped} (use --overwrite to replace)`);
28908
+ case "add:complete": {
28909
+ const written = event.written;
28910
+ const skippedCount = event.skipped;
28911
+ const untrackedCount = event.untracked;
28912
+ const failedCount = event.failed;
28913
+ const headline = `Wrote ${written} item${written !== 1 ? "s" : ""}`;
28914
+ if (failedCount > 0) {
28915
+ context.spinner?.fail(`${headline}, ${failedCount} failed -- see below`);
28916
+ } else {
28917
+ context.spinner?.succeed(headline);
28918
+ }
28919
+ if (skippedCount > 0) {
28920
+ const names = event.skippedComponents ?? [];
28921
+ console.log(
28922
+ ` Skipped: ${skippedCount} (already present; use --update to re-fetch)${names.length > 0 ? ` -- ${names.join(", ")}` : ""}`
28923
+ );
28924
+ }
28925
+ if (untrackedCount > 0) {
28926
+ const names = event.untrackedComponents ?? [];
28927
+ console.log(` Untracked on disk, now tracked: ${untrackedCount} -- ${names.join(", ")}`);
28928
+ }
28929
+ if (failedCount > 0) {
28930
+ const names = event.failedComponents ?? [];
28931
+ console.log(` Failed: ${failedCount} -- ${names.join(", ")}`);
28476
28932
  }
28477
28933
  console.log("");
28478
28934
  break;
28935
+ }
28936
+ case "add:untracked":
28937
+ console.log(` ${event.message}`);
28938
+ break;
28479
28939
  case "add:hint":
28480
28940
  console.log(`
28481
28941
  ${event.message}`);
@@ -28825,6 +29285,51 @@ function resolveReadSet(field, cwd, fallback) {
28825
29285
  return out;
28826
29286
  }
28827
29287
 
29288
+ // src/utils/reconcile.ts
29289
+ import { readdirSync as readdirSync2 } from "fs";
29290
+ var DISCOVERABLE_KINDS = ["components", "primitives", "composites"];
29291
+ var KIND_PATHS = {
29292
+ components: { field: "componentsPath", fallback: "components/ui" },
29293
+ primitives: { field: "primitivesPath", fallback: "lib/primitives" },
29294
+ composites: { field: "compositesPath", fallback: "composites" }
29295
+ };
29296
+ function hasEntryFor(entries, name) {
29297
+ return entries.some((entry) => entry === name || entry.startsWith(`${name}.`));
29298
+ }
29299
+ function buildUpdateCandidates(tracked, index, entries) {
29300
+ const trackedSet = new Set(tracked);
29301
+ const untracked = /* @__PURE__ */ new Set();
29302
+ if (index) {
29303
+ for (const kind of DISCOVERABLE_KINDS) {
29304
+ for (const name of index[kind]) {
29305
+ if (trackedSet.has(name)) continue;
29306
+ if (hasEntryFor(entries[kind], name)) untracked.add(name);
29307
+ }
29308
+ }
29309
+ }
29310
+ return { tracked: [...trackedSet].sort(), untracked: [...untracked].sort() };
29311
+ }
29312
+ function readInstallRoots(cwd, config2) {
29313
+ const entries = { components: [], primitives: [], composites: [] };
29314
+ for (const kind of DISCOVERABLE_KINDS) {
29315
+ const { field, fallback } = KIND_PATHS[kind];
29316
+ const configured = config2?.[field];
29317
+ const pathField = isPathField(configured) ? configured : fallback;
29318
+ const names = /* @__PURE__ */ new Set();
29319
+ for (const dir of resolveReadSet(pathField, cwd, fallback)) {
29320
+ try {
29321
+ for (const entry of readdirSync2(dir)) names.add(entry);
29322
+ } catch {
29323
+ }
29324
+ }
29325
+ entries[kind] = [...names];
29326
+ }
29327
+ return entries;
29328
+ }
29329
+ function isPathField(value) {
29330
+ return typeof value === "string" || Array.isArray(value);
29331
+ }
29332
+
28828
29333
  // src/commands/add.ts
28829
29334
  var REGISTRY_PLUGINS = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
28830
29335
  async function regenerateAfterInstall(cwd, config2) {
@@ -28885,10 +29390,26 @@ function getInstalledNames(config2) {
28885
29390
  const names = /* @__PURE__ */ new Set([
28886
29391
  ...config2.installed.components,
28887
29392
  ...config2.installed.primitives,
28888
- ...config2.installed.composites ?? []
29393
+ ...config2.installed.composites ?? [],
29394
+ ...config2.installed.rules ?? [],
29395
+ ...config2.installed.substrate ?? []
28889
29396
  ]);
28890
29397
  return [...names].sort();
28891
29398
  }
29399
+ async function discoverUntrackedNames(cwd, config2, client, tracked) {
29400
+ let index = null;
29401
+ try {
29402
+ index = await client.fetchIndex();
29403
+ } catch (err) {
29404
+ const message = err instanceof Error ? err.message : String(err);
29405
+ log({
29406
+ event: "add:warning",
29407
+ message: `Could not read the registry index to reconcile on-disk components (${message}). Updating tracked components only.`
29408
+ });
29409
+ }
29410
+ const { untracked } = buildUpdateCandidates(tracked, index, readInstallRoots(cwd, config2));
29411
+ return untracked;
29412
+ }
28892
29413
  function getComponentTarget(config2) {
28893
29414
  return resolveComponentTarget(config2);
28894
29415
  }
@@ -29134,6 +29655,7 @@ async function add(componentArgs, options) {
29134
29655
  if (options.update) {
29135
29656
  options.overwrite = true;
29136
29657
  }
29658
+ let untrackedNames = [];
29137
29659
  if (options.updateAll) {
29138
29660
  options.overwrite = true;
29139
29661
  if (!config2) {
@@ -29141,13 +29663,22 @@ async function add(componentArgs, options) {
29141
29663
  process.exitCode = 1;
29142
29664
  return;
29143
29665
  }
29144
- const installedNames = getInstalledNames(config2);
29145
- if (installedNames.length === 0) {
29666
+ const trackedNames = getInstalledNames(config2);
29667
+ untrackedNames = await discoverUntrackedNames(cwd, config2, client, trackedNames);
29668
+ const candidates = [.../* @__PURE__ */ new Set([...trackedNames, ...untrackedNames])].sort();
29669
+ if (candidates.length === 0) {
29146
29670
  error46("No installed components found. Use 'rafters add <component>' to install first.");
29147
29671
  process.exitCode = 1;
29148
29672
  return;
29149
29673
  }
29150
- components = installedNames;
29674
+ if (untrackedNames.length > 0) {
29675
+ log({
29676
+ event: "add:untracked",
29677
+ components: untrackedNames,
29678
+ message: `Found ${untrackedNames.length} component(s) on disk the config never tracked: ${untrackedNames.join(", ")}. Refreshing and tracking them.`
29679
+ });
29680
+ }
29681
+ components = candidates;
29151
29682
  }
29152
29683
  if (folder === "composites" && components.length === 0) {
29153
29684
  components = ["composites"];
@@ -29198,8 +29729,9 @@ async function add(componentArgs, options) {
29198
29729
  allItems.filter((item) => item.type === "substrate").map((item) => item.files[0]?.path.split("/")[0]).filter((segment) => Boolean(segment))
29199
29730
  )
29200
29731
  ];
29201
- const installed = [];
29732
+ const written = [];
29202
29733
  const skipped = [];
29734
+ const failed = [];
29203
29735
  const installedItems = [];
29204
29736
  const filteredItems = [];
29205
29737
  const target = getComponentTarget(config2);
@@ -29223,7 +29755,7 @@ async function add(componentArgs, options) {
29223
29755
  try {
29224
29756
  const result = await installItem(cwd, item, options, config2, substrateKinds);
29225
29757
  if (result.installed) {
29226
- installed.push(item.name);
29758
+ written.push(item.name);
29227
29759
  installedItems.push(item);
29228
29760
  if (item.type === "ui") {
29229
29761
  const selection = selectFilesForFramework(item.files, target);
@@ -29243,6 +29775,7 @@ async function add(componentArgs, options) {
29243
29775
  installedItems.push(item);
29244
29776
  }
29245
29777
  } catch (err) {
29778
+ failed.push(item.name);
29246
29779
  if (err instanceof Error) {
29247
29780
  log({
29248
29781
  event: "add:warning",
@@ -29300,11 +29833,19 @@ async function add(componentArgs, options) {
29300
29833
  }
29301
29834
  log({
29302
29835
  event: "add:complete",
29303
- installed: installed.length,
29836
+ written: written.length,
29304
29837
  skipped: skipped.length,
29305
- components: installed
29306
- });
29307
- if (skipped.length > 0 && installed.length === 0) {
29838
+ untracked: untrackedNames.length,
29839
+ failed: failed.length,
29840
+ components: written,
29841
+ skippedComponents: skipped,
29842
+ untrackedComponents: untrackedNames,
29843
+ failedComponents: failed
29844
+ });
29845
+ if (failed.length > 0) {
29846
+ process.exitCode = 1;
29847
+ }
29848
+ if (!options.updateAll && skipped.length > 0 && written.length === 0) {
29308
29849
  log({
29309
29850
  event: "add:hint",
29310
29851
  message: "Some components were skipped. Use --update to re-fetch, or --update-all to refresh everything.",
@@ -30187,6 +30728,24 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
30187
30728
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
30188
30729
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
30189
30730
 
30731
+ // src/version.ts
30732
+ import { readFileSync as readFileSync2 } from "fs";
30733
+ var UNKNOWN_VERSION = "0.0.0-unknown";
30734
+ function readVersion() {
30735
+ try {
30736
+ const parsed = JSON.parse(
30737
+ readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
30738
+ );
30739
+ if (parsed !== null && typeof parsed === "object" && "version" in parsed) {
30740
+ const { version: version2 } = parsed;
30741
+ if (typeof version2 === "string" && version2.length > 0) return version2;
30742
+ }
30743
+ } catch {
30744
+ }
30745
+ return UNKNOWN_VERSION;
30746
+ }
30747
+ var VERSION = readVersion();
30748
+
30190
30749
  // src/mcp/tools.ts
30191
30750
  import { readFile as readFile6 } from "fs/promises";
30192
30751
  import { join as join12 } from "path";
@@ -30402,7 +30961,7 @@ async function discoverFromDirs(...directories) {
30402
30961
  }
30403
30962
 
30404
30963
  // src/utils/workspaces.ts
30405
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
30964
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
30406
30965
  import { basename, dirname as dirname3, join as join11, resolve as resolve4 } from "path";
30407
30966
 
30408
30967
  // src/utils/discover.ts
@@ -30430,14 +30989,14 @@ function findMonorepoRoot(startDir, boundary) {
30430
30989
  for (; ; ) {
30431
30990
  const pnpmWorkspace = join11(current, "pnpm-workspace.yaml");
30432
30991
  if (existsSync6(pnpmWorkspace)) {
30433
- const patterns = parsePnpmWorkspaceYaml(readFileSync2(pnpmWorkspace, "utf-8"));
30992
+ const patterns = parsePnpmWorkspaceYaml(readFileSync3(pnpmWorkspace, "utf-8"));
30434
30993
  if (patterns.length > 0) {
30435
30994
  return { root: current, patterns };
30436
30995
  }
30437
30996
  }
30438
30997
  const pkgJson = join11(current, "package.json");
30439
30998
  if (existsSync6(pkgJson)) {
30440
- const patterns = parsePackageJsonWorkspaces(readFileSync2(pkgJson, "utf-8"));
30999
+ const patterns = parsePackageJsonWorkspaces(readFileSync3(pkgJson, "utf-8"));
30441
31000
  if (patterns.length > 0) {
30442
31001
  return { root: current, patterns };
30443
31002
  }
@@ -30493,7 +31052,7 @@ function expandPattern(monorepoRoot, pattern) {
30493
31052
  const parentRel = trimmed.slice(0, -2);
30494
31053
  const parent = join11(monorepoRoot, parentRel);
30495
31054
  if (!existsSync6(parent)) return [];
30496
- return readdirSync2(parent).map((entry) => join11(parent, entry)).filter((path) => {
31055
+ return readdirSync3(parent).map((entry) => join11(parent, entry)).filter((path) => {
30497
31056
  try {
30498
31057
  return statSync2(path).isDirectory();
30499
31058
  } catch {
@@ -30882,7 +31441,7 @@ async function startMcpServer(workspaces, defaultWorkspace) {
30882
31441
  const server = new Server(
30883
31442
  {
30884
31443
  name: "rafters",
30885
- version: "0.0.1"
31444
+ version: VERSION
30886
31445
  },
30887
31446
  {
30888
31447
  capabilities: {
@@ -31678,7 +32237,7 @@ async function studio() {
31678
32237
 
31679
32238
  // src/index.ts
31680
32239
  var program = new Command();
31681
- program.name("rafters").description("Design system CLI - scaffold tokens and serve MCP").version("0.0.1");
32240
+ program.name("rafters").description("Design system CLI - scaffold tokens and serve MCP").version(VERSION);
31682
32241
  program.command("init").description("Initialize .rafters/ with default tokens and config").option("-r, --rebuild", "Regenerate output files from existing tokens").option("--reset", "Re-run generators fresh, replacing persisted tokens").option(
31683
32242
  "--framework <name>",
31684
32243
  "Override framework detection (next|vite|remix|react-router|astro|wc|vanilla)"