#container
  canvas(height=900, width=1600)
View Compiled
body {
  background-color: #444;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  overflow: hidden;
}

#container {
  transform-origin: 50% 0;
}
View Compiled
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class OffScreenCanvas {
  constructor(width, height) {
    let c = document.createElement('canvas');
    c.width = width;
    c.height = height;
    this.ctx = c.getContext('2d');
    this.canvas = c;
  }
}

class Opening {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  
  draw($) {
    let center = this.x - $.state.pos.x,
        l = getCirclePoint(600, 800, center - ($.platform.width / 2)),
        r = getCirclePoint(600, 800, center + ($.platform.width / 2));
    
    if(l > r) {
      let sl = getCirclePoint(560, 800, center - ($.platform.width / 2)),
          sr = getCirclePoint(560, 800, center + ($.platform.width / 2)),
          c = new OffScreenCanvas(1600, 250),
          sc = new OffScreenCanvas(1600, 250),
          smallDoor = drawDoor(sc, '#b9e2d7', sr, sl - sr, 250, '#262525'),
          bigDoor = drawDoor(c, $.ctx.createPattern(smallDoor, 'no-repeat'), r, l - r, 250);

      $.ctx.drawImage(bigDoor, 0, this.y + $.state.pos.y);
    }
  }
}

function prepareGraphics() {
  const b = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/21151/';
  
  prepareImage($, b + 'c-standing.png', 'standing', 0, false);
  prepareImage($, b + 'c-standing.png', 'standing', 1, true);
  
  prepareImage($, b + 'c-jumping3.png', 'jumpingUp', 0, false);
  prepareImage($, b + 'c-jumping3.png', 'jumpingUp', 1, true);
  
  prepareImage($, b + 'c-jumpingdown.png', 'jumpingDown', 0, false);
  prepareImage($, b + 'c-jumpingdown.png', 'jumpingDown', 1, true);

  prepareImage($, b + 'Slice 1_copy.png', 'runningLeft', 0, false);
  prepareImage($, b + 'Slice 1_copy.png', 'runningLeft', 1, false);
  prepareImage($, b + 'c-walking3.png', 'runningLeft', 2, false);
  prepareImage($, b + 'c-walking3.png', 'runningLeft', 3, false);
  
  prepareImage($, b + 'Slice 1_copy.png', 'runningRight', 0, true);
  prepareImage($, b + 'Slice 1_copy.png', 'runningRight', 1, true);
  prepareImage($, b + 'c-walking3.png', 'runningRight', 2, true);
  prepareImage($, b + 'c-walking3.png', 'runningRight', 3, true);
}

function prepareImage($, src, type, index, flipped) {
  let temp = new OffScreenCanvas(317, 300),
      image = new Image();
  
  image.onload = function() {
    if(flipped) {
      temp.ctx.save();
      temp.ctx.scale(-1,1);
    }
    
    temp.ctx.drawImage(image, 0, 0, 317 * (flipped ? -1 : 1), 300);
    
    if(flipped) {
      temp.ctx.restore();
    }
    
    $.animationFrames[type][index] = temp.canvas;
  }
  
  image.src = src;
  
  return temp.canvas;
}

function drawDoor(c, color, x, width, height, bg) {
    let y = 90;
    
    if(bg) {
      c.ctx.fillStyle = bg;
      c.ctx.fillRect(0, 0, c.canvas.width, c.canvas.height);
    }
  
    c.ctx.fillStyle = color;
    c.ctx.beginPath();
    c.ctx.moveTo(x, y);
    c.ctx.lineTo(x, y + height);
    c.ctx.lineTo(x + width, y + height);
    c.ctx.lineTo(x + width, y);
    c.ctx.ellipse(x + (width / 2), y, width / 2, 90, 0, 0, Math.PI, true);
    c.ctx.fill();
  
    return c.canvas;
}

