@weasel-js/labkit 1.2.0 → 1.3.0-pre.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 (196) hide show
  1. package/dist/_dts/{DrawCommand-C_XboUpz.d.ts → DrawCommand-DBB45NfN.d.ts} +196 -52
  2. package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
  3. package/dist/_dts/index-DgcNdEdh.d.ts +314 -0
  4. package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
  5. package/dist/_dts/types-AHlQxBNN.d.ts +162 -0
  6. package/dist/_dts/{useTrialState-BMNIx3Cy.d.ts → useTrialState-CsGMjhu9.d.ts} +5 -87
  7. package/dist/canvas/index.d.ts +7 -3
  8. package/dist/canvas/index.js +2 -1
  9. package/dist/chrome/index.d.ts +86 -0
  10. package/dist/chrome/index.js +8 -0
  11. package/dist/chrome/index.js.map +1 -0
  12. package/dist/{chunk-VUU5UXHE.js → chunk-4TMMVIDM.js} +8 -5
  13. package/dist/chunk-4TMMVIDM.js.map +1 -0
  14. package/dist/{chunk-THBG7FQZ.js → chunk-HFDVGF4X.js} +15 -12
  15. package/dist/chunk-HFDVGF4X.js.map +1 -0
  16. package/dist/{chunk-574LJAV4.js → chunk-NS54R52R.js} +31 -21
  17. package/dist/chunk-NS54R52R.js.map +1 -0
  18. package/dist/{chunk-G5TJVQQT.js → chunk-O2BULFHX.js} +5 -14
  19. package/dist/chunk-O2BULFHX.js.map +1 -0
  20. package/dist/chunk-O7NVSCLN.js +266 -0
  21. package/dist/chunk-O7NVSCLN.js.map +1 -0
  22. package/dist/chunk-PO6L3NSH.js +366 -0
  23. package/dist/chunk-PO6L3NSH.js.map +1 -0
  24. package/dist/chunk-PQJ5B2U2.js +27 -0
  25. package/dist/chunk-PQJ5B2U2.js.map +1 -0
  26. package/dist/chunk-RVNN4CHQ.js +212 -0
  27. package/dist/chunk-RVNN4CHQ.js.map +1 -0
  28. package/dist/chunk-TLGRYALP.js +6151 -0
  29. package/dist/chunk-TLGRYALP.js.map +1 -0
  30. package/dist/{chunk-SFL7NFKN.js → chunk-V7PRSIRQ.js} +15 -7
  31. package/dist/chunk-V7PRSIRQ.js.map +1 -0
  32. package/dist/{chunk-BOHF3PQO.js → chunk-Z6HJ5FEM.js} +8111 -5190
  33. package/dist/chunk-Z6HJ5FEM.js.map +1 -0
  34. package/dist/chunk-ZZAYVX4X.js +505 -0
  35. package/dist/chunk-ZZAYVX4X.js.map +1 -0
  36. package/dist/controls/index.d.ts +19 -5
  37. package/dist/controls/index.js +3 -1
  38. package/dist/dragdrop/index.d.ts +4 -2
  39. package/dist/index.d.ts +246 -106
  40. package/dist/index.js +673 -559
  41. package/dist/index.js.map +1 -1
  42. package/dist/layers/index.d.ts +5 -3
  43. package/dist/layers/index.js +2 -1
  44. package/dist/passthrough/weasel-canvas.d.ts +69 -13
  45. package/dist/passthrough/weasel-canvas.js +1 -1
  46. package/dist/passthrough/weasel-ui.d.ts +3799 -3672
  47. package/dist/passthrough/weasel-ui.js +2 -2
  48. package/dist/primitives/index.d.ts +102 -8
  49. package/dist/primitives/index.js +4 -1
  50. package/dist/state/index.d.ts +3 -2
  51. package/dist/state/index.js +2 -2
  52. package/dist/styles.css +542 -153
  53. package/dist/surface/index.js +2 -1
  54. package/dist/ui/layers/index.js +4 -3
  55. package/dist/undo/index.d.ts +4 -2
  56. package/package.json +16 -7
  57. package/src/canvas/AGENTS.md +4 -0
  58. package/src/canvas/CanvasStack.tsx +6 -2
  59. package/src/canvas/useLayerScheduler.ts +22 -15
  60. package/src/canvas/usePanZoom.test.ts +80 -0
  61. package/src/canvas/usePanZoom.ts +16 -2
  62. package/src/chrome/ChromeRegions.stories.tsx +106 -0
  63. package/src/chrome/builtins.test.ts +153 -0
  64. package/src/chrome/builtins.tsx +205 -0
  65. package/src/chrome/index.ts +25 -0
  66. package/src/chrome/merge.test.ts +51 -0
  67. package/src/chrome/merge.ts +52 -0
  68. package/src/chrome/regions/PaletteRegion.less +14 -0
  69. package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
  70. package/src/chrome/regions/PaletteRegion.tsx +41 -0
  71. package/src/chrome/regions/SidebarRegion.tsx +56 -0
  72. package/src/chrome/regions/StatusRegion.tsx +31 -0
  73. package/src/chrome/regions/TitleBarRegion.tsx +41 -0
  74. package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
  75. package/src/chrome/regions/ToolbarRegion.tsx +71 -0
  76. package/src/chrome/regions/ViewportRegion.less +42 -0
  77. package/src/chrome/regions/ViewportRegion.tsx +37 -0
  78. package/src/chrome/regions/regions.test.tsx +81 -0
  79. package/src/chrome/types.ts +127 -0
  80. package/src/config/builder.test.ts +87 -0
  81. package/src/config/builder.ts +224 -0
  82. package/src/config/fromConfigField.test.ts +93 -0
  83. package/src/config/fromConfigField.ts +74 -0
  84. package/src/config/index.ts +22 -0
  85. package/src/config/resolve.test.ts +120 -0
  86. package/src/config/resolve.ts +67 -0
  87. package/src/config/rules.ts +68 -0
  88. package/src/config/types.ts +108 -0
  89. package/src/config/useConfigSchema.ts +30 -0
  90. package/src/config/visible.ts +20 -0
  91. package/src/controls/ControlPanel.less +4 -58
  92. package/src/controls/ControlPanel.stories.tsx +86 -0
  93. package/src/controls/ControlPanel.test.tsx +223 -10
  94. package/src/controls/ControlPanel.tsx +202 -203
  95. package/src/dragdrop/DragDropRuntime.tsx +36 -18
  96. package/src/dragdrop/DragGhost.less +1 -1
  97. package/src/dragdrop/Palette.less +1 -1
  98. package/src/index.ts +25 -3
  99. package/src/instrument/SineWave.smoke.test.tsx +8 -1
  100. package/src/instrument/defineInstrument.test.ts +38 -1
  101. package/src/instrument/defineInstrument.ts +31 -4
  102. package/src/instrument/index.ts +1 -0
  103. package/src/instrument/types.ts +27 -4
  104. package/src/instrument/validateConfigSchema.test.ts +13 -3
  105. package/src/instrument/validateConfigSchema.ts +7 -10
  106. package/src/lab/Lab.chrome.test.tsx +65 -0
  107. package/src/lab/Lab.tsx +58 -15
  108. package/src/lab/LabContext.ts +6 -0
  109. package/src/lab/LabFullChrome.stories.tsx +308 -0
  110. package/src/lab/LabHeader.test.tsx +42 -0
  111. package/src/lab/LabHeader.tsx +57 -0
  112. package/src/lab/LabPalette.test.tsx +35 -0
  113. package/src/lab/LabPalette.tsx +34 -0
  114. package/src/lab/LabShell.less +50 -3
  115. package/src/lab/Workspace.less +1 -30
  116. package/src/lab/Workspace.test.tsx +20 -10
  117. package/src/lab/Workspace.tsx +5 -8
  118. package/src/lab/index.ts +2 -0
  119. package/src/layers/AGENTS.md +1 -1
  120. package/src/layers/LayerList.less +31 -7
  121. package/src/layers/LayerList.tsx +17 -4
  122. package/src/passthrough/weasel-ui.ts +23 -0
  123. package/src/primitives/DragHandleGlyph.tsx +29 -0
  124. package/src/primitives/FloatingPanel.less +28 -0
  125. package/src/primitives/FloatingPanel.stories.tsx +46 -0
  126. package/src/primitives/FloatingPanel.test.tsx +240 -0
  127. package/src/primitives/FloatingPanel.tsx +190 -0
  128. package/src/primitives/FpsMeter.test.tsx +44 -0
  129. package/src/primitives/FpsMeter.tsx +20 -9
  130. package/src/primitives/JobProgress.less +69 -0
  131. package/src/primitives/JobProgress.stories.tsx +48 -0
  132. package/src/primitives/JobProgress.tsx +65 -0
  133. package/src/primitives/Legend.less +47 -0
  134. package/src/primitives/Legend.stories.tsx +32 -0
  135. package/src/primitives/Legend.test.tsx +57 -0
  136. package/src/primitives/Legend.tsx +40 -0
  137. package/src/primitives/Sidebar.less +32 -2
  138. package/src/primitives/StatusBar.less +7 -0
  139. package/src/primitives/StatusBar.tsx +10 -3
  140. package/src/primitives/Toolbar.less +50 -3
  141. package/src/primitives/Toolbar.test.tsx +67 -1
  142. package/src/primitives/Toolbar.tsx +72 -6
  143. package/src/primitives/ZoomControl.less +31 -0
  144. package/src/primitives/ZoomControl.tsx +76 -0
  145. package/src/primitives/floating.entry.test.tsx +14 -0
  146. package/src/primitives/index.ts +10 -2
  147. package/src/primitives/useRovingTabIndex.test.ts +27 -0
  148. package/src/primitives/useRovingTabIndex.ts +79 -0
  149. package/src/state/store.ts +11 -3
  150. package/src/state/toolSlot.test.ts +36 -0
  151. package/src/state/types.ts +4 -0
  152. package/src/styles.less +6 -0
  153. package/src/surface/useTiledSurface.ts +14 -10
  154. package/src/test-setup.ts +26 -0
  155. package/src/theme/base.less +19 -2
  156. package/src/theme/interstellar.tokens.json +6 -2
  157. package/src/tools/types.ts +24 -0
  158. package/src/trial/Trial.config.test.tsx +113 -0
  159. package/src/trial/Trial.job.test.tsx +13 -15
  160. package/src/trial/Trial.less +115 -10
  161. package/src/trial/Trial.stories.tsx +1 -0
  162. package/src/trial/Trial.test.tsx +67 -96
  163. package/src/trial/Trial.tsx +80 -39
  164. package/src/trial/TrialChrome.tsx +99 -80
  165. package/src/trial/TrialDragContext.ts +10 -0
  166. package/src/trial/TrialTitleBar.tsx +41 -0
  167. package/src/trial/index.ts +1 -15
  168. package/src/trial/trialChrome.borders.test.ts +38 -0
  169. package/src/ui/layers/LayerStack.less +9 -9
  170. package/src/ui/layers/LayerStack.tsx +1 -13
  171. package/src/ui/properties/CurveField.less +2 -2
  172. package/src/ui/properties/CurveField.test.tsx +11 -11
  173. package/src/ui/properties/PropertyGroup.less +2 -2
  174. package/src/ui/properties/PropertyPanel.less +55 -41
  175. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +4 -2
  176. package/src/ui/properties/storyLayouts.tsx +2 -2
  177. package/dist/_dts/index-JFAYj5Tv.d.ts +0 -144
  178. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  179. package/dist/chunk-574LJAV4.js.map +0 -1
  180. package/dist/chunk-5R2ATYPJ.js +0 -201
  181. package/dist/chunk-5R2ATYPJ.js.map +0 -1
  182. package/dist/chunk-73KA7WBO.js +0 -5992
  183. package/dist/chunk-73KA7WBO.js.map +0 -1
  184. package/dist/chunk-BOHF3PQO.js.map +0 -1
  185. package/dist/chunk-G5TJVQQT.js.map +0 -1
  186. package/dist/chunk-PMAU3SEE.js +0 -126
  187. package/dist/chunk-PMAU3SEE.js.map +0 -1
  188. package/dist/chunk-SFL7NFKN.js.map +0 -1
  189. package/dist/chunk-THBG7FQZ.js.map +0 -1
  190. package/dist/chunk-VUU5UXHE.js.map +0 -1
  191. package/src/instrument/capabilityDetector.test.ts +0 -64
  192. package/src/instrument/capabilityDetector.ts +0 -20
  193. package/src/trial/DefaultSidebar.tsx +0 -29
  194. package/src/trial/DefaultStatusBar.tsx +0 -19
  195. package/src/trial/DefaultToolbar.tsx +0 -92
  196. package/src/trial/slotTypes.ts +0 -54
