diff options
-rw-r--r-- | hg.css | 210 | ||||
-rw-r--r-- | index.html | 70 | ||||
-rw-r--r-- | player.js | 217 |
3 files changed, 473 insertions, 24 deletions
@@ -325,6 +325,10 @@ canvas { color: #ffff; right: 0; margin-right: 5px; + padding: 5px; + background: rgba(0, 0, 0, 0.5); + border-top-left-radius: 5px; + border-top-right-radius: 5px; } .ERRORS { @@ -344,3 +348,209 @@ canvas { border: 2px solid red; margin-top: 10px; } + +/* Terminal Overlay Styles - ORED Initial Console Emulator Style */ +.terminal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(192, 192, 192, 0.3); + backdrop-filter: none; + z-index: 10001; + display: flex; + justify-content: center; + align-items: center; +} + +.terminal-window { + width: 80%; + max-width: 600px; + height: 65%; + max-height: 450px; + min-height: 300px; + background: #c0c0c0; + border: 4px outset #c0c0c0; + border-radius: 0; + font-family: 'VCR', 'Monaco', 'Courier New', monospace; + box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.6); + display: flex; + flex-direction: column; + position: relative; +} + +.terminal-header { + background: #c0c0c0; + color: #000000; + padding: 1px 0; + border-bottom: 2px inset #c0c0c0; + display: flex; + justify-content: center; + align-items: center; + font-size: 10px; + font-weight: bold; + font-family: 'VCR', 'Geneva', 'Helvetica', sans-serif; + min-height: 16px; + position: relative; +} + +.terminal-title { + color: #000000; + font-size: 10px; + font-weight: bold; + text-align: center; +} + +.terminal-close { + cursor: pointer; + color: #000000; + font-size: 10px; + font-weight: bold; + width: 16px; + height: 14px; + border: 1px outset #c0c0c0; + background: #c0c0c0; + text-align: center; + line-height: 12px; + border-radius: 0; + position: absolute; + right: 2px; + top: 1px; +} + +.terminal-close:hover { + background: #b0b0b0; +} + +.terminal-close:active { + border: 1px inset #c0c0c0; + background: #a0a0a0; +} + +.terminal-content { + flex: 1; + display: flex; + flex-direction: column; + padding: 2px; + overflow: hidden; + background: #c0c0c0; +} + +#terminal-output { + flex: 1; + overflow-y: auto; + margin-bottom: 4px; + color: #000000; + font-size: 9px; + line-height: 1.1; + font-family: 'VCR', 'Monaco', 'Courier New', monospace; + background: #c0c0c0; + border: 2px inset #c0c0c0; + padding: 2px 4px; + white-space: pre-wrap; +} + +.terminal-line { + margin-bottom: 0; + word-wrap: break-word; + color: #000000; + font-size: 9px; + line-height: 1.1; +} + +.terminal-input-line { + display: flex; + align-items: center; + color: #000000; + font-size: 9px; + background: #c0c0c0; + border: 2px inset #c0c0c0; + padding: 1px 4px; + min-height: 14px; +} + +.terminal-prompt { + color: #000000; + margin-right: 2px; + font-weight: normal; + font-family: 'VCR', 'Monaco', 'Courier New', monospace; + font-size: 9px; +} + +.terminal-input { + flex: 1; + background: transparent; + border: none; + color: #000000; + font-family: 'VCR', 'Monaco', 'Courier New', monospace; + font-size: 9px; + outline: none; + caret-color: #000000; + line-height: 1.1; +} + +.terminal-input::selection { + background: #000080; + color: #ffffff; +} + +.terminal-btn { + float: right; + margin-right: 10px; + font-size: 16px; + color: #565656; +} + +.terminal-btn:hover { + color: #000000; +} + +.terminal-overlay.hidden { + display: none; +} + +/* Custom scrollbar for terminal output - Classic style */ +#terminal-output::-webkit-scrollbar { + width: 16px; +} + +#terminal-output::-webkit-scrollbar-track { + background: #c0c0c0; + border: 1px inset #c0c0c0; +} + +#terminal-output::-webkit-scrollbar-thumb { + background: linear-gradient(to bottom, #e0e0e0 0%, #c0c0c0 50%, #a0a0a0 100%); + border: 1px outset #c0c0c0; + border-radius: 0; +} + +#terminal-output::-webkit-scrollbar-thumb:hover { + background: linear-gradient(to bottom, #d0d0d0 0%, #b0b0b0 50%, #909090 100%); +} + +#terminal-output::-webkit-scrollbar-thumb:active { + border: 1px inset #c0c0c0; + background: linear-gradient(to bottom, #a0a0a0 0%, #b0b0b0 50%, #d0d0d0 100%); +} + +#terminal-output::-webkit-scrollbar-corner { + background: #c0c0c0; +} + +#terminal-output::-webkit-scrollbar-button { + width: 16px; + height: 16px; + background: linear-gradient(to bottom, #e0e0e0 0%, #c0c0c0 50%, #a0a0a0 100%); + border: 1px outset #c0c0c0; +} + +#terminal-output::-webkit-scrollbar-button:hover { + background: linear-gradient(to bottom, #d0d0d0 0%, #b0b0b0 50%, #909090 100%); +} + +#terminal-output::-webkit-scrollbar-button:active { + border: 1px inset #c0c0c0; + background: linear-gradient(to bottom, #a0a0a0 0%, #b0b0b0 50%, #d0d0d0 100%); +} @@ -16,29 +16,32 @@ <div class="modal" id="intro-modal"> <div class="modal-bg modal-exit"></div> <div class="modal-container"> - <h3 class="text-center">Welcome to Retrowave Player</h3> - <p class="mt-5">Here are the controls:</p> + <h3 class="text-center">WELCOME TO RETROWAVE PLAYER</h3> + <p class="mt-5">HERE ARE THE CONTROLS:</p> <h4 class="mt-5"> - Play/Pause <span class="controls">SPACE or Click or Touch</span> + PLAY/PAUSE <span class="controls">SPACE OR CLICK OR TOUCH</span> </h4> <h4 class="mt-5"> - Volume Up <span class="controls">w or Swipe Up</span> + VOLUME UP <span class="controls">W OR SWIPE UP</span> </h4> <h4 class="mt-5"> - Volume Down <span class="controls">s or Swipe Down</span> + VOLUME DOWN <span class="controls">S OR SWIPE DOWN</span> </h4> <h4 class="mt-5"> - Next Track<span class="controls"> n or Press the Refresh Button</span> + NEXT TRACK<span class="controls"> N OR PRESS THE REFRESH BUTTON</span> </h4> <h4 class="mt-5"> - Toggle Controls<span class="controls"> h</span> + TOGGLE CONTROLS<span class="controls"> H</span> + </h4> + <h4 class="mt-5"> + OPEN TERMINAL<span class="controls"> T</span> </h4> <p class="mt-5"> - (You can download the music history of your current browser by - clicking the history link on the player) + (YOU CAN DOWNLOAD THE MUSIC HISTORY OF YOUR CURRENT BROWSER BY + CLICKING THE HISTORY LINK ON THE PLAYER) </p> - <p class="mt-5">All the music is fetched from retrowave.ru</p> - <div id="warning">⚠️ WARNING: This application contains strobing/flashing lights</div> + <p class="mt-5">ALL THE MUSIC IS FETCHED FROM RETROWAVE.RU</p> + <div id="warning">⚠️ WARNING: THIS APPLICATION CONTAINS STROBING/FLASHING LIGHTS</div> <button class="modal-close modal-exit" id="initButton">X</button> </div> </div> @@ -78,22 +81,23 @@ </svg> </div> <div class="no-pause buttons refresh toggleable" title="NEXT TRACK">🔃</div> + <div class="no-pause buttons terminal-btn toggleable" title="OPEN TERMINAL">⌨️</div> <div class="title-content"> - <div class="intro">Now Playing</div> + <div class="intro">NOW PLAYING</div> <hr /> - <h3 id="track-name">Retrowave Player</h3> + <h3 id="track-name">RETROWAVE PLAYER</h3> </div> <div class="gradient-overlay"></div> <div class="color-overlay"></div> <div class="footer toggleable"> - <div title="DOWNLOAD YOUR PLAYLIST HISTORY" id="history" class="no-pause footer-items">history</div> + <div title="DOWNLOAD YOUR PLAYLIST HISTORY" id="history" class="no-pause footer-items">HISTORY</div> <div id="source"> <a title="SOURCE CODE" class="no-pause footer-items" href="https://git.indrajith.dev/retrowave-player/" target="_blank" - >source code</a + >SOURCE CODE</a > </div> <div id="retrowaveru" class="no-pause"> @@ -102,7 +106,7 @@ class="no-pause footer-items" href="http://retrowave.ru/" target="_blank" - >retrowave.ru</a + >RETROWAVE.RU</a > </div> <div id="guestbook" class="no-pause"> @@ -111,13 +115,45 @@ class="no-pause footer-items" href="https://indrajith.atabook.org/" target="_blank" - >guestbook</a + >GUESTBOOK</a > </div> </div> <!-- <div id="upload-info" class="no-pause" title="Upload a playlist (downloaded from history)">Upload</div> --> </div> </div> + <div id="terminal-overlay" class="terminal-overlay hidden"> + <div class="terminal-window"> + <div class="terminal-header"> + <span class="terminal-title">Retrowave Terminal</span> + <span class="terminal-close">□</span> + </div> + <div class="terminal-content"> + <div id="terminal-output"> + <div class="terminal-line">Welcome to Retrowave Terminal</div> + <div class="terminal-line">Type 'help' for available commands</div> + <div class="terminal-line"></div> + <div class="terminal-line">Available commands:</div> + <div class="terminal-line"> help - Show this help message</div> + <div class="terminal-line"> play - Start/resume playback</div> + <div class="terminal-line"> pause - Pause playback</div> + <div class="terminal-line"> next - Skip to next track</div> + <div class="terminal-line"> volume [0-10] - Set volume (0-10)</div> + <div class="terminal-line"> status - Show current track info</div> + <div class="terminal-line"> history - Download playlist history</div> + <div class="terminal-line"> effect - Change visual effect</div> + <div class="terminal-line"> fullscreen - Toggle fullscreen mode</div> + <div class="terminal-line"> clear - Clear terminal</div> + <div class="terminal-line"> exit - Close terminal</div> + <div class="terminal-line"></div> + </div> + <div class="terminal-input-line"> + <span class="terminal-prompt">indrajith@retrowave:$ </span> + <input type="text" id="terminal-input" class="terminal-input" autocomplete="off" spellcheck="false"> + </div> + </div> + </div> + </div> <div id="codef-canvas"></div> <div class="OVR hidden"></div> <div class="ERRORS hidden"></div> @@ -9,6 +9,11 @@ let refreshBtn = document.querySelector(".refresh"); let ovr = document.querySelector(".OVR"); let fullScreenBtn = document.querySelector(".fullscreen"); + let terminalBtn = document.querySelector(".terminal-btn"); + let terminalOverlay = document.getElementById("terminal-overlay"); + let terminalInput = document.getElementById("terminal-input"); + let terminalOutput = document.getElementById("terminal-output"); + let terminalClose = document.querySelector(".terminal-close"); let errorEl = document.querySelector(".ERRORS"); let currentEffectIndex = 0; const modalEl = document.getElementById("intro-modal"); @@ -113,15 +118,15 @@ }); document.addEventListener("keyup", (event) => { + if (!terminalOverlay.classList.contains("hidden")) { + return; + } + const { key } = event; switch (key) { case " ": togglePlay(); break; - case "a": - break; - case "d": - break; case "w": volumeUp(); break; @@ -143,6 +148,9 @@ case "e": rotateHydraEffect(); break; + case "t": + toggleTerminal(); + break; } }); @@ -255,10 +263,11 @@ } function updateInfo(trackDetails) { - titleEl.innerText = trackDetails.title; - document.title = `Now Playing... ${trackDetails.title}`; + const trackTitle = trackDetails.title.toString().toUpperCase() || "Unknown Track"; + titleEl.innerText = trackTitle; + document.title = `Now Playing... ${trackTitle}`; coverArtEl.style.backgroundImage = `url("${retroWaveRu}${trackDetails.artworkUrl}")`; - ovr.innerHTML = `${trackDetails.title}`; + ovr.innerHTML = `> ${trackTitle}`; } function getHistory() { @@ -324,6 +333,27 @@ https://retrowave.ru/${musicData.streamUrl} fullScreenBtn.addEventListener("click", () => { toggleFullScreen(); }); + + terminalBtn.addEventListener("click", () => { + toggleTerminal(); + }); + + terminalClose.addEventListener("click", () => { + closeTerminal(); + }); + + terminalInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + executeCommand(terminalInput.value.trim()); + terminalInput.value = ""; + } + }); + + document.addEventListener("keydown", (e) => { + if (e.key === "Escape" && !terminalOverlay.classList.contains("hidden")) { + closeTerminal(); + } + }); } async function toggleFullScreen() { @@ -469,4 +499,177 @@ https://retrowave.ru/${musicData.streamUrl} ); } } + + function toggleTerminal() { + terminalOverlay.classList.toggle("hidden"); + if (!terminalOverlay.classList.contains("hidden")) { + terminalInput.focus(); + } + } + + function closeTerminal() { + terminalOverlay.classList.add("hidden"); + } + + function addTerminalLine(text, isCommand = false) { + const line = document.createElement("div"); + line.className = "terminal-line"; + if (isCommand) { + line.innerHTML = `<span style="color: #000000; font-weight: normal;">indrajith@retrowave:$ ${text}</span>`; + } else { + line.textContent = text; + } + terminalOutput.appendChild(line); + terminalOutput.scrollTop = terminalOutput.scrollHeight; + } + + function executeCommand(command) { + if (!command) return; + + addTerminalLine(command, true); + + const args = command.toLowerCase().split(' '); + const cmd = args[0]; + + switch (cmd) { + case 'help': + addTerminalLine('Available commands:'); + addTerminalLine(' help - Show this help message'); + addTerminalLine(' play - Start/resume playback'); + addTerminalLine(' pause - Pause playback'); + addTerminalLine(' next - Skip to next track'); + addTerminalLine(' volume [0-10] - Set volume (0-10)'); + addTerminalLine(' status - Show current track info'); + addTerminalLine(' history - Download playlist history'); + addTerminalLine(' effect [list|name] - Change/list visual effects'); + addTerminalLine(' fullscreen - Toggle fullscreen mode'); + addTerminalLine(' clear - Clear terminal'); + addTerminalLine(' exit - Close terminal'); + break; + + case 'play': + if (howlerInstance && !isPlaying) { + togglePlay(); + addTerminalLine('Playback resumed.'); + } else if (isPlaying) { + addTerminalLine('Already playing.'); + } else { + addTerminalLine('No track loaded.'); + } + break; + + case 'pause': + if (howlerInstance && isPlaying) { + togglePlay(); + addTerminalLine('Playback paused.'); + } else { + addTerminalLine('Not currently playing.'); + } + break; + + case 'next': + if (howlerInstance) { + playNextTrack(); + addTerminalLine('Skipped to next track.'); + } else { + addTerminalLine('No track loaded.'); + } + break; + + case 'volume': + if (args[1]) { + const vol = parseInt(args[1]); + if (vol >= 0 && vol <= 10) { + volume = vol / 10; + if (howlerInstance) setVolume(); + addTerminalLine(`Volume set to ${vol}/10`); + } else { + addTerminalLine('Volume must be between 0 and 10.'); + } + } else { + addTerminalLine(`Current volume: ${Math.round(volume * 10)}/10`); + } + break; + + case 'status': + if (currentTracks.length > 0) { + const track = currentTracks[0]; + addTerminalLine(`Now playing: ${track.title}`); + addTerminalLine(`Status: ${isPlaying ? 'Playing' : 'Paused'}`); + addTerminalLine(`Volume: ${Math.round(volume * 10)}/10`); + } else { + addTerminalLine('No track loaded.'); + } + break; + + case 'history': + downloadHistory(); + addTerminalLine('Playlist history downloaded.'); + break; + + case 'effect': + if (args[1]) { + if (args[1] === 'list') { + addTerminalLine('Available effects:'); + addTerminalLine(' vernoi - Voronoi pattern effect'); + addTerminalLine(' waveyzz - Wave synthesis effect'); + addTerminalLine(' oscrotate - Oscillating rotation effect'); + addTerminalLine('Usage: effect <effect_name> or effect list'); + } else { + const effectName = args[1].toLowerCase(); + let effectIndex = -1; + + switch (effectName) { + case 'vernoi': + effectIndex = 0; + break; + case 'waveyzz': + effectIndex = 1; + break; + case 'oscrotate': + effectIndex = 2; + break; + default: + addTerminalLine(`Unknown effect: ${effectName}`); + addTerminalLine('Type "effect list" to see available effects.'); + break; + } + + if (effectIndex !== -1) { + try { + hydraEffects[effectIndex](); + currentEffectIndex = effectIndex; + addTerminalLine(`Effect changed to: ${effectName}`); + } catch (error) { + addTerminalLine('Failed to apply effect.'); + } + } + } + } else { + rotateHydraEffect(); + addTerminalLine('Visualization changed to next effect.'); + } + break; + + case 'fullscreen': + toggleFullScreen(); + addTerminalLine('Toggled fullscreen mode.'); + break; + + case 'clear': + terminalOutput.innerHTML = ''; + break; + + case 'exit': + closeTerminal(); + break; + + default: + addTerminalLine(`Command not found: ${cmd}`); + addTerminalLine('Type "help" for available commands.'); + break; + } + + addTerminalLine(''); + } })(); |