@stevejtrettel/shader-sandbox 0.1.3 → 0.1.4

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 (113) hide show
  1. package/README.md +220 -23
  2. package/bin/cli.js +106 -14
  3. package/dist-lib/app/App.d.ts +143 -15
  4. package/dist-lib/app/App.d.ts.map +1 -1
  5. package/dist-lib/app/App.js +1343 -108
  6. package/dist-lib/app/app.css +349 -24
  7. package/dist-lib/app/types.d.ts +48 -5
  8. package/dist-lib/app/types.d.ts.map +1 -1
  9. package/dist-lib/editor/EditorPanel.d.ts +2 -2
  10. package/dist-lib/editor/EditorPanel.d.ts.map +1 -1
  11. package/dist-lib/editor/EditorPanel.js +1 -1
  12. package/dist-lib/editor/editor-panel.css +55 -32
  13. package/dist-lib/editor/prism-editor.css +16 -16
  14. package/dist-lib/embed.js +1 -1
  15. package/dist-lib/engine/{ShadertoyEngine.d.ts → ShaderEngine.d.ts} +134 -10
  16. package/dist-lib/engine/ShaderEngine.d.ts.map +1 -0
  17. package/dist-lib/engine/ShaderEngine.js +1523 -0
  18. package/dist-lib/engine/glHelpers.d.ts +24 -0
  19. package/dist-lib/engine/glHelpers.d.ts.map +1 -1
  20. package/dist-lib/engine/glHelpers.js +88 -0
  21. package/dist-lib/engine/std140.d.ts +47 -0
  22. package/dist-lib/engine/std140.d.ts.map +1 -0
  23. package/dist-lib/engine/std140.js +119 -0
  24. package/dist-lib/engine/types.d.ts +55 -5
  25. package/dist-lib/engine/types.d.ts.map +1 -1
  26. package/dist-lib/engine/types.js +1 -1
  27. package/dist-lib/index.d.ts +4 -3
  28. package/dist-lib/index.d.ts.map +1 -1
  29. package/dist-lib/index.js +2 -1
  30. package/dist-lib/layouts/SplitLayout.d.ts +2 -1
  31. package/dist-lib/layouts/SplitLayout.d.ts.map +1 -1
  32. package/dist-lib/layouts/SplitLayout.js +3 -0
  33. package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -1
  34. package/dist-lib/layouts/UILayout.d.ts +55 -0
  35. package/dist-lib/layouts/UILayout.d.ts.map +1 -0
  36. package/dist-lib/layouts/UILayout.js +147 -0
  37. package/dist-lib/layouts/default.css +2 -2
  38. package/dist-lib/layouts/index.d.ts +11 -1
  39. package/dist-lib/layouts/index.d.ts.map +1 -1
  40. package/dist-lib/layouts/index.js +17 -1
  41. package/dist-lib/layouts/split.css +33 -31
  42. package/dist-lib/layouts/tabbed.css +127 -74
  43. package/dist-lib/layouts/types.d.ts +14 -3
  44. package/dist-lib/layouts/types.d.ts.map +1 -1
  45. package/dist-lib/main.js +33 -0
  46. package/dist-lib/project/configHelpers.d.ts +45 -0
  47. package/dist-lib/project/configHelpers.d.ts.map +1 -0
  48. package/dist-lib/project/configHelpers.js +196 -0
  49. package/dist-lib/project/generatedLoader.d.ts +2 -2
  50. package/dist-lib/project/generatedLoader.d.ts.map +1 -1
  51. package/dist-lib/project/generatedLoader.js +23 -5
  52. package/dist-lib/project/loadProject.d.ts +6 -6
  53. package/dist-lib/project/loadProject.d.ts.map +1 -1
  54. package/dist-lib/project/loadProject.js +396 -144
  55. package/dist-lib/project/loaderHelper.d.ts +4 -4
  56. package/dist-lib/project/loaderHelper.d.ts.map +1 -1
  57. package/dist-lib/project/loaderHelper.js +278 -116
  58. package/dist-lib/project/types.d.ts +292 -13
  59. package/dist-lib/project/types.d.ts.map +1 -1
  60. package/dist-lib/project/types.js +13 -1
  61. package/dist-lib/styles/base.css +5 -1
  62. package/dist-lib/uniforms/UniformControls.d.ts +60 -0
  63. package/dist-lib/uniforms/UniformControls.d.ts.map +1 -0
  64. package/dist-lib/uniforms/UniformControls.js +518 -0
  65. package/dist-lib/uniforms/UniformStore.d.ts +74 -0
  66. package/dist-lib/uniforms/UniformStore.d.ts.map +1 -0
  67. package/dist-lib/uniforms/UniformStore.js +145 -0
  68. package/dist-lib/uniforms/UniformsPanel.d.ts +53 -0
  69. package/dist-lib/uniforms/UniformsPanel.d.ts.map +1 -0
  70. package/dist-lib/uniforms/UniformsPanel.js +124 -0
  71. package/dist-lib/uniforms/index.d.ts +11 -0
  72. package/dist-lib/uniforms/index.d.ts.map +1 -0
  73. package/dist-lib/uniforms/index.js +8 -0
  74. package/package.json +1 -1
  75. package/src/app/App.ts +1469 -126
  76. package/src/app/app.css +349 -24
  77. package/src/app/types.ts +53 -5
  78. package/src/editor/EditorPanel.ts +5 -5
  79. package/src/editor/editor-panel.css +55 -32
  80. package/src/editor/prism-editor.css +16 -16
  81. package/src/embed.ts +1 -1
  82. package/src/engine/ShaderEngine.ts +1934 -0
  83. package/src/engine/glHelpers.ts +117 -0
  84. package/src/engine/std140.ts +136 -0
  85. package/src/engine/types.ts +69 -5
  86. package/src/index.ts +4 -3
  87. package/src/layouts/SplitLayout.ts +8 -3
  88. package/src/layouts/TabbedLayout.ts +3 -3
  89. package/src/layouts/UILayout.ts +185 -0
  90. package/src/layouts/default.css +2 -2
  91. package/src/layouts/index.ts +20 -1
  92. package/src/layouts/split.css +33 -31
  93. package/src/layouts/tabbed.css +127 -74
  94. package/src/layouts/types.ts +19 -3
  95. package/src/layouts/ui.css +289 -0
  96. package/src/main.ts +39 -1
  97. package/src/project/configHelpers.ts +225 -0
  98. package/src/project/generatedLoader.ts +27 -6
  99. package/src/project/loadProject.ts +459 -173
  100. package/src/project/loaderHelper.ts +377 -130
  101. package/src/project/types.ts +360 -14
  102. package/src/styles/base.css +5 -1
  103. package/src/styles/theme.css +292 -0
  104. package/src/uniforms/UniformControls.ts +660 -0
  105. package/src/uniforms/UniformStore.ts +166 -0
  106. package/src/uniforms/UniformsPanel.ts +163 -0
  107. package/src/uniforms/index.ts +13 -0
  108. package/src/uniforms/uniform-controls.css +342 -0
  109. package/src/uniforms/uniforms-panel.css +277 -0
  110. package/templates/shaders/example-buffer/config.json +1 -0
  111. package/dist-lib/engine/ShadertoyEngine.d.ts.map +0 -1
  112. package/dist-lib/engine/ShadertoyEngine.js +0 -704
  113. package/src/engine/ShadertoyEngine.ts +0 -929
