Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>8-Bit Game Music Player</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| background: #000; | |
| color: #fff; | |
| font-family: 'Courier New', monospace; | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| background-image: radial-gradient(#0f0 1px, transparent 1px); | |
| background-size: 30px 30px; | |
| } | |
| .player { | |
| background: rgba(0, 20, 0, 0.95); | |
| border: 2px solid #0f0; | |
| padding: 2rem; | |
| border-radius: 15px; | |
| width: 90%; | |
| max-width: 400px; | |
| box-shadow: 0 0 30px rgba(0, 255, 0, 0.2); | |
| } | |
| .title { | |
| text-align: center; | |
| color: #0f0; | |
| text-shadow: 0 0 10px #0f0; | |
| margin-bottom: 1.5rem; | |
| font-size: 1.5rem; | |
| } | |
| .time-display { | |
| text-align: center; | |
| font-size: 1.2rem; | |
| color: #0f0; | |
| margin: 1rem 0; | |
| font-family: 'Digital-7', monospace; | |
| } | |
| .controls { | |
| display: flex; | |
| justify-content: center; | |
| gap: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .btn { | |
| background: none; | |
| border: 2px solid #0f0; | |
| color: #0f0; | |
| padding: 0.5rem 1rem; | |
| cursor: pointer; | |
| border-radius: 5px; | |
| font-family: inherit; | |
| transition: all 0.3s; | |
| } | |
| .btn:hover { | |
| background: #0f0; | |
| color: #000; | |
| box-shadow: 0 0 15px #0f0; | |
| } | |
| .volume-control { | |
| margin: 1.5rem 0; | |
| } | |
| .volume-slider { | |
| width: 100%; | |
| -webkit-appearance: none; | |
| height: 5px; | |
| background: #0f0; | |
| border-radius: 5px; | |
| opacity: 0.7; | |
| } | |
| .volume-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 15px; | |
| height: 15px; | |
| background: #0f0; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| box-shadow: 0 0 10px #0f0; | |
| } | |
| .track-list { | |
| margin-top: 1.5rem; | |
| } | |
| .track { | |
| background: rgba(0, 50, 0, 0.3); | |
| margin: 0.5rem 0; | |
| padding: 1rem; | |
| border: 1px solid #0f0; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| transition: all 0.3s; | |
| } | |
| .track:hover { | |
| background: rgba(0, 100, 0, 0.3); | |
| transform: translateX(5px); | |
| } | |
| .track.playing { | |
| background: rgba(0, 255, 0, 0.2); | |
| border-color: #0f0; | |
| box-shadow: 0 0 15px rgba(0, 255, 0, 0.3); | |
| animation: pulse 2s infinite; | |
| } | |
| .track-info { | |
| flex: 1; | |
| } | |
| .track-title { | |
| font-weight: bold; | |
| color: #0f0; | |
| } | |
| .track-duration { | |
| font-size: 0.8em; | |
| color: #0a0; | |
| } | |
| .status-indicator { | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| margin-left: 1rem; | |
| background: transparent; | |
| border: 2px solid #0f0; | |
| transition: all 0.3s; | |
| } | |
| .playing .status-indicator { | |
| background: #0f0; | |
| box-shadow: 0 0 10px #0f0; | |
| } | |
| @keyframes pulse { | |
| 0% { border-color: rgba(0, 255, 0, 0.5); } | |
| 50% { border-color: rgba(0, 255, 0, 1); } | |
| 100% { border-color: rgba(0, 255, 0, 0.5); } | |
| } | |
| @media (max-width: 480px) { | |
| .player { | |
| width: 95%; | |
| padding: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="player"> | |
| <h1 class="title">8-BIT GAME MUSIC</h1> | |
| <div class="time-display"> | |
| <span id="current-time">00:00</span> / <span id="total-time">00:00</span> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn" id="play-pause">PLAY</button> | |
| <button class="btn" id="stop">STOP</button> | |
| </div> | |
| <div class="volume-control"> | |
| <input type="range" class="volume-slider" min="0" max="1" step="0.01" value="0.7"> | |
| </div> | |
| <div class="track-list"> | |
| <div class="track" data-song="space"> | |
| <div class="track-info"> | |
| <div class="track-title">SPACE ADVENTURE</div> | |
| <div class="track-duration">1:30</div> | |
| </div> | |
| <div class="status-indicator"></div> | |
| </div> | |
| <div class="track" data-song="battle"> | |
| <div class="track-info"> | |
| <div class="track-title">BATTLE ZONE</div> | |
| <div class="track-duration">1:45</div> | |
| </div> | |
| <div class="status-indicator"></div> | |
| </div> | |
| <div class="track" data-song="tetris"> | |
| <div class="track-info"> | |
| <div class="track-title">TETRIS THEME</div> | |
| <div class="track-duration">2:00</div> | |
| </div> | |
| <div class="status-indicator"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let currentTime = 0; | |
| let isPlaying = false; | |
| let currentTrack = null; | |
| let currentTrackElement = null; | |
| const synth = new Tone.PolySynth(Tone.Synth, { | |
| oscillator: { type: "square8" }, | |
| envelope: { | |
| attack: 0.02, | |
| decay: 0.1, | |
| sustain: 0.3, | |
| release: 0.1 | |
| } | |
| }).connect(new Tone.Volume(-12).toDestination()); | |
| // 우주 테마 | |
| const spaceSequence = [ | |
| ["E4", "8n"], ["G4", "8n"], ["C5", "4n"], ["G4", "8n"], | |
| ["C5", "8n"], ["E5", "4n"], ["C5", "8n"], ["G4", "8n"], | |
| ["E4", "8n"], ["C4", "4n"], ["G4", "8n"], ["E4", "8n"], | |
| ["C4", "2n"] | |
| ]; | |
| // 전쟁 테마 | |
| const battleSequence = [ | |
| ["C4", "16n"], ["C4", "16n"], ["G4", "8n"], ["E4", "16n"], | |
| ["F4", "16n"], ["G4", "8n"], ["C4", "8n"], ["G3", "8n"], | |
| ["C4", "4n"], ["E4", "8n"], ["D4", "8n"], ["C4", "2n"] | |
| ]; | |
| // 테트리스 테마 | |
| const tetrisSequence = [ | |
| ["E5", "4n"], ["B4", "8n"], ["C5", "8n"], ["D5", "4n"], | |
| ["C5", "8n"], ["B4", "8n"], ["A4", "4n"], ["A4", "8n"], | |
| ["C5", "8n"], ["E5", "4n"], ["D5", "8n"], ["C5", "8n"], | |
| ["B4", "4n"], ["C5", "8n"], ["D5", "4n"], ["E5", "4n"], | |
| ["C5", "4n"], ["A4", "4n"], ["A4", "2n"] | |
| ]; | |
| function updateTimer() { | |
| if (isPlaying) { | |
| currentTime += 0.1; | |
| document.getElementById('current-time').textContent = | |
| formatTime(currentTime); | |
| setTimeout(updateTimer, 100); | |
| } | |
| } | |
| function formatTime(time) { | |
| const minutes = Math.floor(time / 60); | |
| const seconds = Math.floor(time % 60); | |
| return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; | |
| } | |
| function stopCurrentTrack() { | |
| if (currentTrack) { | |
| currentTrack.stop(); | |
| currentTrack.dispose(); | |
| } | |
| if (currentTrackElement) { | |
| currentTrackElement.classList.remove('playing'); | |
| } | |
| isPlaying = false; | |
| currentTime = 0; | |
| document.getElementById('current-time').textContent = '00:00'; | |
| document.getElementById('play-pause').textContent = 'PLAY'; | |
| } | |
| function createAndPlaySequence(sequence, element) { | |
| stopCurrentTrack(); | |
| currentTrack = new Tone.Part((time, note) => { | |
| synth.triggerAttackRelease(note[0], note[1], time); | |
| }, sequence.map((note, i) => [i * 0.25, note])); | |
| currentTrack.loop = true; | |
| currentTrack.start(0); | |
| Tone.Transport.start(); | |
| currentTrackElement = element; | |
| element.classList.add('playing'); | |
| isPlaying = true; | |
| document.getElementById('play-pause').textContent = 'PAUSE'; | |
| updateTimer(); | |
| } | |
| document.getElementById('play-pause').addEventListener('click', () => { | |
| if (!currentTrack) return; | |
| if (isPlaying) { | |
| Tone.Transport.pause(); | |
| document.getElementById('play-pause').textContent = 'PLAY'; | |
| } else { | |
| Tone.Transport.start(); | |
| document.getElementById('play-pause').textContent = 'PAUSE'; | |
| updateTimer(); | |
| } | |
| isPlaying = !isPlaying; | |
| }); | |
| document.getElementById('stop').addEventListener('click', stopCurrentTrack); | |
| document.querySelector('.volume-slider').addEventListener('input', (e) => { | |
| synth.volume.value = Tone.gainToDb(parseFloat(e.target.value)); | |
| }); | |
| document.querySelectorAll('.track').forEach(track => { | |
| track.addEventListener('click', async () => { | |
| await Tone.start(); | |
| const songType = track.dataset.song; | |
| if (currentTrackElement === track && isPlaying) { | |
| stopCurrentTrack(); | |
| return; | |
| } | |
| switch(songType) { | |
| case 'space': | |
| document.getElementById('total-time').textContent = '01:30'; | |
| createAndPlaySequence(spaceSequence, track); | |
| break; | |
| case 'battle': | |
| document.getElementById('total-time').textContent = '01:45'; | |
| createAndPlaySequence(battleSequence, track); | |
| break; | |
| case 'tetris': | |
| document.getElementById('total-time').textContent = '02:00'; | |
| createAndPlaySequence(tetrisSequence, track); | |
| break; | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html><script async data-explicit-opt-in="true" data-cookie-opt-in="true" src="https://vercel.live/_next-live/feedback/feedback.js"></script> |