<div class="canvas" name="smoke">
<canvas id="smoke" width="200" height="200"></canvas>
<script id="smoke.js">
// https://github.com/bijection/smoke.js/blob/master/smoke.js
(function(){
	var canvas   = document.getElementById('smoke');
	var ctx      = canvas.getContext('2d');
	var buffer   = document.createElement('canvas');
	var bctx     = buffer.getContext('2d');
	buffer.width = buffer.height = 20;
	var data     = bctx.createImageData(20, 20);
	var color    = [54, 16, 18];
	var opacity  = [0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,3,5,5,7,4,4,1,1,0,1,0,0,0,0,0,1,0,0,17,27,41,52,56,34,23,15,11,4,9,5,1,0,0,0,0,0,0,1,45,63,57,45,78,66,52,41,34,37,23,20,0,1,0,0,0,0,1,43,62,66,64,67,115,112,114,56,58,47,33,18,12,10,0,0,0,0,39,50,63,76,87,107,105,112,128,104,69,64,29,18,21,15,0,0,0,7,42,52,85,91,103,126,153,128,124,82,57,52,52,24,1,0,0,0,2,17,41,67,84,100,122,136,159,127,78,69,60,50,47,25,7,1,0,0,0,34,33,66,82,113,138,149,168,175,82,142,133,70,62,41,25,6,0,0,0,18,39,55,113,111,137,141,139,141,128,102,130,90,96,65,37,0,0,0,2,15,27,71,104,129,129,158,140,154,146,150,131,92,100,67,26,3,0,0,0,0,46,73,104,124,145,135,122,107,120,122,101,98,96,35,38,7,2,0,0,0,50,58,91,124,127,139,118,121,177,156,88,90,88,28,43,3,0,0,0,0,30,62,68,91,83,117,89,139,139,99,105,77,32,1,1,0,0,0,0,0,16,21,8,45,101,125,118,87,110,86,64,39,0,0,0,0,0,0,0,0,0,1,28,79,79,117,122,88,84,54,46,11,0,0,0,0,0,0,0,0,0,1,0,6,55,61,68,71,30,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,23,25,20,12,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,12,9,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,2,0,0,0,0,0,0,0,0];
    
	var d = data.data;
	for (let i=0; i<d.length; i+=4) {
		d[i]   = color[0];
		d[i+1] = color[1];
		d[i+2] = color[2];
		d[i+3] = opacity[i/4];
	}
	bctx.putImageData(data, 0, 0);

	function particle(x,y,l) {
		this.x        = x;
		this.y        = y;
		this.u        = Math.random() * 8 - 4;
		this.v0       = Math.random() * -30 - 10;
		this.v        = this.v0;
		this.scale    = Math.random() * 0.5;
		this.scale0   = Math.random() + this.scale + 5;
		this.lifetime = Math.random() * l + l/2;
		this.age      = 0;

		this.update = function(){
			this.x    += this.u * dt;
			this.y    += this.v * dt;
			var frac   = (1 - Math.sqrt(this.age / this.lifetime));
			this.v     = this.v0     * frac;
			this.scale = this.scale0 * frac;
			this.age  += dt;
        }

		this.draw = function(){
			var width = this.scale * 20;
			var x = this.x - width / 2;
			var y = this.y - width / 2;
			ctx.globalAlpha = (1-Math.abs(1-2*this.age/this.lifetime))/8;
			ctx.drawImage(buffer, x, y, width, width);
		}
	}

	var currentparticles = [];
	var pendingparticles = [];

	function create(x, y, n, lifetime){
		lifetime = lifetime || 20;
		n = n || 10;
		for (let i=0; i<n; i++)
			pendingparticles.push(new particle(x,y,lifetime));
	}

	function update() {
		currentparticles = currentparticles.concat(pendingparticles);
		pendingparticles = [];

		var newparticles = [];
		for (let p of currentparticles) {
			p.update();
			if (p.age < p.lifetime) newparticles.push(p);
		}
		currentparticles = newparticles;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);  
		for (let i=0; i<currentparticles.length; ++i)
			currentparticles[i].draw();
	}

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(animate, canvas);};
	canvas.onmousedown = function(e){create(e.offsetX, e.offsetY, 10);};

	var t0, t1, dt;
	function animate() {
		t0 = new Date().getTime();
		frame();
	}
	function frame() {
		id = requestAnimationFrame(frame, canvas);
		t1 = new Date().getTime();
		dt = 0.001 * (t1-t0);
		t0 = t1;
		update();
		draw();
	}
})();
</script>
</div><div class="canvas" name="curl">
<canvas id="curl" width="200" height="200"></canvas>
<script id="curl.js">
// https://gist.github.com/banksean/304522
var SimplexNoise = function(r) {
  if (r == undefined) r = Math;
  this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0], 
                                 [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1], 
                                 [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]]; 
  this.p = [];
  for (var i=0; i<256; i++) {
    this.p[i] = Math.floor(r.random()*256);
  }
  this.perm = []; 
  for(var i=0; i<512; i++) {
    this.perm[i]=this.p[i & 255];
  } 

  this.simplex = [ 
    [0,1,2,3],[0,1,3,2],[0,0,0,0],[0,2,3,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,2,3,0], 
    [0,2,1,3],[0,0,0,0],[0,3,1,2],[0,3,2,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,3,2,0], 
    [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0], 
    [1,2,0,3],[0,0,0,0],[1,3,0,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,3,0,1],[2,3,1,0], 
    [1,0,2,3],[1,0,3,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,0,3,1],[0,0,0,0],[2,1,3,0], 
    [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0], 
    [2,0,1,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,0,1,2],[3,0,2,1],[0,0,0,0],[3,1,2,0], 
    [2,1,0,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,1,0,2],[0,0,0,0],[3,2,0,1],[3,2,1,0]]; 
};

