Html Tags

<!DOCTYPE html>
<html>
<title></title>
<body>

<div id="large-header" class="large-header">
  <canvas id="demo-canvas"></canvas>
    <h1 class="main-title">Ventiuk <span class="thin">Team</span></h1>
</div>
</body>
</html>

Css Tags

/* Header */
.large-header {
	position: relative;
	width: 100%;
	background: #333;
	overflow: hidden;
	background-size: cover;
	background-position: center center;
	z-index: 1;
}

#large-header {
	background-image: url('https://www.marcoguglie.it/Codepen/AnimatedHeaderBg/demo-1/img/demo-1-bg.jpg');
}

.main-title {
	position: absolute;
	margin: 0;
	padding: 0;
	color: #f9f1e9;
	text-align: center;
	top: 50%;
	left: 50%;
	-webkit-transform: translate3d(-50%,-50%,0);
	transform: translate3d(-50%,-50%,0);
}

.demo-1 .main-title {
	text-transform: uppercase;
	font-size: 4.2em;
	letter-spacing: 0.1em;
}

.main-title .thin {
	font-weight: 200;
}

@media only screen and (max-width : 768px) {
	.demo-1 .main-title {
		font-size: 3em;
	}
}

JavaScript Tags

(function() {

    var width, height, largeHeader, canvas, ctx, points, target, animateHeader = true;

    // Main
    initHeader();
    initAnimation();
    addListeners();

    function initHeader() {
        width = window.innerWidth;
        height = window.innerHeight;
        target = {x: width/2, y: height/2};

        largeHeader = document.getElementById('large-header');
        largeHeader.style.height = height+'px';

        canvas = document.getElementById('demo-canvas');
        canvas.width = width;
        canvas.height = height;
        ctx = canvas.getContext('2d');

        // create points
        points = [];
        for(var x = 0; x < width; x = x + width/20) {
            for(var y = 0; y < height; y = y + height/20) {
                var px = x + Math.random()*width/20;
                var py = y + Math.random()*height/20;
                var p = {x: px, originX: px, y: py, originY: py };
                points.push(p);
            }
        }

        // for each point find the 5 closest points
        for(var i = 0; i < points.length; i++) {
            var closest = [];
            var p1 = points[i];
            for(var j = 0; j < points.length; j++) {
                var p2 = points[j]
                if(!(p1 == p2)) {
                    var placed = false;
                    for(var k = 0; k < 5; k++) {
                        if(!placed) {
                            if(closest[k] == undefined) {
                                closest[k] = p2;
                                placed = true;
                            }
                        }
                    }

                    for(var k = 0; k < 5; k++) {
                        if(!placed) {
                            if(getDistance(p1, p2) < getDistance(p1, closest[k])) {
                                closest[k] = p2;
                                placed = true;
                            }
                        }
                    }
                }
            }
            p1.closest = closest;
        }

        // assign a circle to each point
        for(var i in points) {
            var c = new Circle(points[i], 2+Math.random()*2, 'rgba(255,255,255,0.3)');
            points[i].circle = c;
        }
    }

    // Event handling
    function addListeners() {
        if(!('ontouchstart' in window)) {
            window.addEventListener('mousemove', mouseMove);
        }
        window.addEventListener('scroll', scrollCheck);
        window.addEventListener('resize', resize);
    }

    function mouseMove(e) {
        var posx = posy = 0;
        if (e.pageX || e.pageY) {
            posx = e.pageX;
            posy = e.pageY;
        }
        else if (e.clientX || e.clientY)    {
            posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
            posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
        }
        target.x = posx;
        target.y = posy;
    }

    function scrollCheck() {
        if(document.body.scrollTop > height) animateHeader = false;
        else animateHeader = true;
    }

    function resize() {
        width = window.innerWidth;
        height = window.innerHeight;
        largeHeader.style.height = height+'px';
        canvas.width = width;
        canvas.height = height;
    }

    // animation
    function initAnimation() {
        animate();
        for(var i in points) {
            shiftPoint(points[i]);
        }
    }

    function animate() {
        if(animateHeader) {
            ctx.clearRect(0,0,width,height);
            for(var i in points) {
                // detect points in range
                if(Math.abs(getDistance(target, points[i])) < 4000) {
                    points[i].active = 0.3;
                    points[i].circle.active = 0.6;
                } else if(Math.abs(getDistance(target, points[i])) < 20000) {
                    points[i].active = 0.1;
                    points[i].circle.active = 0.3;
                } else if(Math.abs(getDistance(target, points[i])) < 40000) {
                    points[i].active = 0.02;
                    points[i].circle.active = 0.1;
                } else {
                    points[i].active = 0;
                    points[i].circle.active = 0;
                }

                drawLines(points[i]);
                points[i].circle.draw();
            }
        }
        requestAnimationFrame(animate);
    }

    function shiftPoint(p) {
        TweenLite.to(p, 1+1*Math.random(), {x:p.originX-50+Math.random()*100,
            y: p.originY-50+Math.random()*100, ease:Circ.easeInOut,
            onComplete: function() {
                shiftPoint(p);
            }});
    }

    // Canvas manipulation
    function drawLines(p) {
        if(!p.active) return;
        for(var i in p.closest) {
            ctx.beginPath();
            ctx.moveTo(p.x, p.y);
            ctx.lineTo(p.closest[i].x, p.closest[i].y);
            ctx.strokeStyle = 'rgba(156,217,249,'+ p.active+')';
            ctx.stroke();
        }
    }

    function Circle(pos,rad,color) {
        var _this = this;

        // constructor
        (function() {
            _this.pos = pos || null;
            _this.radius = rad || null;
            _this.color = color || null;
        })();

        this.draw = function() {
            if(!_this.active) return;
            ctx.beginPath();
            ctx.arc(_this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false);
            ctx.fillStyle = 'rgba(156,217,249,'+ _this.active+')';
            ctx.fill();
        };
    }

    // Util
    function getDistance(p1, p2) {
        return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
    }
    
})();

Html Tags

<!DOCTYPE html>
<html>
<title></title>
<body>

<h1></h1>
<p></p>

</body>
</html>

Css Tags

body {
  margin: 0;
  background-color: #000;
}

canvas {
  width: 100%;
  height: 100%;
}

JavaScript Tags

const { random } = _

class Vector {
  constructor (x, y) {
    this.x = x
    this.y = y
  }
  
  static fromPolar (r, t) {
    return new Vector(
      r * Math.cos(t), 
      r * Math.sin(t))
  }
  
  add (v) {
    this.x += v.x
    this.y += v.y
    return this
  }
  
  mul (x) {
    this.x *= x
    this.y *= x
    return this
  }
  
  dist (v) {
    let dx, dy
    return Math.sqrt(
      (dx = this.x - v.x) * dx,
      (dy = this.y - v.y) * dy)
  }
  
  get mag () {
    return Math.sqrt(
      this.x * this.x,
      this.y * this.y)
  }
  
  set mag (v) {
    let n = this.norm()
    this.x = n.x * v
    this.y = n.y * v
  }
  
  norm () {
    let mag = this.mag
    return new Vector(
      this.x / mag, this.y / mag)
  }
}

