@zylem/ui 0.8.9 → 0.8.12

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 (61) hide show
  1. package/README.md +3 -0
  2. package/dist/styles.css +1 -1
  3. package/package.json +5 -1
  4. package/src/components/Accordion/Accordion.tsx +10 -1
  5. package/src/components/AddTile/AddTile.css.ts +17 -0
  6. package/src/components/AddTile/AddTile.tsx +15 -7
  7. package/src/components/Callout/Callout.css.ts +45 -0
  8. package/src/components/Callout/Callout.tsx +32 -0
  9. package/src/components/Card/Card.css.ts +18 -0
  10. package/src/components/Card/Card.tsx +25 -2
  11. package/src/components/Dialog/Dialog.css.ts +69 -2
  12. package/src/components/Dialog/Dialog.tsx +35 -7
  13. package/src/components/DropdownMenu/DropdownMenu.css.ts +22 -0
  14. package/src/components/DropdownMenu/DropdownMenu.tsx +67 -20
  15. package/src/components/EmptyState/EmptyState.css.ts +9 -0
  16. package/src/components/EmptyState/EmptyState.tsx +3 -1
  17. package/src/components/FrameStrip/FrameStrip.css.ts +6 -0
  18. package/src/components/FrameStrip/FrameStrip.tsx +30 -2
  19. package/src/components/Menubar/Menubar.css.ts +52 -0
  20. package/src/components/Menubar/Menubar.tsx +77 -0
  21. package/src/components/Mixer/Knob.tsx +188 -0
  22. package/src/components/Mixer/Mixer.css.ts +246 -0
  23. package/src/components/Mixer/MixerStrip.tsx +150 -0
  24. package/src/components/NumberField/NumberField.tsx +5 -1
  25. package/src/components/Panel/Panel.tsx +6 -5
  26. package/src/components/PianoRoll/PianoRoll.css.ts +224 -0
  27. package/src/components/PianoRoll/PianoRoll.tsx +353 -0
  28. package/src/components/PianoRoll/piano-roll-math.ts +89 -0
  29. package/src/components/Property/Property.css.ts +1 -1
  30. package/src/components/Property/Property.tsx +3 -0
  31. package/src/components/Sidebar/Sidebar.css.ts +8 -0
  32. package/src/components/Sidebar/Sidebar.tsx +10 -2
  33. package/src/components/Slider/Slider.tsx +6 -0
  34. package/src/components/SpriteSizePicker/SpriteSizePicker.tsx +15 -4
  35. package/src/components/Tabs/Tabs.css.ts +48 -0
  36. package/src/components/Tabs/Tabs.tsx +41 -3
  37. package/src/components/TextField/TextField.tsx +49 -5
  38. package/src/components/TimelineLanes/TimelineLanes.css.ts +365 -0
  39. package/src/components/TimelineLanes/TimelineLanes.tsx +509 -0
  40. package/src/components/TimelineLanes/lanes-math.ts +182 -0
  41. package/src/components/Toast/Toast.css.ts +48 -0
  42. package/src/components/Toast/Toast.tsx +37 -0
  43. package/src/components/TrackHeader/TrackHeader.css.ts +162 -0
  44. package/src/components/TrackHeader/TrackHeader.tsx +145 -0
  45. package/src/components/TransportBar/TransportBar.css.ts +65 -0
  46. package/src/components/TransportBar/TransportBar.tsx +154 -0
  47. package/src/components/Waveform/Waveform.css.ts +77 -0
  48. package/src/components/Waveform/Waveform.tsx +195 -0
  49. package/src/components/Waveform/peaks.ts +49 -0
  50. package/src/components/index.ts +72 -1
  51. package/src/global/accordion.css.ts +23 -0
  52. package/src/icons/create-icon.tsx +71 -0
  53. package/src/icons/icon-nodes.ts +75 -0
  54. package/src/icons/index.ts +54 -0
  55. package/src/icons/svg/camera-isometric.svg +16 -0
  56. package/src/icons/svg/free-perspective.svg +8 -0
  57. package/src/icons/svg/game-camera.svg +6 -0
  58. package/src/icons/svg/perspective.svg +5 -0
  59. package/src/icons/svg/sideview-perspective.svg +5 -0
  60. package/src/icons/svg/top-down-perspective.svg +5 -0
  61. package/src/styles.ts +11 -0
