#app{":class" => "{'pressed': pressed}"}
  %audio{:controls => "", :crossorigin => "anonymous", :style => "display: none;"}
    %source{:src => "https://assets.codepen.io/217233/ktkBgAudio.mp3", :type => "audio/mp3"}
  .game
    .game_lose{":class" => "{'show': gameover}"}
      .inner
        %h1 YOU RAN OUT OF TIME!
        %p King Trost was victorious. Refresh to try again.
        %p 
          Be sure to 
          %a{:href => 'https://www.codepen.io/jcoulterdesign'} follow me on Codepen 
          for more pointless stuff.
    .game_win{":class" => "{'show': gamewin}"}
      .inner
        %h1 YOU WIN!
        %p King Trost was slain. Congratulations!
        %p 
          Be sure to 
          %a{:href => 'https://www.codepen.io/jcoulterdesign'} follow me on Codepen 
          for more pointless stuff.
    .game_intro{":class" => "{'gamestarted': gameStarted}"}
      .game_intro__inner.start{":class" => "{'gamestarted': introClicked}"}
        %img{:src => 'https://assets.codepen.io/217233/ktkLogo.png'}
        %br
        %p.begin{'@click' => 'introClicked = !introClicked, audioController.play("crushyou")'} BEGIN GAME
      .game_intro__inner.end{":class" => "{'gamestarted': !introClicked}"}
        .dialogue
          %span King Trost
          %p HAHAHA, YOU HONESTLY THINK YOU CAN DEFEAT ME!? MY MEN WILL CRUSH YOU, BOY.
          %p.continue{'@click' => 'introClicked = !introClicked, startGame()'} Continue
        %img.king{:src => 'https://assets.codepen.io/217233/kingTrost.png'}
    .game_inner
      .game_inner__tooltip{":class" => "{'active': tooltip}"}
        .space
        .smash Smash the spacebar!
      .game_inner__footer
        .madeby
          Made by 
          %a{:href => 'https://www.codepen.io/jcoulterdesign', :target => '_blank'} Jamie Coulter
        .resources
          .ui_xp
            %img{:src => 'https://assets.codepen.io/217233/ktkXpIxon.png'}
            {{ xp }}xp
      .game_inner__left
        .buttons
          %img{:src => 'https://assets.codepen.io/217233/ktkSfxButton.png', '@click' => 'audioController.sfxOn = !audioController.sfxOn', ":class" => "{'off': !audioController.sfxOn}"}
          %img{:src => 'https://assets.codepen.io/217233/ktkBgButton.png', '@click' => 'toggleBg()', ":class" => "{'off': muteBg}"}
        .logo
          %img{:src => 'https://assets.codepen.io/217233/ktkLogo.png'}
        .timer
          %img{:src => 'https://assets.codepen.io/217233/ktkTimerBg.png'}
          .timer_inner
            .minutes
              {{ minutes }}
            .seconds
              {{ seconds }}
            .ms
              {{ ms }}
        .ui
          .ui_progress
            .ui_progress__stage
              STAGE {{ stage }}
            .ui_progress__bar
              .inner{":style" => "{width: `${((500 / enemiesPerStage) * (enemiesDefeated + 1)) - ((500 / enemiesPerStage) / 2)}px`}"}
              .outer
            .ui_progress__icons
              .icon{":key" => "enemies", "v-for" => "(enemies, index) in enemiesPerStage"}
                .icon_bg{":class" => "{'complete': index < enemiesDefeated, 'active': index == enemiesDefeated}"}
        .center
          .characters
            .player{":class" => "{'run': stageComplete}"}
              .stats
                .ui_strength
                  %img{:src => 'https://assets.codepen.io/217233/ktkStrengthIcon.png'}
                  {{ strength }} STR
                  %br
                .ui_intelligence
                  %img{:src => 'https://assets.codepen.io/217233/ktkKnowledgeIcon.png'}
                  {{ intelligence }} INT
                .ui_speed
                  %img{:src => 'https://assets.codepen.io/217233/ktkSpeedIcon.png'}
                  {{ speed }} SPD

              .player_sprite{":class" => "{'pressed': pressed}"}
            .enemy{":class" => "{'run': stageComplete}"}
              .enemy_sprite{":class" => "{'pressed': pressed, 'killed' : enemyKilled, 'boss' : boss}", ":style" => "{filter: `hue-rotate(${80 * (stage - 1)}deg)`}"}
              .enemy_hit{":class" => "{'pressed': pressed}"}
                {{ damage }}
              .enemy_xp{":class" => "{'killed': enemyKilled}"}
                %img{:src => 'https://assets.codepen.io/217233/ktkXpIxon.png'}
                {{ xpGained }}xp
              .enemy_health
                .enemy_health_stats
                  .name
                    {{ enemy.name }}
                  .level
                    HP: {{ enemy.health }}
                .enemy_health_inner{":style" => "{width: `${100 * (enemy.health / enemy.initHealth)}%`}"}
                
      .game_inner__right{"v-if" => "shoppingPhase"}
        .timer
          %img{:src => 'https://assets.codepen.io/217233/ktkTimerBg.png'}
          .timer_inner
            .minutes
              {{ minutes }}
            .seconds
              {{ seconds }}
            .ms
              {{ ms }}
        %p Spend your gold and experience on upgrades
        .xp
          %img{:src => 'https://assets.codepen.io/217233/ktkXpIxon.png'}
          {{ xp }}xp
        .upgrades
          .upgrades_upgrade{":key" => "upgrades.name", "v-for" => "(upgrade, index) in upgrades"}
            %h2 {{ index != 0 ? upgrade.type != upgrades[index - 1].type ? upgrade.type : '' : 'Skills' }}
            .upgrade{":class" => "{'max' : upgrade.level > upgrade.maxLevel, 'available': upgrade.type == 'stat' ? xp >= upgrade.cost : gold >= upgrade.cost, 'bought': upgrade.bought}", '@click' => 'buy(index, upgrade.type, upgrade.stat);audioController.play("upgrade")', '@mouseenter' => 'audioController.play("hover")'}
              .name {{ upgrade.names }}
              .description {{ upgrade.descriptions }}
              .cost 
                Cost: {{ upgrade.cost }} {{ upgrade.type == "stat" ? 'xp' : 'gold' }}
              .effect
                +{{ upgrade.type == "stat" ? upgrade.increment : upgrade.damage }} {{ upgrade.metric }}
              .level{"v-if" => "upgrade.type == 'stat'"}
                Level: {{ upgrade.level }}
        .nextPhase{'@click' => 'exitShoppingPhase'} Next round