class Noise {
  constructor (w, h, oct) {
    this.width = w
    this.height = h
    this.octaves = oct
    this.canvas = Noise.compositeNoise(w, h, oct)
    let ctx = this.canvas.getContext('2d')
    this.data = ctx.getImageData(0, 0, w, h).data
  }
  
  // create w by h noise
  static noise (w, h) {
    let cv = document.createElement('canvas'),
        ctx = cv.getContext('2d')
    
    cv.width = w
    cv.height = h
    
    let img = ctx.getImageData(0, 0, w, h),
        data = img.data
    
    for (let i = 0, l = data.length; i < l; i += 4) {
      data[i + 0] = random(0, 255)
      data[i + 1] = random(0, 255)
      data[i + 2] = random(0, 255)
      data[i + 3] = 255
    }
    
    ctx.putImageData(img, 0, 0)
    return cv;
  }
  
  // create composite noise with multiple octaves
  static compositeNoise (w, h, oct) {
    let cv = document.createElement('canvas'),
        ctx = cv.getContext('2d')
    
    cv.width = w
    cv.height = h
    
    ctx.fillStyle = '#000'
    ctx.fillRect(0, 0, w, h)
    
    ctx.globalCompositeOperation = 'lighter'
    ctx.globalAlpha = 1 / oct
    
    for (let i = 0; i < oct; i++) {
      let noise = Noise.noise(w >> i, h >> i)
      ctx.drawImage(noise, 0, 0, w, h)
    }
    
    return cv
  }
  
  // returns noise from -1.0 to 1.0
  getNoise (x, y, ch) {
    // bitwise ~~ to floor
    let i = (~~x + ~~y * this.width) * 4
    return this.data[i + ch] / 127 - 1
  }
}

class Particle {
  constructor (x, y, vx = 0, vy = 0) {
    this.pos = new Vector(x, y)
    this.vel = new Vector(vx, vy)
    this.acc = new Vector(0, 0)
    // this.tick = 0
    // this.life = random(100, 300)
  }
  
  update (noise) {
    // this.tick++
    // if (this.tick > this.life)
    //   return
      
    this.pos.add(this.vel)
    
    let { x, y } = this.pos
    let dx = noise.getNoise(x, y, 0),
        dy = noise.getNoise(x, y, 1) 
    
    // this.vel.add(this.acc)
    this.vel.add(new Vector(dx, dy))
    // this.acc.add(new Vector(dx / 10, dy / 10))
    // this.acc.mul(0.95)
    this.vel.mul(0.95)
  }
  
  draw (ctx) {
    // if (this.tick > this.life) return
    ctx.fillRect(this.pos.x, this.pos.y, 2, 2)
  }
}

function init () {
  noise = new Noise(w, h, 8)
  // document.body.appendChild(noise.canvas)
  particles = []
  
  for (let i = 0; i < 10000; i++)  {
    let r1 = w / 4,//random(w / 4 - 100, w / 4, true),
        a1 = random(0, 2 * Math.PI, true),
        r2 = random(0, 1, true),
        a2 = random(0, 2 * Math.PI, true)

    let pos = Vector.fromPolar(r1, a1),
        vel = Vector.fromPolar(r2, a2)
    
    pos.add(new Vector(w / 2, h / 2))
//     let x = random(0, w, true),
//         y = random(0, h, true),
//         vx = random(-1, 1, true),
//         vy = random(-1, 1, true)

//     let pos = new Vector(x, y),
//         vel = new Vector(vx, vy)
    
    particles.push(new Particle(
      pos.x, pos.y, vel.x, vel.y))
  }

  ctx.fillStyle = '#000'
  ctx.fillRect(0, 0, w, h)

  ctx.fillStyle = 'rgba(255, 255, 255, 0.05)'
  
  animate()
}

// click once to pause, twice to regen
function generate () {
  if (rid) {
    window.cancelAnimationFrame(rid)
    rid = 0
  } else {
    init()
  }
}

function render () {
  for (let p of particles) {
    p.update(noise)
    p.draw(ctx)
  }
}

function animate () {
  // for (let i = 0; i < 10; i++)
  render()
  
  rid = window.requestAnimationFrame(animate)
}

let w = innerWidth * devicePixelRatio,
    h = innerHeight * devicePixelRatio,
    noise, particles, rid,
    cv = document.createElement('canvas'),
    ctx = cv.getContext('2d')

cv.width = w
cv.height = h

document.body.appendChild(cv)
cv.addEventListener('mousedown', generate)
cv.addEventListener('touchstart', generate)

init()

Html Tags

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 600">
  <defs>
    <linearGradient id="liquidGrad" x1="557" y1="150" x2="557" y2="546" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#FF0909"></stop>
      <stop offset="0.2" stop-color="#F3481A"></stop>
      <stop offset="0.5" stop-color="#FABA2C"></stop>
      <stop offset="1" stop-color="#00BCF2"></stop>
    </linearGradient>
<rect id="tube" x="357" y="150" width="86" height="400" rx="43" ry="43"></rect>   
    <clipPath id="liquidMask">
        <use xlink:href="#tube" class="liquidMask" ></use>
    </clipPath>
   <clipPath id="tubeMask">
        <use xlink:href="#tube" class="liquidMask" ></use>
    </clipPath>
   <path id="liquid" d="M757,552v490H357V552c50,0,50,20,100,20s50-20,100-20,50,20,100,20S707,552,757,552Z" ></path>
<mask id="gradMask">   

      <use xlink:href="#liquid" class="liquid" x="0" fill="#FCEFD6" ></use>
      <use xlink:href="#liquid" class="liquid" x="0" fill="#EEE" opacity="0.7"></use>
 
    </mask>
  </defs>
  
  
    <g class="whole" transform="translate(0, -40)">

           <use xlink:href="#tube" class="tubeBg" fill="#C8D9D3" opacity="0.61"></use>
   
      <g class="dragger" transform="translate(-6, 0)">
        <circle cx="294" cy="540" r="36" fill="#3A3335"></circle>
        <path class="dragTip" d="M315.5,556.76,299.24,540.5l16.26-16.26,36.26,16.26Z" fill="#3A3335"></path>
        <text class="label" x="294" y="551">100</text>
<!--  <path id="dragShine" d="M330,540c-26,2-18-44-72,0a36,36,0,0,1,72,0Z" fill="#E9E9E9" opacity="0.052"></path>    -->    
      </g>       
     
    
 <g mask="url(#gradMask)">
        <use xlink:href="#tube" fill="url(#liquidGrad)" ></use>
  </g>
  <line class="tubeShine" x1="371" y1="200" x2="371" y2="443" fill="none" stroke="#FFF" stroke-linecap="round" stroke-miterlimit="10" stroke-width="8" opacity="0.21" stroke-dasharray="153 30" stroke-dashoffset="-20"></line>
 <!--        <text class="tubeLabel" x="400" y="180">100°</text>
        <text class="tubeLabel" x="400" y="356">50°</text>
        <text class="tubeLabel" x="400" y="540">0°</text> -->
      <g class="measurements" fill="none" stroke="#FCEFD6" stroke-width="3" stroke-linecap="round" opacity="1">
        <line x1="358" y1="196" x2="370" y2="196" ></line>
        <line x1="358" y1="234" x2="370" y2="234" ></line>
        <line x1="358" y1="273" x2="370" y2="273" ></line>
        <line x1="358" y1="311" x2="370" y2="311" ></line>
        <line x1="358" y1="350" x2="370" y2="350" ></line>
        <line x1="358" y1="388" x2="370" y2="388" ></line>
        <line x1="358" y1="426" x2="370" y2="426" ></line>
        <line x1="358" y1="465" x2="370" y2="465" ></line>
        <line x1="358" y1="503" x2="370" y2="503" ></line>
      </g>
     
