@motion-proto/live-tokens 0.68.1 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.claude/skills/live-tokens-build-page/SKILL.md +16 -5
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +55 -19
  3. package/.claude/skills/live-tokens-create-component/references/token-naming.md +30 -1
  4. package/.claude/skills/live-tokens-fix-findings/SKILL.md +133 -0
  5. package/.claude/skills/live-tokens-pick-component/SKILL.md +10 -1
  6. package/CHANGELOG.md +236 -0
  7. package/README.md +13 -4
  8. package/bin/check-component.mjs +367 -63
  9. package/bin/check-page.mjs +409 -0
  10. package/bin/cli.mjs +107 -9
  11. package/bin/lib/catalogue.mjs +123 -0
  12. package/bin/lib/cssValues.mjs +50 -0
  13. package/bin/lib/findings.mjs +106 -0
  14. package/bin/lib/tokenVocabulary.mjs +240 -0
  15. package/dist-plugin/adjust/index.cjs +174 -23
  16. package/dist-plugin/adjust/index.js +68 -23
  17. package/dist-plugin/{chunk-2UX6EVVA.js → chunk-2YNERPXY.js} +1 -1
  18. package/dist-plugin/{chunk-NE6N66EE.js → chunk-GPIBU44G.js} +107 -1
  19. package/dist-plugin/{chunk-ZHPX7ZYQ.js → chunk-RFVYPNRO.js} +39 -1
  20. package/dist-plugin/generateColorsAndType/index.cjs +107 -1
  21. package/dist-plugin/generateColorsAndType/index.js +1 -1
  22. package/dist-plugin/index.cjs +146 -2
  23. package/dist-plugin/index.js +3 -3
  24. package/dist-plugin/migrateData/index.cjs +107 -1
  25. package/dist-plugin/migrateData/index.js +2 -2
  26. package/dist-plugin/tokensCssMigrations/index.cjs +39 -1
  27. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  28. package/package.json +3 -2
  29. package/src/app/site.css +4 -4
  30. package/src/editor/component-editor/ButtonEditor.svelte +71 -4
  31. package/src/editor/component-editor/CardEditor.svelte +6 -6
  32. package/src/editor/component-editor/DialogEditor.svelte +3 -3
  33. package/src/editor/component-editor/IconButtonEditor.svelte +68 -3
  34. package/src/editor/component-editor/ImageEditor.svelte +8 -8
  35. package/src/editor/component-editor/ImageLightboxEditor.svelte +12 -1
  36. package/src/editor/component-editor/MenuSelectEditor.svelte +63 -3
  37. package/src/editor/component-editor/SegmentedControlEditor.svelte +63 -3
  38. package/src/editor/component-editor/SideNavigationEditor.svelte +67 -3
  39. package/src/editor/component-editor/SliderEditor.svelte +186 -0
  40. package/src/editor/component-editor/TabBarEditor.svelte +63 -3
  41. package/src/editor/component-editor/registry.ts +10 -0
  42. package/src/editor/core/components/aliasKinds.ts +51 -28
  43. package/src/editor/core/sketch/sketchLayer.ts +16 -0
  44. package/src/editor/core/store/editorPersistence.ts +44 -1
  45. package/src/editor/core/store/editorRenderer.ts +2 -2
  46. package/src/editor/core/store/editorStore.ts +18 -18
  47. package/src/editor/core/store/editorTypes.ts +7 -6
  48. package/src/editor/core/themes/migrations/2026-09-01-gate-suffix-enabled.ts +31 -0
  49. package/src/editor/core/themes/migrations/2026-09-01-scrim-rename.ts +52 -0
  50. package/src/editor/core/themes/migrations/2026-09-01-tabbar-active-tint.ts +27 -0
  51. package/src/editor/core/themes/migrations/2026-09-01-tint-rename.ts +42 -0
  52. package/src/editor/core/themes/migrations/index.ts +16 -0
  53. package/src/editor/core/themes/slices/domainVars.ts +2 -2
  54. package/src/editor/core/themes/slices/washes.ts +107 -0
  55. package/src/editor/docs/content/editing-tokens.md +5 -3
  56. package/src/editor/docs/content.generated.ts +1 -1
  57. package/src/editor/index.ts +1 -1
  58. package/src/editor/pages/EditorShell.svelte +1 -1
  59. package/src/editor/ui/SurfacesTab.svelte +3 -3
  60. package/src/editor/ui/UITokenSelector.svelte +1 -0
  61. package/src/editor/ui/VariablesTab.svelte +2 -2
  62. package/src/editor/ui/sections/{OverlaysSection.svelte → WashesSection.svelte} +44 -43
  63. package/src/live-tokens/data/colors-and-type/autumn.json +6 -6
  64. package/src/live-tokens/data/colors-and-type/default.json +6 -6
  65. package/src/live-tokens/data/colors-and-type/halloween.json +6 -6
  66. package/src/live-tokens/data/colors-and-type/midnight-study.json +6 -6
  67. package/src/live-tokens/data/colors-and-type/ocean.json +6 -6
  68. package/src/live-tokens/data/colors-and-type/royal-velvet.json +6 -6
  69. package/src/live-tokens/data/colors-and-type/sketchy.json +6 -6
  70. package/src/live-tokens/data/colors-and-type/spring-meadow.json +6 -6
  71. package/src/live-tokens/data/colors-and-type/sunset.json +6 -6
  72. package/src/live-tokens/data/themes/autumn.json +81 -13
  73. package/src/live-tokens/data/themes/halloween.json +81 -13
  74. package/src/live-tokens/data/themes/midnight-study.json +81 -13
  75. package/src/live-tokens/data/themes/ocean.json +81 -13
  76. package/src/live-tokens/data/themes/royal-velvet.json +81 -13
  77. package/src/live-tokens/data/themes/sketchy.json +81 -13
  78. package/src/live-tokens/data/themes/spring-meadow.json +81 -13
  79. package/src/live-tokens/data/themes/sunset.json +81 -13
  80. package/src/live-tokens/data/tokens.generated.css +6 -6
  81. package/src/system/components/Button.svelte +28 -10
  82. package/src/system/components/Card.svelte +6 -6
  83. package/src/system/components/Dialog.svelte +3 -3
  84. package/src/system/components/IconButton.svelte +23 -7
  85. package/src/system/components/Image.svelte +6 -6
  86. package/src/system/components/MenuSelect.svelte +17 -1
  87. package/src/system/components/SegmentedControl.svelte +20 -2
  88. package/src/system/components/SideNavigation.svelte +22 -2
  89. package/src/system/components/Slider.svelte +348 -0
  90. package/src/system/components/TabBar.svelte +20 -2
  91. package/src/system/styles/CONVENTIONS.md +2 -2
  92. package/src/system/styles/tokens.css +12 -4
  93. package/template/package.json +3 -2
  94. package/template/src/pages/Home.svelte +1 -1
  95. package/src/editor/core/themes/slices/overlays.ts +0 -101
