Spaces:
Running
Running
Update index.html
Browse files- index.html +32 -29
index.html
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
6 |
-
<title>
|
7 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
|
8 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/MidiConvert.min.js"></script>
|
9 |
<style>
|
@@ -12,13 +12,13 @@
|
|
12 |
--piano-bg-start: #1c1c1c;
|
13 |
--piano-bg-end: #0d0d0d;
|
14 |
--piano-border: #2f2f2f;
|
15 |
-
--white-key-bg-start: #fdfdfd;
|
16 |
--white-key-bg-end: #e4e4e4;
|
17 |
--white-key-border: #9e9e9e;
|
18 |
--black-key-bg-start: #353535;
|
19 |
--black-key-bg-end: #181818;
|
20 |
--black-key-border: #050505;
|
21 |
-
--hit-line-color: rgba(255, 80, 180, 0.
|
22 |
--key-active-glow: rgba(255, 80, 180, 0.9);
|
23 |
--particle-base-color-h: 320;
|
24 |
--default-note-color-h: 180;
|
@@ -149,31 +149,33 @@
|
|
149 |
box-shadow: -1px 0 2px rgba(0,0,0,0.4), 1px 0 2px rgba(0,0,0,0.4), 0 2px 3px rgba(0,0,0,0.55), inset 0 -1px 1px rgba(60,60,60,0.3);
|
150 |
}
|
151 |
|
152 |
-
.white-key.active {
|
153 |
-
background: linear-gradient(to bottom, #
|
154 |
transform: perspective(500px) rotateX(1.2deg) translateY(1.8px);
|
155 |
border-bottom-width: 3.2px;
|
156 |
box-shadow: 0 0 22px 6px var(--key-active-glow),
|
157 |
inset 0 -1px 1px rgba(255,255,255,0.38);
|
|
|
158 |
}
|
159 |
|
160 |
-
.black-key.active {
|
161 |
background: linear-gradient(to bottom, #222222, #020202);
|
162 |
transform: perspective(500px) rotateX(0.6deg) translateY(1.2px);
|
163 |
border-bottom-width: 2.8px;
|
164 |
box-shadow: 0 0 22px 6px var(--key-active-glow),
|
165 |
inset 0 -1px 1px rgba(60,60,60,0.18);
|
|
|
166 |
}
|
167 |
|
168 |
#note-fall-area {
|
169 |
-
width: var(--piano-actual-width, 100%);
|
170 |
-
height: calc(100vh - 28vh -
|
171 |
position: absolute;
|
172 |
top: 0;
|
173 |
left: 50%;
|
174 |
transform: translateX(-50%);
|
175 |
pointer-events: none;
|
176 |
-
/* border: 1px solid red; */
|
177 |
}
|
178 |
|
179 |
.note-bar {
|
@@ -204,18 +206,17 @@
|
|
204 |
.particle-container {
|
205 |
position: absolute;
|
206 |
width: var(--piano-actual-width, 100%);
|
207 |
-
height: 28vh;
|
208 |
-
bottom: 5px;
|
209 |
left: 50%;
|
210 |
transform: translateX(-50%);
|
211 |
pointer-events: none;
|
212 |
z-index: 5;
|
213 |
-
/* border: 1px dashed lime; */ /* Debugging */
|
214 |
}
|
215 |
.particle {
|
216 |
position: absolute;
|
217 |
-
width: 8px;
|
218 |
-
height: 8px;
|
219 |
background-color: var(--particle-color);
|
220 |
border-radius: 50%;
|
221 |
opacity: 1;
|
@@ -225,9 +226,9 @@
|
|
225 |
@keyframes particleAnim {
|
226 |
0% { transform: translateY(0) translateX(0) scale(1.3); opacity: 0.95; }
|
227 |
100% {
|
228 |
-
transform: translateY(calc( (var(--random-y) - 0.7) * -
|
229 |
-
translateX(calc( (var(--random-x) - 0.5) *
|
230 |
-
scale(0.
|
231 |
opacity: 0;
|
232 |
}
|
233 |
}
|
@@ -278,11 +279,9 @@
|
|
278 |
'a': 'C4', 'w': 'C#4', 's': 'D4', 'e': 'D#4', 'd': 'E4', 'f': 'F4',
|
279 |
't': 'F#4', 'g': 'G4', 'y': 'G#4', 'h': 'A4', 'u': 'A#4', 'j': 'B4',
|
280 |
'k': 'C5', 'o': 'C#5', 'l': 'D5', 'p': 'D#5', ';': 'E5', "'": 'F5'
|
281 |
-
// Add more mappings as needed
|
282 |
};
|
283 |
const activeKeyboardKeys = new Set();
|
284 |
|
285 |
-
|
286 |
function createPianoKeys() {
|
287 |
pianoElement.innerHTML = '';
|
288 |
whiteKeyCount = 0;
|
@@ -314,7 +313,7 @@
|
|
314 |
if(e && e.preventDefault) e.preventDefault();
|
315 |
if(keyElement.classList.contains('active')) return;
|
316 |
keyElement.classList.add('active');
|
317 |
-
const hue = Math.random() * 360;
|
318 |
keyElement.style.setProperty('--key-active-glow', `hsla(${hue}, 100%, 65%, 0.9)`);
|
319 |
synth.triggerAttack(noteName, Tone.now());
|
320 |
createKeyParticles(keyElement, `hsla(${hue}, 100%, 65%, 0.9)`);
|
@@ -350,8 +349,7 @@
|
|
350 |
particle.style.setProperty('--random-y', Math.random());
|
351 |
|
352 |
const xPos = (keyRect.left - particleContRect.left) + (keyRect.width / 2);
|
353 |
-
|
354 |
-
const yPos = (keyRect.top - particleContRect.top) - 10; // 10px above the key's top
|
355 |
|
356 |
particle.style.left = `${xPos}px`;
|
357 |
particle.style.top = `${yPos}px`;
|
@@ -441,8 +439,7 @@
|
|
441 |
noteElement.classList.add('note-bar');
|
442 |
|
443 |
const keyRect = targetKeyElement.getBoundingClientRect();
|
444 |
-
|
445 |
-
|
446 |
const keyIsBlack = noteData.note.includes('#');
|
447 |
noteElement.style.width = ((keyIsBlack ? BLACK_KEY_WIDTH_PX : WHITE_KEY_WIDTH_PX) - (keyIsBlack ? 1 : 2)) + 'px';
|
448 |
noteElement.style.left = (keyRect.left - fallAreaRect.left + 1) + 'px';
|
@@ -460,13 +457,16 @@
|
|
460 |
noteElement.style.top = `-${noteVisualHeight}px`;
|
461 |
noteFallArea.appendChild(noteElement);
|
462 |
|
463 |
-
const targetY = hitLineTopRelativeToFallArea - noteVisualHeight;
|
|
|
|
|
464 |
|
465 |
noteElement.animate([
|
466 |
-
{ transform: `translateY(0px)` },
|
467 |
-
{ transform: `translateY(${targetY}px)
|
|
|
468 |
], {
|
469 |
-
duration:
|
470 |
easing: 'linear'
|
471 |
}).onfinish = () => {
|
472 |
if (noteElement.parentNode) noteElement.remove();
|
@@ -476,10 +476,12 @@
|
|
476 |
synth.triggerAttackRelease(noteData.note, noteData.duration, hitTime, noteData.velocity);
|
477 |
if (targetKeyElement) {
|
478 |
targetKeyElement.style.setProperty('--key-active-glow-hue', (colorIdx * 40) % 360);
|
479 |
-
targetKeyElement.classList.add('active');
|
|
|
480 |
createKeyParticles(targetKeyElement, noteMainColor);
|
481 |
setTimeout(() => {
|
482 |
targetKeyElement.classList.remove('active');
|
|
|
483 |
}, noteData.duration * 1000);
|
484 |
}
|
485 |
}, time + (NOTE_FALL_DURATION_MS / 1000));
|
@@ -513,6 +515,7 @@
|
|
513 |
|
514 |
window.addEventListener('resize', () => {
|
515 |
createPianoKeys();
|
|
|
516 |
});
|
517 |
|
518 |
</script>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
6 |
+
<title>Enhanced Piano Visualizer</title>
|
7 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
|
8 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/MidiConvert.min.js"></script>
|
9 |
<style>
|
|
|
12 |
--piano-bg-start: #1c1c1c;
|
13 |
--piano-bg-end: #0d0d0d;
|
14 |
--piano-border: #2f2f2f;
|
15 |
+
--white-key-bg-start: #fdfdfd;
|
16 |
--white-key-bg-end: #e4e4e4;
|
17 |
--white-key-border: #9e9e9e;
|
18 |
--black-key-bg-start: #353535;
|
19 |
--black-key-bg-end: #181818;
|
20 |
--black-key-border: #050505;
|
21 |
+
--hit-line-color: rgba(255, 80, 180, 0.85);
|
22 |
--key-active-glow: rgba(255, 80, 180, 0.9);
|
23 |
--particle-base-color-h: 320;
|
24 |
--default-note-color-h: 180;
|
|
|
149 |
box-shadow: -1px 0 2px rgba(0,0,0,0.4), 1px 0 2px rgba(0,0,0,0.4), 0 2px 3px rgba(0,0,0,0.55), inset 0 -1px 1px rgba(60,60,60,0.3);
|
150 |
}
|
151 |
|
152 |
+
.white-key.active, .white-key.pressed-by-note {
|
153 |
+
background: linear-gradient(to bottom, #d5d5d5, #c2c2c2);
|
154 |
transform: perspective(500px) rotateX(1.2deg) translateY(1.8px);
|
155 |
border-bottom-width: 3.2px;
|
156 |
box-shadow: 0 0 22px 6px var(--key-active-glow),
|
157 |
inset 0 -1px 1px rgba(255,255,255,0.38);
|
158 |
+
z-index: 3 !important; /* Ensure active white key is on top */
|
159 |
}
|
160 |
|
161 |
+
.black-key.active, .black-key.pressed-by-note {
|
162 |
background: linear-gradient(to bottom, #222222, #020202);
|
163 |
transform: perspective(500px) rotateX(0.6deg) translateY(1.2px);
|
164 |
border-bottom-width: 2.8px;
|
165 |
box-shadow: 0 0 22px 6px var(--key-active-glow),
|
166 |
inset 0 -1px 1px rgba(60,60,60,0.18);
|
167 |
+
z-index: 4 !important; /* Ensure active black key is on top */
|
168 |
}
|
169 |
|
170 |
#note-fall-area {
|
171 |
+
width: var(--piano-actual-width, 100%);
|
172 |
+
height: calc(100vh - 28vh - 5px - 10px); /* Piano container bottom padding + body bottom padding */
|
173 |
position: absolute;
|
174 |
top: 0;
|
175 |
left: 50%;
|
176 |
transform: translateX(-50%);
|
177 |
pointer-events: none;
|
178 |
+
/* border: 1px solid red; */
|
179 |
}
|
180 |
|
181 |
.note-bar {
|
|
|
206 |
.particle-container {
|
207 |
position: absolute;
|
208 |
width: var(--piano-actual-width, 100%);
|
209 |
+
height: 28vh;
|
210 |
+
bottom: 5px;
|
211 |
left: 50%;
|
212 |
transform: translateX(-50%);
|
213 |
pointer-events: none;
|
214 |
z-index: 5;
|
|
|
215 |
}
|
216 |
.particle {
|
217 |
position: absolute;
|
218 |
+
width: 8px;
|
219 |
+
height: 8px;
|
220 |
background-color: var(--particle-color);
|
221 |
border-radius: 50%;
|
222 |
opacity: 1;
|
|
|
226 |
@keyframes particleAnim {
|
227 |
0% { transform: translateY(0) translateX(0) scale(1.3); opacity: 0.95; }
|
228 |
100% {
|
229 |
+
transform: translateY(calc( (var(--random-y) - 0.7) * -100px - 50px))
|
230 |
+
translateX(calc( (var(--random-x) - 0.5) * 70px))
|
231 |
+
scale(0.1);
|
232 |
opacity: 0;
|
233 |
}
|
234 |
}
|
|
|
279 |
'a': 'C4', 'w': 'C#4', 's': 'D4', 'e': 'D#4', 'd': 'E4', 'f': 'F4',
|
280 |
't': 'F#4', 'g': 'G4', 'y': 'G#4', 'h': 'A4', 'u': 'A#4', 'j': 'B4',
|
281 |
'k': 'C5', 'o': 'C#5', 'l': 'D5', 'p': 'D#5', ';': 'E5', "'": 'F5'
|
|
|
282 |
};
|
283 |
const activeKeyboardKeys = new Set();
|
284 |
|
|
|
285 |
function createPianoKeys() {
|
286 |
pianoElement.innerHTML = '';
|
287 |
whiteKeyCount = 0;
|
|
|
313 |
if(e && e.preventDefault) e.preventDefault();
|
314 |
if(keyElement.classList.contains('active')) return;
|
315 |
keyElement.classList.add('active');
|
316 |
+
const hue = parseFloat(keyElement.style.getPropertyValue('--key-active-glow-hue') || Math.random() * 360);
|
317 |
keyElement.style.setProperty('--key-active-glow', `hsla(${hue}, 100%, 65%, 0.9)`);
|
318 |
synth.triggerAttack(noteName, Tone.now());
|
319 |
createKeyParticles(keyElement, `hsla(${hue}, 100%, 65%, 0.9)`);
|
|
|
349 |
particle.style.setProperty('--random-y', Math.random());
|
350 |
|
351 |
const xPos = (keyRect.left - particleContRect.left) + (keyRect.width / 2);
|
352 |
+
const yPos = (keyRect.top - particleContRect.top) - 10;
|
|
|
353 |
|
354 |
particle.style.left = `${xPos}px`;
|
355 |
particle.style.top = `${yPos}px`;
|
|
|
439 |
noteElement.classList.add('note-bar');
|
440 |
|
441 |
const keyRect = targetKeyElement.getBoundingClientRect();
|
442 |
+
|
|
|
443 |
const keyIsBlack = noteData.note.includes('#');
|
444 |
noteElement.style.width = ((keyIsBlack ? BLACK_KEY_WIDTH_PX : WHITE_KEY_WIDTH_PX) - (keyIsBlack ? 1 : 2)) + 'px';
|
445 |
noteElement.style.left = (keyRect.left - fallAreaRect.left + 1) + 'px';
|
|
|
457 |
noteElement.style.top = `-${noteVisualHeight}px`;
|
458 |
noteFallArea.appendChild(noteElement);
|
459 |
|
460 |
+
const targetY = hitLineTopRelativeToFallArea - noteVisualHeight; // Note's bottom should reach hit line's top
|
461 |
+
|
462 |
+
const noteFallAndPersistDuration = NOTE_FALL_DURATION_MS + (noteData.duration * 1000);
|
463 |
|
464 |
noteElement.animate([
|
465 |
+
{ transform: `translateY(0px)` }, // Start from above
|
466 |
+
{ transform: `translateY(${targetY}px)`, offset: NOTE_FALL_DURATION_MS / noteFallAndPersistDuration }, // Arrive at hit line
|
467 |
+
{ transform: `translateY(${targetY}px)` } // Stay at hit line for its duration
|
468 |
], {
|
469 |
+
duration: noteFallAndPersistDuration,
|
470 |
easing: 'linear'
|
471 |
}).onfinish = () => {
|
472 |
if (noteElement.parentNode) noteElement.remove();
|
|
|
476 |
synth.triggerAttackRelease(noteData.note, noteData.duration, hitTime, noteData.velocity);
|
477 |
if (targetKeyElement) {
|
478 |
targetKeyElement.style.setProperty('--key-active-glow-hue', (colorIdx * 40) % 360);
|
479 |
+
targetKeyElement.classList.add('active'); // General active class
|
480 |
+
targetKeyElement.classList.add('pressed-by-note'); // Specific class for note presses
|
481 |
createKeyParticles(targetKeyElement, noteMainColor);
|
482 |
setTimeout(() => {
|
483 |
targetKeyElement.classList.remove('active');
|
484 |
+
targetKeyElement.classList.remove('pressed-by-note');
|
485 |
}, noteData.duration * 1000);
|
486 |
}
|
487 |
}, time + (NOTE_FALL_DURATION_MS / 1000));
|
|
|
515 |
|
516 |
window.addEventListener('resize', () => {
|
517 |
createPianoKeys();
|
518 |
+
// Re-calculate dynamic positions if necessary
|
519 |
});
|
520 |
|
521 |
</script>
|