<!--  <rect class="tubeShine" x="372" y="162" width="56" height="364.02" rx="31.5" ry="31.5" fill="none" stroke="#fff" stroke-miterlimit="10" stroke-width="8" stroke-linecap="round" stroke-dasharray="15 30" opacity="0.31"></rect> -->
  <circle class="follower" cx="400" cy="540" r="0" fill="#62B6CB" fill-opacity="1" stroke="#FCEFD6" stroke-width="0"></circle>

     
 
     
 </g>   
</svg>

Css Tags

body {
 background-color: #FCEFD6;
 overflow: hidden;
 text-align:center;
}
body,
html {
 height: 100%;
 width: 100%;
 margin: 0;
 padding: 0;
 overflow: hidden;
}

svg {
 
 width: 100%;
 height: 100%;
 
}

.label{
 fill:#FFFCF9;
 font-size:1.8em;
 font-family: "din-condensed-web", sans-serif;
 /* font-family: "filson-soft", sans-serif; */
 text-anchor:middle;
 letter-spacing:1.2px;
}


 .tubeLabel{
 fill:#FCEFD6;
 font-size:1.4em;
 font-family: "din-condensed-web", sans-serif;
 /* font-family: "filson-soft", sans-serif; */
 text-anchor:middle;
}
.dragger{
 -webkit-tap-highlight-color:transparent;
}

JavaScript Tags

var xmlns = "http://www.w3.org/2000/svg",
  xlinkns = "http://www.w3.org/1999/xlink",
  select = function(s) {
    return document.querySelector(s);
  },
  selectAll = function(s) {
    return document.querySelectorAll(s);
  },
    liquid = selectAll('.liquid'),
    tubeShine = select('.tubeShine'),
    label = select('.label'),
    follower = select('.follower'),
    dragger = select('.dragger'),
    dragTip = select('.dragTip'),
    minDragY = -380,
    liquidId = 0,
    step = Math.abs(minDragY/100),
    snap = Math.abs(minDragY/10),
    followerVY = 0
  

TweenMax.set('svg', {
  visibility: 'visible'
})

TweenMax.set(dragTip, {
 transformOrigin:'20% 50%'
})

var tl = new TimelineMax()
tl.staggerTo(liquid, 0.7, {
 x:'-=200',
 ease:Linear.easeNone,
 repeat:-1
},0.9)

tl.time(100);

document.addEventListener("touchmove", function(event){
    event.preventDefault();
});
Draggable.create(dragger, {
 type:'y',
 bounds:{minY:minDragY, maxY:0},
 onDrag:onUpdate,
 throwProps:true,
 throwResistance:2300,
 onThrowUpdate:onUpdate,
 overshootTolerance:0,
 snap:function(value){
  //Use this to snap the values to steps of 10
  //return Math.round(value/snap) * snap
 }
})

function onUpdate(){
 liquidId = Math.abs(Math.round(dragger._gsTransform.y/step));

 label.textContent = liquidId + '°';
 TweenMax.to(liquid, 1.3, {
  y:dragger._gsTransform.y*1.12,
  ease:Elastic.easeOut.config(1,0.4)
 })
 
}

TweenMax.to(follower, 1, {
 y:'+=0',
 repeat:-1,
 modifiers:{
  y:function(y, count){
  followerVY += (dragger._gsTransform.y - follower._gsTransform.y) * 0.23;
   followerVY *= 0.69;
   return follower._gsTransform.y + followerVY; 
  }
 }
})

TweenMax.to(dragTip, 1, {
 rotation:'+=0',
 repeat:-1,
 modifiers:{
  rotation:function(rotation, count){
   return rotation-followerVY
  }
 }
})

TweenMax.to(label, 1, {
 y:'+=0',
 repeat:-1,
 modifiers:{
  y:function(y, count){
   return y-followerVY * 0.5
  }
 }
})


TweenMax.to(dragger, 1.4, {
 y:minDragY/2,
 onUpdate:onUpdate,
 ease:Expo.easeInOut
})


//ScrubGSAPTimeline(tl);

Html Tags

