<canvas></canvas>
<h1 id="h1"></h1>
html, body {
  overflow: hidden;
  font: 18 px/1.4 'roboto', normal;
  color: #fefeff;
}
body {
  margin: 0;
  position: absolute;
  width: 100%;
  height: 100%;
}
canvas {
  width: 100%;
  height: 100%;
}
h1 {
  font: 2.75em 'Cinzel', serif;
  font-weight: 400;
  letter-spacing: 0.35em;
}
[class^="letter"] {
  -webkit-transition: opacity 3s ease;
  -moz-transition: opacity 3s ease;
  transition: opacity 3s ease;
}
.letter-0 {
  transition-delay: 0.2s;
}
.letter-1 {
  transition-delay: 0.4s;
}
.letter-2 {
  transition-delay: 0.6s;
}
.letter-3 {
  transition-delay: 0.8s;
}
.letter-4 {
  transition-delay: 1.0s;
}
.letter-5 {
  transition-delay: 1.2s;
}
.letter-6 {
  transition-delay: 1.4s;
}
.letter-7 {
  transition-delay: 1.6s;
}
.letter-8 {
  transition-delay: 1.8s;
}
.letter-9 {
  transition-delay: 2.0s;
}
.letter-10 {
  transition-delay: 2.2s;
}
.letter-11 {
  transition-delay: 2.4s;
}
.letter-12 {
  transition-delay: 2.6s;
}
.letter-13 {
  transition-delay: 2.8s;
}
.letter-14 {
  transition-delay: 3.0s;
}
.letter-15 {
  transition-delay: 3.2s;
}
h1 {
  visibility: hidden;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  -moz-transform: translate(-50%, -50%);
  -webkit-transform: translate(-50%, -50%);

}
h1.transition-in {
  visibility: visible;
}
h1 [class^="letter"] {
  opacity: 0;
}
h1.transition-in [class^="letter"] {
  opacity: 1;
}
'use strict';

var canvas = document.getElementsByTagName( 'canvas' )[ 0 ];

canvas.width  = canvas.clientWidth;
canvas.height = canvas.clientHeight;

var config = {
    TEXTURE_DOWNSAMPLE: 1,
    DENSITY_DISSIPATION: 0.98,
    VELOCITY_DISSIPATION: 0.99,
    PRESSURE_DISSIPATION: 0.8,
    PRESSURE_ITERATIONS: 25,
    CURL: 30,
    SPLAT_RADIUS: 0.005
};

var pointers   = [];
var splatStack = [];

var _getWebGLContext     = getWebGLContext( canvas );
var gl                   = _getWebGLContext.gl;
var ext                  = _getWebGLContext.ext;
var support_linear_float = _getWebGLContext.support_linear_float;

function getWebGLContext( canvas ) {

    var params = {
        alpha: false,
        depth: false,
        stencil: false,
        antialias: false
    };

    var gl = canvas.getContext( 'webgl2', params );

    var isWebGL2 = !!gl;

    if ( !isWebGL2 ) gl = canvas.getContext( 'webgl', params ) || canvas.getContext( 'experimental-webgl', params );

    var halfFloat            = gl.getExtension( 'OES_texture_half_float' );
    var support_linear_float = gl.getExtension( 'OES_texture_half_float_linear' );

    if ( isWebGL2 ) {
        gl.getExtension( 'EXT_color_buffer_float' );
        support_linear_float = gl.getExtension( 'OES_texture_float_linear' );
    }

    gl.clearColor( 0.0, 0.0, 0.0, 1.0 );

    var internalFormat   = isWebGL2 ? gl.RGBA16F : gl.RGBA;
    var internalFormatRG = isWebGL2 ? gl.RG16F : gl.RGBA;
    var formatRG         = isWebGL2 ? gl.RG : gl.RGBA;
    var texType          = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;

    return {
        gl: gl,
        ext: {
            internalFormat: internalFormat,
            internalFormatRG: internalFormatRG,
            formatRG: formatRG,
            texType: texType
        },
        support_linear_float: support_linear_float
    };
}

function pointerPrototype() {
    this.id    = -1;
    this.x     = 0;
    this.y     = 0;
    this.dx    = 0;
    this.dy    = 0;
    this.down  = false;
    this.moved = false;
    this.color = [ 30, 0, 300 ];
}

pointers.push( new pointerPrototype() );

