<!-- 
Project forked from:
https://codepen.io/jackrugile/pen/ZYMJpd
-->

<link href="https://fonts.googleapis.com/css?family=Oxygen:300" rel="stylesheet">
  <canvas id="c"></canvas>

  <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha256-/SIrNqv8h6QGKDuNoLGA4iret+kyesCkHGzVUUV0shc=" crossorigin="anonymous"></script>
  <script src="js/index.js"></script>
  <div class="modal">
    <div>
      <p>St. Patrick is famous for banishing snakes from Ireland <a href="https://en.wikipedia.org/wiki/Saint_Patrick#Patrick_banishes_all_snakes_from_Ireland" target="_blank">(source)</a>. In St. Patrick's honor:</p>
      <button>Chase Snakes With Shamrocks</button>
    </div>
  </div>
  <div class="modal-overlay" id="modal-overlay"></div>
  <script>
  $(document).ready(function() {
    $('.modal button').click(function() {
      $('div.modal').remove();
      $('div.modal-overlay').remove();
    });
  });
  </script>
html,
body {
  height: 100%;
}

body {
  background-color: #37444F;
  font-family: 'Oxygen', sans-serif;
  line-height: 2rem;
}

a {
  color: #F65A5B;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

canvas {
  display: block;
}

body {
  word-wrap: break-word;
}

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 80vw;
  max-width: 100%;
  height: 30vh;
  max-height: 100%;
  background-color: rgba(255,255,255,1);
  border-radius: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  z-index: 1010;
  font-size: calc(1rem + 1vw);
  padding: 3rem 4rem;
}

.modal-overlay {
  z-index: 1000;
  background-color: rgba(0, 0, 0, 0.5);
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

}

.modal button {
  background-color: #34B3A0;
  border: none;
  display: block;
  width: 100%;
  margin-top: 2rem;
  padding: 1.5rem 2.5rem;
  color: white;
  font-size: calc(0.9rem + 1vw);
  font-weight: 300;
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.05rem;
  animation-name: lantern;
  animation-duration: 3s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-play-state: running;
}

.text {
  font: 16px/300px 'Lato', sans-serif;
  width: 300px;
  height: 300px;
  position: absolute;
  margin: auto;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
  user-select: none;
}

.click-explosion {
  width: 50px;
  height: 50px;
  background-image: url('https://cloud.githubusercontent.com/assets/6455018/24014398/2d53347a-0a5b-11e7-831e-0fb82b1d1008.png');
  animation: click-explode 1s ease-out forwards;
  position: absolute;
  z-index: 400;
}

.click-explosion-right {
  width: 50px;
  height: 50px;
  border-radius: 50px;
  background: #f00;
  animation: click-explode 1s ease-out forwards;
  position: absolute;
  z-index: 400;
}

@keyframes click-explode {
  100% {
    transform: scale(1.5);
    opacity: 0;
  }
}

@keyframes lantern {
  from {
    background-color: #34B3A0;
  }
  50% {
    background-color: HSL(171, 55%, 30%);
  }
  to {
    background-color: #34B3A0;
  }
}
const a = document.getElementById('c');
var c = a.getContext('2d');

const chains = [];
const chainCount = 30;
const entityCount = 20;
let w = a.width;
let h = a.height;
let cx = w / 2;
let cy = h / 2;
let mx = cx;
let my = cy;
let md = 0;
const maxa = 2;
const maxv = 1;
const avoidTick = 20;
const avoidThresh = 50;

function rand(min, max) {
  return (Math.random() * (max - min)) + min;
}

function Impulse() {
  this.x = cx;
  this.y = cy;
  this.ax = 0;
  this.ay = 0;
  this.vx = 0;
  this.vy = 0;
  this.r = 1;
}

Impulse.prototype.step = function step() {
  this.x += this.vx;
  this.y += this.vy;
  if (this.x + this.r >= w || this.x <= this.r) {
    this.vx = 0;
    this.ax = 0;
  }
  if (this.y + this.r >= h || this.y <= this.r) {
    this.vy = 0;
    this.ay = 0;
  }
  if (this.x + this.r >= w) {
    this.x = w - this.r;
  }
  if (this.x <= this.r) {
    this.x = this.r;
  }
  if (this.y + this.r >= h) {
    this.y = h - this.r;
  }
  if (this.y <= this.r) {
    this.y = this.r;
  }

  if (md) {
    this.vx += (this.x - mx) * 0.03;
    this.vy += (this.y - my) * 0.03;
  }

  this.ax += rand(-0.4, 0.4);
  this.ay += rand(-0.4, 0.4);
  this.vx += this.ax;
  this.vy += this.ay;
  this.ax *= Math.abs(this.ax) > maxa
    ? 0.75
    : 1;
  this.ay *= Math.abs(this.ay) > maxa
    ? 0.75
    : 1;
  this.vx *= Math.abs(this.vx) > maxv
    ? 0.75
    : 1;
  this.vy *= Math.abs(this.vy) > maxv
    ? 0.75
    : 1;
};