class Platform {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.infront = false;
    this.outerBox = null;
  }
  
  getY($) {
    return this.y + $.state.pos.y;
  }
  
  isInFront($) {
    let center = this.x - $.state.pos.x,
      innerBox = getBox($, 600, center),
      outerBox = getBox($, 680, center);
    
    this.infront = outerBox.left > outerBox.right;
    return this.infront;
  }
  
  drawFront($) {
    $.ctx.fillStyle = $.colors.wood2;
    $.ctx.fillRect(this.outerBox.left, this.getY($), this.outerBox.width, $.platform.height);
  }
  
  draw($) {
    let center = this.x - $.state.pos.x,
      innerBox = getBox($, 600, center),
      outerBox = getBox($, 680, center),
      isLeftSide = innerBox.left > outerBox.left;
    
    this.infront = outerBox.left > outerBox.right;
    
    for (let dir of ['left', 'right']) {
      let adjust = dir === 'left' ? outerBox.unit : (outerBox.unit * 6),
      outer = {
        top: {
          left: new Point(outerBox.left + adjust, this.getY($) + $.platform.height),
          right: new Point(outerBox.left + outerBox.unit + adjust, this.getY($) + $.platform.height)
        },
        bottom: {
          left: new Point(innerBox.left + adjust, this.getY($) + 70),
          right: new Point(innerBox.left + innerBox.unit + adjust, this.getY($) + 70)
        }
      },
      inner = {
        top: {
          left: new Point(outerBox.left + adjust, this.getY($) + ($.platform.height - 10)),
          right: new Point(outerBox.left + outerBox.unit + adjust, this.getY($) + ($.platform.height - 10))
        },
        bottom: {
          left: new Point(innerBox.left + adjust, this.getY($) + 60),
          right: new Point(innerBox.left + innerBox.unit + adjust, this.getY($) + 60)
        }
      };
      
      drawPolygon($, $.colors.wood3, inner.top.left, inner.bottom.left, inner.bottom.right, inner.top.right);
      drawPolygon($, $.colors.wood4, outer.top.left, outer.bottom.left, outer.bottom.right, outer.top.right);
      
      if(!isLeftSide) {
        drawPolygon($, $.colors.wood5, inner.top.right, outer.top.right, outer.bottom.right, inner.bottom.right);
      } else {
        drawPolygon($, $.colors.wood5, inner.top.left, outer.top.left, outer.bottom.left, inner.bottom.left);
      }
    }
    
    $.ctx.fillStyle = $.colors.wood1;
    if(isLeftSide) {
      $.ctx.fillRect(innerBox.left, this.getY($), outerBox.left - innerBox.left, $.platform.height);
    } else {
      $.ctx.fillRect(outerBox.right, this.getY($), innerBox.left - outerBox.left, $.platform.height);
    }
    
    this.outerBox = outerBox;
  }
}

function drawPolygon($, color, ...points) {
  $.ctx.fillStyle = color;
  $.ctx.beginPath();
  $.ctx.moveTo(points[0].x, points[0].y);
  for (let i = 1; i < points.length; i++) {
    $.ctx.lineTo(points[i].x, points[i].y);
  }
  $.ctx.fill();
}

function getBox($, radius, center) {
    let l = getCirclePoint(radius, 800, center - ($.platform.width / 2)),
        r = getCirclePoint(radius, 800, center + ($.platform.width / 2));
  
    return {
      left: l,
      right: r,
      width: r - l,
      unit: (r - l) / 8
    };
}

var fallbackCanvas = new OffScreenCanvas(10, 10).canvas;