@@ -876,6 +876,106 @@ var componentMigration_2026_08_15_lineHeightScaleRename = {
876
876
  apply: renameValues
877
877
  };
878
878
 
879
+ // src/editor/core/themes/migrations/2026-09-01-scrim-rename.ts
880
+ var RENAMED2 = {
881
+ "--overlay-low": "--scrim-low",
882
+ "--overlay": "--scrim",
883
+ "--overlay-high": "--scrim-high"
884
+ };
885
+ var RENAMED_COMPONENT_KEYS = {
886
+ "--dialog-overlay-surface": "--dialog-scrim-surface"
887
+ };
888
+ var colorsAndTypeMigration_2026_09_01_scrimRename = {
889
+ id: "2026-09-01-scrim-rename-theme",
890
+ fromVersion: 5,
891
+ toVersion: 6,
892
+ appliesTo: "colors-and-type",
893
+ apply(rawVars) {
894
+ const out = {};
895
+ for (const [key, value] of Object.entries(rawVars)) {
896
+ out[RENAMED2[key] ?? key] = value;
897
+ }
898
+ return out;
899
+ }
900
+ };
901
+ var componentMigration_2026_09_01_scrimRename = {
902
+ id: "2026-09-01-scrim-rename-component",
903
+ fromVersion: 21,
904
+ toVersion: 22,
905
+ appliesTo: "component-config",
906
+ apply(rawVars) {
907
+ const out = {};
908
+ for (const [key, value] of Object.entries(rawVars)) {
909
+ out[RENAMED_COMPONENT_KEYS[key] ?? key] = RENAMED2[value] ?? value;
910
+ }
911
+ return out;
912
+ }
913
+ };
914
+
915
+ // src/editor/core/themes/migrations/2026-09-01-tint-rename.ts
916
+ var RENAMED3 = {
917
+ "--hover-low": "--tint-low",
918
+ "--hover": "--tint",
919
+ "--hover-high": "--tint-high"
920
+ };
921
+ var colorsAndTypeMigration_2026_09_01_tintRename = {
922
+ id: "2026-09-01-tint-rename-theme",
923
+ fromVersion: 6,
924
+ toVersion: 7,
925
+ appliesTo: "colors-and-type",
926
+ apply(rawVars) {
927
+ const out = {};
928
+ for (const [key, value] of Object.entries(rawVars)) out[RENAMED3[key] ?? key] = value;
929
+ return out;
930
+ }
931
+ };
932
+ var componentMigration_2026_09_01_tintRename = {
933
+ id: "2026-09-01-tint-rename-component",
934
+ fromVersion: 22,
935
+ toVersion: 23,
936
+ appliesTo: "component-config",
937
+ apply(rawVars) {
938
+ const out = {};
939
+ for (const [key, value] of Object.entries(rawVars)) out[key] = RENAMED3[value] ?? value;
940
+ return out;
941
+ }
942
+ };
943
+
944
+ // src/editor/core/themes/migrations/2026-09-01-tabbar-active-tint.ts
945
+ var componentMigration_2026_09_01_tabbarActiveTint = {
946
+ id: "2026-09-01-tabbar-active-tint",
947
+ fromVersion: 23,
948
+ toVersion: 24,
949
+ appliesTo: "component-config",
950
+ apply(rawVars, meta) {
951
+ if (meta.component !== "tabbar") return { ...rawVars };
952
+ const out = { ...rawVars };
953
+ if (out["--tabbar-active-surface"] === "--scrim-low") {
954
+ out["--tabbar-active-surface"] = "--tint-low";
955
+ }
956
+ return out;
957
+ }
958
+ };
959
+
960
+ // src/editor/core/themes/migrations/2026-09-01-gate-suffix-enabled.ts
961
+ var RENAMED4 = {
962
+ "--card-hover-border-active": "--card-hover-border-enabled",
963
+ "--card-hover-shadow-active": "--card-hover-shadow-enabled",
964
+ "--image-zoom-hover": "--image-zoom-enabled",
965
+ "--image-grow-hover": "--image-grow-enabled"
966
+ };
967
+ var componentMigration_2026_09_01_gateSuffixEnabled = {
968
+ id: "2026-09-01-gate-suffix-enabled",
969
+ fromVersion: 24,
970
+ toVersion: 25,
971
+ appliesTo: "component-config",
972
+ apply(rawVars) {
973
+ const out = {};
974
+ for (const [key, value] of Object.entries(rawVars)) out[RENAMED4[key] ?? key] = value;
975
+ return out;
976
+ }
977
+ };
978
+
879
979
  // src/editor/core/themes/migrations/index.ts