var GLProgram = function () {
    
    function GLProgram( vertexShader, fragmentShader ) {

        if ( !(this instanceof GLProgram) )
            throw new TypeError( "Cannot call a class as a function" );

        this.uniforms = {};
        this.program  = gl.createProgram();

        gl.attachShader( this.program, vertexShader );
        gl.attachShader( this.program, fragmentShader );
        gl.linkProgram( this.program );

        if ( !gl.getProgramParameter( this.program, gl.LINK_STATUS ) ) throw gl.getProgramInfoLog( this.program );

        var uniformCount = gl.getProgramParameter( this.program, gl.ACTIVE_UNIFORMS );
        
        for ( var i = 0; i < uniformCount; i++ ) {
            
            var uniformName = gl.getActiveUniform( this.program, i ).name;
            
            this.uniforms[ uniformName ] = gl.getUniformLocation( this.program, uniformName );
            
        }
    }

    GLProgram.prototype.bind = function bind() {
        gl.useProgram( this.program );
    };

    return GLProgram;
    
}();

function compileShader( type, source ) {

    var shader = gl.createShader( type );
    
    gl.shaderSource( shader, source );
    gl.compileShader( shader );

    if ( !gl.getShaderParameter( shader, gl.COMPILE_STATUS ) ) throw gl.getShaderInfoLog( shader );

    return shader;

}

var baseVertexShader               = compileShader( gl.VERTEX_SHADER, 'precision highp float; precision mediump sampler2D; attribute vec2 aPosition; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform vec2 texelSize; void main () {     vUv = aPosition * 0.5 + 0.5;     vL = vUv - vec2(texelSize.x, 0.0);     vR = vUv + vec2(texelSize.x, 0.0);     vT = vUv + vec2(0.0, texelSize.y);     vB = vUv - vec2(0.0, texelSize.y);     gl_Position = vec4(aPosition, 0.0, 1.0); }' );
var clearShader                    = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uTexture; uniform float value; void main () {     gl_FragColor = value * texture2D(uTexture, vUv); }' );
var displayShader                  = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uTexture; void main () {     gl_FragColor = texture2D(uTexture, vUv); }' );
var splatShader                    = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uTarget; uniform float aspectRatio; uniform vec3 color; uniform vec2 point; uniform float radius; void main () {     vec2 p = vUv - point.xy;     p.x *= aspectRatio;     vec3 splat = exp(-dot(p, p) / radius) * color;     vec3 base = texture2D(uTarget, vUv).xyz;     gl_FragColor = vec4(base + splat, 1.0); }' );
var advectionManualFilteringShader = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; vec4 bilerp (in sampler2D sam, in vec2 p) {     vec4 st;     st.xy = floor(p - 0.5) + 0.5;     st.zw = st.xy + 1.0;     vec4 uv = st * texelSize.xyxy;     vec4 a = texture2D(sam, uv.xy);     vec4 b = texture2D(sam, uv.zy);     vec4 c = texture2D(sam, uv.xw);     vec4 d = texture2D(sam, uv.zw);     vec2 f = p - st.xy;     return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); } void main () {     vec2 coord = gl_FragCoord.xy - dt * texture2D(uVelocity, vUv).xy;     gl_FragColor = dissipation * bilerp(uSource, coord);     gl_FragColor.a = 1.0; }' );
var advectionShader                = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; void main () {     vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;     gl_FragColor = dissipation * texture2D(uSource, coord); }' );
var divergenceShader               = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; vec2 sampleVelocity (in vec2 uv) {     vec2 multiplier = vec2(1.0, 1.0);     if (uv.x < 0.0) { uv.x = 0.0; multiplier.x = -1.0; }     if (uv.x > 1.0) { uv.x = 1.0; multiplier.x = -1.0; }     if (uv.y < 0.0) { uv.y = 0.0; multiplier.y = -1.0; }     if (uv.y > 1.0) { uv.y = 1.0; multiplier.y = -1.0; }     return multiplier * texture2D(uVelocity, uv).xy; } void main () {     float L = sampleVelocity(vL).x;     float R = sampleVelocity(vR).x;     float T = sampleVelocity(vT).y;     float B = sampleVelocity(vB).y;     float div = 0.5 * (R - L + T - B);     gl_FragColor = vec4(div, 0.0, 0.0, 1.0); }' );
var curlShader                     = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; void main () {     float L = texture2D(uVelocity, vL).y;     float R = texture2D(uVelocity, vR).y;     float T = texture2D(uVelocity, vT).x;     float B = texture2D(uVelocity, vB).x;     float vorticity = R - L - T + B;     gl_FragColor = vec4(vorticity, 0.0, 0.0, 1.0); }' );
var vorticityShader                = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; uniform sampler2D uCurl; uniform float curl; uniform float dt; void main () {     float L = texture2D(uCurl, vL).y;     float R = texture2D(uCurl, vR).y;     float T = texture2D(uCurl, vT).x;     float B = texture2D(uCurl, vB).x;     float C = texture2D(uCurl, vUv).x;     vec2 force = vec2(abs(T) - abs(B), abs(R) - abs(L));     force *= 1.0 / length(force + 0.00001) * curl * C;     vec2 vel = texture2D(uVelocity, vUv).xy;     gl_FragColor = vec4(vel + force * dt, 0.0, 1.0); }' );
var pressureShader                 = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uDivergence; vec2 boundary (in vec2 uv) {     uv = min(max(uv, 0.0), 1.0);     return uv; } void main () {     float L = texture2D(uPressure, boundary(vL)).x;     float R = texture2D(uPressure, boundary(vR)).x;     float T = texture2D(uPressure, boundary(vT)).x;     float B = texture2D(uPressure, boundary(vB)).x;     float C = texture2D(uPressure, vUv).x;     float divergence = texture2D(uDivergence, vUv).x;     float pressure = (L + R + B + T - divergence) * 0.25;     gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); }' );
var gradientSubtractShader         = compileShader( gl.FRAGMENT_SHADER, 'precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uVelocity; vec2 boundary (in vec2 uv) {     uv = min(max(uv, 0.0), 1.0);     return uv; } void main () {     float L = texture2D(uPressure, boundary(vL)).x;     float R = texture2D(uPressure, boundary(vR)).x;     float T = texture2D(uPressure, boundary(vT)).x;     float B = texture2D(uPressure, boundary(vB)).x;     vec2 velocity = texture2D(uVelocity, vUv).xy;     velocity.xy -= vec2(R - L, T - B);     gl_FragColor = vec4(velocity, 0.0, 1.0); }' );