var $ = {
      container: null,
      canvas: null,
      ctx: null,
      platforms: [new Platform(1600, 600),
                  new Platform(1585, 600),
                  new Platform(1570, 600),
                  new Platform(1540, 500),
                  new Platform(1525, 500),
                  new Platform(1480, 500),
                  new Platform(1465, 500),
                 new Platform(1435, 400),
                 new Platform(1415, 270),
                 new Platform(1435, 135),
                 new Platform(1465, 40),
                 new Platform(1480, 40),
                 new Platform(1500, -80),
                 new Platform(1520, -200),
                 new Platform(1520, -335),
                 new Platform(1490, -460),
                 new Platform(1460, -535),
                 new Platform(1430, -610),
                 new Platform(1415, -610),
                 new Platform(1370, -610),
                 new Platform(1355, -610),
                 new Platform(1355, -610),
                 new Platform(1330, -710),
                 new Platform(1305, -810),
                 new Platform(1280, -910),
                 new Platform(1265, -910),
                 new Platform(1220, -910),
                 new Platform(1205, -910),
                 new Platform(1150, -910),
                 new Platform(1100, -910)],
      openings: [new Opening(1600, 350),
                 new Opening(1205, -1160)],
      brick: {
        shine: "",
        shade: "rgba(256, 256, 256, 0.8)",
        color: "rgba(186, 186, 173, 0.8)", // #BABAAD
        width: 16,
        height: 48,
        padding: 4
      },
      platform: {
        height: 22,
        width: 13, /* Degrees */
        color: '#5A4142'
      },
      tower: {
        width: 1200,
        shadowWidth: 130,
        skyWidth: 200
      },
      sky: {
        bg: '#1F0B22',
        starSizes : [2, 3, 4, 5],
        starColors: ['#6C586F',
                '#857188',
                '#D1BDD4',
                '#E6D1A8']
      },
      colors: {
        bg: '#FBD0D0',
        wood1: '#BC8550', // side
        wood2: '#ECC897', // front
        wood3: '#4B3937', // back support
        wood4: '#FFB287', // front support
        wood5: '#D58212' // side support
      },
      settings: {
        maxSpeed: 0.09, // left right
        minSpeed: 0.01, // left right
        friction: 0.7, // left right
        acceleration: 0.02, // left right
        jump: {
          gravity: {
            boost: 0.0014,
            normal: 0.003,
            down: 0.004
          },
          maxSpeed: 0.6,
          fallStartSpeed: 0.07,
          friction: 0.98
        }
      },
      storage: {
        bricks: null,
        sky: null,
        shadows: null
      },
      input: {
        left: false,
        right: false,
        jump: false
      },
      animationFrames: {
        standing: [fallbackCanvas, fallbackCanvas],
        jumpingUp: [fallbackCanvas, fallbackCanvas],
        jumpingDown: [fallbackCanvas, fallbackCanvas],
        runningLeft: [fallbackCanvas, fallbackCanvas, fallbackCanvas, fallbackCanvas],
        runningRight: [fallbackCanvas, fallbackCanvas, fallbackCanvas, fallbackCanvas]
      },
      savedState: null,
      state: {
        paused: false,
        titles: {
          opacity: 0,
          ready: false,
          text: "Game Over"
        },
        climbstarted: false,
        time: null,
        dt: null,
        climbspeed: {
          normal: 0.05,
          fast: 0.12
        },
        pos: {
          x: 1485,
          y: 0
        },
        activePlatforms: [],
        jump: {
          isGrounded: true,
          isJumping: false,
          isBoosting: false,
          speed: 0,
          nextY: 0
        },
        player: {
          dir: 1,
          x: 725,
          y: 350,
          prevY: 350,
          speed: 0,
          animationFrame: 0,
          animationFrameCount: 0
        }
      }
};

$.container = document.getElementById('container');
$.canvas = document.getElementsByTagName('canvas')[0];
$.ctx = $.canvas.getContext('2d');

resize();
if(!$.savedState) {
  $.savedState = JSON.parse(JSON.stringify($.state));
}

prepareGraphics();
draw();

window.addEventListener("keydown", keyDown, false);
window.addEventListener("keyup", keyUp, false);

function keyUp(e) {
  move(e, false);
}

function keyDown(e) {
  move(e, true);
}

function move(e, keyDown) {
    if(e.keyCode === 37)
      $.input.left = keyDown;
    if(e.keyCode === 39)
      $.input.right = keyDown;
    if(e.keyCode === 32)
      $.input.jump = keyDown;
}

function resize() {
  $.rect = $.container.getBoundingClientRect();
  
  if($.canvas.height > window.innerHeight) {
      $.container.style.transform = `scale(${ window.innerHeight / $.canvas.height })`;
  }
}

window.addEventListener('resize', function(event) {
  resize();
});

function draw() {
    let now = new Date().getTime();
    $.state.dt = now - ($.state.time || now);
    $.state.time = now;
    
    if(!$.state.paused) {
      doCalculations();
    }
  
    if(!$.state.paused && $.state.titles.opacity !== 100) {
      drawSky();
      drawPlatforms(false);
      drawBricks();
      drawDoors();
      drawShadows();
      drawPlatforms(true);
      drawPlayer($);
    }
  
    if($.state.paused) {
      drawTitles();
    }
  
    requestAnimationFrame(draw);
}

