Sam5920 commited on
Commit
56504cf
·
verified ·
1 Parent(s): e9f5fe6

a dancing cartoon

Browse files
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Groovetoon Interactive
3
- emoji: 📊
4
- colorFrom: green
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: GrooveToon Interactive 🎭
3
+ colorFrom: gray
4
+ colorTo: yellow
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/groove-character.js ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GrooveToon Character Component - Animated SVG Character
2
+ class GrooveCharacter extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.mood = 'happy';
6
+ }
7
+
8
+ connectedCallback() {
9
+ this.attachShadow({ mode: 'open' });
10
+ this.render();
11
+ this.setupInteractions();
12
+ }
13
+
14
+ render() {
15
+ this.shadowRoot.innerHTML = `
16
+ <style>
17
+ :host {
18
+ display: block;
19
+ width: 320px;
20
+ height: 420px;
21
+ position: relative;
22
+ }
23
+
24
+ .character-container {
25
+ width: 100%;
26
+ height: 100%;
27
+ position: relative;
28
+ }
29
+
30
+ /* Body Parts with Animation Support */
31
+ .char-svg {
32
+ width: 100%;
33
+ height: 100%;
34
+ filter: drop-shadow(0 20px 40px rgba(244, 63, 94, 0.3));
35
+ }
36
+
37
+ #char-body {
38
+ transform-origin: center bottom;
39
+ transition: filter 0.1s ease;
40
+ }
41
+
42
+ #char-head {
43
+ transform-origin: center bottom;
44
+ }
45
+
46
+ #char-arm-left, #char-arm-right {
47
+ transform-origin: top center;
48
+ }
49
+
50
+ #char-leg-left, #char-leg-right {
51
+ transform-origin: top center;
52
+ }
53
+
54
+ /* Mood-Based Face Styles */
55
+ .face-happy .eye { ry: 8; }
56
+ .face-happy .mouth {
57
+ d: path("M 95 130 Q 120 150 145 130");
58
+ }
59
+
60
+ .face-excited .eye {
61
+ ry: 12;
62
+ rx: 10;
63
+ }
64
+ .face-excited .mouth {
65
+ d: path("M 100 125 Q 120 160 140 125 Z");
66
+ }
67
+
68
+ .face-chill .eye { ry: 4; }
69
+ .face-chill .mouth {
70
+ d: path("M 105 135 Q 120 140 135 135");
71
+ }
72
+
73
+ .face-hype .eye {
74
+ ry: 6;
75
+ rx: 12;
76
+ }
77
+ .face-hype .mouth {
78
+ d: path("M 90 120 Q 120 170 150 120");
79
+ }
80
+
81
+ /* Gradient Definitions */
82
+ .grad-primary { fill: url(#gradPrimary); }
83
+ .grad-secondary { fill: url(#gradSecondary); }
84
+ .grad-accent { fill: url(#gradAccent); }
85
+
86
+ /* Interactive Hover States */
87
+ .character-container:hover #char-body {
88
+ filter: brightness(1.1);
89
+ }
90
+
91
+ /* Glow Effects */
92
+ .glow-layer {
93
+ position: absolute;
94
+ inset: -20px;
95
+ background: radial-gradient(ellipse at center, rgba(244, 63, 94, 0.2) 0%, transparent 70%);
96
+ pointer-events: none;
97
+ opacity: 0.5;
98
+ animation: glow-pulse 2s ease-in-out infinite;
99
+ }
100
+
101
+ @keyframes glow-pulse {
102
+ 0%, 100% { opacity: 0.3; transform: scale(1); }
103
+ 50% { opacity: 0.6; transform: scale(1.02); }
104
+ }
105
+
106
+ /* Floating particles around character */
107
+ .orbit-particle {
108
+ position: absolute;
109
+ width: 12px;
110
+ height: 12px;
111
+ border-radius: 50%;
112
+ background: linear-gradient(135deg, #f43f5e, #8b5cf6);
113
+ animation: orbit 4s linear infinite;
114
+ }
115
+
116
+ .orbit-particle:nth-child(2) {
117
+ animation-delay: -1.5s;
118
+ width: 8px;
119
+ height: 8px;
120
+ }
121
+
122
+ .orbit-particle:nth-child(3) {
123
+ animation-delay: -3s;
124
+ width: 6px;
125
+ height: 6px;
126
+ }
127
+
128
+ @keyframes orbit {
129
+ 0% { transform: rotate(0deg) translateX(180px) rotate(0deg); }
130
+ 100% { transform: rotate(360deg) translateX(180px) rotate(-360deg); }
131
+ }
132
+ </style>
133
+
134
+ <div class="character-container">
135
+ <!-- Orbiting Particles -->
136
+ <div class="orbit-particle" style="left: 50%; top: 50%;"></div>
137
+ <div class="orbit-particle" style="left: 50%; top: 50%;"></div>
138
+ <div class="orbit-particle" style="left: 50%; top: 50%;"></div>
139
+
140
+ <!-- Glow Background -->
141
+ <div class="glow-layer"></div>
142
+
143
+ <!-- Main Character SVG -->
144
+ <svg class="char-svg" viewBox="0 0 240 320" xmlns="http://www.w3.org/2000/svg">
145
+ <defs>
146
+ <!-- Dynamic Gradients -->
147
+ <linearGradient id="gradPrimary" x1="0%" y1="0%" x2="100%" y2="100%">
148
+ <stop offset="0%" stop-color="#f43f5e" />
149
+ <stop offset="50%" stop-color="#e11d48" />
150
+ <stop offset="100%" stop-color="#be123c" />
151
+ </linearGradient>
152
+ <linearGradient id="gradSecondary" x1="0%" y1="0%" x2="100%" y2="100%">
153
+ <stop offset="0%" stop-color="#8b5cf6" />
154
+ <stop offset="50%" stop-color="#7c3aed" />
155
+ <stop offset="100%" stop-color="#6d28d9" />
156
+ </linearGradient>
157
+ <radialGradient id="gradAccent" cx="50%" cy="50%" r="50%">
158
+ <stop offset="0%" stop-color="#fb7185" />
159
+ <stop offset="100%" stop-color="#f43f5e" />
160
+ </radialGradient>
161
+ <filter id="glow"><feGaussianBlur stdDeviation="3" result="coloredBlur"/>
162
+ <feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
163
+ </filter>
164
+ </defs>
165
+
166
+ <!-- Shadow -->
167
+ <ellipse cx="120" cy="305" rx="60" ry="15" fill="rgba(0,0,0,0.3)" filter="url(#glow)"/>
168
+
169
+ <!-- Left Arm (Behind Body) -->
170
+ <g id="char-arm-left">
171
+ <path d="M 75 140 Q 45 180 40 230" stroke="url(#gradSecondary)" stroke-width="20" fill="none" stroke-linecap="round"/>
172
+ <circle cx="40" cy="235" r="15" fill="url(#gradAccent)"/>
173
+ </g>
174
+
175
+ <!-- Right Arm (Behind Body) -->
176
+ <g id="char-arm-right">
177
+ <path d="M 165 140 Q 195 180 200 230" stroke="url(#gradSecondary)" stroke-width="20" fill="none" stroke-linecap="round"/>
178
+ <circle cx="200" cy="235" r="15" fill="url(#gradAccent)"/>
179
+ </g>
180
+
181
+ <!-- Left Leg -->
182
+ <g id="char-leg-left">
183
+ <path d="M 95 220 L 90 280" stroke="url(#gradSecondary)" stroke-width="18" fill="none" stroke-linecap="round"/>
184
+ <ellipse cx="90" cy="285" rx="14" ry="10" fill="#fb7185"/>
185
+ </g>
186
+
187
+ <!-- Right Leg -->
188
+ <g id="char-leg-right">
189
+ <path d="M 145 220 L 150 280" stroke="url(#gradSecondary)" stroke-width="18" fill="none" stroke-linecap="round"/>
190
+ <ellipse cx="150" cy="285" rx="14" ry="10" fill="#fb7185"/>
191
+ </g>
192
+
193
+ <!-- Body -->
194
+ <g id="char-body">
195
+ <!-- Main Body Shape -->
196
+ <ellipse cx="120" cy="190" rx="55" ry="65" fill="url(#gradPrimary)"/>
197
+
198
+ <!-- Body Details -->
199
+ <circle cx="120" cy="170" r="25" fill="rgba(255,255,255,0.15)"/>
200
+ <ellipse cx="120" cy="210" rx="30" ry="25" fill="rgba(255,255,255,0.1)"/>
201
+
202
+ <!-- Music Note Decoration -->
203
+ <g transform="translate(140, 160) rotate(15)">
204
+ <text x="0" y="0" fill="rgba(255,255,255,0.4)" font-size="30" font-family="Fredoka One">♪</text>
205
+ </g>
206
+ </g>
207
+
208
+ <!-- Head -->
209
+ <g id="char-head">
210
+ <!-- Head Base -->
211
+ <circle cx="120" cy="85" r="60" fill="url(#gradPrimary)"/>
212
+
213
+ <!-- Headphones -->
214
+ <g id="headphones">
215
+ <path d="M 55 85 Q 55 20 120 20 Q 185 20 185 85" stroke="url(#gradSecondary)" stroke-width="12" fill="none"/>
216
+ <rect x="45" y="65" width="20" height="40" rx="10" fill="url(#gradSecondary)"/>
217
+ <rect x="175" y="65" width="20" height="40" rx="10" fill="url(#gradSecondary)"/>
218
+ <rect x="50" y="70" width="10" height="30" rx="5" fill="#a78bfa"/>
219
+ <rect x="180" y="70" width="10" height="30" rx="5" fill="#a78bfa"/>
220
+ </g>
221
+
222
+ <!-- Face Group -->
223
+ <g id="char-face" class="face-happy" data-mood="happy">
224
+ <!-- Eyes -->
225
+ <ellipse class="eye left" cx="95" cy="90" rx="10" ry="8" fill="white"/>
226
+ <circle cx="95" cy="90" r="5" fill="#1a1a2e"/>
227
+ <ellipse class="eye right" cx="145" cy="90" rx="10" ry="8" fill="white"/>
228
+ <circle cx="145" cy="90" r="5" fill="#1a1a2e"/>
229
+
230
+ <!-- Cheeks -->
231
+ <ellipse cx="85" cy="105" rx="8" ry="5" fill="rgba(251,113,133,0.5)"/>
232
+ <ellipse cx="155" cy="105" rx="8" ry="5" fill="rgba(251,113,133,0.5)"/>
233
+
234
+ <!-- Mouth (dynamic path) -->
235
+ <path class="mouth" d="M 95 110 Q 120 135 145 110" stroke="#1a1a2e" stroke-width="4" fill="none" stroke-linecap="round"/>
236
+ </g>
237
+
238
+ <!-- Antenna -->
239
+ <line x1="120" y1="25" x2="120" y2="5" stroke="url(#gradSecondary)" stroke-width="4"/>
240
+ <circle cx="120" cy="5" r="8" fill="url(#gradAccent)">
241
+ <animate attributeName="opacity" values="1;0.5;1" dur="0.5s" repeatCount="indefinite"/>
242
+ </circle>
243
+ </g>
244
+ </svg>
245
+ </div>
246
+ `;
247
+ }
248
+
249
+ setupInteractions() {
250
+ // Click to change mood
251
+ this.shadowRoot.querySelector('.character-container').addEventListener('click', () => {
252
+ const moods = ['happy', 'excited', 'chill', 'hype'];
253
+ const currentIndex = moods.indexOf(this.mood);
254
+ this.mood = moods[(currentIndex + 1) % moods.length];
255
+
256
+ const face = this.shadowRoot.getElementById('char-face');
257
+ face.setAttribute('class', `face-${this.mood}`);
258
+ face.setAttribute('data-mood', this.mood);
259
+
260
+ // Create burst effect
261
+ const rect = this.getBoundingClientRect();
262
+ document.dispatchEvent(new CustomEvent('character-click', {
263
+ detail: { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }
264
+ }));
265
+ });
266
+ }
267
+
268
+ // Public methods for external control
269
+ setMood(mood) {
270
+ this.mood = mood;
271
+ const face = this.shadowRoot?.getElementById('char-face');
272
+ if (face) {
273
+ face.setAttribute('class', `face-${mood}`);
274
+ }
275
+ }
276
+
277
+ pulse() {
278
+ const body = this.shadowRoot?.getElementById('char-body');
279
+ if (body) {
280
+ body.style.filter = 'brightness(1.4) drop-shadow(0 0 20px #f43f5e)';
281
+ requestAnimationFrame(() => {
282
+ setTimeout(() => {
283
+ body.style.filter = '';
284
+ }, 100);
285
+ });
286
+ }
287
+ }
288
+ }
289
+
290
+ customElements.define('groove-character', GrooveCharacter);
components/groove-controls.js ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GrooveToon Controls Component
2
+ class GrooveControls extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.isPlaying = false;
6
+ this.currentDance = 'bounce';
7
+ this.tempo = 120;
8
+ this.primaryColor = '#f43f5e';
9
+ this.secondaryColor = '#8b5cf6';
10
+ }
11
+
12
+ connectedCallback() {
13
+ this.attachShadow({ mode: 'open' });
14
+ this.render();
15
+ this.setupEventListeners();
16
+ this.setupAudioVisualization();
17
+ }
18
+
19
+ render() {
20
+ this.shadowRoot.innerHTML = `
21
+ <style>
22
+ :host {
23
+ display: block;
24
+ width: 100%;
25
+ max-width: 800px;
26
+ margin: 0 auto;
27
+ padding: 1.5rem;
28
+ }
29
+
30
+ .controls-panel {
31
+ background: rgba(0, 0, 0, 0.4);
32
+ backdrop-filter: blur(20px);
33
+ border: 1px solid rgba(255, 255, 255, 0.1);
34
+ border-radius: 24px;
35
+ padding: 1.5rem;
36
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
37
+ }
38
+
39
+ .main-controls {
40
+ display: flex;
41
+ gap: 1rem;
42
+ align-items: center;
43
+ justify-content: center;
44
+ margin-bottom: 1.5rem;
45
+ flex-wrap: wrap;
46
+ }
47
+
48
+ .play-btn {
49
+ width: 72px;
50
+ height: 72px;
51
+ border-radius: 50%;
52
+ border: none;
53
+ background: linear-gradient(135deg, #f43f5e, #e11d48);
54
+ cursor: pointer;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
59
+ box-shadow: 0 10px 40px rgba(244, 63, 94, 0.4);
60
+ }
61
+
62
+ .play-btn:hover {
63
+ transform: scale(1.05);
64
+ box-shadow: 0 15px 50px rgba(244, 63, 94, 0.5);
65
+ }
66
+
67
+ .play-btn.playing {
68
+ background: linear-gradient(135deg, #8b5cf6, #7c3aed);
69
+ box-shadow: 0 10px 40px rgba(139, 92, 246, 0.4);
70
+ }
71
+
72
+ .play-btn svg {
73
+ width: 32px;
74
+ height: 32px;
75
+ color: white;
76
+ margin-left: 4px;
77
+ }
78
+
79
+ .play-btn.playing svg {
80
+ margin-left: 0;
81
+ }
82
+
83
+ .dance-grid {
84
+ display: grid;
85
+ grid-template-columns: repeat(4, 1fr);
86
+ gap: 0.75rem;
87
+ margin-bottom: 1.5rem;
88
+ }
89
+
90
+ .dance-btn {
91
+ padding: 0.875rem 1rem;
92
+ border: 1px solid rgba(255, 255, 255, 0.1);
93
+ border-radius: 12px;
94
+ background: rgba(255, 255, 255, 0.05);
95
+ color: rgba(255, 255, 255, 0.8);
96
+ font-weight: 600;
97
+ font-size: 0.875rem;
98
+ cursor: pointer;
99
+ transition: all 0.2s ease;
100
+ display: flex;
101
+ flex-direction: column;
102
+ align-items: center;
103
+ gap: 0.25rem;
104
+ }
105
+
106
+ .dance-btn:hover {
107
+ background: rgba(255, 255, 255, 0.1);
108
+ transform: translateY(-2px);
109
+ }
110
+
111
+ .dance-btn.active {
112
+ background: linear-gradient(135deg, rgba(244, 63, 94, 0.2), rgba(139, 92, 246, 0.2));
113
+ border-color: #f43f5e;
114
+ color: white;
115
+ }
116
+
117
+ .dance-btn svg {
118
+ width: 20px;
119
+ height: 20px;
120
+ }
121
+
122
+ .sliders-section {
123
+ display: grid;
124
+ grid-template-columns: 1fr 1fr;
125
+ gap: 1.5rem;
126
+ margin-bottom: 1.5rem;
127
+ }
128
+
129
+ @media (max-width: 640px) {
130
+ .sliders-section {
131
+ grid-template-columns: 1fr;
132
+ }
133
+ .dance-grid {
134
+ grid-template-columns: repeat(2, 1fr);
135
+ }
136
+ }
137
+
138
+ .slider-group {
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 0.5rem;
142
+ }
143
+
144
+ .slider-label {
145
+ display: flex;
146
+ justify-content: space-between;
147
+ align-items: center;
148
+ color: rgba(255, 255, 255, 0.7);
149
+ font-size: 0.875rem;
150
+ font-weight: 600;
151
+ }
152
+
153
+ .slider-value {
154
+ color: #f43f5e;
155
+ font-weight: 700;
156
+ }
157
+
158
+ input[type="range"] {
159
+ -webkit-appearance: none;
160
+ width: 100%;
161
+ height: 6px;
162
+ border-radius: 3px;
163
+ background: rgba(255, 255, 255, 0.1);
164
+ outline: none;
165
+ cursor: pointer;
166
+ }
167
+
168
+ input[type="range"]::-webkit-slider-thumb {
169
+ -webkit-appearance: none;
170
+ width: 20px;
171
+ height: 20px;
172
+ border-radius: 50%;
173
+ background: linear-gradient(135deg, #f43f5e, #8b5cf6);
174
+ cursor: pointer;
175
+ box-shadow: 0 0 10px rgba(244, 63, 94, 0.5);
176
+ transition: transform 0.2s;
177
+ }
178
+
179
+ input[type="range"]::-webkit-slider-thumb:hover {
180
+ transform: scale(1.1);
181
+ }
182
+
183
+ .visualizer {
184
+ height: 60px;
185
+ display: flex;
186
+ align-items: flex-end;
187
+ justify-content: center;
188
+ gap: 4px;
189
+ padding: 1rem;
190
+ background: rgba(0, 0, 0, 0.3);
191
+ border-radius: 12px;
192
+ margin-bottom: 1.5rem;
193
+ }
194
+
195
+ .viz-bar {
196
+ width: 8px;
197
+ background: linear-gradient(to top, #f43f5e, #8b5cf6);
198
+ border-radius: 4px;
199
+ transition: height 0.1s ease;
200
+ min-height: 4px;
201
+ }
202
+
203
+ .color-controls {
204
+ display: flex;
205
+ gap: 1rem;
206
+ justify-content: center;
207
+ flex-wrap: wrap;
208
+ }
209
+
210
+ .color-group {
211
+ display: flex;
212
+ align-items: center;
213
+ gap: 0.75rem;
214
+ padding: 0.75rem 1rem;
215
+ background: rgba(255, 255, 255, 0.05);
216
+ border-radius: 12px;
217
+ }
218
+
219
+ .color-label {
220
+ font-size: 0.875rem;
221
+ color: rgba(255, 255, 255, 0.7);
222
+ font-weight: 600;
223
+ }
224
+
225
+ input[type="color"] {
226
+ width: 48px;
227
+ height: 48px;
228
+ border: none;
229
+ border-radius: 12px;
230
+ cursor: pointer;
231
+ background: transparent;
232
+ }
233
+
234
+ input[type="color"]::-webkit-color-swatch-wrapper {
235
+ padding: 0;
236
+ border-radius: 12px;
237
+ }
238
+
239
+ input[type="color"]::-webkit-color-swatch {
240
+ border: 2px solid rgba(255, 255, 255, 0.2);
241
+ border-radius: 12px;
242
+ }
243
+ </style>
244
+
245
+ <div class="controls-panel">
246
+ <!-- Audio Visualizer -->
247
+ <div class="visualizer" id="visualizer">
248
+ ${Array(16).fill(0).map((_, i) => `<div class="viz-bar" style="height: ${4 + Math.random() * 20}px"></div>`).join('')}
249
+ </div>
250
+
251
+ <!-- Main Play Control -->
252
+ <div class="main-controls">
253
+ <button class="play-btn ${this.isPlaying ? 'playing' : ''}" id="playBtn">
254
+ ${this.isPlaying ?
255
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>' :
256
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>'
257
+ }
258
+ </button>
259
+ </div>
260
+
261
+ <!-- Dance Style Selector -->
262
+ <div class="dance-grid">
263
+ <button class="dance-btn ${this.currentDance === 'bounce' ? 'active' : ''}" data-dance="bounce">
264
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
265
+ Bounce
266
+ </button>
267
+ <button class="dance-btn ${this.currentDance === 'sway' ? 'active' : ''}" data-dance="sway">
268
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12h16M4 18h16M4 6h16"/></svg>
269
+ Sway
270
+ </button>
271
+ <button class="dance-btn ${this.currentDance === 'spin' ? 'active' : ''}" data-dance="spin">
272
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>
273
+ Spin
274
+ </button>
275
+ <button class="dance-btn ${this.currentDance === 'wave' ? 'active' : ''}" data-dance="wave">
276
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12s4-4 6-4 4 4 6 4 4-4 6-4"/></svg>
277
+ Wave
278
+ </button>
279
+ </div>
280
+
281
+ <!-- Sliders -->
282
+ <div class="sliders-section">
283
+ <div class="slider-group">
284
+ <div class="slider-label">
285
+ <span>Tempo (BPM)</span>
286
+ <span class="slider-value" id="bpmValue">${this.tempo}</span>
287
+ </div>
288
+ <input type="range" id="tempoSlider" min="60" max="200" value="${this.tempo}">
289
+ </div>
290
+ <div class="slider-group">
291
+ <div class="slider-label">
292
+ <span>Groove Level</span>
293
+ <span class="slider-value" id="grooveValue">⚡ Max</span>
294
+ </div>
295
+ <input type="range" id="grooveSlider" min="1" max="10" value="10">
296
+ </div>
297
+ </div>
298
+
299
+ <!-- Color Picker -->
300
+ <div class="color-controls">
301
+ <div class="color-group">
302
+ <span class="color-label">Primary</span>
303
+ <input type="color" id="primaryColor" value="${this.primaryColor}">
304
+ </div>
305
+ <div class="color-group">
306
+ <span class="color-label">Accent</span>
307
+ <input type="color" id="secondaryColor" value="${this.secondaryColor}">
308
+ </div>
309
+ </div>
310
+ </div>
311
+ `;
312
+ }
313
+
314
+ setupEventListeners() {
315
+ // Play button
316
+ const playBtn = this.shadowRoot.getElementById('playBtn');
317
+ playBtn.addEventListener('click', () => {
318
+ this.isPlaying = !this.isPlaying;
319
+ playBtn.classList.toggle('playing', this.isPlaying);
320
+ playBtn.innerHTML = this.isPlaying ?
321
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>' :
322
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>';
323
+
324
+ document.dispatchEvent(new CustomEvent('toggle-play', {
325
+ detail: { playing: this.isPlaying }
326
+ }));
327
+ });
328
+
329
+ // Dance buttons
330
+ this.shadowRoot.querySelectorAll('.dance-btn').forEach(btn => {
331
+ btn.addEventListener('click', () => {
332
+ this.shadowRoot.querySelectorAll('.dance-btn').forEach(b => b.classList.remove('active'));
333
+ btn.classList.add('active');
334
+ this.currentDance = btn.dataset.dance;
335
+ document.dispatchEvent(new CustomEvent('dance-select', {
336
+ detail: { dance: this.currentDance }
337
+ }));
338
+ });
339
+ });
340
+
341
+ // Tempo slider
342
+ const tempoSlider = this.shadowRoot.getElementById('tempoSlider');
343
+ const bpmValue = this.shadowRoot.getElementById('bpmValue');
344
+ tempoSlider.addEventListener('input', (e) => {
345
+ this.tempo = parseInt(e.target.value);
346
+ bpmValue.textContent = this.tempo;
347
+ document.dispatchEvent(new CustomEvent('tempo-change', {
348
+ detail: { tempo: this.tempo }
349
+ }));
350
+ });
351
+
352
+ // Color pickers
353
+ const primaryPicker = this.shadowRoot.getElementById('primaryColor');
354
+ const secondaryPicker = this.shadowRoot.getElementById('secondaryColor');
355
+
356
+ const updateColors = () => {
357
+ this.primaryColor = primaryPicker.value;
358
+ this.secondaryColor = secondaryPicker.value;
359
+
360
+ // Update CSS custom properties
361
+ const style = this.shadowRoot.style;
362
+ style.setProperty('--primary', this.primaryColor);
363
+ style.setProperty('--secondary', this.secondaryColor);
364
+
365
+ document.dispatchEvent(new CustomEvent('color-change', {
366
+ detail: { primary: this.primaryColor, secondary: this.secondaryColor }
367
+ }));
368
+ };
369
+
370
+ primaryPicker.addEventListener('input', updateColors);
371
+ secondaryPicker.addEventListener('input', updateColors);
372
+ }
373
+
374
+ setupAudioVisualization() {
375
+ const visualizer = this.shadowRoot.getElementById('visualizer');
376
+ const bars = visualizer.querySelectorAll('.viz-bar');
377
+
378
+ // Beat animation
379
+ document.addEventListener('beat', () => {
380
+ if (!this.isPlaying) return;
381
+
382
+ bars.forEach((bar, i) => {
383
+ const height = 4 + Math.random() * 50 + (Math.sin(Date.now() / 200 + i) * 20);
384
+ bar.style.height = `${Math.min(height, 56)}px`;
385
+ bar.style.opacity = 0.5 + Math.random() * 0.5;
386
+ });
387
+ });
388
+
389
+ // Idle animation
390
+ const animate = () => {
391
+ if (!this.isPlaying) {
392
+ bars.forEach((bar, i) => {
393
+ const height = 4 + Math.sin(Date.now() / 500 + i * 0.5) * 10 + 5;
394
+ bar.style.height = `${height}px`;
395
+ bar.style.opacity = 0.3;
396
+ });
397
+ }
398
+ requestAnimationFrame(animate);
399
+ };
400
+ animate();
401
+ }
402
+ }
403
+
404
+ customElements.define('groove-controls', GrooveControls);
components/groove-footer.js ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GrooveToon Footer Component
2
+ class GrooveFooter extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ }
6
+
7
+ connectedCallback() {
8
+ this.attachShadow({ mode: 'open' });
9
+ this.render();
10
+ this.setupShortcuts();
11
+ }
12
+
13
+ render() {
14
+ this.shadowRoot.innerHTML = `
15
+ <style>
16
+ :host {
17
+ display: block;
18
+ }
19
+
20
+ .footer {
21
+ padding: 1.5rem 2rem;
22
+ display: flex;
23
+ justify-content: space-between;
24
+ align-items: center;
25
+ flex-wrap: wrap;
26
+ gap: 1rem;
27
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
28
+ }
29
+
30
+ .shortcuts {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: 1rem;
34
+ font-size: 0.75rem;
35
+ color: rgba(255, 255, 255, 0.5);
36
+ }
37
+
38
+ .shortcut {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 0.375rem;
42
+ }
43
+
44
+ .key {
45
+ padding: 0.25rem 0.5rem;
46
+ background: rgba(255, 255, 255, 0.1);
47
+ border: 1px solid rgba(255, 255, 255, 0.1);
48
+ border-radius: 6px;
49
+ font-weight: 600;
50
+ color: rgba(255, 255, 255, 0.8);
51
+ font-size: 0.625rem;
52
+ text-transform: uppercase;
53
+ }
54
+
55
+ .stats {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 1.5rem;
59
+ font-size: 0.875rem;
60
+ color: rgba(255, 255, 255, 0.6);
61
+ }
62
+
63
+ .stat {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.5rem;
67
+ }
68
+
69
+ .stat-value {
70
+ color: #f43f5e;
71
+ font-weight: 700;
72
+ }
73
+
74
+ .social-links {
75
+ display: flex;
76
+ gap: 0.75rem;
77
+ }
78
+
79
+ .social-btn {
80
+ width: 40px;
81
+ height: 40px;
82
+ border-radius: 10px;
83
+ background: rgba(255, 255, 255, 0.05);
84
+ border: 1px solid rgba(255, 255, 255, 0.1);
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ color: rgba(255, 255, 255, 0.7);
89
+ cursor: pointer;
90
+ transition: all 0.2s ease;
91
+ }
92
+
93
+ .social-btn:hover {
94
+ background: rgba(244, 63, 94, 0.2);
95
+ border-color: rgba(244, 63, 94, 0.3);
96
+ color: #f43f5e;
97
+ transform: translateY(-2px);
98
+ }
99
+
100
+ .social-btn svg {
101
+ width: 20px;
102
+ height: 20px;
103
+ }
104
+
105
+ @media (max-width: 768px) {
106
+ .footer {
107
+ flex-direction: column;
108
+ text-align: center;
109
+ padding: 1rem;
110
+ }
111
+
112
+ .shortcuts {
113
+ display: none;
114
+ }
115
+ }
116
+ </style>
117
+
118
+ <footer class="footer">
119
+ <div class="shortcuts">
120
+ <span class="shortcut"><span class="key">Space</span> Play/Pause</span>
121
+ <span class="shortcut"><span class="key">1-4</span> Dance</span>
122
+ <span class="shortcut"><span class="key">↑↓</span> Tempo</span>
123
+ </div>
124
+
125
+ <div class="stats">
126
+ <div class="stat">
127
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
128
+ <span>Dance: <span class="stat-value" id="currentDose">Bounce</span></span>
129
+ </div>
130
+ <div class="stat">
131
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 10v3"/><path d="M6 6v11"/><path d="M10 3v18"/><path d="M14 8v7"/><path d="M18 5v13"/><path d="M22 10v4"/></svg>
132
+ <span>BPM: <span class="stat-value" id="currentBpm">120</span></span>
133
+ </div>
134
+ </div>
135
+
136
+ <div class="social-links">
137
+ <button class="social-btn" title="Share" onclick="alert('Share feature coming soon!')">
138
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
139
+ </button>
140
+ <button class="social-btn" title="Fullscreen" onclick="document.documentElement.requestFullscreen()">
141
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
142
+ </button>
143
+ </div>
144
+ </footer>
145
+ `;
146
+ }
147
+
148
+ setupShortcuts() {
149
+ // Update stats display
150
+ document.addEventListener('dance-change', (e) => {
151
+ const doseEl = this.shadowRoot.getElementById('currentDose');
152
+ if (doseEl) doseEl.textContent = e.detail.charAt(0).toUpperCase() + e.detail.slice(1);
153
+ });
154
+
155
+ document.addEventListener('tempo-change', (e) => {
156
+ const bpmEl = this.shadowRoot.getElementById('currentBpm');
157
+ if (bpmEl) bpmEl.textContent = e.detail.tempo;
158
+ });
159
+ }
160
+ }
161
+
162
+ customElements.define('groove-footer', GrooveFooter);
components/groove-header.js ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GrooveToon Header Component
2
+ class GrooveHeader extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ }
6
+
7
+ connectedCallback() {
8
+ this.attachShadow({ mode: 'open' });
9
+ this.shadowRoot.innerHTML = `
10
+ <style>
11
+ :host {
12
+ display: block;
13
+ }
14
+ .header {
15
+ padding: 1.5rem 2rem;
16
+ display: flex;
17
+ justify-content: space-between;
18
+ align-items: center;
19
+ }
20
+ .logo {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 0.75rem;
24
+ font-family: 'Fredoka One', cursive;
25
+ font-size: 1.75rem;
26
+ color: white;
27
+ text-decoration: none;
28
+ cursor: pointer;
29
+ }
30
+ .logo-icon {
31
+ width: 48px;
32
+ height: 48px;
33
+ background: linear-gradient(135deg, #f43f5e, #8b5cf6);
34
+ border-radius: 16px;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ animation: logo-bounce 2s ease-in-out infinite;
39
+ box-shadow: 0 0 20px rgba(244, 63, 94, 0.4);
40
+ }
41
+ @keyframes logo-bounce {
42
+ 0%, 100% { transform: scale(1) rotate(-5deg); }
43
+ 50% { transform: scale(1.05) rotate(5deg); }
44
+ }
45
+ .logo-text {
46
+ background: linear-gradient(90deg, #f43f5e, #8b5cf6);
47
+ -webkit-background-clip: text;
48
+ -webkit-text-fill-color: transparent;
49
+ background-clip: text;
50
+ }
51
+ .nav-links {
52
+ display: flex;
53
+ gap: 2rem;
54
+ align-items: center;
55
+ }
56
+ .nav-link {
57
+ color: rgba(255, 255, 255, 0.7);
58
+ text-decoration: none;
59
+ font-weight: 600;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 0.5rem;
63
+ padding: 0.5rem 1rem;
64
+ border-radius: 9999px;
65
+ transition: all 0.3s ease;
66
+ }
67
+ .nav-link:hover {
68
+ color: white;
69
+ background: rgba(255, 255, 255, 0.1);
70
+ }
71
+ .status {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 0.5rem;
75
+ padding: 0.5rem 1rem;
76
+ background: rgba(139, 92, 246, 0.2);
77
+ border: 1px solid rgba(139, 92, 246, 0.3);
78
+ border-radius: 9999px;
79
+ font-size: 0.875rem;
80
+ font-weight: 600;
81
+ }
82
+ .status-dot {
83
+ width: 8px;
84
+ height: 8px;
85
+ background: #8b5cf6;
86
+ border-radius: 50%;
87
+ animation: pulse 1.5s ease-in-out infinite;
88
+ }
89
+ .status.playing .status-dot {
90
+ background: #f43f5e;
91
+ animation: pulse 0.5s ease-in-out infinite;
92
+ }
93
+ @media (max-width: 768px) {
94
+ .nav-links {
95
+ display: none;
96
+ }
97
+ .header {
98
+ padding: 1rem;
99
+ }
100
+ }
101
+ </style>
102
+ <header class="header">
103
+ <a href="index.html" class="logo">
104
+ <div class="logo-icon">
105
+ <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
106
+ <path d="M9 18V5l12-2v13"></path>
107
+ <circle cx="6" cy="18" r="3"></circle>
108
+ <circle cx="18" cy="16" r="3"></circle>
109
+ </svg>
110
+ </div>
111
+ <span class="logo-text">GrooveToon</span>
112
+ </a>
113
+ <nav class="nav-links">
114
+ <a href="index.html" class="nav-link">
115
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
116
+ Stage
117
+ </a>
118
+ <a href="#" class="nav-link" onclick="event.preventDefault(); alert('Studio coming soon!')">
119
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v14a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v4a7 7 0 0 1-14 0v-4"></path><line x1="12" y1="19" x2="12" y2="22"></line></svg>
120
+ Studio
121
+ </a>
122
+ </nav>
123
+ <div class="status" id="status">
124
+ <span class="status-dot"></span>
125
+ <span id="status-text">Ready to Groove</span>
126
+ </div>
127
+ </header>
128
+ `;
129
+
130
+ // Listen for play state changes
131
+ document.addEventListener('toggle-play', (e) => {
132
+ const status = this.shadowRoot.getElementById('status');
133
+ const statusText = this.shadowRoot.getElementById('status-text');
134
+ status.classList.toggle('playing', e.detail.playing);
135
+ statusText.textContent = e.detail.playing ? 'Dancing!' : 'Ready to Groove';
136
+ });
137
+ }
138
+ }
139
+
140
+ customElements.define('groove-header', GrooveHeader);
index.html CHANGED
@@ -1,19 +1,97 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>GrooveToon - Interactive Dancing Character</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
+ <script>
12
+ tailwind.config = {
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ primary: {
17
+ 50: '#fff1f2',
18
+ 100: '#ffe4e6',
19
+ 200: '#fecdd3',
20
+ 300: '#fda4af',
21
+ 400: '#fb7185',
22
+ 500: '#f43f5e',
23
+ 600: '#e11d48',
24
+ 700: '#be123c',
25
+ 800: '#9f1239',
26
+ 900: '#881337',
27
+ },
28
+ secondary: {
29
+ 50: '#f5f3ff',
30
+ 100: '#ede9fe',
31
+ 200: '#ddd6fe',
32
+ 300: '#c4b5fd',
33
+ 400: '#a78bfa',
34
+ 500: '#8b5cf6',
35
+ 600: '#7c3aed',
36
+ 700: '#6d28d9',
37
+ 800: '#5b21b6',
38
+ 900: '#4c1d95',
39
+ }
40
+ },
41
+ animation: {
42
+ 'bounce-slow': 'bounce 2s infinite',
43
+ 'pulse-slow': 'pulse 3s infinite',
44
+ 'spin-slow': 'spin 8s linear infinite',
45
+ }
46
+ }
47
+ }
48
+ }
49
+ </script>
50
+ </head>
51
+ <body class="bg-neutral-950 text-white min-h-screen overflow-x-hidden">
52
+ <!-- WebGL Background Canvas -->
53
+ <canvas id="bgCanvas" class="fixed inset-0 w-full h-full pointer-events-none"></canvas>
54
+
55
+ <!-- Main Content -->
56
+ <div class="relative z-10 min-h-screen flex flex-col">
57
+ <!-- Header -->
58
+ <groove-header></groove-header>
59
+
60
+ <!-- Main Stage -->
61
+ <main class="flex-1 flex flex-col items-center justify-center px-4 py-8">
62
+ <!-- Dance Floor -->
63
+ <div class="relative w-full max-w-4xl">
64
+ <!-- Stage Lights -->
65
+ <div class="absolute -top-20 left-1/4 w-32 h-32 bg-primary-500/20 rounded-full blur-3xl animate-pulse-slow"></div>
66
+ <div class="absolute -top-20 right-1/4 w-32 h-32 bg-secondary-500/20 rounded-full blur-3xl animate-pulse-slow" style="animation-delay: 1s;"></div>
67
+
68
+ <!-- Spotlight -->
69
+ <div class="absolute top-0 left-1/2 -translate-x-1/2 w-96 h-[600px] bg-gradient-to-b from-primary-500/10 to-transparent pointer-events-none"></div>
70
+
71
+ <!-- Character Container -->
72
+ <div class="relative flex justify-center items-end min-h-[500px] pb-8">
73
+ <groove-character id="dancer"></groove-character>
74
+ </div>
75
+
76
+ <!-- Floor Reflection -->
77
+ <div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-[80%] h-24 bg-gradient-to-t from-primary-500/10 to-transparent rounded-full blur-xl"></div>
78
+ </div>
79
+
80
+ <!-- Controls -->
81
+ <groove-controls></groove-controls>
82
+ </main>
83
+
84
+ <!-- Footer -->
85
+ <groove-footer></groove-footer>
86
+ </div>
87
+
88
+ <!-- Scripts -->
89
+ <script src="components/groove-header.js"></script>
90
+ <script src="components/groove-character.js"></script>
91
+ <script src="components/groove-controls.js"></script>
92
+ <script src="components/groove-footer.js"></script>
93
+ <script src="script.js"></script>
94
+ <script>feather.replace();</script>
95
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
96
+ </body>
97
+ </html>
script.js ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GrooveToon Interactive - Main Script
2
+
3
+ // Global State
4
+ const state = {
5
+ isPlaying: false,
6
+ currentDance: 'bounce',
7
+ tempo: 120,
8
+ accentColor: 'primary',
9
+ secondaryColor: 'secondary',
10
+ audioContext: null,
11
+ beatInterval: null,
12
+ particles: [],
13
+ characterMood: 'happy'
14
+ };
15
+
16
+ // Color Themes
17
+ const themes = {
18
+ primary: {
19
+ 500: '#f43f5e',
20
+ 600: '#e11d48',
21
+ 400: '#fb7185'
22
+ },
23
+ secondary: {
24
+ 500: '#8b5cf6',
25
+ 600: '#7c3aed',
26
+ 400: '#a78bfa'
27
+ }
28
+ };
29
+
30
+ // Dance Moves Library
31
+ const danceMoves = {
32
+ bounce: {
33
+ name: 'Bounce',
34
+ css: {
35
+ body: 'animation: dance-bounce 0.5s ease-in-out infinite',
36
+ arms: 'animation: dance-wave 1s ease-in-out infinite',
37
+ head: 'animation: dance-head-bop 0.5s ease-in-out infinite'
38
+ }
39
+ },
40
+ sway: {
41
+ name: 'Sway',
42
+ css: {
43
+ body: 'animation: dance-sway 2s ease-in-out infinite',
44
+ arms: 'animation: dance-wave 2s ease-in-out infinite reverse',
45
+ head: 'animation: dance-sway 2s ease-in-out infinite 0.5s'
46
+ }
47
+ },
48
+ spin: {
49
+ name: 'Spin',
50
+ css: {
51
+ body: 'animation: dance-spin 2s ease-in-out infinite',
52
+ arms: 'animation: dance-sway 1s ease-in-out infinite',
53
+ head: 'animation: none'
54
+ }
55
+ },
56
+ wave: {
57
+ name: 'Wave',
58
+ css: {
59
+ body: 'animation: float-gentle 3s ease-in-out infinite',
60
+ arms: 'animation: dance-wave 0.5s ease-in-out infinite',
61
+ head: 'animation: dance-head-bop 0.25s ease-in-out infinite'
62
+ }
63
+ },
64
+ idle: {
65
+ name: 'Idle',
66
+ css: {
67
+ body: 'animation: float-gentle 4s ease-in-out infinite',
68
+ arms: 'animation: none',
69
+ head: 'animation: none'
70
+ }
71
+ }
72
+ };
73
+
74
+ // Audio System
75
+ class AudioEngine {
76
+ constructor() {
77
+ this.ctx = null;
78
+ this.masterGain = null;
79
+ this.isInitialized = false;
80
+ }
81
+
82
+ init() {
83
+ if (this.isInitialized) return;
84
+ this.ctx = new (window.AudioContext || window.webkitAudioContext)();
85
+ this.masterGain = this.ctx.createGain();
86
+ this.masterGain.gain.value = 0.3;
87
+ this.masterGain.connect(this.ctx.destination);
88
+ this.isInitialized = true;
89
+ }
90
+
91
+ playKick(time) {
92
+ const osc = this.ctx.createOscillator();
93
+ const gain = this.ctx.createGain();
94
+
95
+ osc.frequency.setValueAtTime(150, time);
96
+ osc.frequency.exponentialRampToValueAtTime(0.01, time + 0.5);
97
+
98
+ gain.gain.setValueAtTime(1, time);
99
+ gain.gain.exponentialRampToValueAtTime(0.01, time + 0.5);
100
+
101
+ osc.connect(gain);
102
+ gain.connect(this.masterGain);
103
+
104
+ osc.start(time);
105
+ osc.stop(time + 0.5);
106
+ }
107
+
108
+ playHiHat(time) {
109
+ const bufferSize = this.ctx.sampleRate * 0.1;
110
+ const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
111
+ const data = buffer.getChannelData(0);
112
+
113
+ for (let i = 0; i < bufferSize; i++) {
114
+ data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2);
115
+ }
116
+
117
+ const noise = this.ctx.createBufferSource();
118
+ noise.buffer = buffer;
119
+
120
+ const filter = this.ctx.createBiquadFilter();
121
+ filter.type = 'highpass';
122
+ filter.frequency.value = 5000;
123
+
124
+ const gain = this.ctx.createGain();
125
+ gain.gain.value = 0.3;
126
+
127
+ noise.connect(filter);
128
+ filter.connect(gain);
129
+ gain.connect(this.masterGain);
130
+
131
+ noise.start(time);
132
+ }
133
+
134
+ playSnare(time) {
135
+ const bufferSize = this.ctx.sampleRate * 0.2;
136
+ const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
137
+ const data = buffer.getChannelData(0);
138
+
139
+ for (let i = 0; i < bufferSize; i++) {
140
+ data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (this.ctx.sampleRate * 0.05));
141
+ }
142
+
143
+ const noise = this.ctx.createBufferSource();
144
+ noise.buffer = buffer;
145
+
146
+ const filter = this.ctx.createBiquadFilter();
147
+ filter.type = 'highpass';
148
+ filter.frequency.value = 1000;
149
+
150
+ const gain = this.ctx.createGain();
151
+ gain.gain.setValueAtTime(0.5, time);
152
+ gain.gain.exponentialRampToValueAtTime(0.01, time + 0.2);
153
+
154
+ noise.connect(filter);
155
+ filter.connect(gain);
156
+ gain.connect(this.masterGain);
157
+
158
+ noise.start(time);
159
+ }
160
+
161
+ setTempo(bpm) {
162
+ state.tempo = bpm;
163
+ if (state.isPlaying) {
164
+ this.stopBeat();
165
+ this.startBeat();
166
+ }
167
+ }
168
+
169
+ startBeat() {
170
+ this.init();
171
+ const beatDuration = 60 / state.tempo;
172
+ let beatCount = 0;
173
+
174
+ const scheduleBeat = () => {
175
+ const now = this.ctx.currentTime;
176
+ const nextBeat = Math.ceil(now / beatDuration) * beatDuration;
177
+
178
+ this.playKick(nextBeat);
179
+ if (beatCount % 2 === 1) this.playSnare(nextBeat);
180
+ this.playHiHat(nextBeat + beatDuration / 2);
181
+
182
+ beatCount++;
183
+
184
+ const delay = (nextBeat - now) * 1000;
185
+ this.beatTimeout = setTimeout(scheduleBeat, delay);
186
+ };
187
+
188
+ scheduleBeat();
189
+
190
+ // Visual beat indicator
191
+ state.beatInterval = setInterval(() => {
192
+ document.dispatchEvent(new CustomEvent('beat'));
193
+ }, beatDuration * 1000);
194
+ }
195
+
196
+ stopBeat() {
197
+ clearTimeout(this.beatTimeout);
198
+ clearInterval(state.beatInterval);
199
+ }
200
+ }
201
+
202
+ // Visual Effects Engine
203
+ class VisualEffects {
204
+ constructor() {
205
+ this.canvas = document.getElementById('bgCanvas');
206
+ this.ctx = this.canvas.getContext('2d');
207
+ this.particles = [];
208
+ this.resize();
209
+ window.addEventListener('resize', () => this.resize());
210
+ this.animate();
211
+ }
212
+
213
+ resize() {
214
+ this.canvas.width = window.innerWidth;
215
+ this.canvas.height = window.innerHeight;
216
+ }
217
+
218
+ createParticle(x, y, type = 'sparkle') {
219
+ const particle = {
220
+ x, y,
221
+ vx: (Math.random() - 0.5) * 4,
222
+ vy: -Math.random() * 3 - 1,
223
+ life: 1,
224
+ decay: 0.02,
225
+ size: Math.random() * 4 + 2,
226
+ hue: Math.random() > 0.5 ? 348 : 262, // primary or secondary hue
227
+ type
228
+ };
229
+ this.particles.push(particle);
230
+ }
231
+
232
+ createBurst(x, y) {
233
+ for (let i = 0; i < 20; i++) {
234
+ this.createParticle(x, y, 'burst');
235
+ }
236
+ }
237
+
238
+ animate() {
239
+ this.ctx.fillStyle = 'rgba(10, 10, 10, 0.1)';
240
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
241
+
242
+ // Draw connecting lines between nearby particles
243
+ for (let i = 0; i < this.particles.length; i++) {
244
+ for (let j = i + 1; j < this.particles.length; j++) {
245
+ const dx = this.particles[i].x - this.particles[j].x;
246
+ const dy = this.particles[i].y - this.particles[j].y;
247
+ const dist = Math.sqrt(dx * dx + dy * dy);
248
+
249
+ if (dist < 100) {
250
+ this.ctx.beginPath();
251
+ this.ctx.strokeStyle = `hsla(${this.particles[i].hue}, 70%, 60%, ${0.1 * (1 - dist/100)})`;
252
+ this.ctx.lineWidth = 1;
253
+ this.ctx.moveTo(this.particles[i].x, this.particles[i].y);
254
+ this.ctx.lineTo(this.particles[j].x, this.particles[j].y);
255
+ this.ctx.stroke();
256
+ }
257
+ }
258
+ }
259
+
260
+ // Update and draw particles
261
+ this.particles = this.particles.filter(p => {
262
+ p.x += p.vx;
263
+ p.y += p.vy;
264
+ p.vy += 0.05; // gravity
265
+ p.life -= p.decay;
266
+
267
+ if (p.life > 0) {
268
+ this.ctx.beginPath();
269
+ this.ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
270
+ this.ctx.fillStyle = `hsla(${p.hue}, 80%, 60%, ${p.life})`;
271
+ this.ctx.fill();
272
+ return true;
273
+ }
274
+ return false;
275
+ });
276
+
277
+ // Draw subtle grid
278
+ this.drawGrid();
279
+
280
+ requestAnimationFrame(() => this.animate());
281
+ }
282
+
283
+ drawGrid() {
284
+ const time = Date.now() * 0.001;
285
+ const gridSize = 50;
286
+
287
+ this.ctx.strokeStyle = 'rgba(244, 63, 94, 0.03)';
288
+ this.ctx.lineWidth = 1;
289
+
290
+ for (let x = 0; x < this.canvas.width; x += gridSize) {
291
+ const wave = Math.sin(x * 0.01 + time) * 10;
292
+ this.ctx.beginPath();
293
+ this.ctx.moveTo(x, 0);
294
+ this.ctx.lineTo(x + wave, this.canvas.height);
295
+ this.ctx.stroke();
296
+ }
297
+
298
+ for (let y = 0; y < this.canvas.height; y += gridSize) {
299
+ const wave = Math.cos(y * 0.01 + time) * 10;
300
+ this.ctx.beginPath();
301
+ this.ctx.moveTo(0, y);
302
+ this.ctx.lineTo(this.canvas.width, y + wave);
303
+ this.ctx.stroke();
304
+ }
305
+ }
306
+ }
307
+
308
+ // Character Controller
309
+ class CharacterController {
310
+ constructor() {
311
+ this.element = document.getElementById('dancer');
312
+ this.currentMove = 'idle';
313
+ }
314
+
315
+ setDance(moveName) {
316
+ this.currentMove = moveName;
317
+ const move = danceMoves[moveName] || danceMoves.idle;
318
+
319
+ // Apply CSS animations to character parts
320
+ const characterParts = this.element.shadowRoot;
321
+ if (!characterParts) return;
322
+
323
+ const body = characterParts.getElementById('char-body');
324
+ const leftArm = characterParts.getElementById('char-arm-left');
325
+ const rightArm = characterParts.getElementById('char-arm-right');
326
+ const head = characterParts.getElementById('char-head');
327
+
328
+ if (body) body.style.cssText = move.css.body;
329
+ if (leftArm) leftArm.style.cssText = move.css.arms;
330
+ if (rightArm) rightArm.style.cssText = move.css.arms + ';animation-direction: reverse;';
331
+ if (head) head.style.cssText = move.css.head;
332
+
333
+ // Dispatch event for UI update
334
+ document.dispatchEvent(new CustomEvent('dance-change', { detail: moveName }));
335
+ }
336
+
337
+ setMood(mood) {
338
+ state.characterMood = mood;
339
+ const characterParts = this.element.shadowRoot;
340
+ if (!characterParts) return;
341
+
342
+ const face = characterParts.getElementById('char-face');
343
+ if (face) {
344
+ face.setAttribute('data-mood', mood);
345
+ }
346
+ }
347
+
348
+ pulse() {
349
+ const body = this.element.shadowRoot?.getElementById('char-body');
350
+ if (body) {
351
+ body.style.filter = 'brightness(1.3)';
352
+ setTimeout(() => {
353
+ body.style.filter = 'brightness(1)';
354
+ }, 100);
355
+ }
356
+ }
357
+ }
358
+
359
+ // Main App Controller
360
+ class GrooveApp {
361
+ constructor() {
362
+ this.audio = new AudioEngine();
363
+ this.visuals = new VisualEffects();
364
+ this.character = new CharacterController();
365
+
366
+ this.initListeners();
367
+ this.character.setDance('idle');
368
+ }
369
+
370
+ initListeners() {
371
+ // Play/Pause
372
+ document.addEventListener('toggle-play', (e) => {
373
+ state.isPlaying = e.detail.playing;
374
+ if (state.isPlaying) {
375
+ this.audio.startBeat();
376
+ this.character.setDance(state.currentDance);
377
+ } else {
378
+ this.audio.stopBeat();
379
+ this.character.setDance('idle');
380
+ }
381
+ });
382
+
383
+ // Dance change
384
+ document.addEventListener('dance-select', (e) => {
385
+ state.currentDance = e.detail.dance;
386
+ if (state.isPlaying) {
387
+ this.character.setDance(state.currentDance);
388
+ }
389
+ });
390
+
391
+ // Tempo change
392
+ document.addEventListener('tempo-change', (e) => {
393
+ this.audio.setTempo(e.detail.tempo);
394
+ });
395
+
396
+ // Beat event
397
+ document.addEventListener('beat', () => {
398
+ if (state.isPlaying) {
399
+ this.character.pulse();
400
+ // Random particles
401
+ const x = Math.random() * window.innerWidth;
402
+ const y = window.innerHeight - 200;
403
+ this.visuals.createParticle(x, y);
404
+ }
405
+ });
406
+
407
+ // Color change
408
+ document.addEventListener('color-change', (e) => {
409
+ const { primary, secondary } = e.detail;
410
+ document.documentElement.style.setProperty('--primary-hue', this.hexToHue(primary));
411
+ document.documentElement.style.setProperty('--secondary-hue', this.hexToHue(secondary));
412
+ });
413
+
414
+ // Click effects
415
+ document.addEventListener('click', (e) => {
416
+ if (e.target.closest('button') || e.target.closest('.control-btn')) {
417
+ this.visuals.createBurst(e.clientX, e.clientY);
418
+ }
419
+ });
420
+ }
421
+
422
+ hexToHue(hex) {
423
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
424
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
425
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
426
+ const max = Math.max(r, g, b);
427
+ const min = Math.min(r, g, b);
428
+ let h = 0;
429
+ if (max !== min) {
430
+ const d = max - min;
431
+ h = max === r ? (g - b) / d + (g < b ? 6 : 0) :
432
+ max === g ? (b - r) / d + 2 :
433
+ (r - g) / d + 4;
434
+ h *= 60;
435
+ }
436
+ return h;
437
+ }
438
+ }
439
+
440
+ // Initialize when DOM is ready
441
+ document.addEventListener('DOMContentLoaded', () => {
442
+ window.grooveApp = new GrooveApp();
443
+ });
style.css CHANGED
@@ -1,28 +1,191 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Custom CSS for GrooveToon */
2
+ @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;600;700;800&display=swap');
3
+
4
+ /* CSS Variables for Theme */
5
+ :root {
6
+ --primary-hue: 348;
7
+ --secondary-hue: 262;
8
+ --bg-luminance: 8%;
9
+ --glow-strength: 1;
10
+ }
11
+
12
+ * {
13
+ font-family: 'Nunito', sans-serif;
14
+ }
15
+
16
+ h1, h2, h3, .font-display {
17
+ font-family: 'Fredoka One', cursive;
18
+ }
19
+
20
+ /* Smooth Transitions */
21
+ * {
22
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
23
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
24
+ transition-duration: 200ms;
25
+ }
26
+
27
+ /* Glass Effect */
28
+ .glass {
29
+ background: rgba(255, 255, 255, 0.05);
30
+ backdrop-filter: blur(10px);
31
+ border: 1px solid rgba(255, 255, 255, 0.1);
32
+ }
33
+
34
+ .glass-dark {
35
+ background: rgba(0, 0, 0, 0.3);
36
+ backdrop-filter: blur(10px);
37
+ border: 1px solid rgba(255, 255, 255, 0.05);
38
+ }
39
+
40
+ /* Neon Glow Effects */
41
+ .neon-primary {
42
+ box-shadow: 0 0 20px hsla(var(--primary-hue), 90%, 60%, 0.5),
43
+ 0 0 40px hsla(var(--primary-hue), 90%, 60%, 0.3),
44
+ 0 0 60px hsla(var(--primary-hue), 90%, 60%, 0.1);
45
+ }
46
+
47
+ .neon-secondary {
48
+ box-shadow: 0 0 20px hsla(var(--secondary-hue), 90%, 60%, 0.5),
49
+ 0 0 40px hsla(var(--secondary-hue), 90%, 60%, 0.3),
50
+ 0 0 60px hsla(var(--secondary-hue), 90%, 60%, 0.1);
51
+ }
52
+
53
+ /* Character Animations */
54
+ @keyframes dance-bounce {
55
+ 0%, 100% { transform: translateY(0) scale(1); }
56
+ 50% { transform: translateY(-20px) scale(1.02); }
57
+ }
58
+
59
+ @keyframes dance-sway {
60
+ 0%, 100% { transform: rotate(-5deg); }
61
+ 50% { transform: rotate(5deg); }
62
+ }
63
+
64
+ @keyframes dance-spin {
65
+ 0% { transform: rotate(0deg) scale(1); }
66
+ 50% { transform: rotate(180deg) scale(1.1); }
67
+ 100% { transform: rotate(360deg) scale(1); }
68
+ }
69
+
70
+ @keyframes dance-wave {
71
+ 0%, 100% { transform: rotate(-20deg) translateY(0); }
72
+ 25% { transform: rotate(10deg) translateY(-10px); }
73
+ 50% { transform: rotate(-10deg) translateY(0); }
74
+ 75% { transform: rotate(20deg) translateY(-10px); }
75
+ }
76
+
77
+ @keyframes dance-head-bop {
78
+ 0%, 100% { transform: rotate(0deg) translateY(0); }
79
+ 25% { transform: rotate(-10deg) translateY(5px); }
80
+ 50% { transform: rotate(0deg) translateY(0); }
81
+ 75% { transform: rotate(10deg) translateY(5px); }
82
  }
