Retro Topographic Grid
🟠 WebGLA gorgeous 3D wireframe topographic landscape under a neon retro sun. Features a procedural Web Audio synthesizer playing an interactive, heavy-bass ambient beat that drives real-time landscape morphing, pulse lighting, and camera vibrations.
Responsive Horizontal Leaderboard
Slot ID: ca-pub-detail-below-preview
Interactive Settings
Medium Rectangle Block
Slot ID: ca-pub-detail-after-customization
Quick Copy Actions
Download File Packages
Keyboard Shortcuts
Responsive Horizontal Leaderboard
Slot ID: ca-pub-detail-before-related
More in WebGL
Loading Canvas Shader...
WebGL Aurora
A gorgeous, high-performance WebGL-powered Aurora Borealis simulation. Watch glowing ribbons of green and violet light wave gracefully across the deep cosmos.
Loading Canvas Shader...
Midnight JDM Street Race
A deeply motion-blurred late-night street race simulation. Glowing red and white headlights and taillights continuously warp past with responsive speed-boosting scroll wheel and immersive camera shake.
Loading Canvas Shader...
WebGL Chromatic Distortion
An interactive, fluid screen-warp simulation featuring dynamic chromatic aberration. Watch red, green, and blue wave vectors split and refract in real-time as you move the cursor.
Retro Topographic Grid - WebGL 3D GPU WebGL background for React & Tailwind
Integrate the Retro Topographic Grid directly into your website. This asset is rendered using raw WebGL shader context. It is optimized for zero layout-shifts and runs with high-performance hardware-accelerated processing.
⚡ Performance Specifications
- Render Mode: WEBGL (WebGL 3D GPU)
- Fluidity: Locked at 60fps dynamic loop
- Bundle Footprint: Zero external NPM dependencies
- SEO Indexing status: 100% Crawlable static semantic HTML
🛠️ Integration Capabilities
Our templates expose inline design tokens like --color-1 and --color-2 for infinite color palettes. This template is designed to fit inside hero elements, full-screen landing pages, and interactive presentation cards.
Technical Code Reference & Syntaxes for Googlebot & Crawlers
The snippets below display the direct, unminified source code utilized for rendering this background.
HTML & Inline CSS Snippet (Vanilla)
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Retro Topographic Grid</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #09090b;
}
.retro-topo-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#canvas-webgl-retro-topo {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.audio-control-panel {
position: absolute;
bottom: 24px;
right: 24px;
z-index: 50;
pointer-events: auto;
}
.audio-btn {
background: rgba(11, 11, 15, 0.75);
border: 1px solid rgba(255, 0, 128, 0.35);
box-shadow: 0 4px 20px rgba(255, 0, 128, 0.15);
border-radius: 12px;
color: #fff;
padding: 10px 16px;
font-family: monospace;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(8px);
}
.audio-btn:hover {
background: rgba(255, 0, 128, 0.15);
border-color: rgba(255, 0, 128, 0.8);
box-shadow: 0 4px 25px rgba(255, 0, 128, 0.3);
transform: translateY(-2px);
}
.audio-btn:active {
transform: translateY(0);
}
.audio-btn.active {
border-color: rgba(0, 240, 255, 0.85);
box-shadow: 0 4px 25px rgba(0, 240, 255, 0.35);
}
.audio-btn.active:hover {
background: rgba(0, 240, 255, 0.15);
}
</style>
</head>
<body>
<div class="retro-topo-container">
<canvas id="canvas-webgl-retro-topo"></canvas>
<div class="audio-control-panel">
<button id="toggle-topo-audio" class="audio-btn">
<span class="btn-icon">🔇</span>
<span id="btn-text" class="btn-text">MUTED - CLICK TO START RETRO BEAT</span>
</button>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-webgl-retro-topo');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return;
const vertices = new Float32Array([
-1, -1, 1, -1, -1, 1,
-1, 1, 1, -1, 1, 1
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vsSource = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fsSource = `
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_audio_pulse;
uniform vec2 u_mouse;
float getHeight(vec2 p) {
float h = 0.0;
float t = u_time * 0.15;
float wave1 = sin(p.x * 0.25 + t) * cos(p.y * 0.22 - t * 0.8);
float wave2 = cos(p.x * 0.55 - t * 1.5) * sin(p.y * 0.45 + t);
float wave3 = sin(p.x * 1.15 + t * 2.0) * cos(p.y * 1.05 - t * 1.2) * 0.35;
h = wave1 * 1.5 + wave2 * 0.75 + wave3 * 0.2;
h += abs(sin(p.x * 0.15) * cos(p.y * 0.15)) * 1.8;
float pulseMod = 1.0 + u_audio_pulse * 1.6;
return h * pulseMod - 0.4;
}
vec3 raymarch(vec3 ro, vec3 rd, out float hitDist) {
float t = 0.01;
float max_dist = 28.0;
for (int i = 0; i < 64; i++) {
vec3 p = ro + rd * t;
float h = getHeight(p.xz);
if (p.y <= h) {
hitDist = t;
return p;
}
t += max(0.06, (p.y - h) * 0.45);
if (t > max_dist) break;
}
hitDist = -1.0;
return vec3(0.0);
}
void main() {
vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
vec2 shake = vec2(0.0);
if (u_audio_pulse > 0.01) {
shake = vec2(sin(u_time * 75.0), cos(u_time * 65.0)) * u_audio_pulse * 0.004;
}
p += shake;
vec3 ro = vec3(0.0, 3.6, u_time * 0.9);
vec2 m = u_mouse / u_resolution - 0.5;
ro.x += m.x * 6.0;
ro.y += m.y * 2.5;
float floorH = getHeight(ro.xz);
if (ro.y < floorH + 1.1) {
ro.y = floorH + 1.1;
}
vec3 rd = normalize(vec3(p.x, p.y - 0.12, 0.85));
float pitch = -0.3 + m.y * 0.12;
float cp = cos(pitch), sp = sin(pitch);
vec3 rdp = vec3(rd.x, rd.y * cp - rd.z * sp, rd.y * sp + rd.z * cp);
float yaw = m.x * 0.3;
float cy = cos(yaw), sy = sin(yaw);
vec3 rdy = vec3(rdp.x * cy - rdp.z * sy, rdp.y, rdp.x * sy + rdp.z * cy);
rd = normalize(rdy);
float hitDist;
vec3 hitPos = raymarch(ro, rd, hitDist);
vec3 color = vec3(0.003, 0.002, 0.006);
if (hitDist > 0.0) {
float gridFreq = 1.2;
vec2 gridPos = hitPos.xz * gridFreq;
float w = 0.02 + hitDist * 0.009;
float gx = abs(fract(gridPos.x - 0.5) - 0.5);
float gz = abs(fract(gridPos.y - 0.5) - 0.5);
float gridX = smoothstep(w, 0.0, gx);
float gridZ = smoothstep(w, 0.0, gz);
float gridFactor = max(gridX, gridZ);
float contourFreq = 2.0;
float cy = abs(fract(hitPos.y * contourFreq - 0.5) - 0.5);
float contourFactor = smoothstep(w * 0.6, 0.0, cy);
float lineGlow = max(gridFactor, contourFactor * 0.8);
float fog = exp(-hitDist * 0.08);
float heightRel = (hitPos.y + 0.8) / 3.0;
vec3 lowColor = vec3(0.0, 0.45, 1.0);
vec3 highColor = vec3(1.0, 0.0, 0.5);
vec3 gridColor = mix(lowColor, highColor, clamp(heightRel + sin(u_time * 0.4) * 0.15 + u_audio_pulse * 0.35, 0.0, 1.0));
gridColor += vec3(0.12, 0.18, 0.35) * u_audio_pulse;
color = mix(color, gridColor, lineGlow * fog);
float peakSheen = smoothstep(0.4, 2.0, hitPos.y) * 0.12 * fog;
color += vec3(0.0, 0.85, 1.0) * peakSheen;
} else {
float horizonGlow = smoothstep(0.15, -0.22, rd.y) * 0.28;
color += vec3(0.45, 0.0, 0.75) * horizonGlow;
vec3 sunPos = normalize(vec3(0.0, 0.04, 1.0));
float dotSun = dot(rd, sunPos);
if (dotSun > 0.0) {
float sunRadius = 0.17;
float sunGlow = exp(-(1.0 - dotSun) * 55.0);
float sunMask = smoothstep(sunRadius + 0.008, sunRadius, 1.0 - dotSun);
float stripes = step(0.16, fract(rd.y * 38.0));
sunMask *= mix(1.0, stripes, smoothstep(-0.06, 0.08, rd.y));
vec3 sunColor = mix(vec3(1.0, 0.0, 0.45), vec3(1.0, 0.75, 0.0), rd.y * 4.5 + 0.25);
color += sunColor * (sunMask + sunGlow * 0.35);
}
}
float scanline = sin(gl_FragCoord.y * 2.2) * 0.025;
color -= vec3(scanline);
gl_FragColor = vec4(color, 1.0);
}
`;
const vs = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vs || !fs) return;
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;
gl.useProgram(program);
const positionLoc = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
const uResolution = gl.getUniformLocation(program, 'u_resolution');
const uTime = gl.getUniformLocation(program, 'u_time');
const uAudioPulse = gl.getUniformLocation(program, 'u_audio_pulse');
const uMouse = gl.getUniformLocation(program, 'u_mouse');
let mouseX = 0, mouseY = 0;
window.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = rect.height - (e.clientY - rect.top);
});
// Web Audio Synthesizer Logic
let audioCtx = null;
let analyser = null;
let dataArray = null;
let synthInterval = null;
let isPlaying = false;
let masterGain = null;
let step = 0;
function initAudio() {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 32;
dataArray = new Uint8Array(analyser.frequencyBinCount);
masterGain = audioCtx.createGain();
masterGain.gain.setValueAtTime(0.0, audioCtx.currentTime);
masterGain.connect(analyser);
analyser.connect(audioCtx.destination);
// Smooth transition in gain
masterGain.gain.linearRampToValueAtTime(1.0, audioCtx.currentTime + 1.2);
}
function playSynthwave() {
if (synthInterval) clearInterval(synthInterval);
synthInterval = setInterval(() => {
if (!isPlaying || !audioCtx) return;
if (audioCtx.state === 'suspended') return;
const now = audioCtx.currentTime;
// KICK DRUM on even beats
if (step % 2 === 0) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(125, now);
osc.frequency.exponentialRampToValueAtTime(0.01, now + 0.28);
gain.gain.setValueAtTime(0.85, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.25);
osc.connect(gain);
gain.connect(masterGain);
osc.start(now);
osc.stop(now + 0.28);
}
// SYNTH RESONANT BASSLINE
const notes = [55.0, 55.0, 65.4, 48.9, 58.2, 58.2, 65.4, 48.9];
const currentNote = notes[step % notes.length];
const bassOsc = audioCtx.createOscillator();
const bassFilter = audioCtx.createBiquadFilter();
const bassGain = audioCtx.createGain();
bassOsc.type = 'sawtooth';
bassOsc.frequency.setValueAtTime(currentNote, now);
bassFilter.type = 'lowpass';
bassFilter.frequency.setValueAtTime(280, now);
bassFilter.Q.setValueAtTime(6.0, now);
bassFilter.frequency.exponentialRampToValueAtTime(950, now + 0.08);
bassFilter.frequency.exponentialRampToValueAtTime(180, now + 0.38);
bassGain.gain.setValueAtTime(0.14, now);
bassGain.gain.linearRampToValueAtTime(0.01, now + 0.42);
bassOsc.connect(bassFilter);
bassFilter.connect(bassGain);
bassGain.connect(masterGain);
bassOsc.start(now);
bassOsc.stop(now + 0.42);
step = (step + 1) % 8;
}, 380);
}
const audioToggleBtn = document.getElementById('toggle-topo-audio');
const btnText = document.getElementById('btn-text');
const btnIcon = audioToggleBtn.querySelector('.btn-icon');
audioToggleBtn.addEventListener('click', async () => {
if (!audioCtx) {
initAudio();
}
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
if (!isPlaying) {
isPlaying = true;
playSynthwave();
btnText.innerText = "BEAT ACTIVE - CLICK TO MUTE";
btnIcon.innerText = "🔊";
audioToggleBtn.classList.add('active');
} else {
isPlaying = false;
if (synthInterval) {
clearInterval(synthInterval);
synthInterval = null;
}
btnText.innerText = "MUTED - CLICK TO START RETRO BEAT";
btnIcon.innerText = "🔇";
audioToggleBtn.classList.remove('active');
}
});
function resize() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
const width = rect ? rect.width : window.innerWidth;
const height = rect ? rect.height : window.innerHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
gl.viewport(0, 0, width, height);
}
}
window.addEventListener('resize', resize);
if (canvas.parentNode) {
const observer = new MutationObserver(resize);
observer.observe(canvas.parentNode, { attributes: true });
}
resize();
let startTime = Date.now();
let audioPulse = 0.0;
function render() {
const now = Date.now();
let elapsed = (now - startTime) / 1000.0;
if (analyser && isPlaying) {
analyser.getByteFrequencyData(dataArray);
let sum = 0;
for (let i = 0; i < dataArray.length; i++) {
sum += dataArray[i];
}
const average = sum / dataArray.length;
audioPulse = average / 255.0;
} else {
audioPulse = (Math.sin(elapsed * 2.8) * 0.5 + 0.5) * 0.07;
}
gl.uniform2f(uResolution, canvas.width, canvas.height);
gl.uniform1f(uTime, elapsed);
gl.uniform1f(uAudioPulse, audioPulse);
gl.uniform2f(uMouse, mouseX, mouseY);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('beforeunload', () => {
if (synthInterval) clearInterval(synthInterval);
if (audioCtx) audioCtx.close();
});
})();
</script>
</body>
</html>React Component Wrapper (TSX)
import React from 'react';
export default function RetroTopographicGridBackground() {
return (
<div
style={{ width: '100%', height: '100%', position: 'relative', overflow: 'hidden' }}
>
<style dangerouslySetInnerHTML={{ __html: `
.retro-topo-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#canvas-webgl-retro-topo {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.audio-control-panel {
position: absolute;
bottom: 24px;
right: 24px;
z-index: 50;
pointer-events: auto;
}
.audio-btn {
background: rgba(11, 11, 15, 0.75);
border: 1px solid rgba(255, 0, 128, 0.35);
box-shadow: 0 4px 20px rgba(255, 0, 128, 0.15);
border-radius: 12px;
color: #fff;
padding: 10px 16px;
font-family: monospace;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(8px);
}
.audio-btn:hover {
background: rgba(255, 0, 128, 0.15);
border-color: rgba(255, 0, 128, 0.8);
box-shadow: 0 4px 25px rgba(255, 0, 128, 0.3);
transform: translateY(-2px);
}
.audio-btn:active {
transform: translateY(0);
}
.audio-btn.active {
border-color: rgba(0, 240, 255, 0.85);
box-shadow: 0 4px 25px rgba(0, 240, 255, 0.35);
}
.audio-btn.active:hover {
background: rgba(0, 240, 255, 0.15);
}
` }} />
{/* HTML Structure */}
<div
style={{ width: '100%', height: '100%' }}
dangerouslySetInnerHTML={{ __html: `
<div class="retro-topo-container">
<canvas id="canvas-webgl-retro-topo"></canvas>
<div class="audio-control-panel">
<button id="toggle-topo-audio" class="audio-btn">
<span class="btn-icon">🔇</span>
<span id="btn-text" class="btn-text">MUTED - CLICK TO START RETRO BEAT</span>
</button>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-webgl-retro-topo');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return;
const vertices = new Float32Array([
-1, -1, 1, -1, -1, 1,
-1, 1, 1, -1, 1, 1
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vsSource = \`
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
\`;
const fsSource = \`
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_audio_pulse;
uniform vec2 u_mouse;
float getHeight(vec2 p) {
float h = 0.0;
float t = u_time * 0.15;
float wave1 = sin(p.x * 0.25 + t) * cos(p.y * 0.22 - t * 0.8);
float wave2 = cos(p.x * 0.55 - t * 1.5) * sin(p.y * 0.45 + t);
float wave3 = sin(p.x * 1.15 + t * 2.0) * cos(p.y * 1.05 - t * 1.2) * 0.35;
h = wave1 * 1.5 + wave2 * 0.75 + wave3 * 0.2;
h += abs(sin(p.x * 0.15) * cos(p.y * 0.15)) * 1.8;
float pulseMod = 1.0 + u_audio_pulse * 1.6;
return h * pulseMod - 0.4;
}
vec3 raymarch(vec3 ro, vec3 rd, out float hitDist) {
float t = 0.01;
float max_dist = 28.0;
for (int i = 0; i < 64; i++) {
vec3 p = ro + rd * t;
float h = getHeight(p.xz);
if (p.y <= h) {
hitDist = t;
return p;
}
t += max(0.06, (p.y - h) * 0.45);
if (t > max_dist) break;
}
hitDist = -1.0;
return vec3(0.0);
}
void main() {
vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
vec2 shake = vec2(0.0);
if (u_audio_pulse > 0.01) {
shake = vec2(sin(u_time * 75.0), cos(u_time * 65.0)) * u_audio_pulse * 0.004;
}
p += shake;
vec3 ro = vec3(0.0, 3.6, u_time * 0.9);
vec2 m = u_mouse / u_resolution - 0.5;
ro.x += m.x * 6.0;
ro.y += m.y * 2.5;
float floorH = getHeight(ro.xz);
if (ro.y < floorH + 1.1) {
ro.y = floorH + 1.1;
}
vec3 rd = normalize(vec3(p.x, p.y - 0.12, 0.85));
float pitch = -0.3 + m.y * 0.12;
float cp = cos(pitch), sp = sin(pitch);
vec3 rdp = vec3(rd.x, rd.y * cp - rd.z * sp, rd.y * sp + rd.z * cp);
float yaw = m.x * 0.3;
float cy = cos(yaw), sy = sin(yaw);
vec3 rdy = vec3(rdp.x * cy - rdp.z * sy, rdp.y, rdp.x * sy + rdp.z * cy);
rd = normalize(rdy);
float hitDist;
vec3 hitPos = raymarch(ro, rd, hitDist);
vec3 color = vec3(0.003, 0.002, 0.006);
if (hitDist > 0.0) {
float gridFreq = 1.2;
vec2 gridPos = hitPos.xz * gridFreq;
float w = 0.02 + hitDist * 0.009;
float gx = abs(fract(gridPos.x - 0.5) - 0.5);
float gz = abs(fract(gridPos.y - 0.5) - 0.5);
float gridX = smoothstep(w, 0.0, gx);
float gridZ = smoothstep(w, 0.0, gz);
float gridFactor = max(gridX, gridZ);
float contourFreq = 2.0;
float cy = abs(fract(hitPos.y * contourFreq - 0.5) - 0.5);
float contourFactor = smoothstep(w * 0.6, 0.0, cy);
float lineGlow = max(gridFactor, contourFactor * 0.8);
float fog = exp(-hitDist * 0.08);
float heightRel = (hitPos.y + 0.8) / 3.0;
vec3 lowColor = vec3(0.0, 0.45, 1.0);
vec3 highColor = vec3(1.0, 0.0, 0.5);
vec3 gridColor = mix(lowColor, highColor, clamp(heightRel + sin(u_time * 0.4) * 0.15 + u_audio_pulse * 0.35, 0.0, 1.0));
gridColor += vec3(0.12, 0.18, 0.35) * u_audio_pulse;
color = mix(color, gridColor, lineGlow * fog);
float peakSheen = smoothstep(0.4, 2.0, hitPos.y) * 0.12 * fog;
color += vec3(0.0, 0.85, 1.0) * peakSheen;
} else {
float horizonGlow = smoothstep(0.15, -0.22, rd.y) * 0.28;
color += vec3(0.45, 0.0, 0.75) * horizonGlow;
vec3 sunPos = normalize(vec3(0.0, 0.04, 1.0));
float dotSun = dot(rd, sunPos);
if (dotSun > 0.0) {
float sunRadius = 0.17;
float sunGlow = exp(-(1.0 - dotSun) * 55.0);
float sunMask = smoothstep(sunRadius + 0.008, sunRadius, 1.0 - dotSun);
float stripes = step(0.16, fract(rd.y * 38.0));
sunMask *= mix(1.0, stripes, smoothstep(-0.06, 0.08, rd.y));
vec3 sunColor = mix(vec3(1.0, 0.0, 0.45), vec3(1.0, 0.75, 0.0), rd.y * 4.5 + 0.25);
color += sunColor * (sunMask + sunGlow * 0.35);
}
}
float scanline = sin(gl_FragCoord.y * 2.2) * 0.025;
color -= vec3(scanline);
gl_FragColor = vec4(color, 1.0);
}
\`;
const vs = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vs || !fs) return;
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;
gl.useProgram(program);
const positionLoc = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
const uResolution = gl.getUniformLocation(program, 'u_resolution');
const uTime = gl.getUniformLocation(program, 'u_time');
const uAudioPulse = gl.getUniformLocation(program, 'u_audio_pulse');
const uMouse = gl.getUniformLocation(program, 'u_mouse');
let mouseX = 0, mouseY = 0;
window.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = rect.height - (e.clientY - rect.top);
});
// Web Audio Synthesizer Logic
let audioCtx = null;
let analyser = null;
let dataArray = null;
let synthInterval = null;
let isPlaying = false;
let masterGain = null;
let step = 0;
function initAudio() {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 32;
dataArray = new Uint8Array(analyser.frequencyBinCount);
masterGain = audioCtx.createGain();
masterGain.gain.setValueAtTime(0.0, audioCtx.currentTime);
masterGain.connect(analyser);
analyser.connect(audioCtx.destination);
// Smooth transition in gain
masterGain.gain.linearRampToValueAtTime(1.0, audioCtx.currentTime + 1.2);
}
function playSynthwave() {
if (synthInterval) clearInterval(synthInterval);
synthInterval = setInterval(() => {
if (!isPlaying || !audioCtx) return;
if (audioCtx.state === 'suspended') return;
const now = audioCtx.currentTime;
// KICK DRUM on even beats
if (step % 2 === 0) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(125, now);
osc.frequency.exponentialRampToValueAtTime(0.01, now + 0.28);
gain.gain.setValueAtTime(0.85, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.25);
osc.connect(gain);
gain.connect(masterGain);
osc.start(now);
osc.stop(now + 0.28);
}
// SYNTH RESONANT BASSLINE
const notes = [55.0, 55.0, 65.4, 48.9, 58.2, 58.2, 65.4, 48.9];
const currentNote = notes[step % notes.length];
const bassOsc = audioCtx.createOscillator();
const bassFilter = audioCtx.createBiquadFilter();
const bassGain = audioCtx.createGain();
bassOsc.type = 'sawtooth';
bassOsc.frequency.setValueAtTime(currentNote, now);
bassFilter.type = 'lowpass';
bassFilter.frequency.setValueAtTime(280, now);
bassFilter.Q.setValueAtTime(6.0, now);
bassFilter.frequency.exponentialRampToValueAtTime(950, now + 0.08);
bassFilter.frequency.exponentialRampToValueAtTime(180, now + 0.38);
bassGain.gain.setValueAtTime(0.14, now);
bassGain.gain.linearRampToValueAtTime(0.01, now + 0.42);
bassOsc.connect(bassFilter);
bassFilter.connect(bassGain);
bassGain.connect(masterGain);
bassOsc.start(now);
bassOsc.stop(now + 0.42);
step = (step + 1) % 8;
}, 380);
}
const audioToggleBtn = document.getElementById('toggle-topo-audio');
const btnText = document.getElementById('btn-text');
const btnIcon = audioToggleBtn.querySelector('.btn-icon');
audioToggleBtn.addEventListener('click', async () => {
if (!audioCtx) {
initAudio();
}
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
if (!isPlaying) {
isPlaying = true;
playSynthwave();
btnText.innerText = "BEAT ACTIVE - CLICK TO MUTE";
btnIcon.innerText = "🔊";
audioToggleBtn.classList.add('active');
} else {
isPlaying = false;
if (synthInterval) {
clearInterval(synthInterval);
synthInterval = null;
}
btnText.innerText = "MUTED - CLICK TO START RETRO BEAT";
btnIcon.innerText = "🔇";
audioToggleBtn.classList.remove('active');
}
});
function resize() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
const width = rect ? rect.width : window.innerWidth;
const height = rect ? rect.height : window.innerHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
gl.viewport(0, 0, width, height);
}
}
window.addEventListener('resize', resize);
if (canvas.parentNode) {
const observer = new MutationObserver(resize);
observer.observe(canvas.parentNode, { attributes: true });
}
resize();
let startTime = Date.now();
let audioPulse = 0.0;
function render() {
const now = Date.now();
let elapsed = (now - startTime) / 1000.0;
if (analyser && isPlaying) {
analyser.getByteFrequencyData(dataArray);
let sum = 0;
for (let i = 0; i < dataArray.length; i++) {
sum += dataArray[i];
}
const average = sum / dataArray.length;
audioPulse = average / 255.0;
} else {
audioPulse = (Math.sin(elapsed * 2.8) * 0.5 + 0.5) * 0.07;
}
gl.uniform2f(uResolution, canvas.width, canvas.height);
gl.uniform1f(uTime, elapsed);
gl.uniform1f(uAudioPulse, audioPulse);
gl.uniform2f(uMouse, mouseX, mouseY);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('beforeunload', () => {
if (synthInterval) clearInterval(synthInterval);
if (audioCtx) audioCtx.close();
});
})();
</script>
` }}
/>
</div>
);
}Next.js App Router Component (use client)
'use client';
import React from 'react';
export default function RetroTopographicGridBackground() {
return (
<div
className="w-full h-full relative overflow-hidden"
>
<style dangerouslySetInnerHTML={{ __html: `
.retro-topo-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#canvas-webgl-retro-topo {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.audio-control-panel {
position: absolute;
bottom: 24px;
right: 24px;
z-index: 50;
pointer-events: auto;
}
.audio-btn {
background: rgba(11, 11, 15, 0.75);
border: 1px solid rgba(255, 0, 128, 0.35);
box-shadow: 0 4px 20px rgba(255, 0, 128, 0.15);
border-radius: 12px;
color: #fff;
padding: 10px 16px;
font-family: monospace;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(8px);
}
.audio-btn:hover {
background: rgba(255, 0, 128, 0.15);
border-color: rgba(255, 0, 128, 0.8);
box-shadow: 0 4px 25px rgba(255, 0, 128, 0.3);
transform: translateY(-2px);
}
.audio-btn:active {
transform: translateY(0);
}
.audio-btn.active {
border-color: rgba(0, 240, 255, 0.85);
box-shadow: 0 4px 25px rgba(0, 240, 255, 0.35);
}
.audio-btn.active:hover {
background: rgba(0, 240, 255, 0.15);
}
` }} />
{/* HTML Structure */}
<div
className="w-full h-full"
dangerouslySetInnerHTML={{ __html: `
<div class="retro-topo-container">
<canvas id="canvas-webgl-retro-topo"></canvas>
<div class="audio-control-panel">
<button id="toggle-topo-audio" class="audio-btn">
<span class="btn-icon">🔇</span>
<span id="btn-text" class="btn-text">MUTED - CLICK TO START RETRO BEAT</span>
</button>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-webgl-retro-topo');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return;
const vertices = new Float32Array([
-1, -1, 1, -1, -1, 1,
-1, 1, 1, -1, 1, 1
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vsSource = \`
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
\`;
const fsSource = \`
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_audio_pulse;
uniform vec2 u_mouse;
float getHeight(vec2 p) {
float h = 0.0;
float t = u_time * 0.15;
float wave1 = sin(p.x * 0.25 + t) * cos(p.y * 0.22 - t * 0.8);
float wave2 = cos(p.x * 0.55 - t * 1.5) * sin(p.y * 0.45 + t);
float wave3 = sin(p.x * 1.15 + t * 2.0) * cos(p.y * 1.05 - t * 1.2) * 0.35;
h = wave1 * 1.5 + wave2 * 0.75 + wave3 * 0.2;
h += abs(sin(p.x * 0.15) * cos(p.y * 0.15)) * 1.8;
float pulseMod = 1.0 + u_audio_pulse * 1.6;
return h * pulseMod - 0.4;
}
vec3 raymarch(vec3 ro, vec3 rd, out float hitDist) {
float t = 0.01;
float max_dist = 28.0;
for (int i = 0; i < 64; i++) {
vec3 p = ro + rd * t;
float h = getHeight(p.xz);
if (p.y <= h) {
hitDist = t;
return p;
}
t += max(0.06, (p.y - h) * 0.45);
if (t > max_dist) break;
}
hitDist = -1.0;
return vec3(0.0);
}
void main() {
vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
vec2 shake = vec2(0.0);
if (u_audio_pulse > 0.01) {
shake = vec2(sin(u_time * 75.0), cos(u_time * 65.0)) * u_audio_pulse * 0.004;
}
p += shake;
vec3 ro = vec3(0.0, 3.6, u_time * 0.9);
vec2 m = u_mouse / u_resolution - 0.5;
ro.x += m.x * 6.0;
ro.y += m.y * 2.5;
float floorH = getHeight(ro.xz);
if (ro.y < floorH + 1.1) {
ro.y = floorH + 1.1;
}
vec3 rd = normalize(vec3(p.x, p.y - 0.12, 0.85));
float pitch = -0.3 + m.y * 0.12;
float cp = cos(pitch), sp = sin(pitch);
vec3 rdp = vec3(rd.x, rd.y * cp - rd.z * sp, rd.y * sp + rd.z * cp);
float yaw = m.x * 0.3;
float cy = cos(yaw), sy = sin(yaw);
vec3 rdy = vec3(rdp.x * cy - rdp.z * sy, rdp.y, rdp.x * sy + rdp.z * cy);
rd = normalize(rdy);
float hitDist;
vec3 hitPos = raymarch(ro, rd, hitDist);
vec3 color = vec3(0.003, 0.002, 0.006);
if (hitDist > 0.0) {
float gridFreq = 1.2;
vec2 gridPos = hitPos.xz * gridFreq;
float w = 0.02 + hitDist * 0.009;
float gx = abs(fract(gridPos.x - 0.5) - 0.5);
float gz = abs(fract(gridPos.y - 0.5) - 0.5);
float gridX = smoothstep(w, 0.0, gx);
float gridZ = smoothstep(w, 0.0, gz);
float gridFactor = max(gridX, gridZ);
float contourFreq = 2.0;
float cy = abs(fract(hitPos.y * contourFreq - 0.5) - 0.5);
float contourFactor = smoothstep(w * 0.6, 0.0, cy);
float lineGlow = max(gridFactor, contourFactor * 0.8);
float fog = exp(-hitDist * 0.08);
float heightRel = (hitPos.y + 0.8) / 3.0;
vec3 lowColor = vec3(0.0, 0.45, 1.0);
vec3 highColor = vec3(1.0, 0.0, 0.5);
vec3 gridColor = mix(lowColor, highColor, clamp(heightRel + sin(u_time * 0.4) * 0.15 + u_audio_pulse * 0.35, 0.0, 1.0));
gridColor += vec3(0.12, 0.18, 0.35) * u_audio_pulse;
color = mix(color, gridColor, lineGlow * fog);
float peakSheen = smoothstep(0.4, 2.0, hitPos.y) * 0.12 * fog;
color += vec3(0.0, 0.85, 1.0) * peakSheen;
} else {
float horizonGlow = smoothstep(0.15, -0.22, rd.y) * 0.28;
color += vec3(0.45, 0.0, 0.75) * horizonGlow;
vec3 sunPos = normalize(vec3(0.0, 0.04, 1.0));
float dotSun = dot(rd, sunPos);
if (dotSun > 0.0) {
float sunRadius = 0.17;
float sunGlow = exp(-(1.0 - dotSun) * 55.0);
float sunMask = smoothstep(sunRadius + 0.008, sunRadius, 1.0 - dotSun);
float stripes = step(0.16, fract(rd.y * 38.0));
sunMask *= mix(1.0, stripes, smoothstep(-0.06, 0.08, rd.y));
vec3 sunColor = mix(vec3(1.0, 0.0, 0.45), vec3(1.0, 0.75, 0.0), rd.y * 4.5 + 0.25);
color += sunColor * (sunMask + sunGlow * 0.35);
}
}
float scanline = sin(gl_FragCoord.y * 2.2) * 0.025;
color -= vec3(scanline);
gl_FragColor = vec4(color, 1.0);
}
\`;
const vs = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vs || !fs) return;
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;
gl.useProgram(program);
const positionLoc = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
const uResolution = gl.getUniformLocation(program, 'u_resolution');
const uTime = gl.getUniformLocation(program, 'u_time');
const uAudioPulse = gl.getUniformLocation(program, 'u_audio_pulse');
const uMouse = gl.getUniformLocation(program, 'u_mouse');
let mouseX = 0, mouseY = 0;
window.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = rect.height - (e.clientY - rect.top);
});
// Web Audio Synthesizer Logic
let audioCtx = null;
let analyser = null;
let dataArray = null;
let synthInterval = null;
let isPlaying = false;
let masterGain = null;
let step = 0;
function initAudio() {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 32;
dataArray = new Uint8Array(analyser.frequencyBinCount);
masterGain = audioCtx.createGain();
masterGain.gain.setValueAtTime(0.0, audioCtx.currentTime);
masterGain.connect(analyser);
analyser.connect(audioCtx.destination);
// Smooth transition in gain
masterGain.gain.linearRampToValueAtTime(1.0, audioCtx.currentTime + 1.2);
}
function playSynthwave() {
if (synthInterval) clearInterval(synthInterval);
synthInterval = setInterval(() => {
if (!isPlaying || !audioCtx) return;
if (audioCtx.state === 'suspended') return;
const now = audioCtx.currentTime;
// KICK DRUM on even beats
if (step % 2 === 0) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(125, now);
osc.frequency.exponentialRampToValueAtTime(0.01, now + 0.28);
gain.gain.setValueAtTime(0.85, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.25);
osc.connect(gain);
gain.connect(masterGain);
osc.start(now);
osc.stop(now + 0.28);
}
// SYNTH RESONANT BASSLINE
const notes = [55.0, 55.0, 65.4, 48.9, 58.2, 58.2, 65.4, 48.9];
const currentNote = notes[step % notes.length];
const bassOsc = audioCtx.createOscillator();
const bassFilter = audioCtx.createBiquadFilter();
const bassGain = audioCtx.createGain();
bassOsc.type = 'sawtooth';
bassOsc.frequency.setValueAtTime(currentNote, now);
bassFilter.type = 'lowpass';
bassFilter.frequency.setValueAtTime(280, now);
bassFilter.Q.setValueAtTime(6.0, now);
bassFilter.frequency.exponentialRampToValueAtTime(950, now + 0.08);
bassFilter.frequency.exponentialRampToValueAtTime(180, now + 0.38);
bassGain.gain.setValueAtTime(0.14, now);
bassGain.gain.linearRampToValueAtTime(0.01, now + 0.42);
bassOsc.connect(bassFilter);
bassFilter.connect(bassGain);
bassGain.connect(masterGain);
bassOsc.start(now);
bassOsc.stop(now + 0.42);
step = (step + 1) % 8;
}, 380);
}
const audioToggleBtn = document.getElementById('toggle-topo-audio');
const btnText = document.getElementById('btn-text');
const btnIcon = audioToggleBtn.querySelector('.btn-icon');
audioToggleBtn.addEventListener('click', async () => {
if (!audioCtx) {
initAudio();
}
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
if (!isPlaying) {
isPlaying = true;
playSynthwave();
btnText.innerText = "BEAT ACTIVE - CLICK TO MUTE";
btnIcon.innerText = "🔊";
audioToggleBtn.classList.add('active');
} else {
isPlaying = false;
if (synthInterval) {
clearInterval(synthInterval);
synthInterval = null;
}
btnText.innerText = "MUTED - CLICK TO START RETRO BEAT";
btnIcon.innerText = "🔇";
audioToggleBtn.classList.remove('active');
}
});
function resize() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
const width = rect ? rect.width : window.innerWidth;
const height = rect ? rect.height : window.innerHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
gl.viewport(0, 0, width, height);
}
}
window.addEventListener('resize', resize);
if (canvas.parentNode) {
const observer = new MutationObserver(resize);
observer.observe(canvas.parentNode, { attributes: true });
}
resize();
let startTime = Date.now();
let audioPulse = 0.0;
function render() {
const now = Date.now();
let elapsed = (now - startTime) / 1000.0;
if (analyser && isPlaying) {
analyser.getByteFrequencyData(dataArray);
let sum = 0;
for (let i = 0; i < dataArray.length; i++) {
sum += dataArray[i];
}
const average = sum / dataArray.length;
audioPulse = average / 255.0;
} else {
audioPulse = (Math.sin(elapsed * 2.8) * 0.5 + 0.5) * 0.07;
}
gl.uniform2f(uResolution, canvas.width, canvas.height);
gl.uniform1f(uTime, elapsed);
gl.uniform1f(uAudioPulse, audioPulse);
gl.uniform2f(uMouse, mouseX, mouseY);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('beforeunload', () => {
if (synthInterval) clearInterval(synthInterval);
if (audioCtx) audioCtx.close();
});
})();
</script>
` }}
/>
</div>
);
}Raw CSS Stylesheet Snippet
.retro-topo-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#canvas-webgl-retro-topo {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.audio-control-panel {
position: absolute;
bottom: 24px;
right: 24px;
z-index: 50;
pointer-events: auto;
}
.audio-btn {
background: rgba(11, 11, 15, 0.75);
border: 1px solid rgba(255, 0, 128, 0.35);
box-shadow: 0 4px 20px rgba(255, 0, 128, 0.15);
border-radius: 12px;
color: #fff;
padding: 10px 16px;
font-family: monospace;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(8px);
}
.audio-btn:hover {
background: rgba(255, 0, 128, 0.15);
border-color: rgba(255, 0, 128, 0.8);
box-shadow: 0 4px 25px rgba(255, 0, 128, 0.3);
transform: translateY(-2px);
}
.audio-btn:active {
transform: translateY(0);
}
.audio-btn.active {
border-color: rgba(0, 240, 255, 0.85);
box-shadow: 0 4px 25px rgba(0, 240, 255, 0.35);
}
.audio-btn.active:hover {
background: rgba(0, 240, 255, 0.15);
}