diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0d90a4c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# 20euros
+
+A clone of 2048 game with euro coins and bills
+https://github.com/jatekos101/20euros
+
diff --git a/conf/nginx.conf b/conf/nginx.conf
new file mode 100644
index 0000000..8ec2b2d
--- /dev/null
+++ b/conf/nginx.conf
@@ -0,0 +1,13 @@
+location YNH_WWW_PATH {
+ alias YNH_WWW_ALIAS ;
+
+ # Force https
+ if ($scheme = http) {
+ rewrite ^ https://$server_name$request_uri? permanent;
+ }
+
+ index index.html;
+
+ # Include SSOWAT user panel.
+ include conf.d/yunohost_panel.conf.inc;
+}
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..c29c92c
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,43 @@
+{
+ "name": "20euros",
+ "id": "20euros",
+ "description": {
+ "en": "2048 - 20euros",
+ "fr": "2048 - 20euros"
+ },
+ "developer": {
+ "name": "Moul",
+ "email": "moul@moul.re"
+ },
+ "multi_instance": "false",
+ "arguments": {
+ "install" : [
+ {
+ "name": "domain",
+ "ask": {
+ "en": "Choose a domain for 20euros",
+ "fr": "Choisissez un nom de domaine pour 20euros"
+ },
+ "example": "domain.org"
+ },
+ {
+ "name": "path",
+ "ask": {
+ "en": "Choose a path for 20euros",
+ "fr": "Choisissez un chemin pour 20euros"
+ },
+ "example": "/20euros",
+ "default": "/20euros"
+ },
+ {
+ "name": "is_public",
+ "ask": {
+ "en": "Is it a public game?",
+ "fr": "Est-ce un jeu public ?"
+ },
+ "choices": ["Yes", "No"],
+ "default": "Yes"
+ }
+ ]
+ }
+}
diff --git a/scripts/install b/scripts/install
new file mode 100755
index 0000000..07e1fd2
--- /dev/null
+++ b/scripts/install
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+# Retrieve arguments
+domain=$1
+path=$2
+is_public=$3
+
+# Check domain/path availability
+sudo yunohost app checkurl $domain$path -a 20euros
+if [[ ! $? -eq 0 ]]; then
+ exit 1
+fi
+
+
+# Save app settings
+sudo yunohost app setting 20euros is_public -v "$is_public"
+
+# Copy files to the right place
+final_path=/var/www/20euros
+sudo mkdir -p $final_path
+sudo cp -a ../sources/* $final_path
+
+# Set permissions
+sudo chown -R www-data: $final_path
+
+# Modify Nginx configuration file and copy it to Nginx conf directory
+sed -i "s@YNH_WWW_PATH@$path@g" ../conf/nginx.conf
+sed -i "s@YNH_WWW_ALIAS@$final_path/@g" ../conf/nginx.conf
+nginxconf=/etc/nginx/conf.d/$domain.d/20euros.conf
+sudo cp ../conf/nginx.conf $nginxconf
+sudo chown root: $nginxconf
+sudo chmod 600 $nginxconf
+
+if [ "$is_public" = "Yes" ];
+then
+ sudo yunohost app setting 20euros skipped_uris -v "/"
+fi
+
+# Reload web server & sso
+sudo service nginx reload
+sudo yunohost app ssowatconf
diff --git a/scripts/remove b/scripts/remove
new file mode 100755
index 0000000..d70c7e4
--- /dev/null
+++ b/scripts/remove
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+domain=$(sudo yunohost app setting 20euros domain)
+
+sudo rm -rf /var/www/20euros
+sudo rm -f /etc/nginx/conf.d/$domain.d/20euros.conf
diff --git a/scripts/upgrade b/scripts/upgrade
new file mode 100644
index 0000000..21363b7
--- /dev/null
+++ b/scripts/upgrade
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Retrieve settings
+domain=$(sudo yunohost app setting 20euros domain)
+path=$(sudo yunohost app setting 20euros path)
+is_public=$(sudo yunohost app setting 20euros is_public)
+
+# Remove trailing "/" for next commands
+path=${path%/}
+
+# Copy files to the right place
+final_path=/var/www/20euros
+sudo mkdir -p $final_path
+sudo cp -a ../sources/* $final_path
+
+# Set permissions
+sudo chown -R www-data: $final_path
+
+# Modify Nginx configuration file and copy it to Nginx conf directory
+sed -i "s@YNH_WWW_PATH@$path@g" ../conf/nginx.conf
+sed -i "s@YNH_WWW_ALIAS@$final_path/@g" ../conf/nginx.conf
+nginxconf=/etc/nginx/conf.d/$domain.d/20euros.conf
+sudo cp ../conf/nginx.conf $nginxconf
+sudo chown root: $nginxconf
+sudo chmod 600 $nginxconf
+
+if [ "$is_public" = "Yes" ];
+then
+ sudo yunohost app setting 20euros skipped_uris -v "/"
+fi
+
+# Reload web server & sso
+sudo service nginx reload
+sudo yunohost app ssowatconf
diff --git a/sources/README.md b/sources/README.md
new file mode 100644
index 0000000..6bf2010
--- /dev/null
+++ b/sources/README.md
@@ -0,0 +1,8 @@
+20euros
+=======
+
+A 2048 variant with Euro coins and notes.
+
+You'll find the game forum here: http://20euros.freeforums.net/
+
+Feel free to fork my version and make pull requests!
diff --git a/sources/comment.html b/sources/comment.html
new file mode 100644
index 0000000..cb35282
--- /dev/null
+++ b/sources/comment.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ 20 Euros Forum
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/faq.html b/sources/faq.html
new file mode 100644
index 0000000..b36cc61
--- /dev/null
+++ b/sources/faq.html
@@ -0,0 +1,79 @@
+
+
+
+
+ 20 Euros FAQ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
20 Euros FAQ
+
+
Frequently asked questions about my game 20 Euros .
+
+
+
Rules
+
Q: The game is buggy, sometimes it won't let me add two equal coins.
+A: Read the rules under the game first! For example, you can't add up two 2's to make 5, it wouldn't even make sense! To make a 5 cent coin, you have to combine two 2's and a 1.
+
+
Q: Why doesn't 2+2+2+2+2 make 10?
+A: It's clearly written in the rules that at most 3 coins can merge at a time. Otherwise it would be too easy.
+
+
Q: Can I continue after winning and make a 50 Euro note?
+A: Yes! You can even go all the way up to the 500 Euro note, if you can! That has actually been achieved (with an AI).
+
+
+
Variants
+
Q: Does the game have any variants?
+A: The only official variant is the undo version .
+
+
Q: Can I make my own variant/fork?
+A: Yes! You are free to make your own forks based on my variant, you can download it or fork it on GitHub.
+
+
+
+
Others
+
+Q: Is there a forum about the game?
+A: Yes, it's here . Feel free to add comments, suggestions or further questions!
+
+
Q: Are the statistics of the game public?
+A: Yes, you can find them here .
+
+
+
+If you didn't find the answer to your question, you can ask it on the
forum .
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/images/bg_hr.png b/sources/images/bg_hr.png
new file mode 100644
index 0000000..7973bd6
Binary files /dev/null and b/sources/images/bg_hr.png differ
diff --git a/sources/images/blacktocat.png b/sources/images/blacktocat.png
new file mode 100644
index 0000000..6e264fe
Binary files /dev/null and b/sources/images/blacktocat.png differ
diff --git a/sources/images/icon_download.png b/sources/images/icon_download.png
new file mode 100644
index 0000000..a2a287f
Binary files /dev/null and b/sources/images/icon_download.png differ
diff --git a/sources/images/sprite_download.png b/sources/images/sprite_download.png
new file mode 100644
index 0000000..f2babd5
Binary files /dev/null and b/sources/images/sprite_download.png differ
diff --git a/sources/index.html b/sources/index.html
new file mode 100644
index 0000000..d56b4f9
--- /dev/null
+++ b/sources/index.html
@@ -0,0 +1,161 @@
+
+
+
+
+ 20 Euros
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/javascripts/main.js b/sources/javascripts/main.js
new file mode 100644
index 0000000..d8135d3
--- /dev/null
+++ b/sources/javascripts/main.js
@@ -0,0 +1 @@
+console.log('This would be the main JS file.');
diff --git a/sources/js/animframe_polyfill.js b/sources/js/animframe_polyfill.js
new file mode 100644
index 0000000..c45a13e
--- /dev/null
+++ b/sources/js/animframe_polyfill.js
@@ -0,0 +1,26 @@
+(function() {
+ var lastTime = 0;
+ var vendors = ['webkit', 'moz'];
+ for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
+ window.cancelAnimationFrame =
+ window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
+ }
+
+ if (!window.requestAnimationFrame) {
+ window.requestAnimationFrame = function(callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function() { callback(currTime + timeToCall); },
+ timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ }
+
+ if (!window.cancelAnimationFrame) {
+ window.cancelAnimationFrame = function(id) {
+ clearTimeout(id);
+ };
+ }
+}());
diff --git a/sources/js/application.js b/sources/js/application.js
new file mode 100644
index 0000000..7cf796a
--- /dev/null
+++ b/sources/js/application.js
@@ -0,0 +1,3 @@
+// Wait till the browser is ready to render the game (avoids glitches)
+window.requestAnimationFrame(function () {
+ new GameManager(5, KeyboardInputManager, HTMLActuator, LocalScoreManager);});
diff --git a/sources/js/game_manager.js b/sources/js/game_manager.js
new file mode 100644
index 0000000..c5e145b
--- /dev/null
+++ b/sources/js/game_manager.js
@@ -0,0 +1,328 @@
+function GameManager(size, InputManager, Actuator, ScoreManager) {
+ this.size = size; // Size of the grid
+ this.inputManager = new InputManager;
+ this.scoreManager = new ScoreManager;
+ this.actuator = new Actuator;
+
+ this.startTiles = 2;
+
+ this.inputManager.on("move", this.move.bind(this));
+ this.inputManager.on("restart", this.restart.bind(this));
+ this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
+
+ this.setup();
+}
+
+// Restart the game
+GameManager.prototype.restart = function () {
+ this.actuator.
+ continue ();
+ this.setup();
+};
+
+// Keep playing after winning
+GameManager.prototype.keepPlaying = function () {
+ this.keepPlaying = true;
+ this.actuator.
+ continue ();
+};
+
+GameManager.prototype.isGameTerminated = function () {
+ if (this.over || (this.won && !this.keepPlaying)) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
+// Set up the game
+GameManager.prototype.setup = function () {
+ this.grid = new Grid(this.size);
+
+ this.score = 0;
+ this.highestTile = 5;
+ this.over = false;
+ this.won = false;
+ this.keepPlaying = false;
+
+ // Add the initial tiles
+ this.addStartTiles();
+
+ // Update the actuator
+ this.actuate();
+};
+
+// Set up the initial tiles to start the game with
+GameManager.prototype.addStartTiles = function () {
+ for (var i = 0; i < this.startTiles; i++) {
+ this.addRandomTile();
+ }
+};
+
+// Adds a tile in a random position
+GameManager.prototype.addRandomTile = function () {
+ if (this.grid.cellsAvailable()) {
+ var value = Math.random() < 0.9 ? 1 : 5;
+ var tile = new Tile(this.grid.randomAvailableCell(), value);
+
+ this.grid.insertTile(tile);
+ }
+};
+
+// Sends the updated grid to the actuator
+GameManager.prototype.actuate = function () {
+ if (this.scoreManager.get() < this.score) {
+ this.scoreManager.set(this.score);
+ }
+
+ this.actuator.actuate(this.grid, {
+ score: this.score,
+ highest: this.highestTile,
+ over: this.over,
+ won: this.won,
+ bestScore: this.scoreManager.get(),
+ terminated: this.isGameTerminated()
+ });
+
+};
+
+// Save all tile positions and remove merger info
+GameManager.prototype.prepareTiles = function () {
+ this.grid.eachCell(function (x, y, tile) {
+ if (tile) {
+ tile.mergedFrom = null;
+ tile.savePosition();
+ }
+ });
+};
+
+// Move a tile and its representation
+GameManager.prototype.moveTile = function (tile, cell) {
+ this.grid.cells[tile.x][tile.y] = null;
+ this.grid.cells[cell.x][cell.y] = tile;
+ tile.updatePosition(cell);
+};
+
+// Move tiles on the grid in the specified direction
+GameManager.prototype.move = function (direction) {
+ // 0: up, 1: right, 2:down, 3: left
+ var self = this;
+
+ if (this.isGameTerminated()) return; // Don't do anything if the game's over
+ var cell, tile;
+
+ var vector = this.getVector(direction);
+ var traversals = this.buildTraversals(vector);
+ var moved = false;
+
+ // Save the current tile positions and remove merger information
+ this.prepareTiles();
+
+ // Traverse the grid in the right direction and move tiles
+ traversals.x.forEach(function (x) {
+ traversals.y.forEach(function (y) {
+ cell = {
+ x: x,
+ y: y
+ };
+ tile = self.grid.cellContent(cell);
+
+ if (tile && tile.merged) {
+ self.grid.removeTile(tile);
+ } else if (tile) {
+ var positions = self.findFarthestPosition(cell, vector);
+ var next = self.grid.cellContent(positions.next);
+ var pos2 = null;
+ var next2 = null;
+ if (next) {
+ pos2 = self.findFarthestPosition({
+ x: next.x,
+ y: next.y
+ }, vector);
+ next2 = self.grid.cellContent(pos2.next);
+ }
+ // Only one merger per row traversal?
+ if (next && next2 && !next.mergedFrom && next != next2 && (((tile.value == 1) && (next.value == 2) && (next2.value == 2)) || ((tile.value == 2) && (next.value == 1) && (next2.value == 2)) || ((tile.value == 2) && (next.value == 2) && (next2.value == 1)) || ((tile.value == 10) && (next.value == 20) && (next2.value == 20)) || ((tile.value == 20) && (next.value == 10) && (next2.value == 20)) || ((tile.value == 20) && (next.value == 20) && (next2.value == 10)) || ((tile.value == 100) && (next.value == 200) && (next2.value == 200)) || ((tile.value == 200) && (next.value == 100) && (next2.value == 200)) || ((tile.value == 200) && (next.value == 200) && (next2.value == 100)) || ((tile.value == 1000) && (next.value == 2000) && (next2.value == 2000)) || ((tile.value == 2000) && (next.value == 1000) && (next2.value == 2000)) || ((tile.value == 2000) && (next.value == 2000) && (next2.value == 1000)) || ((tile.value == 10000) && (next.value == 20000) && (next2.value == 20000)) || ((tile.value == 20000) && (next.value == 10000) && (next2.value == 20000)) || ((tile.value == 20000) && (next.value == 20000) && (next2.value == 10000)) || ((tile.value == 5) && (next.value == 10) && (next2.value == 5)) || ((tile.value == 50) && (next.value == 100) && (next2.value == 50)) || ((tile.value == 500) && (next.value == 1000) && (next2.value == 500)) || ((tile.value == 5000) && (next.value == 10000) && (next2.value == 5000)))) {
+
+ var merged = new Tile(pos2.next, tile.value + next.value + next2.value);
+ merged.mergedFrom = [next2, next, tile];
+ tile.merged = true;
+ next.merged = true;
+ next2.merged = true;
+
+ self.grid.removeTile(tile);
+ self.grid.removeTile(next);
+ self.grid.removeTile(next2);
+
+ self.grid.insertTile(merged);
+ // Converge the two tiles' positions
+ tile.updatePosition(pos2.next);
+ next.updatePosition(pos2.next);
+ // Update the score
+ self.score += merged.value;
+
+ if (merged.value > self.highestTile) self.highestTile = merged.value;
+
+ // The mighty 20 Euro tile
+ if (merged.value === 2000) self.won = true;
+ } else if (next && next.value === tile.value && !next.mergedFrom && next.value != 2 && next.value != 20 && next.value != 200 && next.value != 2000 && next.value != 20000 && next.value != 50000) {
+
+ var merged = new Tile(positions.next, tile.value * 2);
+ merged.mergedFrom = [tile, next];
+ tile.merged = true;
+ next.merged = true;
+
+ self.grid.insertTile(merged);
+ self.grid.removeTile(tile);
+
+ // Converge the two tiles' positions
+ tile.updatePosition(positions.next);
+
+ // Update the score
+ self.score += merged.value;
+
+ if (merged.value > self.highestTile) self.highestTile = merged.value;
+
+ // The mighty 20 Euro tile
+ if (merged.value === 2000) self.won = true;
+ } else if (!tile.merged) {
+ self.moveTile(tile, positions.farthest);
+ }
+
+ if (tile && !self.positionsEqual(cell, tile)) {
+ moved = true; // The tile moved from its original cell!
+ }
+ }
+
+ });
+ });
+
+ if (moved) {
+ this.addRandomTile();
+
+ if (!this.movesAvailable()) {
+ this.over = true; // Game over!
+ }
+
+ this.actuate();
+ }
+};
+
+// Get the vector representing the chosen direction
+GameManager.prototype.getVector = function (direction) {
+ // Vectors representing tile movement
+ var map = {
+ 0: {
+ x: 0,
+ y: -1
+ },
+ // up
+ 1: {
+ x: 1,
+ y: 0
+ },
+ // right
+ 2: {
+ x: 0,
+ y: 1
+ },
+ // down
+ 3: {
+ x: -1,
+ y: 0
+ } // left
+ };
+
+ return map[direction];
+};
+
+// Build a list of positions to traverse in the right order
+GameManager.prototype.buildTraversals = function (vector) {
+ var traversals = {
+ x: [],
+ y: []
+ };
+
+ for (var pos = 0; pos < this.size; pos++) {
+ traversals.x.push(pos);
+ traversals.y.push(pos);
+ }
+
+ // Always traverse from the farthest cell in the chosen direction
+ if (vector.x === 1) traversals.x = traversals.x.reverse();
+ if (vector.y === 1) traversals.y = traversals.y.reverse();
+
+ return traversals;
+};
+
+GameManager.prototype.findFarthestPosition = function (cell, vector) {
+ var previous;
+
+ // Progress towards the vector direction until an obstacle is found
+ do {
+ previous = cell;
+ cell = {
+ x: previous.x + vector.x,
+ y: previous.y + vector.y
+ };
+ } while (this.grid.withinBounds(cell) && this.grid.cellAvailable(cell));
+
+ return {
+ farthest: previous,
+ next: cell // Used to check if a merge is required
+ };
+};
+
+GameManager.prototype.movesAvailable = function () {
+ return this.grid.cellsAvailable() || this.tileMatchesAvailable();
+};
+
+// Check for available matches between tiles(more expensive check)
+GameManager.prototype.tileMatchesAvailable = function () {
+ var self = this;
+
+ var tile;
+ var i = 0;
+ while (i < 4) {
+ for (var x = 0; x < this.size; x++) {
+ for (var y = 0; y < this.size; y++) {
+ var vector = this.getVector(i);
+ tile = this.grid.cellContent({
+ x: x,
+ y: y
+ });
+
+ cell = {
+ x: x,
+ y: y
+ };
+ tile = self.grid.cellContent(cell);
+
+ if (tile) {
+ var positions = self.findFarthestPosition(cell, vector);
+ var next = self.grid.cellContent(positions.next);
+ var pos2 = self.findFarthestPosition({
+ x: cell.x + vector.x,
+ y: cell.y + vector.y
+ }, vector);
+ var next2 = self.grid.cellContent(pos2.next);
+ // Only one merger per row traversal?
+ if (next && next2 && next != next2 && (((tile.value == 1) && (next.value == 2) && (next2.value == 2)) || ((tile.value == 2) && (next.value == 1) && (next2.value == 2)) || ((tile.value == 2) && (next.value == 2) && (next2.value == 1)) || ((tile.value == 10) && (next.value == 20) && (next2.value == 20)) || ((tile.value == 20) && (next.value == 10) && (next2.value == 20)) || ((tile.value == 20) && (next.value == 20) && (next2.value == 10)) || ((tile.value == 100) && (next.value == 200) && (next2.value == 200)) || ((tile.value == 200) && (next.value == 100) && (next2.value == 200)) || ((tile.value == 200) && (next.value == 200) && (next2.value == 100)) || ((tile.value == 1000) && (next.value == 2000) && (next2.value == 2000)) || ((tile.value == 2000) && (next.value == 1000) && (next2.value == 2000)) || ((tile.value == 2000) && (next.value == 2000) && (next2.value == 1000)) || ((tile.value == 10000) && (next.value == 20000) && (next2.value == 20000)) || ((tile.value == 20000) && (next.value == 10000) && (next2.value == 20000)) || ((tile.value == 20000) && (next.value == 20000) && (next2.value == 10000)) || ((tile.value == 5) && (next.value == 10) && (next2.value == 5)) || ((tile.value == 50) && (next.value == 100) && (next2.value == 50)) || ((tile.value == 500) && (next.value == 1000) && (next2.value == 500)) || ((tile.value == 5000) && (next.value == 10000) && (next2.value == 5000)))) {
+ return true;
+ }
+ if (next && next.value === tile.value && next.value != 2 && next.value != 20 && next.value != 200 && next.value != 2000 && next.value != 20000 && next.value != 50000) {
+ return true;
+ }
+
+ }
+ }
+ }
+ i += 1;
+ }
+
+ return false;
+};
+
+GameManager.prototype.positionsEqual = function (first, second) {
+ return first.x === second.x && first.y === second.y;
+};
\ No newline at end of file
diff --git a/sources/js/grid.js b/sources/js/grid.js
new file mode 100644
index 0000000..05fe057
--- /dev/null
+++ b/sources/js/grid.js
@@ -0,0 +1,84 @@
+function Grid(size) {
+ this.size = size;
+
+ this.cells = [];
+
+ this.build();
+}
+
+// Build a grid of the specified size
+Grid.prototype.build = function () {
+ for (var x = 0; x < this.size; x++) {
+ var row = this.cells[x] = [];
+
+ for (var y = 0; y < this.size; y++) {
+ row.push(null);
+ }
+ }
+};
+
+// Find the first available random position
+Grid.prototype.randomAvailableCell = function () {
+ var cells = this.availableCells();
+
+ if (cells.length) {
+ return cells[Math.floor(Math.random() * cells.length)];
+ }
+};
+
+Grid.prototype.availableCells = function () {
+ var cells = [];
+
+ this.eachCell(function (x, y, tile) {
+ if (!tile) {
+ cells.push({ x: x, y: y });
+ }
+ });
+
+ return cells;
+};
+
+// Call callback for every cell
+Grid.prototype.eachCell = function (callback) {
+ for (var x = 0; x < this.size; x++) {
+ for (var y = 0; y < this.size; y++) {
+ callback(x, y, this.cells[x][y]);
+ }
+ }
+};
+
+// Check if there are any cells available
+Grid.prototype.cellsAvailable = function () {
+ return !!this.availableCells().length;
+};
+
+// Check if the specified cell is taken
+Grid.prototype.cellAvailable = function (cell) {
+ return !this.cellOccupied(cell);
+};
+
+Grid.prototype.cellOccupied = function (cell) {
+ return !!this.cellContent(cell);
+};
+
+Grid.prototype.cellContent = function (cell) {
+ if (this.withinBounds(cell)) {
+ return this.cells[cell.x][cell.y];
+ } else {
+ return null;
+ }
+};
+
+// Inserts a tile at its position
+Grid.prototype.insertTile = function (tile) {
+ this.cells[tile.x][tile.y] = tile;
+};
+
+Grid.prototype.removeTile = function (tile) {
+ this.cells[tile.x][tile.y] = null;
+};
+
+Grid.prototype.withinBounds = function (position) {
+ return position.x >= 0 && position.x < this.size &&
+ position.y >= 0 && position.y < this.size;
+};
diff --git a/sources/js/html_actuator.js b/sources/js/html_actuator.js
new file mode 100644
index 0000000..5cdf9cf
--- /dev/null
+++ b/sources/js/html_actuator.js
@@ -0,0 +1,337 @@
+function HTMLActuator() {
+ this.tileContainer = document.querySelector(".tile-container");
+ this.scoreContainer = document.querySelector(".score-container");
+ this.bestContainer = document.querySelector(".best-container");
+ this.messageContainer = document.querySelector(".game-message");
+ this.sharingContainer = document.querySelector(".score-sharing");
+
+ this.score = 0;
+ this.highestTile = 1;
+}
+
+HTMLActuator.prototype.actuate = function (grid, metadata) {
+ var self = this;
+
+ window.requestAnimationFrame(function () {
+ self.clearContainer(self.tileContainer);
+
+ grid.cells.forEach(function (column) {
+ column.forEach(function (cell) {
+ if (cell) {
+ self.addTile(cell);
+ }
+ });
+ });
+
+ self.updateScore(metadata.score);
+ self.updateBestScore(metadata.bestScore);
+ self.highestTile = metadata.highest;
+
+ if (metadata.terminated) {
+ if (metadata.over) {
+ self.message(false); // You lose
+ } else if (metadata.won) {
+ self.message(true); // You win!
+ }
+ }
+
+ });
+};
+
+// Continues the game (both restart and keep playing)
+HTMLActuator.prototype.continue = function () {
+ if (typeof ga !== "undefined") {
+ ga("send", "event", "game", "restart");
+ }
+
+ this.clearMessage();
+};
+
+HTMLActuator.prototype.clearContainer = function (container) {
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+};
+
+HTMLActuator.prototype.addTile = function (tile) {
+ var self = this;
+
+ var wrapper = document.createElement("div");
+ var inner = document.createElement("div");
+ var position = tile.previousPosition || { x: tile.x, y: tile.y };
+ var positionClass = this.positionClass(position);
+
+ // We can't use classlist because it somehow glitches when replacing classes
+ var classes = ["tile", "tile-" + tile.value, positionClass];
+
+ if (tile.value > 50000) classes.push("tile-super");
+
+ this.applyClasses(wrapper, classes);
+
+ inner.classList.add("tile-inner");
+ inner.textContent = tile.value;
+
+ if (tile.previousPosition) {
+ // Make sure that the tile gets rendered in the previous position first
+ window.requestAnimationFrame(function () {
+ classes[2] = self.positionClass({ x: tile.x, y: tile.y });
+ self.applyClasses(wrapper, classes); // Update the position
+ });
+ } else if (tile.mergedFrom) {
+ classes.push("tile-merged");
+ this.applyClasses(wrapper, classes);
+
+ // Render the tiles that merged
+ tile.mergedFrom.forEach(function (merged) {
+ self.addTile(merged);
+ });
+ } else {
+ classes.push("tile-new");
+ this.applyClasses(wrapper, classes);
+ }
+
+ // Add the inner part of the tile to the wrapper
+ wrapper.appendChild(inner);
+
+ // Put the tile on the board
+ this.tileContainer.appendChild(wrapper);
+};
+
+HTMLActuator.prototype.applyClasses = function (element, classes) {
+ element.setAttribute("class", classes.join(" "));
+};
+
+HTMLActuator.prototype.normalizePosition = function (position) {
+ return { x: position.x + 1, y: position.y + 1 };
+};
+
+HTMLActuator.prototype.positionClass = function (position) {
+ position = this.normalizePosition(position);
+ return "tile-position-" + position.x + "-" + position.y;
+};
+
+HTMLActuator.prototype.updateScore = function (score) {
+ this.clearContainer(this.scoreContainer);
+
+ var difference = score - this.score;
+ this.score = score;
+
+ this.scoreContainer.textContent = this.score;
+
+ if (difference > 0) {
+ var addition = document.createElement("div");
+ addition.classList.add("score-addition");
+ addition.textContent = "+" + difference;
+
+ this.scoreContainer.appendChild(addition);
+ }
+};
+
+HTMLActuator.prototype.updateBestScore = function (bestScore) {
+ this.bestContainer.textContent = bestScore;
+};
+
+HTMLActuator.prototype.message = function (won) {
+ var type = won ? "game-won" : "game-over";
+ var message = won ? "You won!" : "No more moves. Game over.";
+
+ // Build some firebase references.
+ var rootRef = new Firebase('https://20-euros.firebaseio.com');
+ var scoreListRef = rootRef.child("scoreList");
+ var highestScoreRef = rootRef.child("highestScore");
+ var RefPlays = rootRef.child("plays");
+ var Ref50 = rootRef.child("reached_50c");
+ var Ref100 = rootRef.child("reached_1e");
+ var Ref200 = rootRef.child("reached_2e");
+ var Ref500 = rootRef.child("reached_5e");
+ var Ref1000 = rootRef.child("reached_10e");
+ var Ref2000 = rootRef.child("reached_20e");
+ var Ref5000 = rootRef.child("reached_50e");
+ var Ref10000 = rootRef.child("reached_100e");
+ var Ref20000 = rootRef.child("reached_200e");
+ var Ref50000 = rootRef.child("reached_500e");
+
+ // Keep a mapping of firebase locations to HTML elements, so we can move / remove elements as necessary.
+ var htmlForPath = {};
+
+ // Helper function that takes a new score snapshot and adds an appropriate row to our leaderboard table.
+ function handleScoreAdded(scoreSnapshot, prevScoreName) {
+ var newScoreRow = $(" ");
+ newScoreRow.append($(" ").append($(" ").text(scoreSnapshot.val().name)));
+ newScoreRow.append($(" ").text(scoreSnapshot.val().score));
+
+ // Store a reference to the table row so we can get it again later.
+ htmlForPath[scoreSnapshot.name()] = newScoreRow;
+
+ // Insert the new score in the appropriate place in the table.
+ if (prevScoreName === null) {
+ $("#leaderboardTable").append(newScoreRow);
+ }
+ else {
+ var lowerScoreRow = htmlForPath[prevScoreName];
+ lowerScoreRow.before(newScoreRow);
+ }
+ }
+
+ // Helper function to handle a score object being removed; just removes the corresponding table row.
+ function handleScoreRemoved(scoreSnapshot) {
+ var removedScoreRow = htmlForPath[scoreSnapshot.name()];
+ removedScoreRow.remove();
+ delete htmlForPath[scoreSnapshot.name()];
+ }
+
+function makeid()
+{
+ var text = "";
+ var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+ for( var i=0; i < 8; i++ )
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+
+ return text;
+}
+
+ // Create a view to only receive callbacks for the last LEADERBOARD_SIZE scores
+ var scoreListView = scoreListRef.limit(10);
+
+ // Add a callback to handle when a new score is added.
+ scoreListView.on('child_added', function (newScoreSnapshot, prevScoreName) {
+ handleScoreAdded(newScoreSnapshot, prevScoreName);
+ });
+
+ // Add a callback to handle when a score is removed
+ scoreListView.on('child_removed', function (oldScoreSnapshot) {
+ handleScoreRemoved(oldScoreSnapshot);
+ });
+
+ // Add a callback to handle when a score changes or moves positions.
+ var changedCallback = function (scoreSnapshot, prevScoreName) {
+ handleScoreRemoved(scoreSnapshot);
+ handleScoreAdded(scoreSnapshot, prevScoreName);
+ };
+ scoreListView.on('child_moved', changedCallback);
+ scoreListView.on('child_changed', changedCallback);
+
+ var newScore = this.score;
+ var name = makeid();
+ $("#scoreInput").val("");
+
+ var userScoreRef = scoreListRef.child(name);
+
+ // Use setWithPriority to put the name / score in Firebase, and set the priority to be the score.
+ if ((newScore <= 500)||(this.highestTile <= 20)||(this.highestTile >= 2000)){
+ userScoreRef.setWithPriority({ name:name, score:newScore, highest:this.highestTile }, newScore);}
+
+// Track the highest score using a transaction. A transaction guarantees that the code inside the block is
+ // executed on the latest data from the server, so transactions should be used if you have multiple
+ // clients writing to the same data and you want to avoid conflicting changes.
+ highestScoreRef.transaction(function (currentHighestScore) {
+ if (currentHighestScore === null || newScore > currentHighestScore) {
+ // The return value of this function gets saved to the server as the new highest score.
+ return newScore;
+ }
+ // if we return with no arguments, it cancels the transaction.
+ return;
+ });
+
+ RefPlays.transaction(function(current_value) {
+ return current_value + 1;
+ });
+
+ if (this.highestTile >= 50){
+ Ref50.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 100){
+ Ref100.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 200){
+ Ref200.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 500){
+ Ref500.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 1000){
+ Ref1000.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 2000){
+ Ref2000.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 5000){
+ Ref5000.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 10000){
+ Ref10000.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 20000){
+ Ref20000.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+if (this.highestTile >= 50000){
+ Ref50000.transaction(function(current_value) {
+ return current_value + 1;
+ });
+ }
+
+ // Add a callback to the highest score in Firebase so we can update the GUI any time it changes.
+ highestScoreRef.on('value', function (newHighestScore) {
+ $("#highestScoreDiv").text(newHighestScore.val());
+ });
+
+ if (typeof ga !== "undefined") {
+ ga("send", "event", "game", "end", type, this.score);
+ }
+
+ this.messageContainer.classList.add(type);
+ this.messageContainer.getElementsByTagName("p")[0].textContent = message;
+
+ this.clearContainer(this.sharingContainer);
+ this.sharingContainer.appendChild(this.scoreTweetButton());
+ twttr.widgets.load();
+};
+
+HTMLActuator.prototype.clearMessage = function () {
+ // IE only takes one value to remove at a time.
+ this.messageContainer.classList.remove("game-won");
+ this.messageContainer.classList.remove("game-over");
+};
+
+HTMLActuator.prototype.scoreTweetButton = function () {
+ var tweet = document.createElement("a");
+ tweet.classList.add("twitter-share-button");
+ tweet.setAttribute("href", "https://twitter.com/share");
+ tweet.setAttribute("data-via", "gabrielecirulli");
+ tweet.setAttribute("data-url", "http://git.io/2048");
+ tweet.setAttribute("data-counturl", "http://gabrielecirulli.github.io/2048/");
+ tweet.textContent = "Tweet";
+ var text = "I got " + this.score + " points in 20 Euro, a game where you " +
+ "join numbers to score high! #20eurogame";
+ tweet.setAttribute("data-text", text);
+
+ return tweet;
+};
diff --git a/sources/js/keyboard_input_manager.js b/sources/js/keyboard_input_manager.js
new file mode 100644
index 0000000..a29744c
--- /dev/null
+++ b/sources/js/keyboard_input_manager.js
@@ -0,0 +1,130 @@
+function KeyboardInputManager() {
+ this.events = {};
+
+ if (window.navigator.msPointerEnabled) {
+ //Internet Explorer 10 style
+ this.eventTouchstart = "MSPointerDown";
+ this.eventTouchmove = "MSPointerMove";
+ this.eventTouchend = "MSPointerUp";
+ } else {
+ this.eventTouchstart = "touchstart";
+ this.eventTouchmove = "touchmove";
+ this.eventTouchend = "touchend";
+ }
+
+ this.listen();
+}
+
+KeyboardInputManager.prototype.on = function (event, callback) {
+ if (!this.events[event]) {
+ this.events[event] = [];
+ }
+ this.events[event].push(callback);
+};
+
+KeyboardInputManager.prototype.emit = function (event, data) {
+ var callbacks = this.events[event];
+ if (callbacks) {
+ callbacks.forEach(function (callback) {
+ callback(data);
+ });
+ }
+};
+
+KeyboardInputManager.prototype.listen = function () {
+ var self = this;
+
+ var map = {
+ 38: 0, // Up
+ 39: 1, // Right
+ 40: 2, // Down
+ 37: 3, // Left
+ 75: 0, // vim keybindings
+ 76: 1,
+ 74: 2,
+ 72: 3,
+ 87: 0, // W
+ 68: 1, // D
+ 83: 2, // S
+ 65: 3 // A
+ };
+
+ document.addEventListener("keydown", function (event) {
+ var modifiers = event.altKey || event.ctrlKey || event.metaKey ||
+ event.shiftKey;
+ var mapped = map[event.which];
+
+ if (!modifiers) {
+ if (mapped !== undefined) {
+ event.preventDefault();
+ self.emit("move", mapped);
+ }
+
+ if (event.which === 32) self.restart.bind(self)(event);
+ }
+ });
+
+ var retry = document.querySelector(".retry-button");
+ retry.addEventListener("click", this.restart.bind(this));
+ retry.addEventListener(this.eventTouchend, this.restart.bind(this));
+
+ var keepPlaying = document.querySelector(".keep-playing-button");
+ keepPlaying.addEventListener("click", this.keepPlaying.bind(this));
+ keepPlaying.addEventListener("touchend", this.keepPlaying.bind(this));
+
+ // Listen to swipe events
+ var touchStartClientX, touchStartClientY;
+ var gameContainer = document.getElementsByClassName("game-container")[0];
+
+ gameContainer.addEventListener(this.eventTouchstart, function (event) {
+ if (( !window.navigator.msPointerEnabled && event.touches.length > 1) || event.targetTouches > 1) return;
+
+ if(window.navigator.msPointerEnabled){
+ touchStartClientX = event.pageX;
+ touchStartClientY = event.pageY;
+ } else {
+ touchStartClientX = event.touches[0].clientX;
+ touchStartClientY = event.touches[0].clientY;
+ }
+
+ event.preventDefault();
+ });
+
+ gameContainer.addEventListener(this.eventTouchmove, function (event) {
+ event.preventDefault();
+ });
+
+ gameContainer.addEventListener(this.eventTouchend, function (event) {
+ if (( !window.navigator.msPointerEnabled && event.touches.length > 0) || event.targetTouches > 0) return;
+
+ var touchEndClientX, touchEndClientY;
+ if(window.navigator.msPointerEnabled){
+ touchEndClientX = event.pageX;
+ touchEndClientY = event.pageY;
+ } else {
+ touchEndClientX = event.changedTouches[0].clientX;
+ touchEndClientY = event.changedTouches[0].clientY;
+ }
+
+ var dx = touchEndClientX - touchStartClientX;
+ var absDx = Math.abs(dx);
+
+ var dy = touchEndClientY - touchStartClientY;
+ var absDy = Math.abs(dy);
+
+ if (Math.max(absDx, absDy) > 10) {
+ // (right : left) : (down : up)
+ self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0));
+ }
+ });
+};
+
+KeyboardInputManager.prototype.restart = function (event) {
+ event.preventDefault();
+ this.emit("restart");
+};
+
+KeyboardInputManager.prototype.keepPlaying = function (event) {
+ event.preventDefault();
+ this.emit("keepPlaying");
+};
diff --git a/sources/js/local_score_manager.js b/sources/js/local_score_manager.js
new file mode 100644
index 0000000..ec4575d
--- /dev/null
+++ b/sources/js/local_score_manager.js
@@ -0,0 +1,48 @@
+window.fakeStorage = {
+ _data: {},
+
+ setItem: function (id, val) {
+ return this._data[id] = String(val);
+ },
+
+ getItem: function (id) {
+ return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
+ },
+
+ removeItem: function (id) {
+ return delete this._data[id];
+ },
+
+ clear: function () {
+ return this._data = {};
+ }
+};
+
+function LocalScoreManager() {
+ this.key = "bestScore";
+
+ var supported = this.localStorageSupported();
+ this.storage = supported ? window.localStorage : window.fakeStorage;
+}
+
+LocalScoreManager.prototype.localStorageSupported = function () {
+ var testKey = "test";
+ var storage = window.localStorage;
+
+ try {
+ storage.setItem(testKey, "1");
+ storage.removeItem(testKey);
+ return true;
+ } catch (error) {
+ return false;
+ }
+};
+
+LocalScoreManager.prototype.get = function () {
+ return this.storage.getItem(this.key) || 0;
+};
+
+LocalScoreManager.prototype.set = function (score) {
+ this.storage.setItem(this.key, score);
+};
+
diff --git a/sources/js/tile.js b/sources/js/tile.js
new file mode 100644
index 0000000..de08333
--- /dev/null
+++ b/sources/js/tile.js
@@ -0,0 +1,17 @@
+function Tile(position, value) {
+ this.x = position.x;
+ this.y = position.y;
+ this.value = value || 2;
+
+ this.previousPosition = null;
+ this.mergedFrom = null; // Tracks tiles that merged together
+}
+
+Tile.prototype.savePosition = function () {
+ this.previousPosition = { x: this.x, y: this.y };
+};
+
+Tile.prototype.updatePosition = function (position) {
+ this.x = position.x;
+ this.y = position.y;
+};
diff --git a/sources/statistics.html b/sources/statistics.html
new file mode 100644
index 0000000..9b51d9d
--- /dev/null
+++ b/sources/statistics.html
@@ -0,0 +1,122 @@
+
+
+
+
+ 20 Euros Statistics
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Statistics
+
+
Statistics of my game 20 Euros .
+
+
+
+Statistics since 2014-04-06.
+
+
+ Out of the
plays,
+
reached the 50 cent coin,
+
reached the 1 euro coin,
+
reached the 2 euro coin,
+
reached the 5 euro note,
+
reached the 10 euro note,
+ and
won the game!
+
+
+ The highest score achieved was .
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/style/example.css b/sources/style/example.css
new file mode 100644
index 0000000..c941903
--- /dev/null
+++ b/sources/style/example.css
@@ -0,0 +1,50 @@
+/* Global */
+
+
+
+#leaderboardTable {
+ background-color: white;
+ overflow: auto;
+ width: 100%;
+ padding: 10px;
+ border: 8px solid #424547;
+ margin-bottom: 5px;
+}
+
+#scoreInput {
+ width: 68%;
+}
+
+#highestscore {
+ margin-top: 20px;
+ font-size: 14px;
+}
+
+/* Presence */
+
+#presenceDiv {
+ text-align: center;
+}
+
+/* Tetris */
+
+.tetris-body {
+ width: 600px;
+}
+
+#canvas0, #canvas1 {
+ display: inline-block;
+ border: 4px solid #424547;
+}
+
+#restartButton {
+ margin-top: 5px;
+}
+
+#gameInProgress {
+ font-size: 14px;
+}
+
+.hide {
+ display: none;
+}
\ No newline at end of file
diff --git a/sources/style/main.css b/sources/style/main.css
new file mode 100644
index 0000000..1d9a609
--- /dev/null
+++ b/sources/style/main.css
@@ -0,0 +1,869 @@
+@import url(fonts/clear-sans.css);
+html, body {
+ margin: 0;
+ padding: 0;
+ background: #faf8ef;
+ color: #776e65;
+ font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif;
+ font-size: 18px; }
+
+body {
+ margin: 80px 0; }
+
+.heading:after {
+ content: "";
+ display: block;
+ clear: both; }
+
+h1.title {
+ font-size: 80px;
+ font-weight: bold;
+ margin: 0;
+ display: block;
+ float: left; }
+
+@-webkit-keyframes move-up {
+ 0% {
+ top: 25px;
+ opacity: 1; }
+
+ 100% {
+ top: -50px;
+ opacity: 0; } }
+
+@-moz-keyframes move-up {
+ 0% {
+ top: 25px;
+ opacity: 1; }
+
+ 100% {
+ top: -50px;
+ opacity: 0; } }
+
+@keyframes move-up {
+ 0% {
+ top: 25px;
+ opacity: 1; }
+
+ 100% {
+ top: -50px;
+ opacity: 0; } }
+
+.scores-container {
+ float: right;
+ text-align: right; }
+
+.score-container, .best-container {
+ position: relative;
+ display: inline-block;
+ background: #bbada0;
+ padding: 15px 25px;
+ font-size: 25px;
+ height: 25px;
+ line-height: 47px;
+ font-weight: bold;
+ border-radius: 3px;
+ color: white;
+ margin-top: 8px;
+ text-align: center; }
+ .score-container:after, .best-container:after {
+ position: absolute;
+ width: 100%;
+ top: 10px;
+ left: 0;
+ text-transform: uppercase;
+ font-size: 13px;
+ line-height: 13px;
+ text-align: center;
+ color: #eee4da; }
+ .score-container .score-addition, .best-container .score-addition {
+ position: absolute;
+ right: 30px;
+ color: red;
+ font-size: 25px;
+ line-height: 25px;
+ font-weight: bold;
+ color: rgba(119, 110, 101, 0.9);
+ z-index: 100;
+ -webkit-animation: move-up 600ms ease-in;
+ -moz-animation: move-up 600ms ease-in;
+ animation: move-up 600ms ease-in;
+ -webkit-animation-fill-mode: both;
+ -moz-animation-fill-mode: both;
+ animation-fill-mode: both; }
+
+.score-container:after {
+ content: "Score"; }
+
+.best-container:after {
+ content: "My best"; }
+
+p {
+ margin-top: 0;
+ margin-bottom: 10px;
+ line-height: 1.65; }
+
+a {
+ color: #776e65;
+ font-weight: bold;
+ text-decoration: underline;
+ cursor: pointer; }
+
+strong.important {
+ text-transform: uppercase; }
+
+hr {
+ border: none;
+ border-bottom: 1px solid #d8d4d0;
+ margin-top: 20px;
+ margin-bottom: 30px; }
+
+.container {
+ width: 640px;
+ margin: 0 auto; }
+
+@-webkit-keyframes fade-in {
+ 0% {
+ opacity: 0; }
+
+ 100% {
+ opacity: 1; } }
+
+@-moz-keyframes fade-in {
+ 0% {
+ opacity: 0; }
+
+ 100% {
+ opacity: 1; } }
+
+@keyframes fade-in {
+ 0% {
+ opacity: 0; }
+
+ 100% {
+ opacity: 1; } }
+
+.game-container {
+ margin-top: 40px;
+ position: relative;
+ padding: 15px;
+ cursor: default;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ background: #bbada0;
+ border-radius: 6px;
+ width: 640px;
+ height: 640px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box; }
+
+ .game-container .game-message {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: rgba(238, 228, 218, 0.5);
+ z-index: 100;
+ text-align: center;
+ -webkit-animation: fade-in 800ms ease 1200ms;
+ -moz-animation: fade-in 800ms ease 1200ms;
+ animation: fade-in 800ms ease 1200ms;
+ -webkit-animation-fill-mode: both;
+ -moz-animation-fill-mode: both;
+ animation-fill-mode: both;
+}
+
+.game-container .game-message p {
+ font-size: 60px;
+ font-weight: bold;
+ height: 60px;
+ line-height: 60px;
+ margin-top: 222px; }
+ .game-container .game-message .lower {
+ display: block;
+ margin-top: 59px; }
+ .game-container .game-message a {
+ display: inline-block;
+ background: #8f7a66;
+ border-radius: 3px;
+ padding: 0 20px;
+ text-decoration: none;
+ color: #f9f6f2;
+ height: 40px;
+ line-height: 42px;
+ margin-left: 9px; }
+ .game-container .game-message a.keep-playing-button {
+ display: none; }
+ .game-container .game-message.game-won {
+ background: rgba(237, 194, 46, 0.5);
+ color: #f9f6f2; }
+ .game-container .game-message.game-won a.keep-playing-button {
+ display: inline-block; }
+ .game-container .game-message.game-won, .game-container .game-message.game-over {
+ display: block; }
+
+.grid-container {
+ position: absolute;
+ z-index: 1; }
+
+.grid-row {
+ margin-bottom: 15px; }
+ .grid-row:last-child {
+ margin-bottom: 0; }
+ .grid-row:after {
+ content: "";
+ display: block;
+ clear: both; }
+
+.grid-cell {
+ width: 110px;
+ height: 110px;
+ margin-right: 15px;
+ float: left;
+ border-radius: 3px;
+ background: rgba(238, 228, 218, 0.35); }
+ .grid-cell:last-child {
+ margin-right: 0; }
+
+.tile-container {
+ position: absolute;
+ z-index: 2; }
+
+.tile, .tile .tile-inner {
+ width: 110px;
+ height: 110px;
+ line-height: 116.25px; }
+
+.tile.tile-position-1-1 {
+ -webkit-transform: translate(0px, 0px);
+ -moz-transform: translate(0px, 0px);
+ transform: translate(0px, 0px); }
+.tile.tile-position-1-2 {
+ -webkit-transform: translate(0px, 125px);
+ -moz-transform: translate(0px, 125px);
+ transform: translate(0px, 125px); }
+.tile.tile-position-1-3 {
+ -webkit-transform: translate(0px, 250px);
+ -moz-transform: translate(0px, 250px);
+ transform: translate(0px, 250px); }
+.tile.tile-position-1-4 {
+ -webkit-transform: translate(0px, 375px);
+ -moz-transform: translate(0px, 375px);
+ transform: translate(0px, 375px); }
+.tile.tile-position-1-5 {
+ -webkit-transform: translate(0px, 500px);
+ -moz-transform: translate(0px, 500px);
+ transform: translate(0px, 500px); }
+
+.tile.tile-position-2-1 {
+ -webkit-transform: translate(125px, 0px);
+ -moz-transform: translate(125px, 0px);
+ transform: translate(125px, 0px); }
+.tile.tile-position-2-2 {
+ -webkit-transform: translate(125px, 125px);
+ -moz-transform: translate(125px, 125px);
+ transform: translate(125px, 125px); }
+.tile.tile-position-2-3 {
+ -webkit-transform: translate(125px, 250px);
+ -moz-transform: translate(125px, 250px);
+ transform: translate(125px, 250px); }
+.tile.tile-position-2-4 {
+ -webkit-transform: translate(125px, 375px);
+ -moz-transform: translate(125px, 375px);
+ transform: translate(125px, 375px); }
+.tile.tile-position-2-5 {
+ -webkit-transform: translate(125px, 500px);
+ -moz-transform: translate(125px, 500px);
+ transform: translate(125px, 500px); }
+
+.tile.tile-position-3-1 {
+ -webkit-transform: translate(250px, 0px);
+ -moz-transform: translate(250px, 0px);
+ transform: translate(250px, 0px); }
+.tile.tile-position-3-2 {
+ -webkit-transform: translate(250px, 125px);
+ -moz-transform: translate(250px, 125px);
+ transform: translate(250px, 125px); }
+.tile.tile-position-3-3 {
+ -webkit-transform: translate(250px, 250px);
+ -moz-transform: translate(250px, 250px);
+ transform: translate(250px, 250px); }
+.tile.tile-position-3-4 {
+ -webkit-transform: translate(250px, 375px);
+ -moz-transform: translate(250px, 375px);
+ transform: translate(250px, 375px); }
+.tile.tile-position-3-5 {
+ -webkit-transform: translate(250px, 500px);
+ -moz-transform: translate(250px, 500x);
+ transform: translate(250px, 500px); }
+
+.tile.tile-position-4-1 {
+ -webkit-transform: translate(375px, 0px);
+ -moz-transform: translate(375px, 0px);
+ transform: translate(375px, 0px); }
+.tile.tile-position-4-2 {
+ -webkit-transform: translate(375px, 125px);
+ -moz-transform: translate(375px, 125px);
+ transform: translate(375px, 125px); }
+.tile.tile-position-4-3 {
+ -webkit-transform: translate(375px, 250px);
+ -moz-transform: translate(375px, 250px);
+ transform: translate(375px, 250px); }
+.tile.tile-position-4-4 {
+ -webkit-transform: translate(375px, 375px);
+ -moz-transform: translate(375px, 375px);
+ transform: translate(375px, 375px); }
+.tile.tile-position-4-5 {
+ -webkit-transform: translate(375px, 500px);
+ -moz-transform: translate(375px, 500px);
+ transform: translate(375px, 500px); }
+
+.tile.tile-position-5-1 {
+ -webkit-transform: translate(500px, 0px);
+ -moz-transform: translate(500px, 0px);
+ transform: translate(500px, 0px); }
+.tile.tile-position-5-2 {
+ -webkit-transform: translate(500px, 125px);
+ -moz-transform: translate(500px, 125px);
+ transform: translate(500px, 125px); }
+.tile.tile-position-5-3 {
+ -webkit-transform: translate(500px, 250px);
+ -moz-transform: translate(500px, 250px);
+ transform: translate(500px, 250px); }
+.tile.tile-position-5-4 {
+ -webkit-transform: translate(500px, 375px);
+ -moz-transform: translate(500px, 375px);
+ transform: translate(500px, 375px); }
+.tile.tile-position-5-5 {
+ -webkit-transform: translate(500px, 500px);
+ -moz-transform: translate(500px, 500px);
+ transform: translate(500px, 500px); }
+
+
+.tile {
+ position: absolute;
+ -webkit-transition: 100ms ease-in-out;
+ -moz-transition: 100ms ease-in-out;
+ transition: 100ms ease-in-out;
+ -webkit-transition-property: -webkit-transform;
+ -moz-transition-property: -moz-transform;
+ transition-property: transform; }
+ .tile .tile-inner {
+ border-radius: 3px;
+ background: #eee4da;
+ text-align: center;
+ font-weight: bold;
+ z-index: 10;
+ font-size: 0px; }
+
+
+.tile.tile-1 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_1cent.gif');
+ background-size: cover;
+ box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); }
+
+
+.tile.tile-2 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_2cent.gif');
+ background-size: cover;
+ box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); }
+
+
+
+.tile.tile-5 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_5cent.gif');
+ background-size: cover;
+ box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); }
+
+
+
+.tile.tile-10 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_10cent.gif');
+ background-size: cover;}
+
+
+
+.tile.tile-20 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_20cent.gif');
+ background-size: cover;}
+
+
+
+.tile.tile-50 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_50cent.gif');
+ background-size: cover;}
+
+
+
+ .tile.tile-100 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_1euro.gif');
+ background-size: cover;}
+
+
+
+ .tile.tile-200 .tile-inner {
+ background: #f0f url('https://www.ecb.europa.eu/euro/coins/common/shared/img/common_2euro.gif');
+ background-size: cover;
+}
+
+
+ .tile.tile-500 .tile-inner {
+
+ background: #ffffff url('http://www.126bay.com/images/banknote/europe/eURO/5%20Euro%202002%20-%20F.jpg');
+ background-size: contain; background-repeat: no-repeat;}
+
+
+.tile.tile-1000 .tile-inner {
+
+ background: #ffffff url('http://www.126bay.com/images/banknote/europe/eURO/10%20Euro%202002%20-%20F.jpg');
+ background-size: contain; background-repeat: no-repeat;}
+
+.tile.tile-2000 .tile-inner {
+ background: #ffffff url('http://www.126bay.com/images/banknote/europe/eURO/20%20Euro%202002%20-%20F.jpg');
+ background-size: contain; background-repeat: no-repeat;}
+
+
+.tile.tile-5000 .tile-inner {
+ background: #ffffff url('http://www.126bay.com/images/banknote/europe/eURO/50%20Euro%202002%20-%20F.jpg');
+ background-size: contain; background-repeat: no-repeat;}
+
+
+.tile.tile-10000 .tile-inner {
+ background: #ffffff url('http://www.blogger42.com/wp-content/uploads/100euro.jpg');
+ background-size: contain; background-repeat: no-repeat;}
+
+
+.tile.tile-20000 .tile-inner {
+ background: #ffffff url('http://www.blogger42.com/wp-content/uploads/e.jpg');
+ background-size: contain; background-repeat: no-repeat;}
+
+
+.tile.tile-50000 .tile-inner {
+ background: #ffffff url('http://www.rivistastudio.com/wp-content/uploads/2014/03/500euro.jpg');
+ background-size: contain; background-repeat: no-repeat;}
+
+
+.tile.tile-super .tile-inner {
+
+ background: #ffffff url('http://upload.wikimedia.org/wikipedia/commons/c/c3/Euro_symbol_gold.svg');
+ background-size: cover;
+ font-size: 30px; }
+
+
+ @media screen and (max-width: 520px) {
+ .tile.tile-super .tile-inner {
+ font-size: 10px; } }
+
+@-webkit-keyframes appear {
+ 0% {
+ opacity: 0;
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ transform: scale(0); }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ transform: scale(1); } }
+
+@-moz-keyframes appear {
+ 0% {
+ opacity: 0;
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ transform: scale(0); }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ transform: scale(1); } }
+
+@keyframes appear {
+ 0% {
+ opacity: 0;
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ transform: scale(0); }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ transform: scale(1); } }
+
+.tile-new .tile-inner {
+ -webkit-animation: appear 200ms ease 100ms;
+ -moz-animation: appear 200ms ease 100ms;
+ animation: appear 200ms ease 100ms;
+ -webkit-animation-fill-mode: backwards;
+ -moz-animation-fill-mode: backwards;
+ animation-fill-mode: backwards; }
+
+@-webkit-keyframes pop {
+ 0% {
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ transform: scale(0); }
+
+ 50% {
+ -webkit-transform: scale(1.2);
+ -moz-transform: scale(1.2);
+ transform: scale(1.2); }
+
+ 100% {
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ transform: scale(1); } }
+
+@-moz-keyframes pop {
+ 0% {
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ transform: scale(0); }
+
+ 50% {
+ -webkit-transform: scale(1.2);
+ -moz-transform: scale(1.2);
+ transform: scale(1.2); }
+
+ 100% {
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ transform: scale(1); } }
+
+@keyframes pop {
+ 0% {
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ transform: scale(0); }
+
+ 50% {
+ -webkit-transform: scale(1.2);
+ -moz-transform: scale(1.2);
+ transform: scale(1.2); }
+
+ 100% {
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ transform: scale(1); } }
+
+.tile-merged .tile-inner {
+ z-index: 20;
+ -webkit-animation: pop 200ms ease 100ms;
+ -moz-animation: pop 200ms ease 100ms;
+ animation: pop 200ms ease 100ms;
+ -webkit-animation-fill-mode: backwards;
+ -moz-animation-fill-mode: backwards;
+ animation-fill-mode: backwards; }
+
+.game-intro {
+ margin-bottom: 0; }
+
+.game-explanation {
+ margin-top: 50px; }
+
+.sharing {
+ margin-top: 20px;
+ text-align: center; }
+ .sharing > iframe, .sharing > span, .sharing > form {
+ display: inline-block;
+ vertical-align: middle; }
+
+@media screen and (max-width: 520px) {
+ html, body {
+ font-size: 15px; }
+
+ body {
+ margin: 20px 0;
+ padding: 0 20px; }
+
+ h1.title {
+ font-size: 27px;
+ margin-top: 15px; }
+
+ .container {
+ width: 280px;
+ margin: 0 auto; }
+
+ .score-container, .best-container {
+ margin-top: 0;
+ padding: 15px 10px;
+ min-width: 40px; }
+
+ .heading {
+ margin-bottom: 10px; }
+
+ .game-container {
+ margin-top: 40px;
+ position: relative;
+ padding: 10px;
+ cursor: default;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ background: #bbada0;
+ border-radius: 6px;
+ width: 280px;
+ height: 280px;
+ -ms-touch-action: none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box; }
+ .game-container .game-message {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: rgba(238, 228, 218, 0.5);
+ z-index: 100;
+ text-align: center;
+ -webkit-animation: fade-in 800ms ease 1200ms;
+ -moz-animation: fade-in 800ms ease 1200ms;
+ animation: fade-in 800ms ease 1200ms;
+ -webkit-animation-fill-mode: both;
+ -moz-animation-fill-mode: both;
+ animation-fill-mode: both; }
+ .game-container .game-message p {
+ font-size: 60px;
+ font-weight: bold;
+ height: 60px;
+ line-height: 60px;
+ margin-top: 222px; }
+ .game-container .game-message .lower {
+ display: block;
+ margin-top: 59px; }
+ .game-container .game-message a {
+ display: inline-block;
+ background: #8f7a66;
+ border-radius: 3px;
+ padding: 0 20px;
+ text-decoration: none;
+ color: #f9f6f2;
+ height: 40px;
+ line-height: 42px;
+ margin-left: 9px; }
+ .game-container .game-message a.keep-playing-button {
+ display: none; }
+ .game-container .game-message .score-sharing {
+ display: inline-block;
+ vertical-align: middle;
+ margin-left: 10px; }
+ .game-container .game-message.game-won {
+ background: rgba(237, 194, 46, 0.5);
+ color: #f9f6f2; }
+ .game-container .game-message.game-won a.keep-playing-button {
+ display: inline-block; }
+ .game-container .game-message.game-won, .game-container .game-message.game-over {
+ display: block; }
+
+ .grid-container {
+ position: absolute;
+ z-index: 1; }
+
+ .grid-row {
+ margin-bottom: 10px; }
+ .grid-row:last-child {
+ margin-bottom: 0; }
+ .grid-row:after {
+ content: "";
+ display: block;
+ clear: both; }
+
+ .grid-cell {
+ width: 57.5px;
+ height: 57.5px;
+ margin-right: 10px;
+ float: left;
+ border-radius: 3px;
+ background: rgba(238, 228, 218, 0.35); }
+ .grid-cell:last-child {
+ margin-right: 0; }
+
+ .tile-container {
+ position: absolute;
+ z-index: 2; }
+
+ .tile, .tile .tile-inner {
+ width: 58px;
+ height: 58px;
+ line-height: 67.5px; }
+ .tile.tile-position-1-1 {
+ -webkit-transform: translate(0px, 0px);
+ -moz-transform: translate(0px, 0px);
+ transform: translate(0px, 0px); }
+ .tile.tile-position-1-2 {
+ -webkit-transform: translate(0px, 67px);
+ -moz-transform: translate(0px, 67px);
+ transform: translate(0px, 67px); }
+ .tile.tile-position-1-3 {
+ -webkit-transform: translate(0px, 135px);
+ -moz-transform: translate(0px, 135px);
+ transform: translate(0px, 135px); }
+ .tile.tile-position-1-4 {
+ -webkit-transform: translate(0px, 202px);
+ -moz-transform: translate(0px, 202px);
+ transform: translate(0px, 202px); }
+ .tile.tile-position-2-1 {
+ -webkit-transform: translate(67px, 0px);
+ -moz-transform: translate(67px, 0px);
+ transform: translate(67px, 0px); }
+ .tile.tile-position-2-2 {
+ -webkit-transform: translate(67px, 67px);
+ -moz-transform: translate(67px, 67px);
+ transform: translate(67px, 67px); }
+ .tile.tile-position-2-3 {
+ -webkit-transform: translate(67px, 135px);
+ -moz-transform: translate(67px, 135px);
+ transform: translate(67px, 135px); }
+ .tile.tile-position-2-4 {
+ -webkit-transform: translate(67px, 202px);
+ -moz-transform: translate(67px, 202px);
+ transform: translate(67px, 202px); }
+ .tile.tile-position-3-1 {
+ -webkit-transform: translate(135px, 0px);
+ -moz-transform: translate(135px, 0px);
+ transform: translate(135px, 0px); }
+ .tile.tile-position-3-2 {
+ -webkit-transform: translate(135px, 67px);
+ -moz-transform: translate(135px, 67px);
+ transform: translate(135px, 67px); }
+ .tile.tile-position-3-3 {
+ -webkit-transform: translate(135px, 135px);
+ -moz-transform: translate(135px, 135px);
+ transform: translate(135px, 135px); }
+ .tile.tile-position-3-4 {
+ -webkit-transform: translate(135px, 202px);
+ -moz-transform: translate(135px, 202px);
+ transform: translate(135px, 202px); }
+ .tile.tile-position-4-1 {
+ -webkit-transform: translate(202px, 0px);
+ -moz-transform: translate(202px, 0px);
+ transform: translate(202px, 0px); }
+ .tile.tile-position-4-2 {
+ -webkit-transform: translate(202px, 67px);
+ -moz-transform: translate(202px, 67px);
+ transform: translate(202px, 67px); }
+ .tile.tile-position-4-3 {
+ -webkit-transform: translate(202px, 135px);
+ -moz-transform: translate(202px, 135px);
+ transform: translate(202px, 135px); }
+ .tile.tile-position-4-4 {
+ -webkit-transform: translate(202px, 202px);
+ -moz-transform: translate(202px, 202px);
+ transform: translate(202px, 202px); }
+
+ .game-container {
+ margin-top: 20px; }
+
+ .tile .tile-inner {
+ font-size: 35px; }
+
+ .game-message p {
+ font-size: 30px !important;
+ height: 30px !important;
+ line-height: 30px !important;
+ margin-top: 90px !important; }
+ .game-message .lower {
+ margin-top: 30px !important; }
+
+ .sharing > iframe, .sharing > span, .sharing > form {
+ display: block;
+ margin: 0 auto;
+ margin-bottom: 20px; } }
+.pp-donate button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border: none;
+ font: inherit;
+ color: inherit;
+ cursor: pointer;
+ display: inline-block;
+ background: #8f7a66;
+ border-radius: 3px;
+ padding: 0 20px;
+ text-decoration: none;
+ color: #f9f6f2;
+ height: 40px;
+ line-height: 42px; }
+ .pp-donate button img {
+ vertical-align: -4px;
+ margin-right: 8px; }
+
+.btc-donate {
+ position: relative;
+ margin-left: 10px;
+ display: inline-block;
+ background: #8f7a66;
+ border-radius: 3px;
+ padding: 0 20px;
+ text-decoration: none;
+ color: #f9f6f2;
+ height: 40px;
+ line-height: 42px;
+ cursor: pointer; }
+ .btc-donate img {
+ vertical-align: -4px;
+ margin-right: 8px; }
+ .btc-donate a {
+ color: #f9f6f2;
+ text-decoration: none;
+ font-weight: normal; }
+ .btc-donate .address {
+ cursor: auto;
+ position: absolute;
+ width: 340px;
+ right: 50%;
+ margin-right: -170px;
+ padding-bottom: 7px;
+ top: -30px;
+ opacity: 0;
+ pointer-events: none;
+ -webkit-transition: 400ms ease;
+ -moz-transition: 400ms ease;
+ transition: 400ms ease;
+ -webkit-transition-property: top, opacity;
+ -moz-transition-property: top, opacity;
+ transition-property: top, opacity; }
+ .btc-donate .address:after {
+ position: absolute;
+ border-top: 10px solid #bbada0;
+ border-right: 7px solid transparent;
+ border-left: 7px solid transparent;
+ content: "";
+ bottom: 0px;
+ left: 50%;
+ margin-left: -7px; }
+ .btc-donate .address code {
+ background-color: #bbada0;
+ padding: 10px 15px;
+ width: 100%;
+ border-radius: 3px;
+ line-height: 1;
+ font-weight: normal;
+ font-size: 15px;
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ text-align: center; }
+ .btc-donate:hover .address, .btc-donate .address:hover .address {
+ opacity: 1;
+ top: -45px;
+ pointer-events: auto; }
+ @media screen and (max-width: 480px) {
+ .btc-donate {
+ width: 120px; }
+ .btc-donate .address {
+ margin-right: -150px;
+ width: 300px; }
+ .btc-donate .address code {
+ font-size: 13px; }
+ .btc-donate .address:after {
+ left: 50%;
+ bottom: 2px; } }
diff --git a/sources/stylesheets/pygment_trac.css b/sources/stylesheets/pygment_trac.css
new file mode 100644
index 0000000..e65cedf
--- /dev/null
+++ b/sources/stylesheets/pygment_trac.css
@@ -0,0 +1,70 @@
+.highlight .hll { background-color: #ffffcc }
+.highlight { background: #f0f3f3; }
+.highlight .c { color: #0099FF; font-style: italic } /* Comment */
+.highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */
+.highlight .k { color: #006699; font-weight: bold } /* Keyword */
+.highlight .o { color: #555555 } /* Operator */
+.highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #009999 } /* Comment.Preproc */
+.highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */
+.highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #FF0000 } /* Generic.Error */
+.highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */
+.highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */
+.highlight .go { color: #AAAAAA } /* Generic.Output */
+.highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #99CC66 } /* Generic.Traceback */
+.highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #006699 } /* Keyword.Pseudo */
+.highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */
+.highlight .m { color: #FF6600 } /* Literal.Number */
+.highlight .s { color: #CC3300 } /* Literal.String */
+.highlight .na { color: #330099 } /* Name.Attribute */
+.highlight .nb { color: #336666 } /* Name.Builtin */
+.highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */
+.highlight .no { color: #336600 } /* Name.Constant */
+.highlight .nd { color: #9999FF } /* Name.Decorator */
+.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #CC00FF } /* Name.Function */
+.highlight .nl { color: #9999FF } /* Name.Label */
+.highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #003333 } /* Name.Variable */
+.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #FF6600 } /* Literal.Number.Float */
+.highlight .mh { color: #FF6600 } /* Literal.Number.Hex */
+.highlight .mi { color: #FF6600 } /* Literal.Number.Integer */
+.highlight .mo { color: #FF6600 } /* Literal.Number.Oct */
+.highlight .sb { color: #CC3300 } /* Literal.String.Backtick */
+.highlight .sc { color: #CC3300 } /* Literal.String.Char */
+.highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #CC3300 } /* Literal.String.Double */
+.highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */
+.highlight .si { color: #AA0000 } /* Literal.String.Interpol */
+.highlight .sx { color: #CC3300 } /* Literal.String.Other */
+.highlight .sr { color: #33AAAA } /* Literal.String.Regex */
+.highlight .s1 { color: #CC3300 } /* Literal.String.Single */
+.highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */
+.highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #003333 } /* Name.Variable.Class */
+.highlight .vg { color: #003333 } /* Name.Variable.Global */
+.highlight .vi { color: #003333 } /* Name.Variable.Instance */
+.highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */
+
+.type-csharp .highlight .k { color: #0000FF }
+.type-csharp .highlight .kt { color: #0000FF }
+.type-csharp .highlight .nf { color: #000000; font-weight: normal }
+.type-csharp .highlight .nc { color: #2B91AF }
+.type-csharp .highlight .nn { color: #000000 }
+.type-csharp .highlight .s { color: #A31515 }
+.type-csharp .highlight .sc { color: #A31515 }
diff --git a/sources/stylesheets/stylesheet.css b/sources/stylesheets/stylesheet.css
new file mode 100644
index 0000000..7a08b01
--- /dev/null
+++ b/sources/stylesheets/stylesheet.css
@@ -0,0 +1,423 @@
+/*******************************************************************************
+Slate Theme for GitHub Pages
+by Jason Costello, @jsncostello
+*******************************************************************************/
+
+@import url(pygment_trac.css);
+
+/*******************************************************************************
+MeyerWeb Reset
+*******************************************************************************/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font: inherit;
+ vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+
+ol, ul {
+ list-style: none;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+/*******************************************************************************
+Theme Styles
+*******************************************************************************/
+
+body {
+ box-sizing: border-box;
+ color:#373737;
+ background: #212121;
+ font-size: 16px;
+ font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 10px 0;
+ font-weight: 700;
+ color:#222222;
+ font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif;
+ letter-spacing: -1px;
+}
+
+h1 {
+ font-size: 36px;
+ font-weight: 700;
+}
+
+h2 {
+ padding-bottom: 10px;
+ font-size: 32px;
+ background: url('../images/bg_hr.png') repeat-x bottom;
+}
+
+h3 {
+ font-size: 24px;
+}
+
+h4 {
+ font-size: 21px;
+}
+
+h5 {
+ font-size: 18px;
+}
+
+h6 {
+ font-size: 16px;
+}
+
+p {
+ margin: 10px 0 15px 0;
+}
+
+footer p {
+ color: #f2f2f2;
+}
+
+a {
+ text-decoration: none;
+ color: #007edf;
+ text-shadow: none;
+
+ transition: color 0.5s ease;
+ transition: text-shadow 0.5s ease;
+ -webkit-transition: color 0.5s ease;
+ -webkit-transition: text-shadow 0.5s ease;
+ -moz-transition: color 0.5s ease;
+ -moz-transition: text-shadow 0.5s ease;
+ -o-transition: color 0.5s ease;
+ -o-transition: text-shadow 0.5s ease;
+ -ms-transition: color 0.5s ease;
+ -ms-transition: text-shadow 0.5s ease;
+}
+
+a:hover, a:focus {text-decoration: underline;}
+
+footer a {
+ color: #F2F2F2;
+ text-decoration: underline;
+}
+
+em {
+ font-style: italic;
+}
+
+strong {
+ font-weight: bold;
+}
+
+img {
+ position: relative;
+ margin: 0 auto;
+ max-width: 739px;
+ padding: 5px;
+ margin: 10px 0 10px 0;
+ border: 1px solid #ebebeb;
+
+ box-shadow: 0 0 5px #ebebeb;
+ -webkit-box-shadow: 0 0 5px #ebebeb;
+ -moz-box-shadow: 0 0 5px #ebebeb;
+ -o-box-shadow: 0 0 5px #ebebeb;
+ -ms-box-shadow: 0 0 5px #ebebeb;
+}
+
+p img {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ vertical-align: middle;
+ text-align: center;
+ border: none;
+}
+
+pre, code {
+ width: 100%;
+ color: #222;
+ background-color: #fff;
+
+ font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
+ font-size: 14px;
+
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+}
+
+pre {
+ width: 100%;
+ padding: 10px;
+ box-shadow: 0 0 10px rgba(0,0,0,.1);
+ overflow: auto;
+}
+
+code {
+ padding: 3px;
+ margin: 0 3px;
+ box-shadow: 0 0 10px rgba(0,0,0,.1);
+}
+
+pre code {
+ display: block;
+ box-shadow: none;
+}
+
+blockquote {
+ color: #666;
+ margin-bottom: 20px;
+ padding: 0 0 0 20px;
+ border-left: 3px solid #bbb;
+}
+
+
+ul, ol, dl {
+ margin-bottom: 15px
+}
+
+ul {
+ list-style: inside;
+ padding-left: 20px;
+}
+
+ol {
+ list-style: decimal inside;
+ padding-left: 20px;
+}
+
+dl dt {
+ font-weight: bold;
+}
+
+dl dd {
+ padding-left: 20px;
+ font-style: italic;
+}
+
+dl p {
+ padding-left: 20px;
+ font-style: italic;
+}
+
+hr {
+ height: 1px;
+ margin-bottom: 5px;
+ border: none;
+ background: url('../images/bg_hr.png') repeat-x center;
+}
+
+table {
+ border: 1px solid #373737;
+ margin-bottom: 20px;
+ text-align: left;
+ }
+
+th {
+ font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ padding: 10px;
+ background: #373737;
+ color: #fff;
+ }
+
+td {
+ padding: 10px;
+ border: 1px solid #373737;
+ }
+
+form {
+ background: #f2f2f2;
+ padding: 20px;
+}
+
+/*******************************************************************************
+Full-Width Styles
+*******************************************************************************/
+
+.outer {
+ width: 100%;
+}
+
+.inner {
+ position: relative;
+ max-width: 640px;
+ padding: 20px 10px;
+ margin: 0 auto;
+}
+
+#forkme_banner {
+ display: block;
+ position: absolute;
+ top:0;
+ right: 10px;
+ z-index: 10;
+ padding: 10px 50px 10px 10px;
+ color: #fff;
+ background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%;
+ font-weight: 700;
+ box-shadow: 0 0 10px rgba(0,0,0,.5);
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+
+#header_wrap {
+ background: #212121;
+ background: -moz-linear-gradient(top, #373737, #212121);
+ background: -webkit-linear-gradient(top, #373737, #212121);
+ background: -ms-linear-gradient(top, #373737, #212121);
+ background: -o-linear-gradient(top, #373737, #212121);
+ background: linear-gradient(top, #373737, #212121);
+}
+
+#header_wrap .inner {
+ padding: 50px 10px 30px 10px;
+}
+
+#project_title {
+ margin: 0;
+ color: #fff;
+ font-size: 42px;
+ font-weight: 700;
+ text-shadow: #111 0px 0px 10px;
+}
+
+#project_tagline {
+ color: #fff;
+ font-size: 24px;
+ font-weight: 300;
+ background: none;
+ text-shadow: #111 0px 0px 10px;
+}
+
+#downloads {
+ position: absolute;
+ width: 210px;
+ z-index: 10;
+ bottom: -40px;
+ right: 0;
+ height: 70px;
+ background: url('../images/icon_download.png') no-repeat 0% 90%;
+}
+
+.zip_download_link {
+ display: block;
+ float: right;
+ width: 90px;
+ height:70px;
+ text-indent: -5000px;
+ overflow: hidden;
+ background: url(../images/sprite_download.png) no-repeat bottom left;
+}
+
+.tar_download_link {
+ display: block;
+ float: right;
+ width: 90px;
+ height:70px;
+ text-indent: -5000px;
+ overflow: hidden;
+ background: url(../images/sprite_download.png) no-repeat bottom right;
+ margin-left: 10px;
+}
+
+.zip_download_link:hover {
+ background: url(../images/sprite_download.png) no-repeat top left;
+}
+
+.tar_download_link:hover {
+ background: url(../images/sprite_download.png) no-repeat top right;
+}
+
+#main_content_wrap {
+ background: #f2f2f2;
+ border-top: 1px solid #111;
+ border-bottom: 1px solid #111;
+}
+
+#main_content {
+ padding-top: 40px;
+}
+
+#footer_wrap {
+ background: #212121;
+}
+
+
+
+/*******************************************************************************
+Small Device Styles
+*******************************************************************************/
+
+@media screen and (max-width: 480px) {
+ body {
+ font-size:14px;
+ }
+
+ #downloads {
+ display: none;
+ }
+
+ .inner {
+ min-width: 320px;
+ max-width: 480px;
+ }
+
+ #project_title {
+ font-size: 32px;
+ }
+
+ h1 {
+ font-size: 28px;
+ }
+
+ h2 {
+ font-size: 24px;
+ }
+
+ h3 {
+ font-size: 21px;
+ }
+
+ h4 {
+ font-size: 18px;
+ }
+
+ h5 {
+ font-size: 14px;
+ }
+
+ h6 {
+ font-size: 12px;
+ }
+
+ code, pre {
+ min-width: 320px;
+ max-width: 480px;
+ font-size: 11px;
+ }
+
+}