c.width=1E3;c.height=250;a.font="16px arial";a.fillText("Love is in the air",0,16);d=a.getImageData(0,0,116,16);e=[50,0,99,0];f=[2,-2,5,1];q=[];for(o=0;o<6;o++){p=2<<o;r=q[o]=document.createElement("canvas");r.width=1E3;r.height=250;s=r.getContext("2d");s.fillStyle="#fff";for(t=0;t<18*p;t++){j=Math.random()*1E3;k=Math.random()*250;for(i=-1;i<2;i++){s.beginPath();s.arc(j+i*1E3,k,200/p,0,7);s.closePath();s.fill()}}}u=0;
setInterval(function(){for(g=0;g<4;g++){e[g]+=w=f[g];if(e[g]<0||e[g]>99){f[g]=(Math.random()*0.8+0.4)*e[g]>0?-1:1;e[g]-=w}}a.globalAlpha=1;a.fillStyle="#06e";a.fillRect(0,0,1E3,250);a.globalAlpha=0.4;for(o=0;o<6;o++)for(v=0;v<2;v++)a.drawImage(q[o],~~(u*o/9%1E3)-1E3*v,0);u++;h=e[3];a.fillStyle="#e33";for(j=0;j<116;j++){h+=(e[~~(j/30)%4]-h)/10;for(k=0;k<16;k++)if(l=d.data[3+4*(j+116*k)]/5){a.globalAlpha=l*h/6E3+0.1;m=j*8+30.5;n=k*8+50+h/54*(k-23);a.beginPath();a.moveTo(m,n-1.5);x=[3,-3,6,0,0,6,-6,
0,-3,-3];for(i=0;i<10;i+=2)a.lineTo(m+x[i],n+x[i+1]);a.closePath();a.fill()}}},50);
          var width, height, fsize, ts, d, h, hd, hi, hx, hy, i, x, y, k, vx, vy;
var layer, pow, air, airLayer, airContext, cloud, airx, size, lw, hdi, path;
width = 1e3;
height = 250; // higher means higher cpu load
c.width = width;
c.height = height;
// Print the text
fsize = 16;
a.font = '16px arial';
a.fillText("Love is in the air", 0, fsize);
ts = 116; // calculated with a.measureText("Love is in the air")
// get pixeldata of the text
d = a.getImageData(0, 0, ts, fsize);
// the waves are four changing heights
h = [50, 0, 99, 0]; // initial values
// hd is the speed of the height change (wave)
hd = [2, -2, 5, 1]; // initial values
// create six cloud layers, the Perlin noise way
// ten or more layers is nicer, but very cpu-intensive
air = [];
for(layer=0; layer<6; layer++) {
    // higher layers have more, smaller clouds
    pow = 2 << layer; // powers of two with bit shifting
    airLayer = air[layer] = document.createElement('canvas');
    airLayer.width = width;
    airLayer.height = height;
    airContext = airLayer.getContext('2d');
    airContext.fillStyle = '#fff'; // white clouds
	// I didn't manage to squeeze some blur in :-|
    //airContext.shadowColor = '#fff';
    //airContext.shadowBlur = 10;
    // every cloud layer has 18*pow circles (clouds)
    for(cloud=0; cloud<18*pow; cloud++) {
        x = Math.random()*width;
        y = Math.random()*height;
        for(i=-1;i<2;i++) { // three times, to wrap the clouds around the edges on the sides
            airContext.beginPath();
            airContext.arc(x+i*width,y,200/pow,0,7); // 7 is full circle Math.ceil(2*Math.PI)
            airContext.closePath();
            airContext.fill();
        }
    }
}
airx=0;
// Profile
//var timestart = Date.now();
setInterval(function () {
    // calculate new heights of waves
    for (hi = 0; hi < 4; hi++) {
        h[hi] += (hdi = hd[hi]);
        // when the height hits top or bottom, turn around
        if (h[hi] < 0 || h[hi] > 99) {
            hd[hi] = (Math.random() * .8 + .4) * h[hi] > 0 ? -1 : 1; // speed of wave effect
            h[hi] -= hdi; // reverse addition to stay within 0-100 range
        }
    }
    // start with a blue sky
    a.globalAlpha = 1;
    a.fillStyle = '#06e';
    a.fillRect(0, 0, width, height);
    a.globalAlpha = .4; // transparency of each cloud layer
    // draw all cloud layers
    for(layer=0; layer<6; layer++) {
        for(lw=0;lw<2;lw++) { // two layers next to each other
            // Use ~~ to align with pixels for performance
            a.drawImage(air[layer], ~~(airx*layer/9%width)-width*lw, 0);
        }
    }
    airx++; // cloud movement
    hx = h[3];
    a.fillStyle = '#e33'; // heart color
    // draw heart in x-axis
    for (x = 0; x < ts; x++) {
        // hx is the strength of the wave effect (0-99)
        // add one tenth of the difference to the height for a slightly smoother effect
        // ~~ is a trick to turn a float in an int
        hx += (h[~~(x / 30) % 4] - hx) / 10;
        // draw heart in y-axis
        for (y = 0; y < fsize; y++) {
            // k is the alphavalue of each heart / pixel in the text (0-50)
            k = d.data[3 + 4 * (x + ts * y)] / 5;
            if (k) {
                a.globalAlpha = k*hx/6000+.1; // alpha value is k and hx combined
                vx = x * 8 + 30.5; // Use .5 to align with pixels
                vy = y * 8 + 50 + (hx / 54 * (y - 23));
                // Draw heart-like shape
                // 1k+ improvement: with bezier curves:
                // See https://developer.mozilla.org/samples/canvas-tutorial/2_6_canvas_beziercurveto.html
                a.beginPath();
                a.moveTo(vx, vy - 1.5);
                path = [3, -3, 6, 0, 0, 6, -6, 0, -3, -3];
                for(i=0; i<10; i+=2) {
                    a.lineTo(vx+path[i], vy+path[i+1]);
                }
                a.closePath();
                a.fill();
            }
        }
    }
// Profile
//    a.fillStyle = '#000';
//    a.globalAlpha = 1;
//    a.fillText(Math.round(airx/(Date.now()-timestart)*10000)/10+' fps', 0, fsize);
}, 50); // 20 fps