@buoy-gg/core 1.7.7 → 1.7.8

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 (67) hide show
  1. package/lib/commonjs/floatingMenu/AppHost.js +1 -1
  2. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +20 -8
  3. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +746 -0
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.js +16 -4
  5. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +154 -0
  6. package/lib/commonjs/floatingMenu/FloatingMenu.js +4 -4
  7. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +15 -0
  8. package/lib/commonjs/floatingMenu/defaultConfig.js +14 -7
  9. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +8 -2
  10. package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +593 -0
  11. package/lib/commonjs/floatingMenu/floatingTools.web.js +357 -0
  12. package/lib/commonjs/index.js +2 -2
  13. package/lib/commonjs/index.web.js +131 -0
  14. package/lib/commonjs/utils/autoDiscoverPresets.web.js +181 -0
  15. package/lib/module/floatingMenu/AppHost.js +1 -1
  16. package/lib/module/floatingMenu/DevToolsSettingsModal.js +21 -9
  17. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +756 -0
  18. package/lib/module/floatingMenu/FloatingDevTools.js +17 -5
  19. package/lib/module/floatingMenu/FloatingDevTools.web.js +150 -0
  20. package/lib/module/floatingMenu/FloatingMenu.js +4 -4
  21. package/lib/module/floatingMenu/autoDiscoverPresets.js +15 -0
  22. package/lib/module/floatingMenu/defaultConfig.js +14 -7
  23. package/lib/module/floatingMenu/dial/DialDevTools.js +8 -2
  24. package/lib/module/floatingMenu/dial/DialDevTools.web.js +590 -0
  25. package/lib/module/floatingMenu/floatingTools.web.js +357 -0
  26. package/lib/module/index.js +2 -2
  27. package/lib/module/index.web.js +24 -0
  28. package/lib/module/utils/autoDiscoverPresets.web.js +174 -0
  29. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts +23 -0
  31. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -0
  32. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +1 -1
  33. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts +48 -0
  35. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -0
  36. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +8 -7
  38. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts +26 -0
  41. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -0
  42. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts +27 -0
  43. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts.map +1 -0
  44. package/lib/typescript/commonjs/index.web.d.ts +20 -0
  45. package/lib/typescript/commonjs/index.web.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts +58 -0
  47. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts.map +1 -0
  48. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  49. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts +23 -0
  50. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -0
  51. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +1 -1
  52. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  53. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts +48 -0
  54. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -0
  55. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  56. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +8 -7
  57. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
  58. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  59. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts +26 -0
  60. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -0
  61. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts +27 -0
  62. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts.map +1 -0
  63. package/lib/typescript/module/index.web.d.ts +20 -0
  64. package/lib/typescript/module/index.web.d.ts.map +1 -0
  65. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts +58 -0
  66. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts.map +1 -0
  67. package/package.json +5 -4