var textureWidth  = void 0;
var textureHeight = void 0;
var density       = void 0;
var velocity      = void 0;
var divergence    = void 0;
var curl          = void 0;
var pressure      = void 0;

initFramebuffers();

var clearProgram           = new GLProgram( baseVertexShader, clearShader );
var displayProgram         = new GLProgram( baseVertexShader, displayShader );
var splatProgram           = new GLProgram( baseVertexShader, splatShader );
var advectionProgram       = new GLProgram( baseVertexShader, support_linear_float ? advectionShader : advectionManualFilteringShader );
var divergenceProgram      = new GLProgram( baseVertexShader, divergenceShader );
var curlProgram            = new GLProgram( baseVertexShader, curlShader );
var vorticityProgram       = new GLProgram( baseVertexShader, vorticityShader );
var pressureProgram        = new GLProgram( baseVertexShader, pressureShader );
var gradienSubtractProgram = new GLProgram( baseVertexShader, gradientSubtractShader );

function initFramebuffers() {

    textureWidth  = gl.drawingBufferWidth >> config.TEXTURE_DOWNSAMPLE;
    textureHeight = gl.drawingBufferHeight >> config.TEXTURE_DOWNSAMPLE;

    var iFormat   = ext.internalFormat;
    var iFormatRG = ext.internalFormatRG;
    var formatRG  = ext.formatRG;
    var texType   = ext.texType;

    density    = createDoubleFBO( 0, textureWidth, textureHeight, iFormat, gl.RGBA, texType, support_linear_float ? gl.LINEAR : gl.NEAREST );
    velocity   = createDoubleFBO( 2, textureWidth, textureHeight, iFormatRG, formatRG, texType, support_linear_float ? gl.LINEAR : gl.NEAREST );
    divergence = createFBO( 4, textureWidth, textureHeight, iFormatRG, formatRG, texType, gl.NEAREST );
    curl       = createFBO( 5, textureWidth, textureHeight, iFormatRG, formatRG, texType, gl.NEAREST );
    pressure   = createDoubleFBO( 6, textureWidth, textureHeight, iFormatRG, formatRG, texType, gl.NEAREST );

}

function createFBO( texId, w, h, internalFormat, format, type, param ) {

    gl.activeTexture( gl.TEXTURE0 + texId );

    var texture = gl.createTexture();

    gl.bindTexture( gl.TEXTURE_2D, texture );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
    gl.texImage2D( gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null );

    var fbo = gl.createFramebuffer();

    gl.bindFramebuffer( gl.FRAMEBUFFER, fbo );
    gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0 );
    gl.viewport( 0, 0, w, h );
    gl.clear( gl.COLOR_BUFFER_BIT );

    return [ texture, fbo, texId ];

}