83
 
84
+ @keyframes float-gentle {
85
+ 0%, 100% { transform: translateY(0); }
86
+ 50% { transform: translateY(-10px); }
87
  }
88
 
89
+ @keyframes glow-pulse {
90
+ 0%, 100% { filter: brightness(1) drop-shadow(0 0 5px currentColor); }
91
+ 50% { filter: brightness(1.2) drop-shadow(0 0 20px currentColor); }
 
 
92
  }
93
 
94
+ /* Particle Effects */
95
+ @keyframes particle-float {
96
+ 0% { transform: translateY(100vh) rotate(0deg); opacity: 0; }
97
+ 10% { opacity: 1; }
98
+ 90% { opacity: 1; }
99
+ 100% { transform: translateY(-100vh) rotate(720deg); opacity: 0; }
100
  }
101
 
102
+ .particle {
103
+ position: fixed;
104
+ pointer-events: none;
105
+ animation: particle-float linear infinite;
106
  }
107
+
108
+ /* Custom Scrollbar */
109
+ ::-webkit-scrollbar {
110
+ width: 8px;
111
+ }
112
+
113
+ ::-webkit-scrollbar-track {
114
+ background: #171717;
115
+ }
116
+
117
+ ::-webkit-scrollbar-thumb {
118
+ background: linear-gradient(180deg, #f43f5e, #8b5cf6);
119
+ border-radius: 4px;
120
+ }
121
+
122
+ ::-webkit-scrollbar-thumb:hover {
123
+ background: linear-gradient(180deg, #fb7185, #a78bfa);
124
+ }
125
+
126
+ /* Range Slider Styling */
127
+ input[type="range"] {
128
+ -webkit-appearance: none;
129
+ appearance: none;
130
+ background: transparent;
131
+ cursor: pointer;
132
+ }
133
+
134
+ input[type="range"]::-webkit-slider-track {
135
+ background: rgba(255, 255, 255, 0.1);
136
+ height: 6px;
137
+ border-radius: 3px;
138
+ }
139
+
140
+ input[type="range"]::-webkit-slider-thumb {
141
+ -webkit-appearance: none;
142
+ appearance: none;
143
+ background: linear-gradient(135deg, #f43f5e, #8b5cf6);
144
+ height: 20px;
145
+ width: 20px;
146
+ border-radius: 50%;
147
+ margin-top: -7px;
148
+ box-shadow: 0 0 10px rgba(244, 63, 94, 0.5);
149
+ }
150
+
151
+ input[type="range"]::-moz-range-track {
152
+ background: rgba(255, 255, 255, 0.1);
153
+ height: 6px;
154
+ border-radius: 3px;
155
+ }
156
+
157
+ input[type="range"]::-moz-range-thumb {
158
+ background: linear-gradient(135deg, #f43f5e, #8b5cf6);
159
+ height: 20px;
160
+ width: 20px;
161
+ border-radius: 50%;
162
+ border: none;
163
+ box-shadow: 0 0 10px rgba(244, 63, 94, 0.5);
164
+ }
165
+
166
+ /* Beat Indicator */
167
+ .beat-bar {
168
+ animation: beat-pulse 0.5s ease-in-out;
169
+ }
170
+
171
+ @keyframes beat-pulse {
172
+ 0%, 100% { transform: scaleY(1); }
173
+ 50% { transform: scaleY(2); }
174
+ }
175
+
176
+ /* Responsive Touch Targets */
177
+ @media (hover: none) and (pointer: coarse) {
178
+ button, .control-btn {
179
+ min-height: 44px;
180
+ min-width: 44px;
181
+ }
182
+ }
183
+
184
+ /* Reduced Motion */
185
+ @media (prefers-reduced-motion: reduce) {
186
+ *, *::before, *::after {
187
+ animation-duration: 0.01ms !important;
188
+ animation-iteration-count: 1 !important;
189
+ transition-duration: 0.01ms !important;
190
+ }
191
+ }