Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                #container
  #stage.squared
  
    .invader-row.jellyfish-row
    .invader-row.crab-row
    .invader-row.octopus-row
    
    .defender
              
            
!

CSS

              
                // Pixel styles
.solid { 
  background-color: #000;
  border: 1px solid #000;
  
  .pixelated & {    
    border: 1px solid #2f4f4f;
  }
}
.hollow {
  background-color: transparent;
  border: 1px solid transparent;
  transition: all 0.25s;
  
  .squared & {
    border-color: #000;
    background-color: #000;
  }
}
.pixel {
  margin-right: -2px;
  margin-bottom: -2px;
  display: inline-block;
  .solid;
}

// Quantities & Proportions
@invadersPerRow: 5;
@invaderWidth: 100% / (@invadersPerRow + 1);
@defenderWidth: 17%;

// Mixins
.stageSize() {
    width: 100vh;
    height: 100vh;
    max-width: 100vw;
    max-height: 100vw;
    margin: 0 auto;
}
.instant() {  
  transition-duration: 0s;
  animation: none;
}

// Keyframes and animation vars
@rowDelayBase: 3s;
@rowDelayInc: 4.666s;
@rowDropSettings: 0.333s ease-in-out 1 forwards;
@rowDelay1: @rowDelayBase + @rowDelayInc;
@keyframes row-drop-1 {
  100% {
    margin-top: 15%;
  }
}
@rowDelay2: @rowDelayBase + (@rowDelayInc * 1.75);
@keyframes row-drop-2 {
  from {
    margin-top: 15%;
  }
  to {
    margin-top: 25%;
  }
}
@rowDelay3: @rowDelayBase + (@rowDelayInc * 2.5);
@keyframes row-drop-3 {
  from {
    margin-top: 25%;
  }
  to {
    margin-top: 35%;
  }
}
@rowDelay4: @rowDelayBase + (@rowDelayInc * 3.25);
@keyframes row-drop-4 {
  from {
    margin-top: 35%;
  }
  to {
    margin-top: 45%;
  }
}
@keyframes invader-shuffle {
  0%, 50%, 100% {
    margin: 5% 13.333% 0% 13.333%;
  }
  25% {
    margin: 5% 24.666% 0% 2%;
  }
  12.5%, 37.5% {
    margin: 5% 19.666% 0% 7%;
  }
  62.5%, 87.5% {
    margin: 5% 7% 0% 19.666%;
  }
  75%  {
    margin: 5% 2% 0% 24.666%;
  }
}
@keyframes defender-shuffle {
  0%, 50%, 100% {
    padding-left: 41%;
    padding-right: 41%;
  }
  25% {
    padding-left: 80%;
    padding-right: 2%;
  }
  75% {
    padding-left: 2%;
    padding-right: 80%;
  }
}
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
@keyframes bob {
  0%, 50%, 100% {
    transform: translate(0%, 0%);
  }
  25% {
    transform: translate(0%, 10%);
  }
  75% {
    transform: translate(0%, -10%);
  }
}
// Shake keyframes lovingly ripped from https://elrumordelaluz.github.io/csshake/
@keyframes shake {
  0% { transform: translate(0px, 0px) rotate(0deg); }
  2% { transform: translate(0px, 0px) rotate(1.5deg); }
  4% { transform: translate(0px, 0px) rotate(-7.5deg); }
  6% { transform: translate(0px, 0px) rotate(-2.5deg); }
  8% { transform: translate(0px, 0px) rotate(6.5deg); }
  10% { transform: translate(0px, 0px) rotate(-4.5deg); }
  12% { transform: translate(0px, 0px) rotate(0.5deg); }
  14% { transform: translate(0px, 0px) rotate(-6.5deg); }
  16% { transform: translate(0px, 0px) rotate(1.5deg); }
  18% { transform: translate(0px, 0px) rotate(-2.5deg); }
  20% { transform: translate(0px, 0px) rotate(1.5deg); }
  22% { transform: translate(0px, 0px) rotate(1.5deg); }
  24% { transform: translate(0px, 0px) rotate(2.5deg); }
  26% { transform: translate(0px, 0px) rotate(6.5deg); }
  28% { transform: translate(0px, 0px) rotate(2.5deg); }
  30% { transform: translate(0px, 0px) rotate(1.5deg); }
  32% { transform: translate(0px, 0px) rotate(-4.5deg); }
  34% { transform: translate(0px, 0px) rotate(-3.5deg); }
  36% { transform: translate(0px, 0px) rotate(-0.5deg); }
  38% { transform: translate(0px, 0px) rotate(4.5deg); }
  40% { transform: translate(0px, 0px) rotate(4.5deg); }
  42% { transform: translate(0px, 0px) rotate(-0.5deg); }
  44% { transform: translate(0px, 0px) rotate(1.5deg); }
  46% { transform: translate(0px, 0px) rotate(-0.5deg); }
  48% { transform: translate(0px, 0px) rotate(-7.5deg); }
  50% { transform: translate(0px, 0px) rotate(3.5deg); }
  52% { transform: translate(0px, 0px) rotate(-5.5deg); }
  54% { transform: translate(0px, 0px) rotate(-6.5deg); }
  56% { transform: translate(0px, 0px) rotate(-0.5deg); }
  58% { transform: translate(0px, 0px) rotate(3.5deg); }
  60% { transform: translate(0px, 0px) rotate(-2.5deg); }
  62% { transform: translate(0px, 0px) rotate(3.5deg); }
  64% { transform: translate(0px, 0px) rotate(6.5deg); }
  66% { transform: translate(0px, 0px) rotate(-6.5deg); }
  68% { transform: translate(0px, 0px) rotate(-2.5deg); }
  70% { transform: translate(0px, 0px) rotate(-3.5deg); }
  72% { transform: translate(0px, 0px) rotate(-6.5deg); }
  74% { transform: translate(0px, 0px) rotate(-5.5deg); }
  76% { transform: translate(0px, 0px) rotate(1.5deg); }
  78% { transform: translate(0px, 0px) rotate(-1.5deg); }
  80% { transform: translate(0px, 0px) rotate(3.5deg); }
  82% { transform: translate(0px, 0px) rotate(1.5deg); }
  84% { transform: translate(0px, 0px) rotate(-7.5deg); }
  86% { transform: translate(0px, 0px) rotate(-0.5deg); }
  88% { transform: translate(0px, 0px) rotate(0.5deg); }
  90% { transform: translate(0px, 0px) rotate(-3.5deg); }
  92% { transform: translate(0px, 0px) rotate(-4.5deg); }
  94% { transform: translate(0px, 0px) rotate(2.5deg); }
  96% { transform: translate(0px, 0px) rotate(2.5deg); }
  98% { transform: translate(0px, 0px) rotate(-2.5deg); }
}
@squareShake: shake 1s ease-in-out 1 -0.5s;