@@ -0,0 +1,10 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { NodeId } from 'windease';
3
+
4
+ /** Supplied by `<Workspace>` when trials are reorderable, so a trial's title
5
+ * bar can be the drag surface. Null when reordering is off. */
6
+ export const TrialDragContext = createContext<{ nodeId: NodeId } | null>(null);
7
+
8
+ export function useTrialDrag(): { nodeId: NodeId } | null {
9
+ return useContext(TrialDragContext);
10
+ }
@@ -0,0 +1,41 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { NodeId } from 'windease';
3
+ import { useDragHandle } from 'windease/react';
4
+ import { useTrialDrag } from './TrialDragContext';
5
+
6
+ /** Props for `<TrialTitleBar>`. */
7
+ export interface TrialTitleBarProps {
8
+ title: string;
9
+ children?: ReactNode;
10
+ }
11
+
12
+ // `useDragHandle` needs a node id and windease's DragProvider, so the draggable
13
+ // form is a separate component rather than a conditional hook.
14
+ function Draggable({ nodeId, title, children }: TrialTitleBarProps & { nodeId: NodeId }) {
15
+ const handlers = useDragHandle(nodeId);
16
+ return (
17
+ <div className="lk-trial__titlebar lk-trial__titlebar--draggable" {...handlers}>
18
+ <span className="lk-trial__title">{title}</span>
19
+ {children}
20
+ </div>
21
+ );
22
+ }
23
+
24
+ /** A trial's title bar. When the workspace allows reordering the whole bar is
25
+ * the drag surface — there is no separate grip, because a window's title bar
26
+ * is already the thing you expect to drag. */
27
+ export function TrialTitleBar({ title, children }: TrialTitleBarProps) {
28
+ const drag = useTrialDrag();
29
+ if (drag)
30
+ return (
31
+ <Draggable nodeId={drag.nodeId} title={title}>
32
+ {children}
33
+ </Draggable>
34
+ );
35
+ return (
36
+ <div className="lk-trial__titlebar">
37
+ <span className="lk-trial__title">{title}</span>
38
+ {children}
39
+ </div>
40
+ );
41
+ }
@@ -1,20 +1,6 @@
1
- export type { DefaultSidebarProps } from './DefaultSidebar';
2
- export { DefaultSidebar } from './DefaultSidebar';
3
- export type { DefaultStatusBarProps } from './DefaultStatusBar';
4
- export { DefaultStatusBar } from './DefaultStatusBar';
5
- export type { DefaultToolbarProps } from './DefaultToolbar';
6
- export { DefaultToolbar } from './DefaultToolbar';
7
- export type {
8
- SidebarSlot,
9
- StatusBarSlot,
10
- ToolbarSlot,
11
- TrialSidebarContext,
12
- TrialStatusBarContext,
13
- TrialToolbarContext,
14
- } from './slotTypes';
15
1
  export type { TrialProps } from './Trial';