SimplexNoise.prototype.dot = function(g, x, y) { 
	return g[0]*x + g[1]*y;
};

SimplexNoise.prototype.noise = function(xin, yin) { 
  var n0, n1, n2;
  var F2 = 0.5*(Math.sqrt(3.0)-1.0); 
  var s = (xin+yin)*F2;
  var i = Math.floor(xin+s); 
  var j = Math.floor(yin+s); 
  var G2 = (3.0-Math.sqrt(3.0))/6.0; 
  var t = (i+j)*G2; 
  var X0 = i-t;
  var Y0 = j-t; 
  var x0 = xin-X0;
  var y0 = yin-Y0; 
  var i1, j1;
  if(x0>y0) {i1=1; j1=0;}
  else {i1=0; j1=1;}
  var x1 = x0 - i1 + G2;
  var y1 = y0 - j1 + G2; 
  var x2 = x0 - 1.0 + 2.0 * G2;
  var y2 = y0 - 1.0 + 2.0 * G2; 
  var ii = i & 255; 
  var jj = j & 255; 
  var gi0 = this.perm[ii+this.perm[jj]] % 12; 
  var gi1 = this.perm[ii+i1+this.perm[jj+j1]] % 12; 
  var gi2 = this.perm[ii+1+this.perm[jj+1]] % 12; 
  var t0 = 0.5 - x0*x0-y0*y0; 
  if(t0<0) n0 = 0.0; 
  else { 
    t0 *= t0; 
    n0 = t0 * t0 * this.dot(this.grad3[gi0], x0, y0);
  } 
  var t1 = 0.5 - x1*x1-y1*y1; 
  if(t1<0) n1 = 0.0; 
  else { 
    t1 *= t1; 
    n1 = t1 * t1 * this.dot(this.grad3[gi1], x1, y1); 
  }
  var t2 = 0.5 - x2*x2-y2*y2; 
  if(t2<0) n2 = 0.0; 
  else { 
    t2 *= t2; 
    n2 = t2 * t2 * this.dot(this.grad3[gi2], x2, y2); 
  } 
  return 70.0 * (n0 + n1 + n2); 
};