html, body {
  height: 100%;
  overflow: hidden;
  background-color: #3D3D3E;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10%;
  
  #container {
    background: #fff;
    .stageSize;
      
    &.exploding {        
      animation: shake 2s infinite;
    }

    #stage {
      .stageSize;
      margin: 0;
      position: absolute;
      overflow: hidden;
    }
  }
}

.invader-row {
  margin: 5% auto 0;
  display: flex;
  justify-content: space-between;
  position: relative;
  width: 73.333%;
  height: 8.888%;
  background-color: transparent;
  transition: all 2s ease-in-out,
              padding 0s;
  @invaderRowShuffle: invader-shuffle 6.666s ease-in-out infinite 3s;
  animation: @invaderRowShuffle, @squareShake;
  
  &:nth-of-type(1) {
    transition-delay: 0s;
    animation: @invaderRowShuffle,
               row-drop-1 @rowDropSettings @rowDelay1,
               row-drop-2 @rowDropSettings @rowDelay2,
               row-drop-3 @rowDropSettings @rowDelay3,
               row-drop-4 @rowDropSettings @rowDelay4;
    
    .squared & {
      .instant();
    }
  }
  &:nth-of-type(2) {
    transition-delay: 0.333s;
    
    .squared & {
      .instant();
    }
  }
  &:nth-of-type(3) {
    transition-delay: 0.666s;
    
    .squared & {
      .instant();
    }
  }
  
  &.mothership-row {
    position: absolute;
    top: 0;
    left: -25%;
    .instant();
    animation: bob 2s ease-in-out infinite;
  }
  
  .squared & {
    width: 33.333%;
    height: 11.111%;
    margin: 0 33.333% -1px;
    overflow: hidden;
    .instant();
    
    &:nth-of-type(1) {
      margin-top: 33.333%;
    }
  }
}

