New Features

* Implements Terminal
This commit is contained in:
2025-06-30 18:24:18 +05:30
parent 6241c28c0e
commit 3a71e3e8dc
3 changed files with 473 additions and 24 deletions

210
hg.css
View File

@@ -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%);
}

View File

@@ -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>

217
player.js
View File

@@ -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('');
}
})();