@@ -0,0 +1,590 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * DialDevTools - Web renderer for the radial dial menu.
5
+ *
6
+ * Uses shared layout calculations, styling, and animation config from @buoy-gg/floating-tools-core.
7
+ * Platform-specific: CSS animations, mouse events, web JSX.
8
+ *
9
+ * Animation behavior matches mobile exactly - see docs/dial-animation-spec.md
10
+ *
11
+ * @platform React DOM Web (Vite, CRA, Next.js, Electron)
12
+ */
13
+
14
+ import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
15
+ import { getDialLayout, getAllIconPositions, getGridLineRotations, getIconStaggerInputRange, MAX_DIAL_SLOTS, dialAnimationConfig, getDialCSSKeyframesString, getDialCSSAnimationStyles, dialColors, dialStyles } from '@buoy-gg/floating-tools-react';
16
+
17
+ // Inject CSS keyframes once on module load
18
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
19
+ let keyframesInjected = false;
20
+ function injectKeyframes() {
21
+ if (keyframesInjected || typeof document === 'undefined') return;
22
+ const style = document.createElement('style');
23
+ style.textContent = getDialCSSKeyframesString();
24
+ document.head.appendChild(style);
25
+ keyframesInjected = true;
26
+ }
27
+
28
+ // Easing functions
29
+ const easeOutCubic = t => 1 - Math.pow(1 - t, 3);
30
+ const easeInCubic = t => t * t * t;
31
+
32
+ // =============================
33
+ // Types
34
+ // =============================
35
+
36
+ // =============================
37
+ // Dial Icon Component
38
+ // =============================
39
+
40
+ function DialIconItem({
41
+ icon,
42
+ index,
43
+ totalIcons,
44
+ position,
45
+ progress,
46
+ onPress
47
+ }) {
48
+ const [isHovered, setIsHovered] = useState(false);
49
+ const [isPressed, setIsPressed] = useState(false);
50
+ const isEmpty = !icon.icon && icon.id.startsWith('empty-');
51
+ const {
52
+ icons: iconConfig
53
+ } = dialAnimationConfig;
54
+
55
+ // Get stagger input range for this icon
56
+ const staggerInputRange = getIconStaggerInputRange(index, totalIcons);
57
+
58
+ // Calculate this icon's individual progress based on the global progress
59
+ let iconProgress = 0;
60
+ if (progress <= staggerInputRange[1]) {
61
+ iconProgress = 0;
62
+ } else if (progress <= staggerInputRange[2]) {
63
+ iconProgress = (progress - staggerInputRange[1]) / (staggerInputRange[2] - staggerInputRange[1]);
64
+ } else {
65
+ iconProgress = 1;
66
+ }
67
+ iconProgress = Math.max(0, Math.min(1, iconProgress));
68
+ const iconSize = 60;
69
+ const pressedScale = iconConfig.pressIn.scale;
70
+ const hoveredScale = 1.1;
71
+ const scale = iconProgress * (isPressed ? pressedScale : isHovered ? hoveredScale : 1);
72
+
73
+ // Opacity interpolation
74
+ let opacity = 0;
75
+ if (iconProgress <= 0) {
76
+ opacity = 0;
77
+ } else if (iconProgress <= 0.3) {
78
+ opacity = iconProgress;
79
+ } else {
80
+ opacity = 0.3 + (iconProgress - 0.3) * (0.7 / 0.7);
81
+ }
82
+ const x = position.x * iconProgress;
83
+ const y = position.y * iconProgress;
84
+ return /*#__PURE__*/_jsx("button", {
85
+ role: "menuitem",
86
+ "aria-label": isEmpty ? 'Empty slot' : icon.name,
87
+ "aria-disabled": isEmpty,
88
+ tabIndex: isEmpty ? -1 : 0,
89
+ onClick: isEmpty ? undefined : onPress,
90
+ onMouseEnter: () => !isEmpty && setIsHovered(true),
91
+ onMouseLeave: () => {
92
+ setIsHovered(false);
93
+ setIsPressed(false);
94
+ },
95
+ onMouseDown: () => !isEmpty && setIsPressed(true),
96
+ onMouseUp: () => setIsPressed(false),
97
+ style: {
98
+ position: 'absolute',
99
+ left: '50%',
100
+ top: '50%',
101
+ width: iconSize,
102
+ height: iconSize,
103
+ marginLeft: -iconSize / 2,
104
+ marginTop: -iconSize / 2,
105
+ display: 'flex',
106
+ flexDirection: 'column',
107
+ alignItems: 'center',
108
+ justifyContent: 'center',
109
+ cursor: isEmpty ? 'default' : 'pointer',
110
+ transform: `translate(${x}px, ${y}px) scale(${scale})`,
111
+ opacity,
112
+ transition: isHovered || isPressed ? 'transform 100ms ease' : 'none',
113
+ background: 'none',
114
+ border: 'none',
115
+ padding: 0
116
+ },
117
+ children: isEmpty ? /*#__PURE__*/_jsx("div", {
118
+ style: {
119
+ width: dialStyles.emptySlot.dotSize,
120
+ height: dialStyles.emptySlot.dotSize,
121
+ borderRadius: dialStyles.emptySlot.dotSize / 2,
122
+ backgroundColor: dialColors.emptyDotBackground,
123
+ border: `${dialStyles.emptySlot.borderWidth}px solid ${dialColors.emptyDotBorder}`
124
+ }
125
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
126
+ children: [/*#__PURE__*/_jsx("div", {
127
+ style: {
128
+ position: 'absolute',
129
+ width: `${dialStyles.icon.gradientBgSizePercent}%`,
130
+ height: `${dialStyles.icon.gradientBgSizePercent}%`,
131
+ borderRadius: dialStyles.icon.gradientBgBorderRadius,
132
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
133
+ opacity: dialStyles.icon.gradientBgOpacity
134
+ }
135
+ }), /*#__PURE__*/_jsx("div", {
136
+ style: {
137
+ position: 'absolute',
138
+ width: `${dialStyles.icon.innerGlowSizePercent}%`,
139
+ height: `${dialStyles.icon.innerGlowSizePercent}%`,
140
+ borderRadius: dialStyles.icon.innerGlowBorderRadius,
141
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
142
+ opacity: dialStyles.icon.innerGlowOpacity
143
+ }
144
+ }), /*#__PURE__*/_jsx("div", {
145
+ style: {
146
+ marginBottom: dialStyles.icon.wrapperMarginBottom,
147
+ display: 'flex',
148
+ alignItems: 'center',
149
+ justifyContent: 'center',
150
+ fontSize: 32,
151
+ filter: isHovered ? 'brightness(1.2)' : 'none'
152
+ },
153
+ children: icon.icon
154
+ }), /*#__PURE__*/_jsx("span", {
155
+ style: {
156
+ fontSize: dialStyles.icon.label.fontSize,
157
+ fontWeight: dialStyles.icon.label.fontWeight,
158
+ fontFamily: dialStyles.icon.label.fontFamily,
159
+ color: dialColors.iconLabel,
160
+ letterSpacing: dialStyles.icon.label.letterSpacing,
161
+ marginTop: dialStyles.icon.label.marginTop,
162
+ textTransform: 'uppercase'
163
+ },
164
+ children: icon.name
165
+ })]
166
+ })
167
+ });
168
+ }
169
+
170
+ // =============================
171
+ // Main DialMenu Component
172
+ // =============================
173
+
174
+ export function DialMenu({
175
+ icons,
176
+ onClose,
177
+ centerLabel = 'MENU',
178
+ onCenterPress
179
+ }) {
180
+ // Animation values
181
+ const [backdropOpacity, setBackdropOpacity] = useState(0);
182
+ const [dialScale, setDialScale] = useState(0);
183
+ const [dialRotation, setDialRotation] = useState(0);
184
+ const [centerButtonScale, setCenterButtonScale] = useState(0);
185
+ const [iconProgress, setIconProgress] = useState(0);
186
+ const [centerPulseScale, setCenterPulseScale] = useState(1);
187
+
188
+ // UI state
189
+ const [isHoveringCenter, setIsHoveringCenter] = useState(false);
190
+ const [triggerGlitch, setTriggerGlitch] = useState(0);
191
+ const [isExiting, setIsExiting] = useState(false);
192
+ const [entranceComplete, setEntranceComplete] = useState(false);
193
+
194
+ // Refs
195
+ const animationRef = useRef(null);
196
+ const glitchIntervalRef = useRef(null);
197
+ const isClosingRef = useRef(false);
198
+
199
+ // Config
200
+ const {
201
+ entrance,
202
+ exit,
203
+ continuous,
204
+ interaction
205
+ } = dialAnimationConfig;
206
+ const cssAnimations = getDialCSSAnimationStyles();
207
+
208
+ // Layout
209
+ const layout = useMemo(() => getDialLayout({
210
+ screenWidth: window.innerWidth
211
+ }), []);
212
+ const gridRotations = useMemo(() => getGridLineRotations(), []);
213
+ const positions = useMemo(() => getAllIconPositions(MAX_DIAL_SLOTS, layout.iconRadius), [layout.iconRadius]);
214
+ const paddedIcons = useMemo(() => {
215
+ const result = [...icons.slice(0, MAX_DIAL_SLOTS)];
216
+ while (result.length < MAX_DIAL_SLOTS) {
217
+ result.push({
218
+ id: `empty-${result.length}`,
219
+ name: '',
220
+ icon: null,
221
+ onPress: () => {}
222
+ });
223
+ }
224
+ return result;
225
+ }, [icons]);
226
+
227
+ // Inject keyframes
228
+ useEffect(() => {
229
+ injectKeyframes();
230
+ }, []);
231
+
232
+ // ENTRANCE Animation - runs once on mount
233
+ useEffect(() => {
234
+ // Capture config values at mount time
235
+ const backdropDuration = entrance.backdrop.duration;
236
+ const rotationDuration = entrance.dial.rotation.duration;
237
+ const centerButtonDelay = entrance.centerButton.delay;
238
+ const iconsDelay = entrance.icons.delay;
239
+ const iconsDuration = entrance.icons.duration;
240
+ const glitchInterval = continuous.glitch.interval;
241
+ const startTime = performance.now();
242
+ const animate = currentTime => {
243
+ const elapsed = currentTime - startTime;
244
+
245
+ // Backdrop (0-400ms)
246
+ setBackdropOpacity(Math.min(1, elapsed / backdropDuration));
247
+
248
+ // Dial scale with spring overshoot (~400ms)
249
+ const dialProgress = Math.min(1, elapsed / 400);
250
+ let springScale = easeOutCubic(dialProgress) * 1.1;
251
+ if (dialProgress > 0.7) {
252
+ springScale -= (dialProgress - 0.7) * 0.33;
253
+ }
254
+ if (dialProgress >= 1) springScale = 1;
255
+ setDialScale(Math.max(0, Math.min(1.05, springScale)));
256
+
257
+ // Dial rotation (0-800ms)
258
+ const rotProgress = Math.min(1, elapsed / rotationDuration);
259
+ setDialRotation(easeOutCubic(rotProgress) * 360);
260
+
261
+ // Center button (300ms delay)
262
+ if (elapsed >= centerButtonDelay) {
263
+ const centerProgress = Math.min(1, (elapsed - centerButtonDelay) / 300);
264
+ let centerSpring = easeOutCubic(centerProgress) * 1.15;
265
+ if (centerProgress > 0.6) {
266
+ centerSpring -= (centerProgress - 0.6) * 0.375;
267
+ }
268
+ if (centerProgress >= 1) centerSpring = 1;
269
+ setCenterButtonScale(Math.max(0, Math.min(1.05, centerSpring)));
270
+ }
271
+
272
+ // Icons (500ms delay, 600ms duration)
273
+ if (elapsed >= iconsDelay) {
274
+ const iconsProgress = Math.min(1, (elapsed - iconsDelay) / iconsDuration);
275
+ setIconProgress(easeOutCubic(iconsProgress));
276
+ }
277
+
278
+ // Continue or finish
279
+ const totalTime = iconsDelay + iconsDuration;
280
+ if (elapsed < totalTime) {
281
+ animationRef.current = requestAnimationFrame(animate);
282
+ } else {
283
+ // Finalize
284
+ setBackdropOpacity(1);
285
+ setDialScale(1);
286
+ setDialRotation(360);
287
+ setCenterButtonScale(1);
288
+ setIconProgress(1);
289
+ setEntranceComplete(true);
290
+ }
291
+ };
292
+ animationRef.current = requestAnimationFrame(animate);
293
+
294
+ // Glitch interval
295
+ glitchIntervalRef.current = setInterval(() => {
296
+ setTriggerGlitch(prev => prev + 1);
297
+ }, glitchInterval);
298
+ return () => {
299
+ if (animationRef.current) cancelAnimationFrame(animationRef.current);
300
+ if (glitchIntervalRef.current) clearInterval(glitchIntervalRef.current);
301
+ };
302
+ }, []);
303
+
304
+ // EXIT Animation
305
+ const handleClose = useCallback(() => {
306
+ if (isClosingRef.current) return;
307
+ isClosingRef.current = true;
308
+ setIsExiting(true);
309
+ if (glitchIntervalRef.current) {
310
+ clearInterval(glitchIntervalRef.current);
311
+ glitchIntervalRef.current = null;
312
+ }
313
+ if (animationRef.current) {
314
+ cancelAnimationFrame(animationRef.current);
315
+ }
316
+ const startTime = performance.now();
317
+ const iconsDuration = exit.icons.duration;
318
+ const dialDuration = Math.max(exit.centerButton.duration, exit.dialScale.duration);
319
+ const backdropDuration = exit.backdrop.duration;
320
+ const animate = currentTime => {
321
+ const elapsed = currentTime - startTime;
322
+
323
+ // Step 1: Icons collapse (0 - 300ms)
324
+ if (elapsed < iconsDuration) {
325
+ const progress = easeInCubic(elapsed / iconsDuration);
326
+ setIconProgress(1 - progress);
327
+ animationRef.current = requestAnimationFrame(animate);
328
+ return;
329
+ }
330
+
331
+ // Step 2: Dial + Center scale down (300 - 550ms)
332
+ const step2Start = iconsDuration;
333
+ if (elapsed < step2Start + dialDuration) {
334
+ setIconProgress(0);
335
+ const progress = easeInCubic((elapsed - step2Start) / dialDuration);
336
+ setDialScale(1 - progress);
337
+ setCenterButtonScale(1 - progress);
338
+ animationRef.current = requestAnimationFrame(animate);
339
+ return;
340
+ }
341
+
342
+ // Step 3: Backdrop fade (550 - 750ms)
343
+ const step3Start = step2Start + dialDuration;
344
+ if (elapsed < step3Start + backdropDuration) {
345
+ setDialScale(0);
346
+ setCenterButtonScale(0);
347
+ const progress = (elapsed - step3Start) / backdropDuration;
348
+ setBackdropOpacity(1 - progress);
349
+ animationRef.current = requestAnimationFrame(animate);
350
+ return;
351
+ }
352
+
353
+ // Done
354
+ setBackdropOpacity(0);
355
+ onClose();
356
+ };
357
+ animationRef.current = requestAnimationFrame(animate);
358
+ }, [exit, onClose]);
359
+
360
+ // Document-level Escape key handler
361
+ useEffect(() => {
362
+ const handleKeyDown = e => {
363
+ if (e.key === 'Escape') {
364
+ handleClose();
365
+ }
366
+ };
367
+ document.addEventListener('keydown', handleKeyDown);
368
+ return () => document.removeEventListener('keydown', handleKeyDown);
369
+ }, [handleClose]);
370
+
371
+ // Icon press handler
372
+ const handleIconPress = useCallback(icon => {
373
+ if (icon.id.startsWith('empty-') || isClosingRef.current) return;
374
+
375
+ // Pulse animation
376
+ const [pulseIn, pulseOut] = interaction.iconSelect.pulse;
377
+ const startTime = performance.now();
378
+ const pulseDuration = 150;
379
+ const animatePulse = currentTime => {
380
+ const elapsed = currentTime - startTime;
381
+ if (elapsed < pulseDuration) {
382
+ const progress = easeOutCubic(elapsed / pulseDuration);
383
+ setCenterPulseScale(1 - (1 - pulseIn.scale) * progress);
384
+ requestAnimationFrame(animatePulse);
385
+ } else if (elapsed < pulseDuration * 2) {
386
+ const progress = easeOutCubic((elapsed - pulseDuration) / pulseDuration);
387
+ setCenterPulseScale(pulseIn.scale + (pulseOut.scale - pulseIn.scale) * progress);
388
+ requestAnimationFrame(animatePulse);
389
+ } else {
390
+ setCenterPulseScale(1);
391
+ }
392
+ };
393
+ requestAnimationFrame(animatePulse);
394
+
395
+ // Trigger action after delay
396
+ setTimeout(() => {
397
+ icon.onPress();
398
+ handleClose();
399
+ }, interaction.iconSelect.actionDelay);
400
+ }, [interaction.iconSelect, handleClose]);
401
+
402
+ // Computed values
403
+ const buttonContainerSize = layout.buttonSize * dialStyles.centerButton.containerRatio;
404
+ const buttonBorderSize = layout.buttonSize * dialStyles.centerButton.borderRatio;
405
+ const isAnimating = entranceComplete && !isExiting;
406
+ return /*#__PURE__*/_jsxs("div", {
407
+ role: "dialog",
408
+ "aria-label": "Dial Menu",
409
+ "aria-modal": "true",
410
+ style: {
411
+ position: 'fixed',
412
+ inset: 0,
413
+ zIndex: 10000,
414
+ display: 'flex',
415
+ alignItems: 'center',
416
+ justifyContent: 'center'
417
+ },
418
+ children: [/*#__PURE__*/_jsx("div", {
419
+ role: "button",
420
+ "aria-label": "Close dial menu",
421
+ tabIndex: 0,
422
+ onClick: handleClose,
423
+ onKeyDown: e => e.key === 'Escape' && handleClose(),
424
+ style: {
425
+ position: 'absolute',
426
+ inset: 0,
427
+ backgroundColor: dialColors.dialBackdrop,
428
+ opacity: backdropOpacity
429
+ }
430
+ }), /*#__PURE__*/_jsx("div", {
431
+ style: {
432
+ animation: isAnimating ? cssAnimations.floating : 'none'
433
+ },
434
+ children: /*#__PURE__*/_jsx("div", {
435
+ style: {
436
+ animation: triggerGlitch > 0 && isAnimating ? cssAnimations.glitch : 'none'
437
+ },
438
+ children: /*#__PURE__*/_jsxs("div", {
439
+ style: {
440
+ position: 'relative',
441
+ width: layout.circleSize,
442
+ height: layout.circleSize,
443
+ borderRadius: '50%',
444
+ backgroundColor: dialColors.dialBackground,
445
+ border: `${dialStyles.circle.borderWidth}px solid ${dialColors.dialBorder}`,
446
+ boxShadow: `0 0 ${dialStyles.circle.shadowRadius}px ${dialColors.dialShadow}`,
447
+ transform: `scale(${dialScale}) rotate(${dialRotation}deg)`,
448
+ opacity: dialScale > 0.01 ? 1 : 0,
449
+ overflow: 'hidden'
450
+ },
451
+ children: [/*#__PURE__*/_jsx("div", {
452
+ style: {
453
+ position: 'absolute',
454
+ inset: 0,
455
+ borderRadius: '50%',
456
+ backgroundColor: dialColors.dialGradient1,
457
+ opacity: dialStyles.gradientLayers[0].opacity
458
+ }
459
+ }), /*#__PURE__*/_jsx("div", {
460
+ style: {
461
+ position: 'absolute',
462
+ inset: 0,
463
+ top: '30%',
464
+ left: '30%',
465
+ borderRadius: '50%',
466
+ backgroundColor: dialColors.dialGradient2,
467
+ opacity: dialStyles.gradientLayers[1].opacity
468
+ }
469
+ }), /*#__PURE__*/_jsx("div", {
470
+ style: {
471
+ position: 'absolute',
472
+ inset: 0,
473
+ top: '50%',
474
+ left: '50%',
475
+ borderRadius: '50%',
476
+ backgroundColor: dialColors.dialGradient3,
477
+ opacity: dialStyles.gradientLayers[2].opacity
478
+ }
479
+ }), gridRotations.map(deg => /*#__PURE__*/_jsx("div", {
480
+ style: {
481
+ position: 'absolute',
482
+ top: '50%',
483
+ left: 0,
484
+ right: 0,
485
+ height: dialStyles.grid.lineHeight,
486
+ backgroundColor: dialColors.dialGridLine,
487
+ transform: `rotate(${deg}deg)`,
488
+ transformOrigin: 'center'
489
+ }
490
+ }, deg)), paddedIcons.map((icon, index) => /*#__PURE__*/_jsx(DialIconItem, {
491
+ icon: icon,
492
+ index: index,
493
+ totalIcons: paddedIcons.length,
494
+ position: positions[index],
495
+ progress: iconProgress,
496
+ onPress: () => handleIconPress(icon)
497
+ }, icon.id)), /*#__PURE__*/_jsxs("div", {
498
+ style: {
499
+ position: 'absolute',
500
+ left: '50%',
501
+ top: '50%',
502
+ width: buttonContainerSize,
503
+ height: buttonContainerSize,
504
+ marginLeft: -buttonContainerSize / 2,
505
+ marginTop: -buttonContainerSize / 2,
506
+ display: 'flex',
507
+ alignItems: 'center',
508
+ justifyContent: 'center',
509
+ backgroundColor: dialColors.dialBackground,
510
+ borderRadius: layout.buttonSize,
511
+ overflow: 'hidden',
512
+ transform: `scale(${centerButtonScale * centerPulseScale})`,
513
+ opacity: centerButtonScale > 0.01 ? 1 : 0
514
+ },
515
+ children: [/*#__PURE__*/_jsx("div", {
516
+ style: {
517
+ position: 'absolute',
518
+ inset: 0,
519
+ backgroundColor: dialColors.dialGradient1,
520
+ opacity: 0.5,
521
+ borderRadius: layout.buttonSize
522
+ }
523
+ }), /*#__PURE__*/_jsx("div", {
524
+ style: {
525
+ position: 'absolute',
526
+ inset: 0,
527
+ top: '20%',
528
+ left: '20%',
529
+ backgroundColor: dialColors.dialGradient2,
530
+ opacity: 0.3,
531
+ borderRadius: layout.buttonSize
532
+ }
533
+ }), /*#__PURE__*/_jsx("div", {
534
+ style: {
535
+ position: 'absolute',
536
+ inset: 0,
537
+ top: '40%',
538
+ left: '40%',
539
+ backgroundColor: dialColors.dialGradient3,
540
+ opacity: 0.2,
541
+ borderRadius: layout.buttonSize
542
+ }
543
+ }), /*#__PURE__*/_jsx("div", {
544
+ style: {
545
+ width: buttonBorderSize,
546
+ height: buttonBorderSize,
547
+ borderRadius: layout.buttonSize * dialStyles.centerButton.borderRadiusRatio,
548
+ backgroundColor: dialColors.dialGridLine,
549
+ border: `${dialStyles.centerButton.borderWidth}px solid ${dialColors.dialBorder}`,
550
+ display: 'flex',
551
+ alignItems: 'center',
552
+ justifyContent: 'center'
553
+ },
554
+ children: /*#__PURE__*/_jsx("div", {
555
+ onClick: onCenterPress,
556
+ onMouseEnter: () => setIsHoveringCenter(true),
557
+ onMouseLeave: () => setIsHoveringCenter(false),
558
+ style: {
559
+ width: layout.buttonSize,
560
+ height: layout.buttonSize,
561
+ borderRadius: layout.buttonSize / 2,
562
+ display: 'flex',
563
+ flexDirection: 'column',
564
+ alignItems: 'center',
565
+ justifyContent: 'center',
566
+ cursor: onCenterPress ? 'pointer' : 'default',
567
+ transform: isHoveringCenter ? `scale(${interaction.centerHover.scale})` : 'scale(1)',
568
+ transition: `transform ${interaction.centerHover.duration}ms ease`,
569
+ animation: isAnimating && !isHoveringCenter ? cssAnimations.breathing : 'none'
570
+ },
571
+ children: /*#__PURE__*/_jsx("span", {
572
+ style: {
573
+ fontSize: dialStyles.centerButton.text.fontSize,
574
+ fontWeight: dialStyles.centerButton.text.fontWeight,
575
+ fontFamily: dialStyles.centerButton.text.fontFamily,
576
+ color: dialColors.centerText,
577
+ letterSpacing: dialStyles.centerButton.text.letterSpacing,
578
+ textShadow: `0 0 4px ${dialColors.centerTextGlow}`,
579
+ textTransform: 'uppercase'
580
+ },
581
+ children: centerLabel
582
+ })
583
+ })
584
+ })]
585
+ })]
586
+ })
587
+ }, triggerGlitch)
588
+ })]
589
+ });
590
+ }