.invader {
  display: flex;
  flex-flow: row wrap;
  width: @invaderWidth;
  height: 100%;
  transition: all 1.5s ease,
              opacity 0.25s ease-in,
              transform 0.75s ease-in;
  
  .squared & {
    margin: 0 !important;
    width: 100% / @invadersPerRow;
  }
  
  &.destroyed {
    opacity: 0;
    
    &.destroyed-style-0 {
      transform: scale(10) rotate(-540deg);
    }
    &.destroyed-style-1 {
      transform: scale(10) rotate(540deg);
    }
    &.destroyed-style-2 {
      transform: scale(0) rotate(-540deg);
    }
    &.destroyed-style-3 {
      transform: scale(0) rotate(540deg);
    }
  }
  
  &.removed {    
    z-index: -1;
  }
  
  &.jellyfish {
    margin-left: 3%;
    margin-right: 3%;
    
    .pixel {
      width: 12.333%;
    }
    
    // Default pixels
    & {
      
      :nth-child(1),:nth-child(2),:nth-child(3),
      :nth-child(6),:nth-child(7),:nth-child(8),
      :nth-child(9),:nth-child(10),:nth-child(15),
      :nth-child(16),:nth-child(17),:nth-child(24),
      :nth-child(27),:nth-child(30),:nth-child(41),
      :nth-child(42),:nth-child(44),:nth-child(45),
      :nth-child(47),:nth-child(48),:nth-child(49),
      :nth-child(51),:nth-child(54),:nth-child(56),
      :nth-child(58),:nth-child(60),:nth-child(61),
      :nth-child(63) {
        .hollow;
      }
    }
    
    // Alt pixels
    &.alt {
      :nth-child(42),:nth-child(47),
      :nth-child(44),:nth-child(45),
      :nth-child(49),:nth-child(56),
      :nth-child(58),:nth-child(63)
      {
        .solid;
      }
      :nth-child(43),:nth-child(46),
      :nth-child(50),:nth-child(52),
      :nth-child(53),:nth-child(55),
      :nth-child(57),:nth-child(59),
      :nth-child(62),:nth-child(64)
      {
        .hollow;
      }
    }
  }
  
  &.crab {
    margin-left: 1%;
    margin-right: 1%;
    
    .pixel {
      width: 9%;
    }
    
    // Default pixels
    & {
      
      :nth-child(1),:nth-child(2),:nth-child(4),
      :nth-child(5),:nth-child(6),:nth-child(7),
      :nth-child(8),:nth-child(10),:nth-child(11),
      :nth-child(13),:nth-child(14),:nth-child(16),
      :nth-child(17),:nth-child(18),:nth-child(20),
      :nth-child(21),:nth-child(24),:nth-child(32),
      :nth-child(37),:nth-child(41),:nth-child(56),
      :nth-child(57),:nth-child(65),:nth-child(67),
      :nth-child(68),:nth-child(70),:nth-child(71),
      :nth-child(72),:nth-child(73),:nth-child(66),
      :nth-child(74),:nth-child(76),:nth-child(77),
      :nth-child(78),:nth-child(80),:nth-child(81),
      :nth-child(82),:nth-child(83),:nth-child(84),
      :nth-child(85),:nth-child(86),:nth-child(88)
      {
        .hollow;
      }
    }
      
    // Alt pixels
    &.alt {
      :nth-child(56),:nth-child(66),:nth-child(67),
      :nth-child(77),:nth-child(81),:nth-child(82),
      :nth-child(84),:nth-child(85)
      {
        .solid;
      }
      :nth-child(12),:nth-child(22),:nth-child(23),
      :nth-child(33),:nth-child(34),:nth-child(44),
      :nth-child(87),:nth-child(79)
      {
        .hollow;
      }
    }
  }
  
  &.octopus {
    margin-left: 0.75%;
    margin-right: 0.75%;
    
    .pixel {
      width: 8.333%;
    }
    
    // Default pixels
    & {
      
      :nth-child(1),:nth-child(2),:nth-child(3),
      :nth-child(4),:nth-child(9),:nth-child(10),
      :nth-child(11),:nth-child(12),:nth-child(13),
      :nth-child(24),:nth-child(40),:nth-child(41),
      :nth-child(44),:nth-child(45),:nth-child(61),
      :nth-child(62),:nth-child(63),:nth-child(66),
      :nth-child(67),:nth-child(70),:nth-child(71),
      :nth-child(72),:nth-child(73),:nth-child(74),
      :nth-child(77),:nth-child(80),:nth-child(83),
      :nth-child(84),:nth-child(87),:nth-child(88),
      :nth-child(89),:nth-child(90),:nth-child(91),
      :nth-child(92),:nth-child(93),:nth-child(94)
      {
        .hollow;
      }
    }
      
    // Alt pixels
    &.alt {
      :nth-child(63),:nth-child(70),:nth-child(74),
      :nth-child(83),:nth-child(87),:nth-child(88),
      :nth-child(93),:nth-child(94)
      {
        .solid;
      }
      :nth-child(81),:nth-child(76),:nth-child(85),
      :nth-child(86),:nth-child(95),:nth-child(96),
      {
        .hollow;
      }
    }
  }
  
  &.mothership {
    @transformDelay: @rowDelay3;
    @transformDuration: 7s;
      
    width: 28%;
    z-index: -1;
    transition-duration: @transformDuration;
    transition-delay: @transformDelay;
    
    &.incoming {
      z-index: 0;
      transform: translate(700%, 0%);
      transition: transform @transformDuration linear @transformDelay,
                  opacity 0.25s ease-in;
      animation: none;
    }
    
    &.reset {
      transform: translate(0%, 0%);
      transition-duration: 0s !important;
      transition-delay: 0s !important;
    }
    
    .pixel {
      width: 6%;
    }
    
    // Default pixels
    & {      
      :nth-child(1),:nth-child(2),:nth-child(3),
      :nth-child(4),:nth-child(5),:nth-child(12),
      :nth-child(13),:nth-child(14),:nth-child(15),
      :nth-child(16),:nth-child(17),:nth-child(18),
      :nth-child(19),:nth-child(30),:nth-child(31),
      :nth-child(32),:nth-child(33),:nth-child(34),
      :nth-child(47),:nth-child(48),:nth-child(49),
      :nth-child(52),:nth-child(55),:nth-child(58),
      :nth-child(61),:nth-child(64),:nth-child(81),
      :nth-child(82),:nth-child(86),:nth-child(87),
      :nth-child(90),:nth-child(91),:nth-child(95),
      :nth-child(96),:nth-child(97),:nth-child(98),
      :nth-child(91),:nth-child(99),:nth-child(101),
      :nth-child(102),:nth-child(103),:nth-child(104),
      :nth-child(105),:nth-child(106),:nth-child(107),
      :nth-child(108),:nth-child(103),:nth-child(110),
      :nth-child(111),:nth-child(112),:nth-child(104)
      {
        .hollow;
      }
    }
  }
}

