WAVES - VANTAJS PROCEDURAL SHADER SOURCE CODE
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#000000" />
<title>VantaJS: Waves</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@0.5.24/dist/vanta.waves.min.js"></script>
<style>
:root {
--ui-fg: #ffffff;
--ui-line: rgba(255, 255, 255, 0.32);
--ui-panel: rgba(8, 12, 18, 0.58);
--ui-hot: rgba(255, 255, 255, 0.16);
--ui-glow: rgba(20, 136, 204, 0.45);
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Courier New", monospace;
--safe-b: max(16px, env(safe-area-inset-bottom));
--safe-l: max(16px, env(safe-area-inset-left));
--safe-r: max(16px, env(safe-area-inset-right));
}
body.retro {
--ui-fg: #8affa0;
--ui-line: rgba(138, 255, 160, 0.55);
--ui-panel: rgba(2, 14, 6, 0.72);
--ui-hot: rgba(138, 255, 160, 0.16);
--ui-glow: rgba(138, 255, 160, 0.4);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
overflow: hidden;
overscroll-behavior: none;
background: #000;
font-family: var(--mono);
-webkit-tap-highlight-color: transparent;
-webkit-text-size-adjust: 100%;
}
#vanta {
position: fixed;
inset: 0;
touch-action: none;
background: #000;
filter: none;
transition: filter 0.6s ease, background-color 0.6s ease;
}
body.retro #vanta {
filter: grayscale(1) sepia(1) hue-rotate(75deg) saturate(6) brightness(0.9) contrast(1.25);
}
.crt {
position: fixed;
inset: 0;
pointer-events: none;
opacity: 0;
z-index: 5;
transition: opacity 0.6s ease;
}
body.retro .crt { opacity: 1; }
.grain {
position: fixed;
top: -50%; left: -50%;
width: 200%; height: 200%;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
opacity: 0.05;
mix-blend-mode: overlay;
animation: grainMove 0.5s steps(3) infinite;
}
.scanlines {
position: fixed;
inset: 0;
background: repeating-linear-gradient(
to bottom,
rgba(0,0,0,0) 0px,
rgba(0,0,0,0) 1px,
rgba(0,0,0,0.35) 2px,
rgba(0,0,0,0.35) 3px
);
mix-blend-mode: multiply;
}
.beam {
position: fixed;
left: 0; right: 0;
top: -3px;
height: 3px;
background: linear-gradient(to right, transparent, rgba(140,255,170,0.55), transparent);
filter: blur(1px);
mix-blend-mode: screen;
animation: sweep 9s linear infinite;
}
.vignette {
position: fixed;
inset: 0;
background: radial-gradient(ellipse at center, rgba(0,0,0,0) 50%, rgba(0,0,0,0.7) 100%);
}
.flicker {
position: fixed;
inset: 0;
animation: flicker 6s infinite;
}
@keyframes sweep { 0% { top: -3px; } 100% { top: 100%; } }
@keyframes grainMove {
0% { transform: translate(0, 0); }
33% { transform: translate(-4%, 5%); }
66% { transform: translate(3%, -4%); }
100% { transform: translate(0, 0); }
}
@keyframes flicker {
0%, 100% { background: rgba(0,0,0,0); }
92% { background: rgba(0,0,0,0); }
93% { background: rgba(120,255,160,0.05); }
94% { background: rgba(0,0,0,0.08); }
95% { background: rgba(120,255,160,0.03); }
96% { background: rgba(0,0,0,0); }
}
.controls {
position: fixed;
z-index: 20;
left: var(--safe-l);
right: var(--safe-r);
bottom: var(--safe-b);
display: flex;
align-items: flex-end;
justify-content: flex-end;
pointer-events: none;
}
.controls > * { pointer-events: auto; }
button {
font-family: var(--mono);
color: var(--ui-fg);
background: var(--ui-panel);
border: 1px solid var(--ui-line);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
cursor: pointer;
touch-action: manipulation;
transition: background 0.25s ease, color 0.25s ease,
border-color 0.25s ease, box-shadow 0.25s ease, transform 0.2s ease;
}
button:focus-visible {
outline: 2px solid var(--ui-fg);
outline-offset: 3px;
}
#retro-btn {
min-height: 48px;
padding: 0 20px;
border-radius: 999px;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
display: inline-flex;
align-items: center;
gap: 10px;
box-shadow: 0 0 28px var(--ui-glow);
}
#retro-btn .lamp {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
opacity: 0.35;
transition: opacity 0.25s ease, box-shadow 0.25s ease;
}
body.retro #retro-btn .lamp {
opacity: 1;
box-shadow: 0 0 10px currentColor;
}
#retro-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
box-shadow: none;
}
@media (hover: hover) {
.controls button:hover:not(:disabled) { background: var(--ui-hot); transform: translateY(-1px); }
#retro-btn:hover:not(:disabled) { box-shadow: 0 0 46px var(--ui-glow); }
}
button:active:not(:disabled) { transform: scale(0.96); }
#notice {
position: fixed;
z-index: 20;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: min(440px, calc(100vw - 32px));
padding: 18px 20px;
border-radius: 12px;
border: 1px solid var(--ui-line);
background: var(--ui-panel);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: var(--ui-fg);
font-size: 13px;
line-height: 1.6;
text-align: center;
}
#notice[hidden] { display: none; }
@media (max-width: 560px) {
#retro-btn { padding: 0 16px; font-size: 12px; }
}
@media (max-width: 340px) {
#retro-btn { padding: 0 12px; letter-spacing: 0.06em; }
}
@media (prefers-reduced-motion: reduce) {
.grain, .beam, .flicker { animation: none; }
* { transition-duration: 0.01ms !important; }
}
</style>
</head>
<body>
<div id="vanta"></div>
<div class="crt" aria-hidden="true">
<div class="grain"></div>
<div class="scanlines"></div>
<div class="beam"></div>
<div class="vignette"></div>
<div class="flicker"></div>
</div>
<p id="notice" role="status" hidden></p>
<div class="controls">
<button id="retro-btn" type="button" aria-pressed="false">
<span class="lamp" aria-hidden="true"></span>
<span id="retro-label">Retroscope</span>
</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
var COMMON = {
el: "#vanta",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00
};
var DEF = {
id: 'waves',
label: 'Waves',
ctor: 'WAVES',
base: {
scale: 1.00, scaleMobile: 1.00,
shininess: 150.00, waveHeight: 30.50, waveSpeed: 1.65, zoom: 1.16
},
retro: { color: 0x0d3a1f },
bg: { normal: '#000000', retro: '#04120a' }
};
var vantaEl = document.getElementById('vanta');
var retroBtn = document.getElementById('retro-btn');
var retroLbl = document.getElementById('retro-label');
var notice = document.getElementById('notice');
var effect = null;
var normalResolved = null;
var isRetro = false;
(function () {
var NOISE = 'THREE.BufferAttribute: .length has been deprecated';
var warn = console.warn.bind(console);
console.warn = function (msg) {
if (typeof msg === 'string' && msg.indexOf(NOISE) === 0) return;
warn.apply(null, arguments);
};
})();
function fail(message) {
notice.textContent = message;
notice.hidden = false;
retroBtn.disabled = true;
}
function mount() {
if (typeof VANTA === 'undefined' || !VANTA[DEF.ctor]) {
fail(DEF.label + " didn't load. Check your connection and reload the page.");
return;
}
if (DEF.needsP5 && typeof window.p5 === 'undefined') {
fail(DEF.label + " needs p5.js, which didn't load. Check your connection and reload the page.");
return;
}
try {
effect = VANTA[DEF.ctor](Object.assign({}, COMMON, DEF.base));
} catch (err) {
console.error('[waves] failed to start:', err);
effect = null;
fail(DEF.label + " couldn't start. Your browser may not support WebGL.");
return;
}
normalResolved = DEF.normal || Object.keys(DEF.retro).reduce(function (acc, k) {
acc[k] = effect.options[k];
return acc;
}, {});
applyMode(true);
}
function applyMode(justMounted) {
document.body.classList.toggle('retro', isRetro);
if (effect) {
effect.setOptions(isRetro ? DEF.retro : normalResolved);
if (DEF.restartOnPalette && !(justMounted && !isRetro)) {
try { effect.restart(); } catch (err) {
console.error('[waves] restart failed:', err);
}
}
vantaEl.style.backgroundColor = isRetro ? DEF.bg.retro : DEF.bg.normal;
}
retroLbl.textContent = isRetro ? 'Normal' : 'Retroscope';
retroBtn.setAttribute('aria-pressed', String(isRetro));
}
retroBtn.addEventListener('click', function () {
isRetro = !isRetro;
applyMode();
});
window.addEventListener('pagehide', function () {
if (effect) {
try { effect.destroy(); } catch (err) {}
effect = null;
}
});
mount();
});
</script>
</body>
</html>