@djangocfg/ui-nextjs 2.1.65 → 2.1.67

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 (92) hide show
  1. package/package.json +13 -8
  2. package/src/blocks/SplitHero/SplitHeroMedia.tsx +2 -1
  3. package/src/stores/index.ts +8 -0
  4. package/src/stores/mediaCache.ts +464 -0
  5. package/src/tools/AudioPlayer/@refactoring/00-PLAN.md +148 -0
  6. package/src/tools/AudioPlayer/@refactoring/01-TYPES.md +301 -0
  7. package/src/tools/AudioPlayer/@refactoring/02-HOOKS.md +281 -0
  8. package/src/tools/AudioPlayer/@refactoring/03-CONTEXT.md +328 -0
  9. package/src/tools/AudioPlayer/@refactoring/04-COMPONENTS.md +251 -0
  10. package/src/tools/AudioPlayer/@refactoring/05-EFFECTS.md +427 -0
  11. package/src/tools/AudioPlayer/@refactoring/06-UTILS-AND-INDEX.md +193 -0
  12. package/src/tools/AudioPlayer/@refactoring/07-EXECUTION-CHECKLIST.md +146 -0
  13. package/src/tools/AudioPlayer/README.md +325 -0
  14. package/src/tools/AudioPlayer/components/AudioEqualizer.tsx +200 -0
  15. package/src/tools/AudioPlayer/components/AudioPlayer.tsx +231 -0
  16. package/src/tools/AudioPlayer/components/AudioShortcutsPopover.tsx +99 -0
  17. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +147 -0
  18. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  19. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  20. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  21. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  22. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  23. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  24. package/src/tools/AudioPlayer/components/SimpleAudioPlayer.tsx +280 -0
  25. package/src/tools/AudioPlayer/components/VisualizationToggle.tsx +64 -0
  26. package/src/tools/AudioPlayer/components/index.ts +21 -0
  27. package/src/tools/AudioPlayer/context/AudioProvider.tsx +292 -0
  28. package/src/tools/AudioPlayer/context/index.ts +11 -0
  29. package/src/tools/AudioPlayer/context/selectors.ts +96 -0
  30. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  31. package/src/tools/AudioPlayer/hooks/index.ts +29 -0
  32. package/src/tools/AudioPlayer/hooks/useAudioAnalysis.ts +110 -0
  33. package/src/tools/AudioPlayer/hooks/useAudioHotkeys.ts +149 -0
  34. package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +106 -0
  35. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +201 -0
  36. package/src/tools/AudioPlayer/index.ts +139 -0
  37. package/src/tools/AudioPlayer/types/audio.ts +107 -0
  38. package/src/tools/AudioPlayer/types/components.ts +98 -0
  39. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  40. package/src/tools/AudioPlayer/types/index.ts +35 -0
  41. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  42. package/src/tools/AudioPlayer/utils/index.ts +5 -0
  43. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  44. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  45. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  46. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  47. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  48. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  49. package/src/tools/ImageViewer/README.md +174 -0
  50. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  51. package/src/tools/ImageViewer/components/ImageToolbar.tsx +150 -0
  52. package/src/tools/ImageViewer/components/ImageViewer.tsx +235 -0
  53. package/src/tools/ImageViewer/components/index.ts +7 -0
  54. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  55. package/src/tools/ImageViewer/hooks/useImageLoading.ts +153 -0
  56. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  57. package/src/tools/ImageViewer/index.ts +60 -0
  58. package/src/tools/ImageViewer/types.ts +75 -0
  59. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  60. package/src/tools/ImageViewer/utils/index.ts +16 -0
  61. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  62. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  63. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  64. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  65. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  66. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  67. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  68. package/src/tools/VideoPlayer/README.md +212 -187
  69. package/src/tools/VideoPlayer/{VideoControls.tsx → components/VideoControls.tsx} +8 -9
  70. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +174 -0
  71. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  72. package/src/tools/VideoPlayer/components/index.ts +14 -0
  73. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  74. package/src/tools/VideoPlayer/context/index.ts +8 -0
  75. package/src/tools/VideoPlayer/hooks/index.ts +9 -0
  76. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +109 -0
  77. package/src/tools/VideoPlayer/index.ts +70 -9
  78. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +206 -0
  79. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +401 -0
  80. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +332 -0
  81. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  82. package/src/tools/VideoPlayer/types/index.ts +38 -0
  83. package/src/tools/VideoPlayer/types/player.ts +116 -0
  84. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  85. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  86. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  87. package/src/tools/VideoPlayer/utils/index.ts +11 -0
  88. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  89. package/src/tools/index.ts +92 -4
  90. package/src/tools/VideoPlayer/NativePlayer.tsx +0 -141
  91. package/src/tools/VideoPlayer/VideoPlayer.tsx +0 -231
  92. package/src/tools/VideoPlayer/types.ts +0 -118