.defender {
  display: flex;  
  flex-flow: row wrap;
  justify-content: space-around;
  position: absolute;
  left: 0;
  bottom: 0;
  width: @defenderWidth;
  height: @defenderWidth / 2.666;
  padding-left: (100% - @defenderWidth) / 2;
  padding-right: (100% - @defenderWidth) / 2;
  padding-bottom: 0;
  transition: all 1.5s ease,
              width 1.5s ease 0.333s,
              padding-left 1.5s ease 0.333s,
              padding-right 1.5s ease 0.333s,
              left 1.5s ease 0.333s,
              padding-bottom 0s,
              opacity 0.25s ease-in 0s,
              transform 0.75s ease-in 0s;
  animation: defender-shuffle 5s ease infinite 3.333s, @squareShake;
  
  .squared & {
    left: 33.333%;
    bottom: 33.333%;
    width: 33.333%;
    height: 33.333% - 2%;
    padding: 1%;
    .instant();
  }
  
  &.destroyed {
    opacity: 0;
    transform: scale(2) rotate(-540deg) translate(0, -1000%);
  }
}

.defender-part {
  width: 25%;
  height: 100%;
  z-index: 2;
  transition: width 1.5s ease 0.333s;

  .pixel {
    width: 24%;
    height: 18%;
  }

  .squared & {
    width: 3.333%;
  }
      
  .ridged & {
    
    &:nth-child(1) {
      :nth-child(1) {
        transition: all 1.5s ease 1s;
        transform: translate(200%, 0);
        opacity: 0;
      }
    }
    
    &:nth-child(4) {
      :nth-child(4) {
        transition: all 1s ease 1s;
        transform: translate(-200%, 0);
        opacity: 0;
      }   
    }
  }  
  
  .destroyed & {
    opacity: 0;
    transform: scale(10) rotate(-540deg);
  }
    
  &:nth-child(5) {
    position: relative;
    top: -100%;
    opacity: 0;
    transition: all 1s ease,
                margin-top 0.1s ease-out;
    
    :nth-child(6),
    :nth-child(7) {
      transition: all 1s ease 1.333s;
      position: relative;
      top: -19%;
    }
    
    .gunned & {
      z-index: 0;
      opacity: 100;
      top: -120%;
      
      :nth-child(6),
      :nth-child(7) {
        transition: all 1s ease 0.333s,
                    margin-top 0.1s ease-out;
        position: relative;
        top: -38%;
      }
    }
      
    .fired & {
      margin-top: 2.5%;
      
      :nth-child(6),
      :nth-child(7) {
        margin-top: 12.5%;
      }        
    }
  }
}

