1638 lines
46 KiB
JavaScript
1638 lines
46 KiB
JavaScript
(function () {
|
|
let isPlaying = false;
|
|
let currentTracks = [];
|
|
let cursor = 0;
|
|
let howlerInstance;
|
|
const retroWaveRu = "https://retrowave.ru";
|
|
let titleEl = document.getElementById("track-name");
|
|
let coverArtEl = document.getElementsByClassName("music-player")[0];
|
|
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");
|
|
|
|
// Progress bar elements (initialized later to avoid null references)
|
|
let progressBarContainer;
|
|
let progressBar;
|
|
let progressFill;
|
|
let progressHandle;
|
|
let currentTimeEl;
|
|
let totalTimeEl;
|
|
let progressUpdateInterval;
|
|
let isDragging = false;
|
|
|
|
// const uploadInfoEl = document.getElementById("upload-info");
|
|
const fileUploadEl = document.getElementById("file-upload");
|
|
let volume = 1;
|
|
let effectCanvas;
|
|
let starField;
|
|
let line3D;
|
|
const synthwaveColor = 0xff2975;
|
|
|
|
openDialog();
|
|
listenUploadFileChange();
|
|
|
|
function initDynamicTooltips() {
|
|
document.querySelectorAll("[title]").forEach((element) => {
|
|
const title = element.getAttribute("title");
|
|
element.removeAttribute("title");
|
|
element.setAttribute("data-title", title);
|
|
|
|
element.addEventListener("mouseenter", function (e) {
|
|
const tooltip = document.createElement("div");
|
|
tooltip.textContent = this.getAttribute("data-title");
|
|
tooltip.style.cssText = `
|
|
font-family: "VCR", sans-serif;
|
|
position: fixed;
|
|
background:rgb(255, 255, 255);
|
|
color: #000000;
|
|
border: 1px solidrgb(0, 0, 0);
|
|
padding: 4px 8px;
|
|
font-size: 12px;
|
|
border-radius: 3px;
|
|
z-index: 10000;
|
|
pointer-events: none;
|
|
white-space: nowrap;
|
|
`;
|
|
|
|
document.body.appendChild(tooltip);
|
|
this.tooltipEl = tooltip;
|
|
|
|
const rect = this.getBoundingClientRect();
|
|
tooltip.style.left =
|
|
rect.left + rect.width / 2 - tooltip.offsetWidth / 2 + "px";
|
|
tooltip.style.top = rect.top - tooltip.offsetHeight - 8 + "px";
|
|
});
|
|
|
|
element.addEventListener("mouseleave", function () {
|
|
if (this.tooltipEl) {
|
|
document.body.removeChild(this.tooltipEl);
|
|
this.tooltipEl = null;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", initDynamicTooltips);
|
|
|
|
coverArtEl.addEventListener("click", (event) => {
|
|
const isNoPause = event.target.classList.contains("no-pause");
|
|
if (howlerInstance && !isNoPause) {
|
|
togglePlay();
|
|
}
|
|
});
|
|
|
|
// uploadInfoEl.addEventListener("click", () => {
|
|
// const confirmText = `Do you really want to change your playlist?\nThis will replace all your retrowave music history.\nIf you are sure about this, make sure to upload a valid json file probably downloaded using history link.`;
|
|
|
|
// if (confirm(confirmText)) {
|
|
// fileUploadEl.click();
|
|
// }
|
|
// });
|
|
|
|
function listenUploadFileChange() {
|
|
fileUploadEl.onchange = function () {
|
|
const selectedFile = fileUploadEl.files[0];
|
|
const reader = new FileReader();
|
|
reader.readAsText(selectedFile, "UTF-8");
|
|
reader.onload = function (event) {
|
|
try {
|
|
const uploadedPlaylist = JSON.parse(event.target.result);
|
|
localStorage.setItem("retrowave-history", event.target.result);
|
|
} catch (error) {
|
|
alert("malformed/invalid json file");
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
document.getElementById("initButton")?.addEventListener("click", async () => {
|
|
var hydra = new Hydra({ detectAudio: false });
|
|
modalEl.classList.remove("open");
|
|
getMusic().then(() => {
|
|
playMusic();
|
|
});
|
|
initHydra();
|
|
initControls();
|
|
initProgressBar();
|
|
});
|
|
|
|
document.getElementById("history").addEventListener("click", () => {
|
|
downloadHistory();
|
|
});
|
|
|
|
document.addEventListener("keyup", (event) => {
|
|
if (!terminalOverlay.classList.contains("hidden")) {
|
|
return;
|
|
}
|
|
|
|
const { key } = event;
|
|
switch (key) {
|
|
case " ":
|
|
togglePlay();
|
|
break;
|
|
case "w":
|
|
volumeUp();
|
|
break;
|
|
case "s":
|
|
volumeDown();
|
|
break;
|
|
case "n":
|
|
playNextTrack();
|
|
break;
|
|
case "f":
|
|
toggleFullScreen();
|
|
break;
|
|
case "h":
|
|
toggleControls();
|
|
break;
|
|
case "x":
|
|
toggleEverything();
|
|
break;
|
|
case "e":
|
|
rotateHydraEffect();
|
|
break;
|
|
case "t":
|
|
toggleTerminal();
|
|
break;
|
|
}
|
|
});
|
|
|
|
let touchstartX = 0;
|
|
let touchendX = 0;
|
|
let touchstartY = 0;
|
|
let touchendY = 0;
|
|
const musixPlayerEl = document.getElementsByClassName("music-player")[0];
|
|
if (musixPlayerEl) {
|
|
musixPlayerEl.addEventListener("touchstart", (e) => {
|
|
touchstartX = e.changedTouches[0].screenX;
|
|
touchstartY = e.changedTouches[0].screenY;
|
|
});
|
|
|
|
musixPlayerEl.addEventListener("touchend", (e) => {
|
|
touchendX = e.changedTouches[0].screenX;
|
|
touchendY = e.changedTouches[0].screenY;
|
|
checkDirection();
|
|
});
|
|
}
|
|
|
|
function checkDirection() {
|
|
if (touchendX < touchstartX) {
|
|
// Swipe Left
|
|
}
|
|
if (touchendX > touchstartX) {
|
|
// Swipe Right
|
|
}
|
|
|
|
if (touchendY < touchstartY) {
|
|
volumeUp();
|
|
}
|
|
if (touchendY > touchstartY) {
|
|
volumeDown();
|
|
}
|
|
}
|
|
|
|
function openDialog() {
|
|
modalEl.classList.add("open");
|
|
}
|
|
|
|
async function getMusic() {
|
|
try {
|
|
const res = await fetch(
|
|
`https://retrowave.ru/api/v1/tracks?limit=10&cursor=${cursor}`
|
|
).then((res) => res.json());
|
|
const {
|
|
body: { tracks, cursor: currentCursor },
|
|
} = res;
|
|
cursor = currentCursor;
|
|
currentTracks = tracks;
|
|
} catch (error) {
|
|
console.error("Error fetching music:", error);
|
|
showErrors(
|
|
"Failed to fetch music. Please check your internet connection or try again later."
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function initPlayer() {}
|
|
|
|
function playMusic() {
|
|
const currentTrack = currentTracks[0];
|
|
const singleTrack = currentTrack.streamUrl;
|
|
const fullTrack = `${retroWaveRu}${singleTrack}`;
|
|
howlerInstance = new Howl({
|
|
src: [fullTrack],
|
|
html5: true,
|
|
onend: function () {
|
|
playNextTrack();
|
|
},
|
|
onload: function () {
|
|
updateProgressBar();
|
|
},
|
|
onplay: function () {
|
|
startProgressTracking();
|
|
},
|
|
onpause: function () {
|
|
stopProgressTracking();
|
|
},
|
|
onstop: function () {
|
|
stopProgressTracking();
|
|
},
|
|
});
|
|
setVolume();
|
|
isPlaying = true;
|
|
window.hw = howlerInstance;
|
|
|
|
updateInfo(currentTrack);
|
|
|
|
addToHistory(currentTrack);
|
|
howlerInstance.play();
|
|
}
|
|
|
|
function volumeDown() {
|
|
if (volume > 0.1) {
|
|
volume -= 0.1;
|
|
}
|
|
setVolume();
|
|
}
|
|
|
|
function volumeUp() {
|
|
if (volume < 1) {
|
|
volume += 0.1;
|
|
}
|
|
setVolume();
|
|
}
|
|
|
|
function setVolume() {
|
|
howlerInstance.volume(volume);
|
|
}
|
|
|
|
function togglePlay() {
|
|
isPlaying = !isPlaying;
|
|
|
|
if (isPlaying) {
|
|
howlerInstance.play();
|
|
} else {
|
|
howlerInstance.pause();
|
|
}
|
|
}
|
|
|
|
function updateInfo(trackDetails) {
|
|
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 = `> ${trackTitle}`;
|
|
}
|
|
|
|
function getHistory() {
|
|
let localHistoryStore = localStorage.getItem("retrowave-history") || "[]";
|
|
let historyArray = JSON.parse(localHistoryStore);
|
|
return historyArray;
|
|
}
|
|
|
|
function addToHistory(trackDetails) {
|
|
let historyArray = getHistory();
|
|
historyArray.push(trackDetails);
|
|
localStorage.setItem("retrowave-history", JSON.stringify(historyArray));
|
|
}
|
|
|
|
function downloadHistory() {
|
|
const historyArray = getHistory();
|
|
let element = document.createElement("a");
|
|
let playListData = "#EXTM3U";
|
|
historyArray.forEach((musicData) => {
|
|
playListData = `${playListData}
|
|
|
|
#EXTINF:${Math.ceil(musicData.duration / 1000)}, ${musicData.title}
|
|
https://retrowave.ru/${musicData.streamUrl}
|
|
`;
|
|
});
|
|
|
|
element.setAttribute(
|
|
"href",
|
|
"data:audio/x-mpegurl;;charset=utf-8," + encodeURIComponent(playListData)
|
|
);
|
|
element.setAttribute("download", "retrowave_playlist.m3u");
|
|
|
|
element.style.display = "none";
|
|
document.body.appendChild(element);
|
|
|
|
element.click();
|
|
|
|
document.body.removeChild(element);
|
|
}
|
|
|
|
function playNextTrack() {
|
|
resetHowler();
|
|
currentTracks.shift();
|
|
if (currentTracks.length <= 3) {
|
|
getMusic();
|
|
}
|
|
playMusic();
|
|
}
|
|
|
|
function resetHowler(destroy = false) {
|
|
stopProgressTracking();
|
|
howlerInstance.stop();
|
|
if (destroy) {
|
|
howlerInstance.unload();
|
|
howlerInstance = null;
|
|
}
|
|
}
|
|
|
|
// Progress Bar Utility Functions
|
|
function formatTime(seconds) {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
function updateProgressBar() {
|
|
if (!howlerInstance || isDragging || !progressFill || !progressHandle || !currentTimeEl || !totalTimeEl) return;
|
|
|
|
const seek = howlerInstance.seek() || 0;
|
|
const duration = howlerInstance.duration() || 0;
|
|
|
|
if (duration > 0) {
|
|
const percentage = (seek / duration) * 100;
|
|
progressFill.style.width = `${percentage}%`;
|
|
progressHandle.style.left = `${percentage}%`;
|
|
|
|
currentTimeEl.textContent = formatTime(seek);
|
|
totalTimeEl.textContent = formatTime(duration);
|
|
}
|
|
}
|
|
|
|
function startProgressTracking() {
|
|
if (progressUpdateInterval) {
|
|
clearInterval(progressUpdateInterval);
|
|
}
|
|
progressUpdateInterval = setInterval(updateProgressBar, 100);
|
|
}
|
|
|
|
function stopProgressTracking() {
|
|
if (progressUpdateInterval) {
|
|
clearInterval(progressUpdateInterval);
|
|
progressUpdateInterval = null;
|
|
}
|
|
}
|
|
|
|
function seekToPosition(percentage) {
|
|
if (!howlerInstance) return;
|
|
|
|
const duration = howlerInstance.duration() || 0;
|
|
if (duration > 0) {
|
|
const seekTime = (percentage / 100) * duration;
|
|
howlerInstance.seek(seekTime);
|
|
updateProgressBar();
|
|
}
|
|
}
|
|
|
|
function initProgressBar() {
|
|
// Initialize DOM elements
|
|
progressBarContainer = document.getElementById("progress-bar-container");
|
|
progressBar = document.getElementById("progress-bar");
|
|
progressFill = document.getElementById("progress-fill");
|
|
progressHandle = document.getElementById("progress-handle");
|
|
currentTimeEl = document.getElementById("current-time");
|
|
totalTimeEl = document.getElementById("total-time");
|
|
|
|
if (!progressBar || !progressFill || !progressHandle || !currentTimeEl || !totalTimeEl) {
|
|
console.warn("Progress bar elements not found");
|
|
return;
|
|
}
|
|
|
|
// Click to seek
|
|
progressBar.addEventListener("click", (e) => {
|
|
if (isDragging) return;
|
|
|
|
const rect = progressBar.getBoundingClientRect();
|
|
const percentage = ((e.clientX - rect.left) / rect.width) * 100;
|
|
seekToPosition(Math.max(0, Math.min(100, percentage)));
|
|
});
|
|
|
|
// Drag to seek functionality
|
|
let startX = 0;
|
|
let startPercentage = 0;
|
|
|
|
progressHandle.addEventListener("mousedown", (e) => {
|
|
isDragging = true;
|
|
startX = e.clientX;
|
|
const rect = progressBar.getBoundingClientRect();
|
|
startPercentage = ((startX - rect.left) / rect.width) * 100;
|
|
|
|
document.addEventListener("mousemove", handleMouseMove);
|
|
document.addEventListener("mouseup", handleMouseUp);
|
|
e.preventDefault();
|
|
});
|
|
|
|
function handleMouseMove(e) {
|
|
if (!isDragging) return;
|
|
|
|
const rect = progressBar.getBoundingClientRect();
|
|
const percentage = ((e.clientX - rect.left) / rect.width) * 100;
|
|
const clampedPercentage = Math.max(0, Math.min(100, percentage));
|
|
|
|
progressFill.style.width = `${clampedPercentage}%`;
|
|
progressHandle.style.left = `${clampedPercentage}%`;
|
|
|
|
if (howlerInstance) {
|
|
const duration = howlerInstance.duration() || 0;
|
|
if (duration > 0) {
|
|
const seekTime = (clampedPercentage / 100) * duration;
|
|
currentTimeEl.textContent = formatTime(seekTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleMouseUp(e) {
|
|
if (!isDragging) return;
|
|
|
|
const rect = progressBar.getBoundingClientRect();
|
|
const percentage = ((e.clientX - rect.left) / rect.width) * 100;
|
|
const clampedPercentage = Math.max(0, Math.min(100, percentage));
|
|
|
|
seekToPosition(clampedPercentage);
|
|
isDragging = false;
|
|
|
|
document.removeEventListener("mousemove", handleMouseMove);
|
|
document.removeEventListener("mouseup", handleMouseUp);
|
|
}
|
|
|
|
// Touch support for mobile
|
|
progressBar.addEventListener("touchstart", (e) => {
|
|
const touch = e.touches[0];
|
|
const rect = progressBar.getBoundingClientRect();
|
|
const percentage = ((touch.clientX - rect.left) / rect.width) * 100;
|
|
seekToPosition(Math.max(0, Math.min(100, percentage)));
|
|
});
|
|
}
|
|
|
|
function initControls() {
|
|
refreshBtn.addEventListener("click", () => {
|
|
playNextTrack();
|
|
});
|
|
|
|
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() {
|
|
if (!document.fullscreenElement) {
|
|
try {
|
|
await document?.documentElement?.requestFullscreen();
|
|
} catch (error) {
|
|
console.error("Error attempting to enable full-screen mode:", error);
|
|
showErrors(
|
|
"Failed to enter full-screen mode. Please check your browser settings."
|
|
);
|
|
}
|
|
} else {
|
|
if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
}
|
|
}
|
|
}
|
|
|
|
function toggleControls() {
|
|
const toggleableElements = document.querySelectorAll(".toggleable");
|
|
toggleableElements.forEach((el) => {
|
|
el.classList.toggle("hidden");
|
|
});
|
|
}
|
|
|
|
function toggleEverything() {
|
|
musixPlayerEl.classList.toggle("hidden");
|
|
ovr.classList.toggle("hidden");
|
|
}
|
|
|
|
function showErrors(error) {
|
|
errorEl.classList.remove("hidden");
|
|
errorEl.innerText = error;
|
|
setTimeout(() => {
|
|
errorEl.innerText = "";
|
|
errorEl.classList.add("hidden");
|
|
}, 10000);
|
|
}
|
|
|
|
// function initCodef() {
|
|
// const width = window.innerWidth;
|
|
// const height = window.innerHeight;
|
|
// effectCanvas = new canvas(width, height, 'codef-canvas');
|
|
// starField = new starfield3D(effectCanvas, 500, 2, width, height, width/2, height/2, '#ffffff', 100, 0, 0);
|
|
// line3D = new codef3D(effectCanvas, 320, 75, 1, 1500 );
|
|
// line3D.line({x:-320, y:0, z:0},{x:320, y:0, z:0}, new LineBasicMaterial({ color: synthwaveColor, linewidth:2}));
|
|
// line3D.line({x: 0, y:-240, z:0},{x:0, y:240, z:0}, new LineBasicMaterial({ color: synthwaveColor, linewidth:2}));
|
|
// renderCodeFx();
|
|
// }
|
|
|
|
// function renderCodeFx() {
|
|
// effectCanvas.fill('#000000');
|
|
// line3D.group.rotation.x+=0.01;
|
|
// line3D.group.rotation.y+=0.02;
|
|
// line3D.group.rotation.z+=0.04;
|
|
// starField.draw();
|
|
// line3D.draw();
|
|
// requestAnimationFrame(renderCodeFx);
|
|
// }
|
|
|
|
const oscRotate = () => {
|
|
setTimeout(() => {
|
|
osc(15, 0.02, 0.8)
|
|
.color(1.0, 0.16, 0.46) // Neon pink
|
|
.rotate(0.3)
|
|
.add(
|
|
osc(30, 0.01, 0.6)
|
|
.color(0.0, 1.0, 1.0) // Cyan
|
|
.rotate(-0.2)
|
|
.modulateRotate(osc(2), 0.5),
|
|
0.4
|
|
)
|
|
.diff(
|
|
osc(100, 0.005, 0.3)
|
|
.color(0.8, 0.2, 1.0) // Purple
|
|
.rotate(Math.PI / 3)
|
|
)
|
|
.modulateScale(osc(1, 0.1), 0.2)
|
|
.add(
|
|
shape(6, 0.8, 0.01)
|
|
.color(0.0, 1.0, 1.0) // Cyan grid
|
|
.rotate(() => time * 0.1)
|
|
.scale(0.3),
|
|
0.1
|
|
)
|
|
.contrast(1.8)
|
|
.brightness(0.1)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const rainbowWebcam = () => {
|
|
solid(0, 0).out();
|
|
setTimeout(() => {
|
|
s0.initCam();
|
|
src(s0).out(o0);
|
|
osc(10, 0.2, 0.8).diff(o0).out(o1);
|
|
render(o1);
|
|
});
|
|
};
|
|
|
|
const waveyzz = () => {
|
|
setTimeout(() => {
|
|
osc(40, -0.01, 0.4)
|
|
.color(1.0, 0.16, 0.46) // Neon pink
|
|
.diff(
|
|
osc(40, 0.05)
|
|
.color(0.0, 1.0, 1.0) // Cyan
|
|
.rotate(Math.PI / 2)
|
|
)
|
|
.modulateScale(
|
|
noise(2.5, 0.3).modulateScale(
|
|
osc(10).rotate(() => Math.sin(time / 3))
|
|
),
|
|
0.4
|
|
)
|
|
.add(
|
|
osc(80, 0.02, 0.1)
|
|
.color(0.5, 0.0, 1.0) // Purple
|
|
.rotate(Math.PI / 4),
|
|
0.2
|
|
)
|
|
.contrast(2.0)
|
|
.add(src(o0).modulate(o0, 0.03), 0.7)
|
|
.modulateHue(osc(0.5, 0.1), 0.3)
|
|
.brightness(0.15)
|
|
.contrast(1.6)
|
|
.modulateScale(osc(1.5), -0.15)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const vernoi = () => {
|
|
setTimeout(() => {
|
|
voronoi(25, 0.15)
|
|
.color(1.0, 0.16, 0.46) // Neon pink
|
|
.add(
|
|
voronoi(15, 0.2)
|
|
.color(0.0, 1.0, 1.0) // Cyan
|
|
.modulateScale(osc(4), 0.2),
|
|
0.6
|
|
)
|
|
.add(
|
|
voronoi(35, 0.1)
|
|
.color(0.8, 0.2, 1.0) // Purple
|
|
.modulateRotate(osc(1), 0.3),
|
|
0.4
|
|
)
|
|
.modulateScale(osc(8), 0.3)
|
|
.contrast(1.8)
|
|
.brightness(0.15)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
|
|
const retroSun = () => {
|
|
setTimeout(() => {
|
|
|
|
solid(0.05, 0.0, 0.15)
|
|
.add(
|
|
// Horizontal grid lines
|
|
osc(40, 0, 1)
|
|
.thresh(0.95)
|
|
.color(0.0, 1.0, 1.0)
|
|
.rotate(Math.PI / 2)
|
|
.scrollY(() => time * 0.2)
|
|
.mult(
|
|
gradient(1)
|
|
.rotate(Math.PI)
|
|
.scale(1.3)
|
|
),
|
|
0.6
|
|
)
|
|
.add(
|
|
// Vertical perspective grid
|
|
osc(20, 0, 1)
|
|
.thresh(0.95)
|
|
.color(0.0, 0.8, 1.0)
|
|
.scrollX(() => time * 0.05)
|
|
.mult(
|
|
gradient(0.5)
|
|
.scale(1.5)
|
|
),
|
|
0.4
|
|
)
|
|
.add(
|
|
shape(32, 0.8, 0.01)
|
|
.color(1.0, 0.8, 0.2)
|
|
.scale(() => 0.12 + Math.sin(time) * 0.015)
|
|
.scrollY(-0.25)
|
|
.add(
|
|
// Sun outer glow
|
|
shape(32, 0.9, 0.03)
|
|
.color(1.0, 0.4, 0.1)
|
|
.scale(() => 0.2 + Math.sin(time * 1.2) * 0.02)
|
|
.scrollY(-0.25),
|
|
0.5
|
|
),
|
|
0.8
|
|
)
|
|
.add(
|
|
// Horizon line glow
|
|
solid(1.0, 0.2, 0.6)
|
|
.mask(
|
|
solid(1)
|
|
.scale(1, 0.003)
|
|
.scrollY(0.1)
|
|
),
|
|
0.7
|
|
)
|
|
.add(
|
|
// Mountain silhouette
|
|
noise(0.8, 0.15)
|
|
.thresh(0.65)
|
|
.color(0.1, 0.05, 0.2)
|
|
.scale(2, 0.25)
|
|
.scrollY(0.25),
|
|
0.4
|
|
)
|
|
.contrast(1.6)
|
|
.brightness(0.15)
|
|
.saturate(1.3)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const synthWave = () => {
|
|
setTimeout(() => {
|
|
osc(30, 0, 1)
|
|
.thresh(0.9)
|
|
.color(0.0, 1.0, 1.0) // Cyan horizontal lines
|
|
.scrollY(() => time * 0.5) // Moving grid lines
|
|
.add(
|
|
gradient(0.5)
|
|
.color(1.0, 0.16, 0.46) // Pink to black gradient
|
|
.rotate(Math.PI)
|
|
.scale(1.5)
|
|
.modulateHue(osc(0.3, 0.1), 0.2), // Subtle hue shift
|
|
0.7
|
|
)
|
|
.add(
|
|
shape(3, 0.8, 0.1)
|
|
.color(1.0, 0.5, 0.0) // Orange triangle/sun shape
|
|
.scale(() => 0.3 + Math.sin(time * 2) * 0.05) // Pulsing size
|
|
.repeatY(3, 0.3)
|
|
.mult(
|
|
osc(5, 0.1, 0.8)
|
|
.color(0.8, 0.2, 1.0) // Purple modulation
|
|
.rotate(() => time * 0.1) // Slow rotation
|
|
),
|
|
0.4
|
|
)
|
|
.add(
|
|
osc(8, 0.01, 0.2)
|
|
.color(0.0, 1.0, 1.0) // More cyan lines
|
|
.thresh(0.8)
|
|
.scrollY(() => -time * 0.3) // Counter-moving lines
|
|
.mult(
|
|
gradient(1.0)
|
|
.color(0.2, 0.0, 0.4) // Dark purple fade
|
|
.scale(2.0)
|
|
.modulateScale(osc(1, 0.1), 0.1), // Breathing effect
|
|
),
|
|
0.3
|
|
)
|
|
.modulateHue(osc(0.1), 0.1) // Overall subtle color shift
|
|
.contrast(2.2)
|
|
.brightness(0.1)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const galaxy = () => {
|
|
setTimeout(() => {
|
|
noise(3, 0.1)
|
|
.rotate(() => time * 0.05) // Slow galaxy rotation
|
|
.color(0.6, 0.2, 1.0) // Purple nebula base
|
|
.add(
|
|
// Bright galaxy core
|
|
osc(0.5, 0, 0.8)
|
|
.color(1.0, 0.9, 0.6) // Warm golden core
|
|
.scale(0.3)
|
|
.mult(
|
|
shape(32, 0.9, 0.1)
|
|
.scale(() => 0.15 + Math.sin(time * 0.8) * 0.05) // Pulsing core
|
|
),
|
|
0.8
|
|
)
|
|
.add(
|
|
noise(8, 0.2)
|
|
.rotate(() => time * 0.02)
|
|
.color(0.2, 0.5, 1.0) // Blue spiral arms
|
|
.modulateRotate(
|
|
osc(1, 0, 1)
|
|
.rotate(() => time * 0.03), // Creates spiral pattern
|
|
2.0
|
|
)
|
|
.mult(
|
|
gradient(1.2)
|
|
.rotate(() => time * 0.01)
|
|
.scale(1.5)
|
|
),
|
|
0.6
|
|
)
|
|
.add(
|
|
|
|
noise(50, 0.05)
|
|
.thresh(0.92)
|
|
.color(1.0, 1.0, 0.9) // Bright white stars
|
|
.add(
|
|
// Larger brighter stars
|
|
noise(25, 0.03)
|
|
.thresh(0.95)
|
|
.color(0.9, 0.9, 1.0) // Slightly blue-white
|
|
.scale(() => 1.0 + Math.sin(time * 3) * 0.1), // Twinkling
|
|
0.7
|
|
),
|
|
0.4
|
|
)
|
|
.add(
|
|
// Pink/red nebula regions
|
|
voronoi(8, 0.3)
|
|
.color(1.0, 0.3, 0.5) // Pink nebula
|
|
.modulateScale(
|
|
osc(2, 0.1)
|
|
.rotate(() => time * 0.02),
|
|
0.3
|
|
),
|
|
0.3
|
|
)
|
|
.add(
|
|
// Cyan gas clouds
|
|
noise(4, 0.15)
|
|
.color(0.2, 0.8, 1.0) // Cyan gas
|
|
.modulateRotate(
|
|
osc(0.8, 0, 1)
|
|
.rotate(() => -time * 0.025),
|
|
1.5
|
|
),
|
|
0.25
|
|
)
|
|
.contrast(1.4)
|
|
.brightness(0.1)
|
|
.saturate(1.2)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const electricSheep = () => {
|
|
setTimeout(() => {
|
|
|
|
noise(10, 0.1)
|
|
.color(0.2, 0.8, 1.0) // Electric blue
|
|
.add(
|
|
// Synthetic neural patterns
|
|
osc(30, 0.02, 0.6)
|
|
.color(1.0, 0.3, 0.8) // Electric pink
|
|
.modulateRotate(
|
|
osc(4, 0, 1)
|
|
.rotate(() => time * 0.1),
|
|
Math.PI
|
|
),
|
|
0.7
|
|
)
|
|
.add(
|
|
// Memory fragments - geometric shapes
|
|
shape(6, 0.8, 0.02)
|
|
.color(1.0, 1.0, 0.2) // Electric yellow
|
|
.scale(() => 0.3 + Math.sin(time * 2) * 0.2)
|
|
.repeat(3, 3)
|
|
.modulateKaleid(
|
|
osc(1, 0, 1)
|
|
.rotate(() => time * 0.05),
|
|
4
|
|
),
|
|
0.5
|
|
)
|
|
.add(
|
|
// Digital rain/glitch lines
|
|
osc(100, 0, 1)
|
|
.thresh(0.9)
|
|
.color(0.0, 1.0, 0.3) // Matrix green
|
|
.scrollY(() => time * 2)
|
|
.mult(
|
|
gradient(1)
|
|
.rotate(Math.PI / 2)
|
|
),
|
|
0.4
|
|
)
|
|
.modulatePixelate(
|
|
noise(8, 0.1)
|
|
.thresh(0.6),
|
|
() => 20 + Math.sin(time) * 10 // Glitch pixelation
|
|
)
|
|
.contrast(2.0)
|
|
.brightness(0.2)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const cyberpunkCity = () => {
|
|
setTimeout(() => {
|
|
// Neon-soaked cyberpunk cityscape
|
|
|
|
// Building silhouettes
|
|
solid(0.05, 0.0, 0.15)
|
|
.add(
|
|
// Neon advertising signs
|
|
osc(20, 0, 1)
|
|
.thresh(0.8)
|
|
.color(1.0, 0.0, 0.5) // Hot pink neon
|
|
.scrollY(() => time * 0.5)
|
|
.add(
|
|
osc(25, 0, 1)
|
|
.thresh(0.85)
|
|
.color(0.0, 1.0, 1.0) // Cyan neon
|
|
.scrollY(() => -time * 0.3)
|
|
.scrollX(() => time * 0.2),
|
|
0.6
|
|
),
|
|
0.8
|
|
)
|
|
.add(
|
|
// Holographic displays
|
|
voronoi(15, 0.2)
|
|
.color(0.2, 1.0, 0.3) // Green hologram
|
|
.modulateScale(
|
|
osc(3, 0.1)
|
|
.rotate(() => time * 0.1),
|
|
0.5
|
|
)
|
|
.mult(
|
|
gradient(0.8)
|
|
.rotate(() => time * 0.02)
|
|
),
|
|
0.4
|
|
)
|
|
.add(
|
|
// Rain/atmosphere
|
|
noise(80, 0.05)
|
|
.thresh(0.95)
|
|
.color(0.6, 0.8, 1.0) // Blue rain
|
|
.scrollY(() => time * 3)
|
|
.scale(1, 2),
|
|
0.3
|
|
)
|
|
.add(
|
|
// Street lights glow
|
|
shape(32, 0.9, 0.1)
|
|
.color(1.0, 0.6, 0.0) // Orange street light
|
|
.scale(() => 0.05 + Math.sin(time * 4) * 0.02)
|
|
.repeat(8, 6)
|
|
.scrollY(() => time * 0.1),
|
|
0.5
|
|
)
|
|
.contrast(1.8)
|
|
.brightness(0.15)
|
|
.saturate(1.4)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const digitalDreams = () => {
|
|
setTimeout(() => {
|
|
// Abstract digital consciousness patterns
|
|
|
|
// Neural network base
|
|
noise(5, 0.2)
|
|
.color(0.8, 0.2, 1.0) // Purple mind
|
|
.modulateRotate(
|
|
osc(2, 0, 1)
|
|
.rotate(() => time * 0.08),
|
|
2.0
|
|
)
|
|
.add(
|
|
// Synaptic connections
|
|
osc(40, 0.01, 0.3)
|
|
.color(0.0, 1.0, 1.0) // Cyan connections
|
|
.thresh(0.7)
|
|
.modulateKaleid(
|
|
osc(1.5, 0, 1),
|
|
8
|
|
),
|
|
0.7
|
|
)
|
|
.add(
|
|
// Memory bubbles
|
|
shape(32, 0.8, 0.05)
|
|
.color(1.0, 1.0, 0.6) // Golden memories
|
|
.scale(() => 0.1 + Math.sin(time * 1.5) * 0.08)
|
|
.repeat(4, 4)
|
|
.modulateScale(
|
|
osc(3, 0.1)
|
|
.rotate(() => time * 0.05),
|
|
0.3
|
|
),
|
|
0.5
|
|
)
|
|
.add(
|
|
// Thought streams
|
|
osc(15, 0.02, 0.8)
|
|
.color(1.0, 0.4, 0.8) // Pink thoughts
|
|
.rotate(() => time * 0.1)
|
|
.modulateScrollY(
|
|
osc(2, 0.1),
|
|
0.5
|
|
),
|
|
0.4
|
|
)
|
|
.modulateHue(
|
|
osc(0.5, 0.2),
|
|
0.3
|
|
)
|
|
.contrast(1.6)
|
|
.brightness(0.2)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const quantumStatic = () => {
|
|
setTimeout(() => {
|
|
// Quantum interference patterns and static
|
|
|
|
// Quantum field fluctuations
|
|
noise(20, 0.3)
|
|
.color(0.5, 0.0, 1.0) // Deep purple quantum
|
|
.add(
|
|
// Interference patterns
|
|
osc(60, 0, 0.8)
|
|
.color(0.0, 1.0, 0.8) // Quantum teal
|
|
.mult(
|
|
osc(40, 0, 0.6)
|
|
.color(1.0, 0.2, 0.6) // Quantum pink
|
|
.rotate(Math.PI / 4)
|
|
),
|
|
0.8
|
|
)
|
|
.add(
|
|
// Probability waves
|
|
osc(8, 0.05, 1.0)
|
|
.color(1.0, 1.0, 0.3) // Bright quantum yellow
|
|
.modulateRotate(
|
|
osc(3, 0, 1)
|
|
.rotate(() => time * 0.1),
|
|
1.5
|
|
)
|
|
.thresh(() => 0.7 + Math.sin(time * 2) * 0.2), // Fluctuating threshold
|
|
0.6
|
|
)
|
|
.add(
|
|
// Particle traces
|
|
noise(100, 0.02)
|
|
.thresh(0.98)
|
|
.color(1.0, 0.8, 1.0) // White particle traces
|
|
.scrollX(() => time * 1.5)
|
|
.scrollY(() => Math.sin(time) * 0.5),
|
|
0.4
|
|
)
|
|
.modulatePixelate(
|
|
noise(5, 0.1),
|
|
() => 15 + Math.sin(time * 3) * 10
|
|
)
|
|
.contrast(2.2)
|
|
.brightness(0.1)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const johnnyMnemonic = () => {
|
|
setTimeout(() => {
|
|
// Johnny Mnemonic - data highways and ice breaking
|
|
|
|
// Data stream base
|
|
noise(15, 0.2)
|
|
.color(0.0, 0.8, 0.3) // Matrix green data
|
|
.add(
|
|
// Ice barriers - crystalline structures
|
|
voronoi(20, 0.1)
|
|
.color(0.8, 0.9, 1.0) // Ice blue
|
|
.thresh(0.6)
|
|
.modulateScale(
|
|
osc(3, 0.2)
|
|
.rotate(() => time * 0.1),
|
|
0.4
|
|
),
|
|
0.7
|
|
)
|
|
.add(
|
|
// Data packets flowing
|
|
osc(80, 0, 1)
|
|
.thresh(0.85)
|
|
.color(1.0, 1.0, 0.0) // Yellow data packets
|
|
.scrollX(() => time * 2)
|
|
.scrollY(() => Math.sin(time * 0.5) * 0.3)
|
|
.mult(
|
|
gradient(1)
|
|
.rotate(() => time * 0.02)
|
|
),
|
|
0.6
|
|
)
|
|
.add(
|
|
// Neural pathways
|
|
osc(40, 0.01, 0.5)
|
|
.color(1.0, 0.2, 0.8) // Pink neural connections
|
|
.modulateRotate(
|
|
osc(2, 0, 1)
|
|
.rotate(() => time * 0.08),
|
|
1.5
|
|
),
|
|
0.5
|
|
)
|
|
.add(
|
|
// Glitch corruption
|
|
noise(100, 0.05)
|
|
.thresh(0.95)
|
|
.color(1.0, 0.0, 0.0) // Red corruption
|
|
.scrollY(() => time * 4)
|
|
.modulatePixelate(
|
|
noise(8, 0.1),
|
|
() => 25 + Math.sin(time * 2) * 15
|
|
),
|
|
0.4
|
|
)
|
|
.contrast(2.0)
|
|
.brightness(0.2)
|
|
.saturate(1.3)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const billAndTed = () => {
|
|
setTimeout(() => {
|
|
// Bill & Ted's Excellent Adventure - time travel vortex
|
|
|
|
// Psychedelic time vortex
|
|
osc(10, 0.1, 0.8)
|
|
.color(1.0, 0.6, 0.0) // Orange 80s glow
|
|
.modulateRotate(
|
|
osc(1, 0, 1)
|
|
.rotate(() => time * 0.2),
|
|
3.0 // Strong spiral effect
|
|
)
|
|
.add(
|
|
// Time ripples
|
|
osc(30, 0.02, 0.6)
|
|
.color(0.8, 0.2, 1.0) // Purple time waves
|
|
.modulateKaleid(
|
|
osc(2, 0, 1),
|
|
6 // Kaleidoscope effect
|
|
),
|
|
0.8
|
|
)
|
|
.add(
|
|
// Excellent sparkles
|
|
noise(60, 0.03)
|
|
.thresh(0.9)
|
|
.color(1.0, 1.0, 0.5) // Golden sparkles
|
|
.scale(() => 1.0 + Math.sin(time * 3) * 0.2)
|
|
.rotate(() => time * 0.3),
|
|
0.6
|
|
)
|
|
.add(
|
|
// Phone booth trails
|
|
shape(4, 0.9, 0.02) // Square phone booth
|
|
.color(0.0, 1.0, 1.0) // Cyan trails
|
|
.scale(() => 0.2 + Math.sin(time * 1.5) * 0.1)
|
|
.repeat(5, 5)
|
|
.modulateScrollX(
|
|
osc(1, 0.1),
|
|
0.5
|
|
),
|
|
0.4
|
|
)
|
|
.add(
|
|
// Bogus interference
|
|
osc(200, 0, 0.3)
|
|
.color(1.0, 0.0, 0.5) // Pink static
|
|
.thresh(() => 0.8 + Math.sin(time * 4) * 0.15)
|
|
.scrollY(() => time * 1.5),
|
|
0.3
|
|
)
|
|
.modulateHue(
|
|
osc(0.5, 0.3),
|
|
0.4
|
|
)
|
|
.contrast(1.8)
|
|
.brightness(0.25)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const tronLegacy = () => {
|
|
setTimeout(() => {
|
|
// Tron Legacy - digital grid world
|
|
|
|
// Grid base
|
|
solid(0.0, 0.05, 0.1) // Dark blue-black base
|
|
.add(
|
|
// Main grid lines
|
|
osc(40, 0, 1)
|
|
.thresh(0.98)
|
|
.color(0.0, 0.8, 1.0) // Cyan grid
|
|
.add(
|
|
osc(40, 0, 1)
|
|
.thresh(0.98)
|
|
.color(0.0, 0.8, 1.0)
|
|
.rotate(Math.PI / 2),
|
|
1.0
|
|
),
|
|
0.8
|
|
)
|
|
.add(
|
|
// Light cycles trails
|
|
osc(100, 0, 1)
|
|
.thresh(0.95)
|
|
.color(1.0, 0.4, 0.0) // Orange light cycle
|
|
.scrollX(() => time * 1.5)
|
|
.scrollY(() => Math.sin(time * 0.8) * 0.4)
|
|
.add(
|
|
osc(100, 0, 1)
|
|
.thresh(0.95)
|
|
.color(0.0, 1.0, 1.0) // Cyan light cycle
|
|
.scrollX(() => -time * 1.2)
|
|
.scrollY(() => Math.cos(time * 0.6) * 0.3),
|
|
0.7
|
|
),
|
|
0.7
|
|
)
|
|
.add(
|
|
// Identity discs
|
|
shape(32, 0.8, 0.02)
|
|
.color(1.0, 1.0, 0.6) // Bright disc
|
|
.scale(() => 0.15 + Math.sin(time * 2) * 0.05)
|
|
.rotate(() => time * 2)
|
|
.repeat(3, 3)
|
|
.modulateScale(
|
|
osc(1, 0.1),
|
|
0.2
|
|
),
|
|
0.6
|
|
)
|
|
.add(
|
|
// Digital de-resolution
|
|
noise(8, 0.1)
|
|
.thresh(0.7)
|
|
.color(0.2, 0.6, 1.0) // Blue pixels
|
|
.modulatePixelate(
|
|
osc(2, 0.1),
|
|
() => 8 + Math.sin(time) * 4
|
|
),
|
|
0.4
|
|
)
|
|
.add(
|
|
// Recognition sequences
|
|
osc(20, 0.05, 0.8)
|
|
.color(1.0, 0.8, 0.0) // Golden recognition
|
|
.thresh(() => 0.6 + Math.sin(time * 3) * 0.3)
|
|
.scrollY(() => time * 0.5),
|
|
0.3
|
|
)
|
|
.contrast(2.2)
|
|
.brightness(0.15)
|
|
.saturate(1.4)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const hitchhikersGuide = () => {
|
|
setTimeout(() => {
|
|
// Hitchhiker's Guide - infinite improbability and babel fish
|
|
|
|
// Improbability field
|
|
noise(5, 0.4)
|
|
.color(0.9, 0.7, 0.2) // Golden improbability
|
|
.modulateRotate(
|
|
osc(0.5, 0, 1)
|
|
.rotate(() => time * 0.042), // 42 reference
|
|
2.5
|
|
)
|
|
.add(
|
|
// Babel fish swimming
|
|
shape(8, 0.9, 0.03) // Fish-like shape
|
|
.color(1.0, 0.9, 0.0) // Yellow babel fish
|
|
.scale(() => 0.1 + Math.sin(time * 1.5) * 0.05)
|
|
.repeat(6, 4)
|
|
.modulateScrollX(
|
|
osc(1, 0.2)
|
|
.rotate(() => time * 0.1),
|
|
0.3
|
|
)
|
|
.modulateScrollY(
|
|
osc(0.8, 0.1),
|
|
0.2
|
|
),
|
|
0.7
|
|
)
|
|
.add(
|
|
// Infinite loops and paradoxes
|
|
voronoi(12, 0.3)
|
|
.color(0.4, 1.0, 0.6) // Green paradox patterns
|
|
.modulateKaleid(
|
|
osc(1.5, 0, 1)
|
|
.rotate(() => time * 0.08),
|
|
8
|
|
),
|
|
0.6
|
|
)
|
|
.add(
|
|
// Deep Thought processing
|
|
osc(60, 0, 0.5)
|
|
.color(0.2, 0.8, 1.0) // Blue computational patterns
|
|
.thresh(() => 0.5 + Math.sin(time * 0.42) * 0.4) // 42 again
|
|
.modulateRotate(
|
|
osc(3, 0, 1),
|
|
1.0
|
|
),
|
|
0.5
|
|
)
|
|
.add(
|
|
// Pan-dimensional mice
|
|
noise(80, 0.02)
|
|
.thresh(0.92)
|
|
.color(0.8, 0.8, 0.8) // Gray mice dots
|
|
.scrollX(() => time * 0.7)
|
|
.scrollY(() => Math.sin(time * 1.2) * 0.6),
|
|
0.4
|
|
)
|
|
.add(
|
|
// Answer to everything: 42
|
|
shape(4, 0.95, 0.01) // Square representing "42"
|
|
.color(1.0, 0.2, 0.8) // Pink answer
|
|
.scale(() => 0.05 + Math.sin(time * 0.42) * 0.02)
|
|
.repeat(42, 1) // 42 repetitions horizontally
|
|
.scrollY(() => time * 0.1),
|
|
0.3
|
|
)
|
|
.modulateHue(
|
|
osc(0.42, 0.2), // More 42 references
|
|
0.3
|
|
)
|
|
.contrast(1.6)
|
|
.brightness(0.3)
|
|
.out();
|
|
}, 100);
|
|
};
|
|
|
|
const hydraEffects = [vernoi, waveyzz, oscRotate, retroSun, synthWave, galaxy, electricSheep, cyberpunkCity, digitalDreams, quantumStatic, johnnyMnemonic, billAndTed, tronLegacy, hitchhikersGuide];
|
|
|
|
function rotateHydraEffect() {
|
|
try {
|
|
hydraEffects[currentEffectIndex]();
|
|
currentEffectIndex = (currentEffectIndex + 1) % hydraEffects.length;
|
|
} catch (error) {
|
|
console.error("Error applying random Hydra effect:", error);
|
|
showErrors(
|
|
"Failed to apply random Hydra effect. Please check your browser compatibility or try again later."
|
|
);
|
|
}
|
|
}
|
|
|
|
function initHydra() {
|
|
try {
|
|
hydraEffects[0]();
|
|
} catch (error) {
|
|
console.error("Hydra initialization failed:", error);
|
|
showErrors(
|
|
"Hydra initialization failed. Please check your browser compatibility or try again later."
|
|
);
|
|
}
|
|
}
|
|
|
|
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: #ff2975; font-weight: bold; text-shadow: 0 0 5px rgba(255, 41, 117, 0.8);">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('');
|
|
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(' seek [time] - Seek to time (seconds or mm:ss)');
|
|
addTerminalLine(' status - Show current track info');
|
|
addTerminalLine(' history - Download playlist history');
|
|
addTerminalLine(' effect [list|name]- Change/list visual effects');
|
|
addTerminalLine(' fullscreen - Toggle fullscreen (Desktop only)');
|
|
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 'seek':
|
|
if (args[1] && howlerInstance) {
|
|
let seekTime = 0;
|
|
const input = args[1];
|
|
|
|
if (input.includes(':')) {
|
|
const [mins, secs] = input.split(':').map(Number);
|
|
seekTime = (mins * 60) + secs;
|
|
} else {
|
|
seekTime = parseFloat(input);
|
|
}
|
|
|
|
const duration = howlerInstance.duration() || 0;
|
|
if (seekTime >= 0 && seekTime <= duration) {
|
|
howlerInstance.seek(seekTime);
|
|
updateProgressBar();
|
|
addTerminalLine(`Seeked to ${formatTime(seekTime)}`);
|
|
} else {
|
|
addTerminalLine(`Invalid seek time. Duration: ${formatTime(duration)}`);
|
|
}
|
|
} else if (!howlerInstance) {
|
|
addTerminalLine('No track loaded.');
|
|
} else {
|
|
if (howlerInstance) {
|
|
const seek = howlerInstance.seek() || 0;
|
|
const duration = howlerInstance.duration() || 0;
|
|
addTerminalLine(`Current position: ${formatTime(seek)} / ${formatTime(duration)}`);
|
|
}
|
|
}
|
|
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`);
|
|
if (howlerInstance) {
|
|
const seek = howlerInstance.seek() || 0;
|
|
const duration = howlerInstance.duration() || 0;
|
|
const progress = duration > 0 ? ((seek / duration) * 100).toFixed(1) : 0;
|
|
addTerminalLine(`Progress: ${formatTime(seek)} / ${formatTime(duration)} (${progress}%)`);
|
|
}
|
|
} 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 - Synthwave Voronoi patterns');
|
|
addTerminalLine(' waveyzz - Neon wave synthesis');
|
|
addTerminalLine(' oscrotate - Oscillating neon shapes');
|
|
addTerminalLine(' retrosun - Retro sunset with grid');
|
|
addTerminalLine(' synthwave - Basic synthwave pattern');
|
|
addTerminalLine(' galaxy - Deep space galaxy with stars');
|
|
addTerminalLine(' electricsheep- Digital dreams & synthetic memories');
|
|
addTerminalLine(' cyberpunk - Neon-soaked cityscape');
|
|
addTerminalLine(' digitaldreams- Abstract consciousness patterns');
|
|
addTerminalLine(' quantumstatic- Quantum interference & particles');
|
|
addTerminalLine(' johnny - Johnny Mnemonic data highways');
|
|
addTerminalLine(' billted - Bill & Ted time travel vortex');
|
|
addTerminalLine(' tron - Tron Legacy digital grid world');
|
|
addTerminalLine(' hitchhiker - Hitchhiker\'s Guide improbability');
|
|
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;
|
|
case 'retrosun':
|
|
effectIndex = 3;
|
|
break;
|
|
case 'synthwave':
|
|
effectIndex = 4;
|
|
break;
|
|
case 'galaxy':
|
|
effectIndex = 5;
|
|
break;
|
|
case 'electricsheep':
|
|
effectIndex = 6;
|
|
break;
|
|
case 'cyberpunk':
|
|
effectIndex = 7;
|
|
break;
|
|
case 'digitaldreams':
|
|
effectIndex = 8;
|
|
break;
|
|
case 'quantumstatic':
|
|
effectIndex = 9;
|
|
break;
|
|
case 'johnny':
|
|
effectIndex = 10;
|
|
break;
|
|
case 'billted':
|
|
effectIndex = 11;
|
|
break;
|
|
case 'tron':
|
|
effectIndex = 12;
|
|
break;
|
|
case 'hitchhiker':
|
|
effectIndex = 13;
|
|
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('');
|
|
}
|
|
})();
|