Files
Website-Fa/Shader Wallpapers.html
2026-05-18 20:14:54 +00:00

906 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aurora · Shader Wallpapers</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--ink: #ffffff;
--dim: rgba(255,255,255,0.55);
--faint: rgba(255,255,255,0.25);
--mono: 'JetBrains Mono', ui-monospace, monospace;
--sans: 'Inter', system-ui, sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; color: var(--ink); font-family: var(--sans); }
canvas#gl { position: fixed; inset: 0; width: 100%; height: 100%; display: block; cursor: crosshair; }
/* ── TOP-LEFT INDEX ── */
.top-left {
position: fixed; top: 28px; left: 32px; z-index: 10;
display: flex; flex-direction: column; gap: 6px;
pointer-events: none;
mix-blend-mode: difference;
}
.top-left .kicker {
font-family: var(--mono); font-size: 10px;
letter-spacing: 3px; text-transform: uppercase;
color: var(--dim);
}
.top-left .title {
font-family: var(--sans); font-weight: 300;
font-size: clamp(28px, 3.4vw, 44px);
letter-spacing: -1.2px; line-height: 1;
color: var(--ink);
}
.top-left .title .accent { font-style: italic; font-weight: 400; }
.top-left .meta {
font-family: var(--mono); font-size: 10px;
letter-spacing: 1.5px; color: var(--faint);
margin-top: 14px;
}
/* ── TOP-RIGHT INFO ── */
.top-right {
position: fixed; top: 28px; right: 32px; z-index: 10;
display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
mix-blend-mode: difference; pointer-events: none;
}
.badge {
display: inline-flex; align-items: center; gap: 8px;
font-family: var(--mono); font-size: 10px;
letter-spacing: 2px; text-transform: uppercase;
color: var(--ink);
padding: 6px 12px;
border: 0.5px solid rgba(255,255,255,0.3);
background: rgba(0,0,0,0.001);
}
.badge .dot {
width: 6px; height: 6px; border-radius: 50%;
background: #66ffaa;
box-shadow: 0 0 8px #66ffaa;
animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: 0.3; } }
.hint {
font-family: var(--mono); font-size: 10px;
color: var(--dim); letter-spacing: 1px;
}
.hint kbd {
display: inline-block;
padding: 2px 6px;
border: 0.5px solid rgba(255,255,255,0.4);
font-family: var(--mono); font-size: 9px;
margin: 0 2px; color: var(--ink);
}
/* ── BOTTOM DOCK / SWITCHER ── */
.dock {
position: fixed; bottom: 28px; left: 50%;
transform: translateX(-50%);
z-index: 10;
display: flex; gap: 1px;
background: rgba(255,255,255,0.08);
padding: 1px;
backdrop-filter: blur(20px) saturate(140%);
-webkit-backdrop-filter: blur(20px) saturate(140%);
border: 0.5px solid rgba(255,255,255,0.18);
border-radius: 2px;
}
.dock-btn {
background: rgba(0,0,0,0.55);
border: none;
color: var(--dim);
font-family: var(--mono);
font-size: 10px;
letter-spacing: 2.5px;
text-transform: uppercase;
padding: 14px 22px;
cursor: pointer;
transition: color 0.25s, background 0.25s;
position: relative;
display: flex; align-items: center; gap: 10px;
}
.dock-btn::before {
content: ''; display: block; width: 6px; height: 6px;
background: currentColor; opacity: 0.5;
transition: opacity 0.25s, transform 0.25s;
}
.dock-btn[data-shape="circle"]::before { border-radius: 50%; }
.dock-btn[data-shape="diamond"]::before { transform: rotate(45deg); }
.dock-btn[data-shape="bar"]::before { width: 10px; height: 2px; }
.dock-btn[data-shape="ring"]::before { background: transparent; border: 1px solid currentColor; border-radius: 50%; }
.dock-btn[data-shape="cross"]::before {
width: 8px; height: 8px;
background: linear-gradient(currentColor, currentColor) center/100% 1px no-repeat,
linear-gradient(currentColor, currentColor) center/1px 100% no-repeat;
}
.dock-btn:hover { color: var(--ink); background: rgba(0,0,0,0.7); }
.dock-btn:hover::before { opacity: 1; }
.dock-btn.active { color: var(--ink); background: rgba(0,0,0,0.85); }
.dock-btn.active::before { opacity: 1; transform: scale(1.3); }
.dock-btn[data-shape="diamond"].active::before { transform: rotate(45deg) scale(1.3); }
/* ── BOTTOM-LEFT STATS ── */
.bottom-left {
position: fixed; bottom: 28px; left: 32px; z-index: 10;
display: flex; flex-direction: column; gap: 6px;
font-family: var(--mono); font-size: 10px;
letter-spacing: 1.5px;
mix-blend-mode: difference;
color: var(--dim);
pointer-events: none;
}
.bottom-left .row { display: flex; gap: 14px; }
.bottom-left .row .k { color: var(--faint); }
.bottom-left .row .v { color: var(--ink); min-width: 50px; }
/* ── BOTTOM-RIGHT FPS / DESC ── */
.bottom-right {
position: fixed; bottom: 28px; right: 32px; z-index: 10;
text-align: right; max-width: 320px;
mix-blend-mode: difference;
pointer-events: none;
}
.desc-name {
font-family: var(--sans); font-weight: 500;
font-size: 13px; color: var(--ink);
letter-spacing: 0.5px; margin-bottom: 4px;
}
.desc-body {
font-family: var(--mono); font-size: 10px;
color: var(--dim); line-height: 1.6;
letter-spacing: 0.5px;
}
/* ── INTRO PULSE ON SWITCH ── */
.name-flash {
position: fixed; top: 50%; left: 50%;
transform: translate(-50%, -50%);
pointer-events: none; z-index: 20;
font-family: var(--sans); font-weight: 200;
font-size: clamp(60px, 12vw, 180px);
letter-spacing: -4px; color: rgba(255,255,255,0.95);
mix-blend-mode: difference;
opacity: 0; transition: opacity 0.4s, transform 0.6s;
}
.name-flash.show { opacity: 1; }
@media (max-width: 720px) {
.dock { flex-wrap: wrap; max-width: calc(100vw - 40px); }
.dock-btn { padding: 12px 14px; font-size: 9px; }
.top-right .hint, .bottom-right { display: none; }
.bottom-left { display: none; }
.top-left .title { font-size: 24px; }
}
</style>
</head>
<body>
<canvas id="gl"></canvas>
<!-- TOP LEFT -->
<div class="top-left">
<div class="kicker">Frontier Algorithmics · Lab</div>
<div class="title">Aurora<span class="accent"> · Field</span></div>
<div class="meta" id="meta-coord">42.0235 N · 11.1812 E</div>
</div>
<!-- TOP RIGHT -->
<div class="top-right">
<div class="badge"><span class="dot"></span><span>Live · GLSL</span></div>
<div class="hint">Move <kbd>cursor</kbd> · Click for ripple</div>
</div>
<!-- BOTTOM LEFT -->
<div class="bottom-left">
<div class="row"><span class="k">FPS</span><span class="v" id="fps">--</span></div>
<div class="row"><span class="k">CURSOR</span><span class="v" id="curs">0,0</span></div>
<div class="row"><span class="k">SHADER</span><span class="v" id="shname">aurora</span></div>
</div>
<!-- BOTTOM DOCK -->
<div class="dock" id="dock">
<button class="dock-btn active" data-shape="diamond" data-shader="aurora">Aurora</button>
<button class="dock-btn" data-shape="circle" data-shader="plasma">Plasma</button>
<button class="dock-btn" data-shape="ring" data-shader="mercury">Mercury</button>
<button class="dock-btn" data-shape="cross" data-shader="crystal">Crystal</button>
<button class="dock-btn" data-shape="bar" data-shader="waves">Waves</button>
</div>
<!-- BOTTOM RIGHT -->
<div class="bottom-right">
<div class="desc-name" id="desc-name">Aurora Field</div>
<div class="desc-body" id="desc-body">FBM-warped ribbon bands drifting over a deep-night gradient. Cursor displaces field, click discharges a luminous burst.</div>
</div>
<!-- FULLSCREEN FLASH -->
<div class="name-flash" id="flash">aurora</div>
<!-- ─────────── VERTEX SHADER (shared) ─────────── -->
<script id="vs" type="x-shader/x-vertex">
attribute vec2 a_pos;
void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }
</script>
<!-- ─────────── COMMON FRAGMENT HEADER ─────────── -->
<script id="frag-header" type="x-shader/x-fragment">
precision highp float;
uniform vec2 uRes;
uniform float uTime;
uniform vec2 uMouse; // pixels, top-left origin
uniform vec2 uMouseSmooth; // eased mouse
uniform vec4 uClicks[6]; // xy = pos px, z = startTime, w = active flag
uniform float uShaderTime; // time since this shader was activated
// ── Hash & noise utilities ──
float hash21(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
vec2 hash22(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xx + p3.yz) * p3.zy);
}
float vnoise(vec2 p) {
vec2 i = floor(p), f = fract(p);
vec2 u = f*f*(3.0-2.0*f);
float a = hash21(i);
float b = hash21(i + vec2(1.0, 0.0));
float c = hash21(i + vec2(0.0, 1.0));
float d = hash21(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float fbm(vec2 p) {
float v = 0.0, a = 0.5;
mat2 rot = mat2(0.8, -0.6, 0.6, 0.8);
for (int i = 0; i < 5; i++) {
v += a * vnoise(p);
p = rot * p * 2.02;
a *= 0.5;
}
return v;
}
// Click ripple decay (returns 0..1, peaks then decays)
float clickRipple(vec2 frag, vec4 click) {
if (click.w < 0.5) return 0.0;
float age = uTime - click.z;
if (age < 0.0 || age > 2.4) return 0.0;
float d = distance(frag, click.xy);
float radius = age * 600.0;
float thickness = 60.0 + age * 80.0;
float ring = exp(-pow((d - radius) / thickness, 2.0));
return ring * exp(-age * 1.3);
}
float clickEnergy(vec2 frag) {
float e = 0.0;
e += clickRipple(frag, uClicks[0]);
e += clickRipple(frag, uClicks[1]);
e += clickRipple(frag, uClicks[2]);
e += clickRipple(frag, uClicks[3]);
e += clickRipple(frag, uClicks[4]);
e += clickRipple(frag, uClicks[5]);
return e;
}
</script>
<!-- ─────────── 1. AURORA ─────────── -->
<script id="fs-aurora" type="x-shader/x-fragment">
void main() {
vec2 frag = gl_FragCoord.xy;
vec2 uv = frag / uRes.xy;
vec2 m = uMouseSmooth / uRes.xy;
// Deep-night background gradient (top: cold violet, bottom: black-teal)
vec3 sky = mix(
vec3(0.005, 0.01, 0.06),
vec3(0.04, 0.02, 0.10),
pow(1.0 - uv.y, 1.5)
);
// Stars
float sH = hash21(floor(frag));
float starMask = step(0.9985, sH);
float twinkle = 0.5 + 0.5 * sin(uTime * 2.0 + sH * 200.0);
sky += vec3(1.0) * starMask * twinkle * (1.0 - smoothstep(0.0, 0.55, uv.y));
// Aurora bands — multiple stacked, animated, mouse-displaced
vec3 aurora = vec3(0.0);
float mouseInf = exp(-distance(uv, m) * 2.2);
for (int i = 0; i < 5; i++) {
float fi = float(i);
float seed = fi * 17.3;
// Domain warp using FBM
vec2 q = vec2(uv.x * 1.6 + uTime * 0.08 + seed, uv.y * 0.9);
float n1 = fbm(q + vec2(0.0, uTime * 0.07));
float n2 = fbm(q * 1.5 + n1 + vec2(uTime * 0.05, 0.0));
// Base band centerline (varies with x)
float center = 0.18 + fi * 0.16 + sin(uTime * 0.25 + fi * 1.3) * 0.04;
center += (n1 - 0.5) * 0.28;
// Mouse pushes nearby bands upward
center += mouseInf * (m.y - center) * 0.25;
// Vertical profile: thin band that softens at top
float band = exp(-pow((uv.y - center) * (9.0 - fi * 0.8), 2.0));
// Vertical streak detail (the iconic vertical strands)
float strand = fbm(vec2(q.x * 18.0 + uTime * 0.2, q.y * 4.0 + n2 * 2.0));
strand = smoothstep(0.35, 0.85, strand);
band *= 0.55 + 0.7 * strand;
// Color shifts across bands and across x
vec3 cA = vec3(0.10, 0.95, 0.55); // emerald
vec3 cB = vec3(0.20, 0.45, 1.00); // cyan-blue
vec3 cC = vec3(0.90, 0.30, 0.85); // magenta
float mixA = sin(uTime * 0.3 + fi * 1.5 + uv.x * 1.2) * 0.5 + 0.5;
float mixB = cos(uTime * 0.22 + fi * 0.9) * 0.5 + 0.5;
vec3 bandCol = mix(mix(cA, cB, mixA), cC, mixB * 0.35);
aurora += band * bandCol * (1.1 - fi * 0.08);
}
// Reflective glow on lower edge
float ground = smoothstep(0.0, 0.25, uv.y);
aurora *= ground;
// Click ripple: brightens region, slight chromatic separation
float ce = clickEnergy(frag);
vec3 burst = vec3(0.5, 1.2, 1.5) * ce;
vec3 col = sky + aurora * 1.15 + burst;
// Subtle vignette
vec2 vuv = uv - 0.5;
col *= 1.0 - dot(vuv, vuv) * 0.6;
gl_FragColor = vec4(col, 1.0);
}
</script>
<!-- ─────────── 2. PLASMA SUN ─────────── -->
<script id="fs-plasma" type="x-shader/x-fragment">
void main() {
vec2 frag = gl_FragCoord.xy;
vec2 uv = (frag - 0.5 * uRes) / min(uRes.x, uRes.y);
vec2 m = (uMouseSmooth - 0.5 * uRes) / min(uRes.x, uRes.y);
// Orb center follows mouse, with slow return-to-center
vec2 center = m * 0.7;
vec2 p = uv - center;
float r = length(p);
float a = atan(p.y, p.x);
// Domain-warped corona using polar coords
float corona = 0.0;
for (int i = 0; i < 4; i++) {
float fi = float(i);
float swirl = fbm(vec2(a * (3.0 + fi) + uTime * (0.2 + fi * 0.1), r * 4.0 - uTime * 0.4));
corona += swirl / (fi + 1.0);
}
corona /= 1.8;
// Core
float core = exp(-pow(r * 3.5, 2.0));
// Outer corona pulsing
float outer = exp(-r * 2.8) * (0.6 + 0.4 * corona);
// Solar arc filaments
float fil = smoothstep(0.55, 0.85, fbm(vec2(a * 6.0, r * 8.0 + uTime * 0.5)));
fil *= smoothstep(0.05, 0.25, r) * smoothstep(0.7, 0.2, r);
// Background space
vec3 bg = mix(vec3(0.02, 0.005, 0.04), vec3(0.0, 0.0, 0.0), smoothstep(0.0, 1.2, r));
// Distant nebula
float neb = fbm(uv * 1.5 + uTime * 0.02);
bg += vec3(0.15, 0.05, 0.30) * neb * 0.18;
// Sun palette: deep orange → yellow → white core
vec3 sunCol = mix(
vec3(1.0, 0.25, 0.05), // outer red
vec3(1.0, 0.85, 0.4), // mid amber
smoothstep(0.0, 0.35, core + outer * 0.5)
);
sunCol = mix(sunCol, vec3(1.0, 1.0, 0.95), pow(core, 2.5));
// Color shift hue toward magenta when cursor far
float hue = clamp(length(m), 0.0, 1.0);
sunCol = mix(sunCol, sunCol.bgr * vec3(1.2, 0.6, 1.0), hue * 0.25);
vec3 col = bg
+ sunCol * core * 1.5
+ sunCol * outer * 1.0
+ vec3(1.0, 0.6, 0.2) * fil * 0.9;
// Click ripple → shockwave
float ce = clickEnergy(frag);
col += vec3(1.4, 1.0, 0.6) * ce * 1.5;
// Lens dust speckle
float dust = step(0.9992, hash21(floor(frag * 0.8)));
col += vec3(dust) * 0.6;
// Tonemap
col = col / (1.0 + col);
col = pow(col, vec3(0.92));
gl_FragColor = vec4(col, 1.0);
}
</script>
<!-- ─────────── 3. MERCURY (chrome metaballs) ─────────── -->
<script id="fs-mercury" type="x-shader/x-fragment">
// 2D metaball SDF approximation
float metaball(vec2 p, vec2 c, float r) {
return r / max(length(p - c), 0.0001);
}
void main() {
vec2 frag = gl_FragCoord.xy;
vec2 uv = (frag - 0.5 * uRes) / min(uRes.x, uRes.y);
vec2 m = (uMouseSmooth - 0.5 * uRes) / min(uRes.x, uRes.y);
// Five drifting metaballs + one tied to the cursor
vec2 b0 = vec2(sin(uTime * 0.31) * 0.45, cos(uTime * 0.27) * 0.32);
vec2 b1 = vec2(cos(uTime * 0.41) * 0.5, sin(uTime * 0.23) * 0.28);
vec2 b2 = vec2(sin(uTime * 0.19 + 2.0) * 0.55, cos(uTime * 0.35 + 1.0) * 0.4);
vec2 b3 = vec2(cos(uTime * 0.29 + 4.0) * 0.4, sin(uTime * 0.17 + 3.0) * 0.5);
vec2 b4 = m;
vec2 b5 = -m * 0.6;
float f = 0.0;
f += metaball(uv, b0, 0.18);
f += metaball(uv, b1, 0.20);
f += metaball(uv, b2, 0.14);
f += metaball(uv, b3, 0.16);
f += metaball(uv, b4, 0.26); // cursor-attached blob — biggest
f += metaball(uv, b5, 0.12);
// Smoothstep into a "surface"
float surface = smoothstep(2.4, 3.0, f);
float edge = smoothstep(2.0, 2.4, f) - smoothstep(2.4, 3.0, f);
// Approximate a normal by finite-differencing the field
vec2 e = vec2(0.0035, 0.0);
float fx = (
metaball(uv + e.xy, b0, 0.18) + metaball(uv + e.xy, b1, 0.20)
+ metaball(uv + e.xy, b2, 0.14) + metaball(uv + e.xy, b3, 0.16)
+ metaball(uv + e.xy, b4, 0.26) + metaball(uv + e.xy, b5, 0.12)
) - f;
float fy = (
metaball(uv + e.yx, b0, 0.18) + metaball(uv + e.yx, b1, 0.20)
+ metaball(uv + e.yx, b2, 0.14) + metaball(uv + e.yx, b3, 0.16)
+ metaball(uv + e.yx, b4, 0.26) + metaball(uv + e.yx, b5, 0.12)
) - f;
vec2 n = normalize(vec2(fx, fy) + vec2(1e-5));
// Iridescent fresnel based on "view angle" (here: how grazing the normal is)
float fres = 1.0 - abs(n.y);
fres = pow(fres, 1.5);
// Reflected environment: spectral bands as a function of normal angle
float ang = atan(n.y, n.x);
vec3 envA = vec3(0.20, 0.55, 1.0); // cyan
vec3 envB = vec3(0.95, 0.25, 0.85); // magenta
vec3 envC = vec3(0.30, 1.0, 0.60); // mint
vec3 envD = vec3(1.0, 0.8, 0.25); // gold
vec3 chrome = mix(envA, envB, sin(ang * 2.0 + uTime * 0.4) * 0.5 + 0.5);
chrome = mix(chrome, envC, sin(ang * 3.0 + uTime * 0.7) * 0.5 + 0.5 * 0.4);
chrome = mix(chrome, envD, fres);
// Specular highlight where normal points toward "light"
float spec = pow(max(dot(n, normalize(vec2(0.6, 0.8))), 0.0), 16.0);
chrome += vec3(spec) * 0.9;
// Background: subtle gradient with vignette
vec3 bg = mix(vec3(0.02, 0.03, 0.05), vec3(0.05, 0.02, 0.07), uv.y * 0.5 + 0.5);
// Soft glow under metaballs
bg += smoothstep(2.0, 3.0, f) * chrome * 0.15;
vec3 col = bg;
col = mix(col, chrome, surface);
col += edge * mix(envA, envB, sin(uTime * 0.6) * 0.5 + 0.5) * 0.6;
// Click → push surface
float ce = clickEnergy(frag);
col += vec3(0.9, 0.95, 1.0) * ce * 1.2;
// Tonemap
col = col / (1.0 + col * 0.6);
gl_FragColor = vec4(col, 1.0);
}
</script>
<!-- ─────────── 4. CRYSTAL (voronoi lattice) ─────────── -->
<script id="fs-crystal" type="x-shader/x-fragment">
// Voronoi distance to 1st and 2nd nearest
vec2 voronoi(vec2 p, out vec2 closestId) {
vec2 i = floor(p), f = fract(p);
float d1 = 1e9, d2 = 1e9;
vec2 ic = vec2(0.0);
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec2 g = vec2(float(x), float(y));
vec2 o = hash22(i + g);
o = 0.5 + 0.5 * sin(uTime * 0.6 + 6.28 * o);
vec2 r = g + o - f;
float d = dot(r, r);
if (d < d1) { d2 = d1; d1 = d; ic = i + g; }
else if (d < d2) { d2 = d; }
}
}
closestId = ic;
return vec2(sqrt(d1), sqrt(d2));
}
void main() {
vec2 frag = gl_FragCoord.xy;
vec2 uv = frag / min(uRes.x, uRes.y);
vec2 m = uMouseSmooth / min(uRes.x, uRes.y);
// Mouse warps the lattice (radial pull)
vec2 d = uv - m;
float warpStrength = 0.6 * exp(-length(d) * 2.0);
vec2 uvw = uv + d * warpStrength;
// Click pulses: temporary scale push
float ce = clickEnergy(frag);
uvw *= 1.0 - ce * 0.05;
vec2 ic;
vec2 v = voronoi(uvw * 4.0, ic);
float edge = v.y - v.x;
float cellId = hash21(ic);
// Cell base color — prismatic palette per cell
vec3 cA = vec3(0.05, 0.15, 0.40);
vec3 cB = vec3(0.30, 0.10, 0.50);
vec3 cC = vec3(0.10, 0.50, 0.55);
vec3 cD = vec3(0.55, 0.20, 0.35);
vec3 base = mix(mix(cA, cB, cellId), mix(cC, cD, fract(cellId * 7.1)), fract(cellId * 3.3));
// Inside-cell gradient — darken away from center
float inner = smoothstep(0.0, 0.6, v.x);
vec3 fill = base * (1.0 - inner * 0.75);
// Glowing prismatic edges
float edgeMask = 1.0 - smoothstep(0.0, 0.04, edge);
vec3 edgeCol = mix(vec3(0.4, 0.9, 1.0), vec3(1.0, 0.3, 0.8),
sin(uTime * 0.4 + cellId * 10.0) * 0.5 + 0.5);
// Hover highlight on cell under cursor
vec2 mIc;
voronoi(m * min(uRes.x, uRes.y) / min(uRes.x, uRes.y) * 4.0, mIc);
float hovered = step(distance(ic, mIc), 0.1);
edgeCol = mix(edgeCol, vec3(1.0, 1.0, 0.85), hovered * 0.8);
// Refractive caustic — fine secondary lines inside cells
float caustic = sin((v.x * 30.0) + uTime * 1.2 + cellId * 20.0);
caustic = smoothstep(0.4, 0.95, caustic) * (1.0 - edgeMask);
vec3 causticCol = mix(vec3(0.6, 0.8, 1.0), vec3(1.0, 0.7, 0.9), cellId);
vec3 col = fill + edgeCol * edgeMask * 1.4 + causticCol * caustic * 0.15;
// Hovered cell brightens overall
col += hovered * vec3(0.15, 0.18, 0.22) * (1.0 - edgeMask);
// Click → ripple of brightness
col += vec3(0.6, 0.9, 1.0) * ce * 0.9;
// Vignette
vec2 vuv = frag / uRes - 0.5;
col *= 1.0 - dot(vuv, vuv) * 0.7;
gl_FragColor = vec4(col, 1.0);
}
</script>
<!-- ─────────── 5. WAVES (interference) ─────────── -->
<script id="fs-waves" type="x-shader/x-fragment">
void main() {
vec2 frag = gl_FragCoord.xy;
vec2 uv = frag / uRes;
vec2 m = uMouseSmooth / uRes;
// Drifting wave sources (4 of them) + cursor source + click sources
vec2 sources[5];
sources[0] = vec2(0.2 + 0.05 * sin(uTime * 0.3), 0.3 + 0.04 * cos(uTime * 0.4));
sources[1] = vec2(0.75 + 0.06 * cos(uTime * 0.25), 0.25 + 0.05 * sin(uTime * 0.35));
sources[2] = vec2(0.3 + 0.07 * sin(uTime * 0.2 + 1.0), 0.78 + 0.04 * cos(uTime * 0.22));
sources[3] = vec2(0.78 + 0.05 * cos(uTime * 0.28 + 2.0), 0.72 + 0.06 * sin(uTime * 0.31));
sources[4] = m;
float field = 0.0;
for (int i = 0; i < 5; i++) {
float dist = distance(uv, sources[i]);
// Wave with falloff so sources don't dominate at distance
float falloff = 1.0 / (1.0 + dist * 4.0);
float speed = (i == 4) ? 5.0 : 3.5 + float(i) * 0.4;
float freq = (i == 4) ? 55.0 : 36.0 + float(i) * 4.0;
field += sin(dist * freq - uTime * speed) * falloff;
}
// Click sources add expanding wavefronts
for (int i = 0; i < 6; i++) {
if (uClicks[i].w < 0.5) continue;
float age = uTime - uClicks[i].z;
if (age < 0.0 || age > 4.0) continue;
vec2 cp = uClicks[i].xy / uRes;
float d = distance(uv, cp);
float radius = age * 0.35;
float thickness = 0.04 + age * 0.05;
float front = exp(-pow((d - radius) / thickness, 2.0));
field += front * exp(-age * 0.7) * 3.0;
}
// Color from field amplitude
float v = field * 0.18;
vec3 cDeep = vec3(0.02, 0.04, 0.12);
vec3 cMid = vec3(0.20, 0.55, 0.95);
vec3 cHigh = vec3(0.85, 0.95, 1.00);
vec3 cCrest = vec3(1.0, 0.8, 0.5);
vec3 col = cDeep;
col = mix(col, cMid, smoothstep(-0.2, 0.4, v));
col = mix(col, cHigh, smoothstep(0.4, 0.8, v));
col = mix(col, cCrest, smoothstep(0.8, 1.2, v));
// Iso-lines for that schlieren / oscilloscope look
float iso = abs(fract(field * 1.0) - 0.5);
float lines = 1.0 - smoothstep(0.0, 0.03, iso);
col += lines * vec3(0.05, 0.15, 0.25);
// Subtle radial highlight at cursor source
float curDist = distance(uv, m);
col += exp(-curDist * 10.0) * vec3(0.4, 0.6, 1.0) * 0.3;
// Vignette
vec2 vuv = uv - 0.5;
col *= 1.0 - dot(vuv, vuv) * 0.5;
gl_FragColor = vec4(col, 1.0);
}
</script>
<!-- ─────────── RENDERER ─────────── -->
<script>
(function() {
const canvas = document.getElementById('gl');
const gl = canvas.getContext('webgl', { antialias: false, alpha: false, preserveDrawingBuffer: false });
if (!gl) {
document.body.innerHTML = '<div style="color:#fff;font-family:monospace;padding:40px;">WebGL not available.</div>';
return;
}
const SHADERS = ['aurora','plasma','mercury','crystal','waves'];
const DESC = {
aurora: { name: 'Aurora Field', body: 'FBM-warped ribbon bands drifting over a deep-night gradient. Cursor displaces field, click discharges a luminous burst.' },
plasma: { name: 'Plasma Sun', body: 'A breathing star anchored to the cursor. Corona swirls via polar-domain noise; click triggers a solar shockwave.' },
mercury: { name: 'Liquid Mercury', body: 'Iridescent metaball field with one blob attached to the cursor. Chrome reflections shift by surface angle.' },
crystal: { name: 'Crystal Lattice', body: 'Animated Voronoi cells with prismatic edges. Cursor warps the lattice radially; the cell beneath it highlights.' },
waves: { name: 'Wave Interference',body: 'Superposed sine emitters with the cursor as a roaming source. Click drops a real wavefront that propagates outward.' },
};
// Build vertex shader once
const VS = document.getElementById('vs').textContent;
const HEADER = document.getElementById('frag-header').textContent;
function compile(type, src) {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(s), src);
return null;
}
return s;
}
function makeProgram(fsSrc) {
const vs = compile(gl.VERTEX_SHADER, VS);
const fs = compile(gl.FRAGMENT_SHADER, HEADER + '\n' + fsSrc);
if (!vs || !fs) return null;
const p = gl.createProgram();
gl.attachShader(p, vs); gl.attachShader(p, fs);
gl.linkProgram(p);
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
console.error('Link error:', gl.getProgramInfoLog(p));
return null;
}
return p;
}
// Fullscreen quad
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1,-1, 1,-1, -1, 1,
-1, 1, 1,-1, 1, 1,
]), gl.STATIC_DRAW);
// Compile all programs lazily
const programs = {};
const uniformCache = {};
function getProgram(name) {
if (programs[name]) return programs[name];
const fsEl = document.getElementById('fs-' + name);
if (!fsEl) return null;
const p = makeProgram(fsEl.textContent);
programs[name] = p;
uniformCache[name] = {
uRes: gl.getUniformLocation(p, 'uRes'),
uTime: gl.getUniformLocation(p, 'uTime'),
uMouse: gl.getUniformLocation(p, 'uMouse'),
uMouseSmooth: gl.getUniformLocation(p, 'uMouseSmooth'),
uClicks: gl.getUniformLocation(p, 'uClicks[0]'),
uShaderTime: gl.getUniformLocation(p, 'uShaderTime'),
a_pos: gl.getAttribLocation(p, 'a_pos'),
};
return p;
}
// Resize
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const w = Math.floor(window.innerWidth * dpr);
const h = Math.floor(window.innerHeight * dpr);
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w; canvas.height = h;
gl.viewport(0, 0, w, h);
}
}
window.addEventListener('resize', resize);
resize();
// Mouse state
const dprFor = () => Math.min(window.devicePixelRatio || 1, 2);
const mouse = { x: window.innerWidth * 0.5, y: window.innerHeight * 0.5 };
const mouseSmooth = { x: mouse.x, y: mouse.y };
window.addEventListener('mousemove', e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
window.addEventListener('touchmove', e => {
if (e.touches.length) {
mouse.x = e.touches[0].clientX;
mouse.y = e.touches[0].clientY;
}
}, { passive: true });
// Clicks — keep last 6, each vec4(x, y, startTime, active)
const clicks = new Float32Array(6 * 4);
let clickIdx = 0;
function recordClick(x, y) {
const base = clickIdx * 4;
clicks[base + 0] = x;
clicks[base + 1] = y;
clicks[base + 2] = (performance.now() - startMs) / 1000;
clicks[base + 3] = 1.0;
clickIdx = (clickIdx + 1) % 6;
}
canvas.addEventListener('mousedown', e => {
const dpr = dprFor();
recordClick(e.clientX * dpr, (window.innerHeight - e.clientY) * dpr);
});
canvas.addEventListener('touchstart', e => {
if (e.touches.length) {
const dpr = dprFor();
recordClick(e.touches[0].clientX * dpr, (window.innerHeight - e.touches[0].clientY) * dpr);
}
}, { passive: true });
// UI / switching
const dock = document.getElementById('dock');
const fpsEl = document.getElementById('fps');
const cursEl = document.getElementById('curs');
const shNameEl = document.getElementById('shname');
const descName = document.getElementById('desc-name');
const descBody = document.getElementById('desc-body');
const titleEl = document.querySelector('.top-left .title');
const flashEl = document.getElementById('flash');
let current = 'aurora';
let shaderStartMs = performance.now();
function setShader(name) {
if (!SHADERS.includes(name) || name === current) {
// still allow refresh of UI
if (name !== current) return;
}
current = name;
shaderStartMs = performance.now();
getProgram(name); // compile if needed
// UI
dock.querySelectorAll('.dock-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.shader === name);
});
shNameEl.textContent = name;
descName.textContent = DESC[name].name;
descBody.textContent = DESC[name].body;
titleEl.innerHTML = DESC[name].name.split(' ')[0] +
'<span class="accent"> · ' + (DESC[name].name.split(' ')[1] || 'Field') + '</span>';
// Flash overlay
flashEl.textContent = name;
flashEl.classList.remove('show');
// force reflow
void flashEl.offsetWidth;
flashEl.classList.add('show');
setTimeout(() => flashEl.classList.remove('show'), 650);
}
dock.querySelectorAll('.dock-btn').forEach(btn => {
btn.addEventListener('click', () => setShader(btn.dataset.shader));
});
// Keyboard shortcuts 15
window.addEventListener('keydown', e => {
const i = parseInt(e.key, 10);
if (i >= 1 && i <= 5) setShader(SHADERS[i - 1]);
});
// Prime aurora
getProgram('aurora');
// Render loop
const startMs = performance.now();
let lastFrame = startMs;
let fpsAccum = 0, fpsFrames = 0, fpsLast = startMs;
function frame() {
const now = performance.now();
const dt = (now - lastFrame) / 1000;
lastFrame = now;
const t = (now - startMs) / 1000;
const tShader = (now - shaderStartMs) / 1000;
// FPS
fpsAccum += dt; fpsFrames++;
if (now - fpsLast > 500) {
fpsEl.textContent = (fpsFrames / fpsAccum).toFixed(0);
fpsAccum = 0; fpsFrames = 0; fpsLast = now;
}
// Smooth mouse
mouseSmooth.x += (mouse.x - mouseSmooth.x) * Math.min(1, dt * 6);
mouseSmooth.y += (mouse.y - mouseSmooth.y) * Math.min(1, dt * 6);
cursEl.textContent = Math.round(mouse.x) + ',' + Math.round(mouse.y);
resize();
const dpr = dprFor();
const W = canvas.width, H = canvas.height;
const p = getProgram(current);
if (!p) { requestAnimationFrame(frame); return; }
const u = uniformCache[current];
gl.useProgram(p);
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.enableVertexAttribArray(u.a_pos);
gl.vertexAttribPointer(u.a_pos, 2, gl.FLOAT, false, 0, 0);
gl.uniform2f(u.uRes, W, H);
gl.uniform1f(u.uTime, t);
gl.uniform1f(u.uShaderTime, tShader);
// Mouse in GL coords (Y inverted relative to CSS)
gl.uniform2f(u.uMouse, mouse.x * dpr, (window.innerHeight - mouse.y) * dpr);
gl.uniform2f(u.uMouseSmooth, mouseSmooth.x * dpr, (window.innerHeight - mouseSmooth.y) * dpr);
gl.uniform4fv(u.uClicks, clicks);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>