.bullet {
  width: 1%;
  height: 1%;
  position: absolute;
  bottom: 5%;
  opacity: 0;
  transition: bottom 2s linear,
              opacity 0.1s linear;
  animation: spin 0.5s linear infinite;
  
  &.fired {
    opacity: 100;
    bottom: 110%;
  }
}

.bomb {
  width: 2%;
  height: 2%;
  position: absolute;
  top: 12%;
  left: 49%;
  transition: top 2.5s linear;
  animation: spin 1s linear infinite;
  
  &.dropped {
    top: 105%;
  }
  
  .pixel {
    width: 50%;
    height: 50%;
  }
}

.explosion {
  @transTime: 1s;
  @hTrans: ease-out @transTime;
  @vTrans: ease-in @transTime;
  
  .solid;
  
  position: absolute;
  top: 105%;
  left: 25%;
  width: 50%;
  height: 200%;
  margin-top: -1px;
  margin-left: -1px;
  border-radius: 50%;
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  align-content: flex-end;
  justify-content: center;
  
  &.triggered {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: 0;
    transform: scale(0, 0);
    transition: top @vTrans 0s,
                height @vTrans 0s,
                left @hTrans 0s,
                width @hTrans 0s,
                border-radius ease-in (@transTime / 2) (@transTime / 2),
                transform @vTrans (@transTime * 3);
  }
}
              
            
!

JS

              
                // Make NodeList easier to iterate over
NodeList.prototype.forEach = Array.prototype.forEach;

// Convenience function full null or empty checks
function isNullOrEmpty(foo) {
  if (typeof foo === "undefined" || foo === null || foo === "") {
    return true;
  }
  return false;
}