function drawTitles() {
  if($.state.titles.opacity < 100) {
    $.state.titles.opacity += Math.floor($.state.dt * 0.2);
  }

  if($.state.titles.opacity > 100)
    $.state.titles.opacity = 100;

  $.ctx.fillStyle = "rgba(0, 0, 0, " + $.state.titles.opacity / 100 + ")";
  $.ctx.rect(0, 0, $.canvas.width, $.canvas.height);
  $.ctx.fill();

  $.ctx.fillStyle = "rgba(251, 199, 15, " + $.state.titles.opacity / 100 + ")";
  $.ctx.font = "96px 'Germania One', cursive";
  $.ctx.fillText($.state.titles.text, 600, 520 - (easing($.state.titles.opacity / 100) * 40));
  
  if($.state.titles.opacity == 100 && !$.input.jump) {
    $.state.titles.ready = true;
  }
  
  if($.state.titles.ready && $.input.jump) {
    $.state = JSON.parse(JSON.stringify($.savedState));
  }
}

function easing (n) {
  // https://github.com/component/ease
  var s = 1.70158;
  return --n * n * ((s + 1) * n + s) + 1;
}

function drawPlayer($) {
  let drawY = $.state.player.y + $.state.pos.y - 48,
      drawX = $.state.player.x - ($.state.player.dir ? 120 : 80);

  if($.state.jump.isJumping) {
    if($.state.jump.speed > 0) {
      $.ctx.drawImage($.animationFrames.jumpingUp[$.state.player.dir], drawX, drawY);
    } else {
      $.ctx.drawImage($.animationFrames.jumpingDown[$.state.player.dir], drawX, drawY);
    }
  }
  else if($.state.player.speed !== 0) {
    if($.state.player.dir) {
       $.ctx.drawImage($.animationFrames.runningRight[$.state.player.animationFrame], drawX, drawY);
    } else {
       $.ctx.drawImage($.animationFrames.runningLeft[$.state.player.animationFrame], drawX, drawY);
    }
  } else {
       $.ctx.drawImage($.animationFrames.standing[$.state.player.dir], drawX, drawY);
  }

  //$.ctx.fillRect($.state.player.x, $.state.player.y + $.state.pos.y, 150, 250);
  $.state.player.animationFrameCount += $.state.dt;
  
  if($.state.player.animationFrameCount > 50) {
    $.state.player.animationFrame += 1;
    $.state.player.animationFrameCount = 0;
  }
  
  if($.state.player.animationFrame > 3) {
    $.state.player.animationFrame = 0;
  }
}

function drawDoors() {
  for (let i = 0; i < $.openings.length; i++) {
    let opening = $.openings[i];
    
    if(opening.x < $.state.pos.x - 40)
      continue;
    
    if(opening.x > ($.state.pos.x + 220))
      continue;
    
    opening.draw($);
  }
}

function drawPlatforms(drawInfrontPlatforms) {
  if(drawInfrontPlatforms) {
    $.state.activePlatforms = [];
  }
  
  for (let i = 0; i < $.platforms.length; i++) {
    let platform = $.platforms[i];
    
    if(platform.x < $.state.pos.x - 40)
      continue;
    
    if(platform.x > ($.state.pos.x + 220))
      continue;
    
    if(drawInfrontPlatforms) {
      if(platform.isInFront($)) {
        platform.draw($);
        $.state.activePlatforms.push(platform);
      }
    }
    else if(!platform.isInFront($)) {
        platform.draw($);
    }
  }
  
  for (let i = 0; i < $.state.activePlatforms.length; i++) { 
    $.state.activePlatforms[i].drawFront($);
  }
}