@@ -0,0 +1,412 @@
1
+ /**
2
+ * Audio Reactive Effects - Common utilities and types
3
+ *
4
+ * Provides reusable effect calculations and configurations
5
+ */
6
+
7
+ // =============================================================================
8
+ // TYPES
9
+ // =============================================================================
10
+
11
+ export type EffectVariant = 'glow' | 'orbs' | 'spotlight' | 'mesh';
12
+ export type EffectIntensity = 'subtle' | 'medium' | 'strong';
13
+ export type EffectColorScheme = 'primary' | 'vibrant' | 'cool' | 'warm';
14
+
15
+ export interface AudioLevels {
16
+ bass: number;
17
+ mid: number;
18
+ high: number;
19
+ overall: number;
20
+ }
21
+
22
+ export interface EffectConfig {
23
+ opacity: number;
24
+ scale: number;
25
+ blur: string;
26
+ }
27
+
28
+ export interface EffectColors {
29
+ colors: string[];
30
+ hueShift: number;
31
+ }
32
+
33
+ export interface EffectLayer {
34
+ inset: number;
35
+ opacity: number;
36
+ scale: number;
37
+ background: string;
38
+ blur: string;
39
+ animation?: string;
40
+ }
41
+
42
+ // =============================================================================
43
+ // CONSTANTS
44
+ // =============================================================================
45
+
46
+ export const INTENSITY_CONFIG: Record<EffectIntensity, EffectConfig> = {
47
+ subtle: { opacity: 0.3, scale: 0.02, blur: 'blur-2xl' },
48
+ medium: { opacity: 0.5, scale: 0.04, blur: 'blur-xl' },
49
+ strong: { opacity: 0.7, scale: 0.06, blur: 'blur-lg' },
50
+ };
51
+
52
+ export const COLOR_SCHEMES: Record<EffectColorScheme, string[]> = {
53
+ primary: ['217 91% 60%'],
54
+ vibrant: ['217 91% 60%', '142 76% 36%', '262 83% 58%', '25 95% 53%'],
55
+ cool: ['217 91% 60%', '262 83% 58%', '199 89% 48%'],
56
+ warm: ['25 95% 53%', '0 84% 60%', '38 92% 50%'],
57
+ };
58
+
59
+ // Default multi-color palette when single color provided
60
+ const DEFAULT_GLOW_COLORS = [
61
+ '217 91% 60%', // Blue
62
+ '262 83% 58%', // Purple
63
+ '330 81% 60%', // Pink
64
+ '25 95% 53%', // Orange
65
+ ];
66
+
67
+ // =============================================================================
68
+ // UTILITIES
69
+ // =============================================================================
70
+
71
+ /**
72
+ * Get effect configuration from intensity setting
73
+ */
74
+ export function getEffectConfig(intensity: EffectIntensity): EffectConfig {
75
+ return INTENSITY_CONFIG[intensity];
76
+ }
77
+
78
+ /**
79
+ * Get color array from color scheme
80
+ */
81
+ export function getColors(colorScheme: EffectColorScheme): string[] {
82
+ return COLOR_SCHEMES[colorScheme];
83
+ }
84
+
85
+ /**
86
+ * Prepare colors with hue shift based on audio levels
87
+ */
88
+ export function prepareEffectColors(
89
+ colorScheme: EffectColorScheme,
90
+ levels: AudioLevels
91
+ ): EffectColors {
92
+ const baseColors = COLOR_SCHEMES[colorScheme];
93
+ const colors = baseColors.length > 1 ? baseColors : DEFAULT_GLOW_COLORS;
94
+ const hueShift = Math.floor(
95
+ (levels.bass * 30) + (levels.mid * 20) + (levels.high * 10)
96
+ );
97
+
98
+ return { colors, hueShift };
99
+ }
100
+
101
+ /**
102
+ * Calculate glow layer properties
103
+ */
104
+ export function calculateGlowLayers(
105
+ levels: AudioLevels,
106
+ config: EffectConfig,
107
+ colors: string[]
108
+ ): EffectLayer[] {
109
+ const { bass, mid, high } = levels;
110
+
111
+ return [
112
+ // Layer 1: Bass glow - bottom
113
+ {
114
+ inset: 60 + bass * 90,
115
+ opacity: 1,
116
+ scale: 1 + bass * 0.5,
117
+ background: `radial-gradient(ellipse 80% 60% at 50% 100%, hsl(${colors[0]} / ${0.4 + bass * 0.4}) 0%, transparent 70%)`,
118
+ blur: 'blur-3xl',
119
+ },
120
+ // Layer 2: Mid glow - top
121
+ {
122
+ inset: 45 + mid * 75,
123
+ opacity: 1,
124
+ scale: 1 + mid * 0.4,
125
+ background: `radial-gradient(ellipse 70% 50% at 50% 0%, hsl(${colors[1] || colors[0]} / ${0.3 + mid * 0.5}) 0%, transparent 70%)`,
126
+ blur: 'blur-2xl',
127
+ },
128
+ // Layer 3: High glow - left
129
+ {
130
+ inset: 30 + high * 60,
131
+ opacity: 1,
132
+ scale: 1 + high * 0.3,
133
+ background: `radial-gradient(ellipse 50% 80% at 0% 50%, hsl(${colors[2] || colors[0]} / ${0.3 + high * 0.4}) 0%, transparent 60%)`,
134
+ blur: 'blur-2xl',
135
+ },
136
+ // Layer 4: High glow - right
137
+ {
138
+ inset: 30 + high * 60,
139
+ opacity: 1,
140
+ scale: 1 + high * 0.3,
141
+ background: `radial-gradient(ellipse 50% 80% at 100% 50%, hsl(${colors[3] || colors[0]} / ${0.3 + high * 0.4}) 0%, transparent 60%)`,
142
+ blur: 'blur-2xl',
143
+ },
144
+ // Layer 5: Center pulsing glow
145
+ {
146
+ inset: 24 + bass * 45,
147
+ opacity: 1,
148
+ scale: 1 + bass * 0.2,
149
+ background: `radial-gradient(circle at 50% 50%, hsl(${colors[0]} / ${0.2 + bass * 0.3}) 0%, hsl(${colors[1] || colors[0]} / ${0.1 + mid * 0.2}) 40%, transparent 70%)`,
150
+ blur: 'blur-xl',
151
+ animation: 'glow-breathe 2s ease-in-out infinite',
152
+ },
153
+ ];
154
+ }
155
+
156
+ /**
157
+ * Calculate orb positions and properties - highly reactive
158
+ */
159
+ export function calculateOrbs(
160
+ levels: AudioLevels,
161
+ config: EffectConfig,
162
+ colors: string[],
163
+ baseSize: number = 50
164
+ ) {
165
+ const { bass, mid, high, overall } = levels;
166
+ const size = baseSize * 3;
167
+
168
+ // Dynamic position offsets
169
+ const bassMove = bass * 30;
170
+ const midMove = mid * 25;
171
+ const highMove = high * 20;
172
+
173
+ return [
174
+ // Bass orb - top left, big pulses
175
+ {
176
+ x: -40 + bassMove,
177
+ y: -40 - bassMove * 0.5,
178
+ size: size * (1 + bass * 1.2),
179
+ color: colors[0],
180
+ opacity: 0.5 + bass * 0.5,
181
+ scale: 1 + bass * 0.6,
182
+ },
183
+ // Mid orb - top right
184
+ {
185
+ x: 130 - midMove * 0.5,
186
+ y: -30 + midMove,
187
+ size: size * (0.9 + mid * 1.0),
188
+ color: colors[1] || colors[0],
189
+ opacity: 0.5 + mid * 0.5,
190
+ scale: 1 + mid * 0.5,
191
+ },
192
+ // High orb - bottom right
193
+ {
194
+ x: 140 + highMove * 0.3,
195
+ y: 120 - highMove,
196
+ size: size * (0.8 + high * 0.8),
197
+ color: colors[2] || colors[0],
198
+ opacity: 0.4 + high * 0.6,
199
+ scale: 1 + high * 0.45,
200
+ },
201
+ // Mid orb 2 - bottom left
202
+ {
203
+ x: -30 - midMove * 0.4,
204
+ y: 130 + midMove * 0.3,
205
+ size: size * (0.9 + mid * 0.9),
206
+ color: colors[3] || colors[0],
207
+ opacity: 0.45 + mid * 0.55,
208
+ scale: 1 + mid * 0.5,
209
+ },
210
+ // Center overall orb
211
+ {
212
+ x: 50,
213
+ y: 50,
214
+ size: size * (0.6 + overall * 1.5),
215
+ color: colors[0],
216
+ opacity: 0.3 + overall * 0.5,
217
+ scale: 1 + overall * 0.7,
218
+ },
219
+ ];
220
+ }
221
+
222
+ /**
223
+ * Calculate mesh gradient positions - dynamic and reactive
224
+ */
225
+ export function calculateMeshGradients(
226
+ levels: AudioLevels,
227
+ config: EffectConfig,
228
+ colors: string[]
229
+ ) {
230
+ const { bass, mid, high, overall } = levels;
231
+
232
+ // More aggressive offsets for visible movement
233
+ const bassOffset = bass * 40;
234
+ const midOffset = mid * 30;
235
+ const highOffset = high * 25;
236
+
237
+ return [
238
+ // Large bass blob - top right, pulses hard
239
+ {
240
+ width: `${200 + bass * 150}%`,
241
+ height: `${200 + bass * 150}%`,
242
+ top: `${-80 + bassOffset}%`,
243
+ right: `${-80 - bassOffset}%`,
244
+ color: colors[0],
245
+ opacity: 0.4 + bass * 0.6,
246
+ scale: 1 + bass * 0.5,
247
+ rotation: bass * 45,
248
+ blur: 'blur-2xl',
249
+ },
250
+ // Mid blob - bottom left
251
+ {
252
+ width: `${180 + mid * 120}%`,
253
+ height: `${180 + mid * 120}%`,
254
+ bottom: `${-60 + midOffset}%`,
255
+ left: `${-60 - midOffset}%`,
256
+ color: colors[1] || colors[0],
257
+ opacity: 0.4 + mid * 0.6,
258
+ scale: 1 + mid * 0.4,
259
+ rotation: -mid * 40,
260
+ blur: 'blur-2xl',
261
+ },
262
+ // High blob - bottom right
263
+ {
264
+ width: `${140 + high * 100}%`,
265
+ height: `${140 + high * 100}%`,
266
+ top: `${70 - highOffset}%`,
267
+ right: `${-50 + highOffset}%`,
268
+ color: colors[2] || colors[0],
269
+ opacity: 0.35 + high * 0.65,
270
+ scale: 1 + high * 0.35,
271
+ rotation: high * 35,
272
+ blur: 'blur-xl',
273
+ },
274
+ // Extra bass reactive blob - top left
275
+ {
276
+ width: `${160 + bass * 140}%`,
277
+ height: `${160 + bass * 140}%`,
278
+ top: `${-60 - bassOffset * 0.8}%`,
279
+ left: `${-60 + bassOffset * 0.8}%`,
280
+ color: colors[3] || colors[1] || colors[0],
281
+ opacity: 0.35 + bass * 0.65,
282
+ scale: 1 + bass * 0.55,
283
+ rotation: -bass * 50,
284
+ blur: 'blur-2xl',
285
+ },
286
+ // Center glow - pulses with overall
287
+ {
288
+ width: `${80 + overall * 150}%`,
289
+ height: `${80 + overall * 150}%`,
290
+ top: '50%',
291
+ left: '50%',
292
+ color: colors[0],
293
+ opacity: 0.3 + overall * 0.5,
294
+ scale: 1 + overall * 0.4,
295
+ rotation: 0,
296
+ isCenter: true,
297
+ blur: 'blur-3xl',
298
+ },
299
+ ];
300
+ }
301
+
302
+ /**
303
+ * Calculate spotlight effect properties - highly reactive
304
+ */
305
+ export function calculateSpotlight(
306
+ levels: AudioLevels,
307
+ config: EffectConfig,
308
+ colors: string[],
309
+ rotation: number
310
+ ) {
311
+ const { bass, mid, high, overall } = levels;
312
+
313
+ return {
314
+ // Rotation speed increases with mid frequencies
315
+ rotation: rotation + mid * 180,
316
+ // Border expands with bass
317
+ inset: 12 + bass * 30,
318
+ // Color intensities react to different frequencies
319
+ colors: colors.map((c, i) => ({
320
+ color: c,
321
+ opacity: i === 0
322
+ ? 0.3 + bass * 0.7
323
+ : i === 1
324
+ ? 0.3 + mid * 0.7
325
+ : 0.3 + high * 0.7,
326
+ })),
327
+ // Pulse glow - big and reactive
328
+ pulseInset: 24 + bass * 50,
329
+ pulseOpacity: 0.3 + bass * 0.7,
330
+ pulseScale: 1 + bass * 0.4,
331
+ // Extra glow ring
332
+ ringOpacity: 0.2 + overall * 0.6,
333
+ ringScale: 1 + overall * 0.3,
334
+ };
335
+ }
336
+
337
+ /**
338
+ * CSS for effect animations - can be injected once
339
+ */
340
+ export const EFFECT_ANIMATIONS = `
341
+ @keyframes spotlight-spin {
342
+ 0% { transform: rotate(0deg); }
343
+ 100% { transform: rotate(360deg); }
344
+ }
345
+
346
+ @keyframes orb-float-1 {
347
+ 0%, 100% { transform: translate(-50%, -50%) translateY(0); }
348
+ 50% { transform: translate(-50%, -50%) translateY(-15px); }
349
+ }
350
+
351
+ @keyframes orb-float-2 {
352
+ 0%, 100% { transform: translate(-50%, -50%) translateX(0); }
353
+ 50% { transform: translate(-50%, -50%) translateX(15px); }
354
+ }
355
+
356
+ @keyframes orb-float-3 {
357
+ 0%, 100% { transform: translate(-50%, -50%) translate(0, 0); }
358
+ 33% { transform: translate(-50%, -50%) translate(10px, -10px); }
359
+ 66% { transform: translate(-50%, -50%) translate(-10px, 10px); }
360
+ }
361
+
362
+ @keyframes orb-float-4 {
363
+ 0%, 100% { transform: translate(-50%, -50%) translate(0, 0); }
364
+ 50% { transform: translate(-50%, -50%) translate(-15px, -10px); }
365
+ }
366
+
367
+ @keyframes mesh-float-1 {
368
+ 0%, 100% { transform: translate(0, 0) scale(1); }
369
+ 25% { transform: translate(-5%, 10%) scale(1.05); }
370
+ 50% { transform: translate(5%, 5%) scale(0.95); }
371
+ 75% { transform: translate(-3%, -5%) scale(1.02); }
372
+ }
373
+
374
+ @keyframes mesh-float-2 {
375
+ 0%, 100% { transform: translate(0, 0) scale(1); }
376
+ 33% { transform: translate(8%, -8%) scale(1.08); }
377
+ 66% { transform: translate(-6%, 6%) scale(0.92); }
378
+ }
379
+
380
+ @keyframes mesh-float-3 {
381
+ 0%, 100% { transform: translate(0, 0) scale(1); }
382
+ 50% { transform: translate(10%, 10%) scale(1.1); }
383
+ }
384
+
385
+ @keyframes mesh-float-4 {
386
+ 0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
387
+ 25% { transform: translate(10%, -5%) scale(1.1) rotate(5deg); }
388
+ 50% { transform: translate(-5%, 10%) scale(0.95) rotate(-5deg); }
389
+ 75% { transform: translate(-10%, -10%) scale(1.05) rotate(3deg); }
390
+ }
391
+
392
+ @keyframes mesh-pulse {
393
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
394
+ 50% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.5; }
395
+ }
396
+
397
+ @keyframes glow-breathe {
398
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
399
+ 50% { opacity: 1; transform: scale(1.05); }
400
+ }
401
+
402
+ @keyframes glow-rotate {
403
+ 0% { transform: rotate(0deg); }
404
+ 100% { transform: rotate(360deg); }
405
+ }
406
+
407
+ @keyframes sparkle-move {
408
+ 0% { opacity: 0; transform: scale(0.8); }
409
+ 50% { opacity: 1; }
410
+ 100% { opacity: 0; transform: scale(1.2); }
411
+ }
412
+ `;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * AudioPlayer hooks - re-exports all hooks
3
+ */
4
+
5
+ // Internal hooks (used by provider)
6
+ export { useSharedWebAudio } from './useSharedWebAudio';
7
+ export { useAudioAnalysis } from './useAudioAnalysis';
8
+
9
+ // Public hooks
10
+ export { useAudioHotkeys, AUDIO_SHORTCUTS } from './useAudioHotkeys';
11
+ export type { AudioHotkeyOptions, ShortcutItem, ShortcutGroup } from './useAudioHotkeys';
12
+
13
+ export {
14
+ useVisualization,
15
+ useAudioVisualization, // backward compat alias
16
+ VisualizationProvider,
17
+ VARIANT_INFO,
18
+ INTENSITY_INFO,
19
+ COLOR_SCHEME_INFO,
20
+ } from './useVisualization';
21
+ export type {
22
+ VisualizationSettings,
23
+ VisualizationVariant,
24
+ VisualizationIntensity,
25
+ VisualizationColorScheme,
26
+ UseVisualizationReturn,
27
+ UseAudioVisualizationReturn,
28
+ VisualizationProviderProps,
29
+ } from './useVisualization';
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * useAudioAnalysis - Real-time audio frequency analysis for reactive effects.
5
+ *
6
+ * Analyzes audio frequencies for bass, mid, high, and overall levels.
7
+ * Uses shared Web Audio context to prevent duplicate source nodes.
8
+ */
9
+
10
+ import { useState, useRef, useEffect, useCallback } from 'react';
11
+ import type { SharedWebAudioContext } from '../types';
12
+ import type { AudioLevels } from '../effects';
13
+
14
+ export function useAudioAnalysis(
15
+ sharedAudio: SharedWebAudioContext,
16
+ isPlaying: boolean
17
+ ): AudioLevels {
18
+ const [levels, setLevels] = useState<AudioLevels>({ bass: 0, mid: 0, high: 0, overall: 0 });
19
+ const analyserRef = useRef<AnalyserNode | null>(null);
20
+ const animationRef = useRef<number | null>(null);
21
+ const dataArrayRef = useRef<Uint8Array | null>(null);
22
+
23
+ const cleanup = useCallback(() => {
24
+ if (animationRef.current) {
25
+ cancelAnimationFrame(animationRef.current);
26
+ animationRef.current = null;
27
+ }
28
+ }, []);
29
+
30
+ // Create analyser when shared audio is ready
31
+ useEffect(() => {
32
+ if (!sharedAudio.sourceNode || analyserRef.current) return;
33
+
34
+ const analyser = sharedAudio.createAnalyser({ fftSize: 256, smoothing: 0.85 });
35
+ if (analyser) {
36
+ analyserRef.current = analyser;
37
+ dataArrayRef.current = new Uint8Array(analyser.frequencyBinCount);
38
+ }
39
+
40
+ return () => {
41
+ if (analyserRef.current) {
42
+ sharedAudio.disconnectAnalyser(analyserRef.current);
43
+ analyserRef.current = null;
44
+ dataArrayRef.current = null;
45
+ }
46
+ };
47
+ }, [sharedAudio.sourceNode, sharedAudio.createAnalyser, sharedAudio.disconnectAnalyser]);
48
+
49
+ // Animation loop
50
+ useEffect(() => {
51
+ if (!isPlaying || !analyserRef.current || !dataArrayRef.current) {
52
+ cleanup();
53
+ // Smooth fade out
54
+ setLevels(prev => ({
55
+ bass: prev.bass * 0.95 < 0.01 ? 0 : prev.bass * 0.95,
56
+ mid: prev.mid * 0.95,
57
+ high: prev.high * 0.95,
58
+ overall: prev.overall * 0.95,
59
+ }));
60
+ return;
61
+ }
62
+
63
+ const analyser = analyserRef.current;
64
+ const dataArray = dataArrayRef.current;
65
+
66
+ const animate = () => {
67
+ analyser.getByteFrequencyData(dataArray as Uint8Array<ArrayBuffer>);
68
+ const binCount = dataArray.length;
69
+
70
+ // Bass (0-15%)
71
+ const bassEnd = Math.floor(binCount * 0.15);
72
+ let bassSum = 0;
73
+ for (let i = 0; i < bassEnd; i++) bassSum += dataArray[i];
74
+ const bass = bassSum / bassEnd / 255;
75
+
76
+ // Mids (15-50%)
77
+ const midStart = bassEnd;
78
+ const midEnd = Math.floor(binCount * 0.5);
79
+ let midSum = 0;
80
+ for (let i = midStart; i < midEnd; i++) midSum += dataArray[i];
81
+ const mid = midSum / (midEnd - midStart) / 255;
82
+
83
+ // Highs (50-100%)
84
+ const highStart = midEnd;
85
+ let highSum = 0;
86
+ for (let i = highStart; i < binCount; i++) highSum += dataArray[i];
87
+ const high = highSum / (binCount - highStart) / 255;
88
+
89
+ // Overall
90
+ let totalSum = 0;
91
+ for (let i = 0; i < binCount; i++) totalSum += dataArray[i];
92
+ const overall = totalSum / binCount / 255;
93
+
94
+ // Smooth with lerp
95
+ setLevels(prev => ({
96
+ bass: prev.bass * 0.7 + bass * 0.3,
97
+ mid: prev.mid * 0.7 + mid * 0.3,
98
+ high: prev.high * 0.7 + high * 0.3,
99
+ overall: prev.overall * 0.7 + overall * 0.3,
100
+ }));
101
+
102
+ animationRef.current = requestAnimationFrame(animate);
103
+ };
104
+
105
+ animationRef.current = requestAnimationFrame(animate);
106
+ return cleanup;
107
+ }, [isPlaying, cleanup]);
108
+
109
+ return levels;
110
+ }
@@ -0,0 +1,149 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * useAudioHotkeys - Keyboard shortcuts for audio playback control.
5
+ *
6
+ * Uses useHotkey from @djangocfg/ui-nextjs.
7
+ */
8
+
9
+ import { useHotkey, useDeviceDetect } from '@djangocfg/ui-nextjs';
10
+ import { useAudioControls, useAudioState } from '../context';
11
+
12
+ // =============================================================================
13
+ // TYPES
14
+ // =============================================================================
15
+
16
+ export interface AudioHotkeyOptions {
17
+ /** Enable hotkeys (default: true) */
18
+ enabled?: boolean;
19
+ /** Skip duration in seconds (default: 10) */
20
+ skipDuration?: number;
21
+ /** Volume step (default: 0.1) */
22
+ volumeStep?: number;
23
+ }
24
+
25
+ // =============================================================================
26
+ // HOOK
27
+ // =============================================================================
28
+
29
+ export function useAudioHotkeys(options: AudioHotkeyOptions = {}) {
30
+ const { enabled = true, skipDuration = 10, volumeStep = 0.1 } = options;
31
+
32
+ const { togglePlay, skip, setVolume, toggleMute, toggleLoop, isReady } = useAudioControls();
33
+ const { volume, duration } = useAudioState();
34
+ const device = useDeviceDetect();
35
+
36
+ // Play/Pause - Space
37
+ useHotkey(
38
+ 'space',
39
+ (e) => {
40
+ e.preventDefault();
41
+ togglePlay();
42
+ },
43
+ { enabled: enabled && isReady, description: 'Play/Pause' }
44
+ );
45
+
46
+ // Skip backward - ArrowLeft or J
47
+ useHotkey(
48
+ ['ArrowLeft', 'j'],
49
+ () => skip(-skipDuration),
50
+ { enabled: enabled && isReady, description: `Skip ${skipDuration}s backward` }
51
+ );
52
+
53
+ // Skip forward - ArrowRight or L
54
+ useHotkey(
55
+ ['ArrowRight', 'l'],
56
+ () => skip(skipDuration),
57
+ { enabled: enabled && isReady, description: `Skip ${skipDuration}s forward` }
58
+ );
59
+
60
+ // Volume up - ArrowUp
61
+ useHotkey(
62
+ 'ArrowUp',
63
+ (e) => {
64
+ e.preventDefault();
65
+ setVolume(Math.min(1, volume + volumeStep));
66
+ },
67
+ { enabled: enabled && isReady, description: 'Volume up' }
68
+ );
69
+
70
+ // Volume down - ArrowDown
71
+ useHotkey(
72
+ 'ArrowDown',
73
+ (e) => {
74
+ e.preventDefault();
75
+ setVolume(Math.max(0, volume - volumeStep));
76
+ },
77
+ { enabled: enabled && isReady, description: 'Volume down' }
78
+ );
79
+
80
+ // Mute/Unmute - M
81
+ useHotkey(
82
+ 'm',
83
+ () => toggleMute(),
84
+ { enabled: enabled && isReady, description: 'Mute/Unmute' }
85
+ );
86
+
87
+ // Loop/Repeat - L key (conflicts with skip forward, using Shift+L)
88
+ useHotkey(
89
+ 'shift+l',
90
+ () => toggleLoop(),
91
+ { enabled: enabled && isReady, description: 'Toggle loop' }
92
+ );
93
+
94
+ // Number keys 0-9 to seek to percentage
95
+ useHotkey(
96
+ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
97
+ (e) => {
98
+ if (!duration) return;
99
+ const percent = parseInt(e.key, 10) / 10;
100
+ skip(duration * percent - duration * (volume || 0));
101
+ },
102
+ { enabled: enabled && isReady, description: 'Seek to percentage' }
103
+ );
104
+
105
+ return {
106
+ isMac: device.isMacOs,
107
+ isEnabled: enabled && isReady,
108
+ };
109
+ }
110
+
111
+ // =============================================================================
112
+ // SHORTCUTS CONFIG (for display)
113
+ // =============================================================================
114
+
115
+ export interface ShortcutItem {
116
+ keys: string[];
117
+ label: string;
118
+ }
119
+
120
+ export interface ShortcutGroup {
121
+ title: string;
122
+ shortcuts: ShortcutItem[];
123
+ }
124
+
125
+ export const AUDIO_SHORTCUTS: ShortcutGroup[] = [
126
+ {
127
+ title: 'Playback',
128
+ shortcuts: [
129
+ { keys: ['Space'], label: 'Play/Pause' },
130
+ { keys: ['←'], label: 'Skip 10s back' },
131
+ { keys: ['→'], label: 'Skip 10s forward' },
132
+ { keys: ['⇧', 'L'], label: 'Toggle loop' },
133
+ ],
134
+ },
135
+ {
136
+ title: 'Volume',
137
+ shortcuts: [
138
+ { keys: ['↑'], label: 'Volume up' },
139
+ { keys: ['↓'], label: 'Volume down' },
140
+ { keys: ['M'], label: 'Mute/Unmute' },
141
+ ],
142
+ },
143
+ {
144
+ title: 'Seek',
145
+ shortcuts: [
146
+ { keys: ['0-9'], label: 'Jump to 0-90%' },
147
+ ],
148
+ },
149
+ ];