(function(){
	var canvas = document.getElementById('curl');
	var ctx    = canvas.getContext('2d');
	var w      = canvas.width;
	var h      = canvas.height;

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var params = {
		N: 100,
		speed: 5,
	};

	var noise = new SimplexNoise();
	var curl  = new Array(w * h);
	for (let x = 0; x < w; ++x) {
		for (let y = 0; y < h; ++y) {
			var n = noise.noise(x, y);
			var dx = noise.noise(x+1, y) - n;
			var dy = noise.noise(x, y+1) - n;
			curl[x * w + y] = {
				x: -dy * params.speed,
				y:  dx * params.speed
			};
		}
	}

	var particles = new Array(params.N);
	for (let i=0; i<particles.length; ++i)
		particles[i] = {x:0, y:0, vx:0, vy:0};

	var t = 0;
	var steer = .1;
	function move() {
		if (--t < 0) {
			t = 500;
			steer = Math.random() * .5 + 0.2;
			var c = Math.random() * 360 | 0;
			ctx.fillStyle = 'hsl(' + c + ', 100%, 10%)';
			for (let p of particles) {
				p.x = w / 2;
				p.y = h / 2;
				p.vx = Math.random() - .5;
				p.vy = Math.random() - .5;
			}
		} else {
			for (let p of particles)
				if (p.x >= 0 && p.x < w && p.y >= 0 && p.y < h) {
					var c = curl[(p.x | 0) * w + (p.y | 0)];
					p.vx += (c.x - p.vx) * steer;
					p.vy += (c.y - p.vy) * steer;
					p.vx *= 0.95;
					p.vy *= 0.95;
					p.x += p.vx;
					p.y += p.vy;
				}
		}
	}

	function draw() {
		ctx.beginPath();
		for (let p of particles) ctx.rect(p.x, p.y, 1, 1);
		ctx.fill();

		var imgdt = ctx.getImageData(0, 0, w, h);
		var a     = imgdt.data;
		for (let i = 0; i < a.length; i += 4) a[i+3] *= 0.99;
		ctx.putImageData(imgdt, 0, 0);
	}

	function update() {
		move();
		draw();
	}

	update();
})();
</script>
</div><div class="canvas" name="gas">
<canvas id="gas" style="width:200px; height:200px;"></canvas>
<script id="gas.js">
// https://codepen.io/FWeinb/full/JhzvI/
// http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf
(function() {
	var N         = 50;			// resolution
	var scale     = N / 200;	// mouse coordinate

	var canvas    = document.getElementById("gas");
	canvas.width  = N;
	canvas.height = N;
	var ctx       = canvas.getContext("2d");
	var imageData = ctx.getImageData(0, 0, N, N);

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(animate, canvas);};
	function animate() {id = requestAnimationFrame(animate, canvas); update();}

	var U         = new Float32Array2D(N + 2);
	var V         = new Float32Array2D(N + 2);
	var D         = new Float32Array2D(N + 2);
	var U0        = new Float32Array2D(N + 2);
	var V0        = new Float32Array2D(N + 2);
	var D0        = new Float32Array2D(N + 2);

	function Float32Array2D(n) {
		var a = new Array(n);
		for (let i=0; i<n; ++i)
			a[i] = new Float32Array(n).fill(0);
		return a;
	}

	function update(){
		zero(D0);
		zero(U0);
		zero(V0);
		interact(D0, U0, V0);
		add_source(D, D0);
		add_source(U, U0);
		add_source(V, V0);

		project(U, V, U0, V0);
		advect(D, U, V, D0, U0, V0);
		swap(D, D0);
		swap(U, U0);
		swap(V, V0);

		draw(D, U, V);
		decay(D, U, V);
	}

	function zero(a) {
		for (let i=0; i<N+2; ++i)
			a[i].fill(0);
	}

	function swap(a, b) {
		for (let i=0; i<N+2; ++i)
			[a[i], b[i]] = [b[i], a[i]];
	}

	function add_source(a, s) {
		for (let i=1; i<=N; i++)
			for (let j=1; j<=N; j++)
				a[i][j] += s[i][j];
	}

	function decay(D, U, V) {
		for (let i=1; i<=N; i++)
			for (let j=1; j<=N; j++) {
				D[i][j] *= 0.998;
				U[i][j] *= 0.998;
				V[i][j] *= 0.998;
			}
	}

	function project(U, V, a, b) {
		boundary(1, U);
		boundary(2, V);
		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				a[i][j] = 0;
				b[i][j] = (U[i+1][j] - U[i-1][j]
						+  V[i][j+1] - V[i][j-1]) / 2;
			}
		}

		const iteration = 5;
		for (let k=0; k<iteration; k++) {
			boundary(3, a);
			for (let i=1; i<=N; i++) {
				for (let j=1; j<=N; j++) {
					a[i][j] = (a[i][j-1] + a[i][j+1] + a[i-1][j] + a[i+1][j] - b[i][j]) / 4;
				}
			}
		}

		boundary(3, a);
		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				U[i][j] -= (a[i+1][j] - a[i-1][j]) / 2;
				V[i][j] -= (a[i][j+1] - a[i][j-1]) / 2;
			}
		}
	}

	function advect(D, U, V, Dn, Un, Vn){
		boundary(3, D); boundary(1, U); boundary(2, V);	// extrapolation

		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				var x = i - U[i][j];
				var y = j - V[i][j];
				if (x < 0.5) x = 0.5; if (x > N + 0.5) x = N + 0.5;
				if (y < 0.5) y = 0.5; if (y > N + 0.5) y = N + 0.5;
				var x0 = x | 0;  var x1 = x0 + 1;
				var y0 = y | 0;  var y1 = y0 + 1;
				var s1 = x - x0; var s0 = 1 - s1;
				var t1 = y - y0; var t0 = 1 - t1;
				Dn[i][j] = s0 * (t0 * D[x0][y0] + t1 * D[x0][y1])
						 + s1 * (t0 * D[x1][y0] + t1 * D[x1][y1]);
				Un[i][j] = s0 * (t0 * U[x0][y0] + t1 * U[x0][y1])
						 + s1 * (t0 * U[x1][y0] + t1 * U[x1][y1]);
				Vn[i][j] = s0 * (t0 * V[x0][y0] + t1 * V[x0][y1])
						 + s1 * (t0 * V[x1][y0] + t1 * V[x1][y1]);
			}
		}
	}

	function boundary(b, a){
		if (b == 0) {
			for (let i=1; i<=N; i++) {
				a[i][0]   = 0;
				a[i][N+1] = 0;
				a[0][i]   = 0;
				a[N+1][i] = 0;
			}
		} else if (b == 3) {
			for (let i=1; i<=N; i++) {
				a[i][0]   = a[i][1];
				a[i][N+1] = a[i][N];
				a[0][i]   = a[1][i];
				a[N+1][i] = a[N][i];
			}
		} else if (b == 1) {
			for (let i=1; i<=N; i++) {
				a[i][0]   =  a[i][1];
				a[i][N+1] =  a[i][N];
				a[0][i]   = -a[1][i];
				a[N+1][i] = -a[N][i];
			}
		} else if (b == 2) {
			for (let i=1; i<=N; i++) {
				a[i][0]   = -a[i][1];
				a[i][N+1] = -a[i][N];
				a[0][i]   =  a[1][i];
				a[N+1][i] =  a[N][i];
			}
		}

		a[0][0]     = (a[0][1]   + a[1][0]  ) * 0.5;
		a[0][N+1]   = (a[0][N]   + a[1][N+1]) * 0.5;
		a[N+1][0]   = (a[N+1][1] + a[N][0]  ) * 0.5;
		a[N+1][N+1] = (a[N+1][N] + a[N][N+1]) * 0.5;
	}

	function draw(D, U, V){
		for (let x=1; x<=N; x++) for (let y=1; y<=N; y++) {
			var k = ((x-1) + (y-1) * N) * 4;
//			var c = colorD(D, U, V, x, y);
			var c = colorUV(D, U, V, x, y);
			imageData.data[k]   = 255 - c[0];
			imageData.data[k+1] = 255 - c[1];
			imageData.data[k+2] = 255 - c[2];
			imageData.data[k+3] = 255;
		}
		ctx.putImageData(imageData, 0, 0);
	}

	function colorD(D, U, V, x, y){
		var bw = (D[x][y] * 255 / 6) | 0;
		return [bw, bw, bw];
	}

	function colorUV(D, U, V, x, y){
		var r = Math.abs(U[x][y] * 1000) | 0;
		var b = Math.abs(V[x][y] * 1000) | 0;
		var g = (D[x][y] * 255 / 3) | 0;
		return [r, g, b];
	}

	var mouseStart = [], mouseEnd = [];
	canvas.onmouseenter = function(e){mouseStart = [e.offsetX, e.offsetY];};
	canvas.onmousemove = function(e){mouseEnd = [e.offsetX, e.offsetY];};

	function interact(D, U, V){
		var dx = mouseEnd[0] - mouseStart[0];
		var dy = mouseEnd[1] - mouseStart[1];
		var length = Math.sqrt(dx * dx + dy * dy) | 0;
		for (let i = 0; i < length; i++) {
			var x = (mouseStart[0] + i / length * dx) * scale | 0;
			var y = (mouseStart[1] + i / length * dy) * scale | 0;
			U[x][y] = dx / 30;
			V[x][y] = dy / 30;
			D[x][y] = 1;
		}
		mouseStart[0] = mouseEnd[0];
		mouseStart[1] = mouseEnd[1];
	}

	update();
})();
</script>
</div><div class="canvas" name="noise">
<canvas id="noise" style="width: 200px; height: 200px;"></canvas>
<script id="noise.js">
(function() {
	var N         = 100;		// resolution

	var canvas    = document.getElementById("noise");
	canvas.width  = N;
	canvas.height = N;
	var ctx       = canvas.getContext("2d");
	var imageData = ctx.getImageData(0, 0, N, N);

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(animate, canvas);};
	function animate() {id = requestAnimationFrame(animate, canvas); update();}

	canvas.onmousedown = init;

	var U         = new Float32Array2D(N + 2);
	var V         = new Float32Array2D(N + 2);
	var D         = new Float32Array2D(N + 2);
	var U0        = new Float32Array2D(N + 2);
	var V0        = new Float32Array2D(N + 2);
	var D0        = new Float32Array2D(N + 2);

	function Float32Array2D(n) {
		var a = new Array(n);
		for (let i=0; i<n; ++i)
			a[i] = new Float32Array(n).fill(0);
		return a;
	}

	function init() {
		for (let i=1; i<=N; i++)
			for (let j=1; j<=N; j++)
				D[i][j] = Math.random();
	}

	function update(){
		force(U, V, D, U0, V0, D0);
		advect(U0, V0, D0, U, V, D);
		draw(D);
	}

	function zero(a) {
		for (let i=0; i<N+2; ++i)
			a[i].fill(0);
	}

	function assign(a, b) {
		for (let i=0; i<N+2; ++i)
			a[i].set(b[i]);
	}

	function force(U, V, D, Un, Vn, Dn) {
		assign(Dn, D); assign(Un, U); assign(Vn, V);

		const compressibility = 0.001;
		boundary(0, D);
		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				Un[i][j] += (D[i+1][j] - D[i-1][j]) * -compressibility;
				Vn[i][j] += (D[i][j+1] - D[i][j-1]) * -compressibility;
			}
		}

		const viscosity = 0.1;
		boundary(1, U); boundary(2, V);
		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				Un[i][j] += (U[i][j+1] - U[i][j] + U[i][j-1] - U[i][j]) * -viscosity;
				Vn[i][j] += (V[i+1][j] - V[i][j] + V[i-1][j] - V[i][j]) * -viscosity;
			}
		}
	}

	function advect(U, V, D, Un, Vn, Dn){
		boundary(0, D); boundary(1, U); boundary(2, V);
		zero(Dn); zero(Un); zero(Vn);

		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				var x = i - U[i][j];
				var y = j - V[i][j];
				var xrev = false, yrev = false;
				if (x < 0.5)     {x = 1 - x;       xrev = true;}
				if (x > N + 0.5) {x = N*2 + 1 - x; xrev = true;}
				if (y < 0.5)     {y = 1 - y;       yrev = true;}
				if (y > N + 0.5) {y = N*2 + 1 - y; yrev = true;}
				x = Math.round(x);
				y = Math.round(y);
				Dn[i][j] = D[x][y];
				Un[i][j] = U[x][y];
				Vn[i][j] = V[x][y];
				if (xrev) Un[i][j] *= -1;
				if (yrev) Vn[i][j] *= -1;
			}
		}
	}

	function boundary(b, a){
		if (b == 0) {
			for (let i=1; i<=N; i++) {
				a[i][0]   = a[i][1];
				a[i][N+1] = a[i][N];
				a[0][i]   = a[1][i];
				a[N+1][i] = a[N][i];
			}
		} else if (b == 1) {
			for (let i=1; i<=N; i++) {
				a[i][0]   =  a[i][1];
				a[i][N+1] =  a[i][N];
				a[0][i]   = -a[1][i];
				a[N+1][i] = -a[N][i];
			}
		} else if (b == 2) {
			for (let i=1; i<=N; i++) {
				a[i][0]   = -a[i][1];
				a[i][N+1] = -a[i][N];
				a[0][i]   =  a[1][i];
				a[N+1][i] =  a[N][i];
			}
		}

		a[0][0]     = (a[0][1]   + a[1][0]  ) * 0.5;
		a[0][N+1]   = (a[0][N]   + a[1][N+1]) * 0.5;
		a[N+1][0]   = (a[N+1][1] + a[N][0]  ) * 0.5;
		a[N+1][N+1] = (a[N+1][N] + a[N][N+1]) * 0.5;
	}

	function draw(D){
		for (let x=1; x<=N; x++) for (let y=1; y<=N; y++) {
			var k = ((x-1) + (y-1) * N) * 4;
			var c = (D[x][y] * 255) | 0;
			imageData.data[k]   = c;
			imageData.data[k+1] = 0;
			imageData.data[k+2] = 0;
			imageData.data[k+3] = 255;
		}
		ctx.putImageData(imageData, 0, 0);
	}

	init();
	update();
})();
</script>
</div><div class="canvas" name="band">
<canvas id="band" width="200" height="200"></canvas>
<script id="band.js">
(function() {
	var canvas    = document.getElementById("band");
	var ctx       = canvas.getContext('2d');
	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	var data      = imageData.data;

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	var type = 3;
	canvas.onmousedown = function() {type = (type + 1) % 4;}

	function hsl2rgb(h, s, l) {
		if (!isFinite(h)) h = 0;
		if (!isFinite(s)) s = 0;
		if (!isFinite(l)) l = 0;

		h /= 60;
		if (h < 0) h = 6 - (-h % 6);
		h %= 6;

		s = Math.max(0, Math.min(1, s / 100));
		l = Math.max(0, Math.min(1, l / 100));

		var c = (1 - Math.abs((2 * l) - 1)) * s;
		var x = c * (1 - Math.abs((h % 2) - 1));

		if      (h < 1) {r = c; g = x; b = 0;}
		else if (h < 2) {r = x; g = c; b = 0;}
		else if (h < 3) {r = 0; g = c; b = x;}
		else if (h < 4) {r = 0; g = x; b = c;}
		else if (h < 5) {r = x; g = 0; b = c;}
		else            {r = c; g = 0; b = x;}

		var m = l - c / 2;
		var r = Math.round((r + m) * 255);
		var g = Math.round((g + m) * 255);
		var b = Math.round((b + m) * 255);
		return [r, g, b];
	}

	var colors = [
		hsl2rgb( 45, 60, 60),
		hsl2rgb(135, 60, 60),
		hsl2rgb(225, 60, 60),
		hsl2rgb(315, 60, 60),
		hsl2rgb(360, 60, 60),
	];

	function assigncolor(color, color1) {
		color[0] = color1[0];
		color[1] = color1[1];
		color[2] = color1[2];
	}

	function mulcolor(color, ratio) {
		color[0] *= ratio;
		color[1] *= ratio;
		color[2] *= ratio;
	}

	function softcolor(color, ratio) {
		ratio = 1 - Math.abs(ratio * 2 - 1);	// linear mountain
		ratio = 1 - Math.pow(2, -5 * ratio);	// easeOutExpo
		mulcolor(color, ratio);
	}

	function interpolatecolor(color, color1, color2, ratio) {
		color[0] = color1[0] * (1 - ratio) + color2[0] * ratio;
		color[1] = color1[1] * (1 - ratio) + color2[1] * ratio;
		color[2] = color1[2] * (1 - ratio) + color2[2] * ratio;
	}

	function getcolor(color, scale) {
		scale -= Math.floor(scale);	// [0, 1)
		scale *= colors.length;
		var index = Math.floor(scale);
		assigncolor(color, colors[index]);
		var color1 = colors[index % colors.length];
		var color2 = colors[(index + 1) % colors.length];
		var ratio = scale - index;
		interpolatecolor(color, color1, color2, ratio);
	}

	var position = 0;
	var angle = 0;	// degree
	var bandwidth = 50;
	var cx = canvas.width / 2;
	var cy = canvas.height / 2;

	function update() {
		position += 1;
		position %= bandwidth * colors.length;
		angle += 1;
		angle %= 360;
	}

	function draw() {
		var theta = 2 * Math.PI * angle / 360;
		var dx =  Math.cos(theta);
		var dy = -Math.sin(theta);
		if (Math.abs(dx) < 1e-5) dx = 0;
		if (Math.abs(dy) < 1e-5) dy = 0;

		for (let y=0; y<canvas.height; ++y)
			for (let x=0; x<canvas.width; ++x) {
				if (type == 0) {
					var d = x;
					var scale = (d - position) / (bandwidth * colors.length);
				} else if (type == 1) {
					var d = Math.hypot(x - cx, y - cy);
					var scale = (d - position) / (bandwidth * colors.length);
				} else if (type == 2) {
					var t = Math.atan2(y - cy, x - cx) * 360 / (2 * Math.PI);
					var scale = (t - angle) / 360;
				} else if (type == 3) {
					var d = x * dx + y * dy;
					var scale = (d - position) / (bandwidth * colors.length);
				} 
				var color = [0,0,0];
				getcolor(color, scale);

				var i = (y * canvas.width + x) * 4;
				data[i  ] = color[0];
				data[i+1] = color[1];
				data[i+2] = color[2];
				data[i+3] = 255;
			}

		ctx.putImageData(imageData, 0, 0);
	}

//	update();
	draw();
})();
</script>
</div>