function createDoubleFBO( texId, w, h, internalFormat, format, type, param ) {

    var fbo1 = createFBO( texId, w, h, internalFormat, format, type, param );
    var fbo2 = createFBO( texId + 1, w, h, internalFormat, format, type, param );

    return {
        get first() {
            return fbo1;
        },
        get second() {
            return fbo2;
        },
        swap: function swap() {
            var temp = fbo1;

            fbo1 = fbo2;
            fbo2 = temp;
        }
    };

}

var blit = function () {

    gl.bindBuffer( gl.ARRAY_BUFFER, gl.createBuffer() );
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1, -1, -1, 1, 1, 1, 1, -1 ] ), gl.STATIC_DRAW );
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer() );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( [ 0, 1, 2, 0, 2, 3 ] ), gl.STATIC_DRAW );
    gl.vertexAttribPointer( 0, 2, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( 0 );

    return function ( destination ) {
        gl.bindFramebuffer( gl.FRAMEBUFFER, destination );
        gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
    };

}();

var lastTime = Date.now();

update();

function update() {

    resizeCanvas();

    var dt = Math.min( (Date.now() - lastTime) / 1000, 0.016 );
    lastTime = Date.now();

    gl.viewport( 0, 0, textureWidth, textureHeight );

    if ( splatStack.length > 0 ) {
        for ( var m = 0; m < splatStack.pop(); m++ ) {

            var color = [ Math.random() * 10, Math.random() * 10, Math.random() * 10 ];
            var x     = canvas.width * Math.random();
            var y     = canvas.height * Math.random();
            var dx    = 1000 * (Math.random() - 0.5);
            var dy    = 1000 * (Math.random() - 0.5);

            splat( x, y, dx, dy, color );
        }
    }

    advectionProgram.bind();
    gl.uniform2f( advectionProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight );
    gl.uniform1i( advectionProgram.uniforms.uVelocity, velocity.first[ 2 ] );
    gl.uniform1i( advectionProgram.uniforms.uSource, velocity.first[ 2 ] );
    gl.uniform1f( advectionProgram.uniforms.dt, dt );
    gl.uniform1f( advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION );
    blit( velocity.second[ 1 ] );
    velocity.swap();

    gl.uniform1i( advectionProgram.uniforms.uVelocity, velocity.first[ 2 ] );
    gl.uniform1i( advectionProgram.uniforms.uSource, density.first[ 2 ] );
    gl.uniform1f( advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION );
    blit( density.second[ 1 ] );
    density.swap();

    for ( var i = 0, len =  pointers.length; i < len; i++ ) {
        var pointer = pointers[ i ];

        if ( pointer.moved ) {
            splat( pointer.x, pointer.y, pointer.dx, pointer.dy, pointer.color );
            pointer.moved = false;
        }
    }

    curlProgram.bind();
    gl.uniform2f( curlProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight );
    gl.uniform1i( curlProgram.uniforms.uVelocity, velocity.first[ 2 ] );
    blit( curl[ 1 ] );

    vorticityProgram.bind();
    gl.uniform2f( vorticityProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight );
    gl.uniform1i( vorticityProgram.uniforms.uVelocity, velocity.first[ 2 ] );
    gl.uniform1i( vorticityProgram.uniforms.uCurl, curl[ 2 ] );
    gl.uniform1f( vorticityProgram.uniforms.curl, config.CURL );
    gl.uniform1f( vorticityProgram.uniforms.dt, dt );
    blit( velocity.second[ 1 ] );
    velocity.swap();

    divergenceProgram.bind();
    gl.uniform2f( divergenceProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight );
    gl.uniform1i( divergenceProgram.uniforms.uVelocity, velocity.first[ 2 ] );
    blit( divergence[ 1 ] );

    clearProgram.bind();

    var pressureTexId = pressure.first[ 2 ];

    gl.activeTexture( gl.TEXTURE0 + pressureTexId );
    gl.bindTexture( gl.TEXTURE_2D, pressure.first[ 0 ] );
    gl.uniform1i( clearProgram.uniforms.uTexture, pressureTexId );
    gl.uniform1f( clearProgram.uniforms.value, config.PRESSURE_DISSIPATION );
    blit( pressure.second[ 1 ] );
    pressure.swap();

    pressureProgram.bind();
    gl.uniform2f( pressureProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight );
    gl.uniform1i( pressureProgram.uniforms.uDivergence, divergence[ 2 ] );
    pressureTexId = pressure.first[ 2 ];
    gl.activeTexture( gl.TEXTURE0 + pressureTexId );

    for ( var _i = 0; _i < config.PRESSURE_ITERATIONS; _i++ ) {
        gl.bindTexture( gl.TEXTURE_2D, pressure.first[ 0 ] );
        gl.uniform1i( pressureProgram.uniforms.uPressure, pressureTexId );
        blit( pressure.second[ 1 ] );
        pressure.swap();
    }

    gradienSubtractProgram.bind();
    gl.uniform2f( gradienSubtractProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight );
    gl.uniform1i( gradienSubtractProgram.uniforms.uPressure, pressure.first[ 2 ] );
    gl.uniform1i( gradienSubtractProgram.uniforms.uVelocity, velocity.first[ 2 ] );
    blit( velocity.second[ 1 ] );
    velocity.swap();

    gl.viewport( 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight );
    displayProgram.bind();
    gl.uniform1i( displayProgram.uniforms.uTexture, density.first[ 2 ] );
    blit( null );

    requestAnimationFrame( update );

}

