(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"); // 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(); }); 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(); }, }); 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) { howlerInstance.stop(); if (destroy) { howlerInstance.unload(); howlerInstance = null; } } 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(' 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 '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 - 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(''); } })();