How I Built a Nintendo Gameboy with Flexbox and SVGs

Gameboy and Gameboy Advance

In an effort to be better at things I've been giving myself little challenges lately. "Be polite and smile to the man behind the counter" the responsible section in my brain reminds me as I politely thank Gabe for my coffee and leave a small tip. Did I say small tip? I meant generous tip.

One of my recent challenges has been to build something inspired by all the things from the most hearted codepens of 2014, and to do that in 2015. It's only 100 things, that should be doable right? Welp, it's July and I'm only on number two (here was number one). Still, better than zero right!?

Number two on that list is an awesome glitch tv effect by Lucas Bebber.

Initially I wanted to do a tv with scanlines...

...but I got distracted and turned it into a gameboy

Play with it in codepen

To find out the method to my madness keep reading.

Paper Prototype Like a Pro

I gave myself 2 hours to do this project. A limited time frame means I need to do this efficiently. Paper prototyping is just as important now as it is in long-term projects. It allows you to quickly think through creative and technical roadblocks while providing you with a visial guide to reference throughout the project. In long-term projects it is useful as a reminder of the project's core message.

gameboy paper prototype

SCSS

I'm building this in SCSS because it is super rad and because a lot of web designers are familiar with it. If you don't know how to use it it is also relatively simple to learn, as its syntax is pretty close to that of css. Apparently the next bootstrap will be in SCSS.