input(type='checkbox')
input(type='checkbox')
input(type='checkbox')
input(type='checkbox')
.wrapper
  svg(xmlns='http://www.w3.org/2000/svg' viewbox='0 0 98.138617 90.336713')
    g.light-bulb.light-bulb--3
      path.light-bulb__chord(d='M45.1005633.00870392v68.74491' fill='none' stroke-width='.5291667')
      g.light-bulb__bulb(transform='translate(2.5955317 -211.35093658)')
        circle.light-bulb__glass(r='3.8407259' cy='286.7189' cx='42.372742' stroke-width='.25604835' stroke-linecap='round' stroke-linejoin='round')
        rect.light-bulb__fitting(ry='.37417734' y='279.35907' x='39.8592' height='3.96875' width='5.2916665')
        g.light-bulb__filament
          path.light-bulb__filament(d='M43.563367 283.32784v2.64583M41.446699 283.32784v2.64583' stroke-width='.21696301')
          path.light-bulb__filament(d='M42.505033 285.17992v1.32292M41.975866 285.17992v1.32292M43.0342 285.17992v1.32292' stroke-width='.25654384')
        path.light-bulb__fitting-shine(d='M43.298786 279.35907h1.5875v3.96875h-1.5875z')
        circle.light-bulb__bloom(r='3.5018382' cx='42.372742' cy='286.7189' stroke-width='.93382347' stroke-linecap='round' stroke-linejoin='round')
    g.light-bulb.light-bulb--6
      path.light-bulb__chord(d='M68.9130633.00870392v68.74491' fill='none' stroke-width='.5291667')
      g.light-bulb__bulb(transform='translate(26.4080317 -211.35093658)')
        circle.light-bulb__glass(cx='42.372742' cy='286.7189' r='3.8407259' stroke-width='.25604835' stroke-linecap='round' stroke-linejoin='round')
        rect.light-bulb__fitting(width='5.2916665' height='3.96875' x='39.8592' y='279.35907' ry='.37417734')
        g.light-bulb__filament
          path.light-bulb__filament(d='M43.563367 283.32784v2.64583M41.446699 283.32784v2.64583' stroke-width='.21696301')
          path.light-bulb__filament(d='M42.505033 285.17992v1.32292M41.975866 285.17992v1.32292M43.0342 285.17992v1.32292' stroke-width='.25654384')
        path.light-bulb__fitting-shine(d='M43.298786 279.35907h1.5875v3.96875h-1.5875z')
        circle.light-bulb__bloom(cy='286.7189' cx='42.372742' r='3.5018382' stroke-linecap='round' stroke-linejoin='round')
    g.light-bulb.light-bulb--5
      path.light-bulb__chord(d='M60.9755633.00870392v68.74491' fill='none' stroke-width='.5291667')
      g.light-bulb__bulb(transform='translate(18.4705317 -211.35093658)')
        circle.light-bulb__glass(r='3.8407259' cy='286.7189' cx='42.372742' stroke-width='.25604835' stroke-linecap='round' stroke-linejoin='round')
        rect.light-bulb__fitting(ry='.37417734' y='279.35907' x='39.8592' height='3.96875' width='5.2916665')
        g.light-bulb__filament
          path.light-bulb__filament(d='M43.563367 283.32784v2.64583M41.446699 283.32784v2.64583' stroke-width='.21696301')
          path.light-bulb__filament(d='M42.505033 285.17992v1.32292M41.975866 285.17992v1.32292M43.0342 285.17992v1.32292' stroke-width='.25654384')
        path.light-bulb__fitting-shine(d='M43.298786 279.35907h1.5875v3.96875h-1.5875z')
        circle.light-bulb__bloom(r='3.5018382' cx='42.372742' cy='286.7189' stroke-linecap='round' stroke-linejoin='round')
    g.light-bulb.light-bulb--2
      path.light-bulb__chord(d='M37.1630633.00870392v68.74491' fill='none' stroke-width='.5291667')
      g.light-bulb__bulb(transform='translate(-5.3419683 -211.35093658)')
        circle.light-bulb__glass(cx='42.372742' cy='286.7189' r='3.8407259' stroke-width='.25604835' stroke-linecap='round' stroke-linejoin='round')
        rect.light-bulb__fitting(width='5.2916665' height='3.96875' x='39.8592' y='279.35907' ry='.37417734')
        g.light-bulb__filament
          path.light-bulb__filament(d='M43.563367 283.32784v2.64583M41.446699 283.32784v2.64583' stroke-width='.21696301')
          path.light-bulb__filament(d='M42.505033 285.17992v1.32292M41.975866 285.17992v1.32292M43.0342 285.17992v1.32292' stroke-width='.25654384')
        path.light-bulb__fitting-shine(d='M43.298786 279.35907h1.5875v3.96875h-1.5875z')
        circle.light-bulb__bloom(cy='286.7189' cx='42.372742' r='3.5018382' stroke-linecap='round' stroke-linejoin='round')
    g.light-bulb.light-bulb--1
      path.light-bulb__chord(d='M29.2255633.00870392v68.74491' fill='none' stroke-width='.5291667')
      g.light-bulb__bulb(transform='translate(-13.2794683 -211.35093658)')
        circle.light-bulb__glass(r='3.8407259' cy='286.7189' cx='42.372742' stroke-width='.25604835' stroke-linecap='round' stroke-linejoin='round')
        rect.light-bulb__fitting(ry='.37417734' y='279.35907' x='39.8592' height='3.96875' width='5.2916665')
        g.light-bulb__filament
          path.light-bulb__filament(d='M43.563367 283.32784v2.64583M41.446699 283.32784v2.64583' stroke-width='.21696301')
          path.light-bulb__filament(d='M42.505033 285.17992v1.32292M41.975866 285.17992v1.32292M43.0342 285.17992v1.32292' stroke-width='.25654384')
        path.light-bulb__fitting-shine(d='M43.298786 279.35907h1.5875v3.96875h-1.5875z')
        circle.light-bulb__bloom(r='3.5018382' cx='42.372742' cy='286.7189' stroke-linecap='round' stroke-linejoin='round')
    g.light-bulb.light-bulb--4
      path.light-bulb__chord(d='M53.0380634.00870392v68.74491' fill='none' stroke-width='.5291667')
      g.light-bulb__bulb(transform='translate(10.5330318 -211.35093658)')
        circle.light-bulb__glass(cx='42.372742' cy='286.7189' r='3.8407259' stroke-width='.25604835' stroke-linecap='round' stroke-linejoin='round')
        rect.light-bulb__fitting(width='5.2916665' height='3.96875' x='39.8592' y='279.35907' ry='.37417734')
        g.light-bulb__filament
          path.light-bulb__filament(d='M43.563367 283.32784v2.64583M41.446699 283.32784v2.64583' fill='none' stroke-width='.21696301')
          path.light-bulb__filament(d='M42.505033 285.17992v1.32292M41.975866 285.17992v1.32292M43.0342 285.17992v1.32292' fill='none' stroke-width='.25654384')
        path.light-bulb__fitting-shine(d='M43.298786 279.35907h1.5875v3.96875h-1.5875z')
        circle.light-bulb__bloom(cy='286.7189' cx='42.372742' r='3.5018382' stroke-linecap='round' stroke-linejoin='round')
.banner-container

Css Tags

*
  box-sizing border-box

:root
  --filament-saturation 0
  --filament-lightness 40
  --fitting-lightness 30
  --chord-lightness 50
  --bg-lightness 5
  --glass-lightness 30
  --glass-saturation 0
  --light-alpha 0
  --ceiling 50
  --bulb-hue 60

  // @media(prefers-color-scheme: light)
  //   --chord-lightness 40
  //   --bg-lightness 90
  //   --glass-lightness 80
  //   --glass-saturation 0
  //   --filament-saturation 0
  //   --filament-lightness 70
  //   --fitting-lightness 80
  //   --light-alpha 0
  //   --ceiling 40
  //   --bulb-hue 60

body
  background 'hsl(215, 100%, %s)' % calc(var(--bg-lightness) * 1%)
  display flex
  align-items center
  justify-content center
  min-height 100vh

svg
  width 90vmin
  transform translate(0, -30%)

.wrapper
  position relative
  &:after
    content ""
    width 90vmin
    position absolute
    bottom 130%
    left 0%
    border-bottom '4px solid hsl(0, 0%, %s)' % calc(var(--ceiling) * 1%)
    transform translate(0, 2px)

.light-bulb
  display block

  &__chord
    stroke 'hsl(0, 0%, %s)' % calc(var(--chord-lightness) * 1%)

  &__glass
    fill 'hsla(%s, 100%, %s, %s)' % (var(--bulb-hue) calc(var(--glass-lightness) * 1%) var(--light-alpha))
    stroke 'hsla(%s, %s, %s, 1)' % (var(--bulb-hue) calc(var(--glass-saturation) * 1%) calc(var(--glass-lightness) * 1%))

  &__filament
    fill none
    stroke 'hsl(60, %s, %s)' % (calc(var(--filament-saturation) * 1%) calc(var(--filament-lightness) * 1%))

  &__fitting
    fill 'hsl(0, 0%, %s)' % calc(var(--fitting-lightness) * 1%)

  &__fitting-shine
    fill 'hsl(0, 0%, %s)' % calc((var(--fitting-lightness) + 20) * 1%)

  &__bloom
    fill none
    stroke 'hsl(%s, 100%, 50%)' % var(--bulb-hue)
    stroke-width 2
    opacity 0


