(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 = `indrajith@retrowave:$ ${text}`;
} 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 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('');
}
})();