@djangocfg/ui-tools 2.1.91
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.
- package/dist/LottiePlayer.client-LBEC2JKY.mjs +161 -0
- package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
- package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
- package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
- package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
- package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
- package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
- package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
- package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
- package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
- package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
- package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
- package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
- package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
- package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
- package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
- package/dist/chunk-37ZI6VD4.mjs +12 -0
- package/dist/chunk-37ZI6VD4.mjs.map +1 -0
- package/dist/chunk-3HK2OE62.cjs +81 -0
- package/dist/chunk-3HK2OE62.cjs.map +1 -0
- package/dist/chunk-7DGDQVQW.cjs +591 -0
- package/dist/chunk-7DGDQVQW.cjs.map +1 -0
- package/dist/chunk-M6P2FU7L.mjs +572 -0
- package/dist/chunk-M6P2FU7L.mjs.map +1 -0
- package/dist/chunk-UQ3XI5MY.cjs +15 -0
- package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
- package/dist/chunk-YFRNE2IR.mjs +79 -0
- package/dist/chunk-YFRNE2IR.mjs.map +1 -0
- package/dist/index.cjs +5042 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1591 -0
- package/dist/index.d.ts +1591 -0
- package/dist/index.mjs +4941 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +86 -0
- package/src/components/markdown/MarkdownMessage.tsx +340 -0
- package/src/components/markdown/index.ts +5 -0
- package/src/index.ts +26 -0
- package/src/stores/index.ts +9 -0
- package/src/stores/mediaCache.ts +534 -0
- package/src/tools/AudioPlayer/README.md +206 -0
- package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
- package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
- package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
- package/src/tools/AudioPlayer/components/index.ts +22 -0
- package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
- package/src/tools/AudioPlayer/context/index.ts +16 -0
- package/src/tools/AudioPlayer/effects/index.ts +412 -0
- package/src/tools/AudioPlayer/hooks/index.ts +35 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
- package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
- package/src/tools/AudioPlayer/index.ts +133 -0
- package/src/tools/AudioPlayer/types/effects.ts +73 -0
- package/src/tools/AudioPlayer/types/index.ts +27 -0
- package/src/tools/AudioPlayer/utils/debug.ts +14 -0
- package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
- package/src/tools/AudioPlayer/utils/index.ts +6 -0
- package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
- package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
- package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
- package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
- package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
- package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
- package/src/tools/ImageViewer/README.md +200 -0
- package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
- package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
- package/src/tools/ImageViewer/components/index.ts +7 -0
- package/src/tools/ImageViewer/hooks/index.ts +9 -0
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
- package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
- package/src/tools/ImageViewer/index.ts +60 -0
- package/src/tools/ImageViewer/types.ts +81 -0
- package/src/tools/ImageViewer/utils/constants.ts +59 -0
- package/src/tools/ImageViewer/utils/debug.ts +14 -0
- package/src/tools/ImageViewer/utils/index.ts +17 -0
- package/src/tools/ImageViewer/utils/lqip.ts +47 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
- package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
- package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
- package/src/tools/JsonForm/index.ts +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
- package/src/tools/JsonForm/templates/index.ts +12 -0
- package/src/tools/JsonForm/types.ts +83 -0
- package/src/tools/JsonForm/utils.ts +213 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
- package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
- package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
- package/src/tools/JsonForm/widgets/index.ts +14 -0
- package/src/tools/JsonTree/index.tsx +243 -0
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
- package/src/tools/LottiePlayer/index.tsx +56 -0
- package/src/tools/LottiePlayer/types.ts +108 -0
- package/src/tools/LottiePlayer/useLottie.ts +164 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
- package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
- package/src/tools/Mermaid/hooks/index.ts +4 -0
- package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
- package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
- package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
- package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
- package/src/tools/Mermaid/index.tsx +44 -0
- package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
- package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
- package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
- package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
- package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
- package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
- package/src/tools/OpenapiViewer/components/index.ts +14 -0
- package/src/tools/OpenapiViewer/constants.ts +39 -0
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
- package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
- package/src/tools/OpenapiViewer/index.tsx +37 -0
- package/src/tools/OpenapiViewer/types.ts +151 -0
- package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
- package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
- package/src/tools/OpenapiViewer/utils/index.ts +9 -0
- package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
- package/src/tools/PrettyCode/index.tsx +47 -0
- package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
- package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
- package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
- package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
- package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
- package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
- package/src/tools/VideoPlayer/README.md +264 -0
- package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
- package/src/tools/VideoPlayer/components/index.ts +14 -0
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
- package/src/tools/VideoPlayer/context/index.ts +8 -0
- package/src/tools/VideoPlayer/hooks/index.ts +12 -0
- package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
- package/src/tools/VideoPlayer/index.ts +77 -0
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
- package/src/tools/VideoPlayer/providers/index.ts +8 -0
- package/src/tools/VideoPlayer/types/index.ts +38 -0
- package/src/tools/VideoPlayer/types/player.ts +116 -0
- package/src/tools/VideoPlayer/types/provider.ts +93 -0
- package/src/tools/VideoPlayer/types/sources.ts +97 -0
- package/src/tools/VideoPlayer/utils/debug.ts +14 -0
- package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
- package/src/tools/VideoPlayer/utils/index.ts +12 -0
- package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
- package/src/tools/_shared.ts +29 -0
- package/src/tools/index.ts +172 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SpotlightEffect - Rotating spotlight with conic gradients
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { cn } from '../../../../_shared';
|
|
8
|
+
import type { calculateSpotlight } from '../../../effects';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// TYPES
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
interface SpotlightEffectProps {
|
|
15
|
+
data: ReturnType<typeof calculateSpotlight>;
|
|
16
|
+
colors: string[];
|
|
17
|
+
blur: string;
|
|
18
|
+
isPlaying: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// COMPONENT
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export function SpotlightEffect({ data, colors, blur, isPlaying }: SpotlightEffectProps) {
|
|
26
|
+
const inset = 'inset' in data ? data.inset : 12;
|
|
27
|
+
const pulseInset = 'pulseInset' in data ? data.pulseInset : 24;
|
|
28
|
+
const ringOpacity = 'ringOpacity' in data ? data.ringOpacity : 0.3;
|
|
29
|
+
const ringScale = 'ringScale' in data ? data.ringScale : 1;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
{/* Rotating conic gradient - reactive size */}
|
|
34
|
+
<div
|
|
35
|
+
className={cn('absolute rounded-xl -z-10', blur)}
|
|
36
|
+
style={{
|
|
37
|
+
inset: `-${inset}px`,
|
|
38
|
+
background: `conic-gradient(
|
|
39
|
+
from ${data.rotation}deg,
|
|
40
|
+
hsl(${colors[0]} / ${data.colors[0]?.opacity || 0.5}),
|
|
41
|
+
hsl(${colors[1] || colors[0]} / ${data.colors[1]?.opacity || 0.7}),
|
|
42
|
+
hsl(${colors[2] || colors[0]} / ${data.colors[2]?.opacity || 0.5}),
|
|
43
|
+
hsl(${colors[0]} / ${data.colors[1]?.opacity || 0.7}),
|
|
44
|
+
hsl(${colors[0]} / ${data.colors[0]?.opacity || 0.5})
|
|
45
|
+
)`,
|
|
46
|
+
opacity: isPlaying ? 1 : 0,
|
|
47
|
+
transition: 'all 0.08s ease-out',
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
{/* Inner border */}
|
|
52
|
+
<div
|
|
53
|
+
className="absolute -inset-1 rounded-lg bg-background -z-10"
|
|
54
|
+
style={{ opacity: isPlaying ? 1 : 0, transition: 'opacity 0.1s' }}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
{/* Bass pulse glow - reactive size */}
|
|
58
|
+
<div
|
|
59
|
+
className={cn('absolute rounded-2xl -z-10', blur)}
|
|
60
|
+
style={{
|
|
61
|
+
inset: `-${pulseInset}px`,
|
|
62
|
+
background: `radial-gradient(circle, hsl(${colors[0]} / 0.7) 0%, hsl(${colors[0]} / 0.3) 50%, transparent 70%)`,
|
|
63
|
+
opacity: isPlaying ? data.pulseOpacity : 0,
|
|
64
|
+
transform: `scale(${data.pulseScale})`,
|
|
65
|
+
transition: 'all 0.08s ease-out',
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
{/* Outer ring glow */}
|
|
70
|
+
<div
|
|
71
|
+
className="absolute rounded-3xl -z-10 blur-2xl"
|
|
72
|
+
style={{
|
|
73
|
+
inset: `-${pulseInset + 30}px`,
|
|
74
|
+
background: `radial-gradient(circle, hsl(${colors[1] || colors[0]} / 0.4) 0%, transparent 60%)`,
|
|
75
|
+
opacity: isPlaying ? ringOpacity : 0,
|
|
76
|
+
transform: `scale(${ringScale})`,
|
|
77
|
+
transition: 'all 0.08s ease-out',
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReactiveCover effects - Audio-reactive visual effects
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { GlowEffect, type GlowEffectData } from './GlowEffect';
|
|
6
|
+
export { OrbsEffect } from './OrbsEffect';
|
|
7
|
+
export { SpotlightEffect } from './SpotlightEffect';
|
|
8
|
+
export { MeshEffect } from './MeshEffect';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPlayer components - Public API
|
|
3
|
+
*
|
|
4
|
+
* Uses HTML5 audio for playback + Web Audio API for visualization.
|
|
5
|
+
* No crackling, native streaming support.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Player components
|
|
9
|
+
export { HybridAudioPlayer, type HybridAudioPlayerProps } from './HybridAudioPlayer';
|
|
10
|
+
export { HybridSimplePlayer, type HybridSimplePlayerProps } from './HybridSimplePlayer';
|
|
11
|
+
export { HybridWaveform, type HybridWaveformProps } from './HybridWaveform';
|
|
12
|
+
|
|
13
|
+
// ReactiveCover
|
|
14
|
+
export {
|
|
15
|
+
AudioReactiveCover,
|
|
16
|
+
type AudioReactiveCoverProps,
|
|
17
|
+
GlowEffect,
|
|
18
|
+
OrbsEffect,
|
|
19
|
+
SpotlightEffect,
|
|
20
|
+
MeshEffect,
|
|
21
|
+
type GlowEffectData,
|
|
22
|
+
} from './ReactiveCover';
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HybridAudioProvider - Context provider for hybrid audio player.
|
|
5
|
+
*
|
|
6
|
+
* Provides audio state, controls, and analysis data to child components.
|
|
7
|
+
* Uses native HTML5 audio for playback with Web Audio API for visualization only.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createContext, useContext, useMemo, useEffect, useRef, type ReactNode } from 'react';
|
|
11
|
+
import {
|
|
12
|
+
useHybridAudio,
|
|
13
|
+
type UseHybridAudioOptions,
|
|
14
|
+
type HybridAudioState,
|
|
15
|
+
type HybridAudioControls,
|
|
16
|
+
type HybridWebAudioAPI,
|
|
17
|
+
} from '../hooks/useHybridAudio';
|
|
18
|
+
import { useHybridAudioAnalysis } from '../hooks/useHybridAudioAnalysis';
|
|
19
|
+
import { useVisualization } from '../hooks/useVisualization';
|
|
20
|
+
import type { AudioLevels } from '../effects';
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// TYPES
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
export interface HybridAudioContextValue {
|
|
27
|
+
// Audio state
|
|
28
|
+
state: HybridAudioState;
|
|
29
|
+
|
|
30
|
+
// Controls
|
|
31
|
+
controls: HybridAudioControls;
|
|
32
|
+
|
|
33
|
+
// Web Audio (for visualizations)
|
|
34
|
+
webAudio: HybridWebAudioAPI;
|
|
35
|
+
|
|
36
|
+
// Audio levels (for reactive effects)
|
|
37
|
+
audioLevels: AudioLevels;
|
|
38
|
+
|
|
39
|
+
// Audio element ref (for custom integrations)
|
|
40
|
+
audioRef: React.RefObject<HTMLAudioElement | null>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// CONTEXT
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
const HybridAudioContext = createContext<HybridAudioContextValue | null>(null);
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// PROVIDER
|
|
51
|
+
// =============================================================================
|
|
52
|
+
|
|
53
|
+
export interface HybridAudioProviderProps extends UseHybridAudioOptions {
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function HybridAudioProvider({ children, ...options }: HybridAudioProviderProps) {
|
|
58
|
+
// Load persisted settings (shared with visualization)
|
|
59
|
+
const { settings: savedSettings, setSetting } = useVisualization();
|
|
60
|
+
|
|
61
|
+
// Use saved settings as initial values
|
|
62
|
+
const effectiveOptions = {
|
|
63
|
+
...options,
|
|
64
|
+
initialVolume: savedSettings.volume,
|
|
65
|
+
loop: savedSettings.isLooping,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const { audioRef, state, controls, webAudio } = useHybridAudio(effectiveOptions);
|
|
69
|
+
|
|
70
|
+
// Track if we've applied initial settings
|
|
71
|
+
const hasAppliedInitialSettings = useRef(false);
|
|
72
|
+
|
|
73
|
+
// Apply saved settings after audio is ready (for hydration timing)
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!state.isReady || hasAppliedInitialSettings.current) return;
|
|
76
|
+
hasAppliedInitialSettings.current = true;
|
|
77
|
+
|
|
78
|
+
// Apply saved volume and loop settings
|
|
79
|
+
controls.setVolume(savedSettings.volume);
|
|
80
|
+
controls.setLoop(savedSettings.isLooping);
|
|
81
|
+
}, [state.isReady, savedSettings, controls]);
|
|
82
|
+
|
|
83
|
+
// Persist settings when they change
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!state.isReady) return;
|
|
86
|
+
|
|
87
|
+
// Only save if values actually changed
|
|
88
|
+
if (state.volume !== savedSettings.volume) {
|
|
89
|
+
setSetting('volume', state.volume);
|
|
90
|
+
}
|
|
91
|
+
if (state.isLooping !== savedSettings.isLooping) {
|
|
92
|
+
setSetting('isLooping', state.isLooping);
|
|
93
|
+
}
|
|
94
|
+
}, [state.isReady, state.volume, state.isLooping, savedSettings, setSetting]);
|
|
95
|
+
|
|
96
|
+
// Audio analysis for reactive effects
|
|
97
|
+
const audioLevels = useHybridAudioAnalysis(webAudio.analyser, state.isPlaying);
|
|
98
|
+
|
|
99
|
+
const value = useMemo<HybridAudioContextValue>(
|
|
100
|
+
() => ({
|
|
101
|
+
state,
|
|
102
|
+
controls,
|
|
103
|
+
webAudio,
|
|
104
|
+
audioLevels,
|
|
105
|
+
audioRef,
|
|
106
|
+
}),
|
|
107
|
+
[state, controls, webAudio, audioLevels, audioRef]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return <HybridAudioContext.Provider value={value}>{children}</HybridAudioContext.Provider>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// HOOKS
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Access full hybrid audio context
|
|
119
|
+
*/
|
|
120
|
+
export function useHybridAudioContext(): HybridAudioContextValue {
|
|
121
|
+
const context = useContext(HybridAudioContext);
|
|
122
|
+
if (!context) {
|
|
123
|
+
throw new Error('useHybridAudioContext must be used within HybridAudioProvider');
|
|
124
|
+
}
|
|
125
|
+
return context;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Access audio state only (read-only)
|
|
130
|
+
*/
|
|
131
|
+
export function useHybridAudioState(): HybridAudioState {
|
|
132
|
+
const { state } = useHybridAudioContext();
|
|
133
|
+
return state;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Access audio controls only (no state re-renders)
|
|
138
|
+
*/
|
|
139
|
+
export function useHybridAudioControls(): HybridAudioControls {
|
|
140
|
+
const { controls } = useHybridAudioContext();
|
|
141
|
+
return controls;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Access audio levels for reactive effects
|
|
146
|
+
*/
|
|
147
|
+
export function useHybridAudioLevels(): AudioLevels {
|
|
148
|
+
const { audioLevels } = useHybridAudioContext();
|
|
149
|
+
return audioLevels;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Access Web Audio API for custom visualizations
|
|
154
|
+
*/
|
|
155
|
+
export function useHybridWebAudio(): HybridWebAudioAPI {
|
|
156
|
+
const { webAudio } = useHybridAudioContext();
|
|
157
|
+
return webAudio;
|
|
158
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPlayer context - Public API
|
|
3
|
+
*
|
|
4
|
+
* HybridAudioProvider: HTML5 audio + Web Audio API for visualization
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
HybridAudioProvider,
|
|
9
|
+
useHybridAudioContext,
|
|
10
|
+
useHybridAudioState,
|
|
11
|
+
useHybridAudioControls,
|
|
12
|
+
useHybridAudioLevels,
|
|
13
|
+
useHybridWebAudio,
|
|
14
|
+
type HybridAudioContextValue,
|
|
15
|
+
type HybridAudioProviderProps,
|
|
16
|
+
} from './HybridAudioProvider';
|
|
@@ -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
|
+
`;
|