// EASTER EGG
[type='checkbox']
  position fixed
  bottom 1rem
  opacity .1

  &:nth-of-type(1)
    right 1rem

    &:checked ~ .wrapper svg
      .light-bulb:nth-of-type(odd)
        --bulb-hue 90
      .light-bulb:nth-of-type(even)
        --bulb-hue 0

  &:nth-of-type(2)
    right 2rem

    &:checked ~ .wrapper svg
      .light-bulb--1
        --bulb-hue 0
      .light-bulb--2
        --bulb-hue 30
      .light-bulb--3
        --bulb-hue 60
      .light-bulb--4
        --bulb-hue 90
      .light-bulb--5
        --bulb-hue 200
      .light-bulb--6
        --bulb-hue 270

  &:nth-of-type(3)
    right 3rem

    &:checked ~ .wrapper svg
      .light-bulb--1
        --bulb-hue 60
      .light-bulb--2
        --bulb-hue 120
      .light-bulb--3
        --bulb-hue 180
      .light-bulb--4
        --bulb-hue 240
      .light-bulb--5
        --bulb-hue 300
      .light-bulb--6
        --bulb-hue 360

  &:nth-of-type(4)
    right 4rem

    &:checked ~ .wrapper svg
      .light-bulb--1
        --bulb-hue 10
      .light-bulb--2
        --bulb-hue 20
      .light-bulb--3
        --bulb-hue 30
      .light-bulb--4
        --bulb-hue 40
      .light-bulb--5
        --bulb-hue 50
      .light-bulb--6
        --bulb-hue 60

.banner-container
  position fixed
  bottom 0
  left 50%
  transform translate(-50%, 0)

.hue-banner
  color 'hsl(%s, 100%, 50%)' % var(--hue, 0)
  border-color 'hsl(%s, 100%, 50%)' % var(--hue, 0)
  border-style solid
  border-width 4px
  border-radius 4px
  margin-bottom 1rem
  padding 1rem
  font-weight bold
  font-family -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
  line-height 1.5
  animation fadeToBlack 0.5s 1s both

@keyframes fadeToBlack
  to
    opacity 0

JavaScript Tags

