From 6df27b58bc1b0241a2ceaf0ba836c30084546699 Mon Sep 17 00:00:00 2001 From: Akshat Date: Fri, 31 Oct 2025 21:22:04 +0530 Subject: [PATCH] Added bomb --- src/data/content.js | 9 +- src/pages/games/DontClickBomb.js | 134 ++++++++++++++++++ src/styles/pages/games/DontClickBomb.css | 173 +++++++++++++++++++++++ 3 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 src/pages/games/DontClickBomb.js create mode 100644 src/styles/pages/games/DontClickBomb.css diff --git a/src/data/content.js b/src/data/content.js index 1652e19..2e4a4e2 100644 --- a/src/data/content.js +++ b/src/data/content.js @@ -28,7 +28,7 @@ import { CatHttpCode } from "../pages/activities/CatHttpCode"; import FlappyBird from "../pages/games/FlappyBird"; import RandomIdentity from "../pages/activities/RandomIdentity"; import word_scramble_icon from "../assets/games/WordScramble/word_scramble.png"; - +import DontClickBomb from "../pages/games/DontClickBomb"; export const activities = [ { title: "Random quotes", @@ -179,5 +179,12 @@ export const games = [ icon: word_scramble_icon, urlTerm: "WordScramble", element: , + }, + { + title: "Don't Click the Bomb", + description: "Click everything except ๐Ÿ’ฃ. Gets harder every round.", + icon: "https://img.freepik.com/free-vector/round-black-bomb-realistic-style_52683-15190.jpg?semt=ais_hybrid&w=740&q=80", + urlTerm: "dont-click-the-bomb", + element: , } ]; \ No newline at end of file diff --git a/src/pages/games/DontClickBomb.js b/src/pages/games/DontClickBomb.js new file mode 100644 index 0000000..1336f74 --- /dev/null +++ b/src/pages/games/DontClickBomb.js @@ -0,0 +1,134 @@ +import React, { useEffect, useMemo, useState } from "react"; +import "../../styles/pages/games/DontClickBomb.css"; + +const SAFE_EMOJIS = ["๐Ÿ˜€", "๐Ÿ˜Ž", "๐Ÿ˜บ", "๐ŸฆŠ", "๐Ÿถ", "๐Ÿป", "๐Ÿ”", "๐Ÿฉ", "๐Ÿ“", "๐ŸŒธ", "โญ", "๐Ÿ’Ž"]; + +export default function DontClickBomb() { + const [level, setLevel] = useState(1); + const [score, setScore] = useState(0); + const [gameOver, setGameOver] = useState(false); + const [timeLeft, setTimeLeft] = useState(2.5); // seconds per round, will shrink + const [tick, setTick] = useState(0); // to re-gen grid when timer refreshes + + // grid scales with level + const gridSize = Math.min(3 + Math.floor(level / 1.3), 8); // 3..8 + const totalCells = gridSize * gridSize; + + // number of bombs grows + const numBombs = Math.min(Math.floor(level * 0.7) + 1, 10); + + + // choose unique bomb positions + const bombIndexes = useMemo(() => { + const set = new Set(); + while (set.size < numBombs) { + set.add(Math.floor(Math.random() * totalCells)); + } + return Array.from(set); + }, [level, totalCells, numBombs, tick]); + + // timer logic: every level you get slightly less time + useEffect(() => { + if (gameOver) return; + setTimeLeft(Math.max(0.9, 2.6 - level * 0.1)); // min ~0.9s + }, [level, gameOver]); + + useEffect(() => { + if (gameOver) return; + const interval = setInterval(() => { + setTimeLeft((t) => { + if (t <= 0.1) { + // time up -> lose + setGameOver(true); + return 0; + } + return Number((t - 0.1).toFixed(1)); + }); + }, 100); + return () => clearInterval(interval); + }, [gameOver]); + + function handleCellClick(idx, isBomb) { + if (gameOver) return; + if (isBomb) { + setGameOver(true); + return; + } + // safe + setScore((s) => s + 10 + level * 2); + setLevel((l) => l + 1); + // refresh grid + setTick((t) => t + 1); + } + + function handleRestart() { + setLevel(1); + setScore(0); + setGameOver(false); + setTimeLeft(2.5); + setTick((t) => t + 1); + } + + return ( +
+
+

Don't Click the Bomb ๐Ÿ’ฃ

+

Click any safe emoji before the timer runs out. More levels = more bombs.

+
+ +
+
+ Level + {level} +
+
+ Score + {score} +
+
+ โฑ {timeLeft.toFixed(1)}s +
+ {gameOver && ( +
๐Ÿ’ฅ Boom! Too slow / bomb clicked.
+ )} +
+ +
+ {Array.from({ length: totalCells }).map((_, idx) => { + const isBomb = bombIndexes.includes(idx); + const emoji = isBomb + ? "๐Ÿ’ฃ" + : SAFE_EMOJIS[Math.floor(Math.random() * SAFE_EMOJIS.length)]; + + return ( + + ); + })} +
+ +
+ +
+ +

+ From level 4: 2 bombs ๐Ÿงจ. From level 8: 3 bombs ๐Ÿ˜ˆ. Timer also gets shorter. +

+
+ ); +} diff --git a/src/styles/pages/games/DontClickBomb.css b/src/styles/pages/games/DontClickBomb.css new file mode 100644 index 0000000..dad509c --- /dev/null +++ b/src/styles/pages/games/DontClickBomb.css @@ -0,0 +1,173 @@ +.bomb-wrapper { + max-width: 700px; + margin: 2rem auto; + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 16px; + padding: 1.6rem 1.4rem 1.4rem; + box-shadow: 0 10px 25px rgba(15, 23, 42, 0.05); + text-align: center; + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.bomb-header h2 { + font-size: 1.6rem; + font-weight: 700; + color: #0f172a; + margin-bottom: 0.4rem; +} + +.bomb-header p { + color: #64748b; + font-size: 0.9rem; + margin-bottom: 1.1rem; +} + +.bomb-stats { + display: flex; + justify-content: center; + gap: 1rem; + flex-wrap: wrap; + margin-bottom: 1rem; + align-items: center; +} + +.bomb-stat { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 0.5rem 1.1rem; + min-width: 90px; +} + +.bomb-stat .label { + font-size: 0.65rem; + text-transform: uppercase; + color: #94a3b8; + letter-spacing: 0.04em; +} + +.bomb-stat .value { + font-size: 1.1rem; + font-weight: 600; + color: #0f172a; +} + +.bomb-over { + background: #fee2e2; + color: #b91c1c; + padding: 0.35rem 0.7rem; + border-radius: 999px; + font-weight: 500; +} + +.bomb-grid { + display: grid; + gap: 0.6rem; + margin-top: 1.1rem; +} + +.bomb-cell { + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 12px; + height: 60px; + font-size: 1.7rem; + cursor: pointer; + transition: transform 0.1s ease, box-shadow 0.1s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.bomb-cell.is-safe:hover { + transform: translateY(-2px); + box-shadow: 0 6px 15px rgba(37, 99, 235, 0.12); +} + +.bomb-cell:disabled { + cursor: not-allowed; + opacity: 0.9; +} + +/* bomb click animation */ +.explode { + animation: bombBoom 0.4s ease forwards; +} + +@keyframes bombBoom { + 0% { + transform: scale(1); + background: #fee2e2; + } + 50% { + transform: scale(1.3); + background: #fca5a5; + } + 100% { + transform: scale(0.9); + background: #fee2e2; + } +} + +.bomb-actions { + margin-top: 1.2rem; +} + +.bomb-btn { + background: #2563eb; + color: #ffffff; + border: none; + border-radius: 10px; + padding: 0.5rem 1.2rem; + font-weight: 500; + cursor: pointer; + transition: background 0.15s ease; +} + +.bomb-btn:hover { + background: #1d4ed8; +} + +.bomb-hint { + margin-top: 0.85rem; + color: #94a3b8; + font-size: 0.78rem; +} + +@media (max-width: 520px) { + .bomb-grid { + grid-template-columns: repeat(auto-fit, minmax(55px, 1fr)) !important; + } + .bomb-cell { + height: 55px; + font-size: 1.4rem; + } +} +.bomb-wrapper.hard .bomb-grid { + margin-top: 1rem; +} + +.bomb-timer { + background: #eff6ff; + border: 1px solid #bfdbfe; + padding: 0.35rem 0.8rem; + border-radius: 999px; + font-weight: 500; + color: #1d4ed8; + display: flex; + align-items: center; + gap: 4px; +} + +.bomb-timer.low { + background: #fee2e2; + border-color: #fecaca; + color: #b91c1c; +} + +@media (max-width: 520px) { + .bomb-wrapper.hard .bomb-grid { + grid-template-columns: repeat(auto-fit, minmax(45px, 1fr)) !important; + } +}