16
2
  export { Trial } from './Trial';
17
- export type { TrialChromeProps } from './TrialChrome';
3
+ export type { TrialChromeProps, UndoBindings } from './TrialChrome';
18
4
  export { TrialChrome } from './TrialChrome';
19
5
  export {
20
6
  addTrial,
@@ -0,0 +1,38 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ const less = readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'Trial.less'), 'utf8');
7
+
8
+ /** The body of one rule, by selector. Resolves Less's `&__suffix` nesting and
9
+ * matches braces, so a nested block cannot end the slice early. */
10
+ function rule(selector: string): string {
11
+ const nested = selector.replace(/^\.[a-z-]+?(__|--)/, '&$1');
12
+ const start = [selector, nested].map((s) => less.indexOf(`${s} {`)).find((i) => i !== -1);
13
+ if (start === undefined) throw new Error(`no rule for ${selector}`);
14
+ const open = less.indexOf('{', start);
15
+ let depth = 0;
16
+ for (let i = open; i < less.length; i += 1) {
17
+ if (less[i] === '{') depth += 1;
18
+ else if (less[i] === '}') {
19
+ depth -= 1;
20
+ if (depth === 0) return less.slice(open + 1, i);
21
+ }
22
+ }
23
+ throw new Error(`unterminated rule for ${selector}`);
24
+ }
25
+
26
+ describe('trial chrome seams', () => {
27
+ // A `not.toMatch` passes on an empty string, so pin that `rule()` found the
28
+ // rule it was asked for.
29
+ it('isolates the rule it is asked for', () => {
30
+ expect(rule('.lk-trial__toolbar')).toMatch(/flex-shrink/);
31
+ expect(rule('.lk-trial__status')).toMatch(/flex-shrink/);
32
+ });
33
+
34
+ it('lets the component draw its own border, not the wrapper', () => {
35
+ expect(rule('.lk-trial__toolbar')).not.toMatch(/border-bottom:\s*[^;]*solid/);
36
+ expect(rule('.lk-trial__status')).not.toMatch(/border-top:\s*[^;]*solid/);
37
+ });
38
+ });
@@ -14,7 +14,7 @@
14
14
  margin: 0;