880
980
  var MIGRATIONS = [
881
981
  colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
@@ -903,7 +1003,13 @@ var MIGRATIONS = [
903
1003
  componentMigration_2026_05_29_toggleDeriveTrackFromThumb,
904
1004
  colorsAndTypeMigration_2026_08_13_dropLegacyShapeSpaceKeys,
905
1005
  colorsAndTypeMigration_2026_08_15_lineHeightScaleRename,
906
- componentMigration_2026_08_15_lineHeightScaleRename
1006
+ componentMigration_2026_08_15_lineHeightScaleRename,
1007
+ colorsAndTypeMigration_2026_09_01_scrimRename,
1008
+ componentMigration_2026_09_01_scrimRename,
1009
+ colorsAndTypeMigration_2026_09_01_tintRename,
1010
+ componentMigration_2026_09_01_tintRename,
1011
+ componentMigration_2026_09_01_tabbarActiveTint,
1012
+ componentMigration_2026_09_01_gateSuffixEnabled
907
1013
  ];
908
1014
  function countFor(kind) {
909
1015
  return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
@@ -7,10 +7,10 @@ import {
7
7
  normalizeTheme,
8
8
  planLegacyRenames,
9
9
  versionedFileResourceServer
10
- } from "../chunk-2UX6EVVA.js";
10
+ } from "../chunk-2YNERPXY.js";
11
11
  import {
12
12
  CURRENT_COMPONENT_SCHEMA_VERSION
13
- } from "../chunk-NE6N66EE.js";
13
+ } from "../chunk-GPIBU44G.js";
14
14
  import {
15
15
  readLiveTokensConfig,
16
16
  resolveDataDirs
@@ -603,6 +603,42 @@ var tokensCssMigration_2026_08_27_editorialLargeSteps = {
603
603
  }
604
604
  };
605
605
 