@@ -126,6 +126,12 @@ globalStyle('.zylem-frame-actions-spacer', {
126
126
  flex: '1 1 auto',
127
127
  });
128
128
 
129
+ globalStyle('.zylem-frame-actions-group', {
130
+ display: 'inline-flex',
131
+ alignItems: 'center',
132
+ gap: vars.spacing.xs,
133
+ });
134
+
129
135
  globalStyle('.zylem-frame-actions-step', {
130
136
  width: '34px',
131
137
  padding: 0,
@@ -1,3 +1,5 @@
1
+ import ArrowLeftToLine from 'lucide-solid/icons/arrow-left-to-line';
2
+ import ArrowRightToLine from 'lucide-solid/icons/arrow-right-to-line';
1
3
  import ChevronLeft from 'lucide-solid/icons/chevron-left';
2
4
  import ChevronRight from 'lucide-solid/icons/chevron-right';
3
5
  import Copy from 'lucide-solid/icons/copy';
@@ -67,18 +69,24 @@ export function FrameThumb(props: FrameThumbProps) {
67
69
  export interface FrameActionsProps {
68
70
  onDuplicate?: () => void;
69
71
  onDelete?: () => void;
72
+ /** Reorder: swap the selected frame with its left neighbour. Shown when set. */
73
+ onMoveLeft?: () => void;
74
+ /** Reorder: swap the selected frame with its right neighbour. Shown when set. */
75
+ onMoveRight?: () => void;
70
76
  onPrev?: () => void;
71
77
  onNext?: () => void;
72
78
  canDuplicate?: boolean;
73
79
  canDelete?: boolean;
80
+ canMoveLeft?: boolean;
81
+ canMoveRight?: boolean;
74
82
  canPrev?: boolean;
75
83
  canNext?: boolean;
76
84
  class?: string;
77
85
  }
78
86
 
79
87
  /**
80
- * Frame commands under the strip: Duplicate and Delete, then step arrows
81
- * whose chevrons are centered in square toolbar buttons.
88
+ * Frame commands under the strip: Duplicate and Delete, the optional reorder
89
+ * pair, then step arrows whose chevrons are centered in square toolbar buttons.
82
90
  */
83
91
  export function FrameActions(props: FrameActionsProps) {
84
92
  return (
@@ -96,6 +104,26 @@ export function FrameActions(props: FrameActionsProps) {
96
104
  <Trash2 class="zylem-icon" aria-hidden="true" />
97
105
  Delete
98
106
  </Button>
107
+ <Show when={props.onMoveLeft || props.onMoveRight}>
108
+ <span class="zylem-frame-actions-group" role="group" aria-label="Reorder frame">
109
+ <ToolbarButton
110
+ label="Move frame left"
111
+ disabled={props.canMoveLeft === false}
112
+ onClick={() => props.onMoveLeft?.()}
113
+ class="zylem-frame-actions-step"
114
+ >
115
+ <ArrowLeftToLine class="zylem-icon" />
116
+ </ToolbarButton>
117
+ <ToolbarButton
118
+ label="Move frame right"
119
+ disabled={props.canMoveRight === false}
120
+ onClick={() => props.onMoveRight?.()}
121
+ class="zylem-frame-actions-step"
122
+ >
123
+ <ArrowRightToLine class="zylem-icon" />
124
+ </ToolbarButton>
125
+ </span>
126
+ </Show>
99
127
  <span class="zylem-frame-actions-spacer" />
100
128
  <ToolbarButton
101
129
  label="Previous frame"
@@ -0,0 +1,52 @@
1
+ import { globalStyle } from '@vanilla-extract/css';
2
+ import { vars } from '../../theme.css';
3
+
4
+ globalStyle('.zylem-menubar', {
5
+ display: 'flex',
6
+ alignItems: 'center',
7
+ gap: '2px',
8
+ minWidth: 0,
9
+ });
10
+
11
+ // Flat chrome (see DESIGN.md): transparent at rest, a tint on hover and a
12
+ // stronger tint while the menu is open. No gloss, so the bar's glossy title
13
+ // surfaces and primary buttons stay the focal points.
14
+ globalStyle('.zylem-menubar-trigger', {
15
+ display: 'inline-flex',
16
+ alignItems: 'center',
17
+ gap: vars.spacing.xs,
18
+ padding: `${vars.spacing.xs} ${vars.spacing.sm}`,
19
+ border: '1px solid transparent',
20
+ borderRadius: vars.radii.button,
21
+ background: 'transparent',
22
+ color: vars.colors.textSecondary,
23
+ fontFamily: vars.typography.fontFamily,
24
+ fontSize: `calc(${vars.typography.fontSize} - 1px)`,
25
+ fontWeight: 600,
26
+ letterSpacing: '0.02em',
27
+ lineHeight: 1.2,
28
+ cursor: 'pointer',
29
+ userSelect: 'none',
30
+ outline: 'none',
31
+ transition: `background ${vars.motion.fast} ${vars.motion.easeOut}, color ${vars.motion.fast} ${vars.motion.easeOut}`,
32
+ });
33
+
34
+ globalStyle('.zylem-menubar-trigger:hover', {
35
+ background: 'rgba(97, 166, 232, 0.12)',
36
+ color: vars.colors.text,
37
+ });
38
+
39
+ globalStyle('.zylem-menubar-trigger[data-expanded]', {
40
+ background: 'rgba(97, 166, 232, 0.22)',
41
+ borderColor: 'rgba(97, 166, 232, 0.35)',
42
+ color: '#F4FAFF',
43
+ });
44
+
45
+ globalStyle('.zylem-menubar-trigger:focus-visible', {
46
+ boxShadow: vars.effects.focusRing,
47
+ });
48
+
49
+ globalStyle('.zylem-menubar-trigger[data-disabled]', {
50
+ opacity: 0.45,
51
+ cursor: 'not-allowed',
52
+ });
@@ -0,0 +1,77 @@
1
+ import { Menubar as KMenubar } from '@kobalte/core';
2
+ import { For, Show } from 'solid-js';
3
+ import type { JSX } from 'solid-js';
4
+ import { createDismissGuard, useLayer } from '../../layers';
5
+ import { MenuItems, type DropdownMenuItemConfig } from '../DropdownMenu/DropdownMenu';
6
+
7
+ export interface MenubarMenuConfig {
8
+ id: string;
9
+ label: JSX.Element;
10
+ /**
11
+ * Rows of the menu. A function is read each time the menu renders, so a
12
+ * menu whose rows depend on app state (the open stages, the dirty flag) can
13
+ * hand over its accessor instead of rebuilding the whole bar on every change.
14
+ */
15
+ items: DropdownMenuItemConfig[] | (() => DropdownMenuItemConfig[]);
16
+ disabled?: boolean;
17
+ }
18
+
19
+ export interface MenubarProps {
20
+ menus: MenubarMenuConfig[];
21
+ class?: string;
22
+ 'aria-label'?: string;
23
+ /** Class applied to every trigger button. */
24
+ triggerClass?: string;
25
+ }
26
+
27
+ /**
28
+ * Desktop-style menu bar: a row of flat text triggers, one open at a time.
29
+ * Moving the pointer onto a sibling trigger while a menu is open swaps menus,
30
+ * and the arrow keys walk the bar — behaviour that comes from Kobalte's
31
+ * Menubar. Each menu is a `DropdownMenu` sheet, portaled at the `menu` tier.
32
+ */
33
+ export function Menubar(props: MenubarProps) {
34
+ const layer = useLayer('menu');
35
+
36
+ return (
37
+ <KMenubar.Root
38
+ ref={layer.anchorRef}
39
+ class={props.class ? `zylem-menubar ${props.class}` : 'zylem-menubar'}
40
+ aria-label={props['aria-label']}
41
+ >
42
+ <For each={props.menus}>
43
+ {(menu) => {
44
+ const dismiss = createDismissGuard();
45
+ const items = () => (typeof menu.items === 'function' ? menu.items() : menu.items);
46
+ return (
47
+ <KMenubar.Menu>
48
+ <KMenubar.Trigger
49
+ class={
50
+ props.triggerClass
51
+ ? `zylem-menubar-trigger ${props.triggerClass}`
52
+ : 'zylem-menubar-trigger'
53
+ }
54
+ disabled={menu.disabled}
55
+ >
56
+ {menu.label}
57
+ </KMenubar.Trigger>
58
+ <Show when={layer.mount()} keyed>
59
+ {(mount) => (
60
+ <KMenubar.Portal mount={mount}>
61
+ <KMenubar.Content
62
+ ref={dismiss.ref}
63
+ class="zylem-dropdown-content zylem-menubar-content"
64
+ onInteractOutside={dismiss.onInteractOutside}
65
+ >
66
+ <MenuItems items={items()} />
67
+ </KMenubar.Content>
68
+ </KMenubar.Portal>
69
+ )}
70
+ </Show>
71
+ </KMenubar.Menu>
72
+ );
73
+ }}
74
+ </For>
75
+ </KMenubar.Root>
76
+ );
77
+ }
@@ -0,0 +1,188 @@
1
+ import { createSignal, Show } from 'solid-js';
2
+ import { clamp } from '../TimelineLanes/lanes-math';
3
+
4
+ export interface KnobProps {
5
+ value: number;
6
+ onChange?: (value: number) => void;
7
+ /** Fires once when a drag ends or a key edit lands. */
8
+ onCommit?: (value: number) => void;
9
+ min?: number;
10
+ max?: number;
11
+ step?: number;
12
+ /** Double-click resets to this. */
13
+ defaultValue?: number;
14
+ label?: string;
15
+ /** Readout under the knob; defaults to the value with `step` precision. */
16
+ format?: (value: number) => string;
17
+ /** Draw the arc from the centre (for pan and other ±ranges). */
18
+ bipolar?: boolean;
19
+ size?: number;
20
+ disabled?: boolean;
21
+ class?: string;
22
+ }
23
+
24
+ const SWEEP = 270;
25
+ const START_ANGLE = -135;
26
+
27
+ function polar(cx: number, cy: number, radius: number, degrees: number) {
28
+ const radians = ((degrees - 90) * Math.PI) / 180;
29
+ return { x: cx + radius * Math.cos(radians), y: cy + radius * Math.sin(radians) };
30
+ }
31
+
32
+ /** SVG arc path from `from` to `to` degrees (clockwise, 12 o'clock = 0). */
33
+ export function arcPath(cx: number, cy: number, radius: number, from: number, to: number): string {
34
+ const [a, b] = from <= to ? [from, to] : [to, from];
35
+ const start = polar(cx, cy, radius, a);
36
+ const end = polar(cx, cy, radius, b);
37
+ const large = b - a > 180 ? 1 : 0;
38
+ return `M ${start.x.toFixed(3)} ${start.y.toFixed(3)} A ${radius} ${radius} 0 ${large} 1 ${end.x.toFixed(3)} ${end.y.toFixed(3)}`;
39
+ }
40
+
41
+ /**
42
+ * Rotary control: drag vertically (Shift for fine steps), arrow keys to
43
+ * step, double-click to reset. The arc fills from the minimum, or from the
44
+ * centre when `bipolar`.
45
+ */
46
+ export function Knob(props: KnobProps) {
47
+ const min = () => props.min ?? 0;
48
+ const max = () => props.max ?? 1;
49
+ const step = () => props.step ?? (max() - min()) / 100;
50
+ const size = () => props.size ?? 36;
51
+ const ratio = () => (max() > min() ? clamp((props.value - min()) / (max() - min()), 0, 1) : 0);
52
+ const angle = () => START_ANGLE + ratio() * SWEEP;
53
+ const zeroAngle = () =>
54
+ START_ANGLE + clamp((0 - min()) / (max() - min() || 1), 0, 1) * SWEEP;
55
+ const [dragging, setDragging] = createSignal(false);
56
+
57
+ const quantize = (value: number) => {
58
+ const s = step();
59
+ const snapped = s > 0 ? Math.round((value - min()) / s) * s + min() : value;
60
+ return clamp(Number(snapped.toFixed(6)), min(), max());
61
+ };
62
+ const readout = () => {
63
+ if (props.format) return props.format(props.value);
64
+ const decimals = Math.max(0, Math.min(3, -Math.floor(Math.log10(step() || 1))));
65
+ return props.value.toFixed(decimals);
66
+ };
67
+
68
+ const beginDrag = (event: PointerEvent) => {
69
+ if (props.disabled || event.button !== 0) return;
70
+ event.preventDefault();
71
+ const target = event.currentTarget as HTMLElement;
72
+ target.focus({ preventScroll: true });
73
+ target.setPointerCapture?.(event.pointerId);
74
+ const originY = event.clientY;
75
+ const originValue = props.value;
76
+ let last = originValue;
77
+ setDragging(true);
78
+ const move = (moveEvent: PointerEvent) => {
79
+ const pixels = moveEvent.shiftKey ? 600 : 150;
80
+ const delta = ((originY - moveEvent.clientY) / pixels) * (max() - min());
81
+ last = quantize(originValue + delta);
82
+ props.onChange?.(last);
83
+ };
84
+ const stop = () => {
85
+ target.removeEventListener('pointermove', move);
86
+ target.removeEventListener('pointerup', stop);
87
+ target.removeEventListener('pointercancel', stop);
88
+ target.releasePointerCapture?.(event.pointerId);
89
+ setDragging(false);
90
+ props.onCommit?.(last);
91
+ };
92
+ target.addEventListener('pointermove', move);
93
+ target.addEventListener('pointerup', stop);
94
+ target.addEventListener('pointercancel', stop);
95
+ };
96
+
97
+ const nudge = (direction: number, big: boolean) => {
98
+ const next = quantize(props.value + direction * step() * (big ? 10 : 1));
99
+ props.onChange?.(next);
100
+ props.onCommit?.(next);
101
+ };
102
+
103
+ const handleKeyDown = (event: KeyboardEvent) => {
104
+ if (props.disabled) return;
105
+ switch (event.key) {
106
+ case 'ArrowUp':
107
+ case 'ArrowRight':
108
+ event.preventDefault();
109
+ nudge(1, event.shiftKey);
110
+ break;
111
+ case 'ArrowDown':
112
+ case 'ArrowLeft':
113
+ event.preventDefault();
114
+ nudge(-1, event.shiftKey);
115
+ break;
116
+ case 'Home':
117
+ event.preventDefault();
118
+ props.onChange?.(min());
119
+ props.onCommit?.(min());
120
+ break;
121
+ case 'End':
122
+ event.preventDefault();
123
+ props.onChange?.(max());
124
+ props.onCommit?.(max());
125
+ break;
126
+ default:
127
+ }
128
+ };
129
+
130
+ const reset = () => {
131
+ if (props.disabled || props.defaultValue === undefined) return;
132
+ const next = quantize(props.defaultValue);
133
+ props.onChange?.(next);
134
+ props.onCommit?.(next);
135
+ };
136
+
137
+ const radius = () => size() / 2 - 3;
138
+ const center = () => size() / 2;
139
+
140
+ return (
141
+ <div
142
+ class={props.class ? `zylem-knob ${props.class}` : 'zylem-knob'}
143
+ data-disabled={props.disabled ? '' : undefined}
144
+ data-dragging={dragging() ? '' : undefined}
145
+ >
146
+ <div
147
+ class="zylem-knob-dial"
148
+ role="slider"
149
+ tabIndex={props.disabled ? -1 : 0}
150
+ aria-label={props.label}
151
+ aria-valuemin={min()}
152
+ aria-valuemax={max()}
153
+ aria-valuenow={props.value}
154
+ aria-valuetext={readout()}
155
+ aria-disabled={props.disabled ? 'true' : undefined}
156
+ style={{ width: `${size()}px`, height: `${size()}px` }}
157
+ onPointerDown={beginDrag}
158
+ onKeyDown={handleKeyDown}
159
+ onDblClick={reset}
160
+ >
161
+ <svg viewBox={`0 0 ${size()} ${size()}`} width={size()} height={size()} aria-hidden="true">
162
+ <path
163
+ class="zylem-knob-track"
164
+ d={arcPath(center(), center(), radius(), START_ANGLE, START_ANGLE + SWEEP)}
165
+ />
166
+ <path
167
+ class="zylem-knob-fill"
168
+ d={arcPath(center(), center(), radius(), props.bipolar ? zeroAngle() : START_ANGLE, angle())}
169
+ />
170
+ <g transform={`rotate(${angle()} ${center()} ${center()})`}>
171
+ <circle class="zylem-knob-cap" cx={center()} cy={center()} r={radius() - 4} />
172
+ <line
173
+ class="zylem-knob-pointer"
174
+ x1={center()}
175
+ y1={center() - radius() + 6}
176
+ x2={center()}
177
+ y2={center() - radius() / 2}
178
+ />
179
+ </g>
180
+ </svg>
181
+ </div>
182
+ <Show when={props.label}>
183
+ <span class="zylem-knob-label">{props.label}</span>
184
+ </Show>
185
+ <span class="zylem-knob-value">{readout()}</span>
186
+ </div>
187
+ );
188
+ }
@@ -0,0 +1,246 @@
1
+ import { globalStyle } from '@vanilla-extract/css';
2
+ import { vars } from '../../theme.css';
3
+
4
+ // Knob -----------------------------------------------------------------------
5
+
6
+ globalStyle('.zylem-knob', {
7
+ display: 'inline-flex',
8
+ flexDirection: 'column',
9
+ alignItems: 'center',
10
+ gap: '2px',
11
+ fontFamily: vars.typography.fontFamily,
12
+ color: vars.colors.text,
13
+ userSelect: 'none',
14
+ });
15
+
16
+ globalStyle('.zylem-knob[data-disabled]', {
17
+ opacity: 0.5,
18
+ });
19
+
20
+ globalStyle('.zylem-knob-dial', {
21
+ display: 'block',
22
+ borderRadius: '999px',
23
+ cursor: 'ns-resize',
24
+ outline: 'none',
25
+ touchAction: 'none',
26
+ });
27
+
28
+ globalStyle('.zylem-knob[data-dragging] .zylem-knob-dial', {
29
+ cursor: 'grabbing',
30
+ });
31
+
32
+ globalStyle('.zylem-knob-dial:focus-visible', {
33
+ boxShadow: vars.effects.focusRing,
34
+ });
35
+
36
+ globalStyle('.zylem-knob-dial svg', {
37
+ display: 'block',
38
+ overflow: 'visible',
39
+ });
40
+
41
+ globalStyle('.zylem-knob-track', {
42
+ fill: 'none',
43
+ stroke: 'rgba(8, 19, 31, 0.9)',
44
+ strokeWidth: 3,
45
+ strokeLinecap: 'round',
46
+ });
47
+
48
+ globalStyle('.zylem-knob-fill', {
49
+ fill: 'none',
50
+ stroke: 'rgba(120, 190, 255, 0.95)',
51
+ strokeWidth: 3,
52
+ strokeLinecap: 'round',
53
+ filter: 'drop-shadow(0 0 3px rgba(97, 166, 232, 0.7))',
54
+ });
55
+
56
+ globalStyle('.zylem-knob-cap', {
57
+ fill: 'rgba(40, 70, 105, 1)',
58
+ stroke: 'rgba(178, 219, 255, 0.5)',
59
+ strokeWidth: 1,
60
+ });
61
+
62
+ globalStyle('.zylem-knob-pointer', {
63
+ stroke: 'rgba(240, 250, 255, 0.98)',
64
+ strokeWidth: 2,
65
+ strokeLinecap: 'round',
66
+ });
67
+
68
+ globalStyle('.zylem-knob-label', {
69
+ fontSize: '9px',
70
+ letterSpacing: '0.08em',
71
+ textTransform: 'uppercase',
72
+ color: vars.colors.textSecondary,
73
+ });
74
+
75
+ globalStyle('.zylem-knob-value', {
76
+ fontSize: '10px',
77
+ fontVariantNumeric: 'tabular-nums',
78
+ color: vars.colors.text,
79
+ });
80
+
81
+ // Mixer strip ----------------------------------------------------------------
82
+
83
+ globalStyle('.zylem-mixer-strip', {
84
+ boxSizing: 'border-box',
85
+ display: 'flex',
86
+ flexDirection: 'column',
87
+ alignItems: 'center',
88
+ gap: vars.spacing.xs,
89
+ width: '64px',
90
+ padding: `${vars.spacing.sm} ${vars.spacing.xs}`,
91
+ fontFamily: vars.typography.fontFamily,
92
+ color: vars.colors.text,
93
+ background: vars.material.glassPanelDark,
94
+ border: '1px solid rgba(97, 166, 232, 0.2)',
95
+ borderTop: '3px solid var(--zylem-track-color)',
96
+ borderRadius: vars.radii.control,
97
+ boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.06)',
98
+ userSelect: 'none',
99
+ });
100
+
101
+ globalStyle('.zylem-mixer-strip[data-master]', {
102
+ width: '76px',
103
+ background: vars.material.glassPanel,
104
+ });
105
+
106
+ globalStyle('.zylem-mixer-strip[data-selected]', {
107
+ boxShadow: `inset 0 1px 0 rgba(255,255,255,0.06), 0 0 0 1px rgba(178, 219, 255, 0.6), ${vars.effects.glowPrimary}`,
108
+ });
109
+
110
+ globalStyle('.zylem-mixer-strip[data-disabled]', {
111
+ opacity: 0.6,
112
+ });
113
+
114
+ globalStyle('.zylem-mixer-fader-row', {
115
+ display: 'flex',
116
+ alignItems: 'stretch',
117
+ gap: '4px',
118
+ height: '132px',
119
+ });
120
+
121
+ globalStyle('.zylem-fader', {
122
+ position: 'relative',
123
+ display: 'flex',
124
+ flexDirection: 'column',
125
+ width: '24px',
126
+ height: '100%',
127
+ touchAction: 'none',
128
+ });
129
+
130
+ globalStyle('.zylem-fader[data-disabled]', {
131
+ opacity: 0.5,
132
+ });
133
+
134
+ globalStyle('.zylem-fader-sr', {
135
+ position: 'absolute',
136
+ width: '1px',
137
+ height: '1px',
138
+ padding: 0,
139
+ margin: '-1px',
140
+ overflow: 'hidden',
141
+ clip: 'rect(0, 0, 0, 0)',
142
+ whiteSpace: 'nowrap',
143
+ border: 0,
144
+ });
145
+
146
+ globalStyle('.zylem-fader-track', {
147
+ position: 'relative',
148
+ flex: '1 1 auto',
149
+ width: '8px',
150
+ margin: '0 auto',
151
+ borderRadius: '999px',
152
+ border: '1px solid rgba(97, 166, 232, 0.28)',
153
+ background: vars.material.fieldGlass,
154
+ boxShadow: vars.effects.shadowInset,
155
+ cursor: 'pointer',
156
+ });
157
+
158
+ globalStyle('.zylem-fader-fill', {
159
+ position: 'absolute',
160
+ bottom: 0,
161
+ width: '100%',
162
+ borderRadius: '999px',
163
+ background:
164
+ 'linear-gradient(0deg, rgba(48, 137, 220, 0.8), rgba(178, 219, 255, 0.55))',
165
+ });
166
+
167
+ globalStyle('.zylem-fader-zero', {
168
+ position: 'absolute',
169
+ left: '-6px',
170
+ right: '-6px',
171
+ bottom: 'calc(100% * 60 / 66)',
172
+ height: '1px',
173
+ background: 'rgba(205, 232, 255, 0.4)',
174
+ pointerEvents: 'none',
175
+ });
176
+
177
+ globalStyle('.zylem-fader-thumb', {
178
+ display: 'block',
179
+ left: '-7px',
180
+ width: '22px',
181
+ height: '12px',
182
+ borderRadius: '4px',
183
+ border: '1px solid rgba(255,255,255,0.55)',
184
+ background:
185
+ 'linear-gradient(180deg, rgba(240,250,255,0.96), rgba(140,190,235,0.92))',
186
+ boxShadow: `inset 0 1px 0 rgba(255,255,255,0.75), inset 0 -3px 0 rgba(0,0,0,0.18), 0 1px 4px rgba(0,0,0,0.5)`,
187
+ cursor: 'grab',
188
+ outline: 'none',
189
+ });
190
+
191
+ globalStyle('.zylem-fader-thumb:active', {
192
+ cursor: 'grabbing',
193
+ });
194
+
195
+ globalStyle('.zylem-fader-thumb:focus-visible', {
196
+ boxShadow: `inset 0 1px 0 rgba(255,255,255,0.75), ${vars.effects.focusRing}`,
197
+ });
198
+
199
+ globalStyle('.zylem-mixer-meter', {
200
+ position: 'relative',
201
+ width: '6px',
202
+ borderRadius: '3px',
203
+ background: 'rgba(8, 19, 31, 0.9)',
204
+ border: '1px solid rgba(97, 166, 232, 0.2)',
205
+ overflow: 'hidden',
206
+ });
207
+
208
+ globalStyle('.zylem-mixer-meter-fill', {
209
+ position: 'absolute',
210
+ bottom: 0,
211
+ left: 0,
212
+ right: 0,
213
+ background:
214
+ 'linear-gradient(0deg, rgba(28, 174, 63, 0.95) 0%, rgba(170, 255, 190, 0.95) 65%, rgba(255, 220, 130, 0.95) 85%, rgba(230, 69, 52, 0.95) 100%)',
215
+ transition: `height 60ms linear`,
216
+ });
217
+
218
+ globalStyle('.zylem-mixer-readout', {
219
+ fontSize: '10px',
220
+ fontVariantNumeric: 'tabular-nums',
221
+ color: vars.colors.textSecondary,
222
+ whiteSpace: 'nowrap',
223
+ });
224
+
225
+ globalStyle('.zylem-mixer-toggles', {
226
+ display: 'flex',
227
+ gap: '3px',
228
+ });
229
+
230
+ globalStyle('.zylem-mixer-name', {
231
+ maxWidth: '100%',
232
+ fontSize: '11px',
233
+ fontWeight: 600,
234
+ whiteSpace: 'nowrap',
235
+ overflow: 'hidden',
236
+ textOverflow: 'ellipsis',
237
+ });
238
+
239
+ globalStyle('.zylem-mixer-strip[data-muted] .zylem-mixer-name', {
240
+ color: vars.colors.textSecondary,
241
+ });
242
+
243
+ globalStyle('.zylem-mixer-strip[data-master] .zylem-mixer-name', {
244
+ letterSpacing: '0.08em',
245
+ textTransform: 'uppercase',
246
+ });