80s Visual Toys
SYNTHWAVE HORIZON
Animated 80s Retrowave horizon visualizer. Sliced neon sun, moving perspective grid, VHS scanline overlay, and screenshot export.
Grid Speed
1x
Sun Size
90px
Grid Color
Sun Color
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
const canvas = document.getElementById('synth-canvas');
const ctx = canvas.getContext('2d');
const wrap = document.getElementById('canvas-wrap');
const speedInput = document.getElementById('input-speed');
const sunInput = document.getElementById('input-sun');
const gridColorInput = document.getElementById('input-grid');
const sunColorInput = document.getElementById('input-sun-col');
const toast = document.getElementById('toast');
let gridOffset = 0;
function resize() {
canvas.width = wrap.clientWidth;
canvas.height = wrap.clientHeight;
}
function drawSynthwave() {
const w = canvas.width;
const h = canvas.height;
const horizonY = h * 0.55;
// Sky Background Gradient
const skyGrad = ctx.createLinearGradient(0, 0, 0, horizonY);
skyGrad.addColorStop(0, '#05010d');
skyGrad.addColorStop(0.7, '#240046');
skyGrad.addColorStop(1, '#ff0077');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, w, horizonY);
// Sun
const sunR = parseInt(sunInput.value);
const sunX = w / 2;
const sunY = horizonY - 10;
document.getElementById('val-sun').textContent = sunR + 'px';
const sunGrad = ctx.createLinearGradient(0, sunY - sunR, 0, sunY + sunR);
sunGrad.addColorStop(0, '#ffff00');
sunGrad.addColorStop(1, sunColorInput.value);
ctx.fillStyle = sunGrad;
ctx.beginPath();
ctx.arc(sunX, sunY, sunR, Math.PI, 0, false);
ctx.fill();
// Sun Slices
ctx.fillStyle = '#05010d';
for (let i = 0; i < 6; i++) {
const sliceY = sunY - (i * (sunR / 7));
const sliceH = (i + 1) * 2;
ctx.fillRect(sunX - sunR - 10, sliceY, (sunR + 10) * 2, sliceH);
}
// Ground Background
ctx.fillStyle = '#0a0014';
ctx.fillRect(0, horizonY, w, h - horizonY);
// Perspective Grid Lines
const speed = parseFloat(speedInput.value);
document.getElementById('val-speed').textContent = speed + 'x';
gridOffset = (gridOffset + speed * 1.5) % 40;
ctx.strokeStyle = gridColorInput.value;
ctx.lineWidth = 1.5;
// Horizontal moving lines
for (let y = horizonY; y < h; y += 40) {
const drawY = y + (gridOffset * ((y - horizonY) / (h - horizonY)));
if (drawY <= h) {
ctx.beginPath();
ctx.moveTo(0, drawY);
ctx.lineTo(w, drawY);
ctx.stroke();
}
}
// Vertical perspective lines
const numLines = 24;
for (let i = -numLines; i <= numLines; i++) {
ctx.beginPath();
ctx.moveTo(w / 2, horizonY);
ctx.lineTo(w / 2 + i * 40, h);
ctx.stroke();
}
requestAnimationFrame(drawSynthwave);
}
function showToast(msg) {
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2000);
}
document.getElementById('btn-fullscreen').addEventListener('click', () => {
if (!document.fullscreenElement) {
wrap.requestFullscreen();
} else {
document.exitFullscreen();
}
});
document.getElementById('btn-screenshot').addEventListener('click', () => {
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = 'synthwave.png';
a.click();
showToast('Screenshot Saved!');
});
window.addEventListener('resize', resize);
resize();
drawSynthwave();