View Compiled
@font-face {
  font-family: 'AddLGBitmap09';
  src: url('https://assets.codepen.io/217233/AddLGBitmap09.woff2') format('woff2'),
    url('https://assets.codepen.io/217233/AddLGBitmap09.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}


body {
  background: black;
  color: white;
  margin: 0;
  padding: 0;
  overflow: hidden;
  font-family: 'AddLGBitmap09';
}

.game_lose,
.game_win {
  position: absolute;
  background: rgba(14, 3, 13, 0.94);
  z-index: 99999;
  width: 100%;
  height: 100%;
  text-align: center;
  display: none;
  
  &.show {
    display: block;
  }
  
  .inner {
    position: absolute;
    width: 500px;
    left: 0;
    right: 0;
    margin: auto;
    top: 50%;
    transform: translateY(-50%);
    
    
    
    p {
      font-size: 12px;
      line-height: 20px;
    }
  }
}

#app {
  background: url('https://assets.codepen.io/217233/ssBg.png');
  background-size: cover;
  background-position: center;
  height: 100vh;
  width: 100%;
  position: relative;
  transition: all .1s;
  position: relative;

  $shake: 3px;

  @keyframes shake {
    0% {left:-$shake; top: $shake}
    20%{left:$shake; top: -$shake;}
    40%{left:$shake; top: $shake;}
    60%{left:-$shake; top: -$shake;}
    80%{left:$shake; top: $shake;}
    100%{left: 0; top: 0px;}
  }
  &.pressed {
    animation: shake .1s forwards;
  }
}

.game {
  &_intro {
    background: rgba(14, 3, 13, 0.94);
    width: 100%;
    height: 100%;
    position: fixed;
    z-index: 99;
    transition: all .3s;

    &.gamestarted {
      opacity: 0;
      pointer-events: none;
    }

    &__inner {
      position: absolute;
      left: 0;
      right: 0;
      margin: auto;
      top: 50%;
      width: 680px;
      transform: translateY(-50%);
      text-align: center;
      transition: all .3s;
      font-size: 12px;
      line-height: 30px;

      .begin {
        text-align: center;
        margin-top: 80px;
        cursor: pointer;
        transition: all .3s;

        &:hover {
          color: #76ece2;
        }
      }

      .dialogue {
        float: left;
        width: 60%;
        background: black;
        color: white;
        border-radius: 20px;
        padding: 20px 31px;
        position: relative;
      }

      p {
        text-align: left;
        clear: both;
      }

      span {

        display: block;
        color: #76ece2;
        font-size: 9px;
        text-align: left;
        margin-bottom: -11px;
        text-transform: uppercase;
      }

      .king {
        float: right;
        width: 140px !important;
      }


      &.start {
        transition: all 1s;
        &.gamestarted {
          opacity: 0;
          pointer-events: none;
        }
      }

      .continue {
        position: absolute;
        z-index: 1;
        cursor: pointer;
        right: 20px;
        font-size: 10px;
        top: 175px;

        transition: all .3s;

        &:hover {
          color: #76ece2;
        }
      }

      &.end {
        opacity: 1;
        pointer-events: all;
        transition: all 1s 1s;

        .continue {
          opacity: 1;
          transition: all .3s 6s;
        }

        &.gamestarted {
          opacity: 0;

          .continue {
            opacity: 0;

          }
        }
      }

      &.gamestarted {
        //opacity: 0;
        pointer-events: none;
      }

      img:nth-of-type(1) {
        width: 400px;
        image-rendering: pixelated;
      }


      img:nth-of-type(2) {
        position: relative;
        left: 0px;
      }

      img:nth-of-type(2) {
        cursor: pointer;
        margin-top: 40px;
      }
    }
  }

  &_inner {
    &__tooltip {
      width: 390px;
      margin: 0 auto;
      text-align: left;
      position: absolute;
      top: calc(50% + 240px);
      left: 0;
      right: 0;
      margin: auto;
      opacity: 0;
      transition: all .3s;

      &.active {
        opacity: 1;
      }

      .smash {
        width: 100%;
        text-align: center;
        margin-top: 23px;
        font-size: 9px;
      }

      .space {
        width: 390px;
        margin-top: 50px;
        height: 50px;
        background: white;
        position: relative;
        top: 0;
        border-radius: 6px;
        box-shadow: 0 10px #d6d6d6;
        animation: press2 .1s infinite;  

        @keyframes press2{
          0% {box-shadow: 0 10px #d6d6d6; top: 0px;}
          100% {box-shadow: 0 0px #d6d6d6; top:10px;}
        }
      }
    }

    &__footer {
      position: fixed;
      left: 50px;
      font-size: 7px;
      z-index: 2;
      bottom: 40px;
      width: calc(100% - 100px);

      .madeby {
        position: relative;
        top: 50px;
      }

      .resources {
        float: right;

        div {
          margin: 0 0 10px 0;
          font-size: 12px;
          img {
            position: relative;
            top: 3px;
            margin-right: 8px;
          }
        }
      }

      a {
        color: #d0295f;
        text-decoration: none;
      }
    }
    &__left {
      text-align: center;
      float: left;
      position: relative;
      width: calc(100%);
      height: 100vh;

      .buttons {
        float: right;
        padding: 50px;

        img {
          width: 32px;
          float: left;
          margin-left: 12px;
          cursor: pointer;

          &.off {
            opacity: 0.3;
          }
        }
      }
      .logo {
        padding: 50px;
        float: left;
      }



      .ui {
        width: 100%;

        &_spaces {
          font-size: 24px;
          margin-bottom: 7px;
        }

        &_progress {
          width: 500px;
          margin: 0 auto;
          text-align: left;
          position: absolute;
          top: calc(50% - 300px);
          left: 0;
          right: 0;
          margin: auto;
          &__stage {
            font-size: 9px;
            margin-bottom: 11px;
          }


          &__bar {
            width: 100%;
            position: relative;

            .inner {
              background: url('https://assets.codepen.io/217233/ktkProgressInner.png');
              height: 7px;
              width: calc(100% - 6px);
              background-size: 494px 7px;
              position: absolute;
              top: 10px;
              z-index: 1;
              left: 3px;
              transition: all .4s;
              max-width: 494px;
            }

            .outer {
              background: url('https://assets.codepen.io/217233/ktkProgressOuter.png');
              height: 12px;
              width: 100%;
              background-size: 500px 12px;
              position: absolute;
              top: 8px;
            }
          }

          &__icons {
            display: flex;
            justify-content: space-between;
            position: relative;
            z-index: 1;
            padding: 0 14px;

            .icon {
              flex-grow: 1;
              text-align: center;

              &_bg {
                width: 28px;
                height: 28px;
                margin: auto;
                background: url('https://assets.codepen.io/217233/ktkStageLocked.png');

                &.active {
                  &:after {
                    width: 10px;
                    height: 10px;
                    content: '▲';
                    display: block;
                    position: relative;
                    top: 31px;
                    left: 9px;
                    font-size: 10px;
                  }
                }

                &.complete {
                  background: url('https://assets.codepen.io/217233/ktkStageComplete.png');
                }
              }
            }
          }
        }

        &_presses {
          font-size: 12px;
          opacity: 0.4;
        }
      }
      .center {
        position: absolute;
        left: 0;
        right: 0;
        margin: auto;
        top: 50%;
        transform: translateY(-50%);
        width: 390px;

        .player_sprite,
        .enemy_sprite {
          width: 150px;
          height: 150px;
          animation: idle .5s steps(7, end) infinite;
          transform: scale(5);
          image-rendering: pixelated;

          &.pressed {
            animation: attack .15s steps(3, end);
          }
        }

        .characters {
          width: 400px;
          margin: 80px auto;

          .stand {
            width: 100%;
            height: 100px;
            background: #141627;
            border-radius: 100%;
            position: absolute;
            bottom: -20px;
            left: 9px;
          }


          .player {
            z-index: 1;
            position: relative;
            left: 10px;
            top: 60px;
            transition: all 1s .3s;

            &.run {
              left: 400px;
              opacity: 0;
            }

            .stats {
              position: absolute;
              left: -110px;
              text-align: left;
              transform: translateY(4px);
              font-size: 8px;
              animation: health 2s infinite;

              span {
                font-size: 6px;
                opacity: 0.75;
                display: block;
                padding-left: 24px;
                padding-top: 4px;
              }

              div {
                margin-bottom: 10px;

                img {
                  position: relative;
                  margin-right: 4px;
                  top: 3px;
                }
              }
            }
          }

          .enemy {
            position: relative;
            top: -134px;
            left: -40px;
            transition: all .3s;



            &.run {
              opacity: 0;
            }
          }

          .enemy,
          .player {
            width: 50%;
            float: left;
          }
        }

        .enemy {
          width: 100px;

          &_hit,
          &_gold,
          &_xp {
            position: absolute;
            left: 98px;
            top: 40px;
            z-index: 1;
            opacity: 0;

            &.pressed {
              animation: hit .15s forwards;
            }
          }

          &_gold {
            &.killed {
              animation: hit .3s .1s forwards;
            }
          }

          &_xp {
            left: 0;
            right: 0;

            &.killed {
              animation: hit .3s .12s forwards;
            }
          }

          &_sprite {
            background: url('https://assets.codepen.io/217233/ssEnemyIdle_1.png');
            animation: none;
            width: 200px;
            animation: enemyIdle .5s steps(7, end) infinite;
            transform: scaleX(-5) scaleY(5);
            position: relative;

            &.boss {
              background: url('https://assets.codepen.io/217233/ktkTrostIdle.png');
              animation: bossIdle 1s steps(7, end) infinite;
              width: 160px;
              top: 10px;
              left: 40px;
              height: 105px;
              filter: hue-rotate(0deg) !important;

              &.pressed {
                animation: bossHit .15s steps(3, end);
              }

              &.killed {
                animation: bossKilled .8s steps(5, end) ;
              }
            }

            &.pressed {
              animation: enemyHit .15s steps(3, end);
            }

            &.killed {
              animation: enemyKilled .8s steps(5, end) ;
            }
          }

          &_health {
            width: 190px;
            height: 12px;
            position: absolute;
            left: 0;
            font-size: 13px;
            right: 0;
            line-height: 39px;
            border-radius: 10px;
            top: 10px;
            padding-top: 0px;
            margin: auto;
            background: url(https://assets.codepen.io/217233/ktkEnemyHealth.png);
            animation: health 2s infinite;

            &_stats {
              font-size: 7px;
              padding-top: 6px;
              text-align: left;

              .name {
                float: left;
              }

              .level {
                float: right;
              }
            }

            &_inner {
              position: absolute;
              left: 4px;
              top: 3px;
              height: 5px;
              border-radius: 10px;
              transition: all .1s;
              max-width: 182px;
              background: url(https://assets.codepen.io/217233/ktkEnemyHealthInner.png);
            }
          }
        }


      }

      & .spaceBar {
        width: 390px;
        margin-top: 50px;
        height: 50px;
        background: white;
        position: relative;
        top: 0;
        border-radius: 6px;
        box-shadow: 0 10px #d6d6d6;
        animation: press2 .1s forwards;

        @keyframes health {
          0% {    top: 10px;}
          50% {    top: 13px;}
          100% {    top: 10px;}
        }

        @keyframes idle {
          0% {

            background: url('https://assets.codepen.io/217233/ssIdle.png');
            background-position: 0% 0;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ssIdle.png');
            background-position: 100% 0;
          }
        }

        @keyframes attack {
          0% {
            background: url('https://assets.codepen.io/217233/ssAttack1.png');
            background-position: 0% 0;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ssAttack1.png');
            background-position: 100% 0;
          }
        }

        @keyframes enemyIdle {
          0% {
            background: url('https://assets.codepen.io/217233/ssEnemyIdle_1.png');
            background-position: 0% 0;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ssEnemyIdle_1.png');
            background-position: 100% 0;
          }
        }

        @keyframes bossIdle {
          0% {
            background: url('https://assets.codepen.io/217233/ktkTrostIdle.png');
            background-position: 0% 0;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ktkTrostIdle.png');
            background-position: 100% 0;
          }
        }

        @keyframes bossHit {
          0% {
            background: url('https://assets.codepen.io/217233/ktkTrostHit.png');
            background-position: 0% 0;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ktkTrostHit.png');
            background-position: 100% 0;
          }
        }

        @keyframes bossKilled {
          0% {
            background: url('https://assets.codepen.io/217233/ktkTrostKilled.png');
            background-position: 0% 0;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ktkTrostKilled.png');
            background-position: 100% 0;
          }
        }

        @keyframes enemyHit {
          0% {
            background: url('https://assets.codepen.io/217233/ssEnemyHit_1.png');
            background-position: 0% 0;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ssEnemyHit_1.png');
            background-position: 100% 0;
          }
        }

        @keyframes enemyKilled {
          0% {
            background: url('https://assets.codepen.io/217233/ssEnemyDeath_1.png');
            background-position: 0% 0;
            opacity: 1;
          }

          100% {
            background: url('https://assets.codepen.io/217233/ssEnemyDeath_1.png');
            background-position: 100% 0;
            opacity: 0;
          }
        }

        @keyframes hit {
          0% {    top: 40px;opacity: 0}
          100% {    top: -30px;opacity:1}
        }

        @for $i from 1 through 20 {
          @keyframes hit-#{$i} {

            $left: random(400) + 80 + px;
            $bottom: random(100) + px;
            0% {
              left: 0px;
              bottom: 0px;
              opacity: 1;

            }
            60% {
              opacity:1;
            }

            100% {
              left: $left;
              bottom: $bottom;
              opacity:0;
            }
          }
        }


        &.pressed {
          animation: press .05s forwards;

        }
      }
    }

    &__right {
      float: right;
      position: fixed;
      width: 720px;
      background: #160917;
      left: 0;
      right: 0;
      border-radius: 10px;
      border: 2px solid white;
      z-index: 999;
      top: 50%;
      box-shadow: 0 0 0 1160px #0a0209d1;
      margin: auto;
      transform: translateY(-50%);

      p {
        text-align: left;
        font-size: 10px;
        padding: 26px;
      }

      .xp {
        padding: 0 26px;
        font-size: 12px;
      }

      .nextPhase {
        background: #73256a;
        width: calc(100% - 52px);
        margin: 0 26px 26px 26px;
        text-align: center;
        font-size: 12px;
        padding: 20px 0;
        border-radius: 10px;
        cursor: pointer;
        box-shadow: 0 4px #3d074e;
      }

      .upgrades {
        padding: 24px;


        h2 {
          text-transform: capitalize;
          margin-bottom: 12px;
          font-weight: normal;
          font-size: 14px;
        }

        &_upgrade {
          .upgrade {
            border: 2px solid white;
            padding: 12px;
            border-radius: 6px;
            opacity: 0.6;
            transition: all .3s;
            pointer-events: none;
            position: relative;
            margin-bottom: 6px;
            font-size: 11px;
            
            &:hover {
              background: #ffffff0f;
              padding: 12px 12px 12px 20px;
            }

            &.max {
              background: black;
              pointer-events: none;

              &:after {
                content: 'max';
                display: block;
                position: absolute;
                left: 0;
                right: 0;
                text-align: center;
                margin: auto;
                top: 50%;
                transform: translateY(-50%);
                width: 100%;
                text-transform: uppercase;
              }

              div {
                opacity: 0.12;
              }
            }

            &.bought {
              pointer-events: none !important;
              background: green;
              opacity: 0.2 !important;
            }

            .name {
              font-size: 11px;
              margin-bottom: 5px;
              text-transform: uppercase;
            }

            .description {
              font-size: 8px;
              opacity: 0.5;
              margin-bottom: 4px;
            }

            .cost {
              font-size: 8px;
              color: red;
              transition: all .3s;
              margin-bottom: 3px;
            }

            .level {
              font-size: 8px;
              position: absolute;
              right: 12px;
              bottom: 12px;
            }

            .effect {
              font-size: 8px;
            }

            &.available {
              opacity: 1;
              pointer-events: all;
              cursor: pointer;

              .cost {
                color: green;
              }
            }
          }
        }
      }
    }
  }
}

      .timer {
        width: 128px;
        margin: 0 auto;
        text-align: left;
        position: absolute;
        top: calc(50% - 400px);
        left: 4px;
        right: 0;
        font-size: 14px;
        margin: auto;


        &_inner {
          position: relative;
          top: -40px;
          text-align: center;

          div {
            display: inline-block;
            width: 28px;
            position: relative;
            margin: 0 3px 0;
            &:after {
              display: block;
              content: ':';
              position: absolute;
              right: -11px;
              top: 50%;
              transform: translateY(-50%);
            }
          }

          .ms {
            font-size: 9px;
            opacity: 0.7;
            width: 22px;

            &:after {
              display: none;
            }
          }
        }
      }
View Compiled
class Enemy {
  constructor(health, name) {
    this.health = health;
    this.initHealth = health;
    this.name = name;
  }
}

const development = true;

class AudioController {
  constructor(audioArray, helpers) {

    this.sampleCount = 0;
    this.loadedIndex = 0;
    this.sfxOn = true;

    if(helpers == undefined) {
      this.helpers = true;
    }

    // Affirm in console that module has been included
    console.log('%c 🔊 Audio module active ', 'padding: 10px; background: #cbfd9f; color: #3b4e2a');

    // Helpers
    if(this.helpers) {
      if(audioArray.length == 0) {
        console.warn('No audio array or audio array empty');
      }
    }

    audioArray.forEach(function(aud, index) {
      if(aud.stackSize != undefined) {
        this.sampleCount += aud.stackSize;
      } else {
        this.sampleCount += 1;
      }
    }.bind(this));

    // Create a global audio array
    this._globalAudio = [];

    // Iterate through all our samples
    audioArray.forEach(function(aud, index) {

      if(aud.stackSize != undefined) {
        this.stackSize = aud.stackSize;
      } else {
        this.stackSize = 1;
      }

      // If the type of audio is not background music, stack it and play based on an index,
      // This means you can play small samples very quickly. You cannot play the same audio
      // Object until the current object has finished

      var audioObject = [];

      let a = new Audio();
      let b;
      a.preload = true; 
      a.src = aud.source;

      for(var i = 0; i < this.stackSize; i++) {

        b = new Audio()
        b.src = a.src

        b.onloadeddata = function() {
          this.loadedIndex++;
          this.progress = Math.ceil(this.loadedIndex / this.sampleCount * 100);

          if(this.helpers) {
            console.clear();
            console.log('%c 🔊 Audio module active ', 'padding: 10px; background: #cbfd9f; color: #3b4e2a');
            console.log(`Loading ${audioArray.length} audio sample(s)`);
            console.log(`Loading ${aud.name}`);
            console.log(`${this.progress}%`)
          }

          if(this.progress == 100) {
            this.onLoaded();
          }

        }.bind(this);

        audioObject.push(b);
      }

      audioObject.index = 0;
      audioObject.maxIndex = this.stackSize;

      this._globalAudio[aud.name] = audioObject;

    }.bind(this));
  }

  play(audio) {


    let sample =  this._globalAudio[audio];

    if(sample != undefined) {
      // Get the current audio object in the stack
      let index = sample.index;
      let aud = sample[index];

      // Play the audio object
      if(this.sfxOn) {
        aud.play();
      }


      // Increase the stack index or reset if it exceeds the max stack size
      if(sample.index > sample.maxIndex - 2) {
        sample.index = 0;
      } else {
        sample.index++;
      }
    } else {
      console.warn(`${audio} does not exist.`);
    }
  }

  stop(audio) {
    let sample =  this._globalAudio[audio];

    if(sample != undefined) {
      // Get the current audio object in the stack
      let index = sample.index;
      let aud = sample[index];

      // Stop the audio object
      aud.pause();
      aud.currentTime = 0;
    }
  }

  restart(audio) {
    let sample =  this._globalAudio[audio];

    if(sample != undefined) {
      // Get the current audio object in the stack
      let index = sample.index;
      let aud = sample[index];

      // Stop the audio object
      aud.pause();
      aud.currentTime = 0;
      aud.play();
    }
  }

  pause(audio) {
    let sample =  this._globalAudio[audio];

    if(sample != undefined) {
      // Get the current audio object in the stack
      let index = sample.index;
      let aud = sample[index];

      // Stop the audio object
      aud.pause();
    }
  }

  mute(audio) {
    let sample =  this._globalAudio[audio];

    sample.forEach(function(s) {
      s.volume = 0;
    })
  }

  setVolume(audio, volume) {
    let sample =  this._globalAudio[audio];
    let vol = volume / 100;

    sample.forEach(function(s) {
      s.volume = vol;
    })
  }

  // Destroy an audio sample to save memory

  destroy(audio) {
    this._globalAudio[audio] = undefined;
  }

  stopAll() {
    Object.keys(this._globalAudio).forEach(function(key) {
      this._globalAudio[key].forEach(function(aud) {
        aud.pause();
        aud.currentTime = 0;
      })
    }.bind(this));
  }

  muteAll() {
    Object.keys(this._globalAudio).forEach(function(key) {
      this._globalAudio[key].forEach(function(aud) {
        aud.volume = 0;
      })
    }.bind(this));
  } 

  list() {
    Object.keys(this._globalAudio).forEach(function(key) {
      console.log('%c' + key, 'font-weight: bold; color: green');
    }.bind(this));
  }

  onLoaded() {
    if(this.helpers) {
      console.log('All audio loaded');
      this.list();
    }
  }
}

new Vue({
  el: '#app',


  data() {
    return {
      keyCode: 32,
      minutes: 4,
      seconds: 0,
      ms: 1,
      pressed: false,
      strength: 1,
      intelligence: 1,
      luck: 1,
      speed: 1,
      gameStarted: false,
      muteBg: false, 
      boss: false,
      damage: 1,
      weapon: 'Blunt sword',
      weaponDamage: 1,
      enemiesPerStage: 2,
      canAttack: false,
      stage: 1,
      goldGained: 0,
      sfx: true,
      xpGained: 0,
      introClicked: false,
      bgMusicStarted: false,
      stageComplete: false,
      xp: 0,
      damageAnim: 1, 
      shoppingPhase: false,
      gold: 0,
      gameover: false,
      gamewin: false,
      audioController: '',
      enemy: new Enemy(5, 'HAREK SEDGWICK'),
      enemiesDefeated: 0,
      enemyKilled: false,
      tooltipTimer: 0,
      tooltip: true,
      enemyNames: [
        'JACOB DANGERS',
        'MILEON MASON',
        'MACE CAVELIER',
        'OSRIC GRAGOLOON',
        'MOSES STONEWELL',
        'TRISTAN GOSBECK',
        'REDWALD CROMWELL',
        'JEREMIAS PICARD',
        'EGRIC MAIDSTONE',
        'ROBIN CURTEYS',
        'DINUS DE REUE',
        'HAREK SEDGWICK',
        'FLORA DAUBERVILLE',
        'RAMETTA THE SLENDER',
        'ISEMAY VERNOLD',
        'AVINA CECIL',
        'FANUS THE GREAT',
        'GASPAR SHADOWSEEKER',
        'GOUBERT THE RED',
        'ALDOUS DARCY',
        'RYN THE RED',
        'FULLER CARDON',
        'ANSELM THE OLD',
        'ALVINA BLUETOOTH',
        'MICKNEY  CORVISER',
        'RYKOR RAVENSGATE',
        'REYNARD LONGBOW',
        'ALEX TROST',
        'ADAM KUHN',
        'STEVE GARDNER',
        'CHASSIE EVANS',
        'STEVEN SHAW',
        'CHRIS COYIER',
        'JHEY',
        'PETE BARR',
        'ZACH SAUCIER'
      ],

      audioArray: [
        {
          'name'      : 'swordHit1',
          'source'    : 'https://assets.codepen.io/217233/ktkSwordHit1.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'swordHit2',
          'source'    : 'https://assets.codepen.io/217233/ktkSwordHit2.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'swordHit3',
          'source'    : 'https://assets.codepen.io/217233/ktkSwordHit3.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'death1',
          'source'    : 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/217233/ktkDeath1.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'death2',
          'source'    : 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/217233/ktkDeath2.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'death3',
          'source'    : 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/217233/ktkDeath3.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'death4',
          'source'    : 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/217233/ktkDeath4.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt1',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt1.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt2',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt2.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt3',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt3.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt4',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt4.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt5',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt5.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt6',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt6.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt7',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt7.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt8',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt8.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'grunt9',
          'source'    : 'https://assets.codepen.io/217233/ktkGrunt9.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'bgmusic',
          'source'    : 'https://assets.codepen.io/217233/ktkBgAudio.mp3'
        },
        {
          'name'      : 'shopBell',
          'source'    : 'https://assets.codepen.io/217233/ktkBell.wav'
        },
        {
          'name'      : 'shopWoosh',
          'source'    : 'https://assets.codepen.io/217233/ktkWoosh.wav'
        },
        {
          'name'      : 'upgrade',
          'source'    : 'https://assets.codepen.io/217233/ktkUpgrade.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'hover',
          'source'    : 'https://assets.codepen.io/217233/ktkHover.mp3',
          'stackSize' : 10
        },
        {
          'name'      : 'crushyou',
          'source'    : 'https://assets.codepen.io/217233/crush+you.wav',
          'stackSize' : 1
        },
        {
          'name'      : 'neverstop',
          'source'    : 'https://assets.codepen.io/217233/neverstop.wav',
          'stackSize' : 1
        },
        {
          'name'      : 'persistance',
          'source'    : 'https://assets.codepen.io/217233/persistance.wav',
          'stackSize' : 1
        },
        {
          'name'      : 'mercy',
          'source'    : 'https://assets.codepen.io/217233/mercy.wav',
          'stackSize' : 1
        },
        {
          'name'      : 'purchases',
          'source'    : 'https://assets.codepen.io/217233/pointlesspurchases.wav',
          'stackSize' : 1
        },
        {
          'name'      : 'fool',
          'source'    : 'https://assets.codepen.io/217233/fool.wav',
          'stackSize' : 1
        },
        {
          'name'      : 'notpossible',
          'source'    : 'https://assets.codepen.io/217233/not+possible.wav',
          'stackSize' : 1
        }
      ],

      // Hmm. Upgrades
      upgrades: {
        0: {
          'type': 'stat',
          'names': 'Strength up',
          'descriptions' : 'Increase your base strength. Do more damage.',
          'cost' : 25,
          'level' : 1,
          'increment' : 1,
          'costIncreasePerLevel' : 20,
          'metric' : 'Strength',
          'stat' : 'strength',
          'maxLevel' : 100
        },
        1: {
          'type': 'stat',
          'names': 'Intelligence up',
          'descriptions' : 'Increase your intelligence and gain more xp per kill.',
          'cost' : 25,
          'level' : 1,
          'increment' : 1,
          'costIncreasePerLevel' : 20,
          'metric' : 'Intelligence',
          'stat' : 'intelligence',
          'maxLevel' : 100
        },
        2: {
          'type': 'stat',
          'names': 'Speed up',
          'descriptions' : 'Increase your speed. Strike quicker!',
          'cost' : 25,
          'level' : 1,
          'increment' : 1,
          'costIncreasePerLevel' : 20,
          'metric' : 'Speed',
          'stat' : 'speed',
          'maxLevel' : 7
        }
      }
    }
  },

  methods: {
    punch() {
      if(_this.canAttack && !_this.gamewin && !_this.gameover) {
        _this.tooltip = false;
        _this.tooltipTimer = 0;

        _this.canAttack = !_this.canAttack;
        _this.pressed = !_this.pressed;
        _this.damageAnim = Math.floor(Math.random() * 10) + 1;


        let hitSound = Math.floor(Math.random() * 3) + 1;
        let gruntSound = Math.floor(Math.random() * 9) + 1;
        _this.audioController.play(`swordHit${hitSound}`);
        _this.audioController.play(`grunt${gruntSound}`);

        setTimeout(function() {
          _this.pressed = !_this.pressed;
        }, 150)

        setTimeout(function() {
          if(_this.enemyKilled == false) {
            _this.canAttack = !_this.canAttack;
          }

        }, 500 - (50 * _this.speed))

        if(_this.enemy.health > _this.damage) {
          _this.enemy.health -= _this.damage;
        } else {

          _this.canAttack = false;

          let deathSound = Math.floor(Math.random() * 4) + 1;
          _this.audioController.play(`death${deathSound}`)

          _this.enemy.health = 0;  
          _this.enemiesDefeated++;
          _this.enemyKilled = true;

          let baseXpPerKill = 10 + Math.floor(Math.random() * 3) + 1;
          let xpPerKillWithBonus = Math.ceil((baseXpPerKill * _this.stage) + (((baseXpPerKill * _this.stage) / 100) * (_this.intelligence * 10)));

          _this.xp += xpPerKillWithBonus;
          _this.xpGained = xpPerKillWithBonus;

          let baseGoldPerKill = 10 + Math.floor(Math.random() * 3) + 1;
          let goldPerKillWithBonus = Math.ceil((baseGoldPerKill * _this.stage) + (((baseGoldPerKill * _this.stage) / 100) * (_this.luck * 10)));

          _this.goldGained = goldPerKillWithBonus;
          _this.gold += goldPerKillWithBonus;

          if(_this.boss) {
            _this.audioController.play('notpossible');
            window.clearInterval(timer)
            _this.gamewin = true;

          } else {

            if(_this.enemiesDefeated > _this.enemiesPerStage - 1) {

              _this.stageComplete = true;

              setTimeout(function() {


                _this.shoppingPhase = true;

                _this.audioController.play('shopWoosh');
                _this.audioController.play('shopBell');

                if(_this.stage == 1) {
                  _this.audioController.play('fool');
                }

                if(_this.stage == 3) {
                  _this.audioController.play('neverstop');
                }

                if(_this.stage == 5) {
                  _this.audioController.play('purchases');
                }

                if(_this.stage == 7) {
                  _this.audioController.play('persistance');
                }

                if(_this.stage == 9) {
                  _this.audioController.play('mercy');
                }

                console.log('lowpass')
                lowpassNode.frequency.value = 250;

              }, 1000)

              // Use Web Audio to create an audio graph that uses the stream from the audio element


            } else {
              setTimeout(function() {
                console.log(_this.enemiesDefeated, _this.stage)
                if(_this.enemiesDefeated == 10 && _this.stage == 10) {
                  _this.enemy = new Enemy(3000, 'King Trost');
                  _this.boss = true;
                } else {
                  _this.enemy = new Enemy(7 * (_this.enemiesDefeated + 1 * _this.stage), _this.enemyNames[ Math.floor(Math.random() * _this.enemyNames.length)]);
                }
              }, 800)
            }

          }

          setTimeout(function() {
            _this.canAttack = true;
            _this.enemyKilled = false;
          }, 800)
        }
      }
    },

    toggleBg() {
      if(this.muteBg == false) {
        audioNode.volume = 0;
        this.muteBg = true;
      } else {
        audioNode.volume = 1;
        this.muteBg = false;
      }
    },
    toggleSFX() {
      this.sfx = !this.sfx;
    },

    buy (upgrade, type, stat) {

      if(type == 'stat') {
        let u = this.upgrades[upgrade];
        u.level++;

        this.xp -= u.cost;

        if(stat == 'strength') {
          this.strength += u.increment;
          this.damage = (this.strength * 1) + this.weaponDamage;
        }

        if(stat == 'speed') {
          this.speed += u.increment;
        }

        if(stat == 'intelligence') {
          this.intelligence += u.increment
        }

        if(stat == 'luck') {
          this.luck += u.increment
        }

        let newCost = u.costIncreasePerLevel * u.level;
        u.cost = newCost; 

      }

      if(type == 'weapons') {
        let w = this.upgrades[upgrade];
        this.gold -= w.cost;
        this.weaponDamage = w.damage;
        this.damage = (this.strength * 1) + this.weaponDamage;
        this.weapon = w.names;
        w.bought = true;

        let newCost = u.costIncreasePerLevel * u.level;
        u.cost = newCost;   
      }
    },

    exitShoppingPhase() {
      _this.stageComplete = false;
      this.shoppingPhase = !this.shoppingPhase;
      this.stage++;
      this.enemiesPerStage++;
      this.enemiesDefeated = 0;
      this.enemy = new Enemy(5 * (_this.enemiesDefeated + 1 * _this.stage), _this.enemyNames[ Math.floor(Math.random() * _this.enemyNames.length)]);
      lowpassNode.frequency.value = 15000;
    },

    
    startGame() {
      this.gameStarted = true;
      lowpassNode.frequency.value = 15000;
      this.canAttack = true;

      timer = setInterval(function() {
        _this.tooltipTimer++;

        if(_this.minutes == 0 && _this.seconds == 0 && _this.ms == 0) {
          // Show game over!
          window.clearInterval(timer)
          _this.gameover = true;
          _this.canAttack = false;
        }

        if(_this.tooltipTimer > 100) {
          _this.tooltip = true;
        }
        if(_this.ms > 0) {
          _this.ms--;
          if(_this.ms < 10) {
            _this.ms = '0' + _this.ms;
          }
        } else {
          _this.ms = 99;
          if(_this.seconds < 1) {
            _this.seconds = 59;
            _this.minutes--;
            _this.minutes = '0' + _this.minutes;
          } else {
            _this.seconds--;
            if(_this.seconds < 10) {
              _this.seconds = '0' + _this.seconds;
            }
          }
        }
      },10)

    }
  },


  mounted () {
    _this = this;

    this.audioController = new AudioController(this.audioArray);

    // JCanvas Audio Module
    audioNode = document.querySelector("audio");

    document.onclick = function() {
      audioNode.loop = true;
      audioNode.play()

      if(!_this.bgMusicStarted) {
        _this.bgMusicStarted = true;
        audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        sourceNode = audioCtx.createMediaElementSource(audioNode);

        // Create the lowpass filter
        lowpassNode = audioCtx.createBiquadFilter();

        // Connect the source to the lowpass filter
        sourceNode.connect(lowpassNode);

        // Connect the lowpass filter to the output (speaker)
        lowpassNode.connect(audioCtx.destination);

        console.log('lowpass')
        lowpassNode.frequency.value = 250;
      }
    }

    document.body.onkeyup = function(e) {
      if(e.keyCode == _this.keyCode) {
        if(!_this.shoppingPhase)
          _this.punch();
      }

    }
  }
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.9/vue.min.js