diff --git a/Games/Menja_block_breaker/README.md b/Games/Menja_block_breaker/README.md
new file mode 100644
index 0000000000..5c68f770cd
--- /dev/null
+++ b/Games/Menja_block_breaker/README.md
@@ -0,0 +1,18 @@
+# Block Breaker Game
+
+## Description
+The Block Breaker Game is a classic arcade-style game where the player controls a paddle to bounce a ball and break blocks. The objective is to clear all the blocks on the screen by hitting them with the ball while preventing the ball from falling off the screen using the paddle.
+
+## Features
+- **Multiple Levels:** Progressive difficulty with increasing levels.
+- **Power-Ups:** Various power-ups to enhance gameplay (e.g., paddle size increase, extra balls).
+- **Score Tracking:** Keeps track of the player's score.
+- **Sound Effects:** Engaging sound effects for different actions like hitting blocks, catching power-ups, etc.
+- **Responsive Controls:** Smooth and responsive paddle controls.
+
+## Gameplay
+- Use the left and right arrow keys to move the paddle.
+- Prevent the ball from falling off the bottom of the screen by bouncing it back with the paddle.
+- Break all the blocks on the screen to advance to the next level.
+- Collect power-ups for special abilities and higher scores.
+
diff --git a/Games/Menja_block_breaker/index.html b/Games/Menja_block_breaker/index.html
new file mode 100644
index 0000000000..dd8641f679
--- /dev/null
+++ b/Games/Menja_block_breaker/index.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+ Menja
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Games/Menja_block_breaker/script.js b/Games/Menja_block_breaker/script.js
new file mode 100644
index 0000000000..f4ee8312af
--- /dev/null
+++ b/Games/Menja_block_breaker/script.js
@@ -0,0 +1,1664 @@
+let gameSpeed = 1;
+const BLUE = { r: 0x67, g: 0xd7, b: 0xf0 };
+const GREEN = { r: 0xa6, g: 0xe0, b: 0x2c };
+const PINK = { r: 0xfa, g: 0x24, b: 0x73 };
+const ORANGE = { r: 0xfe, g: 0x95, b: 0x22 };
+const allColors = [BLUE, GREEN, PINK, ORANGE];
+const getSpawnDelay = () => {
+ const spawnDelayMax = 1400;
+ const spawnDelayMin = 550;
+ const spawnDelay = spawnDelayMax - state.game.cubeCount * 3.1;
+ return Math.max(spawnDelay, spawnDelayMin);
+}
+const doubleStrongEnableScore = 2000;
+const slowmoThreshold = 10;
+const strongThreshold = 25;
+const spinnerThreshold = 25;
+let pointerIsDown = false;
+let pointerScreen = { x: 0, y: 0 };
+let pointerScene = { x: 0, y: 0 };
+const minPointerSpeed = 60;
+const hitDampening = 0.1;
+const backboardZ = -400;
+const shadowColor = '#262e36';
+const airDrag = 0.022;
+const gravity = 0.3;
+const sparkColor = 'rgba(170,221,255,.9)';
+const sparkThickness = 2.2;
+const airDragSpark = 0.1;
+const touchTrailColor = 'rgba(170,221,255,.62)';
+const touchTrailThickness = 7;
+const touchPointLife = 120;
+const touchPoints = [];
+const targetRadius = 40;
+const targetHitRadius = 50;
+const makeTargetGlueColor = target => {
+ return 'rgb(170,221,255)';
+};const fragRadius = targetRadius / 3;
+const canvas = document.querySelector('#c');
+const cameraDistance = 900;
+const sceneScale = 1;
+const cameraFadeStartZ = 0.45*cameraDistance;
+const cameraFadeEndZ = 0.65*cameraDistance;
+const cameraFadeRange = cameraFadeEndZ - cameraFadeStartZ;
+const allVertices = [];
+const allPolys = [];
+const allShadowVertices = [];
+const allShadowPolys = [];
+const GAME_MODE_RANKED = Symbol('GAME_MODE_RANKED');
+const GAME_MODE_CASUAL = Symbol('GAME_MODE_CASUAL');
+const MENU_MAIN = Symbol('MENU_MAIN');
+const MENU_PAUSE = Symbol('MENU_PAUSE');
+const MENU_SCORE = Symbol('MENU_SCORE');
+const state = {
+ game: {
+ mode: GAME_MODE_RANKED,
+ time: 0,
+ score: 0,
+ cubeCount: 0
+ },
+ menus: {
+ active: MENU_MAIN
+ }
+};
+
+const isInGame = () => !state.menus.active;
+const isMenuVisible = () => !!state.menus.active;
+const isCasualGame = () => state.game.mode === GAME_MODE_CASUAL;
+const isPaused = () => state.menus.active === MENU_PAUSE;
+const highScoreKey = '__menja__highScore';
+const getHighScore = () => {
+ const raw = localStorage.getItem(highScoreKey);
+ return raw ? parseInt(raw, 10) : 0;
+};
+let _lastHighscore = getHighScore();
+const setHighScore = score => {
+ _lastHighscore = getHighScore();
+ localStorage.setItem(highScoreKey, String(score));
+};
+const isNewHighScore = () => state.game.score > _lastHighscore;
+const invariant = (condition, message) => {
+ if (!condition) throw new Error(message);
+};
+const $ = selector => document.querySelector(selector);
+const handleClick = (element, handler) => element.addEventListener('click', handler);
+const handlePointerDown = (element, handler) => {
+ element.addEventListener('touchstart', handler);
+ element.addEventListener('mousedown', handler);
+};
+const formatNumber = num => num.toLocaleString();
+const PI = Math.PI;
+const TAU = Math.PI * 2;
+const ETA = Math.PI * 0.5;
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+const lerp = (a, b, mix) => (b - a) * mix + a;
+const random = (min, max) => Math.random() * (max - min) + min;
+const randomInt = (min, max) => ((Math.random() * (max - min + 1)) | 0) + min;
+const pickOne = arr => arr[Math.random() * arr.length | 0];
+const colorToHex = color => {
+ return '#' +
+ (color.r | 0).toString(16).padStart(2, '0') +
+ (color.g | 0).toString(16).padStart(2, '0') +
+ (color.b | 0).toString(16).padStart(2, '0');
+};
+const shadeColor = (color, lightness) => {
+ let other, mix;
+ if (lightness < 0.5) {
+ other = 0;
+ mix = 1 - (lightness * 2);
+ } else {
+ other = 255;
+ mix = lightness * 2 - 1;
+ }
+ return '#' +
+ (lerp(color.r, other, mix) | 0).toString(16).padStart(2, '0') +
+ (lerp(color.g, other, mix) | 0).toString(16).padStart(2, '0') +
+ (lerp(color.b, other, mix) | 0).toString(16).padStart(2, '0');
+};
+const _allCooldowns = [];
+const makeCooldown = (rechargeTime, units=1) => {
+ let timeRemaining = 0;
+ let lastTime = 0;
+ const initialOptions = { rechargeTime, units };
+ const updateTime = () => {
+ const now = state.game.time;
+ if (now < lastTime) {
+ timeRemaining = 0;
+ } else {
+ timeRemaining -= now-lastTime;
+ if (timeRemaining < 0) timeRemaining = 0;
+ }
+ lastTime = now;
+ };
+
+ const canUse = () => {
+ updateTime();
+ return timeRemaining <= (rechargeTime * (units-1));
+ };
+
+ const cooldown = {
+ canUse,
+ useIfAble() {
+ const usable = canUse();
+ if (usable) timeRemaining += rechargeTime;
+ return usable;
+ },
+ mutate(options) {
+ if (options.rechargeTime) {
+ timeRemaining -= rechargeTime-options.rechargeTime;
+ if (timeRemaining < 0) timeRemaining = 0;
+ rechargeTime = options.rechargeTime;
+ }
+ if (options.units) units = options.units;
+ },
+ reset() {
+ timeRemaining = 0;
+ lastTime = 0;
+ this.mutate(initialOptions);
+ }
+ };
+
+ _allCooldowns.push(cooldown);
+
+ return cooldown;
+};
+
+const resetAllCooldowns = () => _allCooldowns.forEach(cooldown => cooldown.reset());
+
+const makeSpawner = ({ chance, cooldownPerSpawn, maxSpawns }) => {
+ const cooldown = makeCooldown(cooldownPerSpawn, maxSpawns);
+ return {
+ shouldSpawn() {
+ return Math.random() <= chance && cooldown.useIfAble();
+ },
+ mutate(options) {
+ if (options.chance) chance = options.chance;
+ cooldown.mutate({
+ rechargeTime: options.cooldownPerSpawn,
+ units: options.maxSpawns
+ });
+ }
+ };
+};
+const normalize = v => {
+ const mag = Math.hypot(v.x, v.y, v.z);
+ return {
+ x: v.x / mag,
+ y: v.y / mag,
+ z: v.z / mag
+ };
+}
+const add = a => b => a + b;
+const scaleVector = scale => vector => {
+ vector.x *= scale;
+ vector.y *= scale;
+ vector.z *= scale;
+};
+function cloneVertices(vertices) {
+ return vertices.map(v => ({ x: v.x, y: v.y, z: v.z }));
+}
+function copyVerticesTo(arr1, arr2) {
+ const len = arr1.length;
+ for (let i=0; i {
+ const targetVertex = target[i];
+
+ const x1 = v.x;
+ const y1 = v.z*sinX + v.y*cosX;
+ const z1 = v.z*cosX - v.y*sinX;
+
+ const x2 = x1*cosY - z1*sinY;
+ const y2 = y1;
+ const z2 = x1*sinY + z1*cosY;
+
+ const x3 = x2*cosZ - y2*sinZ;
+ const y3 = x2*sinZ + y2*cosZ;
+ const z3 = z2;
+
+ targetVertex.x = x3 * sX + tX;
+ targetVertex.y = y3 * sY + tY;
+ targetVertex.z = z3 * sZ + tZ;
+ });
+}
+
+const projectVertex = v => {
+ const focalLength = cameraDistance * sceneScale;
+ const depth = focalLength / (cameraDistance - v.z);
+ v.x = v.x * depth;
+ v.y = v.y * depth;
+};
+
+const projectVertexTo = (v, target) => {
+ const focalLength = cameraDistance * sceneScale;
+ const depth = focalLength / (cameraDistance - v.z);
+ target.x = v.x * depth;
+ target.y = v.y * depth;
+};
+
+const PERF_START = () => {};
+const PERF_END = () => {};
+const PERF_UPDATE = () => {};
+
+function makeCubeModel({ scale=1 }) {
+ return {
+ vertices: [
+
+ { x: -scale, y: -scale, z: scale },
+ { x: scale, y: -scale, z: scale },
+ { x: scale, y: scale, z: scale },
+ { x: -scale, y: scale, z: scale },
+
+ { x: -scale, y: -scale, z: -scale },
+ { x: scale, y: -scale, z: -scale },
+ { x: scale, y: scale, z: -scale },
+ { x: -scale, y: scale, z: -scale }
+ ],
+ polys: [
+
+ { vIndexes: [0, 1, 2, 3] },
+
+ { vIndexes: [7, 6, 5, 4] },
+
+ { vIndexes: [3, 2, 6, 7] },
+
+ { vIndexes: [4, 5, 1, 0] },
+
+ { vIndexes: [5, 6, 2, 1] },
+
+ { vIndexes: [0, 3, 7, 4] }
+ ]
+ };
+}
+
+function makeRecursiveCubeModel({ recursionLevel, splitFn, color, scale=1 }) {
+ const getScaleAtLevel = level => 1 / (3 ** level);
+
+ let cubeOrigins = [{ x: 0, y: 0, z: 0 }];
+
+ for (let i=1; i<=recursionLevel; i++) {
+ const scale = getScaleAtLevel(i) * 2;
+ const cubeOrigins2 = [];
+ cubeOrigins.forEach(origin => {
+ cubeOrigins2.push(...splitFn(origin, scale));
+ });
+ cubeOrigins = cubeOrigins2;
+ }
+
+ const finalModel = { vertices: [], polys: [] };
+
+ const cubeModel = makeCubeModel({ scale: 1 });
+ cubeModel.vertices.forEach(scaleVector(getScaleAtLevel(recursionLevel)));
+
+ const maxComponent = getScaleAtLevel(recursionLevel) * (3 ** recursionLevel - 1);
+
+ cubeOrigins.forEach((origin, cubeIndex) => {
+
+ const occlusion = Math.max(
+ Math.abs(origin.x),
+ Math.abs(origin.y),
+ Math.abs(origin.z)
+ ) / maxComponent;
+
+ const occlusionLighter = recursionLevel > 2
+ ? occlusion
+ : (occlusion + 0.8) / 1.8;
+
+ finalModel.vertices.push(
+ ...cubeModel.vertices.map(v => ({
+ x: (v.x + origin.x) * scale,
+ y: (v.y + origin.y) * scale,
+ z: (v.z + origin.z) * scale
+ }))
+ );
+
+ finalModel.polys.push(
+ ...cubeModel.polys.map(poly => ({
+ vIndexes: poly.vIndexes.map(add(cubeIndex * 8))
+ }))
+ );
+ });
+
+ return finalModel;
+}
+
+function mengerSpongeSplit(o, s) {
+ return [
+
+ { x: o.x + s, y: o.y - s, z: o.z + s },
+ { x: o.x + s, y: o.y - s, z: o.z + 0 },
+ { x: o.x + s, y: o.y - s, z: o.z - s },
+ { x: o.x + 0, y: o.y - s, z: o.z + s },
+ { x: o.x + 0, y: o.y - s, z: o.z - s },
+ { x: o.x - s, y: o.y - s, z: o.z + s },
+ { x: o.x - s, y: o.y - s, z: o.z + 0 },
+ { x: o.x - s, y: o.y - s, z: o.z - s },
+
+ { x: o.x + s, y: o.y + s, z: o.z + s },
+ { x: o.x + s, y: o.y + s, z: o.z + 0 },
+ { x: o.x + s, y: o.y + s, z: o.z - s },
+ { x: o.x + 0, y: o.y + s, z: o.z + s },
+ { x: o.x + 0, y: o.y + s, z: o.z - s },
+ { x: o.x - s, y: o.y + s, z: o.z + s },
+ { x: o.x - s, y: o.y + s, z: o.z + 0 },
+ { x: o.x - s, y: o.y + s, z: o.z - s },
+
+ { x: o.x + s, y: o.y + 0, z: o.z + s },
+ { x: o.x + s, y: o.y + 0, z: o.z - s },
+ { x: o.x - s, y: o.y + 0, z: o.z + s },
+ { x: o.x - s, y: o.y + 0, z: o.z - s }
+ ];
+}
+
+function optimizeModel(model, threshold=0.0001) {
+ const { vertices, polys } = model;
+
+ const compareVertices = (v1, v2) => (
+ Math.abs(v1.x - v2.x) < threshold &&
+ Math.abs(v1.y - v2.y) < threshold &&
+ Math.abs(v1.z - v2.z) < threshold
+ );
+
+ const comparePolys = (p1, p2) => {
+ const v1 = p1.vIndexes;
+ const v2 = p2.vIndexes;
+ return (
+ (
+ v1[0] === v2[0] ||
+ v1[0] === v2[1] ||
+ v1[0] === v2[2] ||
+ v1[0] === v2[3]
+ ) && (
+ v1[1] === v2[0] ||
+ v1[1] === v2[1] ||
+ v1[1] === v2[2] ||
+ v1[1] === v2[3]
+ ) && (
+ v1[2] === v2[0] ||
+ v1[2] === v2[1] ||
+ v1[2] === v2[2] ||
+ v1[2] === v2[3]
+ ) && (
+ v1[3] === v2[0] ||
+ v1[3] === v2[1] ||
+ v1[3] === v2[2] ||
+ v1[3] === v2[3]
+ )
+ );
+ };
+
+ vertices.forEach((v, i) => {
+ v.originalIndexes = [i];
+ });
+
+ for (let i=vertices.length-1; i>=0; i--) {
+ for (let ii=i-1; ii>=0; ii--) {
+ const v1 = vertices[i];
+ const v2 = vertices[ii];
+ if (compareVertices(v1, v2)) {
+ vertices.splice(i, 1);
+ v2.originalIndexes.push(...v1.originalIndexes);
+ break;
+ }
+ }
+ }
+
+ vertices.forEach((v, i) => {
+ polys.forEach(p => {
+ p.vIndexes.forEach((vi, ii, arr) => {
+ const vo = v.originalIndexes;
+ if (vo.includes(vi)) {
+ arr[ii] = i;
+ }
+ });
+ });
+ });
+
+ polys.forEach(p => {
+ const vi = p.vIndexes;
+ p.sum = vi[0] + vi[1] + vi[2] + vi[3];
+ });
+ polys.sort((a, b) => b.sum - a.sum);
+
+ for (let i=polys.length-1; i>=0; i--) {
+ for (let ii=i-1; ii>=0; ii--) {
+ const p1 = polys[i];
+ const p2 = polys[ii];
+ if (p1.sum !== p2.sum) break;
+ if (comparePolys(p1, p2)) {
+ polys.splice(i, 1);
+ polys.splice(ii, 1);
+ i--;
+ break;
+ }
+ }
+ }
+
+ return model;
+}
+
+class Entity {
+ constructor({ model, color, wireframe=false }) {
+ const vertices = cloneVertices(model.vertices);
+ const shadowVertices = cloneVertices(model.vertices);
+ const colorHex = colorToHex(color);
+ const darkColorHex = shadeColor(color, 0.4);
+
+ const polys = model.polys.map(p => ({
+ vertices: p.vIndexes.map(vIndex => vertices[vIndex]),
+ color: color,
+ wireframe: wireframe,
+ strokeWidth: wireframe ? 2 : 0,
+ strokeColor: colorHex,
+ strokeColorDark: darkColorHex,
+ depth: 0,
+ middle: { x: 0, y: 0, z: 0 },
+ normalWorld: { x: 0, y: 0, z: 0 },
+ normalCamera: { x: 0, y: 0, z: 0 }
+ }));
+
+ const shadowPolys = model.polys.map(p => ({
+ vertices: p.vIndexes.map(vIndex => shadowVertices[vIndex]),
+ wireframe: wireframe,
+ normalWorld: { x: 0, y: 0, z: 0 }
+ }));
+
+ this.projected = {};
+ this.model = model;
+ this.vertices = vertices;
+ this.polys = polys;
+ this.shadowVertices = shadowVertices;
+ this.shadowPolys = shadowPolys;
+ this.reset();
+ }
+
+ reset() {
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ this.xD = 0;
+ this.yD = 0;
+ this.zD = 0;
+
+ this.rotateX = 0;
+ this.rotateY = 0;
+ this.rotateZ = 0;
+ this.rotateXD = 0;
+ this.rotateYD = 0;
+ this.rotateZD = 0;
+
+ this.scaleX = 1;
+ this.scaleY = 1;
+ this.scaleZ = 1;
+
+ this.projected.x = 0;
+ this.projected.y = 0;
+ }
+
+ transform() {
+ transformVertices(
+ this.model.vertices,
+ this.vertices,
+ this.x,
+ this.y,
+ this.z,
+ this.rotateX,
+ this.rotateY,
+ this.rotateZ,
+ this.scaleX,
+ this.scaleY,
+ this.scaleZ
+ );
+
+ copyVerticesTo(this.vertices, this.shadowVertices);
+ }
+
+ project() {
+ projectVertexTo(this, this.projected);
+ }
+}
+
+const targets = [];
+
+const targetPool = new Map(allColors.map(c=>([c, []])));
+const targetWireframePool = new Map(allColors.map(c=>([c, []])));
+
+const getTarget = (() => {
+
+ const slowmoSpawner = makeSpawner({
+ chance: 0.5,
+ cooldownPerSpawn: 10000,
+ maxSpawns: 1
+ });
+
+ let doubleStrong = false;
+ const strongSpawner = makeSpawner({
+ chance: 0.3,
+ cooldownPerSpawn: 12000,
+ maxSpawns: 1
+ });
+
+ const spinnerSpawner = makeSpawner({
+ chance: 0.1,
+ cooldownPerSpawn: 10000,
+ maxSpawns: 1
+ });
+
+ const axisOptions = [
+ ['x', 'y'],
+ ['y', 'z'],
+ ['z', 'x']
+ ];
+
+ function getTargetOfStyle(color, wireframe) {
+ const pool = wireframe ? targetWireframePool : targetPool;
+ let target = pool.get(color).pop();
+ if (!target) {
+ target = new Entity({
+ model: optimizeModel(makeRecursiveCubeModel({
+ recursionLevel: 1,
+ splitFn: mengerSpongeSplit,
+ scale: targetRadius
+ })),
+ color: color,
+ wireframe: wireframe
+ });
+
+ target.color = color;
+ target.wireframe = wireframe;
+
+ target.hit = false;
+ target.maxHealth = 0;
+ target.health = 0;
+ }
+ return target;
+ }
+
+ return function getTarget() {
+ if (doubleStrong && state.game.score <= doubleStrongEnableScore) {
+ doubleStrong = false;
+
+ } else if (!doubleStrong && state.game.score > doubleStrongEnableScore) {
+ doubleStrong = true;
+ strongSpawner.mutate({ maxSpawns: 2 });
+ }
+
+ let color = pickOne([BLUE, GREEN, ORANGE]);
+ let wireframe = false;
+ let health = 1;
+ let maxHealth = 3;
+ const spinner = state.game.cubeCount >= spinnerThreshold && isInGame() && spinnerSpawner.shouldSpawn();
+
+ if (state.game.cubeCount >= slowmoThreshold && slowmoSpawner.shouldSpawn()) {
+ color = BLUE;
+ wireframe = true;
+ }
+ else if (state.game.cubeCount >= strongThreshold && strongSpawner.shouldSpawn()) {
+ color = PINK;
+ health = 3;
+ }
+
+ const target = getTargetOfStyle(color, wireframe);
+ target.hit = false;
+ target.maxHealth = maxHealth;
+ target.health = health;
+ updateTargetHealth(target, 0);
+
+ const spinSpeeds = [
+ Math.random() * 0.1 - 0.05,
+ Math.random() * 0.1 - 0.05
+ ];
+
+ if (spinner) {
+
+ spinSpeeds[0] = -0.25;
+ spinSpeeds[1] = 0;
+ target.rotateZ = random(0, TAU);
+ }
+
+ const axes = pickOne(axisOptions);
+
+ spinSpeeds.forEach((spinSpeed, i) => {
+ switch (axes[i]) {
+ case 'x':
+ target.rotateXD = spinSpeed;
+ break;
+ case 'y':
+ target.rotateYD = spinSpeed;
+ break;
+ case 'z':
+ target.rotateZD = spinSpeed;
+ break;
+ }
+ });
+
+ return target;
+ }
+})();
+
+const updateTargetHealth = (target, healthDelta) => {
+ target.health += healthDelta;
+
+ if (!target.wireframe) {
+ const strokeWidth = target.health - 1;
+ const strokeColor = makeTargetGlueColor(target);
+ for (let p of target.polys) {
+ p.strokeWidth = strokeWidth;
+ p.strokeColor = strokeColor;
+ }
+ }
+};
+
+const returnTarget = target => {
+ target.reset();
+ const pool = target.wireframe ? targetWireframePool : targetPool;
+ pool.get(target.color).push(target);
+};
+
+function resetAllTargets() {
+ while(targets.length) {
+ returnTarget(targets.pop());
+ }
+}
+
+const frags = [];
+
+const fragPool = new Map(allColors.map(c=>([c, []])));
+const fragWireframePool = new Map(allColors.map(c=>([c, []])));
+
+const createBurst = (() => {
+
+ const basePositions = mengerSpongeSplit({ x:0, y:0, z:0 }, fragRadius*2);
+ const positions = cloneVertices(basePositions);
+ const prevPositions = cloneVertices(basePositions);
+ const velocities = cloneVertices(basePositions);
+
+ const basePositionNormals = basePositions.map(normalize);
+ const positionNormals = cloneVertices(basePositionNormals);
+
+ const fragCount = basePositions.length;
+
+ function getFragForTarget(target) {
+ const pool = target.wireframe ? fragWireframePool : fragPool;
+ let frag = pool.get(target.color).pop();
+ if (!frag) {
+ frag = new Entity({
+ model: makeCubeModel({ scale: fragRadius }),
+ color: target.color,
+ wireframe: target.wireframe
+ });
+ frag.color = target.color;
+ frag.wireframe = target.wireframe;
+ }
+ return frag;
+ }
+
+ return (target, force=1) => {
+
+ transformVertices(
+ basePositions, positions,
+ target.x, target.y, target.z,
+ target.rotateX, target.rotateY, target.rotateZ,
+ 1, 1, 1
+ );
+ transformVertices(
+ basePositions, prevPositions,
+ target.x - target.xD, target.y - target.yD, target.z - target.zD,
+ target.rotateX - target.rotateXD, target.rotateY - target.rotateYD, target.rotateZ - target.rotateZD,
+ 1, 1, 1
+ );
+
+ for (let i=0; i {
+ frag.reset();
+ const pool = frag.wireframe ? fragWireframePool : fragPool;
+ pool.get(frag.color).push(frag);
+};
+
+const sparks = [];
+const sparkPool = [];
+
+function addSpark(x, y, xD, yD) {
+ const spark = sparkPool.pop() || {};
+
+ spark.x = x + xD * 0.5;
+ spark.y = y + yD * 0.5;
+ spark.xD = xD;
+ spark.yD = yD;
+ spark.life = random(200, 300);
+ spark.maxLife = spark.life;
+
+ sparks.push(spark);
+
+ return spark;
+}
+
+function sparkBurst(x, y, count, maxSpeed) {
+ const angleInc = TAU / count;
+ for (let i=0; i {
+ if (Math.random() < 0.4) {
+ projectVertex(v);
+ addSpark(
+ v.x,
+ v.y,
+ random(-12, 12),
+ random(-12, 12)
+ );
+ }
+ });
+}
+
+function returnSpark(spark) {
+ sparkPool.push(spark);
+}
+
+const hudContainerNode = $('.hud');
+
+function setHudVisibility(visible) {
+ if (visible) {
+ hudContainerNode.style.display = 'block';
+ } else {
+ hudContainerNode.style.display = 'none';
+ }
+}
+
+const scoreNode = $('.score-lbl');
+const cubeCountNode = $('.cube-count-lbl');
+
+function renderScoreHud() {
+ if (isCasualGame()) {
+ scoreNode.style.display = 'none';
+ cubeCountNode.style.opacity = 1;
+ } else {
+ scoreNode.innerText = `SCORE: ${state.game.score}`;
+ scoreNode.style.display = 'block';
+ cubeCountNode.style.opacity = 0.65 ;
+ }
+ cubeCountNode.innerText = `CUBES SMASHED: ${state.game.cubeCount}`;
+}
+
+renderScoreHud();
+
+handlePointerDown($('.pause-btn'), () => pauseGame());
+
+const slowmoNode = $('.slowmo');
+const slowmoBarNode = $('.slowmo__bar');
+
+function renderSlowmoStatus(percentRemaining) {
+ slowmoNode.style.opacity = percentRemaining === 0 ? 0 : 1;
+ slowmoBarNode.style.transform = `scaleX(${percentRemaining.toFixed(3)})`;
+}
+
+const menuContainerNode = $('.menus');
+const menuMainNode = $('.menu--main');
+const menuPauseNode = $('.menu--pause');
+const menuScoreNode = $('.menu--score');
+
+const finalScoreLblNode = $('.final-score-lbl');
+const highScoreLblNode = $('.high-score-lbl');
+
+function showMenu(node) {
+ node.classList.add('active');
+}
+
+function hideMenu(node) {
+ node.classList.remove('active');
+}
+
+function renderMenus() {
+ hideMenu(menuMainNode);
+ hideMenu(menuPauseNode);
+ hideMenu(menuScoreNode);
+
+ switch (state.menus.active) {
+ case MENU_MAIN:
+ showMenu(menuMainNode);
+ break;
+ case MENU_PAUSE:
+ showMenu(menuPauseNode);
+ break;
+ case MENU_SCORE:
+ finalScoreLblNode.textContent = formatNumber(state.game.score);
+ if (isNewHighScore()) {
+ highScoreLblNode.textContent = 'New High Score!';
+ } else {
+ highScoreLblNode.textContent = `High Score: ${formatNumber(getHighScore())}`;
+ }
+ showMenu(menuScoreNode);
+ break;
+ }
+
+ setHudVisibility(!isMenuVisible());
+ menuContainerNode.classList.toggle('has-active', isMenuVisible());
+ menuContainerNode.classList.toggle('interactive-mode', isMenuVisible() && pointerIsDown);
+}
+
+renderMenus();
+
+handleClick($('.play-normal-btn'), () => {
+ setGameMode(GAME_MODE_RANKED);
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.play-casual-btn'), () => {
+ setGameMode(GAME_MODE_CASUAL);
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.resume-btn'), () => resumeGame());
+handleClick($('.menu-btn--pause'), () => setActiveMenu(MENU_MAIN));
+
+handleClick($('.play-again-btn'), () => {
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.menu-btn--score'), () => setActiveMenu(MENU_MAIN));
+
+handleClick($('.play-normal-btn'), () => {
+ setGameMode(GAME_MODE_RANKED);
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.play-casual-btn'), () => {
+ setGameMode(GAME_MODE_CASUAL);
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.resume-btn'), () => resumeGame());
+handleClick($('.menu-btn--pause'), () => setActiveMenu(MENU_MAIN));
+
+handleClick($('.play-again-btn'), () => {
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.menu-btn--score'), () => setActiveMenu(MENU_MAIN));
+
+function setActiveMenu(menu) {
+ state.menus.active = menu;
+ renderMenus();
+}
+
+function setScore(score) {
+ state.game.score = score;
+ renderScoreHud();
+}
+
+function incrementScore(inc) {
+ if (isInGame()) {
+ state.game.score += inc;
+ if (state.game.score < 0) {
+ state.game.score = 0;
+ }
+ renderScoreHud();
+ }
+}
+
+function setCubeCount(count) {
+ state.game.cubeCount = count;
+ renderScoreHud();
+}
+
+function incrementCubeCount(inc) {
+ if (isInGame()) {
+ state.game.cubeCount += inc;
+ renderScoreHud();
+ }
+}
+
+function setGameMode(mode) {
+ state.game.mode = mode;
+}
+
+function resetGame() {
+ resetAllTargets();
+ state.game.time = 0;
+ resetAllCooldowns();
+ setScore(0);
+ setCubeCount(0);
+ spawnTime = getSpawnDelay();
+}
+
+function pauseGame() {
+ isInGame() && setActiveMenu(MENU_PAUSE);
+}
+
+function resumeGame() {
+ isPaused() && setActiveMenu(null);
+}
+
+function endGame() {
+ handleCanvasPointerUp();
+ if (isNewHighScore()) {
+ setHighScore(state.game.score);
+ }
+ setActiveMenu(MENU_SCORE);
+}
+
+window.addEventListener('keydown', event => {
+ if (event.key === 'p') {
+ isPaused() ? resumeGame() : pauseGame();
+ }
+});
+
+let spawnTime = 0;
+const maxSpawnX = 450;
+const pointerDelta = { x: 0, y: 0 };
+const pointerDeltaScaled = { x: 0, y: 0 };
+
+const slowmoDuration = 1500;
+let slowmoRemaining = 0;
+let spawnExtra = 0;
+const spawnExtraDelay = 300;
+let targetSpeed = 1;
+
+function tick(width, height, simTime, simSpeed, lag) {
+ PERF_START('frame');
+ PERF_START('tick');
+
+ state.game.time += simTime;
+
+ if (slowmoRemaining > 0) {
+ slowmoRemaining -= simTime;
+ if (slowmoRemaining < 0) {
+ slowmoRemaining = 0;
+ }
+ targetSpeed = pointerIsDown ? 0.075 : 0.3;
+ } else {
+ const menuPointerDown = isMenuVisible() && pointerIsDown;
+ targetSpeed = menuPointerDown ? 0.025 : 1;
+ }
+
+ renderSlowmoStatus(slowmoRemaining / slowmoDuration);
+
+ gameSpeed += (targetSpeed - gameSpeed) / 22 * lag;
+ gameSpeed = clamp(gameSpeed, 0, 1);
+
+ const centerX = width / 2;
+ const centerY = height / 2;
+
+ const simAirDrag = 1 - (airDrag * simSpeed);
+ const simAirDragSpark = 1 - (airDragSpark * simSpeed);
+
+ const forceMultiplier = 1 / (simSpeed * 0.75 + 0.25);
+ pointerDelta.x = 0;
+ pointerDelta.y = 0;
+ pointerDeltaScaled.x = 0;
+ pointerDeltaScaled.y = 0;
+ const lastPointer = touchPoints[touchPoints.length - 1];
+
+ if (pointerIsDown && lastPointer && !lastPointer.touchBreak) {
+ pointerDelta.x = (pointerScene.x - lastPointer.x);
+ pointerDelta.y = (pointerScene.y - lastPointer.y);
+ pointerDeltaScaled.x = pointerDelta.x * forceMultiplier;
+ pointerDeltaScaled.y = pointerDelta.y * forceMultiplier;
+ }
+ const pointerSpeed = Math.hypot(pointerDelta.x, pointerDelta.y);
+ const pointerSpeedScaled = pointerSpeed * forceMultiplier;
+
+ touchPoints.forEach(p => p.life -= simTime);
+
+ if (pointerIsDown) {
+ touchPoints.push({
+ x: pointerScene.x,
+ y: pointerScene.y,
+ life: touchPointLife
+ });
+ }
+
+ while (touchPoints[0] && touchPoints[0].life <= 0) {
+ touchPoints.shift();
+ }
+
+ PERF_START('entities');
+
+ spawnTime -= simTime;
+ if (spawnTime <= 0) {
+ if (spawnExtra > 0) {
+ spawnExtra--;
+ spawnTime = spawnExtraDelay;
+ } else {
+ spawnTime = getSpawnDelay();
+ }
+ const target = getTarget();
+ const spawnRadius = Math.min(centerX * 0.8, maxSpawnX);
+ target.x = (Math.random() * spawnRadius * 2 - spawnRadius);
+ target.y = centerY + targetHitRadius * 2;
+ target.z = (Math.random() * targetRadius*2 - targetRadius);
+ target.xD = Math.random() * (target.x * -2 / 120);
+ target.yD = -20;
+ targets.push(target);
+ }
+
+ const leftBound = -centerX + targetRadius;
+ const rightBound = centerX - targetRadius;
+ const ceiling = -centerY - 120;
+ const boundDamping = 0.4;
+
+ targetLoop:
+ for (let i = targets.length - 1; i >= 0; i--) {
+ const target = targets[i];
+ target.x += target.xD * simSpeed;
+ target.y += target.yD * simSpeed;
+
+ if (target.y < ceiling) {
+ target.y = ceiling;
+ target.yD = 0;
+ }
+
+ if (target.x < leftBound) {
+ target.x = leftBound;
+ target.xD *= -boundDamping;
+ } else if (target.x > rightBound) {
+ target.x = rightBound;
+ target.xD *= -boundDamping;
+ }
+
+ if (target.z < backboardZ) {
+ target.z = backboardZ;
+ target.zD *= -boundDamping;
+ }
+
+ target.yD += gravity * simSpeed;
+ target.rotateX += target.rotateXD * simSpeed;
+ target.rotateY += target.rotateYD * simSpeed;
+ target.rotateZ += target.rotateZD * simSpeed;
+ target.transform();
+ target.project();
+
+ if (target.y > centerY + targetHitRadius * 2) {
+ targets.splice(i, 1);
+ returnTarget(target);
+ if (isInGame()) {
+ if (isCasualGame()) {
+ incrementScore(-25);
+ } else {
+ endGame();
+ }
+ }
+ continue;
+ }
+
+ const hitTestCount = Math.ceil(pointerSpeed / targetRadius * 2);
+
+ for (let ii=1; ii<=hitTestCount; ii++) {
+ const percent = 1 - (ii / hitTestCount);
+ const hitX = pointerScene.x - pointerDelta.x * percent;
+ const hitY = pointerScene.y - pointerDelta.y * percent;
+ const distance = Math.hypot(
+ hitX - target.projected.x,
+ hitY - target.projected.y
+ );
+
+ if (distance <= targetHitRadius) {
+
+ if (!target.hit) {
+ target.hit = true;
+
+ target.xD += pointerDeltaScaled.x * hitDampening;
+ target.yD += pointerDeltaScaled.y * hitDampening;
+ target.rotateXD += pointerDeltaScaled.y * 0.001;
+ target.rotateYD += pointerDeltaScaled.x * 0.001;
+
+ const sparkSpeed = 7 + pointerSpeedScaled * 0.125;
+
+ if (pointerSpeedScaled > minPointerSpeed) {
+ target.health--;
+ incrementScore(10);
+
+ if (target.health <= 0) {
+ incrementCubeCount(1);
+ createBurst(target, forceMultiplier);
+ sparkBurst(hitX, hitY, 8, sparkSpeed);
+ if (target.wireframe) {
+ slowmoRemaining = slowmoDuration;
+ spawnTime = 0;
+ spawnExtra = 2;
+ }
+ targets.splice(i, 1);
+ returnTarget(target);
+ } else {
+ sparkBurst(hitX, hitY, 8, sparkSpeed);
+ glueShedSparks(target);
+ updateTargetHealth(target, 0);
+ }
+ } else {
+ incrementScore(5);
+ sparkBurst(hitX, hitY, 3, sparkSpeed);
+ }
+ }
+
+ continue targetLoop;
+ }
+ }
+
+ target.hit = false;
+ }
+
+ const fragBackboardZ = backboardZ + fragRadius;
+
+ const fragLeftBound = -width;
+ const fragRightBound = width;
+
+ for (let i = frags.length - 1; i >= 0; i--) {
+ const frag = frags[i];
+ frag.x += frag.xD * simSpeed;
+ frag.y += frag.yD * simSpeed;
+ frag.z += frag.zD * simSpeed;
+
+ frag.xD *= simAirDrag;
+ frag.yD *= simAirDrag;
+ frag.zD *= simAirDrag;
+
+ if (frag.y < ceiling) {
+ frag.y = ceiling;
+ frag.yD = 0;
+ }
+
+ if (frag.z < fragBackboardZ) {
+ frag.z = fragBackboardZ;
+ frag.zD *= -boundDamping;
+ }
+
+ frag.yD += gravity * simSpeed;
+ frag.rotateX += frag.rotateXD * simSpeed;
+ frag.rotateY += frag.rotateYD * simSpeed;
+ frag.rotateZ += frag.rotateZD * simSpeed;
+ frag.transform();
+ frag.project();
+
+ if (
+
+ frag.projected.y > centerY + targetHitRadius ||
+
+ frag.projected.x < fragLeftBound ||
+ frag.projected.x > fragRightBound ||
+
+ frag.z > cameraFadeEndZ
+ ) {
+ frags.splice(i, 1);
+ returnFrag(frag);
+ continue;
+ }
+ }
+
+ for (let i = sparks.length - 1; i >= 0; i--) {
+ const spark = sparks[i];
+ spark.life -= simTime;
+ if (spark.life <= 0) {
+ sparks.splice(i, 1);
+ returnSpark(spark);
+ continue;
+ }
+ spark.x += spark.xD * simSpeed;
+ spark.y += spark.yD * simSpeed;
+ spark.xD *= simAirDragSpark;
+ spark.yD *= simAirDragSpark;
+ spark.yD += gravity * simSpeed;
+ }
+
+ PERF_END('entities');
+
+ PERF_START('3D');
+
+ allVertices.length = 0;
+ allPolys.length = 0;
+ allShadowVertices.length = 0;
+ allShadowPolys.length = 0;
+ targets.forEach(entity => {
+ allVertices.push(...entity.vertices);
+ allPolys.push(...entity.polys);
+ allShadowVertices.push(...entity.shadowVertices);
+ allShadowPolys.push(...entity.shadowPolys);
+ });
+
+ frags.forEach(entity => {
+ allVertices.push(...entity.vertices);
+ allPolys.push(...entity.polys);
+ allShadowVertices.push(...entity.shadowVertices);
+ allShadowPolys.push(...entity.shadowPolys);
+ });
+
+ allPolys.forEach(p => computePolyNormal(p, 'normalWorld'));
+ allPolys.forEach(computePolyDepth);
+ allPolys.sort((a, b) => b.depth - a.depth);
+
+ allVertices.forEach(projectVertex);
+
+ allPolys.forEach(p => computePolyNormal(p, 'normalCamera'));
+
+ PERF_END('3D');
+
+ PERF_START('shadows');
+
+ transformVertices(
+ allShadowVertices,
+ allShadowVertices,
+ 0, 0, 0,
+ TAU/8, 0, 0,
+ 1, 1, 1
+ );
+
+ allShadowPolys.forEach(p => computePolyNormal(p, 'normalWorld'));
+
+ const shadowDistanceMult = Math.hypot(1, 1);
+ const shadowVerticesLength = allShadowVertices.length;
+ for (let i=0; i {
+ if (p.wireframe) {
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ const { vertices } = p;
+ const vCount = vertices.length;
+ const firstV = vertices[0];
+ ctx.moveTo(firstV.x, firstV.y);
+ for (let i=1; i {
+ if (!p.wireframe && p.normalCamera.z < 0) return;
+
+ if (p.strokeWidth !== 0) {
+ ctx.lineWidth = p.normalCamera.z < 0 ? p.strokeWidth * 0.5 : p.strokeWidth;
+ ctx.strokeStyle = p.normalCamera.z < 0 ? p.strokeColorDark : p.strokeColor;
+ }
+
+ const { vertices } = p;
+ const lastV = vertices[vertices.length - 1];
+ const fadeOut = p.middle.z > cameraFadeStartZ;
+
+ if (!p.wireframe) {
+ const normalLight = p.normalWorld.y * 0.5 + p.normalWorld.z * -0.5;
+ const lightness = normalLight > 0
+ ? 0.1
+ : ((normalLight ** 32 - normalLight) / 2) * 0.9 + 0.1;
+ ctx.fillStyle = shadeColor(p.color, lightness);
+ }
+
+ if (fadeOut) {
+
+ ctx.globalAlpha = Math.max(0, 1 - (p.middle.z - cameraFadeStartZ) / cameraFadeRange);
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(lastV.x, lastV.y);
+ for (let v of vertices) {
+ ctx.lineTo(v.x, v.y);
+ }
+
+ if (!p.wireframe) {
+ ctx.fill();
+ }
+ if (p.strokeWidth !== 0) {
+ ctx.stroke();
+ }
+
+ if (fadeOut) {
+ ctx.globalAlpha = 1;
+ }
+ });
+ PERF_END('drawPolys');
+
+ PERF_START('draw2D');
+
+ ctx.strokeStyle = sparkColor;
+ ctx.lineWidth = sparkThickness;
+ ctx.beginPath();
+ sparks.forEach(spark => {
+ ctx.moveTo(spark.x, spark.y);
+
+ const scale = (spark.life / spark.maxLife) ** 0.5 * 1.5;
+ ctx.lineTo(spark.x - spark.xD*scale, spark.y - spark.yD*scale);
+
+ });
+ ctx.stroke();
+
+ ctx.strokeStyle = touchTrailColor;
+ const touchPointCount = touchPoints.length;
+ for (let i=1; i 68) {
+ frameTime = 68;
+ }
+
+ const halfW = width / 2;
+ const halfH = height / 2;
+
+ pointerScene.x = pointerScreen.x / viewScale - halfW;
+ pointerScene.y = pointerScreen.y / viewScale - halfH;
+
+ const lag = frameTime / 16.6667;
+ const simTime = gameSpeed * frameTime;
+ const simSpeed = gameSpeed * lag;
+ tick(width, height, simTime, simSpeed, lag);
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ const drawScale = dpr * viewScale;
+ ctx.scale(drawScale, drawScale);
+ ctx.translate(halfW, halfH);
+ draw(ctx, width, height, viewScale);
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ }
+ const raf = () => requestAnimationFrame(frameHandler);
+
+ raf();
+}
+
+function handleCanvasPointerDown(x, y) {
+ if (!pointerIsDown) {
+ pointerIsDown = true;
+ pointerScreen.x = x;
+ pointerScreen.y = y;
+
+ if (isMenuVisible()) renderMenus();
+ }
+}
+
+function handleCanvasPointerUp() {
+ if (pointerIsDown) {
+ pointerIsDown = false;
+ touchPoints.push({
+ touchBreak: true,
+ life: touchPointLife
+ });
+ if (isMenuVisible()) renderMenus();
+ }
+}
+function handleCanvasPointerMove(x, y) {
+ if (pointerIsDown) {
+ pointerScreen.x = x;
+ pointerScreen.y = y;
+ }
+}
+
+if ('PointerEvent' in window) {
+ canvas.addEventListener('pointerdown', event => {
+ event.isPrimary && handleCanvasPointerDown(event.clientX, event.clientY);
+ });
+
+ canvas.addEventListener('pointerup', event => {
+ event.isPrimary && handleCanvasPointerUp();
+ });
+
+ canvas.addEventListener('pointermove', event => {
+ event.isPrimary && handleCanvasPointerMove(event.clientX, event.clientY);
+ });
+
+ document.body.addEventListener('mouseleave', handleCanvasPointerUp);
+} else {
+ let activeTouchId = null;
+ canvas.addEventListener('touchstart', event => {
+ if (!pointerIsDown) {
+ const touch = event.changedTouches[0];
+ activeTouchId = touch.identifier;
+ handleCanvasPointerDown(touch.clientX, touch.clientY);
+ }
+ });
+ canvas.addEventListener('touchend', event => {
+ for (let touch of event.changedTouches) {
+ if (touch.identifier === activeTouchId) {
+ handleCanvasPointerUp();
+ break;
+ }
+ }
+ });
+ canvas.addEventListener('touchmove', event => {
+ for (let touch of event.changedTouches) {
+ if (touch.identifier === activeTouchId) {
+ handleCanvasPointerMove(touch.clientX, touch.clientY);
+ event.preventDefault();
+ break;
+ }
+ }
+ }, { passive: false });
+}
+
+setupCanvases();
\ No newline at end of file
diff --git a/Games/Menja_block_breaker/style.css b/Games/Menja_block_breaker/style.css
new file mode 100644
index 0000000000..87ffba7281
--- /dev/null
+++ b/Games/Menja_block_breaker/style.css
@@ -0,0 +1,242 @@
+body {
+ margin: 0;
+ background-color: #000;
+ background-image: radial-gradient(ellipse at top, #335476 0.0%, #31506e 11.1%, #304b67 22.2%, #2f4760 33.3%, #2d4359 44.4%, #2c3f51 55.6%, #2a3a4a 66.7%, #293643 77.8%, #28323d 88.9%, #262e36 100.0%);
+ height: 100vh;
+ overflow: hidden;
+
+ font-family: monospace;
+ font-weight: bold;
+ letter-spacing: 0.06em;
+ color: rgba(255, 255, 255, 0.75);
+}
+
+#c {
+ display: block;
+ touch-action: none;
+ transform: translateZ(0);
+}
+
+.hud__score,
+.pause-btn {
+ position: fixed;
+ font-size: calc(14px + 2vw + 1vh);
+}
+
+.hud__score {
+ top: 0.65em;
+ left: 0.65em;
+ pointer-events: none;
+ user-select: none;
+}
+
+.cube-count-lbl {
+ font-size: 0.46em;
+}
+
+.pause-btn {
+ position: fixed;
+ top: 0;
+ right: 0;
+ padding: 0.8em 0.65em;
+}
+
+.pause-btn > div {
+ position: relative;
+ width: 0.8em;
+ height: 0.8em;
+ opacity: 0.75;
+}
+
+.pause-btn > div::before,
+.pause-btn > div::after {
+ content: '';
+ display: block;
+ width: 34%;
+ height: 100%;
+ position: absolute;
+ background-color: #fff;
+}
+
+.pause-btn > div::after {
+ right: 0;
+}
+
+.slowmo {
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.4s;
+ will-change: opacity;
+}
+
+.slowmo::before {
+ content: 'SLOW-MO';
+ display: block;
+ font-size: calc(8px + 1vw + 0.5vh);
+ margin-left: 0.5em;
+ margin-bottom: 8px;
+}
+
+.slowmo::after {
+ content: '';
+ display: block;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ height: 1.5vh;
+ background-color: rgba(0, 0, 0, 0.25);
+ z-index: -1;
+}
+
+.slowmo__bar {
+ height: 1.5vh;
+ background-color: rgba(255, 255, 255, 0.75);
+ transform-origin: 0 0;
+}
+.menus::before {
+ content: '';
+ pointer-events: none;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: #000;
+ opacity: 0;
+ transition: opacity 0.2s;
+ transition-timing-function: ease-in;
+}
+
+.menus.has-active::before {
+ opacity: 0.08;
+ transition-duration: 0.4s;
+ transition-timing-function: ease-out;
+}
+
+.menus.interactive-mode::before {
+ opacity: 0.02;
+}
+.menu {
+ pointer-events: none;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ user-select: none;
+ text-align: center;
+ color: rgba(255, 255, 255, 0.9);
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(30px);
+ transition-property: opacity, visibility, transform;
+ transition-duration: 0.2s;
+ transition-timing-function: ease-in;
+}
+
+.menu.active {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+ transition-duration: 0.4s;
+ transition-timing-function: ease-out;
+}
+
+.menus.interactive-mode .menu.active {
+ opacity: 0.6;
+}
+
+.menus:not(.interactive-mode) .menu.active > * {
+ pointer-events: auto;
+}
+h1 {
+ font-size: 4rem;
+ line-height: 0.95;
+ text-align: center;
+ font-weight: bold;
+ margin: 0 0.65em 1em;
+}
+
+h2 {
+ font-size: 1.2rem;
+ line-height: 1;
+ text-align: center;
+ font-weight: bold;
+ margin: -1em 0.65em 1em;
+}
+
+.final-score-lbl {
+ font-size: 5rem;
+ margin: -0.2em 0 0;
+}
+
+.high-score-lbl {
+ font-size: 1.2rem;
+ margin: 0 0 2.5em;
+}
+
+button {
+ display: block;
+ position: relative;
+ width: 200px;
+ padding: 12px 20px;
+ background: transparent;
+ border: none;
+ outline: none;
+ user-select: none;
+ font-family: monospace;
+ font-weight: bold;
+ font-size: 1.4rem;
+ color: #fff;
+ opacity: 0.75;
+ transition: opacity 0.3s;
+}
+
+button::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: rgba(255, 255, 255, 0.15);
+ transform: scale(0, 0);
+ opacity: 0;
+ transition: opacity 0.3s, transform 0.3s;
+}
+button:active {
+ opacity: 1;
+}
+
+button:active::before {
+ transform: scale(1, 1);
+ opacity: 1;
+}
+
+.credits {
+ position: fixed;
+ width: 100%;
+ left: 0;
+ bottom: 20px;
+}
+
+a {
+ color: white;
+}
+@media (min-width: 1025px) {
+ button:hover {
+ opacity: 1;
+ }
+
+ button:hover::before {
+ transform: scale(1, 1);
+ opacity: 1;
+ }
+}
diff --git a/README.md b/README.md
index 37420ce1b6..a620fb3d90 100644
--- a/README.md
+++ b/README.md
@@ -120,6 +120,7 @@ This repository also provides one such platforms where contributers come over an
| [Word Scramble Game](https://github.com/kunjgit/GameZone/tree/main/Games/Word_Scramble_Game) | [Tetris](https://github.com/kunjgit/GameZone/tree/main/Games/Tetris) | [Interactive Quizzing Application](https://github.com/kunjgit/GameZone/tree/main/Games/Interactive_Quizzing) | [Planet Defense Game](https://github.com/kunjgit/GameZone/tree/main/Games/Planet_Defense) | [Rabbit Rush Game](https://github.com/kunjgit/GameZone/tree/main/Games/Rabbit_Rush) |
| [Wordle](https://github.com/kunjgit/GameZone/tree/main/Games/Wordle) | [Roll Race Game](https://github.com/kunjgit/GameZone/tree/main/Games/Roll_Race) | [Menja Game](https://github.com/kunjgit/GameZone/tree/main/Games/Menja) | [Typing Speed Test Game](https://github.com/kunjgit/GameZone/tree/main/Games/Typing_Speed_Test_Game) | [Tile Game](https://github.com/kunjgit/GameZone/tree/main/Games/Tile_Game) |
| [Stick Hero Game](https://github.com/kunjgit/GameZone/tree/main/Games/Stick_Hero_Game) | [Starwars Character Game](https://github.com/kunjgit/GameZone/tree/main/Games/Starwars_Character_Game) | [Traffic Run](https://github.com/kunjgit/GameZone/tree/main/Games/Traffic_Run) | [Love Result Predictor](https://github.com/kunjgit/GameZone/tree/main/Games/Love_Result_Predictor) | [Tower Defense](https://github.com/kunjgit/GameZone/tree/main/Games/Tower_Defense) |
+[Menja_block_breaker](https://github.com/kunjgit/GameZone/tree/main/Games/Menja_block_breaker) |
| [Bird Game](https://github.com/kunjgit/GameZone/tree/main/Games/Bird_game) | [Bubble Blast Game](https://github.com/kunjgit/GameZone/tree/main/Games/Bubble_Blast_Game) | [Emoji Charades](https://github.com/kunjgit/GameZone/tree/main/Games/Emoji_Charades) | [Drum And Kit](https://github.com/kunjgit/GameZone/tree/main/Games/Drum_Kit_Game) | [Rock Paper Scissors](https://github.com/kunjgit/GameZone/tree/main/Games/Rock_Paper_Scissors) |
| [Frogger](https://github.com/kunjgit/GameZone/tree/main/Games/Frogger) | [!morethan5 ](https://github.com/kunjgit/GameZone/tree/main/Games/Not_morethan5) | [Unruly Tower](https://github.com/kunjgit/GameZone/tree/main/Games/Unruly_Tower) | [Maze Game](https://github.com/kunjgit/GameZone/tree/main/Games/MazeGame) | [Connect4](https://github.com/kunjgit/GameZone/tree/main/Games/Connect4) |
| [Spelling_Bee](https://github.com/kunjgit/GameZone/tree/main/Games/Spelling_Bee) | [2048](https://github.com/kunjgit/GameZone/tree/main/Games/2048) | [Spin the Wheel](https://github.com/kunjgit/GameZone/tree/main/Games/Spin_the_wheel) | [Breakout](https://github.com/kunjgit/GameZone/tree/main/Games/Breakout) | [Tower Blocks](https://github.com/kunjgit/GameZone/tree/main/Games/Tower_Blocks) |
@@ -280,6 +281,9 @@ This repository also provides one such platforms where contributers come over an
| [Guess_num](https://github.com/kunjgit/GameZone/tree/main/Games/Guess_num) |
| [QuickFingers](https://github.com/kunjgit/GameZone/tree/main/Games/QuickFingers) |
| [Tiny_Fishing](https://github.com/kunjgit/GameZone/tree/main/Games/Tiny_Fishing) |
+
+| [Hover_Board_Effect](https://github.com/kunjgit/GameZone/tree/main/Games/Hover_Board_Effect) |
+
| [namefate](https://github.com/kunjgit/GameZone/tree/main/Games/namefate) |
| [Fruit_Catching_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Fruit_Catching_Game) |
| [color_matching_application](https://github.com/kunjgit/GameZone/tree/main/Games/color_matching_application) |
diff --git a/assets/images/Menja.png b/assets/images/Menja.png
deleted file mode 100644
index 0a3d5f243a..0000000000
Binary files a/assets/images/Menja.png and /dev/null differ
diff --git a/assets/images/menja_Block_breaker.png b/assets/images/menja_Block_breaker.png
new file mode 100644
index 0000000000..24f12b5b35
Binary files /dev/null and b/assets/images/menja_Block_breaker.png differ
diff --git a/assets/js/gamesData.json b/assets/js/gamesData.json
index 4804631e5d..fb0bf8c94e 100644
--- a/assets/js/gamesData.json
+++ b/assets/js/gamesData.json
@@ -2025,6 +2025,11 @@
"thumbnailUrl": "namefate.png"
}
,
+"500":{
+ "gameTitle": "Menja block breaker",
+ "gameUrl": "Menja_block_breaker",
+ "thumbnailUrl": "menja_Block_breaker.png"
+},
"393":{
"gameTitle": "Pop My Balloon",
@@ -2044,16 +2049,25 @@
"gameUrl": "Virtual_Pet_Game",
"thumbnailUrl": "Virtual_Pet_Game.png"
+ "gameTitle": "path finder puzzle",
+ "gameUrl": "path_finder",
+ "thumbnailUrl": "pathfinder.png"
},
"411":{ "gameTitle": "Tiny Fishing",
"gameUrl": "Tiny_Fishing",
"thumbnailUrl": "Tiny_Fishing.png"
},
"398":{
+"410":{
"gameTitle": "Shrek Vs Wild",
"gameUrl": "Shrek_Vs_Wild",
"thumbnailUrl": "Shrek_Vs_Wild.png"
},
+"409":{
+ "gameTitle": "Hover_Board_Effect",
+ "gameUrl": "Hover_Board_Effect",
+ "thumbnailUrl": "Hover_Board_Effect.png"
+},
"405":{
"gameTitle": "Candy_Crush_Saga",
"gameUrl": "Candy_Crush_Saga",