ZONE UP!
// --- -1. COMMERCIAL CONFIGURATION --- const Config = { VERSION: "6.0.1", AI_LEARNING_RATE: 0.12, // Future-ready: Slightly faster adaptation BASE_SPEED: 35, HYPER_SPEED_BOOST: 45, DIFFICULTY_RAMP: 5, // Metres per difficulty level INVINCIBLE_TIME: 2.5, }; const Utils = { formatTime: (sec) => { const m = Math.floor(sec / 60) .toString() .padStart(2, "0"); const s = Math.floor(sec % 60) .toString() .padStart(2, "0"); return `${m}:${s}`; }, randRange: (min, max) => Math.random() * (max - min) + min, clamp: (val, min, max) => Math.min(Math.max(val, min), max), }; // --- 0. AI & NLP MODULES --- const Narrator = { synth: window.speechSynthesis, voice: null, speaking: false, init: () => { // Try to select a "System" or "Google US English" voice if (Narrator.synth) { const voices = Narrator.synth.getVoices(); Narrator.voice = voices.find((v) => v.name.includes("Google US English")) || voices[0]; } }, speak: (text, pitch = 1.0, rate = 1.0) => { if (!Narrator.synth || Game.isMuted) return; // Cancel prev speech to avoid queue lag Narrator.synth.cancel(); const utter = new SpeechSynthesisUtterance(text); if (Narrator.voice) utter.voice = Narrator.voice; utter.pitch = pitch; utter.rate = rate; utter.volume = 0.8; Narrator.synth.speak(utter); }, }; // Markov Chain + Heuristics for Player Prediction const Cortex = { // 5 lanes: Indices 0 to 4 correspond to x: -6, -3, 0, 3, 6 laneHistory: [2, 2, 2, 2, 2], // Last 5 positions transitionMatrix: Array(5) .fill() .map(() => Array(5).fill(0.2)), // Probabilities learningRate: 0.1, analyzeMove: (prevLaneIdx, newLaneIdx) => { if (prevLaneIdx === newLaneIdx) return; // Update Markov Matrix: Increase prob of newLane given prevLane const row = Cortex.transitionMatrix[prevLaneIdx]; for (let i = 0; i < 5; i++) { if (i === newLaneIdx) row[i] += Cortex.learningRate; else row[i] -= Cortex.learningRate / 4; row[i] = Utils.clamp(row[i], 0.05, 0.95); // Keep bounds } // Normalize row const sum = row.reduce((a, b) => a + b, 0); for (let i = 0; i < 5; i++) row[i] /= sum; Cortex.laneHistory.push(newLaneIdx); if (Cortex.laneHistory.length > 10) Cortex.laneHistory.shift(); document.getElementById("ai-status").innerText = "CORTEX: ADAPTING..."; }, predictNextLane: (currentLaneIdx) => { const row = Cortex.transitionMatrix[currentLaneIdx]; let maxP = -1; let predicted = 2; for (let i = 0; i < 5; i++) { if (row[i] > maxP) { maxP = row[i]; predicted = i; } } return predicted; }, }; // --- 1. STORAGE (Commercial Data Module) --- const Storage = { key: "cr_save_v6_ai", data: { bestTime: 0, bestDist: 0, totalGems: 0, aiMatrix: null }, load: async () => { try { let d; // Try CrazyGames SDK Data Module first if (Monetization.sdk?.data) { d = await Monetization.sdk.data.getItem(Storage.key); } // Fallback to localStorage if (!d) { d = localStorage.getItem(Storage.key); } if (d) { Storage.data = typeof d === "string" ? JSON.parse(d) : d; if (Storage.data.aiMatrix) Cortex.transitionMatrix = Storage.data.aiMatrix; } if (document.getElementById("menu-best-time")) { document.getElementById("menu-best-time").innerText = Utils.formatTime(Storage.data.bestTime); } } catch (e) { console.error("Save Load Error", e); } }, save: async () => { Storage.data.aiMatrix = Cortex.transitionMatrix; try { if (Monetization.sdk?.data) { await Monetization.sdk.data.setItem(Storage.key, Storage.data); } // Always sync to localStorage as secondary redundant save localStorage.setItem(Storage.key, JSON.stringify(Storage.data)); } catch (e) { console.error("Save Error", e); } }, updateRecords: (time, dist) => { let improved = false; if (time > Storage.data.bestTime) { Storage.data.bestTime = time; improved = true; } if (dist > Storage.data.bestDist) { Storage.data.bestDist = dist; improved = true; } if (improved) Storage.save(); }, }; // --- 2. AUDIO ENGINE --- const AudioEngine = { ctx: null, isPlaying: false, isMuted: false, nextNoteTime: 0, beatCount: 0, scales: { neon: [440, 523, 659, 783], heat: [440, 493, 554, 659], void: [220, 311, 370, 415], }, currentScale: "neon", init: () => { window.AudioContext = window.AudioContext || window.webkitAudioContext; AudioEngine.ctx = new AudioContext(); }, toggleMute: () => { AudioEngine.isMuted = !AudioEngine.isMuted; Game.isMuted = AudioEngine.isMuted; document.getElementById("mute-btn").innerText = AudioEngine.isMuted ? "🔇" : "🔊"; if (AudioEngine.ctx) AudioEngine.isMuted ? AudioEngine.ctx.suspend() : AudioEngine.ctx.resume(); if (AudioEngine.isMuted && Narrator.synth) Narrator.synth.cancel(); }, start: () => { if (!AudioEngine.ctx) AudioEngine.init(); if (AudioEngine.ctx.state === "suspended" && !AudioEngine.isMuted) AudioEngine.ctx.resume(); AudioEngine.isPlaying = true; AudioEngine.nextNoteTime = AudioEngine.ctx.currentTime; AudioEngine.loop(); }, stop: () => { AudioEngine.isPlaying = false; }, loop: () => { if (!AudioEngine.isPlaying) return; const lookahead = 0.1; const tempo = 125 + Game.difficulty * 10; const secondsPer16th = 60.0 / tempo / 4; while ( AudioEngine.nextNoteTime < AudioEngine.ctx.currentTime + lookahead ) { if (!AudioEngine.isMuted) AudioEngine.playBeat(AudioEngine.nextNoteTime); AudioEngine.nextNoteTime += secondsPer16th; } requestAnimationFrame(AudioEngine.loop); }, playBeat: (time) => { const beat = AudioEngine.beatCount % 16; const scale = AudioEngine.scales[AudioEngine.currentScale] || AudioEngine.scales.neon; if (beat % 4 === 0) AudioEngine.synthKick(time); if (beat % 2 === 0) AudioEngine.synthBass(time, scale[0] / (beat % 8 === 0 ? 4 : 2)); if (Math.random() > 0.5) AudioEngine.synthLead( time, scale[Math.floor(Math.random() * scale.length)] * 2, ); AudioEngine.beatCount++; if (beat % 4 === 0 && Game.active) Game.pulseWorld(); }, synthKick: (t) => { const o = AudioEngine.ctx.createOscillator(); const g = AudioEngine.ctx.createGain(); o.connect(g); g.connect(AudioEngine.ctx.destination); o.frequency.setValueAtTime(150, t); o.frequency.exponentialRampToValueAtTime(0.01, t + 0.5); g.gain.setValueAtTime(0.8, t); g.gain.exponentialRampToValueAtTime(0.01, t + 0.5); o.start(t); o.stop(t + 0.5); }, synthBass: (t, f) => { const o = AudioEngine.ctx.createOscillator(); o.type = "sawtooth"; const g = AudioEngine.ctx.createGain(); o.connect(g); g.connect(AudioEngine.ctx.destination); o.frequency.setValueAtTime(f, t); g.gain.setValueAtTime(0.3, t); g.gain.linearRampToValueAtTime(0, t + 0.2); o.start(t); o.stop(t + 0.2); }, synthLead: (t, f) => { const o = AudioEngine.ctx.createOscillator(); o.type = "square"; const g = AudioEngine.ctx.createGain(); o.connect(g); g.connect(AudioEngine.ctx.destination); o.frequency.setValueAtTime(f, t); g.gain.setValueAtTime(0.05, t); g.gain.exponentialRampToValueAtTime(0.001, t + 0.1); o.start(t); o.stop(t + 0.1); }, }; // --- 3. MONETIZATION --- const Monetization = { sdk: { game: { gameplayStart: () => {}, gameplayStop: () => {} }, ad: { requestAd: (t, cb) => { console.log("Mock Ad"); setTimeout(() => cb.adFinished(), 1000); }, }, }, init: async () => { try { if ( typeof window.CrazyGames !== "undefined" && window.CrazyGames.SDK ) { const realSdk = await window.CrazyGames.SDK.init(); if (realSdk) { Monetization.sdk = realSdk; console.log("Commercial | CrazyGames SDK Active"); } } else { console.warn( "Commercial | SDK Not Detected - Entering Mock Mode", ); } } catch (e) { console.error("Commercial | SDK Init Error", e); } }, watchAdForHyper: () => { if (Monetization.sdk) Monetization.sdk.ad.requestAd("rewarded", { adFinished: () => { document.getElementById("hyper-offer").classList.add("hidden"); Game.enterHyperMode(); }, adError: () => Game.resumeFromGate(), }); else Game.resumeFromGate(); }, watchAdForRevive: () => { const btn = document.getElementById("btn-revive"); btn.innerHTML = "LOADING..."; btn.disabled = true; // Callback helper const onSuccess = () => { btn.innerHTML = "📺 REVIVE"; btn.disabled = false; document.getElementById("game-over").classList.add("hidden"); Game.revive(); }; const onFail = () => { btn.innerHTML = "UNAVAILABLE"; setTimeout(() => { btn.disabled = false; btn.innerHTML = "📺 REVIVE"; }, 2000); }; if (Monetization.sdk) { Monetization.sdk.ad.requestAd("rewarded", { adFinished: onSuccess, adError: onFail, }); } else { setTimeout(onSuccess, 1000); // Mock } }, }; // --- 4. ENGINE --- const Engine = { scene: null, camera: null, renderer: null, clock: null, geometries: {}, materials: {}, grid: null, sun: null, pool: { obstacle: [], gem: [], gate: [], particle: [] }, init: () => { const cvs = document.getElementById("gl-canvas"); Engine.clock = new THREE.Clock(); Engine.scene = new THREE.Scene(); Engine.scene.fog = new THREE.FogExp2(0x000000, 0.02); Engine.camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 300, ); Engine.camera.position.set(0, 3, -6); Engine.camera.lookAt(0, 0, 15); Engine.renderer = new THREE.WebGLRenderer({ canvas: cvs, antialias: true, powerPreference: "high-performance", }); if (!Engine.renderer.capabilities.isWebGL2 && !Engine.renderer.getContext().getExtension('WEBGL_depth_texture')) { console.warn("Commercial | Legacy Device Detected"); } Engine.renderer.setSize(window.innerWidth, window.innerHeight); Engine.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); // Shared Resources Engine.geometries.box = new THREE.BoxGeometry(2, 2, 2); Engine.geometries.gem = new THREE.OctahedronGeometry(0.5); Engine.geometries.gate = new THREE.TorusGeometry(2.5, 0.2, 8, 20); Engine.geometries.particle = new THREE.PlaneGeometry(0.5, 0.5); Engine.materials.obstacle = new THREE.MeshStandardMaterial({ color: 0xff0000, emissive: 0x440000, }); Engine.materials.gem = new THREE.MeshBasicMaterial({ color: 0x00ffff, }); Engine.materials.gate = new THREE.MeshBasicMaterial({ color: 0xffd700, }); Engine.materials.particle = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.8, }); // World const gridGeo = new THREE.PlaneGeometry(400, 400, 40, 40); const gridMat = new THREE.MeshBasicMaterial({ color: 0xff00cc, wireframe: true, transparent: true, opacity: 0.4, }); Engine.grid = new THREE.Mesh(gridGeo, gridMat); Engine.grid.rotation.x = -Math.PI / 2; Engine.grid.position.y = -1; Engine.scene.add(Engine.grid); const sunGeo = new THREE.CircleGeometry(40, 32); Engine.sun = new THREE.Mesh( sunGeo, new THREE.MeshBasicMaterial({ color: 0xffd700 }), ); Engine.sun.position.set(0, 15, 200); Engine.scene.add(Engine.sun); const hemi = new THREE.HemisphereLight(0xffffff, 0x000000, 0.8); Engine.scene.add(hemi); const dir = new THREE.DirectionalLight(0xffffff, 0.8); dir.position.set(10, 20, 10); Engine.scene.add(dir); window.addEventListener("resize", Engine.resize); Engine.animate(); // Initialize Narrator Voice if (window.speechSynthesis) window.speechSynthesis.onvoiceschanged = Narrator.init; Narrator.init(); }, resize: () => { Engine.camera.aspect = window.innerWidth / window.innerHeight; Engine.camera.updateProjectionMatrix(); Engine.renderer.setSize(window.innerWidth, window.innerHeight); }, getFromPool: (type) => { if (Engine.pool[type].length > 0) { const mesh = Engine.pool[type].pop(); mesh.visible = true; return mesh; } let mesh; if (type === "obstacle") mesh = new THREE.Mesh( Engine.geometries.box, Engine.materials.obstacle, ); else if (type === "gem") mesh = new THREE.Mesh(Engine.geometries.gem, Engine.materials.gem); else if (type === "gate") mesh = new THREE.Mesh( Engine.geometries.gate, Engine.materials.gate, ); else if (type === "particle") { mesh = new THREE.Mesh( Engine.geometries.particle, Engine.materials.particle.clone(), ); mesh.lookAt(Engine.camera.position); } Engine.scene.add(mesh); return mesh; }, returnToPool: (type, mesh) => { mesh.visible = false; Engine.pool[type].push(mesh); }, animate: () => { requestAnimationFrame(Engine.animate); const dt = Engine.clock.getDelta(); Game.update(dt); Engine.renderer.render(Engine.scene, Engine.camera); }, }; // --- 5. GAME LOGIC --- const Game = { active: false, paused: false, usedRevive: false, speed: 0, distance: 0, timeAlive: 0, gems: 0, inputX: 0, difficulty: 0, invincible: 0, isMuted: false, currentLaneIdx: 2, // 0-4 zones: [ { name: "NEON GRID", color: 0xff00cc, bg: 0x050011, scale: "neon" }, { name: "SOLAR HEAT", color: 0xff4400, bg: 0x220500, scale: "heat" }, { name: "CYBER VOID", color: 0x00ffaa, bg: 0x001111, scale: "void" }, ], currentZoneIdx: 0, player: null, objects: [], particles: [], lastSpawnZ: 0, init: async () => { await Storage.load(); const geo = new THREE.SphereGeometry(0.5, 32, 32); const mat = new THREE.MeshStandardMaterial({ color: 0x00ffff, roughness: 0.1, metalness: 0.9, emissive: 0x0044aa, }); Game.player = new THREE.Mesh(geo, mat); Engine.scene.add(Game.player); const handleMove = (xNorm) => { if (!Game.active || Game.paused) return; const rawX = Math.max(-1, Math.min(1, xNorm)) * 8; Game.inputX = rawX; // Calculate lane index (approximate) for AI learning // Lanes are at -6, -3, 0, 3, 6 const lane = Math.round((rawX + 6) / 3); const clampedLane = Utils.clamp(lane, 0, 4); if (clampedLane !== Game.currentLaneIdx) { Cortex.analyzeMove(Game.currentLaneIdx, clampedLane); Game.currentLaneIdx = clampedLane; } }; document.addEventListener("mousemove", (e) => handleMove( (e.clientX - window.innerWidth / 2) / (window.innerWidth * 0.4), ), ); document.addEventListener( "touchmove", (e) => { handleMove( (e.touches[0].clientX - window.innerWidth / 2) / (window.innerWidth * 0.4), ); }, { passive: false }, ); }, start: () => { AudioEngine.start(); Monetization.sdk.game.gameplayStart(); document.getElementById("main-menu").classList.add("hidden"); document.getElementById("hud").classList.remove("hidden"); document.getElementById("game-over").classList.add("hidden"); Narrator.speak("System initialized. Begin run.", 1.2, 1.1); Game.resetState(); Game.setInvincible(2.0); }, resetState: () => { Game.active = true; Game.paused = false; Game.usedRevive = false; Game.speed = 30; Game.distance = 0; Game.timeAlive = 0; Game.gems = 0; Game.difficulty = 0; Game.currentZoneIdx = 0; Game.objects.forEach((o) => Engine.returnToPool(o.type, o.mesh)); Game.objects = []; Game.lastSpawnZ = 0; Game.setZone(0); Game.player.position.set(0, 0, 0); Game.player.visible = true; }, update: (dt) => { if (!Game.active || Game.paused) return; if (Game.invincible > 0) { Game.invincible -= dt; Game.player.visible = Math.floor(Game.timeAlive * 20) % 2 === 0; } else { Game.player.visible = true; } Game.timeAlive += dt; const newDifficulty = Math.floor(Game.distance / 1000); if (newDifficulty > Game.difficulty) { Game.difficulty = newDifficulty; Narrator.speak("Difficulty Increasing. Stay alert.", 1.0, 1.2); } // Physics const targetSpeed = 35 + Game.difficulty * 4 + (Game.hyperMode ? 40 : 0); Game.speed += (targetSpeed - Game.speed) * dt; const distDelta = Game.speed * dt; Game.distance += distDelta; Game.player.position.z += distDelta; const lerpFactor = 10 * dt; Game.player.position.x += (Game.inputX - Game.player.position.x) * lerpFactor; Game.player.rotation.z = -(Game.player.position.x - Game.inputX) * 0.15; Game.player.rotation.x -= Game.speed * dt * 0.5; Engine.camera.position.z = Game.player.position.z - 8; Engine.camera.lookAt(0, 0, Game.player.position.z + 20); Engine.grid.position.z = Game.player.position.z + 50; if (Engine.grid.material.map) Engine.grid.material.map.offset.y -= distDelta * 0.01; Engine.sun.position.z = Game.player.position.z + 180; const zoneIdx = Math.floor(Game.distance / 1000) % Game.zones.length; if (zoneIdx !== Game.currentZoneIdx) Game.setZone(zoneIdx); if (Game.player.position.z + 120 > Game.lastSpawnZ) { Game.spawnPattern(Game.lastSpawnZ); Game.lastSpawnZ += Utils.randRange(20, 30); } // Object Update for (let i = Game.objects.length - 1; i >= 0; i--) { const obj = Game.objects[i]; if (obj.moveType === "slide") { obj.mesh.position.x = obj.origX + Math.sin(Game.timeAlive * 3) * 3; } else if (obj.moveType === "rotate") { obj.mesh.rotation.z += 3 * dt; obj.mesh.rotation.y += 1 * dt; } if ( obj.active && Math.abs(obj.mesh.position.z - Game.player.position.z) < 1.3 ) { const dx = Math.abs(obj.mesh.position.x - Game.player.position.x); if (dx < 1.4) { if (obj.type === "obstacle") { if (Game.invincible <= 0) Game.crash(); } else if (obj.type === "gem") { obj.active = false; Game.spawnParticles(obj.mesh.position, 0x00ffff); Engine.returnToPool("gem", obj.mesh); Game.objects.splice(i, 1); Game.gems++; } else if (obj.type === "gate") Game.offerHyper(); } } if (obj.mesh.position.z < Game.player.position.z - 15) { Engine.returnToPool(obj.type, obj.mesh); Game.objects.splice(i, 1); } } for (let i = Game.particles.length - 1; i >= 0; i--) { const p = Game.particles[i]; p.life -= dt * 2; p.mesh.scale.setScalar(1 + (1 - p.life) * 2); p.mesh.material.opacity = p.life; if (p.life <= 0) { Engine.returnToPool("particle", p.mesh); Game.particles.splice(i, 1); } } Game.updateHUD(); }, spawnPattern: (z) => { if (Game.hyperMode) { for (let i = -6; i <= 6; i += 3) if (Math.random() > 0.5) Game.createObj("gem", i, z); return; } const lanes = [-6, -3, 0, 3, 6]; // AI CORTEX PREDICTION const predictedLaneIdx = Cortex.predictNextLane(Game.currentLaneIdx); // Trap logic: 30% chance AI traps the predicted lane const trapLane = Math.random() < 0.3; // Standard random safe index logic mixed with AI const safeIdx = Math.floor(Math.random() * lanes.length); lanes.forEach((x, idx) => { // If AI is trapping this lane, spawn obstacle if (trapLane && idx === predictedLaneIdx && idx !== safeIdx) { Game.createObj("obstacle", x, z); return; } if (idx === safeIdx) { if (Math.random() < 0.15) Game.createObj("gem", x, z); else if (Math.random() < 0.02) Game.createObj("gate", x, z); } else { if (Math.random() < 0.4 + Game.difficulty * 0.05) { let moveType = null; if (Game.difficulty >= 1 && Math.random() < 0.3) moveType = "slide"; else if (Game.difficulty >= 2 && Math.random() < 0.3) moveType = "rotate"; Game.createObj("obstacle", x, z, moveType); } } }); }, createObj: (type, x, z, moveType = null) => { const mesh = Engine.getFromPool(type); mesh.position.set(x, 1, z); mesh.rotation.set(0, 0, 0); Game.objects.push({ mesh, type, active: true, moveType, origX: x }); }, spawnParticles: (pos, color) => { const p = Engine.getFromPool("particle"); p.position.copy(pos); p.material.color.setHex(color); p.material.opacity = 1; p.scale.setScalar(1); Game.particles.push({ mesh: p, life: 1.0 }); }, setInvincible: (duration) => { Game.invincible = duration; }, crash: () => { Game.active = false; Monetization.sdk.game.gameplayStop(); Narrator.speak("Critical Failure. Reboot required.", 0.8, 1.0); Storage.data.totalGems += Game.gems; Storage.updateRecords(Game.timeAlive, Math.floor(Game.distance)); Game.spawnParticles(Game.player.position, 0xff0044); document.getElementById("hud").classList.add("hidden"); document.getElementById("game-over").classList.remove("hidden"); document.getElementById("final-time").innerText = Utils.formatTime( Game.timeAlive, ); document.getElementById("final-score").innerText = Math.floor(Game.distance) + "m"; const revBtn = document.getElementById("revive-container"); revBtn.style.display = Game.usedRevive ? "none" : "block"; // Future-ready: Analytics Trigger (Stub) console.log(`Commercial | Run Result: ${Math.floor(Game.distance)}m in ${Game.timeAlive.toFixed(1)}s`); }, revive: () => { Game.usedRevive = true; Game.active = true; Game.paused = false; document.getElementById("hud").classList.remove("hidden"); Monetization.sdk.game.gameplayStart(); Narrator.speak("System restored. Good luck.", 1.2, 1.1); // Clear area safely ahead for (let i = Game.objects.length - 1; i >= 0; i--) { const o = Game.objects[i]; if ( o.mesh.position.z > Game.player.position.z - 5 && o.mesh.position.z < Game.player.position.z + 50 ) { Engine.returnToPool(o.type, o.mesh); Game.objects.splice(i, 1); } } Game.player.position.x = 0; Game.setInvincible(2.0); }, restart: () => { Monetization.sdk.game.gameplayStart(); Narrator.speak("Restarting simulation.", 1.0, 1.2); Game.resetState(); Game.setInvincible(2.0); }, home: () => { Game.active = false; AudioEngine.stop(); document.getElementById("game-over").classList.add("hidden"); document.getElementById("hud").classList.add("hidden"); document.getElementById("main-menu").classList.remove("hidden"); Engine.camera.position.set(0, 3, -6); Engine.camera.lookAt(0, 0, 15); }, offerHyper: () => { Game.paused = true; document.getElementById("hyper-offer").classList.remove("hidden"); Game.player.position.z += 2; Narrator.speak("Hyper Gate detected.", 1.5, 1.2); }, resumeFromGate: () => { Game.paused = false; document.getElementById("hyper-offer").classList.add("hidden"); }, enterHyperMode: () => { Game.paused = false; Game.hyperMode = true; Engine.scene.fog.color.setHex(0xffaa00); Narrator.speak("Hyper speed engaged.", 1.2, 1.5); setTimeout(() => { Game.hyperMode = false; Game.setZone(Game.currentZoneIdx); Narrator.speak("Speed normalizing.", 1.0, 1.0); }, 8000); }, setZone: (idx) => { Game.currentZoneIdx = idx; const cfg = Game.zones[idx]; Engine.scene.fog.color.setHex(cfg.bg); Engine.renderer.setClearColor(cfg.bg); Engine.grid.material.color.setHex(cfg.color); Engine.sun.material.color.setHex(cfg.color); AudioEngine.currentScale = cfg.scale; const t = document.getElementById("zone-toast"); t.innerText = cfg.name; t.style.color = "#" + cfg.color.toString(16); t.style.opacity = 1; setTimeout(() => (t.style.opacity = 0), 2000); document.getElementById("zone-display").innerText = `ZONE ${idx + 1}`; document.getElementById("zone-display").style.color = "#" + cfg.color.toString(16); if (Game.active) Narrator.speak(`Entering ${cfg.name}`, 0.9, 1.0); }, pulseWorld: () => { if (Engine.grid) { Engine.grid.position.y = -0.5; setTimeout(() => (Engine.grid.position.y = -1), 100); } }, updateHUD: () => { document.getElementById("score-display").innerText = Math.floor(Game.distance) + "m"; document.getElementById("time-display").innerText = Utils.formatTime( Game.timeAlive, ); document.getElementById("gems-display").innerText = Game.gems; }, }; (async () => { await Monetization.init(); Engine.init(); await Game.init(); })(); // PWA: Service Worker Registration if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("./sw.js") .then((reg) => console.log("SW Registered", reg)) .catch((err) => console.log("SW Fail", err)); }); }