Custom Code Experiment •
Conformal Map 1
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>conformal map</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
width: 100%; height: 100%;
background: #000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: system-ui, -apple-system, sans-serif;
color: #ccc;
}
#wrap {
width: min(96vw, 900px);
display: flex;
flex-direction: column;
align-items: center;
}
canvas {
display: block;
border-radius: 6px;
width: 60%;
}
#ctrl {
display: flex;
gap: 20px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
padding: 10px 2px 0;
font-size: 13px;
color: #888;
}
#ctrl label {
display: flex;
align-items: center;
gap: 6px;
}
#ctrl input[type=range] {
width: 80px;
accent-color: #aaa;
}
#ctrl input[type=checkbox] {
accent-color: #aaa;
}
#ctrl span {
min-width: 30px;
color: #bbb;
}
</style>
</head>
<body>
<div id="wrap">
<canvas id="c"></canvas>
<div id="ctrl">
<label>Zoom <input type="range" id="zoom" min="0.5" max="3" step="0.01" value="1.3"> <span id="zv">1.30</span></label>
<label>Spiral <input type="range" id="spiral" min="-2" max="2" step="0.01" value="0.5"> <span id="sv">0.50</span></label>
<label>Recursion <input type="range" id="depth" min="1" max="6" step="1" value="4"> <span id="dv">4</span></label>
<label>Animate <input type="checkbox" id="anim" checked></label>
</div>
</div>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
function resize() {
const wrapW = canvas.parentElement.clientWidth || 900;
const w = Math.round(wrapW * 0.6);
const h = Math.round(w * 0.85);
canvas.width = w;
canvas.height = h;
canvas.style.height = h + 'px';
if (gl) gl.viewport(0, 0, w, h);
}
resize();
window.addEventListener('resize', () => { resize(); if (!animOn) requestAnimationFrame(render); });
const vs = `attribute vec2 a; void main(){ gl_Position=vec4(a,0,1); }`;
const fs = `
precision highp float;
uniform vec2 uRes; uniform float uTime,uZoom,uSpiral; uniform int uDepth;
#define PI 3.14159265358979
vec2 cmul(vec2 a,vec2 b){return vec2(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
vec2 cexp(vec2 z){return exp(z.x)*vec2(cos(z.y),sin(z.y));}
vec2 clog(vec2 z){return vec2(log(length(z)),atan(z.y,z.x));}
vec2 droste(vec2 z,float s,float r){return cexp(cmul(vec2(s,r),clog(z)));}
float tile(vec2 p){
p=fract(p)-0.5; float d=1e9;
for(int k=0;k<6;k++){
float a=float(k)*PI/3.0;
vec2 q=vec2(cos(a)*p.x-sin(a)*p.y,sin(a)*p.x+cos(a)*p.y);
float f=length(q-vec2(0.25,0.0))-0.22;
f=max(f,-length(q-vec2(0.38,0.0))+0.12); d=min(d,f);
}
vec2 h=vec2(p.x+p.y*0.577,p.y*1.1547);
vec2 hf=fract(h*1.5)-0.5;
float hex=max(abs(hf.x),abs(hf.x*0.5+hf.y*0.866));
return min(d,hex-0.45);
}
float scene(vec2 uv){
float v=0.0; vec2 z=uv;
for(int i=0;i<8;i++){
if(i>=uDepth) break;
if(length(z)<0.001) break;
z=droste(z,1.0,uSpiral+uTime*0.12)*uZoom;
float d=tile(z*0.5);
float w=exp(-float(i)*0.4)*smoothstep(0.05,-0.05,d);
v+=w*(0.5+0.5*sin(float(i)*1.3+uTime*0.3));
}
return v;
}
void main(){
vec2 uv=(gl_FragCoord.xy-uRes*0.5)/min(uRes.x,uRes.y)*2.2;
vec2 px=1.0/uRes; float v=0.0;
v+=scene(uv+vec2(-0.25,-0.25)*px); v+=scene(uv+vec2(0.25,-0.25)*px);
v+=scene(uv+vec2(-0.25,0.25)*px); v+=scene(uv+vec2(0.25,0.25)*px);
v*=0.25; float r=length(uv);
v*=smoothstep(0.05,0.35,r);
vec3 ink=vec3(0.96,0.96,0.94),paper=vec3(0.03,0.03,0.04),mid=vec3(0.45,0.55,0.60);
vec3 col=mix(paper,ink,smoothstep(0.0,1.0,v));
col=mix(col,mid,0.18*sin(v*PI));
col+=(fract(sin(dot(gl_FragCoord.xy,vec2(127.1,311.7)))*43758.5453)-0.5)*0.025;
col=mix(col,paper,smoothstep(0.9,1.1,r));
gl_FragColor=vec4(clamp(col,0.0,1.0),1.0);
}`;
function mkShader(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));
return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, mkShader(gl.VERTEX_SHADER, vs));
gl.attachShader(prog, mkShader(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(prog);
gl.useProgram(prog);
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]), gl.STATIC_DRAW);
const a = gl.getAttribLocation(prog, 'a');
gl.enableVertexAttribArray(a);
gl.vertexAttribPointer(a, 2, gl.FLOAT, false, 0, 0);
const uResL = gl.getUniformLocation(prog, 'uRes');
const uTimeL = gl.getUniformLocation(prog, 'uTime');
const uZoomL = gl.getUniformLocation(prog, 'uZoom');
const uSpiralL = gl.getUniformLocation(prog, 'uSpiral');
const uDepthL = gl.getUniformLocation(prog, 'uDepth');
let t0 = performance.now(), animOn = true;
function render(now){
const t = animOn ? (now - t0) * 0.001 : 0;
gl.uniform2f(uResL, canvas.width, canvas.height);
gl.uniform1f(uTimeL, t);
gl.uniform1f(uZoomL, parseFloat(document.getElementById('zoom').value));
gl.uniform1f(uSpiralL, parseFloat(document.getElementById('spiral').value));
gl.uniform1i(uDepthL, parseInt(document.getElementById('depth').value));
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
if (animOn) requestAnimationFrame(render);
}
requestAnimationFrame(render);
document.getElementById('zoom').oninput = function(){
document.getElementById('zv').textContent = parseFloat(this.value).toFixed(2);
if (!animOn) requestAnimationFrame(render);
};
document.getElementById('spiral').oninput = function(){
document.getElementById('sv').textContent = parseFloat(this.value).toFixed(2);
if (!animOn) requestAnimationFrame(render);
};
document.getElementById('depth').oninput = function(){
document.getElementById('dv').textContent = this.value;
if (!animOn) requestAnimationFrame(render);
};
document.getElementById('anim').onchange = function(){
animOn = this.checked;
if (animOn){ t0 = performance.now(); requestAnimationFrame(render); }
};
</script>
</body>
</html>