Mega-Evolutions commited on
Commit
d7a1de8
·
verified ·
1 Parent(s): d7b5d65

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +190 -0
index.html ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Rasengan</title>
7
+
8
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
11
+
12
+ <style>
13
+ html, body { margin: 0; height: 100%; background: #000; overflow: hidden; font-family: system-ui, sans-serif; }
14
+
15
+ .layer {
16
+ position: fixed; inset: 0;
17
+ width: 100vw; height: 100vh;
18
+ object-fit: cover;
19
+ transform: scaleX(-1);
20
+ }
21
+ #cam { z-index: 0; }
22
+ #skeleton { z-index: 2; pointer-events: none; }
23
+
24
+ .vignette {
25
+ position: fixed; inset: 0;
26
+ background: radial-gradient(ellipse at center, transparent 35%, rgba(0,0,0,0.55) 100%);
27
+ pointer-events: none; z-index: 4;
28
+ }
29
+
30
+ .rasengan {
31
+ position: fixed; top: 0; left: 0;
32
+ width: 1500px; height: auto;
33
+ transform: translate(-50%, -50%);
34
+ pointer-events: none;
35
+ mix-blend-mode: screen;
36
+ opacity: 0;
37
+ will-change: opacity, left, top;
38
+ z-index: 10;
39
+ }
40
+
41
+ #hint {
42
+ position: fixed; bottom: 16px; left: 50%;
43
+ transform: translateX(-50%);
44
+ color: #cfe9ff; font-size: 14px; letter-spacing: 0.04em;
45
+ background: rgba(0,0,0,0.45);
46
+ padding: 6px 12px; border-radius: 999px;
47
+ z-index: 6; pointer-events: none;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <video id="cam" class="layer" autoplay playsinline muted></video>
53
+ <canvas id="skeleton" class="layer"></canvas>
54
+ <div class="vignette"></div>
55
+
56
+ <video class="rasengan" data-slot="0" src="assets/rasengan.mp4" muted autoplay loop playsinline preload="auto"></video>
57
+ <video class="rasengan" data-slot="1" src="assets/rasengan.mp4" muted autoplay loop playsinline preload="auto"></video>
58
+
59
+ <script>
60
+ const camVideo = document.getElementById('cam');
61
+ const canvas = document.getElementById('skeleton');
62
+ const pen = canvas.getContext('2d');
63
+ const orbs = Array.from(document.querySelectorAll('.rasengan'));
64
+
65
+ // Two distinct palettes so each hand's skeleton is obviously different,
66
+ // and bones vs joints inside one hand are also different colors.
67
+ const PALETTES = {
68
+ Left: { bone: '#ff8a3d', joint: '#fff36a', glow: '#ff5e1a' }, // warm — Naruto orange
69
+ Right: { bone: '#62f0ff', joint: '#ff66e0', glow: '#1aa6ff' } // cool — chakra blue
70
+ };
71
+
72
+ // Per-hand fade-in / fade-out state for the orbs.
73
+ const orbState = [
74
+ { charge: 0, wasOpen: false },
75
+ { charge: 0, wasOpen: false }
76
+ ];
77
+
78
+ const FINGER_TIPS = [8, 12, 16, 20];
79
+ const FINGER_KNUCKLES = [6, 10, 14, 18];
80
+
81
+ function isPalmOpen(lm) {
82
+ const wrist = lm[0];
83
+ let extended = 0;
84
+ for (let i = 0; i < FINGER_TIPS.length; i++) {
85
+ const tip = lm[FINGER_TIPS[i]];
86
+ const knk = lm[FINGER_KNUCKLES[i]];
87
+ const dTip = Math.hypot(tip.x - wrist.x, tip.y - wrist.y);
88
+ const dKnk = Math.hypot(knk.x - wrist.x, knk.y - wrist.y);
89
+ if (dTip > dKnk) extended++;
90
+ }
91
+ return extended >= 3;
92
+ }
93
+
94
+ // Draw bones in one color, joints in another, with a colored glow,
95
+ // so the tracking sticks visibly follow each finger.
96
+ function paintSkeleton(lm, colors) {
97
+ pen.save();
98
+ pen.shadowBlur = 18;
99
+ pen.shadowColor = colors.glow;
100
+ drawConnectors(pen, lm, HAND_CONNECTIONS, { color: colors.bone, lineWidth: 3 });
101
+ pen.restore();
102
+
103
+ pen.save();
104
+ pen.shadowBlur = 10;
105
+ pen.shadowColor = colors.glow;
106
+ pen.fillStyle = colors.joint;
107
+ for (const p of lm) {
108
+ pen.beginPath();
109
+ pen.arc(p.x * canvas.width, p.y * canvas.height, 3.5, 0, Math.PI * 2);
110
+ pen.fill();
111
+ }
112
+ pen.restore();
113
+ }
114
+
115
+ function placeOrb(slot, lm, charge) {
116
+ const wrist = lm[0];
117
+ const midKnk = lm[9];
118
+
119
+ const cx = (wrist.x + midKnk.x) / 2;
120
+ const cy = (wrist.y + midKnk.y) / 2;
121
+ const handSize = Math.hypot(midKnk.x - wrist.x, midKnk.y - wrist.y);
122
+ const lift = handSize * 1.8;
123
+
124
+ const orb = orbs[slot];
125
+ orb.style.left = `${(1 - cx) * window.innerWidth}px`;
126
+ orb.style.top = `${(cy - lift) * window.innerHeight}px`;
127
+ orb.style.opacity = charge.toFixed(3);
128
+ }
129
+
130
+ function onResults(res) {
131
+ canvas.width = camVideo.videoWidth || canvas.clientWidth;
132
+ canvas.height = camVideo.videoHeight || canvas.clientHeight;
133
+ pen.clearRect(0, 0, canvas.width, canvas.height);
134
+
135
+ const seen = [false, false];
136
+
137
+ if (res.multiHandLandmarks && res.multiHandedness) {
138
+ res.multiHandLandmarks.forEach((lm, i) => {
139
+ const label = res.multiHandedness[i].label; // "Left" | "Right"
140
+ const slot = label === 'Right' ? 1 : 0;
141
+ const colors = PALETTES[label] || PALETTES.Left;
142
+ seen[slot] = true;
143
+
144
+ paintSkeleton(lm, colors);
145
+
146
+ const open = isPalmOpen(lm);
147
+ const st = orbState[slot];
148
+ st.charge = Math.max(0, Math.min(1, st.charge + (open ? 0.06 : -0.18)));
149
+
150
+ if (open && !st.wasOpen) {
151
+ const v = orbs[slot];
152
+ v.currentTime = 0;
153
+ v.play().catch(err => console.warn('rasengan play failed', err));
154
+ }
155
+ st.wasOpen = open;
156
+
157
+ if (st.charge > 0.01) placeOrb(slot, lm, st.charge);
158
+ else orbs[slot].style.opacity = 0;
159
+ });
160
+ }
161
+
162
+ // Decay any hand we didn't see this frame.
163
+ for (let i = 0; i < 2; i++) {
164
+ if (seen[i]) continue;
165
+ orbState[i].charge = Math.max(0, orbState[i].charge - 0.18);
166
+ orbState[i].wasOpen = false;
167
+ orbs[i].style.opacity = orbState[i].charge.toFixed(3);
168
+ }
169
+ }
170
+
171
+ const hands = new Hands({
172
+ locateFile: f => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${f}`
173
+ });
174
+ hands.setOptions({
175
+ maxNumHands: 2,
176
+ modelComplexity: 1,
177
+ minDetectionConfidence: 0.65,
178
+ minTrackingConfidence: 0.65
179
+ });
180
+ hands.onResults(onResults);
181
+
182
+ const camera = new Camera(camVideo, {
183
+ onFrame: async () => { await hands.send({ image: camVideo }); },
184
+ width: 1280,
185
+ height: 720
186
+ });
187
+ camera.start();
188
+ </script>
189
+ </body>
190
+ </html>