// eslint-disable-next-line
;(function() {
  const {
    // GSDevTools,
    gsap,
    gsap: { timeline, to, fromTo },
  } = window

  const CURVES = {
    RIGHT:
      'M68.91306296.0087038s.77124463 23.43903799 6.34896852 36.75152668c9.014994 21.51630343 16.57659448 28.05805003 16.57659448 28.05805003',
    LEFT:
      'M29.2255629.00870376s-.77124454 23.43903794-6.34896837 36.75152662C13.86160061 58.2765338 6.3000002 64.81828041 6.3000002 64.81828041',
  }
  const STRAIGHTS = {
    LEFT: 'M29.2255633.00870392v68.74491',
    RIGHT: 'M68.9130633.00870392v68.74491',
  }

  const SPEEDS = {
    ON: 0.05,
    STAGGER: 0.05,
    SWING: 0.5,
    EASE: 4,
  }
  // Disabled for now
  // const isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches
  const isLightMode = false
  const CONFIG = {
    ROTATION: 30,
    FILAMENT: {
      LIGHTNESS: {
        OFF: isLightMode ? 70 : 40,
        ON: 100,
      },
    },
    GLASS: {
      ALPHA: {
        ON: 1,
        OFF: 0,
      },
      SATURATION: {
        ON: 100,
        OFF: 0,
      },
      LIGHTNESS: {
        ON: isLightMode ? 80 : 50,
        OFF: isLightMode ? 80 : 30,
      },
    },
  }

  // DOM ORDERING IS 3, 6, 5, 2, 1, 4 🤦‍♂
  const BULBS = document.querySelectorAll('.light-bulb__bulb')
  const GLASSES = document.querySelectorAll('.light-bulb__glass')
  const CHORDS = document.querySelectorAll('.light-bulb__chord')
  const BLOOMS = document.querySelectorAll('.light-bulb__bloom')
  const FILAMENTS = document.querySelectorAll('g.light-bulb__filament')

  // Set transform origin on swinging bulbs
  gsap.set('.wrapper', { display: 'block' })
  gsap.set(BULBS[1], { transformOrigin: `50% -375%`, rotate: -CONFIG.ROTATION })
  gsap.set(CHORDS[1], { morphSVG: { d: CURVES.RIGHT } })
  gsap.set(GLASSES, { '--light-alpha': CONFIG.GLASS.ALPHA.OFF })
  gsap.set(GLASSES[1], {
    '--light-alpha': CONFIG.GLASS.ALPHA.ON,
    '--glass-saturation': CONFIG.GLASS.SATURATION.ON,
    '--glass-lightness': CONFIG.GLASS.LIGHTNESS.ON,
  })
  gsap.set(BULBS[4], { transformOrigin: '50% -375%' })
  gsap.set(BLOOMS, { scale: 0, transformOrigin: '50% 50%' })
  gsap.set(FILAMENTS[1], {
    '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.ON,
  })

  //Create sub timelines
  const SWING_LEFT_TL = new timeline()
    .add(
      to(CHORDS[4], {
        ease: `power${SPEEDS.EASE}.out`,
        duration: SPEEDS.SWING,
        morphSVG: {
          d: CURVES.LEFT,
        },
      }),
      0
    )
    .add(
      to(BULBS[4], {
        ease: `power${SPEEDS.EASE}.out`,
        rotate: CONFIG.ROTATION,
        duration: SPEEDS.SWING,
      }),
      0
    )
    .add(
      to(GLASSES[4], {
        '--light-alpha': CONFIG.GLASS.ALPHA.ON,
        '--glass-saturation': CONFIG.GLASS.SATURATION.ON,
        '--glass-lightness': CONFIG.GLASS.LIGHTNESS.ON,
        duration: SPEEDS.ON,
      }),
      0
    )
    .add(
      to(FILAMENTS[4], {
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.ON,
        duration: SPEEDS.ON,
      }),
      0
    )
    .add(
      to(BLOOMS[4], {
        duration: SPEEDS.ON * 5,
        scale: 1.3,
        opacity: 0,
      }),
      0
    )
    .add(
      to(CHORDS[4], {
        ease: `power${SPEEDS.EASE}.in`,
        duration: SPEEDS.SWING,
        morphSVG: {
          d: STRAIGHTS.LEFT,
        },
      }),
      SPEEDS.SWING
    )
    .add(
      to(BULBS[4], {
        ease: `power${SPEEDS.EASE}.in`,
        rotate: 0,
        duration: SPEEDS.SWING,
      }),
      SPEEDS.SWING
    )
    .add(
      to(GLASSES[4], {
        duration: SPEEDS.ON,
        '--light-alpha': CONFIG.GLASS.ALPHA.OFF,
        '--glass-saturation': CONFIG.GLASS.SATURATION.OFF,
        '--glass-lightness': CONFIG.GLASS.LIGHTNESS.OFF,
      }),
      'SWINGING_BACK'
    )
    .add(
      to(FILAMENTS[4], {
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.OFF,
        duration: SPEEDS.ON,
      }),
      'SWINGING_BACK'
    )

  const SWING_RIGHT_TL = new timeline()
    .add(
      to(CHORDS[1], SPEEDS.SWING, {
        ease: `power${SPEEDS.EASE}.in`,
        morphSVG: {
          d: STRAIGHTS.RIGHT,
        },
      }),
      0
    )
    .add(
      to(BULBS[1], SPEEDS.SWING, {
        ease: `power${SPEEDS.EASE}.in`,
        rotate: 0,
      }),
      0
    )
    .add(
      to(
        GLASSES[1],
        {
          duration: SPEEDS.ON,
          '--light-alpha': CONFIG.GLASS.ALPHA.OFF,
          '--glass-saturation': CONFIG.GLASS.SATURATION.OFF,
          '--glass-lightness': CONFIG.GLASS.LIGHTNESS.OFF,
        },
        'SWITCH_OFF'
      )
    )
    .add(
      to(FILAMENTS[1], {
        duration: SPEEDS.ON,
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.OFF,
      }),
      'SWITCH_OFF'
    )

  const FLASH_TL = new timeline()
    .add(
      to([GLASSES[2], GLASSES[5], GLASSES[0], GLASSES[3]], {
        duration: SPEEDS.ON,
        stagger: SPEEDS.STAGGER,
        '--light-alpha': CONFIG.GLASS.ALPHA.ON,
        '--glass-saturation': CONFIG.GLASS.SATURATION.ON,
        '--glass-lightness': CONFIG.GLASS.LIGHTNESS.ON,
      }),
      'SWITCHING_ON'
    )
    .add(
      to([BLOOMS[2], BLOOMS[5], BLOOMS[0], BLOOMS[3]], {
        duration: SPEEDS.ON * 5,
        stagger: SPEEDS.STAGGER,
        scale: 1.25,
        opacity: 0,
      }),
      'SWITCHING_ON'
    )
    .add(
      to([FILAMENTS[2], FILAMENTS[5], FILAMENTS[0], FILAMENTS[3]], {
        duration: SPEEDS.ON,
        stagger: SPEEDS.STAGGER,
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.ON,
      }),
      'SWITCHING_ON'
    )
    .add(
      to([GLASSES[2], GLASSES[5], GLASSES[0], GLASSES[3]], {
        duration: SPEEDS.ON * 2,
        stagger: SPEEDS.STAGGER,
        '--light-alpha': CONFIG.GLASS.ALPHA.OFF,
        '--glass-saturation': CONFIG.GLASS.SATURATION.OFF,
        '--glass-lightness': CONFIG.GLASS.LIGHTNESS.OFF,
      }),
      SPEEDS.ON
    )
    .add(
      to([FILAMENTS[2], FILAMENTS[5], FILAMENTS[0], FILAMENTS[3]], {
        duration: SPEEDS.ON * 2,
        stagger: SPEEDS.STAGGER,
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.OFF,
      }),
      SPEEDS.ON
    )

  const FLASH_BACK_TL = new timeline()
    .add(
      to([GLASSES[3], GLASSES[0], GLASSES[5], GLASSES[2]], {
        duration: SPEEDS.ON,
        stagger: SPEEDS.STAGGER,
        '--light-alpha': CONFIG.GLASS.ALPHA.ON,
        '--glass-saturation': CONFIG.GLASS.SATURATION.ON,
        '--glass-lightness': CONFIG.GLASS.LIGHTNESS.ON,
      }),
      'SWITCHING_ON_2'
    )
    .add(
      fromTo(
        [BLOOMS[3], BLOOMS[0], BLOOMS[5], BLOOMS[2]],
        {
          scale: 0,
          opacity: 1,
        },
        {
          scale: 1.25,
          opacity: 0,
          duration: SPEEDS.ON * 5,
          stagger: SPEEDS.STAGGER,
        }
      ),
      'SWITCHING_ON_2'
    )
    .add(
      to([FILAMENTS[3], FILAMENTS[0], FILAMENTS[5], FILAMENTS[2]], {
        duration: SPEEDS.ON,
        stagger: SPEEDS.STAGGER,
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.ON,
      }),
      'SWITCHING_ON_2'
    )
    .add(
      to([GLASSES[3], GLASSES[0], GLASSES[5], GLASSES[2]], {
        duration: SPEEDS.ON * 2,
        stagger: SPEEDS.STAGGER,
        '--light-alpha': CONFIG.GLASS.ALPHA.OFF,
        '--glass-saturation': CONFIG.GLASS.SATURATION.OFF,
        '--glass-lightness': CONFIG.GLASS.LIGHTNESS.OFF,
      }),
      SPEEDS.ON
    )
    .add(
      to([FILAMENTS[3], FILAMENTS[0], FILAMENTS[5], FILAMENTS[2]], {
        duration: SPEEDS.ON * 2,
        stagger: SPEEDS.STAGGER,
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.OFF,
      }),
      SPEEDS.ON
    )

  const SWING_RIGHT_BACK_TL = new timeline()
    .add(
      to(CHORDS[1], {
        ease: `power${SPEEDS.EASE}.out`,
        duration: SPEEDS.SWING,
        morphSVG: {
          d: CURVES.RIGHT,
        },
      }),
      0
    )
    .add(
      to(BULBS[1], {
        ease: `power${SPEEDS.EASE}.out`,
        duration: SPEEDS.SWING,
        rotation: -CONFIG.ROTATION,
      }),
      0
    )
    .add(
      to(GLASSES[1], {
        '--light-alpha': CONFIG.GLASS.ALPHA.ON,
        '--glass-saturation': CONFIG.GLASS.SATURATION.ON,
        '--glass-lightness': CONFIG.GLASS.LIGHTNESS.ON,
        duration: SPEEDS.ON,
      }),
      0
    )
    .add(
      to(FILAMENTS[1], {
        '--filament-lightness': CONFIG.FILAMENT.LIGHTNESS.ON,
        duration: SPEEDS.ON,
      }),
      0
    )
    .add(
      to(BLOOMS[1], {
        duration: SPEEDS.ON * 5,
        stagger: SPEEDS.STAGGER,
        scale: 1.3,
        opacity: 0,
      }),
      0
    )

  const BULBS_TL = new timeline({ repeat: -1 })

  BULBS_TL.add(SWING_RIGHT_TL, 'SWING_IN')
  BULBS_TL.add(FLASH_TL, `>-${SPEEDS.ON * 2}`)
  BULBS_TL.add(SWING_LEFT_TL, `>-${SPEEDS.ON * 4}`)
  BULBS_TL.add(FLASH_BACK_TL, `>-${SPEEDS.ON}`)
  BULBS_TL.add(SWING_RIGHT_BACK_TL, `>-${SPEEDS.ON * 4}`)

  /**
   * Easter EGG - Type to Hue
   */
  const CONTAINER = document.querySelector('.banner-container')
  let HUE = ''
  const processHue = e => {
    if (e.key !== undefined && parseInt(e.key, 10) !== undefined) {
      HUE += e.key
      if (HUE.length === 3) {
        if (HUE >= 0 && HUE <= 360) {
          for (const BULB of BULBS) {
            BULB.style.setProperty('--bulb-hue', HUE)
          }
          const BANNER = document.createElement('div')
          BANNER.className = 'hue-banner'
          BANNER.style = `--hue: ${HUE};`
          BANNER.innerHTML = `Hue changed to ${HUE}`
          CONTAINER.appendChild(BANNER)
          BANNER.addEventListener('animationend', BANNER.remove)
        }
        // Reset the hue regardless of whether it is valid
        HUE = ''
      }
    }
  }
  window.addEventListener('keyup', processHue)
  // GSDevTools.create()
})()

Html Tags

<!DOCTYPE html>
<html>
<title></title>
<body>
<div id="jsi-sea-container" class="container"></div>