Chain.prototype.step = function () {
  this.impulse.step();
  this.branches.forEach((branch) => {
    branch.step();
  });

  this.branches.forEach((branch) => {
    branch.draw();
  });
};

function Branch(opt) {
  this.entities = [];
  this.chain = opt.chain;
  this.avoiding = 0;
  let entity;
  for (let i = 0; i < entityCount; i++) {
    entity = new Entity({
      branch: this,
      i,
      x: cx,
      y: cy,
      radius: 1 + (((entityCount - i) / entityCount) * 5),
      damp: 0.2,
      attractor: i === 0
        ? opt.attractor
        : this.entities[i - 1]
    });
    this.entities.push(entity);
  }
}

function Chain() {
  this.branches = [];
  this.impulse = new Impulse();
  this.branches.push(new Branch({chain: this, attractor: this.impulse}));
}

Branch.prototype.step = function () {
  let i = chains.length;
  while (i--) {
    const impulse = this.chain.impulse;
    const oImpulse = chains[i].impulse;
    const dx = oImpulse.x - impulse.x;
    const dy = oImpulse.y - impulse.y;
    const dist = Math.sqrt((dx * dx) + (dy * dy));
    if (!md && impulse !== oImpulse && dist < avoidThresh) {
      impulse.ax = 0;
      impulse.ay = 0;
      impulse.vx -= dx * 0.1;
      impulse.vy -= dy * 0.1;
      this.avoiding = avoidTick;
    }
  }

  this.entities.forEach((entity) => {
    entity.step();
  });

  if (this.avoiding > 0) {
    this.avoiding--;
  }
};

Branch.prototype.draw = function () {
  c.beginPath();
  c.moveTo(this.entities[0].x, this.entities[0].y);
  this.entities.forEach((entity, i) => {
    if (i > 0) {
      c.lineTo(entity.x, entity.y);
    }
  });

  this.entities.forEach((entity) => {
    c.save();
    c.translate(entity.x, entity.y);
    c.beginPath();
    c.rotate(entity.rot);
    c.fillRect(-entity.radius, -entity.radius, entity.radius * 3, entity.radius * 3);
    c.restore();
  });
};

function Entity(opt) {
  this.branch = opt.branch;
  this.i = opt.i;
  this.x = opt.x;
  this.y = opt.y;
  this.vx = 0;
  this.vy = 0;
  this.radius = opt.radius;
  this.attractor = opt.attractor;
  this.damp = opt.damp;
}

Entity.prototype.step = function () {
  this.vx = (this.attractor.x - this.x) * this.damp;
  this.vy = (this.attractor.y - this.y) * this.damp;
  this.x += this.vx;
  this.y += this.vy;
  this.av = (Math.abs(this.vx) + Math.abs(this.vy)) / 2;

  const dx = this.attractor.x - this.x;
  const dy = this.attractor.y - this.y;
  this.rot = Math.atan2(dy, dx);
};

function loop() {
  requestAnimationFrame(loop);

  c.globalCompositeOperation = 'destination-out';
  c.fillStyle = md
    ? '#F65A5B'
    : '#34B3A0';
  c.fillRect(0, 0, a.width, a.height);
  c.globalCompositeOperation = 'lighter';

  chains.forEach((chain) => {
    chain.step();
  });

  tick++;
}

function resize() {
  a.width = window.innerWidth;
  a.height = window.innerHeight;
  w = a.width;
  h = a.height;
  cx = w / 2;
  cy = h / 2;
}

function createShamrock(e) {
  const elem = $('<div class="click-explosion"></div>').appendTo('body');
  const mouseX = e.pageX - 20;
  const mouseY = e.pageY - 20;
  elem.css('top', mouseY);
  elem.css('left', mouseX);
  setTimeout(() => {
    elem.remove();
  }, 1000);
}

window.addEventListener('resize', resize);

window.addEventListener('mousedown', (e) => {
  md = 1;
  createShamrock(e);
});

window.addEventListener('touchstart', (e) => {
  md = 1;
  createShamrock(e);
});

window.addEventListener('mouseup', () => {
  md = 0;
});

window.addEventListener('touchend', (e) => {
  md = 1;
});

window.addEventListener('mousemove', (e) => {
  mx = e.clientX;
  my = e.clientY;
});

resize();

for (let i = 0; i < chainCount; i++) {
  chains.push(new Chain());
}

loop();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.