@@ -0,0 +1,166 @@
1
+ /**
2
+ * UniformStore - Simple state manager for custom uniforms
3
+ *
4
+ * Encapsulates uniform values and provides clean get/set interface.
5
+ * Used by the engine to manage uniform state.
6
+ */
7
+
8
+ import {
9
+ UniformDefinitions,
10
+ UniformDefinition,
11
+ UniformValue,
12
+ UniformValues,
13
+ isArrayUniform,
14
+ } from '../project/types';
15
+
16
+ import { tightFloatCount } from '../engine/std140';
17
+
18
+ export class UniformStore {
19
+ private definitions: UniformDefinitions;
20
+ private values: UniformValues = {};
21
+
22
+ constructor(definitions: UniformDefinitions) {
23
+ this.definitions = definitions;
24
+ this.initializeDefaults();
25
+ }
26
+
27
+ /**
28
+ * Initialize all values to their definition defaults.
29
+ */
30
+ private initializeDefaults(): void {
31
+ for (const [name, def] of Object.entries(this.definitions)) {
32
+ if (isArrayUniform(def)) {
33
+ // Array uniforms initialize to zeroed Float32Array
34
+ this.values[name] = new Float32Array(tightFloatCount(def.type, def.count));
35
+ } else {
36
+ this.values[name] = this.cloneValue((def as { value: UniformValue }).value);
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Clone a value to avoid mutation of arrays.
43
+ */
44
+ private cloneValue(value: UniformValue): UniformValue {
45
+ if (value instanceof Float32Array) return value.slice();
46
+ return Array.isArray(value) ? [...value] : value;
47
+ }
48
+
49
+ /**
50
+ * Get the definition for a uniform.
51
+ */
52
+ getDefinition(name: string): UniformDefinition | undefined {
53
+ return this.definitions[name];
54
+ }
55
+
56
+ /**
57
+ * Get all definitions.
58
+ */
59
+ getDefinitions(): UniformDefinitions {
60
+ return this.definitions;
61
+ }
62
+
63
+ /**
64
+ * Check if a uniform exists.
65
+ */
66
+ has(name: string): boolean {
67
+ return name in this.definitions;
68
+ }
69
+
70
+ /**
71
+ * Get the current value of a uniform.
72
+ */
73
+ get(name: string): UniformValue | undefined {
74
+ return this.values[name];
75
+ }
76
+
77
+ /**
78
+ * Get all current values (returns a shallow copy).
79
+ */
80
+ getAll(): UniformValues {
81
+ const result: UniformValues = {};
82
+ for (const [name, value] of Object.entries(this.values)) {
83
+ result[name] = this.cloneValue(value);
84
+ }
85
+ return result;
86
+ }
87
+
88
+ /**
89
+ * Set the value of a uniform.
90
+ * Returns true if the value was set, false if the uniform doesn't exist.
91
+ */
92
+ set(name: string, value: UniformValue): boolean {
93
+ if (!this.has(name)) {
94
+ return false;
95
+ }
96
+ this.values[name] = this.cloneValue(value);
97
+ return true;
98
+ }
99
+
100
+ /**
101
+ * Set multiple values at once.
102
+ */
103
+ setAll(values: Partial<UniformValues>): void {
104
+ for (const [name, value] of Object.entries(values)) {
105
+ if (value !== undefined) {
106
+ this.set(name, value);
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Reset a single uniform to its default value.
113
+ */
114
+ reset(name: string): boolean {
115
+ const def = this.definitions[name];
116
+ if (!def) {
117
+ return false;
118
+ }
119
+ if (isArrayUniform(def)) {
120
+ this.values[name] = new Float32Array(tightFloatCount(def.type, def.count));
121
+ } else {
122
+ this.values[name] = this.cloneValue(def.value);
123
+ }
124
+ return true;
125
+ }
126
+
127
+ /**
128
+ * Reset all uniforms to their default values.
129
+ */
130
+ resetAll(): void {
131
+ this.initializeDefaults();
132
+ }
133
+
134
+ /**
135
+ * Get the default value for a uniform.
136
+ */
137
+ getDefault(name: string): UniformValue | undefined {
138
+ const def = this.definitions[name];
139
+ if (!def) return undefined;
140
+ if (isArrayUniform(def)) return new Float32Array(tightFloatCount(def.type, def.count));
141
+ return this.cloneValue(def.value);
142
+ }
143
+
144
+ /**
145
+ * Iterate over all uniforms (name, definition, current value).
146
+ */
147
+ *entries(): IterableIterator<[string, UniformDefinition, UniformValue]> {
148
+ for (const [name, def] of Object.entries(this.definitions)) {
149
+ yield [name, def, this.values[name]];
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Get the number of uniforms.
155
+ */
156
+ get size(): number {
157
+ return Object.keys(this.definitions).length;
158
+ }
159
+
160
+ /**
161
+ * Check if there are any uniforms.
162
+ */
163
+ get isEmpty(): boolean {
164
+ return this.size === 0;
165
+ }
166
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Uniforms Panel - Floating overlay for uniform controls
3
+ *
4
+ * A compact panel that floats on the right side of the canvas.
5
+ * Includes a toggle button to show/hide. Starts closed by default.
6
+ */
7
+
8
+ import './uniforms-panel.css';
9
+
10
+ import { UniformDefinitions, UniformValue, UniformValues, hasUIControl } from '../project/types';
11
+ import { UniformControls } from './UniformControls';
12
+
13
+ export interface UniformsPanelOptions {
14
+ /** Parent container to attach the panel to */
15
+ container: HTMLElement;
16
+ /** Uniform definitions from project */
17
+ uniforms: UniformDefinitions;
18
+ /** Callback when uniform value changes */
19
+ onChange: (name: string, value: UniformValue) => void;
20
+ /** Initial values (optional) */
21
+ initialValues?: UniformValues;
22
+ /** Start with panel open (default: false) */
23
+ startOpen?: boolean;
24
+ }
25
+
26
+ export class UniformsPanel {
27
+ private wrapper: HTMLElement;
28
+ private panel: HTMLElement;
29
+ private toggleButton: HTMLElement;
30
+ private controls: UniformControls | null = null;
31
+ private isOpen: boolean;
32
+
33
+ constructor(opts: UniformsPanelOptions) {
34
+ this.isOpen = opts.startOpen ?? false;
35
+
36
+ // Create wrapper for both toggle button and panel
37
+ this.wrapper = document.createElement('div');
38
+ this.wrapper.className = 'uniforms-panel-wrapper';
39
+
40
+ // Create toggle button (always visible)
41
+ this.toggleButton = document.createElement('button');
42
+ this.toggleButton.className = 'uniforms-toggle-button';
43
+ this.toggleButton.title = 'Toggle Uniforms Panel';
44
+ this.toggleButton.innerHTML = `
45
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
46
+ <line x1="4" y1="21" x2="4" y2="14"></line>
47
+ <line x1="4" y1="10" x2="4" y2="3"></line>
48
+ <line x1="12" y1="21" x2="12" y2="12"></line>
49
+ <line x1="12" y1="8" x2="12" y2="3"></line>
50
+ <line x1="20" y1="21" x2="20" y2="16"></line>
51
+ <line x1="20" y1="12" x2="20" y2="3"></line>
52
+ <line x1="1" y1="14" x2="7" y2="14"></line>
53
+ <line x1="9" y1="8" x2="15" y2="8"></line>
54
+ <line x1="17" y1="16" x2="23" y2="16"></line>
55
+ </svg>
56
+ `;
57
+ this.toggleButton.addEventListener('click', () => this.toggle());
58
+ this.wrapper.appendChild(this.toggleButton);
59
+
60
+ // Create panel element
61
+ this.panel = document.createElement('div');
62
+ this.panel.className = 'uniforms-panel';
63
+
64
+ // Only create content if there are visible uniforms
65
+ const hasVisible = Object.values(opts.uniforms).some(def => hasUIControl(def));
66
+ if (!hasVisible) {
67
+ this.wrapper.style.display = 'none';
68
+ opts.container.appendChild(this.wrapper);
69
+ return;
70
+ }
71
+
72
+ // Header with close button
73
+ const header = document.createElement('div');
74
+ header.className = 'uniforms-panel-header';
75
+
76
+ const title = document.createElement('span');
77
+ title.textContent = 'Uniforms';
78
+ header.appendChild(title);
79
+
80
+ const closeButton = document.createElement('button');
81
+ closeButton.className = 'uniforms-panel-close';
82
+ closeButton.innerHTML = '&times;';
83
+ closeButton.title = 'Close';
84
+ closeButton.addEventListener('click', () => this.hide());
85
+ header.appendChild(closeButton);
86
+
87
+ this.panel.appendChild(header);
88
+
89
+ // Content area for controls
90
+ const content = document.createElement('div');
91
+ content.className = 'uniforms-panel-content';
92
+ this.panel.appendChild(content);
93
+
94
+ // Create uniform controls
95
+ this.controls = new UniformControls({
96
+ container: content,
97
+ uniforms: opts.uniforms,
98
+ initialValues: opts.initialValues,
99
+ onChange: opts.onChange,
100
+ });
101
+
102
+ this.wrapper.appendChild(this.panel);
103
+
104
+ // Set initial state
105
+ if (!this.isOpen) {
106
+ this.panel.classList.add('closed');
107
+ }
108
+
109
+ // Append to container
110
+ opts.container.appendChild(this.wrapper);
111
+ }
112
+
113
+ /**
114
+ * Update a uniform value from external source.
115
+ */
116
+ setValue(name: string, value: UniformValue): void {
117
+ this.controls?.setValue(name, value);
118
+ }
119
+
120
+ /**
121
+ * Show the panel.
122
+ */
123
+ show(): void {
124
+ this.isOpen = true;
125
+ this.toggleButton.classList.add('hidden');
126
+ this.panel.classList.remove('closed');
127
+ }
128
+
129
+ /**
130
+ * Hide the panel.
131
+ */
132
+ hide(): void {
133
+ this.isOpen = false;
134
+ this.panel.classList.add('closed');
135
+ this.toggleButton.classList.remove('hidden');
136
+ }
137
+
138
+ /**
139
+ * Toggle panel visibility.
140
+ */
141
+ toggle(): void {
142
+ if (this.isOpen) {
143
+ this.hide();
144
+ } else {
145
+ this.show();
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Check if panel is visible.
151
+ */
152
+ isVisible(): boolean {
153
+ return this.isOpen;
154
+ }
155
+
156
+ /**
157
+ * Destroy the panel.
158
+ */
159
+ destroy(): void {
160
+ this.controls?.destroy();
161
+ this.wrapper.remove();
162
+ }
163
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Uniform Controls Module
3
+ *
4
+ * UI components and state management for custom uniforms.
5
+ */
6
+
7
+ export { UniformControls } from './UniformControls';
8
+ export type { UniformControlsOptions } from './UniformControls';
9
+
10
+ export { UniformsPanel } from './UniformsPanel';
11
+ export type { UniformsPanelOptions } from './UniformsPanel';
12
+
13
+ export { UniformStore } from './UniformStore';
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Uniform Controls Styles
3
+ * Theme-aware styling for uniform sliders and controls
4
+ */
5
+
6
+ .uniform-controls {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: 16px;
10
+ padding: 16px;
11
+ height: 100%;
12
+ overflow-y: auto;
13
+ background: var(--bg-secondary);
14
+ }
15
+
16
+ .uniform-controls-empty {
17
+ color: var(--text-muted);
18
+ font-size: 13px;
19
+ text-align: center;
20
+ padding: 20px;
21
+ }
22
+
23
+ /* Header with reset button */
24
+ .uniform-controls-header {
25
+ display: flex;
26
+ justify-content: flex-end;
27
+ padding-bottom: 8px;
28
+ border-bottom: 1px solid var(--border-primary);
29
+ margin-bottom: 8px;
30
+ }
31
+
32
+ .uniform-controls-reset {
33
+ font-family: inherit;
34
+ font-size: 11px;
35
+ padding: 4px 10px;
36
+ background: var(--bg-tertiary);
37
+ color: var(--text-secondary);
38
+ border: 1px solid var(--border-primary);
39
+ border-radius: 4px;
40
+ cursor: pointer;
41
+ transition: background 0.15s ease, color 0.15s ease;
42
+ }
43
+
44
+ .uniform-controls-reset:hover {
45
+ background: var(--border-primary);
46
+ color: var(--text-primary);
47
+ }
48
+
49
+ .uniform-controls-reset:active {
50
+ transform: translateY(1px);
51
+ }
52
+
53
+ /* Control list container */
54
+ .uniform-controls-list {
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: 16px;
58
+ }
59
+
60
+ /* Individual control wrapper */
61
+ .uniform-control {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 8px;
65
+ }
66
+
67
+ /* Label row with name and value */
68
+ .uniform-control-label-row {
69
+ display: flex;
70
+ justify-content: space-between;
71
+ align-items: center;
72
+ }
73
+
74
+ .uniform-control-label {
75
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
76
+ font-size: 12px;
77
+ font-weight: 500;
78
+ color: var(--text-primary);
79
+ }
80
+
81
+ .uniform-control-value {
82
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
83
+ font-size: 11px;
84
+ color: var(--text-muted);
85
+ background: var(--bg-tertiary);
86
+ padding: 2px 6px;
87
+ border-radius: 3px;
88
+ min-width: 50px;
89
+ text-align: right;
90
+ }
91
+
92
+ /* Slider styling */
93
+ .uniform-control-slider {
94
+ -webkit-appearance: none;
95
+ appearance: none;
96
+ width: 100%;
97
+ height: 6px;
98
+ background: var(--border-primary);
99
+ border-radius: 3px;
100
+ outline: none;
101
+ cursor: pointer;
102
+ }
103
+
104
+ /* Slider track - WebKit */
105
+ .uniform-control-slider::-webkit-slider-runnable-track {
106
+ height: 6px;
107
+ background: var(--border-primary);
108
+ border-radius: 3px;
109
+ }
110
+
111
+ /* Slider thumb - WebKit */
112
+ .uniform-control-slider::-webkit-slider-thumb {
113
+ -webkit-appearance: none;
114
+ appearance: none;
115
+ width: 14px;
116
+ height: 14px;
117
+ background: var(--accent-primary);
118
+ border-radius: 50%;
119
+ cursor: pointer;
120
+ margin-top: -4px;
121
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
122
+ }
123
+
124
+ .uniform-control-slider::-webkit-slider-thumb:hover {
125
+ transform: scale(1.1);
126
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
127
+ }
128
+
129
+ .uniform-control-slider::-webkit-slider-thumb:active {
130
+ transform: scale(0.95);
131
+ }
132
+
133
+ /* Slider track - Firefox */
134
+ .uniform-control-slider::-moz-range-track {
135
+ height: 6px;
136
+ background: var(--border-primary);
137
+ border-radius: 3px;
138
+ }
139
+
140
+ /* Slider thumb - Firefox */
141
+ .uniform-control-slider::-moz-range-thumb {
142
+ width: 14px;
143
+ height: 14px;
144
+ background: var(--accent-primary);
145
+ border: none;
146
+ border-radius: 50%;
147
+ cursor: pointer;
148
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
149
+ }
150
+
151
+ .uniform-control-slider::-moz-range-thumb:hover {
152
+ transform: scale(1.1);
153
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
154
+ }
155
+
156
+ .uniform-control-slider::-moz-range-thumb:active {
157
+ transform: scale(0.95);
158
+ }
159
+
160
+ /* Focus state */
161
+ .uniform-control-slider:focus {
162
+ outline: none;
163
+ }
164
+
165
+ .uniform-control-slider:focus::-webkit-slider-thumb {
166
+ box-shadow: 0 0 0 3px var(--code-selection);
167
+ }
168
+
169
+ .uniform-control-slider:focus::-moz-range-thumb {
170
+ box-shadow: 0 0 0 3px var(--code-selection);
171
+ }
172
+
173
+ /* ===========================================================================
174
+ * Bool Toggle Switch
175
+ * =========================================================================== */
176
+
177
+ .uniform-control-toggle {
178
+ position: relative;
179
+ display: inline-block;
180
+ width: 40px;
181
+ height: 22px;
182
+ cursor: pointer;
183
+ }
184
+
185
+ .uniform-control-toggle input {
186
+ opacity: 0;
187
+ width: 0;
188
+ height: 0;
189
+ }
190
+
191
+ .uniform-control-toggle-slider {
192
+ position: absolute;
193
+ top: 0;
194
+ left: 0;
195
+ right: 0;
196
+ bottom: 0;
197
+ background: var(--border-primary);
198
+ border-radius: 22px;
199
+ transition: background 0.2s ease;
200
+ }
201
+
202
+ .uniform-control-toggle-slider::before {
203
+ content: '';
204
+ position: absolute;
205
+ width: 16px;
206
+ height: 16px;
207
+ left: 3px;
208
+ bottom: 3px;
209
+ background: var(--text-muted);
210
+ border-radius: 50%;
211
+ transition: transform 0.2s ease, background 0.2s ease;
212
+ }
213
+
214
+ .uniform-control-toggle input:checked + .uniform-control-toggle-slider {
215
+ background: var(--accent-primary);
216
+ }
217
+
218
+ .uniform-control-toggle input:checked + .uniform-control-toggle-slider::before {
219
+ transform: translateX(18px);
220
+ background: white;
221
+ }
222
+
223
+ .uniform-control-toggle input:focus + .uniform-control-toggle-slider {
224
+ box-shadow: 0 0 0 2px var(--code-selection);
225
+ }
226
+
227
+ /* ===========================================================================
228
+ * Vec2 XY Pad
229
+ * =========================================================================== */
230
+
231
+ .uniform-control-xy-pad {
232
+ position: relative;
233
+ width: 100%;
234
+ height: 120px;
235
+ background: var(--bg-tertiary);
236
+ border: 1px solid var(--border-primary);
237
+ border-radius: 4px;
238
+ cursor: crosshair;
239
+ overflow: hidden;
240
+ }
241
+
242
+ /* Grid lines */
243
+ .uniform-control-xy-pad::before,
244
+ .uniform-control-xy-pad::after {
245
+ content: '';
246
+ position: absolute;
247
+ background: var(--border-primary);
248
+ opacity: 0.5;
249
+ }
250
+
251
+ .uniform-control-xy-pad::before {
252
+ /* Vertical center line */
253
+ left: 50%;
254
+ top: 0;
255
+ bottom: 0;
256
+ width: 1px;
257
+ }
258
+
259
+ .uniform-control-xy-pad::after {
260
+ /* Horizontal center line */
261
+ top: 50%;
262
+ left: 0;
263
+ right: 0;
264
+ height: 1px;
265
+ }
266
+
267
+ .uniform-control-xy-handle {
268
+ position: absolute;
269
+ width: 14px;
270
+ height: 14px;
271
+ background: var(--accent-primary);
272
+ border: 2px solid white;
273
+ border-radius: 50%;
274
+ transform: translate(-50%, -50%);
275
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
276
+ pointer-events: none;
277
+ z-index: 1;
278
+ }
279
+
280
+ /* ===========================================================================
281
+ * Vec3 Color Picker
282
+ * =========================================================================== */
283
+
284
+ .uniform-control-color-wrapper {
285
+ display: flex;
286
+ align-items: center;
287
+ gap: 8px;
288
+ }
289
+
290
+ .uniform-control-color-swatch {
291
+ width: 100%;
292
+ height: 32px;
293
+ border-radius: 4px;
294
+ border: 1px solid var(--border-primary);
295
+ cursor: pointer;
296
+ transition: box-shadow 0.15s ease;
297
+ }
298
+
299
+ .uniform-control-color-swatch:hover {
300
+ box-shadow: 0 0 0 2px var(--accent-primary);
301
+ }
302
+
303
+ .uniform-control-color-input {
304
+ /* Hide the native input but keep it functional */
305
+ position: absolute;
306
+ width: 0;
307
+ height: 0;
308
+ opacity: 0;
309
+ pointer-events: none;
310
+ }
311
+
312
+ /* ===========================================================================
313
+ * Vec3 Sliders (non-color)
314
+ * =========================================================================== */
315
+
316
+ .uniform-control-vec3 {
317
+ gap: 6px;
318
+ }
319
+
320
+ .uniform-control-vec-slider-row {
321
+ display: flex;
322
+ align-items: center;
323
+ gap: 8px;
324
+ }
325
+
326
+ .uniform-control-vec-component {
327
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
328
+ font-size: 10px;
329
+ font-weight: 600;
330
+ color: var(--text-muted);
331
+ width: 14px;
332
+ text-align: center;
333
+ }
334
+
335
+ .uniform-control-vec-slider {
336
+ flex: 1;
337
+ }
338
+
339
+ .uniform-control-vec-value {
340
+ min-width: 40px;
341
+ font-size: 10px;
342
+ }