function doCalculations() {
  if($.input.left) {
    $.state.player.speed += $.settings.acceleration;
  }
  else if($.input.right) {
    $.state.player.speed -= $.settings.acceleration;
  } else if($.state.player.speed !== 0) {
    $.state.player.speed *= $.state.jump.isJumping ? $.settings.jump.friction : $.settings.friction;
  }
  
  if(Math.abs($.state.player.speed) > $.settings.maxSpeed) {
    $.state.player.speed = $.state.player.speed > 0 ? $.settings.maxSpeed : -1 * $.settings.maxSpeed;
  } else if(Math.abs($.state.player.speed) < $.settings.minSpeed) {
    $.state.player.speed = 0;
  }
  
  if($.state.player.speed !== 0) {
    let currentSpeed = $.state.jump.isJumping ? ($.state.player.speed * 0.7) : $.state.player.speed;
    $.state.pos.x += $.state.player.speed < 0 ? Math.ceil(currentSpeed * $.state.dt) : Math.floor(currentSpeed * $.state.dt);
    $.state.player.dir = currentSpeed > 0 ? 0 : 1;
  }
  
  if(!$.state.climbstarted && $.input.jump) {
    $.state.climbstarted = true;
  }
  
  if($.input.jump || $.state.jump.isJumping) {
    if($.state.jump.isGrounded) {
      $.state.jump.isGrounded = false;
      $.state.jump.isJumping = true;
      $.state.jump.isBoosting = true;
      $.state.jump.speed = $.settings.jump.maxSpeed;
    }
    
    if($.state.jump.isJumping) {
      let upwards = $.state.jump.speed > 0,
          adjust = $.state.dt < 30 ? (30 - $.state.dt) : 0; // .·´¯`(>▂<)´¯`·.
      
      if(!upwards && $.state.jump.isBoosting) {
        $.state.jump.isBoosting = false;
      }
      
      $.state.player.prevY = $.state.player.y;
      $.state.player.y -= ($.state.jump.speed * $.state.dt);
      $.state.jump.speed -= ($.settings.jump.gravity[upwards ? ($.state.jump.isBoosting ? 'boost' : 'normal') : 'down'] - (adjust * 0.00002)) * $.state.dt;
    }
  }
  
  if($.state.jump.isBoosting && !$.input.jump) {
    $.state.jump.isBoosting = false;
  }

  if($.state.climbstarted && $.state.pos.y < 1440) {
    $.state.pos.y += (($.state.player.y + $.state.pos.y) < 250 ? $.state.climbspeed.fast : $.state.climbspeed.normal) * $.state.dt;
  }
  
  collisionDetection();
  
  if(($.state.player.y + $.state.pos.y) > 900) {
    $.state.paused = true;
  }
}

function collisionDetection() {
  if($.state.jump.isJumping && $.state.jump.speed < 0) { 
    for (let i = 0; i < $.state.activePlatforms.length; i++) {
      let platform = $.state.activePlatforms[i];

      if(Math.abs(platform.x - ($.state.pos.x + 90)) < 10) {
        let playerFloor = $.state.player.y + 250,
            playerFloorPrev = $.state.player.prevY + 250;
        
        if(playerFloor > platform.y &&
           playerFloorPrev < platform.y) {       
          $.state.player.y = platform.y - 250;
          $.state.jump.isGrounded = true;
          $.state.jump.isJumping = false;
          $.state.jump.isBoosting = false;
          $.state.jump.speed = 0;
        }
      }
    }
  } else if($.state.jump.isGrounded) {
    let groundToStandOnFound = false;
    
    for (let i = 0; i < $.state.activePlatforms.length; i++) {
      let platform = $.state.activePlatforms[i];
      
      if(Math.abs(platform.x - ($.state.pos.x + 90)) < 10) {
        if(platform.y - ($.state.player.y + 250) === 0) {
          groundToStandOnFound = true;
          break;
        }
      }
    }
    
    if(!groundToStandOnFound) {
      $.state.jump.isGrounded = false;
      $.state.jump.isJumping = true;
      $.state.jump.isBoosting = true;
      $.state.jump.speed = $.settings.jump.fallStartSpeed;
    }
  }
}

function drawSky() {
  if($.storage.sky == null) {
    let height = $.canvas.height,
        temp = new OffScreenCanvas($.canvas.width, height);
    temp.ctx.fillStyle = $.sky.bg;
    temp.ctx.fillRect(0, 0, $.canvas.width, height);

    for (let i = 0; i < 150; i++) {
      let starSize = Math.floor(Math.random() * $.sky.starSizes.length);
      temp.ctx.fillStyle = $.sky.starColors[starSize];
      temp.ctx.beginPath();
      temp.ctx.arc(Math.floor(Math.random() * $.canvas.width),
                Math.floor(Math.random() * height), 
                $.sky.starSizes[starSize], 0, 2 * Math.PI);
      temp.ctx.fill();
    }
    
    $.storage.sky = temp.canvas;
  } else {
    let skypos = ((($.state.pos.x - 2000) % 200) * 8) * -1,
        skyYPos = $.state.pos.y % $.canvas.height;
    
    $.ctx.drawImage($.storage.sky, skypos, skyYPos);
    $.ctx.drawImage($.storage.sky, skypos - $.canvas.width, skyYPos);
    $.ctx.drawImage($.storage.sky, skypos, skyYPos - $.canvas.height);
    $.ctx.drawImage($.storage.sky, skypos - $.canvas.width, skyYPos - $.canvas.height);
  }
}