</body>
</html>

Css Tags

html,body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
}
.container{
	position: relative;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}
canvas{
	position: absolute;
	top: 0;
	left: 0;
}

JavaScript Tags

var HAMMERHEAD_RENDERER = {
	HAMMERHEAD_COUNT : 10,
	ADD_INTERVAL : 3,
	DELTA_THETA : Math.PI / 1000,
	ADJUST_DISTANCE : 50,
	ADJUST_OFFSET : 10,
	
	init : function(){
		this.setParameters();
		this.reconstructMethod();
		this.createHammerHeads(this.INIT_HAMMERHEAD_COUNT);
		this.bindEvent();
		this.render();
	},
	setParameters : function(){
		this.$window = $(window);
		this.$container = $('#jsi-sea-container');
		this.width = this.$container.width();
		this.height = this.$container.height();
		this.context = $('<canvas ></canvas>').attr({width : this.width, height : this.height}).appendTo(this.$container).get(0).getContext('2d');
		this.interval = this.ADD_INTERVAL;
		this.distance = Math.sqrt(Math.pow(this.width / 2, 2) + Math.pow(this.height / 2, 2));
		this.x =  this.width / 2;
		this.destinationX = this.x;
		this.theta = 0;
		this.hammerheads = [];
	},
	reconstructMethod : function(){
		this.render = this.render.bind(this);
	},
	createHammerHeads : function(){
		for(var i = 0, length = this.HAMMERHEAD_COUNT; i < length; i++){
			this.hammerheads.push(new HAMMERHEAD(this.width, this.height));
		}
	},
	bindEvent : function(){
		this.$container.on('mousemove', this.changeView.bind(this, false));
		this.$container.on('mouseout', this.changeView.bind(this, true));
	},
	changeView : function(toAdjust, event){
		this.destinationX = event.clientX - this.$container.offset().left + this.$window.scrollLeft();
		
		if(!toAdjust){
			return;
		}
		if(this.destinationX < this.ADJUST_OFFSET){
			this.destinationX = 0;
		}else if(this.distanceX > this.width - this.ADJUST_OFFSET){
			this.destinationX = this.width;
		}
	},
	render : function(){
		requestAnimationFrame(this.render);
		
		var gradient = this.context.createRadialGradient(this.width / 2, this.height / 2, 0, this.width / 2, this.height / 2, this.distance),
			rate = (1 + 0.2 * Math.sin(this.theta));
			
		gradient.addColorStop(0, 'hsl(195, 80%, ' + (90 * rate) + '%)');
		gradient.addColorStop(0.2, 'hsl(195, 100%, ' + (50 * rate) + '%)');
		gradient.addColorStop(1, 'hsl(220, 100%, ' + (10 * rate) + '%)');
		
		this.context.fillStyle = gradient;
		this.context.fillRect(0, 0, this.width, this.height);
		
		this.hammerheads.sort(function(hammerhead1, hammerhead2){
			return hammerhead1.z - hammerhead2.z;
		});
		for(var i = this.hammerheads.length - 1; i >= 0; i--){
			if(!this.hammerheads[i].render(this.context)){
				this.hammerheads.splice(i, 1);
			}
		}
		this.context.clearRect(this.x, 0, this.width - this.x, this.height);
		
		if(this.interval-- == 0){
			this.interval = this.ADD_INTERVAL;
			this.hammerheads.push(new HAMMERHEAD(this.width, this.height));
		}
		this.theta += this.DELTA_THETA;
		this.theta %= Math.PI * 2;
		
		if(this.destinationX > this.x){
			this.x = Math.min(this.x + this.ADJUST_DISTANCE, this.destinationX);
		}else{
			this.x = Math.max(this.x - this.ADJUST_DISTANCE, this.destinationX);
		}
	}
};
var HAMMERHEAD = function(width, height){
	this.width = width;
	this.height = height;
	this.init();
};
HAMMERHEAD.prototype = {
	COLOR : 'hsl(220, %s%, 30%)',
	ANGLE_RANGE : {min : -Math.PI / 8, max : Math.PI / 8},
	INIT_SCALE : 0.1,
	MAX_Z : 10,
	DELTA_PHI : Math.PI / 80,
	VELOCITY : 3,
	VERTICAL_THRESHOLD : 80,
	
	init : function(){
		this.theta = this.ANGLE_RANGE.min + (this.ANGLE_RANGE.max - this.ANGLE_RANGE.min) * Math.random();
		this.x = this.width / 2 + this.width / 4 * this.theta / Math.PI * 8;
		this.y = this.height + this.VERTICAL_THRESHOLD * this.INIT_SCALE;
		this.z = Math.random() * this.MAX_Z;
		this.vx = -this.VELOCITY * Math.cos(this.theta + Math.PI / 2);
		this.vy = -this.VELOCITY * Math.sin(this.theta + Math.PI / 2);
		
		this.phi = Math.PI * 2 * Math.random();
		this.color = this.COLOR.replace('%s', 90 - 60 * this.z / this.MAX_Z | 0);
	},
	render : function(context){
		var tailX = 20 * Math.sin(this.phi),
			angle = Math.sin(this.phi),
			height = this.height + this.VERTICAL_THRESHOLD,
			scale = this.INIT_SCALE + (1 - this.INIT_SCALE) * (height - this.y) / height * (this.MAX_Z - this.z) / this.MAX_Z;
			
		context.save();
		context.fillStyle = this.color;
		context.translate(this.x, this.y);
		context.scale(scale, scale);
		context.rotate(this.theta);
		context.beginPath();
		context.moveTo(-20, -40);
		context.bezierCurveTo(-8, -48, 8, -48, 20, -40);
		context.lineTo(20, -28);
		context.lineTo(8, -36);
		context.lineTo(8, -8);
		context.lineTo(20, 4 + 6 * angle);
		context.lineTo(8, 0);
		context.lineTo(6, 16);
		context.quadraticCurveTo(4, 32, tailX, 64);
		context.quadraticCurveTo(-4, 32, -6, 16);
		context.lineTo(-8, 0);
		context.lineTo(-20, 4 - 6 * angle);
		context.lineTo(-8, -8);
		context.lineTo(-8, -36);
		context.lineTo(-20, -28);
		context.closePath();
		context.fill();
		
		context.save();
		context.beginPath();
		context.translate(tailX, 64);
		context.rotate(-Math.sin(this.phi) * Math.PI / 6);
		context.moveTo(0, -5);
		context.lineTo(10, 15);
		context.lineTo(0, 5);
		context.lineTo(-10, 15);
		context.closePath();
		context.fill();
		context.restore();
		context.restore();
		
		this.x += this.vx * scale;
		this.y += this.vy * scale;
		this.phi += this.DELTA_PHI;
		this.phi %= Math.PI * 2;
		
		return this.y >= -this.VERTICAL_THRESHOLD;
	}
};
var MANTA_RENDERER = {
	MANTA_COUNT : 3,
	ADD_INTERVAL : 30,
	DELTA_THETA : Math.PI / 1000,
	
	init : function(){
		this.setParameters();
		this.reconstructMethod();
		this.createMantas();
		this.render();
	},
	setParameters : function(){
		this.$container = $('#jsi-sea-container');
		this.width = this.$container.width();
		this.height = this.$container.height();
		this.context = $('<canvas ></canvas>').attr({width : this.width, height : this.height}).appendTo(this.$container).get(0).getContext('2d');
		this.interval = this.ADD_INTERVAL;
		this.distance = Math.sqrt(Math.pow(this.width / 2, 2) + Math.pow(this.height / 2, 2));
		this.theta = 0;
		this.mantas = [];
	},
	reconstructMethod : function(){
		this.render = this.render.bind(this);
	},
	createMantas : function(){
		for(var i = 0, length = this.MANTA_COUNT; i < length; i++){
			this.mantas.push(new MANTA(this.width, this.height, this.context));
		}
	},
	render : function(){
		requestAnimationFrame(this.render);
		
		var gradient = this.context.createRadialGradient(this.width / 2, this.height / 2, 0, this.width / 2, this.height / 2, this.distance),
			rate = (1 + 0.2 * Math.sin(this.theta));
			
		gradient.addColorStop(0, 'hsl(195, 80%, ' + (60 * rate) + '%)');
		gradient.addColorStop(0.2, 'hsl(195, 100%, ' + (40 * rate) + '%)');
		gradient.addColorStop(1, 'hsl(220, 100%, ' + (5 * rate) + '%)');
		
		this.context.fillStyle = gradient;
		this.context.fillRect(0, 0, this.width, this.height);
		
		this.mantas.sort(function(manta1, manta2){
			return manta1.z - manta2.z;
		});
		for(var i = this.mantas.length - 1; i >= 0; i--){
			if(!this.mantas[i].render(this.context)){
				this.mantas.splice(i, 1);
			}
		}
		if(this.interval-- == 0){
			this.interval = this.ADD_INTERVAL;
			this.mantas.push(new MANTA(this.width, this.height, this.context));
		}
		this.theta += this.DELTA_THETA;
		this.theta %= Math.PI * 2;
	}
};
var MANTA = function(width, height, context){
	this.width = width;
	this.height = height;
	this.init(context);
};
MANTA.prototype = {
	COLOR : 'hsl(200, %s%, %l%)',
	ANGLE_RANGE : {min : -Math.PI / 8, max : Math.PI / 8},
	INIT_SCALE : 0.3,
	RANGE_Z : {min : 0, max : 30},
	DELTA_ANGLE : Math.PI / 160,
	VELOCITY : 2,
	VERTICAL_THRESHOLD : 400,
	
	init : function(context){
		this.angle = this.getRandomValue(this.ANGLE_RANGE);
		this.x = this.width / 2 + this.width / 3 * this.angle / Math.PI * 8;
		this.y = this.height + this.VERTICAL_THRESHOLD * this.INIT_SCALE;
		this.z = this.getRandomValue(this.RANGE_Z);
		this.vx = -this.VELOCITY * Math.cos(this.angle + Math.PI / 2);
		this.vy = -this.VELOCITY * Math.sin(this.angle + Math.PI / 2);
		this.phi = Math.PI * 2 * Math.random();
		this.theta = Math.PI * 2 * Math.random();
		this.psi = Math.PI * 2 * Math.random();
		
		var color = this.COLOR.replace('%s', 60),
			luminance = 20 * this.z / this.RANGE_Z.max | 0;
		
		this.gradient = context.createLinearGradient(-140, 0, 140, 0);
		this.gradient.addColorStop(0, color.replace('%l', 10 + luminance));
		this.gradient.addColorStop(0.1, color.replace('%l', 10 + luminance));
		this.gradient.addColorStop(0.5, color.replace('%l', 20 + luminance));
		this.gradient.addColorStop(0.9, color.replace('%l', 10 + luminance));
		this.gradient.addColorStop(1, color.replace('%l', 10 + luminance));
		this.color = this.COLOR.replace('%s', 100).replace('%l', 5 + luminance);
	},
	getRandomValue : function(range){
		return range.min + (range.max - range.min) * Math.random();
	},
	render : function(context){
		var height = this.height + this.VERTICAL_THRESHOLD,
			scale = this.INIT_SCALE + (1 - this.INIT_SCALE) * (height - this.y) / height * (this.RANGE_Z.max - this.z) / this.RANGE_Z.max * 2,
			top = (Math.sin(this.phi) < 0 ? 50 : 60) * Math.sin(this.phi);
			
		context.save();
		context.translate(this.x, this.y);
		context.scale(scale, scale);
		context.rotate(this.angle);
		
		context.fillStyle = this.color;
		context.beginPath();
		context.moveTo((225 + top) / 4, -20);
		context.lineTo((210 + top) / 4, 70 / 4);
		context.lineTo(-(210 + top) / 4, 70 / 4);
		context.lineTo(-(225 + top) / 4, -20);
		context.closePath();
		context.fill();
		
		context.lineWidth = 5;
		context.strokeStyle = this.gradient;
		context.beginPath();
		context.moveTo(0, 70);
		context.quadraticCurveTo(0, 130, 20 * Math.sin(this.theta), 190);
		context.stroke();
		
		context.fillStyle = this.gradient;
		context.beginPath();
		context.moveTo(-15, -40);
		context.bezierCurveTo(-10, -35, 10, -35, 15, -40);
		context.lineTo(30, -40);
		context.quadraticCurveTo(35, -40, 45, -30);
		context.quadraticCurveTo(50, -25, 80 + top, 0);
		context.quadraticCurveTo(60, 0, 10, 70);
		context.lineTo(-10, 70);
		context.quadraticCurveTo(-60, 0, -80 - top, 0);
		context.quadraticCurveTo(-50, -25, -45, -30);
		context.quadraticCurveTo(-35, -40, -30, -40);
		context.lineTo(-15, -40);
		context.closePath();
		context.fill();
		
		context.lineWidth = 12;
		context.strokeStyle = this.gradient;
		context.beginPath();
		context.moveTo(23, -38);
		context.quadraticCurveTo(33, -55, 23 - 10 * Math.sin(this.psi), -70);
		context.stroke();
		
		context.beginPath();
		context.moveTo(-23, -38);
		context.quadraticCurveTo(-33, -55, -23 + 10 * Math.sin(this.psi), -70);
		context.stroke();
		
		context.lineWidth = 1;
		context.strokeStyle = this.color;
		context.beginPath();
		
		for(var i = 0; i < 5; i++){
			var y = -10 + i * 8 + (1 - Math.sin(this.phi)) * 3;
			context.moveTo(10, -20 + i * 8);
			context.quadraticCurveTo(20, -15 + i * 8, 30, y);
			context.moveTo(-10, -20 + i * 8);
			context.quadraticCurveTo(-20, -15 + i * 8, -30, y);
		}
		context.stroke();
		context.restore();
		
		this.x += this.vx * scale;
		this.y += this.vy * scale;
		this.phi += this.DELTA_ANGLE;
		this.phi %= Math.PI * 2;
		this.theta += this.DELTA_ANGLE;
		this.theta %= Math.PI * 2;
		this.psi += this.DELTA_ANGLE;
		this.psi %= Math.PI * 2;
		
		return this.y >= -this.VERTICAL_THRESHOLD;
	}
};
$(function(){
	MANTA_RENDERER.init();
	HAMMERHEAD_RENDERER.init();
});