15
15
  font-family: var(--wzl-font-display);
16
16
  font-weight: var(--wzl-font-weight-light);
17
- font-size: 1rem;
17
+ font-size: var(--wzl-font-size-lg);
18
18
  color: var(--wzl-fg);
19
19
  }
20
20
 
@@ -28,7 +28,7 @@
28
28
  border: 1px dashed var(--wzl-border);
29
29
  border-radius: var(--wzl-radius-sm);
30
30
  padding: 2px 8px;
31
- font-size: 0.78rem;
31
+ font-size: var(--wzl-font-size-sm);
32
32
  color: var(--wzl-fg-subtle);
33
33
  cursor: pointer;
34
34
  }
@@ -46,12 +46,12 @@
46
46
  .lk-layer-stack__drop-hint {
47
47
  height: 2px;
48
48
  background: var(--wzl-accent);
49
- border-radius: 1px;
49
+ border-radius: var(--wzl-radius-sm);
50
50
  }
51
51
 
52
52
  .lk-layer-stack__empty {
53
53
  color: var(--wzl-fg-subtle);
54
- font-size: 0.85rem;
54
+ font-size: var(--wzl-font-size);
55
55
  padding: 8px;
56
56
  text-align: center;
57
57
  }
@@ -96,8 +96,8 @@
96
96
  min-width: 22px;
