fredmo commited on
Commit
8b73986
·
verified ·
1 Parent(s): 0b9d7e1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +423 -18
index.html CHANGED
@@ -1,19 +1,424 @@
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>
3
+ <head>
4
+ <title>K-pop Dance Challenge ✨</title>
5
+ <meta charset="utf-8">
6
+ <link rel="preconnect" href="https://fonts.googleapis.com">
7
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8
+ <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@700&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --pink: #ffafcc;
12
+ --blue: #a2d2ff;
13
+ --purple: #cdb4db;
14
+ --white: #ffffff;
15
+ --dark-text: #333;
16
+ }
17
+
18
+ body {
19
+ font-family: 'Nunito', sans-serif;
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ height: 100vh;
24
+ margin: 0;
25
+ background: linear-gradient(45deg, var(--pink), var(--blue));
26
+ color: var(--dark-text);
27
+ }
28
+
29
+ .container {
30
+ text-align: center;
31
+ background: rgba(255, 255, 255, 0.7);
32
+ padding: 40px;
33
+ border-radius: 20px;
34
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
35
+ backdrop-filter: blur(4px);
36
+ border: 1px solid rgba(255, 255, 255, 0.18);
37
+ }
38
+
39
+ #song-selection, #game, #result {
40
+ display: none;
41
+ }
42
+
43
+ h1 {
44
+ font-size: 2.5em;
45
+ line-height: 1.2;
46
+ }
47
+
48
+ #video-container {
49
+ position: relative;
50
+ width: 640px;
51
+ height: 480px;
52
+ margin: 0 auto;
53
+ border: 4px solid var(--white);
54
+ border-radius: 20px;
55
+ overflow: hidden;
56
+ box-shadow: 0 4px 15px rgba(0,0,0,0.2);
57
+ }
58
+
59
+ #video {
60
+ width: 100%;
61
+ height: 100%;
62
+ transform: scaleX(-1); /* Mirror effect */
63
+ }
64
+
65
+ #output_canvas {
66
+ position: absolute;
67
+ top: 0;
68
+ left: 0;
69
+ width: 100%;
70
+ height: 100%;
71
+ transform: scaleX(-1); /* Match the video flip */
72
+ }
73
+
74
+ .overlay {
75
+ position: absolute;
76
+ top: 0;
77
+ left: 0;
78
+ right: 0;
79
+ bottom: 0;
80
+ display: flex;
81
+ justify-content: center;
82
+ align-items: center;
83
+ color: var(--white);
84
+ font-size: 80px;
85
+ font-weight: 700;
86
+ background-color: rgba(0, 0, 0, 0.4);
87
+ flex-direction: column;
88
+ z-index: 10;
89
+ text-shadow: 2px 2px 8px rgba(0,0,0,0.7);
90
+ }
91
+
92
+ .dancer-name {
93
+ position: absolute;
94
+ top: 20px;
95
+ left: 20px;
96
+ background-color: var(--purple);
97
+ color: var(--white);
98
+ padding: 10px 20px;
99
+ border-radius: 15px;
100
+ font-size: 24px;
101
+ z-index: 5;
102
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
103
+ }
104
+
105
+ .controls {
106
+ margin-top: 20px;
107
+ }
108
+
109
+ button {
110
+ padding: 15px 30px;
111
+ font-family: 'Nunito', sans-serif;
112
+ font-size: 18px;
113
+ font-weight: 700;
114
+ cursor: pointer;
115
+ margin: 10px;
116
+ border-radius: 50px;
117
+ border: none;
118
+ background-color: var(--pink);
119
+ color: var(--white);
120
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
121
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
122
+ }
123
+
124
+ button:hover {
125
+ transform: translateY(-3px);
126
+ box-shadow: 0 6px 15px rgba(0,0,0,0.2);
127
+ }
128
+
129
+ #toggle-skeleton {
130
+ background-color: var(--blue);
131
+ }
132
+ #next-dancer {
133
+ background-color: var(--purple);
134
+ }
135
+ #result h1 {
136
+ font-size: 3em;
137
+ }
138
+
139
+ </style>
140
+ </head>
141
+ <body>
142
+
143
+ <div id="app-container">
144
+ <div id="song-selection" class="container">
145
+ <h1> 🍨 K AI Pop Dance - Challenge your Friend 💖</h1>
146
+ <p>Choose your battle track!</p>
147
+ <button id="song-aespa">Aespa - Dirty Work</button>
148
+ <button id="song-itzy">Itzy - girls will be girl</button>
149
+ <button id="song-blackpink">BlackPink - Jump</button>
150
+ </div>
151
+
152
+ <div id="game" class="container">
153
+ <div id="video-container">
154
+ <video id="video" autoplay playsinline></video>
155
+ <canvas id="output_canvas"></canvas>
156
+ <div id="countdown" class="overlay" style="display: none;"></div>
157
+ <div id="dancer-name" class="dancer-name"></div>
158
+ </div>
159
+ <div class="controls">
160
+ <button id="toggle-skeleton">My Detected Moves</button>
161
+ <button id="next-dancer" style="display: none;">Next Dancer! ✨</button>
162
+ </div>
163
+ </div>
164
+
165
+ <div id="result" class="container">
166
+ <h1>And the result is...</h1>
167
+ <h1 id="score"></h1>
168
+ </div>
169
+ </div>
170
+
171
+ <audio id="audio-player"></audio>
172
+
173
+ <script type="module">
174
+ import { PoseLandmarker, FilesetResolver, DrawingUtils } from "https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]";
175
+
176
+ // DOM Elements
177
+ const video = document.getElementById('video');
178
+ const canvasElement = document.getElementById('output_canvas');
179
+ const canvasCtx = canvasElement.getContext('2d');
180
+ const drawingUtils = new DrawingUtils(canvasCtx);
181
+ const songSelectionScreen = document.getElementById('song-selection');
182
+ const gameScreen = document.getElementById('game');
183
+ const resultScreen = document.getElementById('result');
184
+ const countdownOverlay = document.getElementById('countdown');
185
+ const dancerNameDisplay = document.getElementById('dancer-name');
186
+ const nextDancerButton = document.getElementById('next-dancer');
187
+ const toggleSkeletonButton = document.getElementById('toggle-skeleton');
188
+ const scoreDisplay = document.getElementById('score');
189
+ const audioPlayer = document.getElementById('audio-player');
190
+
191
+ // Game State
192
+ let poseLandmarker;
193
+ let player1Data = [];
194
+ let player2Data = [];
195
+ let currentSong = '';
196
+ let isPlayer1 = true;
197
+ let captureData = false;
198
+ let showSkeleton = false;
199
+ let frameCounter = 0;
200
+ const CAPTURE_INTERVAL = 5; // Capture every 5 frames
201
+
202
+ const songFiles = {
203
+ aespa: 'aespa.mp3',
204
+ itzy: 'itzy.mp3',
205
+ blackpink: 'blackpink.mp3'
206
+ };
207
+
208
+ const dancerNameMap = {
209
+ aespa: { p1: 'My 1 🦋', p2: 'My 2 🦋' },
210
+ itzy: { p1: 'Midzy 1 👑', p2: 'Midzy 2 👑' },
211
+ blackpink: { p1: 'Blink 1 🖤', p2: 'Blink 2 💖' }
212
+ };
213
+
214
+ // These are the body segments we will use for comparison.
215
+ const POSE_SEGMENTS = [
216
+ ['left_shoulder', 'left_elbow'], ['left_elbow', 'left_wrist'],
217
+ ['right_shoulder', 'right_elbow'], ['right_elbow', 'right_wrist'],
218
+ ['left_hip', 'right_hip'],
219
+ ['left_shoulder', 'left_hip'], ['right_shoulder', 'right_hip'],
220
+ ['left_hip', 'left_knee'], ['left_knee', 'left_ankle'],
221
+ ['right_hip', 'right_knee'], ['right_knee', 'right_ankle']
222
+ ];
223
+
224
+ // Landmark names to indices map (for convenience)
225
+ let landmarkNameToIndex = {};
226
+
227
+ const POSE_LANDMARK_NAMES = [
228
+ 'nose', 'left_eye_inner', 'left_eye', 'left_eye_outer', 'right_eye_inner', 'right_eye', 'right_eye_outer',
229
+ 'left_ear', 'right_ear', 'mouth_left', 'mouth_right', 'left_shoulder', 'right_shoulder', 'left_elbow',
230
+ 'right_elbow', 'left_wrist', 'right_wrist', 'left_pinky', 'right_pinky', 'left_index', 'right_index',
231
+ 'left_thumb', 'right_thumb', 'left_hip', 'right_hip', 'left_knee', 'right_knee', 'left_ankle',
232
+ 'right_ankle', 'left_heel', 'right_heel', 'left_foot_index', 'right_foot_index'
233
+ ];
234
+
235
+
236
+ async function main() {
237
+ const filesetResolver = await FilesetResolver.forVisionTasks(
238
+ "https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/wasm"
239
+ );
240
+ poseLandmarker = await PoseLandmarker.createFromOptions(filesetResolver, {
241
+ baseOptions: {
242
+ modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task`,
243
+ delegate: "GPU"
244
+ },
245
+ runningMode: "VIDEO",
246
+ numPoses: 1
247
+ });
248
+
249
+ POSE_LANDMARK_NAMES.forEach((name, index) => {
250
+ landmarkNameToIndex[name] = index;
251
+ });
252
+
253
+ await initWebcam();
254
+ songSelectionScreen.style.display = 'block';
255
+ }
256
+
257
+ async function initWebcam() {
258
+ try {
259
+ const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } });
260
+ video.srcObject = stream;
261
+ video.addEventListener("loadeddata", predictWebcam);
262
+ } catch (err) {
263
+ console.error("Error accessing webcam: ", err);
264
+ alert("Oops! Could not access webcam. Please allow access and reload the page. 💖");
265
+ }
266
+ }
267
+
268
+ let lastVideoTime = -1;
269
+ function predictWebcam() {
270
+ canvasElement.width = video.videoWidth;
271
+ canvasElement.height = video.videoHeight;
272
+
273
+ if (video.currentTime !== lastVideoTime) {
274
+ lastVideoTime = video.currentTime;
275
+ const poseLandmarkerResult = poseLandmarker.detectForVideo(video, performance.now());
276
+
277
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
278
+ if (showSkeleton && poseLandmarkerResult.landmarks.length > 0) {
279
+ drawSkeleton(poseLandmarkerResult.landmarks[0]);
280
+ }
281
+
282
+ frameCounter++;
283
+ if (captureData && frameCounter % CAPTURE_INTERVAL === 0 && poseLandmarkerResult.worldLandmarks.length > 0) {
284
+ const poseVector = getPoseVector(poseLandmarkerResult.worldLandmarks[0]);
285
+ if(poseVector){
286
+ if (isPlayer1) player1Data.push(poseVector);
287
+ else player2Data.push(poseVector);
288
+ }
289
+ }
290
+ }
291
+ window.requestAnimationFrame(predictWebcam);
292
+ }
293
+
294
+ function drawSkeleton(landmarks) {
295
+ drawingUtils.drawLandmarks(landmarks, {
296
+ radius: 5, color: '#FFFFFF', fillColor: 'var(--pink)'
297
+ });
298
+ drawingUtils.drawConnectors(landmarks, PoseLandmarker.POSE_CONNECTIONS, { color: 'var(--white)', lineWidth: 3 });
299
+ }
300
+
301
+ function selectSong(song) {
302
+ currentSong = song;
303
+ audioPlayer.src = songFiles[song];
304
+ songSelectionScreen.style.display = 'none';
305
+ gameScreen.style.display = 'block';
306
+ startPlayerDance();
307
+ }
308
+
309
+ function startPlayerDance() {
310
+ const playerNames = dancerNameMap[currentSong];
311
+ dancerNameDisplay.textContent = isPlayer1 ? playerNames.p1 : playerNames.p2;
312
+ runCountdown(startDanceSession);
313
+ }
314
+
315
+ function runCountdown(onComplete) {
316
+ countdownOverlay.style.display = 'flex';
317
+ let count = 3;
318
+ countdownOverlay.textContent = count;
319
+ const interval = setInterval(() => {
320
+ count--;
321
+ if (count > 0) countdownOverlay.textContent = count;
322
+ else if (count === 0) countdownOverlay.textContent = 'GO!';
323
+ else {
324
+ clearInterval(interval);
325
+ countdownOverlay.style.display = 'none';
326
+ onComplete();
327
+ }
328
+ }, 1000);
329
+ }
330
+
331
+ function startDanceSession() {
332
+ captureData = true;
333
+ audioPlayer.currentTime = 0;
334
+ audioPlayer.play();
335
+
336
+ setTimeout(() => {
337
+ audioPlayer.pause();
338
+ captureData = false;
339
+ if (isPlayer1) {
340
+ isPlayer1 = false;
341
+ nextDancerButton.style.display = 'inline-block';
342
+ } else {
343
+ calculateSimilarity();
344
+ }
345
+ }, 8000);
346
+ }
347
+
348
+ function getPoseVector(worldLandmarks) {
349
+ const vector = [];
350
+ for (const [start, end] of POSE_SEGMENTS) {
351
+ const startIdx = landmarkNameToIndex[start];
352
+ const endIdx = landmarkNameToIndex[end];
353
+
354
+ if(worldLandmarks[startIdx] && worldLandmarks[endIdx]){
355
+ const p1 = worldLandmarks[startIdx];
356
+ const p2 = worldLandmarks[endIdx];
357
+
358
+ const dx = p2.x - p1.x;
359
+ const dy = p2.y - p1.y;
360
+ const dz = p2.z - p1.z;
361
+
362
+ const mag = Math.sqrt(dx*dx + dy*dy + dz*dz);
363
+ if(mag === 0) continue;
364
+
365
+ vector.push(dx / mag, dy / mag, dz / mag);
366
+ }
367
+ }
368
+ return vector.length > 0 ? vector : null;
369
+ }
370
+
371
+ function cosineSimilarity(vecA, vecB) {
372
+ let dotProduct = 0.0;
373
+ let normA = 0.0;
374
+ let normB = 0.0;
375
+ for (let i = 0; i < vecA.length; i++) {
376
+ dotProduct += vecA[i] * vecB[i];
377
+ normA += vecA[i] * vecA[i];
378
+ normB += vecB[i] * vecB[i];
379
+ }
380
+ if (normA === 0 || normB === 0) return 0;
381
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
382
+ }
383
+
384
+ function calculateSimilarity() {
385
+ gameScreen.style.display = 'none';
386
+ resultScreen.style.display = 'block';
387
+ showSkeleton = false;
388
+
389
+ let totalSimilarity = 0;
390
+ const frameCount = Math.min(player1Data.length, player2Data.length);
391
+
392
+ if (frameCount < 5) {
393
+ scoreDisplay.textContent = "Not enough data captured! 😭 Try dancing more clearly!";
394
+ return;
395
+ }
396
+
397
+ for (let i = 0; i < frameCount; i++) {
398
+ totalSimilarity += cosineSimilarity(player1Data[i], player2Data[i]);
399
+ }
400
+
401
+ const avgSimilarity = totalSimilarity / frameCount;
402
+ const scaledScore = Math.pow(avgSimilarity, 2);
403
+ const percentage = Math.min(100, Math.round(scaledScore * 100));
404
+ scoreDisplay.textContent = `${percentage}% Similarity! Great job! 🎉`;
405
+ }
406
+
407
+ // Event Listeners
408
+ document.getElementById('song-aespa').addEventListener('click', () => selectSong('aespa'));
409
+ document.getElementById('song-itzy').addEventListener('click', () => selectSong('itzy'));
410
+ document.getElementById('song-blackpink').addEventListener('click', () => selectSong('blackpink'));
411
+ nextDancerButton.addEventListener('click', () => {
412
+ nextDancerButton.style.display = 'none';
413
+ startPlayerDance();
414
+ });
415
+ toggleSkeletonButton.addEventListener('click', () => {
416
+ showSkeleton = !showSkeleton;
417
+ toggleSkeletonButton.textContent = showSkeleton ? 'Hide Moves' : 'Show Moves';
418
+ });
419
+
420
+ // Start the application
421
+ main();
422
+ </script>
423
+ </body>
424
+ </html>