606
+ // vite-plugin/tokensCssMigrations/migrations/2026-09-01-scrim-rename.ts
607
+ var RENAMES2 = [
608
+ ["--overlay-low", "--scrim-low"],
609
+ ["--overlay-high", "--scrim-high"],
610
+ ["--overlay", "--scrim"]
611
+ ];
612
+ var tokensCssMigration_2026_09_01_scrimRename = {
613
+ id: "2026-09-01-scrim-rename",
614
+ kind: "breaking",
615
+ description: "Rename --overlay-{low,base,high} to --scrim-*; a scrim dims what is behind it",
616
+ apply(css) {
617
+ let out = css;
618
+ for (const [oldName, newName] of RENAMES2) out = renameToken(out, oldName, newName);
619
+ return out;
620
+ }
621
+ };
622
+
623
+ // vite-plugin/tokensCssMigrations/migrations/2026-09-01-tint-scale.ts
624
+ var ENTRIES = [
625
+ { name: "--tint-low", value: "rgba(255, 255, 255, 0.05)" },
626
+ { name: "--tint", value: "rgba(255, 255, 255, 0.1)" },
627
+ { name: "--tint-high", value: "rgba(255, 255, 255, 0.15)" }
628
+ ];
629
+ var tokensCssMigration_2026_09_01_tintScale = {
630
+ id: "2026-09-01-tint-scale",
631
+ kind: "additive",
632
+ description: "Add the --tint-* scale; a tint shades the surface it sits on",
633
+ apply(css) {
634
+ return ensureScale(css, {
635
+ entries: ENTRIES,
636
+ sectionComment: "Tints \u2014 translucent washes that shade the surface they sit on",
637
+ anchorPrefixes: ["--scrim", "--overlay"]
638
+ });
639
+ }
640
+ };
641
+
606
642
  // vite-plugin/files/dataPaths.ts
607
643
  var import_fs = __toESM(require("fs"), 1);
608
644
  var import_path = __toESM(require("path"), 1);
@@ -651,7 +687,9 @@ var TOKENS_CSS_MIGRATIONS = [
651
687
  tokensCssMigration_2026_08_26_gradientStops,
652
688
  tokensCssMigration_2026_08_27_backdropPolarity,
653
689
  tokensCssMigration_2026_08_27_editorialSizeSteps,
654
- tokensCssMigration_2026_08_27_editorialLargeSteps
690
+ tokensCssMigration_2026_08_27_editorialLargeSteps,
691
+ tokensCssMigration_2026_09_01_scrimRename,
692
+ tokensCssMigration_2026_09_01_tintScale
655
693
  ];
656
694
  function runTokensCssMigrations(css) {
657
695
  return foldMigrations(css, () => true);
@@ -13,7 +13,7 @@ import {
13
13
  runTokensCssMigrations,
14
14
  semverBumpType,
15
15
  validateTokensCss
16
- } from "../chunk-ZHPX7ZYQ.js";
16
+ } from "../chunk-RFVYPNRO.js";
17
17
  import "../chunk-7TQQTHI6.js";
18
18
  import {
19
19
  readLiveTokensConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.68.1",
3
+ "version": "0.70.0",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -144,6 +144,7 @@
144
144
  "check:overlay-portal": "node scripts/check-overlay-portal.mjs",
145
145
  "check:editor-font-isolation": "node scripts/check-editor-font-isolation.mjs",
146
146
  "check:component-defaults": "node scripts/sync-component-defaults.mjs --check",
147
+ "check:pages": "node bin/cli.mjs check-page --strict",
147
148
  "check:production-is-default": "node scripts/check-production-is-default.mjs",
148
149
  "check:docs-content": "node scripts/sync-docs.mjs --check",
149
150
  "check:token-contract": "node scripts/check-token-contract.mjs",
@@ -157,7 +158,7 @@
157
158
  "collapse:theme": "node scripts/collapse-theme-to-default.mjs",
158
159
  "check:smoke-install": "bash scripts/smoke-install.sh",
159
160
  "check:smoke-create": "bash scripts/smoke-create.sh",
160
- "prepublishOnly": "npm run check:no-style-imports && npm run check:no-tooling-imports && npm run check:slot-prose && npm run check:overlay-portal && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:production-is-default && npm run check:docs-content && npm run build:lib && npm run check:token-contract && npm run check:preset-themes && npm run check:skills && npm run check:skill-atlas && npm run check:smoke-install && npm run check:smoke-create"
161
+ "prepublishOnly": "npm run check:no-style-imports && npm run check:no-tooling-imports && npm run check:slot-prose && npm run check:overlay-portal && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:pages && npm run check:production-is-default && npm run check:docs-content && npm run build:lib && npm run check:token-contract && npm run check:preset-themes && npm run check:skills && npm run check:skill-atlas && npm run check:smoke-install && npm run check:smoke-create"
161
162
  },
