HTML-kod:
<html lang="sv"><head>
<meta charset="UTF-8">
<title>Plasma Shader</title>
<style>
html, body { margin: 0; padding: 0; overflow: hidden; height: 100%; background: #000; }
canvas { display: block; width: 100vw; height: 100vh; }
.info {
position: fixed; top: 16px; left: 16px;
font-family: ui-monospace, Menlo, monospace;
font-size: 12px; color: rgba(255,255,255,0.7);
text-shadow: 0 1px 2px rgba(0,0,0,0.6);
pointer-events: none;
letter-spacing: 0.05em;
}
</style>
</head>
<body>
<canvas id="gl" width="1972" height="1228"></canvas>
<div class="info">flytta musen · scrolla för att zooma</div>
<script id="vert" type="x-shader/x-vertex">
attribute vec2 a_pos;
void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }
</script>
<script id="frag" type="x-shader/x-fragment">
precision highp float;
uniform vec2 u_res;
uniform vec2 u_mouse; // 0..1
uniform float u_time;
uniform float u_zoom;
// HSV -> RGB
vec3 hsv2rgb(vec3 c) {
vec3 p = abs(fract(c.xxx + vec3(0.0, 2.0/3.0, 1.0/3.0)) * 6.0 - 3.0);
return c.z * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), c.y);
}
void main() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_res) / min(u_res.x, u_res.y);
uv *= u_zoom;
// Musen drar plasmamönstret med sig
vec2 m = (u_mouse - 0.5) * 2.0;
uv += m * 0.6;
float t = u_time * 0.35;
// Klassisk plasma: summa av sinusvågor i olika riktningar
float v = 0.0;
v += sin(uv.x * 3.0 + t);
v += sin(uv.y * 4.0 + t * 1.3);
v += sin((uv.x + uv.y) * 3.5 + t * 0.8);
// Roterande "öga" som följer musen
vec2 c = uv - m * 0.4;
float d = length(c);
v += sin(d * 6.0 - t * 2.0);
v += sin(atan(c.y, c.x) * 5.0 + t * 1.1);
// Andra rotationsskikt
float a = t * 0.7;
mat2 R = mat2(cos(a), -sin(a), sin(a), cos(a));
vec2 q = R * uv;
v += sin(q.x * 5.0 + q.y * 3.0 + t);
v *= 0.18; // normalisera ungefär till [-1, 1]
// Färgmappning — musens X styr hue, Y styr mättnad-bias
float hue = v + u_time * 0.05 + u_mouse.x * 0.6;
float sat = 0.75 + 0.25 * sin(v * 3.14159);
float val = 0.55 + 0.45 * cos(v * 3.14159 + u_mouse.y * 3.14);
vec3 col = hsv2rgb(vec3(fract(hue), sat, val));
// En liten glödfläck där musen är
float glow = 0.05 / (length(uv - m * 0.4) + 0.08);
col += vec3(0.4, 0.2, 0.6) * glow * 0.15;
// Vinjettering
float vig = smoothstep(1.4, 0.4, length((gl_FragCoord.xy - 0.5 * u_res) / u_res.y));
col *= mix(0.7, 1.0, vig);
gl_FragColor = vec4(col, 1.0);
}
</script>
<script>
(() => {
const canvas = document.getElementById('gl');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) { document.body.innerHTML = '<p style="color:#fff;font-family:monospace;padding:20px">WebGL stöds inte i denna webbläsare.</p>'; return; }
const compile = (type, src) => {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(s)); gl.deleteShader(s); return null;
}
return s;
};
const vs = compile(gl.VERTEX_SHADER, document.getElementById('vert').text);
const fs = compile(gl.FRAGMENT_SHADER, document.getElementById('frag').text);
const prog = gl.createProgram();
gl.attachShader(prog, vs); gl.attachShader(prog, fs); gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(prog)); return; }
gl.useProgram(prog);
// Fullskärmsquad
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);
const loc = gl.getAttribLocation(prog, 'a_pos');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
const uRes = gl.getUniformLocation(prog, 'u_res');
const uMouse = gl.getUniformLocation(prog, 'u_mouse');
const uTime = gl.getUniformLocation(prog, 'u_time');
const uZoom = gl.getUniformLocation(prog, 'u_zoom');
// Mjuk uppföljning av musen
let mx = 0.5, my = 0.5, tmx = 0.5, tmy = 0.5;
let zoom = 1.6, tzoom = 1.6;
const resize = () => {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.floor(window.innerWidth * dpr);
canvas.height = Math.floor(window.innerHeight * dpr);
gl.viewport(0, 0, canvas.width, canvas.height);
};
window.addEventListener('resize', resize);
resize();
window.addEventListener('mousemove', e => {
tmx = e.clientX / window.innerWidth;
tmy = 1.0 - e.clientY / window.innerHeight;
});
window.addEventListener('touchmove', e => {
if (e.touches.length) {
tmx = e.touches[0].clientX / window.innerWidth;
tmy = 1.0 - e.touches[0].clientY / window.innerHeight;
}
}, { passive: true });
window.addEventListener('wheel', e => {
tzoom = Math.min(4.0, Math.max(0.6, tzoom + e.deltaY * 0.002));
}, { passive: true });
const t0 = performance.now();
const frame = () => {
// Easing
mx += (tmx - mx) * 0.06;
my += (tmy - my) * 0.06;
zoom += (tzoom - zoom) * 0.05;
gl.uniform2f(uRes, canvas.width, canvas.height);
gl.uniform2f(uMouse, mx, my);
gl.uniform1f(uTime, (performance.now() - t0) / 1000.0);
gl.uniform1f(uZoom, zoom);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(frame);
};
frame();
})();
</script>
</body></html>