diff --git a/Assets/games/STICK HERO/hero.css b/Assets/games/STICK HERO/hero.css
new file mode 100644
index 0000000..9820584
--- /dev/null
+++ b/Assets/games/STICK HERO/hero.css
@@ -0,0 +1,127 @@
+html,
+body {
+ height: 100%;
+ margin: 0;
+}
+
+body {
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+ cursor: pointer;
+}
+
+.container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+}
+
+#score {
+ position: absolute;
+ top: 30px;
+ right: 30px;
+ font-size: 2em;
+ font-weight: 900;
+}
+
+#introduction {
+ width: 200px;
+ height: 150px;
+ position: absolute;
+ font-weight: 600;
+ font-size: 0.8em;
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+ text-align: center;
+ transition: opacity 2s;
+}
+
+#restart {
+ width: 120px;
+ height: 120px;
+ position: absolute;
+ border-radius: 50%;
+ color: white;
+ background-color: red;
+ border: none;
+ font-weight: 700;
+ font-size: 1.2em;
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+ display: none;
+ cursor: pointer;
+}
+
+#perfect {
+ position: absolute;
+ opacity: 0;
+ transition: opacity 2s;
+}
+
+#youtube,
+#youtube-card {
+ display: none;
+}
+
+@media (min-height: 425px) {
+
+ #youtube {
+ z-index: 2;
+ display: block;
+ width: 100px;
+ height: 70px;
+ position: absolute;
+ bottom: 20px;
+ left: 20px;
+ background: red;
+ border-radius: 50% / 11%;
+ transform: scale(0.8);
+ transition: transform 0.5s;
+ }
+
+ #youtube:hover,
+ #youtube:focus {
+ transform: scale(0.9);
+ }
+
+ #youtube::before {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 7.5%;
+ left: -6%;
+ width: 112%;
+ height: 85%;
+ background: red;
+ border-radius: 9% / 50%;
+ }
+
+ #youtube::after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 20px;
+ left: 40px;
+ width: 45px;
+ height: 30px;
+ border: 15px solid transparent;
+ box-sizing: border-box;
+ border-left: 30px solid white;
+ }
+
+ #youtube span {
+ font-size: 0;
+ position: absolute;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ }
+
+ #youtube:hover + #youtube-card {
+ display: block;
+ position: absolute;
+ bottom: 12px;
+ left: 10px;
+ padding: 25px 25px 25px 130px;
+ width: 300px;
+ background-color: white;
+ }
+}
diff --git a/Assets/games/STICK HERO/index.html b/Assets/games/STICK HERO/index.html
new file mode 100644
index 0000000..658eec2
--- /dev/null
+++ b/Assets/games/STICK HERO/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ STICK HERO
+
+
+
+
+
+
+
Hold down the mouse to stretch out a stick
+
DOUBLE SCORE
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Assets/games/STICK HERO/logic.js b/Assets/games/STICK HERO/logic.js
new file mode 100644
index 0000000..fbe9822
--- /dev/null
+++ b/Assets/games/STICK HERO/logic.js
@@ -0,0 +1,513 @@
+// Extend the base functionality of JavaScript
+Array.prototype.last = function () {
+ return this[this.length - 1];
+ };
+
+ // A sinus function that acceps degrees instead of radians
+ Math.sinus = function (degree) {
+ return Math.sin((degree / 180) * Math.PI);
+ };
+
+ // Game data
+ let phase = "waiting"; // waiting | stretching | turning | walking | transitioning | falling
+ let lastTimestamp; // The timestamp of the previous requestAnimationFrame cycle
+
+ let heroX; // Changes when moving forward
+ let heroY; // Only changes when falling
+ let sceneOffset; // Moves the whole game
+
+ let platforms = [];
+ let sticks = [];
+ let trees = [];
+
+ // Todo: Save high score to localStorage (?)
+
+ let score = 0;
+
+ // Configuration
+ const canvasWidth = 375;
+ const canvasHeight = 375;
+ const platformHeight = 100;
+ const heroDistanceFromEdge = 10; // While waiting
+ const paddingX = 100; // The waiting position of the hero in from the original canvas size
+ const perfectAreaSize = 10;
+
+ // The background moves slower than the hero
+ const backgroundSpeedMultiplier = 0.2;
+
+ const hill1BaseHeight = 100;
+ const hill1Amplitude = 10;
+ const hill1Stretch = 1;
+ const hill2BaseHeight = 70;
+ const hill2Amplitude = 20;
+ const hill2Stretch = 0.5;
+
+ const stretchingSpeed = 4; // Milliseconds it takes to draw a pixel
+ const turningSpeed = 4; // Milliseconds it takes to turn a degree
+ const walkingSpeed = 4;
+ const transitioningSpeed = 2;
+ const fallingSpeed = 2;
+
+ const heroWidth = 17; // 24
+ const heroHeight = 30; // 40
+
+ const canvas = document.getElementById("game");
+ canvas.width = window.innerWidth; // Make the Canvas full screen
+ canvas.height = window.innerHeight;
+
+ const ctx = canvas.getContext("2d");
+
+ const introductionElement = document.getElementById("introduction");
+ const perfectElement = document.getElementById("perfect");
+ const restartButton = document.getElementById("restart");
+ const scoreElement = document.getElementById("score");
+
+ // Initialize layout
+ resetGame();
+
+ // Resets game variables and layouts but does not start the game (game starts on keypress)
+ function resetGame() {
+ // Reset game progress
+ phase = "waiting";
+ lastTimestamp = undefined;
+ sceneOffset = 0;
+ score = 0;
+
+ introductionElement.style.opacity = 1;
+ perfectElement.style.opacity = 0;
+ restartButton.style.display = "none";
+ scoreElement.innerText = score;
+
+ // The first platform is always the same
+ // x + w has to match paddingX
+ platforms = [{ x: 50, w: 50 }];
+ generatePlatform();
+ generatePlatform();
+ generatePlatform();
+ generatePlatform();
+
+ sticks = [{ x: platforms[0].x + platforms[0].w, length: 0, rotation: 0 }];
+
+ trees = [];
+ generateTree();
+ generateTree();
+ generateTree();
+ generateTree();
+ generateTree();
+ generateTree();
+ generateTree();
+ generateTree();
+ generateTree();
+ generateTree();
+
+ heroX = platforms[0].x + platforms[0].w - heroDistanceFromEdge;
+ heroY = 0;
+
+ draw();
+ }
+
+ function generateTree() {
+ const minimumGap = 30;
+ const maximumGap = 150;
+
+ // X coordinate of the right edge of the furthest tree
+ const lastTree = trees[trees.length - 1];
+ let furthestX = lastTree ? lastTree.x : 0;
+
+ const x =
+ furthestX +
+ minimumGap +
+ Math.floor(Math.random() * (maximumGap - minimumGap));
+
+ const treeColors = ["#6D8821", "#8FAC34", "#98B333"];
+ const color = treeColors[Math.floor(Math.random() * 3)];
+
+ trees.push({ x, color });
+ }
+
+ function generatePlatform() {
+ const minimumGap = 40;
+ const maximumGap = 200;
+ const minimumWidth = 20;
+ const maximumWidth = 100;
+
+ // X coordinate of the right edge of the furthest platform
+ const lastPlatform = platforms[platforms.length - 1];
+ let furthestX = lastPlatform.x + lastPlatform.w;
+
+ const x =
+ furthestX +
+ minimumGap +
+ Math.floor(Math.random() * (maximumGap - minimumGap));
+ const w =
+ minimumWidth + Math.floor(Math.random() * (maximumWidth - minimumWidth));
+
+ platforms.push({ x, w });
+ }
+
+ resetGame();
+
+ // If space was pressed restart the game
+ window.addEventListener("keydown", function (event) {
+ if (event.key == " ") {
+ event.preventDefault();
+ resetGame();
+ return;
+ }
+ });
+
+ window.addEventListener("mousedown", function (event) {
+ if (phase == "waiting") {
+ lastTimestamp = undefined;
+ introductionElement.style.opacity = 0;
+ phase = "stretching";
+ window.requestAnimationFrame(animate);
+ }
+ });
+
+ window.addEventListener("mouseup", function (event) {
+ if (phase == "stretching") {
+ phase = "turning";
+ }
+ });
+
+ window.addEventListener("resize", function (event) {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ draw();
+ });
+
+ window.requestAnimationFrame(animate);
+
+ // The main game loop
+ function animate(timestamp) {
+ if (!lastTimestamp) {
+ lastTimestamp = timestamp;
+ window.requestAnimationFrame(animate);
+ return;
+ }
+
+ switch (phase) {
+ case "waiting":
+ return; // Stop the loop
+ case "stretching": {
+ sticks.last().length += (timestamp - lastTimestamp) / stretchingSpeed;
+ break;
+ }
+ case "turning": {
+ sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed;
+
+ if (sticks.last().rotation > 90) {
+ sticks.last().rotation = 90;
+
+ const [nextPlatform, perfectHit] = thePlatformTheStickHits();
+ if (nextPlatform) {
+ // Increase score
+ score += perfectHit ? 2 : 1;
+ scoreElement.innerText = score;
+
+ if (perfectHit) {
+ perfectElement.style.opacity = 1;
+ setTimeout(() => (perfectElement.style.opacity = 0), 1000);
+ }
+
+ generatePlatform();
+ generateTree();
+ generateTree();
+ }
+
+ phase = "walking";
+ }
+ break;
+ }
+ case "walking": {
+ heroX += (timestamp - lastTimestamp) / walkingSpeed;
+
+ const [nextPlatform] = thePlatformTheStickHits();
+ if (nextPlatform) {
+ // If hero will reach another platform then limit it's position at it's edge
+ const maxHeroX = nextPlatform.x + nextPlatform.w - heroDistanceFromEdge;
+ if (heroX > maxHeroX) {
+ heroX = maxHeroX;
+ phase = "transitioning";
+ }
+ } else {
+ // If hero won't reach another platform then limit it's position at the end of the pole
+ const maxHeroX = sticks.last().x + sticks.last().length + heroWidth;
+ if (heroX > maxHeroX) {
+ heroX = maxHeroX;
+ phase = "falling";
+ }
+ }
+ break;
+ }
+ case "transitioning": {
+ sceneOffset += (timestamp - lastTimestamp) / transitioningSpeed;
+
+ const [nextPlatform] = thePlatformTheStickHits();
+ if (sceneOffset > nextPlatform.x + nextPlatform.w - paddingX) {
+ // Add the next step
+ sticks.push({
+ x: nextPlatform.x + nextPlatform.w,
+ length: 0,
+ rotation: 0
+ });
+ phase = "waiting";
+ }
+ break;
+ }
+ case "falling": {
+ if (sticks.last().rotation < 180)
+ sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed;
+
+ heroY += (timestamp - lastTimestamp) / fallingSpeed;
+ const maxHeroY =
+ platformHeight + 100 + (window.innerHeight - canvasHeight) / 2;
+ if (heroY > maxHeroY) {
+ restartButton.style.display = "block";
+ return;
+ }
+ break;
+ }
+ default:
+ throw Error("Wrong phase");
+ }
+
+ draw();
+ window.requestAnimationFrame(animate);
+
+ lastTimestamp = timestamp;
+ }
+
+ // Returns the platform the stick hit (if it didn't hit any stick then return undefined)
+ function thePlatformTheStickHits() {
+ if (sticks.last().rotation != 90)
+ throw Error(`Stick is ${sticks.last().rotation}°`);
+ const stickFarX = sticks.last().x + sticks.last().length;
+
+ const platformTheStickHits = platforms.find(
+ (platform) => platform.x < stickFarX && stickFarX < platform.x + platform.w
+ );
+
+ // If the stick hits the perfect area
+ if (
+ platformTheStickHits &&
+ platformTheStickHits.x + platformTheStickHits.w / 2 - perfectAreaSize / 2 <
+ stickFarX &&
+ stickFarX <
+ platformTheStickHits.x + platformTheStickHits.w / 2 + perfectAreaSize / 2
+ )
+ return [platformTheStickHits, true];
+
+ return [platformTheStickHits, false];
+ }
+
+ function draw() {
+ ctx.save();
+ ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
+
+ drawBackground();
+
+ // Center main canvas area to the middle of the screen
+ ctx.translate(
+ (window.innerWidth - canvasWidth) / 2 - sceneOffset,
+ (window.innerHeight - canvasHeight) / 2
+ );
+
+ // Draw scene
+ drawPlatforms();
+ drawHero();
+ drawSticks();
+
+ // Restore transformation
+ ctx.restore();
+ }
+
+ restartButton.addEventListener("click", function (event) {
+ event.preventDefault();
+ resetGame();
+ restartButton.style.display = "none";
+ });
+
+ function drawPlatforms() {
+ platforms.forEach(({ x, w }) => {
+ // Draw platform
+ ctx.fillStyle = "black";
+ ctx.fillRect(
+ x,
+ canvasHeight - platformHeight,
+ w,
+ platformHeight + (window.innerHeight - canvasHeight) / 2
+ );
+
+ // Draw perfect area only if hero did not yet reach the platform
+ if (sticks.last().x < x) {
+ ctx.fillStyle = "red";
+ ctx.fillRect(
+ x + w / 2 - perfectAreaSize / 2,
+ canvasHeight - platformHeight,
+ perfectAreaSize,
+ perfectAreaSize
+ );
+ }
+ });
+ }
+
+ function drawHero() {
+ ctx.save();
+ ctx.fillStyle = "black";
+ ctx.translate(
+ heroX - heroWidth / 2,
+ heroY + canvasHeight - platformHeight - heroHeight / 2
+ );
+
+ // Body
+ drawRoundedRect(
+ -heroWidth / 2,
+ -heroHeight / 2,
+ heroWidth,
+ heroHeight - 4,
+ 5
+ );
+
+ // Legs
+ const legDistance = 5;
+ ctx.beginPath();
+ ctx.arc(legDistance, 11.5, 3, 0, Math.PI * 2, false);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(-legDistance, 11.5, 3, 0, Math.PI * 2, false);
+ ctx.fill();
+
+ // Eye
+ ctx.beginPath();
+ ctx.fillStyle = "white";
+ ctx.arc(5, -7, 3, 0, Math.PI * 2, false);
+ ctx.fill();
+
+ // Band
+ ctx.fillStyle = "red";
+ ctx.fillRect(-heroWidth / 2 - 1, -12, heroWidth + 2, 4.5);
+ ctx.beginPath();
+ ctx.moveTo(-9, -14.5);
+ ctx.lineTo(-17, -18.5);
+ ctx.lineTo(-14, -8.5);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.moveTo(-10, -10.5);
+ ctx.lineTo(-15, -3.5);
+ ctx.lineTo(-5, -7);
+ ctx.fill();
+
+ ctx.restore();
+ }
+
+ function drawRoundedRect(x, y, width, height, radius) {
+ ctx.beginPath();
+ ctx.moveTo(x, y + radius);
+ ctx.lineTo(x, y + height - radius);
+ ctx.arcTo(x, y + height, x + radius, y + height, radius);
+ ctx.lineTo(x + width - radius, y + height);
+ ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
+ ctx.lineTo(x + width, y + radius);
+ ctx.arcTo(x + width, y, x + width - radius, y, radius);
+ ctx.lineTo(x + radius, y);
+ ctx.arcTo(x, y, x, y + radius, radius);
+ ctx.fill();
+ }
+
+ function drawSticks() {
+ sticks.forEach((stick) => {
+ ctx.save();
+
+ // Move the anchor point to the start of the stick and rotate
+ ctx.translate(stick.x, canvasHeight - platformHeight);
+ ctx.rotate((Math.PI / 180) * stick.rotation);
+
+ // Draw stick
+ ctx.beginPath();
+ ctx.lineWidth = 2;
+ ctx.moveTo(0, 0);
+ ctx.lineTo(0, -stick.length);
+ ctx.stroke();
+
+ // Restore transformations
+ ctx.restore();
+ });
+ }
+
+ function drawBackground() {
+ // Draw sky
+ var gradient = ctx.createLinearGradient(0, 0, 0, window.innerHeight);
+ gradient.addColorStop(0, "#BBD691");
+ gradient.addColorStop(1, "#FEF1E1");
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
+
+ // Draw hills
+ drawHill(hill1BaseHeight, hill1Amplitude, hill1Stretch, "#95C629");
+ drawHill(hill2BaseHeight, hill2Amplitude, hill2Stretch, "#659F1C");
+
+ // Draw trees
+ trees.forEach((tree) => drawTree(tree.x, tree.color));
+ }
+
+ // A hill is a shape under a stretched out sinus wave
+ function drawHill(baseHeight, amplitude, stretch, color) {
+ ctx.beginPath();
+ ctx.moveTo(0, window.innerHeight);
+ ctx.lineTo(0, getHillY(0, baseHeight, amplitude, stretch));
+ for (let i = 0; i < window.innerWidth; i++) {
+ ctx.lineTo(i, getHillY(i, baseHeight, amplitude, stretch));
+ }
+ ctx.lineTo(window.innerWidth, window.innerHeight);
+ ctx.fillStyle = color;
+ ctx.fill();
+ }
+
+ function drawTree(x, color) {
+ ctx.save();
+ ctx.translate(
+ (-sceneOffset * backgroundSpeedMultiplier + x) * hill1Stretch,
+ getTreeY(x, hill1BaseHeight, hill1Amplitude)
+ );
+
+ const treeTrunkHeight = 5;
+ const treeTrunkWidth = 2;
+ const treeCrownHeight = 25;
+ const treeCrownWidth = 10;
+
+ // Draw trunk
+ ctx.fillStyle = "#7D833C";
+ ctx.fillRect(
+ -treeTrunkWidth / 2,
+ -treeTrunkHeight,
+ treeTrunkWidth,
+ treeTrunkHeight
+ );
+
+ // Draw crown
+ ctx.beginPath();
+ ctx.moveTo(-treeCrownWidth / 2, -treeTrunkHeight);
+ ctx.lineTo(0, -(treeTrunkHeight + treeCrownHeight));
+ ctx.lineTo(treeCrownWidth / 2, -treeTrunkHeight);
+ ctx.fillStyle = color;
+ ctx.fill();
+
+ ctx.restore();
+ }
+
+ function getHillY(windowX, baseHeight, amplitude, stretch) {
+ const sineBaseY = window.innerHeight - baseHeight;
+ return (
+ Math.sinus((sceneOffset * backgroundSpeedMultiplier + windowX) * stretch) *
+ amplitude +
+ sineBaseY
+ );
+ }
+
+ function getTreeY(x, baseHeight, amplitude) {
+ const sineBaseY = window.innerHeight - baseHeight;
+ return Math.sinus(x) * amplitude + sineBaseY;
+ }
+
\ No newline at end of file
diff --git a/Assets/games/STICK HERO/ss_stick_hero.png b/Assets/games/STICK HERO/ss_stick_hero.png
new file mode 100644
index 0000000..e0e280b
Binary files /dev/null and b/Assets/games/STICK HERO/ss_stick_hero.png differ
diff --git a/index.html b/index.html
index 3291d81..477094b 100644
--- a/index.html
+++ b/index.html
@@ -556,6 +556,14 @@ Random Name Generator
+
+
+

+
STICK HERO
+
+
+
+
@@ -694,6 +702,7 @@ Contact Us
+