function drawShadows() {
    if($.storage.shadows) {
      $.ctx.drawImage($.storage.shadows, $.tower.skyWidth, 0);
    } else {
      var temp = new OffScreenCanvas($.tower.width, $.canvas.height);
      drawTowerShadow(temp.ctx, 0, $.tower.shadowWidth + 80, $.canvas.height, '#727C80', 'transparent');
      drawTowerShadow(temp.ctx, 0, $.tower.shadowWidth, $.canvas.height, '#00011F', 'transparent');
      drawTowerShadow(temp.ctx, temp.canvas.width - ($.tower.shadowWidth + 80), $.tower.shadowWidth + 80, $.canvas.height, 'transparent', '#727C80');
      drawTowerShadow(temp.ctx, temp.canvas.width - $.tower.shadowWidth, $.tower.shadowWidth, $.canvas.height, 'transparent', '#00011F');
      $.storage.shadows = temp.canvas;
    }
}

function drawTowerShadow(ctx, start, width, height, from, to) {
  let grd = ctx.createLinearGradient(start, 0, start + width, 0);
  grd.addColorStop(0, from);
  grd.addColorStop(1, to);

  ctx.fillStyle = grd;
  ctx.fillRect(start, 0, width, height);
}

function drawBricks() {
  let brickRowHeight = ($.brick.height * 2) + ($.brick.padding * 2);
  
  if(!$.storage.bricks) {
    $.storage.bricks = {};
    for (let i = 0; i < 16; i++) { 
      $.storage.bricks["brick" + i] = brickFactory(brickRowHeight, i);
    }
  }
  
  for (let row = -1; row < 12; row++) {
    $.ctx.drawImage($.storage.bricks["brick" + ($.state.pos.x % $.brick.width)], $.tower.skyWidth, (brickRowHeight * row) + ($.state.pos.y % brickRowHeight));
  }
}

function brickFactory(height, pos) {
  let temp = new OffScreenCanvas($.tower.width, height),
      x = $.brick.padding,
      y = $.brick.padding,
      pointA = { x: 0, y: 0 },
      pointB,
      step = $.brick.width, 
      halfrow = true,
      gradient = temp.ctx.createLinearGradient(0, 0, temp.canvas.width, height);
  
  gradient.addColorStop(0,"black");
  gradient.addColorStop(0.35, "#353637");
  gradient.addColorStop(0.65, "#353637");
  gradient.addColorStop(1,"black");

  temp.ctx.fillStyle = gradient;

  temp.ctx.fillRect(0, 0, temp.canvas.width, temp.canvas.height);
  
  for (let i = 0; i < 2; i++) {
    for (let j = 180 + pos; j <= 360; j += step) {
      pointA = getCirclePoint(600, 600, j)
      
      if(halfrow) {
        j += step / 2;
        halfrow = false;
      }
      
      pointB = getCirclePoint(600, 600, j + step);
      
      // Main
      temp.ctx.fillStyle = $.brick.color;
      temp.ctx.fillRect(pointA, y, pointB - pointA - $.brick.padding, $.brick.height);
      
      // Shade
      temp.ctx.fillStyle = $.brick.shade;
      temp.ctx.fillRect(pointA, y, pointB - pointA - $.brick.padding, 3);
    }
    
    y += $.brick.padding;
    y += $.brick.height;
  }
  
  return temp.canvas;
}

function getCirclePoint(radius, center, angle) {
    var radian = (angle / 180) * Math.PI;
  
    return (center + radius * Math.cos(radian));
}

function norm(value, min, max) {
  return (value - min) / (max - min);
}

function lerp(norm, min, max) {
  return (max - min) * norm + min;
}

function map(value, sourceMin, sourceMax, destMin, destMax) {
  return lerp(norm(value, sourceMin, sourceMax), destMin, destMax);
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}
View Compiled

External CSS

  1. https://fonts.googleapis.com/css?family=Germania+One

External JavaScript

This Pen doesn't use any external JavaScript resources.