97
97
  height: 22px;
98
98
  border-radius: var(--wzl-radius-sm);
99
- font-weight: 600;
100
- font-size: 0.85rem;
99
+ font-weight: var(--wzl-font-weight-bold);
100
+ font-size: var(--wzl-font-size);
101
101
  }
102
102
 
103
103
  .lk-layer-card.has-accent .lk-layer-card__handle {
@@ -112,7 +112,7 @@
112
112
  .lk-layer-card__kind,
113
113
  .lk-layer-card__primary {
114
114
  flex: 1;
115
- font-size: 0.9rem;
115
+ font-size: var(--wzl-font-size);
116
116
  }
117
117
 
118
118
  .lk-layer-card__primary {
@@ -121,14 +121,14 @@
121
121
  color: var(--wzl-fg);
122
122
  font: inherit;
123
123
  text-transform: uppercase;
124
- letter-spacing: 0.06em;
124
+ letter-spacing: var(--wzl-tracking-wide);
125
125
  }
126
126
 
127
127
  .lk-root .lk-layer-card__remove {
128
128
  background: transparent;
129
129
  border: 0;
130
130
  color: var(--wzl-fg-subtle);
131
- font-size: 1rem;
131
+ font-size: var(--wzl-font-size-lg);
132
132
  cursor: pointer;
133
133
  padding: 2px 6px;
134
134
  }
@@ -1,5 +1,6 @@
1
1
  import { type CSSProperties, type ReactNode, type RefCallback, useEffect, useState } from 'react';
2
2
  import { dlog, useReorderDragList } from '../../passthrough/weasel-ui';
3
+ import { DragHandleGlyph } from '../../primitives/DragHandleGlyph';
3
4
 
4
5
  /** One card in a layer stack: its identity, the label shown when collapsed,
5
6
  * and the optional select hoisted into its header. */
@@ -205,16 +206,3 @@ export function LayerStack({
205
206
  </div>
206
207
  );
207
208
  }
208
-
209
- function DragHandleGlyph() {
210
- return (
211
- <svg width="12" height="16" viewBox="0 0 8 16" fill="currentColor" aria-hidden="true">
212
- <circle cx="2" cy="3" r="1.1" />
213
- <circle cx="6" cy="3" r="1.1" />
214
- <circle cx="2" cy="8" r="1.1" />
215
- <circle cx="6" cy="8" r="1.1" />
216
- <circle cx="2" cy="13" r="1.1" />
217
- <circle cx="6" cy="13" r="1.1" />
218
- </svg>
219
- );
220
- }
@@ -19,7 +19,7 @@
19
19
  display: flex;
20
20
  justify-content: space-between;
21
21
  font-variant-numeric: tabular-nums;
22
- font-size: 0.85rem;
22
+ font-size: var(--wzl-font-size);
23
23
  color: var(--wzl-fg-subtle);
24
24
  }
25
25
 
@@ -39,7 +39,7 @@
39
39
  border-radius: var(--wzl-radius-sm);
40
40
  color: var(--wzl-fg);
41
41
  padding: 4px 10px;
42
- font-size: 0.78rem;
42
+ font-size: var(--wzl-font-size-sm);
43
43
  cursor: pointer;
44
44
  }
45
45
 
@@ -1,5 +1,5 @@
1
1
  import { render, screen } from '@testing-library/react';
2
- import { describe, expect, it, vi } from 'vitest';
2
+ import { assert, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  // CurveEditor uses weasel's own bundled React and fails in jsdom.
5
5
  // Mock it out — these tests only assert on readouts and the flip button.
@@ -81,13 +81,13 @@ describe('CurveField', () => {
81
81
  />,
82
82
  );
83
83
  const overlay = container.querySelector('.lk-curve-field__marks');
84
- expect(overlay).not.toBeNull();
85
- const rect = overlay!.querySelector('rect');
86
- expect(rect).not.toBeNull();
84
+ assert(overlay);
85
+ const rect = overlay.querySelector('rect');
86
+ assert(rect);
87
87
  // x = 0.2 * 200 = 40; width = (0.4 - 0.2) * 200 = 40.
88
- expect(rect!.getAttribute('x')).toBe('40');
89
- expect(rect!.getAttribute('width')).toBe('40');
90
- expect(rect!.getAttribute('fill')).toBe('#ffcc00');
88
+ expect(rect.getAttribute('x')).toBe('40');
89
+ expect(rect.getAttribute('width')).toBe('40');
90
+ expect(rect.getAttribute('fill')).toBe('#ffcc00');
91
91
  });
92
92
 
