diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 94ee1315..9d38efd5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -113,7 +113,7 @@ repos: hooks: - id: biome-check name: run biome-check - description: Run Biome linter and formatter for JSON files + description: Run Biome linter and formatter for JSON and JavaScript files additional_dependencies: ["@biomejs/biome"] - repo: https://github.com/igorshubovych/markdownlint-cli diff --git a/biome.json b/biome.json index 13c102cc..fa6a1cb9 100644 --- a/biome.json +++ b/biome.json @@ -1,7 +1,7 @@ { "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", "files": { - "includes": ["**/*.json"] + "includes": ["**/*.json", "src/assets/js/**/*.js"] }, "linter": { "enabled": true, diff --git a/src/assets/js/games/code-breaker.js b/src/assets/js/games/code-breaker.js index 7ec36720..b15eab0d 100644 --- a/src/assets/js/games/code-breaker.js +++ b/src/assets/js/games/code-breaker.js @@ -13,6 +13,7 @@ * Theme-aware: reads the current dark/light mode at startup. */ +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const CodeBreaker = (() => { const GAME_ID = "code-breaker"; const TILE_SPEED_BASE = 180; @@ -75,7 +76,7 @@ const CodeBreaker = (() => { }, scene: { create: function () { - _onSceneCreate(this, skills, devName, theme, W, H); + _onSceneCreate(this, skills, theme, W, H); }, update: function () { _onSceneUpdate(this); @@ -91,7 +92,7 @@ const CodeBreaker = (() => { // ─── Scene ─────────────────────────────────────────────────────────────── - function _onSceneCreate(scene, skills, devName, theme, W, H) { + function _onSceneCreate(scene, skills, theme, W, H) { // State stored on scene object scene.cb_lives = LIVES; scene.cb_score = 0; @@ -138,8 +139,8 @@ const CodeBreaker = (() => { scene.physics.add.overlap( scene.cb_catcher, scene.cb_tiles, - (catcher, tile) => { - _collectTile(scene, tile, theme, W, H); + (_catcher, tile) => { + _collectTile(scene, tile, W, H); }, ); @@ -163,7 +164,7 @@ const CodeBreaker = (() => { // Spawn first tile after a short delay scene.time.delayedCall(500, () => { - _spawnTile(scene, theme, W); + _spawnTile(scene, W); }); } @@ -184,7 +185,7 @@ const CodeBreaker = (() => { const now = scene.time.now; if (now - scene.cb_lastSpawn > scene.cb_spawnDelay) { scene.cb_lastSpawn = now; - _spawnTile(scene, getGameTheme(), scene.scale.width); + _spawnTile(scene, scene.scale.width); } // Check if any tile fell off the bottom. @@ -203,7 +204,7 @@ const CodeBreaker = (() => { // ─── Game logic ────────────────────────────────────────────────────────── - function _spawnTile(scene, theme, W) { + function _spawnTile(scene, W) { if (!scene.cb_active) return; const skill = @@ -264,8 +265,7 @@ const CodeBreaker = (() => { scene.cb_spawnDelay = Math.max(600, scene.cb_spawnDelay - 20); } - function _collectTile(scene, tile, theme, W, H) { - const skill = tile.getData("skill"); + function _collectTile(scene, tile, W, H) { const rarity = tile.getData("rarity"); const color = tile.getData("color"); @@ -277,7 +277,7 @@ const CodeBreaker = (() => { TILE_SPEED_BASE + scene.cb_score * TILE_SPEED_INC, ); - if (scene.cb_scoreText && scene.cb_scoreText.active) { + if (scene.cb_scoreText?.active) { scene.cb_scoreText.setText("Score: " + scene.cb_score); } @@ -316,7 +316,7 @@ const CodeBreaker = (() => { scene.cb_lives--; - if (scene.cb_livesText && scene.cb_livesText.active) { + if (scene.cb_livesText?.active) { scene.cb_livesText.setText("❤️".repeat(Math.max(0, scene.cb_lives))); } diff --git a/src/assets/js/games/config.js b/src/assets/js/games/config.js index 61fe8d55..ec6061eb 100644 --- a/src/assets/js/games/config.js +++ b/src/assets/js/games/config.js @@ -4,10 +4,12 @@ */ // CDN URL for the pinned Phaser version used site-wide +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const PHASER_CDN_URL = "https://cdnjs.cloudflare.com/ajax/libs/phaser/3.90.0/phaser.min.js"; // Rarity order and weights used by Dev Duel scoring +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const RARITY_WEIGHTS = { common: 1, uncommon: 2, @@ -19,6 +21,7 @@ const RARITY_WEIGHTS = { }; // CSS variable colour names per rarity (matches style.css) +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const RARITY_COLORS = { common: "#94a3b8", uncommon: "#10b981", @@ -30,6 +33,7 @@ const RARITY_COLORS = { }; // Fallback generic tech skills used when no profile skills are available +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const GENERIC_SKILLS = [ "JavaScript", "TypeScript", @@ -57,6 +61,7 @@ const GENERIC_SKILLS = [ * Reads the current site theme from the document root class. * Returns an object of useful colour strings for Phaser text/backgrounds. */ +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function getGameTheme() { const isDark = document.documentElement.classList.contains("dark"); return { @@ -73,6 +78,7 @@ function getGameTheme() { * Deterministically assigns a rarity tier to a skill name string. * Uses a simple hash so the same skill always gets the same rarity. */ +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function skillRarity(skillName) { let hash = 0; for (let i = 0; i < skillName.length; i++) { @@ -99,6 +105,7 @@ function skillRarity(skillName) { * * @param {object} overrides Fields that replace defaults (e.g. width, scene). */ +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function buildGameConfig(overrides) { return Object.assign( { diff --git a/src/assets/js/games/dev-duel.js b/src/assets/js/games/dev-duel.js index 8bc523e1..88230df1 100644 --- a/src/assets/js/games/dev-duel.js +++ b/src/assets/js/games/dev-duel.js @@ -90,8 +90,8 @@ const DevDuel = (() => { .setOrigin(0.5, 0); // Cards - _drawCard(scene, challenger, cPower, W * 0.22, H / 2 - 20, theme, false); - _drawCard(scene, opponent, oPower, W * 0.78, H / 2 - 20, theme, true); + _drawCard(scene, challenger, cPower, W * 0.22, H / 2 - 20, theme); + _drawCard(scene, opponent, oPower, W * 0.78, H / 2 - 20, theme); // VS label scene.add @@ -127,11 +127,11 @@ const DevDuel = (() => { // Animate the battle after a short delay scene.time.delayedCall(800, () => { - _animateBattle(scene, challenger, opponent, cPower, oPower, W, H, theme); + _animateBattle(scene, challenger, opponent, cPower, oPower, W, H); }); } - function _drawCard(scene, dev, power, cx, cy, theme, flipSide) { + function _drawCard(scene, dev, power, cx, cy, theme) { const cardW = 160; const cardH = 200; const x = cx - cardW / 2; @@ -215,7 +215,7 @@ const DevDuel = (() => { w: barW, duration: 800, ease: "Power2", - onUpdate: (tween, target) => { + onUpdate: (_tween, target) => { fillGfx.clear(); fillGfx.fillStyle(0x38bdf8, 1); fillGfx.fillRoundedRect(x, y + 14, target.w, 10, 3); @@ -231,16 +231,7 @@ const DevDuel = (() => { // ─── Battle animation ───────────────────────────────────────────────────── - function _animateBattle( - scene, - challenger, - opponent, - cPower, - oPower, - W, - H, - theme, - ) { + function _animateBattle(scene, challenger, opponent, cPower, oPower, W, H) { // Flash attacks back and forth let round = 0; const maxRounds = 5; @@ -248,7 +239,7 @@ const DevDuel = (() => { const attackFlash = () => { if (round >= maxRounds) { scene.time.delayedCall(400, () => { - _showResult(scene, challenger, opponent, cPower, oPower, W, H, theme); + _showResult(scene, challenger, opponent, cPower, oPower, W, H); }); return; } @@ -280,16 +271,7 @@ const DevDuel = (() => { // ─── Result screen ──────────────────────────────────────────────────────── - function _showResult( - scene, - challenger, - opponent, - cPower, - oPower, - W, - H, - theme, - ) { + function _showResult(scene, challenger, opponent, cPower, oPower, W, H) { const challengerWins = cPower >= oPower; const winner = challengerWins ? challenger : opponent; @@ -397,6 +379,7 @@ function getCardData(cardEl) { * * @param {HTMLElement} challengerCard The card element that was clicked. */ +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function startDuelFromCard(challengerCard) { const allCards = Array.from( document.querySelectorAll(".user-card[data-name]"), diff --git a/src/assets/js/games/game-manager.js b/src/assets/js/games/game-manager.js index cfe089d3..7407cead 100644 --- a/src/assets/js/games/game-manager.js +++ b/src/assets/js/games/game-manager.js @@ -8,6 +8,7 @@ * - Persisting per-game high scores and achievement flags in localStorage */ +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const GameManager = (() => { /** Active Phaser.Game instances keyed by game id. */ const _instances = {}; diff --git a/src/assets/js/games/space-invaders.js b/src/assets/js/games/space-invaders.js index 1956d70e..580f1efc 100644 --- a/src/assets/js/games/space-invaders.js +++ b/src/assets/js/games/space-invaders.js @@ -8,6 +8,7 @@ * GameManager and lazy-loaded alongside the other mini-games. */ +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const SpaceInvaders = (() => { const ALIEN_ROWS = ["👾", "👽", "🛸", "🐙", "👾"]; const GAME_ID = "space-invaders"; @@ -69,22 +70,29 @@ const SpaceInvaders = (() => { _setupGame(this); } - function _onUpdate() { - if (!this.si_player || !this.si_player.body) return; - - if (this.si_cursors.left.isDown) { - this.si_player.body.setVelocityX(-400); - } else if (this.si_cursors.right.isDown) { - this.si_player.body.setVelocityX(400); + function _updatePlayerVelocity(scene) { + if (scene.si_cursors.left.isDown) { + scene.si_player.body.setVelocityX(-400); + } else if (scene.si_cursors.right.isDown) { + scene.si_player.body.setVelocityX(400); } else { - this.si_player.body.setVelocityX(0); + scene.si_player.body.setVelocityX(0); } + } + + function _getBullets(scene) { + return scene.si_bullets?.getChildren?.() || []; + } + + function _onUpdate() { + if (!this.si_player?.body) return; + _updatePlayerVelocity(this); if (this.si_cursors.space.isDown) { _fireBullet(this); } - const bullets = this.si_bullets?.getChildren?.() || []; + const bullets = _getBullets(this); bullets.forEach((bullet) => { if (!bullet.active) return; if ( diff --git a/src/assets/js/script.js b/src/assets/js/script.js index 9e7637f1..951fcab5 100644 --- a/src/assets/js/script.js +++ b/src/assets/js/script.js @@ -42,6 +42,7 @@ const XP_GRAVITY_SECRET = 250; // Gravity effect easter egg const XP_KONAMI_SECRET = 500; // Konami code easter egg const XP_FOOTER_SURGE = 1000; // Footer surge secret const XP_BADGE_CLICK = 45; // Badge click reward +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. const XP_SPACE_INVADERS_WIN = 200; // Defeat all Space Invaders const _XP_CODE_BREAKER_WIN = 100; // Win a Code Breaker round const _XP_DEV_DUEL_PLAY = 25; // Play a Developer Duel @@ -77,9 +78,28 @@ function initAudio() { window.addEventListener("click", initAudio, { once: true }); window.addEventListener("keydown", initAudio, { once: true }); -function playSound(type) { +const isAudioRunning = () => audioCtx?.state === "running"; + +const LAYERED_SOUND_CONFIGS = { + secret: { + oscillatorType: "triangle", + frequencies: [523.25, 659.25, 783.99, 1046.5], + interval: 0.3, + volume: 0.07, + duration: 0.3, + }, + restore: { + oscillatorType: "sine", + frequencies: [220, 440, 880, 1760], + interval: 0.05, + volume: 0.1, + duration: 0.1, + }, +}; + +window.playSound = function playSound(type) { initAudio(); - if (!audioCtx || audioCtx.state !== "running") return; + if (!isAudioRunning()) return; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); @@ -102,34 +122,25 @@ function playSound(type) { gain.gain.exponentialRampToValueAtTime(0.01, now + 1.0); osc.start(now); osc.stop(now + 1.5); - } else if (type === "secret") { - osc.type = "triangle"; - [523.25, 659.25, 783.99, 1046.5].forEach((freq, i) => { - const s = audioCtx.createOscillator(); - const g = audioCtx.createGain(); - s.connect(g); - g.connect(audioCtx.destination); - s.frequency.setValueAtTime(freq, now + i * 0.3); - g.gain.setValueAtTime(0.07, now + i * 0.3); - g.gain.exponentialRampToValueAtTime(0.01, now + i * 0.3 + 0.3); - s.start(now + i * 0.3); - s.stop(now + i * 0.3 + 0.3); - }); - } else if (type === "restore") { - osc.type = "sine"; - [220, 440, 880, 1760].forEach((freq, i) => { + } else { + const config = LAYERED_SOUND_CONFIGS[type]; + if (!config) return; + + osc.type = config.oscillatorType; + config.frequencies.forEach((freq, i) => { + const startTime = now + i * config.interval; const s = audioCtx.createOscillator(); const g = audioCtx.createGain(); s.connect(g); g.connect(audioCtx.destination); - s.frequency.setValueAtTime(freq, now + i * 0.05); - g.gain.setValueAtTime(0.1, now + i * 0.05); - g.gain.exponentialRampToValueAtTime(0.01, now + i * 0.05 + 0.1); - s.start(now + i * 0.05); - s.stop(now + i * 0.05 + 0.1); + s.frequency.setValueAtTime(freq, startTime); + g.gain.setValueAtTime(config.volume, startTime); + g.gain.exponentialRampToValueAtTime(0.01, startTime + config.duration); + s.start(startTime); + s.stop(startTime + config.duration); }); } -} +}; function getRank(lvl) { const numericLevel = Number(lvl) || 0; @@ -263,6 +274,7 @@ document.addEventListener("mouseup", () => { dragContainer.classList.add("transition-all", "duration-300", "ease-in-out"); }); +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function minimizeConsole() { if (consoleOutput.style.display === "none") { consoleOutput.style.display = "block"; @@ -273,6 +285,7 @@ function minimizeConsole() { } } +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function maximizeConsole() { consoleContainer.classList.toggle("console-maximized"); @@ -353,6 +366,7 @@ window.createFloatingXP = (e) => { setTimeout(() => popup.remove(), 800); }; +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function handleLevelClick() { triggerSecretUnlock("badge_click"); } @@ -447,8 +461,9 @@ function applyTheme(theme) { updateThemeIcon(theme); } +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function toggleTheme() { - playSound("click"); + window.playSound("click"); const current = localStorage.getItem("theme") || "light"; const next = current === "light" ? "dark" : current === "dark" ? "random" : "light"; @@ -545,6 +560,7 @@ function triggerForceSurge() { }, 8000); } +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function triggerMagicXP() { initAudio(); addExperience(XP_MAGIC_BONUS); @@ -567,7 +583,7 @@ function triggerSecretUnlock(type) { // 3. Only process XP and Save if it's the first time if (isNewUnlock) { - playSound("secret"); + window.playSound("secret"); // Update the array and save to localStorage unlockedEggs.push(eggId); localStorage.setItem("unlockedEggs", JSON.stringify(unlockedEggs)); @@ -590,7 +606,7 @@ function triggerSecretUnlock(type) { console.log(`✨ Secret Unlocked: ${eggId}`); } else { - playSound("click"); + window.playSound("click"); console.log(`Secret ${eggId} already discovered. No extra XP granted.`); } } @@ -660,7 +676,7 @@ window.addEventListener("keydown", (e) => { const isOpening = systemDash.classList.contains("hidden"); localStorage.setItem("devToolsVisible", isOpening); - playSound(isOpening ? "secret" : "click"); + window.playSound(isOpening ? "secret" : "click"); if (isOpening) { document @@ -742,7 +758,7 @@ function activateGravityEffect() { box-shadow: 0 0 50px rgba(37, 99, 235, 0.8); `; btn.onclick = () => { - playSound("restore"); + window.playSound("restore"); btn.innerHTML = "SYSTEM RESTORED"; btn.style.pointerEvents = "none"; setTimeout(() => window.location.reload(), 1000); @@ -822,7 +838,7 @@ function triggerBadgeLevelUp() { // 2. Secret Sound & Level Logic if (!hasTriggeredFirstLevel) { // Play your secret sound - playSound("secret"); + window.playSound("secret"); // Force a level up for the "first time" experience addExperience(XP_PER_LEVEL); @@ -921,8 +937,9 @@ window.startSelfDestruct = () => { }, 1000); }; +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function scrollToRandomUser() { - playSound("click"); + window.playSound("click"); surpriseClickCount++; if (surpriseClickCount >= 5) { @@ -945,7 +962,7 @@ function scrollToRandomUser() { randomCard.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { - playSound("levelUp"); + window.playSound("levelUp"); randomCard.classList.add("selected-fancy"); // Inject the Tracing SVG @@ -1336,6 +1353,7 @@ function addMaintenanceXP() { } } +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function jumpToLevel() { const input = document.getElementById("jump-lvl"); if (!input || input.value === "") return; @@ -1360,6 +1378,7 @@ function jumpToLevel() { showLevelUpNotification(rank); } +// biome-ignore lint/correctness/noUnusedVariables: Used outside this classic script. function handleFooterDotClick() { // 1. Get the current list of unlocked eggs const rawEggs = localStorage.getItem("unlockedEggs") || "[]";