Different web browsers display things differently. We want to change some things to ensure different browsers display closer to what we are designing in (in my case I'm using codepen in webkit). The things we want to reset are the the root element's font-size and height as well as the body element's margin and height.

html {font-size:8px;///used for rem base

You can see in my scss above that we are setting the root element's (html) font-size to 8px. The rem unit of measurement is based on this number. If all of our units are rem based, we can change this number (the html font-size: 8px;) later to update all our measurements like magic.


Next we need to fix a problem I don't imagine many sites have. That is the problem of having zero content. There will be markup in the HTML, but there is no content that is being marked up.

Since the html will have no content to display we need to tell the body element to display everything within 100% of the height of the full (100%) root element (html).

It's madness I know.

////////////
/// BASE /// base settings
////////////

html {
  font-size:8px;///used for rem base
  height:100%;///vertically fills page
}

body {
  height:100%;///vertically fills pg

Finally we will remove all the default margins that our browser has set for us so we don't have any white space.

html {
  font-size:8px;///used for rem base
  height:100%;///vertically fills page
}

body {
  height:100%;///vertically fills pg
  margin:0;///browser reset
}

Value Scale Time

Value Scale

We need to generate our greyscale value pallet. The great thing about working in a css preprocessor like Sass is the amount of time you can save by keeping things DRY. In this example we will create a loop that generates an HSL background color value of 100 shades between white and black. I am doing the work in SCSS (a syntax of Sass, which should never be written as SASS #webDesignProblems).

We will asign the value ($lightness in the following example) to a block level element's background color and use our custom grid to build the glorious handheld.

@for $lightness from 0 through 100 {
  .bg#{$lightness} 
  {background-color:
    hsl(0deg, 0%, $lightness);
  }
}

Let us walk through this loop together. It's actually a pretty simple.

@for signifies the beginning of a loop.

We are going to take a variable ($lightness) and loop it through 101 times. See how we are starting at zero and not one (@for $lightness from **0** through **100** { ...)?

The first time the loop runs this is the css output:

.bg0 {
  background-color: black;
}

You can see that we have created a presentational selector class named bg0 that sets the background-color to black.

The second time the loop runs this is the css output:

.bg1 {
  background-color: #030303;
}

The final time the loop runs this is the css output:

.bg100 {
  background-color: white;
}

Here is what is going on. The loop starts with an undeclared variable named $lightness. It is told to expect to loop 101 times. It is then told to append the value of $lightness onto the .bg selector name, which creates

.bg100

Next we are going to assign the background color to this selector. We know that we want to use only shades of grey. There is no reason to use resources and generate every saturation of every hue of every lightness, so we are going to use an HSL based color system in our loop. Not all browsers support an HSL color system so Sass will convert the values to hex for us via the power of magic. Dark magic. BLOOD MAGIC!

That leaves us with the final css output from the first run through:

.bg0 {
  background-color: black;
}

Only 100 more to do. Go Sass! This of course happens very quickly because we aren't wasting computer time generating all the hues and saturations we don't need.

We now have a nice set of greyscale 'swatches' to work from. Just remember that 0=black and 100=white. So .bg50 would be 50% or grey. Now we just need a grid system to lay everything out inside!

Flexbox - Relatively Strong

I really like flexbox. I like the idea of thinking entirely in relative units. The reason why I want to build this using css3 flexbox is because it is difficult to use in our daily production work. It doesn't work right in older browsers, but that doesn't mean we can't learn it and dream about the day when all of the imperfect things will be perfect.

Boxes

You can think of flexboxes like table cells in excel.

Table Cells If you have used display:table-cell in your css you'll be on familiar footing. We will also need to create columns for our grid. We don't need to create rows, as flexbox displays in rows by defualt.

We will create a second loop to generate our grid's cells based on our paper prototype's 21 units tall by 13 units wide. I know (based on my sketch) that I want the handheld to have a border between the edge of the document of 1 relative unit, so I will need a grid with a maximum of 23 units. What if we change our mind later though and we need more (or less) units? We can fix that easily with a couple variabls.

Here is how you declare [global variables] in SCSS to set the min and max unit sizes in our grid:

$grid-min:1;
$grid-max:23;

Pretty simple right? You can name the variables whatever you want too. You could call it $this-is-the-minimum-number-fo-sho:1; but that's just silly.

A dollar sign $ in front of a string tells SCSS it is dealing with a variable. In this case the variable $grid-min is (:) 1, and the variable $grid-max is (:) 23.

Next we will create a loop that utilizes our two shiny new variables. The loop will build a responsive flexable grid (so the gameboy looks similar across multiple portrait devices) using CSS3's display:flex. The downside of this approach is the gameboy won't be the same proportions across devices.

@for $grid from $grid-min through $grid-max {
  .flex#{$grid}
    {display: flex; flex: $grid; 
  }
}

Witchcraft Witchcraft! Witchcraft indeed. Just like the first loop, this second loop starts with @for

Just like our first loop, our second loop then has an undefined variable. $grid

Unlike our first loop, our second loop uses 2 previously defined global variables ($grid-min and $grid-max) with the values 1 and 23 to instruct the loop how many times it will be running.

You might have also noticed we are starting at 1, instead of 0 as in our first loop. Since $grid-min is (:) 1 instead of 0, this loop will run 23 times.

Every time the loop runs it will create a selector named .flex. The loop will append the value of $grid to the end of the .flex selector name.

.flex#{$grid}

This happens the same place it happened in the first loop, the values here are just different.

Remember that in the first loop we had the value of the variable added to the end of the .bg class selector like this: .bg#{$lightness}

Finally we will add the display:flex; declaration which enables the magic of flexbox as well as set the amount (in relative units) assigned to the $grid variable that the flex box can grow and shrink to fill.

By allowing the boxes to get bigger and smaller based on relative units we can give a similar experience to many users across many similar screen orientations (vertical or portrait in the case of the gameboy).

Fruity Loops

When this fruity loop runs the final css output will look like this:

.flex1 {
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-flex: 1;
      -ms-flex: 1;
          flex: 1;
}

...
...

.flex23 {
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-flex: 23;
      -ms-flex: 23;
          flex: 23;
}

Did you notice something you weren't expecting? Where did these extra declarations come from:

display: -webkit-flex;
 display: -ms-flexbox;

     -webkit-flex:...;
         -ms-flex:...;

Those were added automatically with a processor called autoprefixer which you can enable in any codepen, or on your own via your own build process.

Columns

Columns Remember that we don't need to create rows (horizontal), flexbox will layout our boxes in rows for us automatically. We do need to tell a box when to display its children vertically in columns, and we do that with the flex-direction property.

.column {flex-direction:column;}

We can now tell any box to display it's direct descendents in a vertically stacked column.

| | |
| | |
| | |
| | |

Let's Draw a Gameboy!

Gameboy

We have our greyscale color pallet initialized as well as our custom responsive grid. Let's draw our first box in our html!

<div></div>

It needs to be a flexbox (remember they are similar to cells in an excel table), and we will give it a size of 15 units because that matches the grid on our paper prototype.

<div class="box15"></div>

The size of this container box doesn't actually matter. Every flexbox will be nested inside this one, so there is no sibling to compare against this box. In other words it is always relative only to itself.
These all produce the exact same result:

<div class="box15"></div>
<div class="box1"></div>
<div class="box2"></div>

Another way to think about it is:

15/15
1/1
2/2

Since this first box contains all other boxes, it will serve as our background. We will give it a $lightness value of 20 (dark grey).

<div class="box15 bg20"></div>

Finally we need to give this container flexbox a height of 100% (relative to the body, which is 100% of the document, which is 100% of the viewport).

We first create the selector .full-height in the SCSS

.full-height {height:100%;}

and add it to the flexbox in the markup

<div class="box15 bg20 full-height"></div>

Congratulations, you now have a full screen dark grey window.

I'm not going to walk you through all of the nesting madness that is about to happen, but I will walk you through creating the margins and the gameboy's main container.

Nesting Flexboxes

Nesting Dolls Loop

We already have our very first flexbox that everything else will go inside:

<div class="box15 bg20 full-height">
    ...
    ...
    ...
</div>

A parent flexbox will display it's childeren in a row by default, so let's create our first set of rows making up the left margin of 1 unit, the gameboy built out of 15 horizontal units, and the right side margin 1 unit wide.

<div class="box15 bg20 full-height">
    <div class="box1></div>
    <div class="box15 column></div>
    <div class="box1></div>
</div>

Did you notice that the box containing the gameboy has an extra class named column added onto it? The next set of child divs we create will be stacked vertically in a column inside the 15 unit wide box via the .column selector we made previously.

Now we can nest inside the div class="box15" like so:

<div class="box15 column>
    ...
    ...
    ...
</div>

Inside this column we will set the top margin of 1 unit in height.

<div class="box1"></div>

the gameboy container of 21 units with a background that is 80% white and 20% black

<div class="box21 bg80 column"></div>

and finally finish it off with a bottom margin of 1 unit in height.

<div class="box1"></div>

I have added red outlines to the boxes so you can more clearly see how they are laid out.

From this point just start nesting and building colored boxes to match the shapes in the paper-prototype. Think of it like a paint-by-the-number kit. After a bit of playing here is the basic shape built in flexboxes matching our paper-prototype (again with red borders added so you can see where the boxes all line up).

It is starting to look like a Gameboy but it still needs a few buttons (start, select, a and b) as well as the speaker.

Scalable Vector Graphics

Scalable Vector Graphics, (SVG) allow you to do [many amazing things]. It was once a dream of mine to be a comic book illustrator, and creating vector graphics fullfills part of that ancient dream. It was also once a dream of mine to be a cyborg space pirate. Some dreams are before their time I suppose.

The gameboy design only requires us to create 2 basic shapes. A circle shape and a couple pill shapes.

If you don't know how to create vector graphics don't worry about it for now. You can download and use my above linked shapes. You could also use a raster image it just won't look as handsome when it scales.

A and B Buttons

Let's start with the simplest shape. The circle. We will take that shape and set it as a backgournd image inside of a flexbox. We will center the image in the box and stop it from repeating in a pattern. We only want one shape per flexbox. Finally we will position the buttons into the html markup and offset them by 30 degrees. I wonder why Nintendo swapped the A and B button positions? If you know why let me know.

Since I'm prototyping quickly and I'm not sure if I'll need to swap out svgs during this process (I actually swapped out 7 different svgs), let's set the location of this circle-84173e.svg as a variable $button.

$button:
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/52367/circle-84173e.svg";

Next we can create the selector

.svg-circle {}

We then enter the url pointing to our svg background image

background-image: url($button);

Let's position the svg in this selector's block

background-position: center;

Finally we can instruct the background image to appear only once.

background-repeat: no-repeat;

How does that look all assembled?

$button:
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/52367/circle-84173e.svg";

.svg-circle {
  background-image: url($button);
  background-position: center;
  background-repeat: no-repeat;
}

Gorgeous. Like you <3

Now let's add the svgs for the start / select buttons and the speaker grill. As you can see in the links, these images have been rotated 30 degrees in the svg already. We will do some css rotations in just a moment for the A and B buttons.

$start-select:
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/52367/gb-start-button.svg";

$speaker:
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/52367/gb-speaker.svg";

Fabulous. Now let's display those bad boys as a centered, non-repeatable background image.

.svg-pill {
  background-image: url($start-select);
  background-position: center;
  background-repeat: no-repeat;
}

.svg-pills {
  background-image: url($speaker);
  background-position: center;
  background-repeat: no-repeat;
}

It's getting closer. Those A/B buttons and the corners of the gameboy don't look right though. Also it looks pretty bananas on a landscape (horizontal) screen. Let's start by fixing those buttons shall we?

Rotate

It's pretty simple to rotate stuff in CSS. Watch this.

///used to offset the a and b buttons
.rotate30 {transform: rotate(150deg);}

Boom.

Rounded Corners

The Gameboy has a lot of rounded corners. Check it out, they are on the exterior corners as well as the screens. They aren't consistent either, the bottom right corners of the screen and the case are different than the others of their siblings. We will need a few different sizes but what sizes will we need?

I will need some at 1rem, some at 2rem and some at 3rem. I will also need one at .5rem, one at 6rem(the bottom right screen corner) and one at 8rem(the bottom right gameboy corner).

Let's do a loop for 1-3 for good practice. You remember how to do a loop by now right?

@for to initiate the loop

@for

create an undefined variable

@for $corners

loop it from 1 to 3

@for $corners from 1 through 3 {}

append the number to a class selector

@for $corners from 1 through 3 {
  .corners#{$corners}

and define the border-radius in rems.

///loop to create corners
@for $corners from 1 through 3 {
  .corners#{$corners} {border-radius:$corners+rem;}
}

Closer, but still needs the larger rounded corners in the bottom right of the screen and case, as well as smaller rounded screen corners.

///64px - bottom right corner of gameboy
.corner-br-8 {
  border-bottom-right-radius:8rem;
}

///48px - bottom right corner of screen container
.corner-br-6 {
  border-bottom-right-radius:6rem;
}

///inner screen corners 
.corners05 {
  border-radius:.5rem;
}

Landscape Display

Looking pretty gameboy like. Let's add something special in there for when this is displayed in landscape.

@media all and (orientation: landscape) {
  /// hides the original gameboy
  .hide-portrait {display: none;}
}

@media all and (orientation: portrait) {
  /// hides the original gameboy
  .hide-landscape {display: none;}
}

$landscape:"https://s3-us-west-2.amazonaws.com/s.cdpn.io/52367/gba.svg";

.svg-landscape {
  background-image: url($landscape);
  background-position: center;
  background-repeat: no-repeat;
}


This definitely wasn't the cleanest project, but I did learn a few things while playing with it and I hope you did too. If you have any comments, corrections, or questions let me know on twitter, dribbble or behance. I'm available for hire as well if you need help with design, development, strategizing, or anything else that sounds fun :D

If you fork the codepen and make it even more incredible let me know as well!