diff --git a/WhackABug/unitybtw/css/main.css b/WhackABug/unitybtw/css/main.css new file mode 100644 index 000000000..caf97ba69 --- /dev/null +++ b/WhackABug/unitybtw/css/main.css @@ -0,0 +1,241 @@ +/* css/main.css */ + +/* Basic Reset */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background: var(--color-bg-base); + color: var(--color-text-primary); + font-family: var(--font-sans); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Typography */ +.highlight { + color: var(--color-highlight); +} + +.header { + text-align: center; + margin-bottom: var(--spacing-lg); +} + +.header__title { + font-size: 2rem; + font-weight: 700; + letter-spacing: -0.03em; + margin-bottom: var(--spacing-sm); +} + +.header__subtitle { + color: var(--color-text-secondary); + font-size: 0.95rem; +} + +/* Layout */ +.app-container { + display: flex; + flex-direction: column; + align-items: center; + padding: var(--spacing-md); + width: 100%; + max-width: 500px; +} + +.game-board { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-lg); + width: 100%; +} + +/* Clean Panels */ +.glass-panel { + background: var(--color-bg-panel); + border: 1px solid var(--color-border-panel); + border-radius: var(--border-radius-md); + /* Removed all blur and glassmorphism. Flat, clean look instead. */ +} + +/* Stats */ +.stats-panel { + display: flex; + justify-content: space-between; + width: 100%; + padding: var(--spacing-md) var(--spacing-lg); +} + +.stat { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.stat__label { + font-size: 0.75rem; + font-weight: 500; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 4px; +} + +.stat__value { + font-family: var(--font-mono); + font-size: 1.5rem; + font-weight: 600; + color: var(--color-text-primary); +} + +/* Grid & Holes */ +.grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--grid-gap); + padding: var(--spacing-lg); +} + +.hole { + width: var(--hole-size); + height: var(--hole-size); + background: var(--color-hole-bg); + border: 1px solid var(--color-hole-border); + border-radius: var(--border-radius-sm); /* Square with slightly rounded corners */ + box-shadow: inset 0 4px 10px var(--color-hole-shadow); + position: relative; + overflow: hidden; + cursor: pointer; +} + +/* Focus rings for accessibility */ +.hole:focus-visible { + outline: 2px solid var(--color-highlight); + outline-offset: 2px; +} + +/* The Bug (Pure CSS Art - Clean Vector Style) */ +.bug { + width: 60%; + height: 60%; + background: var(--color-bug); + border-radius: 4px; + position: absolute; + bottom: -100%; + left: 20%; + transition: bottom 0.15s cubic-bezier(0.2, 0, 0, 1); + pointer-events: none; /* Clicking passes through to the hole */ +} + +/* Flat Bug details */ +.bug::before, .bug::after { + content: ''; + position: absolute; + width: 12px; + height: 12px; + background: #ffffff; + border-radius: 50%; + top: 20%; +} + +.bug::before { left: 15%; } +.bug::after { right: 15%; } + +/* Animation States */ +.hole--active .bug { + bottom: 10%; +} + +.hole--whacked .bug { + background: var(--color-text-secondary); + transform: scaleY(0.1); + transform-origin: bottom; + transition: transform 0.05s ease-out, background 0.05s; +} + +/* Buttons */ +.btn { + font-family: var(--font-sans); + font-weight: 500; + font-size: 0.95rem; + padding: 10px 24px; + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease; + border: 1px solid transparent; +} + +.btn:focus-visible { + outline: 2px solid var(--color-highlight); + outline-offset: 2px; +} + +.btn--primary { + background: var(--color-btn-primary); + color: var(--color-btn-primary-text); + width: 100%; +} + +.btn--primary:hover { + background: var(--color-btn-primary-hover); +} + +.btn--secondary { + background: var(--color-btn-secondary); + color: var(--color-text-primary); + border: 1px solid var(--color-btn-secondary-border); +} + +.btn--secondary:hover { + background: var(--color-btn-secondary-hover); +} + +/* Modal */ +.modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.95); + padding: var(--spacing-lg); + text-align: center; + border: 1px solid var(--color-border-panel); + border-radius: var(--border-radius-md); + background: var(--color-bg-panel); + color: var(--color-text-primary); + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease, transform 0.2s ease; + box-shadow: 0 20px 40px rgba(0,0,0,0.5); +} + +.modal[open] { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + pointer-events: all; +} + +.modal::backdrop { + background: rgba(0, 0, 0, 0.7); +} + +.modal__title { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: var(--spacing-sm); +} + +.modal__text { + font-size: 1rem; + color: var(--color-text-secondary); + margin-bottom: var(--spacing-lg); +} diff --git a/WhackABug/unitybtw/css/variables.css b/WhackABug/unitybtw/css/variables.css new file mode 100644 index 000000000..0513fb42c --- /dev/null +++ b/WhackABug/unitybtw/css/variables.css @@ -0,0 +1,48 @@ +/* css/variables.css */ +:root { + /* Minimalist Professional Dark Theme (Vercel/GitHub inspired) */ + --color-bg-base: #0a0a0a; + --color-bg-panel: #111111; + --color-border-panel: #333333; + + --color-text-primary: #ededed; + --color-text-secondary: #a1a1aa; + + --color-highlight: #0070f3; /* Clean tech blue */ + --color-bug: #e00000; /* High contrast red for bugs */ + + --color-hole-bg: #1a1a1a; + --color-hole-border: #222222; + --color-hole-shadow: #000000; + + /* Buttons */ + --color-btn-primary: #ededed; + --color-btn-primary-hover: #ffffff; + --color-btn-primary-text: #0a0a0a; + + --color-btn-secondary: #111111; + --color-btn-secondary-border: #333333; + --color-btn-secondary-hover: #222222; + + /* Typography */ + --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + + /* Spacing & Sizes */ + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 32px; + --border-radius-sm: 6px; + --border-radius-md: 12px; + + /* Grid & Game sizes */ + --grid-gap: 24px; + --hole-size: 100px; +} + +@media (max-width: 600px) { + :root { + --hole-size: 80px; + --grid-gap: 16px; + } +} diff --git a/WhackABug/unitybtw/index.html b/WhackABug/unitybtw/index.html new file mode 100644 index 000000000..effac852d --- /dev/null +++ b/WhackABug/unitybtw/index.html @@ -0,0 +1,80 @@ + + + + + + Whack-A-Bug | Developer Edition + + + + + + + + +
+
+

WHACK-A-BUG

+

Smash the bugs before they reach production!

+
+ +
+
+
+ SCORE + 0 +
+
+ TIME + 30 +
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ + + + + + + +
+ + + + + diff --git a/WhackABug/unitybtw/js/Game.js b/WhackABug/unitybtw/js/Game.js new file mode 100644 index 000000000..e81e8c6e5 --- /dev/null +++ b/WhackABug/unitybtw/js/Game.js @@ -0,0 +1,103 @@ +// js/Game.js + +export class Game { + constructor(duration = 30) { + this.initialDuration = duration; + this.score = 0; + this.timeLeft = duration; + this.isPlaying = false; + this.activeHoleIndex = null; + this.timerId = null; + this.moleTimerId = null; + + // Callbacks to be hooked by UI + this.onScoreUpdate = () => {}; + this.onTimeUpdate = () => {}; + this.onMoleSpawn = () => {}; + this.onMoleHide = () => {}; + this.onGameOver = () => {}; + } + + start() { + this.score = 0; + this.timeLeft = this.initialDuration; + this.isPlaying = true; + + this.onScoreUpdate(this.score); + this.onTimeUpdate(this.timeLeft); + + this.tick(); + this.spawnMole(); + } + + stop() { + this.isPlaying = false; + clearTimeout(this.timerId); + clearTimeout(this.moleTimerId); + if (this.activeHoleIndex !== null) { + this.onMoleHide(this.activeHoleIndex); + this.activeHoleIndex = null; + } + this.onGameOver(this.score); + } + + tick() { + this.timerId = setTimeout(() => { + this.timeLeft--; + this.onTimeUpdate(this.timeLeft); + + if (this.timeLeft <= 0) { + this.stop(); + } else { + this.tick(); + } + }, 1000); + } + + spawnMole() { + if (!this.isPlaying) return; + + // Hide previous mole if any + if (this.activeHoleIndex !== null) { + this.onMoleHide(this.activeHoleIndex); + } + + // Pick a random hole different from the last one + let newHole; + do { + newHole = Math.floor(Math.random() * 9); + } while (newHole === this.activeHoleIndex); + + this.activeHoleIndex = newHole; + this.onMoleSpawn(this.activeHoleIndex); + + // Random time between 500ms and 1500ms + const timeToWait = Math.random() * 1000 + 500; + + this.moleTimerId = setTimeout(() => { + this.spawnMole(); + }, timeToWait); + } + + whack(holeIndex) { + if (!this.isPlaying) return false; + + if (holeIndex === this.activeHoleIndex) { + this.score++; + this.onScoreUpdate(this.score); + + // Hide the mole immediately after whacking + this.onMoleHide(this.activeHoleIndex); + this.activeHoleIndex = null; + + // Clear current mole timer and spawn a new one faster + clearTimeout(this.moleTimerId); + this.moleTimerId = setTimeout(() => { + this.spawnMole(); + }, 300); + + return true; // Whack successful + } + return false; // Whack missed + } +} diff --git a/WhackABug/unitybtw/js/UI.js b/WhackABug/unitybtw/js/UI.js new file mode 100644 index 000000000..be6d62c9d --- /dev/null +++ b/WhackABug/unitybtw/js/UI.js @@ -0,0 +1,76 @@ +// js/UI.js + +export class UI { + constructor(gameInstance) { + this.game = gameInstance; + + // DOM Elements + this.scoreElement = document.getElementById('score'); + this.timeElement = document.getElementById('time'); + this.startBtn = document.getElementById('start-btn'); + this.holes = document.querySelectorAll('.hole'); + this.modal = document.getElementById('game-over-modal'); + this.finalScoreElement = document.getElementById('final-score'); + this.restartBtn = document.getElementById('restart-btn'); + + this.bindEvents(); + this.bindGameHooks(); + } + + bindEvents() { + this.startBtn.addEventListener('click', () => { + this.game.start(); + this.startBtn.disabled = true; + this.startBtn.textContent = 'DEBUGGING...'; + }); + + this.restartBtn.addEventListener('click', () => { + this.modal.removeAttribute('open'); + this.game.start(); + }); + + this.holes.forEach((hole) => { + hole.addEventListener('click', (e) => { + // Prevent cheating by triggering click events via JS without user interaction + if (!e.isTrusted) return; + + const index = parseInt(hole.dataset.index, 10); + const isWhacked = this.game.whack(index); + + if (isWhacked) { + hole.classList.add('hole--whacked'); + // Provide sound feedback if desired here + setTimeout(() => { + hole.classList.remove('hole--whacked'); + }, 150); // Fast animation duration + } + }); + }); + } + + bindGameHooks() { + this.game.onScoreUpdate = (score) => { + this.scoreElement.textContent = score; + }; + + this.game.onTimeUpdate = (time) => { + this.timeElement.textContent = time; + }; + + this.game.onMoleSpawn = (index) => { + this.holes[index].classList.add('hole--active'); + }; + + this.game.onMoleHide = (index) => { + this.holes[index].classList.remove('hole--active'); + this.holes[index].classList.remove('hole--whacked'); + }; + + this.game.onGameOver = (finalScore) => { + this.startBtn.disabled = false; + this.startBtn.textContent = 'START DEBUGGING'; + this.finalScoreElement.textContent = finalScore; + this.modal.setAttribute('open', ''); + }; + } +} diff --git a/WhackABug/unitybtw/js/app.js b/WhackABug/unitybtw/js/app.js new file mode 100644 index 000000000..7f4e55ab3 --- /dev/null +++ b/WhackABug/unitybtw/js/app.js @@ -0,0 +1,11 @@ +// js/app.js +import { Game } from './Game.js'; +import { UI } from './UI.js'; + +document.addEventListener('DOMContentLoaded', () => { + // Initialize game with a 30-second duration + const game = new Game(30); + + // Bind UI to the game instance + const ui = new UI(game); +});