93
93
  it('renders a line mark as a positioned line', () => {
@@ -104,10 +104,10 @@ describe('CurveField', () => {
104
104
  />,
105
105
  );
106
106
  const line = container.querySelector('.lk-curve-field__marks line');
107
- expect(line).not.toBeNull();
108
- expect(line!.getAttribute('x1')).toBe('140');
109
- expect(line!.getAttribute('x2')).toBe('140');
110
- expect(line!.getAttribute('stroke')).toBe('#ffcc00');
107
+ assert(line);
108
+ expect(line.getAttribute('x1')).toBe('140');
109
+ expect(line.getAttribute('x2')).toBe('140');
110
+ expect(line.getAttribute('stroke')).toBe('#ffcc00');
111
111
  });
112
112
 
113
113
  it('omits the marks overlay when marks is undefined', () => {
@@ -13,8 +13,8 @@
13
13
  margin: 0 0 8px;
14
14
  font-family: var(--wzl-font-display);
15
15
  font-weight: var(--wzl-font-weight-light);
16
- font-size: 0.78rem;
17
- letter-spacing: 0.08em;
16
+ font-size: var(--wzl-font-size-sm);
17
+ letter-spacing: var(--wzl-tracking-wider);
18
18
  text-transform: uppercase;
19
19
  color: var(--wzl-fg-subtle);
20
20
  }
@@ -15,8 +15,8 @@
15
15
  .lk-property-panel__title {
16
16
  font-family: var(--wzl-font-display);
17
17
  font-weight: var(--wzl-font-weight-light);
18
- font-size: 1.05rem;
19
- line-height: 1.2;
18
+ font-size: var(--wzl-font-size-lg);
19
+ line-height: var(--wzl-leading-snug);
20
20
  letter-spacing: 0.01em;
21
21
  color: var(--wzl-fg);
22
22
  margin: 0 0 12px;
@@ -85,8 +85,8 @@
85
85
  .lk-property-row__label {
86
86
  font-family: var(--wzl-font-display);
87
87
  font-weight: var(--wzl-font-weight-light);
88
- font-size: 0.78rem;
89
- line-height: 1.2;
88
+ font-size: var(--wzl-font-size-sm);
89
+ line-height: var(--wzl-leading-snug);
90
90
  color: var(--wzl-fg-muted);
91
91
  text-transform: uppercase;
92
92
  letter-spacing: 0.05em;
@@ -108,14 +108,13 @@
108
108
  .lk-property-row__readout-input {
109
109
  font: inherit;
110
110
  font-weight: var(--wzl-font-weight-medium);
111
- // Force line-height 1 (the label parent inherits 1.2, which would make
112
- // the input box ~3px taller than the SB reference). Matches SB
113
- // styles.css:385 `font: 500 0.78rem/1`.
114
- line-height: 1;
111
+ // Force leading-tight the label parent inherits 1.2, which would make the
112
+ // input box ~3px taller than the speech-balloons reference.
113
+ line-height: var(--wzl-leading-tight);
115
114
  color: var(--wzl-accent);
116
115
  background: transparent;
117
116
  border: 1px solid transparent;
118
- border-radius: 3px;
117
+ border-radius: var(--wzl-radius-sm);
119
118
  padding: 1px 0;
120
119
  margin: 0;
121
120
  width: calc(5em - 24px);
@@ -126,10 +125,10 @@
126
125
  -moz-appearance: textfield;
127
126
  }
128
127
  .lk-property-row__readout-input:hover {
129
- background: rgba(255, 255, 255, 0.06);
128
+ background: color-mix(in srgb, currentColor 8%, transparent);
130
129
  }
131
130
  .lk-property-row__readout-input:focus {
132
- background: rgba(0, 0, 0, 0.4);
131
+ background: var(--wzl-surface-sunken);
133
132
  border-color: var(--wzl-accent);
134
133
  }
135
134
  .lk-property-row__readout-input::-webkit-inner-spin-button,
@@ -148,8 +147,8 @@
148
147
  // Word-style units (e.g. "px") read as a small dim suffix next to the digits.
149
148
  .lk-property-row__readout-unit {
150
149
  color: var(--wzl-fg-muted);
151
- font-size: 0.78em;
152
- line-height: 1;
150
+ font-size: var(--wzl-font-size-2xs);
151
+ line-height: var(--wzl-leading-tight);
153
152
  pointer-events: none;
154
153
  }
155
154
  // Symbol units (e.g. `<sup>°</sup>`) lean on the browser's native super
@@ -157,7 +156,7 @@
157
156
  // would re-collapse the glyph onto the baseline.
158
157
  .lk-property-row__readout-group sup {
159
158
  color: var(--wzl-fg-muted);
160
- font-size: 0.7em;
159
+ font-size: var(--wzl-font-size-2xs);
161
160
  pointer-events: none;
162
161
  }
163
162
 
@@ -174,12 +173,12 @@
174
173
  }
175
174
  .lk-property-row input[type='range']::-webkit-slider-runnable-track {
176
175
  height: 4px;
177
- border-radius: 2px;
176
+ border-radius: var(--wzl-radius-sm);
178
177
  background: color-mix(in srgb, var(--wzl-accent) 18%, transparent);
179
178
  }
180
179
  .lk-property-row input[type='range']::-moz-range-track {
181
180
  height: 4px;
182
- border-radius: 2px;
181
+ border-radius: var(--wzl-radius-sm);
183
182
  background: color-mix(in srgb, var(--wzl-accent) 18%, transparent);
184
183
  }
185
184
  .lk-property-row input[type='range']::-webkit-slider-thumb {
@@ -225,12 +224,15 @@
225
224
 
226
225
  .lk-property-row input[type='text']:not(.lk-property-row__readout-input),
227
226
  .lk-property-row input[type='number'] {
228
- background: rgba(0, 0, 0, 0.35);
227
+ background: var(--wzl-surface-sunken);
229
228
  color: var(--wzl-fg);
230
229
  border: 1px solid var(--wzl-border);
231
230
  border-radius: var(--wzl-radius-sm);
232
231
  padding: 6px 8px;
233
- font: 400 0.85rem/1 var(--wzl-font-ui);
232
+ font-family: var(--wzl-font-ui);
233
+ font-weight: var(--wzl-font-weight-medium);
234
+ font-size: var(--wzl-font-size);
235
+ line-height: var(--wzl-leading-tight);
234
236
  }
235
237
 
236
238
  .lk-property-row input[type='text']:not(.lk-property-row__readout-input):focus,
@@ -248,7 +250,10 @@
248
250
  border: 0;
249
251
  border-radius: 0;
250
252
  padding: 2px 0;
251
- font: 400 0.85rem/1 var(--wzl-font-ui);
253
+ font-family: var(--wzl-font-ui);
254
+ font-weight: var(--wzl-font-weight-medium);
255
+ font-size: var(--wzl-font-size);
256
+ line-height: var(--wzl-leading-tight);
252
257
  cursor: pointer;
253
258
  }
254
259
  .lk-property-row select:focus {
@@ -344,7 +349,7 @@
344
349
  .lk-property-row--checkbox .lk-property-row__label {
345
350
  text-transform: none;
346
351
  letter-spacing: normal;
347
- font-size: 0.85rem;
352
+ font-size: var(--wzl-font-size);
348
353
  color: var(--wzl-fg);
349
354
  // Place the label after the checkbox visually.
350
355
  order: 1;
@@ -374,7 +379,10 @@
374
379
  color: var(--wzl-fg-muted);
375
380
  border: 0;
376
381
  border-radius: 0;
377
- font: 400 0.78rem/1 var(--wzl-font-ui);
382
+ font-family: var(--wzl-font-ui);
383
+ font-weight: var(--wzl-font-weight-medium);
384
+ font-size: var(--wzl-font-size-sm);
385
+ line-height: var(--wzl-leading-tight);
378
386
  cursor: pointer;
379
387
  backdrop-filter: none;
380
388
  -webkit-backdrop-filter: none;
@@ -391,12 +399,12 @@
391
399
 
392
400
  .lk-property-row__toggle-button--selected {
393
401
  background: var(--wzl-accent);
394
- color: #0a0a14;
402
+ color: var(--wzl-fg-on-accent);
395
403
  font-weight: var(--wzl-font-weight-medium);
396
404
  }
397
405
 
398
406
  .lk-property-row__toggle-button--selected:hover {
399
- color: #0a0a14;
407
+ color: var(--wzl-fg-on-accent);
400
408
  }
401
409
 
402
410
  // ── Subpanel ────────────────────────────────────────────────────────
@@ -420,12 +428,12 @@
420
428
  }
421
429
  .lk-subpanel__title {
422
430
  font-family: var(--wzl-font-display);
423
- font-weight: 400;
424
- font-size: 0.72rem;
425
- line-height: 1;
431
+ font-weight: var(--wzl-font-weight-medium);
432
+ font-size: var(--wzl-font-size-sm);
433
+ line-height: var(--wzl-leading-tight);
426
434
  color: var(--wzl-fg);
427
435
  text-transform: uppercase;
428
- letter-spacing: 0.08em;
436
+ letter-spacing: var(--wzl-tracking-wider);
429
437
  margin: 0;
430
438
  padding: 6px 0 4px;
431
439
  display: flex;
@@ -508,24 +516,27 @@
508
516
  min-width: 14px;
509
517
  padding: 0 2px;
510
518
  color: var(--lk-panel-accent, var(--wzl-fg-muted));
511
- font-size: 14px;
512
- font-weight: 700;
513
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
519
+ font-size: var(--wzl-font-size);
520
+ font-weight: var(--wzl-font-weight-bold);
521
+ font-family: var(--wzl-font-mono);
514
522
  line-height: 1;
515
523
  }
516
524
 
517
525
  .lk-effect-card__title {
518
526
  font-family: var(--wzl-font-display);
519
- font-weight: 500;
520
- font-size: 0.9rem;
521
- line-height: 1;
527
+ font-weight: var(--wzl-font-weight-medium);
528
+ font-size: var(--wzl-font-size);
529
+ line-height: var(--wzl-leading-tight);
522
530
  color: var(--wzl-fg);
523
531
  text-transform: uppercase;
524
532
  letter-spacing: 0.04em;
525
533
  }
526
534
 
527
535
  .lk-effect-card__primary {
528
- font: 400 0.75rem/1 var(--wzl-font-ui);
536
+ font-family: var(--wzl-font-ui);
537
+ font-weight: var(--wzl-font-weight-medium);
538
+ font-size: var(--wzl-font-size-sm);
539
+ line-height: var(--wzl-leading-tight);
529
540
  color: var(--wzl-fg);
530
541
  background: rgba(0, 0, 0, 0.3);
531
542
  border: 1px solid var(--wzl-border);
@@ -547,19 +558,19 @@
547
558
  background: transparent;
548
559
  color: var(--wzl-fg-muted);
549
560
  border: 1px solid transparent;
550
- border-radius: 4px;
561
+ border-radius: var(--wzl-radius-sm);
551
562
  width: 22px;
552
563
  height: 22px;
553
- font-size: 0.85rem;
564
+ font-size: var(--wzl-font-size);
554
565
  cursor: pointer;
555
566
  display: inline-flex;
556
567
  align-items: center;
557
568
  justify-content: center;
558
569
  }
559
570
  .lk-effect-card__remove:hover {
560
- color: #ffb3a8;
561
- border-color: rgba(240, 68, 56, 0.4);
562
- background: rgba(240, 68, 56, 0.08);
571
+ color: var(--wzl-danger-fg, var(--wzl-danger));
572
+ border-color: color-mix(in srgb, var(--wzl-danger) 40%, transparent);
573
+ background: color-mix(in srgb, var(--wzl-danger) 8%, transparent);
563
574
  }
564
575
 
565
576
  .lk-effect-card__body {
@@ -578,12 +589,15 @@
578
589
  .lk-effect-card-list__drop-hint {
579
590
  height: 0;
580
591
  border-top: 2px solid var(--wzl-accent);
581
- border-radius: 2px;
592
+ border-radius: var(--wzl-radius-sm);
582
593
  margin: 2px 0;
583
594
  box-shadow: 0 0 6px color-mix(in srgb, var(--wzl-accent) 60%, transparent);
584
595
  }
585
596
  .lk-effect-card-list__empty {
586
- font: var(--wzl-font-weight-light) 0.78rem/1.4 var(--wzl-font-display);
597
+ font-family: var(--wzl-font-display);
598
+ font-weight: var(--wzl-font-weight-light);
599
+ font-size: var(--wzl-font-size-sm);
600
+ line-height: var(--wzl-leading);
587
601
  color: var(--wzl-fg-muted);
588
602
  text-align: center;
589
603
  padding: 16px 12px;
@@ -299,7 +299,8 @@ export const RightSidebarTails: Story = {
299
299
  const next = prev.filter((t) => t.id !== sourceId);
300
300
  const idx = next.findIndex((t) => t.id === targetId);
301
301
  const insertAt = position === 'before' ? idx : idx + 1;
302
- const src = prev.find((t) => t.id === sourceId)!;
302
+ const src = prev.find((t) => t.id === sourceId);
303
+ if (!src) return prev;
303
304
  next.splice(insertAt, 0, src);
304
305
  return next;
305
306
  });
@@ -348,7 +349,8 @@ export const LayerStack: Story = {
348
349
  const next = prev.filter((t) => t.id !== sourceId);
349
350
  const idx = next.findIndex((t) => t.id === targetId);
350
351
  const insertAt = position === 'before' ? idx : idx + 1;
351
- const src = prev.find((t) => t.id === sourceId)!;
352
+ const src = prev.find((t) => t.id === sourceId);
353
+ if (!src) return prev;
352
354
  next.splice(insertAt, 0, src);
353
355
  return next;
354
356
  });
@@ -7,10 +7,10 @@ import type { ReactNode } from 'react';
7
7
  */
8
8
  export function SideBySide({ block, inline }: { block: ReactNode; inline: ReactNode }) {
9
9
  const labelStyle = {
10
- font: '300 0.72rem/1 Oswald, system-ui',
10
+ font: 'var(--wzl-font-weight-normal) var(--wzl-font-size-sm)/var(--wzl-leading-tight) var(--wzl-font-ui)',
11
11
  color: 'var(--wzl-fg-muted)',
12
12
  textTransform: 'uppercase' as const,
13
- letterSpacing: '0.08em',
13
+ letterSpacing: 'var(--wzl-tracking-wider)',
14
14
  margin: '0 0 8px',
15
15
  };
16
16
  return (