162
163
  "peerDependencies": {
163
164
  "@sveltejs/vite-plugin-svelte": "^7.0",
package/src/app/site.css CHANGED
@@ -71,7 +71,7 @@ p {
71
71
  font-size: var(--body-md-font-size);
72
72
  color: var(--text-secondary);
73
73
  line-height: var(--body-md-line-height);
74
- margin: 0 0 14px;
74
+ margin: 0 0 var(--space-16);
75
75
  }
76
76
 
77
77
  p:last-child {
@@ -142,15 +142,15 @@ ol li::marker {
142
142
 
143
143
  hr {
144
144
  border: none;
145
- border-top: 1px solid var(--border-neutral-faint);
145
+ border-top: var(--border-width-1) solid var(--border-neutral-faint);
146
146
  margin: var(--space-32) 0;
147
147
  }
148
148
 
149
149
  blockquote {
150
150
  margin: var(--space-16) 0;
151
151
  padding: var(--space-12) var(--space-20);
152
- border: 1px solid var(--color-brand-500);
153
- border-left: 4px solid var(--color-brand-500);
152
+ border: var(--border-width-1) solid var(--color-brand-500);
153
+ border-left: var(--border-width-4) solid var(--color-brand-500);
154
154
  background: var(--surface-neutral-high);
155
155
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
156
156
  color: var(--text-secondary);
@@ -1,6 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import { buildSiblings } from './scaffolding/siblings';
3
- import type { Token } from './scaffolding/types';
3
+ import type { Token, IntrinsicSpec } from './scaffolding/types';
4
4
 
5
5
  export const component = 'button';
6
6
 
@@ -101,14 +101,34 @@
101
101
  );
102
102
 
103
103
  const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
104
+
105
+ // The hover tint is off unless a project turns it on: the gate holds a
106
+ // transparent layer, a no-op, until it holds the tint itself.
107
+ export const intrinsics: IntrinsicSpec[] = [
108
+ {
109
+ key: 'shimmer',
110
+ variants: ['default'],
111
+ variable: () => '--button-shimmer',
112
+ values: ['var(--shimmer-on)', 'var(--shimmer-off)'],
113
+ default: { default: 'var(--shimmer-on)' },
114
+ },
115
+ {
116
+ key: 'hover-tint',
117
+ variants: ['default'],
118
+ variable: () => '--button-hover-tint-enabled',
119
+ values: ['var(--color-transparent)', 'var(--button-hover-tint)'],
120
+ default: { default: 'var(--color-transparent)' },
121
+ },
122
+ ];
104
123
  </script>
105
124
 
106
125
  <script lang="ts">
107
126
  import Button from '../../system/components/Button.svelte';
108
127
  import Toggle from '../ui/Toggle.svelte';
128
+ import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
109
129
  import VariantGroup from './scaffolding/VariantGroup.svelte';
110
130
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
111
- import { editorState, setComponentAlias } from '../core/store/editorStore';
131
+ import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
112
132
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
113
133
 
114
134
  let shimmerRef = $derived($editorState.components.button?.aliases['--button-shimmer']);
@@ -118,12 +138,49 @@
118
138
  setComponentAlias('button', '--button-shimmer', { kind: 'token', name: checked ? '--shimmer-on' : '--shimmer-off' });
119
139
  }
120
140
 
141
+ // One switch. Turning the tint on also stands each hover surface down to its
142
+ // own base, so hover is the tint alone rather than a swap wearing a wash.
143
+ // Written as aliases rather than gated in CSS: a component token's value is
144
+ // substituted at :root, so it can never name a per-variant private var.
145
+ // Off clears the overrides, returning each surface to its shipped default.
146
+ const HOVER_SWAPS: readonly (readonly [hover: string, base: string])[] = [
147
+ ['--button-primary-hover-surface', '--button-primary-surface'],
148
+ ['--button-secondary-hover-surface', '--button-secondary-surface'],
149
+ ['--button-outline-hover-surface', '--button-outline-surface'],
150
+ ['--button-success-hover-surface', '--button-success-surface'],
151
+ ['--button-danger-hover-surface', '--button-danger-surface'],
152
+ ['--button-warning-hover-surface', '--button-warning-surface'],
153
+ ];
154
+ const TINT_ON = 'var(--button-hover-tint)';
155
+ const TINT_OFF = 'var(--color-transparent)';
156
+
157
+ let tintOn = $derived.by(() => {
158
+ const ref = $editorState.components.button?.aliases['--button-hover-tint-enabled'];
159
+ return (ref?.kind === 'literal' ? ref.value : TINT_OFF) === TINT_ON;
160
+ });
161
+
162
+ function setTintOn(checked: boolean) {
163
+ setComponentAlias('button', '--button-hover-tint-enabled', {
164
+ kind: 'literal',
165
+ value: checked ? TINT_ON : TINT_OFF,
166
+ });
167
+ for (const [hover, base] of HOVER_SWAPS) {
168
+ if (checked) setComponentAlias('button', hover, { kind: 'token', name: base });
169
+ else clearComponentAlias('button', hover);
170
+ }
171
+ }
172
+
121
173
  let previewSize = $state<'default' | 'small'>('default');
122
174
 
123
175
  let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
124
176
 
125
177
  let visibleVariantStates = $derived((v: Variant) => Object.fromEntries(
126
- Object.entries(variantStates(v)).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
178
+ Object.entries(variantStates(v)).map(([name, list]) => [
179
+ name,
180
+ withLinkedDisabled(list, linked.varSet).map((t) =>
181
+ tintOn && t.variable.endsWith('-hover-surface') ? { ...t, disabled: true } : t,
182
+ ),
183
+ ]),
127
184
  ) as Record<string, Token[]>);
128
185
 
129
186
  let visibleSmallStates = $derived(Object.fromEntries(
@@ -158,7 +215,17 @@
158
215
  >
159
216
  {#snippet extraPropertyRows(stateName)}
160
217
  {#if stateName === 'hover'}
161
- <div class="property-row">
218
+ <div class="property-row" data-token-variables="--button-hover-tint-enabled">
219
+ <span class="property-label">tint layer</span>
220
+ <Toggle checked={tintOn} onchange={setTintOn} />
221
+ </div>
222
+ {#if tintOn}
223
+ <div class="property-row">
224
+ <span class="property-label">tint color</span>
225
+ <UIPaletteSelector variable="--button-hover-tint" {component} showNone={false} />
226
+ </div>
227
+ {/if}
228
+ <div class="property-row" data-token-variables="--button-shimmer">
162
229
  <span class="property-label">hover shimmer</span>
163
230
  <Toggle checked={shimmerEnabled} onchange={handleShimmerChange} />
164
231
  </div>
@@ -92,14 +92,14 @@
92
92
  {
93
93
  key: 'hover-border',
94
94
  variants: ['default'],
95
- variable: () => '--card-hover-border-active',
95
+ variable: () => '--card-hover-border-enabled',
96
96
  values: [HOVER_OFF.border, HOVER_ON.border],
97
97
  default: { default: HOVER_OFF.border },
98
98
  },
99
99
  {
100
100
  key: 'hover-shadow',
101
101
  variants: ['default'],
102
- variable: () => '--card-hover-shadow-active',
102
+ variable: () => '--card-hover-shadow-enabled',
103
103
  values: [HOVER_OFF.shadow, HOVER_ON.shadow],
104
104
  default: { default: HOVER_OFF.shadow },
105
105
  },
@@ -122,15 +122,15 @@
122
122
  ($editorState.components[component]?.aliases ?? {}) as Record<string, import('../core/store/editorTypes').CssVarRef>,
123
123
  );
124
124
  let hoverEnabled = $derived.by(() => {
125
- const ref = aliases['--card-hover-border-active'];
125
+ const ref = aliases['--card-hover-border-enabled'];
126
126
  const raw = ref?.kind === 'literal' ? ref.value : HOVER_OFF.border;
127
127
  return raw === HOVER_ON.border;
128
128
  });
129
129
 
130
130
  function setHoverEnabled(checked: boolean) {
131
131
  const v = checked ? HOVER_ON : HOVER_OFF;
132
- setComponentAlias(component, '--card-hover-border-active', { kind: 'literal', value: v.border });
133
- setComponentAlias(component, '--card-hover-shadow-active', { kind: 'literal', value: v.shadow });
132
+ setComponentAlias(component, '--card-hover-border-enabled', { kind: 'literal', value: v.border });
133
+ setComponentAlias(component, '--card-hover-shadow-enabled', { kind: 'literal', value: v.shadow });
134
134
  }
135
135
  </script>
136
136
 
@@ -146,7 +146,7 @@
146
146
  {#if stateName === 'hover'}
147
147
  <div
148
148
  class="hover-control"
149
- data-token-variables="--card-hover-border-active --card-hover-shadow-active"
149
+ data-token-variables="--card-hover-border-enabled --card-hover-shadow-enabled"
150
150
  >
151
151
  <label class="hover-enable">
152
152
  <input type="checkbox" checked={hoverEnabled} onchange={(e) => setHoverEnabled(e.currentTarget.checked)} />
@@ -12,11 +12,11 @@
12
12
  const DEFAULT_CONFIRM: ButtonVariant = 'primary';
13
13
  const DEFAULT_CANCEL: ButtonVariant = 'outline';
14
14
 
15
- // Frame-level tokens (the dialog box itself + overlay + header + body + footer + close icon).
15
+ // Frame-level tokens (the dialog box itself + scrim + header + body + footer + close icon).
16
16
  // Button styling lives in Button.svelte — the dialog only owns its own chrome.
17
17
  const frameStates: Record<string, Token[]> = {
18
- overlay: [
19
- { label: 'backdrop color', groupKey: 'surface', variable: '--dialog-overlay-surface' },
18
+ scrim: [
19
+ { label: 'scrim color', groupKey: 'surface', variable: '--dialog-scrim-surface' },
20
20
  ],
21
21
  dialog: [
22
22
  { label: 'surface color', variable: '--dialog-surface' },
@@ -1,6 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import { buildSiblings } from './scaffolding/siblings';
3
- import type { Token } from './scaffolding/types';
3
+ import type { Token, IntrinsicSpec } from './scaffolding/types';
4
4
 
5
5
  export const component = 'iconbutton';
6
6
 
@@ -92,21 +92,72 @@
92
92
  danger: 'fas fa-trash',
93
93
  warning: 'fas fa-triangle-exclamation',
94
94
  };
95
+
96
+ // The hover tint is off unless a project turns it on: the gate holds a
97
+ // transparent layer, a no-op, until it holds the tint itself.
98
+ export const intrinsics: IntrinsicSpec[] = [
99
+ {
100
+ key: 'hover-tint',
101
+ variants: ['default'],
102
+ variable: () => '--iconbutton-hover-tint-enabled',
103
+ values: ['var(--color-transparent)', 'var(--iconbutton-hover-tint)'],
104
+ default: { default: 'var(--color-transparent)' },
105
+ },
106
+ ];
95
107
  </script>
96
108
 
97
109
  <script lang="ts">
98
110
  import IconButton from '../../system/components/IconButton.svelte';
99
111
  import VariantGroup from './scaffolding/VariantGroup.svelte';
100
112
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
101
- import { editorState } from '../core/store/editorStore';
113
+ import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
102
114
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
115
+ import Toggle from '../ui/Toggle.svelte';
116
+ import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
117
+
118
+ // One switch. Turning the tint on also stands each hover surface down to its
119
+ // own base, so hover is the tint alone rather than a swap wearing a wash.
120
+ // Written as aliases rather than gated in CSS: a component token's value is
121
+ // substituted at :root, so it can never name a per-variant private var.
122
+ // Off clears the overrides, returning each surface to its shipped default.
123
+ const HOVER_SWAPS: readonly (readonly [hover: string, base: string])[] = [
124
+ ['--iconbutton-primary-hover-surface', '--iconbutton-primary-surface'],
125
+ ['--iconbutton-secondary-hover-surface', '--iconbutton-secondary-surface'],
126
+ ['--iconbutton-outline-hover-surface', '--iconbutton-outline-surface'],
127
+ ['--iconbutton-success-hover-surface', '--iconbutton-success-surface'],
128
+ ['--iconbutton-danger-hover-surface', '--iconbutton-danger-surface'],
129
+ ['--iconbutton-warning-hover-surface', '--iconbutton-warning-surface'],
130
+ ];
131
+ const TINT_ON = 'var(--iconbutton-hover-tint)';
132
+ const TINT_OFF = 'var(--color-transparent)';
133
+
134
+ let tintOn = $derived.by(() => {
135
+ const ref = $editorState.components.iconbutton?.aliases['--iconbutton-hover-tint-enabled'];
136
+ return (ref?.kind === 'literal' ? ref.value : TINT_OFF) === TINT_ON;
137
+ });
138
+
139
+ function setTintOn(checked: boolean) {
140
+ setComponentAlias('iconbutton', '--iconbutton-hover-tint-enabled', {
141
+ kind: 'literal',
142
+ value: checked ? TINT_ON : TINT_OFF,
143
+ });
144
+ for (const [hover, base] of HOVER_SWAPS) {
145
+ if (checked) setComponentAlias('iconbutton', hover, { kind: 'token', name: base });
146
+ else clearComponentAlias('iconbutton', hover);
147
+ }
148
+ }
103
149
 
104
150
  let previewSize = $state<'default' | 'small'>('default');
105
151
 
106
152
  let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
107
153
 
108
154
  let visibleVariantStates = $derived((v: Variant) => Object.fromEntries(
109
- Object.entries(variantStates(v)).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
155
+ Object.entries(variantStates(v)).map(([name, list]) => [
156
+ name,
157
+ withLinkedDisabled(list, linked.varSet).map((t) =>
158
+ tintOn && t.variable.endsWith('-hover-surface') ? { ...t, disabled: true } : t,
159
+ ),
160
+ ]),
110
161
  ) as Record<string, Token[]>);
111
162
 
112
163
  let visibleSmallStates = $derived(Object.fromEntries(
@@ -139,6 +190,20 @@
139
190
  siblings={buildSiblings(variants, v, variantStates)}
140
191
  previewActions={sizeAction}
141
192
  >
193
+ {#snippet extraPropertyRows(stateName)}
194
+ {#if stateName === 'hover'}
195
+ <div class="property-row" data-token-variables="--iconbutton-hover-tint-enabled">
196
+ <span class="property-label">tint layer</span>
197
+ <Toggle checked={tintOn} onchange={setTintOn} />
198
+ </div>
199
+ {#if tintOn}
200
+ <div class="property-row">
201
+ <span class="property-label">tint color</span>
202
+ <UIPaletteSelector variable="--iconbutton-hover-tint" {component} showNone={false} />
203
+ </div>
204
+ {/if}
205
+ {/if}
206
+ {/snippet}
142
207
  {#snippet children({ activeState })}
143
208
  {@const forceClass = activeState === 'hover' ? 'force-hover' : activeState === 'active' ? 'force-active' : ''}
144
209
  {@const isDisabled = activeState === 'disabled'}
@@ -16,22 +16,22 @@
16
16
 
17
17
  export const allTokens: Token[] = Object.values(states).flat();
18
18
 
19
- // Global zoom defaults. `none` = off; `scale(...)` = on. `--image-zoom-hover` scales the
20
- // content within the masked frame (overflow scaling); `--image-grow-hover` scales the whole
19
+ // Global zoom defaults. `none` = off; `scale(...)` = on. `--image-zoom-enabled` scales the
20
+ // content within the masked frame (overflow scaling); `--image-grow-enabled` scales the whole
21
21
  // frame so it grows. At most one is ever `scale(...)`.
22
22
  const ZOOM_ON = 'scale(var(--image-zoom-scale))';
23
23
  export const intrinsics: IntrinsicSpec[] = [
24
24
  {
25
25
  key: 'zoom',
26
26
  variants: ['default'],
27
- variable: () => '--image-zoom-hover',
27
+ variable: () => '--image-zoom-enabled',
28
28
  values: ['none', ZOOM_ON],
29
29
  default: { default: 'none' },
30
30
  },
31
31
  {
32
32
  key: 'grow',
33
33
  variants: ['default'],
34
- variable: () => '--image-grow-hover',
34
+ variable: () => '--image-grow-enabled',
35
35
  values: ['none', ZOOM_ON],
36
36
  default: { default: 'none' },
37
37
  },
@@ -52,15 +52,15 @@
52
52
  const ref = aliases[variable];
53
53
  return ref?.kind === 'literal' ? ref.value : fallback;
54
54
  }
55
- let contentOn = $derived(aliasValue('--image-zoom-hover', intrinsics[0].default.default) === ZOOM_ON);
56
- let frameOn = $derived(aliasValue('--image-grow-hover', intrinsics[1].default.default) === ZOOM_ON);
55
+ let contentOn = $derived(aliasValue('--image-zoom-enabled', intrinsics[0].default.default) === ZOOM_ON);
56
+ let frameOn = $derived(aliasValue('--image-grow-enabled', intrinsics[1].default.default) === ZOOM_ON);
57
57
  let useZoom = $derived(contentOn || frameOn);
58
58
  // Overflow scaling = mask the zoom to the frame (the content path). Off = grow the frame.
59
59
  let overflowScaling = $derived(!frameOn);
60
60
 
61
61
  function applyZoom(zoomOn: boolean, masked: boolean) {
62
- setComponentAlias(component, '--image-zoom-hover', { kind: 'literal', value: zoomOn && masked ? ZOOM_ON : 'none' });
63
- setComponentAlias(component, '--image-grow-hover', { kind: 'literal', value: zoomOn && !masked ? ZOOM_ON : 'none' });
62
+ setComponentAlias(component, '--image-zoom-enabled', { kind: 'literal', value: zoomOn && masked ? ZOOM_ON : 'none' });
63
+ setComponentAlias(component, '--image-grow-enabled', { kind: 'literal', value: zoomOn && !masked ? ZOOM_ON : 'none' });
64
64
  }
65
65
  const setUseZoom = (checked: boolean) => applyZoom(checked, overflowScaling);
66
66
  const setOverflowScaling = (checked: boolean) => applyZoom(useZoom, checked);
@@ -1,5 +1,5 @@
1
1
  <script module lang="ts">
2
- import type { Token } from './scaffolding/types';
2
+ import type { Token, IntrinsicSpec } from './scaffolding/types';
3
3
 
4
4
  export const component = 'imagelightbox';
5
5
 
@@ -28,6 +28,17 @@
28
28
  };
29
29
 
30
30
  export const allTokens: Token[] = Object.values(states).flat();
31
+
32
+ // How a tile crops inside its frame: a structural choice, not a themeable value.
33
+ export const intrinsics: IntrinsicSpec[] = [
34
+ {
35
+ key: 'tile-fit',
36
+ variants: ['default'],
37
+ variable: () => '--imagelightbox-tile-object-fit',
38
+ values: ['contain', 'cover'],
39
+ default: { default: 'contain' },
40
+ },
41
+ ];
31
42
  </script>
32
43
 
33
44
  <script lang="ts">