// The global object
var ali = {  

  invadersPerRow: 5,
  defenderParts: 4,
  baseSpeed: 600,

  invaders: {
    jellyfish: {
      name: "jellyfish",
      width: 8,
      height: 8
    },
    crab: {
      name: "crab",
      width: 11,
      height: 8
    },
    octopus: {
      name: "octopus",
      width: 12,
      height: 8
    },
    mothership: {
      name: "mothership",
      width: 16,
      height: 7,
      
      enter: function () {
                
        var $mothership = ali.get.invader(ali.invaders.mothership);
        $mothership.classList.remove("reset");
        $mothership.classList.add("incoming");
        
        var styles = window.getComputedStyle($mothership),            
            incomingDelay = parseInt(styles.transitionDelay) * 1000,
            incomingMidpoint = parseInt(styles.transitionDuration) * 1000 / 2,
            bombBayDoors = incomingDelay + incomingMidpoint;                
        
        setTimeout(function() {
          ali.invaders.mothership.dropBomb();
        }, bombBayDoors);
      },
      
      dropBomb: function () {
        
        var $stage = ali.get.stage(),
            $bomb = ali.make.bomb();
        
        $stage.appendChild($bomb);
        
        setTimeout(function() {
          $bomb.classList.add("dropped");
          setTimeout(function(){
            ali.invaders.mothership.detonateBomb();
          }, ali.baseSpeed * 4);
        }, 100);
        
      },
      
      detonateBomb: function() {
        
        var $container = document.querySelector("#container"),
            $stage = ali.get.stage(),
            $explosion = document.querySelector(".explosion"),
            $invaders = ali.get.allInvaders(),
            $defender = ali.get.defender();
        
        // Show the explosion
        $explosion.classList.add("triggered");
        
        // Destroy the defender :(
        setTimeout(function() {
          ali.defender.destroy();
        }, ali.baseSpeed / 3);
        
        // Remove pixelation and shake the screen
        setTimeout(function() {
          $stage.classList.remove("pixelated");
          $container.classList.add("exploding");
        }, ali.baseSpeed / 2);
        
        // Destroy all invaders
        setTimeout(function() {
          $invaders.forEach(function($invader) {
            ali.invaders.destroy($invader.classList);
          });
        }, ali.baseSpeed / 2);        
        
        // Reset the stage
        setTimeout(function() {
          ali.reset();
        }, ali.baseSpeed * 2);
        
        // Stop shaking the screen
        setTimeout(function() {
          $container.classList.remove("exploding");
        }, ali.baseSpeed * 3.5);
      }
    },

    add: function(invader) {
      var $invader = ali.make.invader(invader),
        $row = ali.get.row(invader),
        pixels = invader.width * invader.height;

      $invader.className = "invader " + invader.name;
      $row.appendChild($invader);

      for (var p = 0; p < pixels; p++) {
        $invader.appendChild(ali.make.pixel());
      }
    },
    
    addRow: function(invader) {
      var $row = ali.make.invaderRow(invader),
          $stage = document.querySelector("#stage");
      $stage.appendChild($row);
    },
      
    destroy: function(classList) {
      
      // Mothership cannot be destroyed (sorry)
      if (classList.contains(ali.invaders.mothership.name)) {
        return;
      }

      // 'Destroy' an invader using a randomly-selected animation,
      // then 'remove' it to avoid triggering collisions
      var destroyedStyle = parseInt(Math.random() * 3);
      classList.add("destroyed");
      classList.add("destroyed-style-" + destroyedStyle);
      setTimeout(function(){
        classList.add("removed");
      }, 1000);

    }
    
  },

  defender: {

    addPart: function() {
      var $defender = ali.get.defender(),
          $defenderPart = ali.make.defenderPart();
      $defender.appendChild($defenderPart);
    },
    
    removePart: function() {
      var $defender = ali.get.defender(),
          $defenderPart = document.querySelector(".defender-part");
      $defender.removeChild($defenderPart);
    },
    
    fire: function() {
      
      var $stage = document.querySelector("#stage"),
          $defender = ali.get.defender(),
          $bullet = ali.make.bullet();
      
      // Cancel firing if the defender isn't yet armed
      if (!$defender.classList.contains("gunned")) {
        return;
      }
      
      // Add a bullet to the stage
      $stage.appendChild($bullet);
      
      // Update the bullet's styles to position it in the defender's gun
      var styles = window.getComputedStyle($defender),
          left = parseInt(styles.paddingLeft),
          width = parseInt(styles.width);
      $bullet.style.left = left + (width / 2) + "px";
      
      // Fire the bullet from the defender's gun
      setTimeout(function() {
        $bullet.classList.add("fired");
        $defender.classList.add("fired");
      }, 100);      
      setTimeout(function() {
        $defender.classList.remove("fired");
      }, 300);
    },
    
    destroy: function () {
      
      var $defender = ali.get.defender();
      $defender.classList.add("destroyed");
      
    }

  },
  
  collisions: {
    
    bullets: function() {
      
      var $bullets = ali.get.bullets();
      
      $bullets.forEach(function($bullet) {
        
        // Find out where our bullet is
        var bulletCoords = $bullet.getBoundingClientRect(),
            bulletLeft = bulletCoords.left,
            bulletTop = bulletCoords.top,
            
        // Find out what else is occupying that space
            $collidingElement = document.elementFromPoint(bulletLeft, bulletTop),
            $collidingElementParent,
            parentClassList;
        
        // If the bullet has left the document's bounds, remove it and stop checking 
        if (isNullOrEmpty($collidingElement)) {          
          $bullet.parentNode.removeChild($bullet);
          return;
        }
        $collidingElementParent = $collidingElement.parentNode;
        
        // Stop checking if the thing we've hit has no parent
        if (isNullOrEmpty($collidingElementParent)) {
          return;
        }
        
        // Stop checking if the thing's parent has no classes
        if (isNullOrEmpty($collidingElementParent.classList)) {
          return;
        }
        parentClassList = $collidingElementParent.classList;
        
        // Continue if we've hit an invader which hasn't yet been destroyed
        if (parentClassList.contains("invader") &&
            !parentClassList.contains("destroyed")) {
          
          // Manipulate CSS to "destory" the invader
          ali.invaders.destroy(parentClassList);
          
          // Remove the bullet as well
          $bullet.parentNode.removeChild($bullet);
        }
      });
      
    }
    
  },

  get: {
    
    stage: function() {
      return document.getElementById("stage");
    },

    allInvaders: function() {
      return document.querySelectorAll(".invader");
    },
    
    invader: function(type) {
      return document.querySelector(".invader." + type.name);
    },
    
    invaders: function(type) {
      return document.querySelectorAll(".invader." + type.name);
    },

    row: function(type) {
      return document.querySelector(".invader-row." + type.name + "-row");
    },

    defender: function() {
      return document.querySelector(".defender");
    },
    
    bullets: function() {
      return document.querySelectorAll(".bullet");
    }

  },

  make: {

    invader: function(invader) {
      var $invader = document.createElement("div");
      $invader.className = "invader " + invader.name;
      return $invader;
    },
    
    invaderRow: function(invader) {
      var $row = document.createElement("div");
      $row.className = "invader-row " + invader.name + "-row";
      return $row;      
    },

    defenderPart: function() {
      var $defenderPart = document.createElement("div"),
          pixels = 20;      

      for (var p = 0; p < pixels; p++) {
        $defenderPart.appendChild(ali.make.pixel());
      }
      $defenderPart.className = "defender-part";
      
      return $defenderPart;
    },

    pixel: function() {
      var $pixel = document.createElement("div");
      $pixel.className = "pixel";
      return $pixel;
    },
    
    bullet: function() {
      var $bullet = ali.make.pixel();      
      $bullet.classList.add("bullet");
      return $bullet;
    },
    
    bomb: function() {      
      var $bomb = document.createElement("div");
      $bomb.className = "bomb";
      for(var p = 0; p < 4; p++) {
        $bomb.appendChild(ali.make.pixel());
      }
      return $bomb;
    }

  },
  
  start: function () {    
    
    var $stage = ali.get.stage(),
        $defender = ali.get.defender();
    
    // Call in the mothership (she takes a while)
    setTimeout(function() {    
      setTimeout(function() {
        ali.invaders.mothership.enter();
      }, ali.baseSpeed / 2);
    }, ali.baseSpeed / 2);
    
    // Break out of the square
    setTimeout(function() {
      $stage.classList.remove("squared");
    }, ali.baseSpeed);
    
    // Let the defender take shape
    setTimeout(function() {
      $stage.classList.add("pixelated");
      $defender.classList.add("ridged");
    }, ali.baseSpeed * 2);
    
    // Reveal the defender's gun
    setTimeout(function() {
      ali.defender.addPart();
      setTimeout(function() {        
        $defender.classList.add("gunned");
      }, 100);
    }, ali.baseSpeed * 4);
    
  },
  
  reset: function() {
    
    var $stage = ali.get.stage(),
        $invaders = ali.get.allInvaders(),
        $mothership = ali.get.invader(ali.invaders.mothership),
        $defender = ali.get.defender(),
        $bullets = ali.get.bullets(),
        $bomb = document.querySelector(".bomb"),
        $explosion = document.querySelector(".explosion");
    
    // Reset the stage
    $stage.classList.add("squared");
    
    // Reset invaders
    $invaders.forEach(function($invader) {
      $invader.classList.remove("destroyed");
      $invader.classList.remove("destroyed-style-0");
      $invader.classList.remove("destroyed-style-1");
      $invader.classList.remove("destroyed-style-2");
      $invader.classList.remove("destroyed-style-3");
      setTimeout(function() {
        $invader.classList.remove("removed");
      }, ali.baseSpeed * 2);
    });
    
    // Reset the mothership
    setTimeout(function() {
      $mothership.classList.remove("incoming");        
      $mothership.classList.add("reset");
    }, ali.baseSpeed * 2);
    
    // Reset defender
    $defender.classList.remove("destroyed");
    $defender.classList.remove("gunned");
    $defender.classList.remove("ridged");
    ali.defender.removePart();
    
    // Remove any remaining bullets
    $bullets.forEach(function($bullet) {
      $bullet.parentNode.removeChild($bullet);
    });
    
    // Reset the explosion
    setTimeout(function() {
      $explosion.classList.remove("triggered");
    }, ali.baseSpeed * 4.5);
    
    // Circle the square
    setTimeout(function() {
      ali.start();
    }, ali.baseSpeed * 4.5);
    
  },

  init: function() {

    // Add invaders
    for (var j = 0; j < ali.invadersPerRow; j++) {
      ali.invaders.add(ali.invaders.jellyfish);
      ali.invaders.add(ali.invaders.crab);
      ali.invaders.add(ali.invaders.octopus);
    }

    // Add defender parts
    var $defender = ali.get.defender();
    for (var k = 0; k < ali.defenderParts; k++) {
      ali.defender.addPart();
    }
    
    // Cycle invaders' styles
    setInterval(function() {
      var $invaders = ali.get.allInvaders()
      $invaders.forEach(function($invader) {
        $invader.classList.toggle("alt");
      });
    }, ali.baseSpeed * 1.5);
    
    // Prep mothership    
    ali.invaders.addRow(ali.invaders.mothership);
    ali.invaders.add(ali.invaders.mothership);  
    
    // Prep explosion
    var $stage = ali.get.stage(),
        $explosion = document.createElement("div");        
    $explosion.classList.add("explosion");
    $stage.appendChild($explosion);
    
    // Begin bullet-firing timer and start checking for collisions
    setTimeout(function() {
      
      setInterval(function() {
        ali.defender.fire();
      }, ali.baseSpeed * 2);
      
      setInterval(function() {
        ali.collisions.bullets();
      }, 100);
      
    }, ali.baseSpeed * 4.5);
    
    // Start the invasion
    ali.start();
  }  
};

// Let's go
ali.init();
              
            
!
999px

Console