function splat( x, y, dx, dy, color ) {

    splatProgram.bind();
    gl.uniform1i( splatProgram.uniforms.uTarget, velocity.first[ 2 ] );
    gl.uniform1f( splatProgram.uniforms.aspectRatio, canvas.width / canvas.height );
    gl.uniform2f( splatProgram.uniforms.point, x / canvas.width, 1.0 - y / canvas.height );
    gl.uniform3f( splatProgram.uniforms.color, dx, -dy, 1.0 );
    gl.uniform1f( splatProgram.uniforms.radius, config.SPLAT_RADIUS );
    blit( velocity.second[ 1 ] );
    velocity.swap();

    gl.uniform1i( splatProgram.uniforms.uTarget, density.first[ 2 ] );
    gl.uniform3f( splatProgram.uniforms.color, color[ 0 ] * 0.3, color[ 1 ] * 0.3, color[ 2 ] * 0.3 );
    blit( density.second[ 1 ] );
    density.swap();

}

function resizeCanvas() {

    ( canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight ) && ( canvas.width  = canvas.clientWidth, canvas.height = canvas.clientHeight, initFramebuffers() );

}

var count    = 0;
var colorArr = [ Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2 ];

canvas.addEventListener( 'mousemove', function ( e ) {

    count++;

    ( count > 25 ) && (colorArr = [ Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2 ], count = 0);

    pointers[ 0 ].down  = true;
    pointers[ 0 ].color = colorArr;
    pointers[ 0 ].moved = pointers[ 0 ].down;
    pointers[ 0 ].dx    = (e.offsetX - pointers[ 0 ].x) * 10.0;
    pointers[ 0 ].dy    = (e.offsetY - pointers[ 0 ].y) * 10.0;
    pointers[ 0 ].x     = e.offsetX;
    pointers[ 0 ].y     = e.offsetY;

} );

canvas.addEventListener( 'touchmove', function ( e ) {

    e.preventDefault();

    var touches = e.targetTouches;

    count++;

    ( count > 25 ) && (colorArr = [ Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2 ], count = 0);

    for ( var i = 0, len = touches.length; i < len; i++ ) {

        if ( i >= pointers.length ) pointers.push( new pointerPrototype() );

        pointers[ i ].id    = touches[ i ].identifier;
        pointers[ i ].down  = true;
        pointers[ i ].x     = touches[ i ].pageX;
        pointers[ i ].y     = touches[ i ].pageY;
        pointers[ i ].color = colorArr;

        var pointer = pointers[ i ];

        pointer.moved = pointer.down;
        pointer.dx    = (touches[ i ].pageX - pointer.x) * 10.0;
        pointer.dy    = (touches[ i ].pageY - pointer.y) * 10.0;
        pointer.x     = touches[ i ].pageX;
        pointer.y     = touches[ i ].pageY;

    }

}, false );

function m( t ) {

    for ( var e, n = document.getElementById( t ), i = n.innerHTML.replace( "&amp;", "&" ).split( "" ), a = "", o = 0, s = i.length; s > o; o++ ) {
        e = i[ o ].replace( "&", "&amp" );
        a += e.trim() ? '<span class="letter-' + o + '">' + e + "</span>" : "&nbsp;";
    }

    n.innerHTML = a;

    setTimeout( function () {
        n.className = "transition-in";
    }, 500 * Math.random() + 500 );

}

window.onload = function() {
    m( "h1" );
};

External CSS

  1. http://fonts.googleapis.com/css?family=Cinzel
  2. http://fonts.googleapis.com/css?family=Lato:100,300,700

External JavaScript

This Pen doesn't use any external JavaScript resources.