Player Enemy Fast Enemy Vent Exit Door World 1 Grass World 2 World 3 World 4 World 5 World 6 C1 C2 C3 C4 C5
WORLD 1
Survive: 30s
Global Admin Abuse
04:00:00
LOCKOUT
FNAF SECURITY SYSTEM V3.14 - OFFLINE
Authorized Access Only
TSUNAMI WARNING!

SYSTEM OVERRIDE

UNAUTHORIZED ACCESS GRANTED

Chaos Archives

No records found... yet.

YOU DIED

The animatronics got you.

ESCAPED!

You survived 6 worlds and the Animatronic Tsunamis.

System Hacked Successfully
{ "id": "playerSpeed", "label": "Player Speed", "type": "slider", "min": 1, "max": 10, "step": 0.5, "value": 4, "path": "appState.config.playerSpeed" }, { "id": "maxHealth", "label": "Max Health", "type": "slider", "min": 50, "max": 500, "step": 10, "value": 100, "path": "appState.config.maxHealth" }, { "id": "worldTime", "label": "Time per World (sec)", "type": "slider", "min": 10, "max": 120, "step": 5, "value": 30, "path": "appState.config.worldTime" } ], "colors": [ { "id": "accent_color", "label": "Blood/Alert Color", "type": "color", "cssVar": "--accent-color", "value": "#ff0033" }, { "id": "admin_color", "label": "Terminal Color", "type": "color", "cssVar": "--admin-color", "value": "#00ff41" } ], "images": [ { "id": "img_player", "label": "Player", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/9adcaa94-b2eb-4a7e-867d-6fb48ff23eeb.webp", "description": "scary security guard pixel art", "selector": "#img-player" }, { "id": "img_enemy", "label": "Standard Enemy", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/d1fade92-4ce5-4f12-861e-fcc8e9f16076.webp", "description": "creepy bear animatronic pixel art", "selector": "#img-enemy" }, { "id": "img_enemy_fast", "label": "Fast Enemy", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/d3632c0b-ac4f-481d-bd07-85e66852d834.jpeg", "description": "creepy fox animatronic pixel art", "selector": "#img-enemy-fast" }, { "id": "img_vent", "label": "Vent Grate", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/ef3be337-8116-4530-9683-d6cc1753f184.webp", "description": "metal floor grate pixel art", "selector": "#img-vent" }, { "id": "img_door", "label": "Exit Door", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/494f552e-4680-4908-9288-3a5c9ef829ce.webp", "description": "metal security door pixel art", "selector": "#img-door" }, { "id": "img_bg1", "label": "World 1 Floor", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/2e775d7c-ac4b-47f9-80a3-8e700367fd9f.jpeg", "description": "checkered pizzeria floor dark", "selector": "#img-bg1" }, { "id": "img_bg2", "label": "World 2 Floor", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/a52602c5-d6cb-4dcd-b130-8f4c37095c6e.webp", "description": "metallic vent floor texture dark", "selector": "#img-bg2" }, { "id": "img_bg3", "label": "World 3 Floor", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/d8f4e970-c048-478b-96cb-28a00af1cf51.jpg", "description": "creepy arcade carpet dark", "selector": "#img-bg3" }, { "id": "img_bg4", "label": "World 4 Floor", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/a6fc81ff-3aa4-48a9-93ec-3e6d4f05eff8.jpg", "description": "concrete basement floor dark", "selector": "#img-bg4" }, { "id": "img_bg5", "label": "World 5 Floor", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/34aafcba-a451-4d2f-aa7a-087731152bfe.webp", "description": "rusty factory floor dark", "selector": "#img-bg5" }, { "id": "img_bg6", "label": "World 6 Floor", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/c04b0921-dd9e-43f8-923a-d62263f96477.jpg", "description": "glowing toxic facility floor dark", "selector": "#img-bg6" }, { "id": "char_1", "label": "Abuse Character 1", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/d1fade92-4ce5-4f12-861e-fcc8e9f16076.webp", "description": "creepy rabbit animatronic glitch", "selector": "#img-char-1" }, { "id": "char_2", "label": "Abuse Character 2", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/d3632c0b-ac4f-481d-bd07-85e66852d834.jpeg", "description": "broken chicken animatronic glitch", "selector": "#img-char-2" }, { "id": "char_3", "label": "Abuse Character 3", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/9adcaa94-b2eb-4a7e-867d-6fb48ff23eeb.webp", "description": "shadow bear animatronic eyes", "selector": "#img-char-3" }, { "id": "char_4", "label": "Abuse Character 4", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/ef3be337-8116-4530-9683-d6cc1753f184.webp", "description": "marionette fnaf mask glitch", "selector": "#img-char-4" }, { "id": "char_5", "label": "Abuse Character 5", "type": "image", "property": "src", "value": "https://prod-data.sekai.chat/v3-games/chat-creation/494f552e-4680-4908-9288-3a5c9ef829ce.webp", "description": "golden bear animatronic face", "selector": "#img-char-5" } ], "music": [ { "id": "bgm", "label": "Background Music", "type": "audio", "value": "https://prod-data.sekai.chat/chat-creation/19ea4fa3-34c0-4b9c-a116-d6e305b06178.mp3", "name": "Horror Drone", "description": "spooky fnaf horror ambient drone", "selector": "#bgm" } ], "sfx": [ { "id": "sfx_alarm", "label": "Tsunami Alarm", "type": "audio", "value": "https://prod-data.sekai.chat/aiu-music-sfx/4/science_fiction_sci_fi_electronic_warning_01_3daecf45ef5e1a46ad969bb45ba59445.mp3", "name": "Siren", "description": "loud warning siren sound", "selector": "#sfx-alarm" }, { "id": "sfx_jump", "label": "Jumpscare", "type": "audio", "value": "", "name": "Jumpscare Scream", "description": "loud mechanical jump scare sound", "selector": "#sfx-jump" }, { "id": "sfx_vent", "label": "Vent Open", "type": "audio", "value": "https://prod-data.sekai.chat/aiu-music-sfx/9/industry_vent_cover_metal_snapping_01_2ffdc528adab90e11888c979fa1db01e.mp3", "name": "Metal Clang", "description": "metal grate opening sound", "selector": "#sfx-vent" }, { "id": "sfx_win", "label": "World Win", "type": "audio", "value": "", "name": "Spooky Cheer", "description": "creepy children cheering sound", "selector": "#sfx-win" } ], "videos": [], "text": [], "prompts": [], "voices": [] }; // App State - Bound to Tune properties window.appState = { config: { playerSpeed: 4, maxHealth: 100, worldTime: 30 } }; // Engine State const GameState = { PLAYING: 0, PAUSED: 1, // When admin menu is open GAMEOVER: 2, VICTORY: 3, TRANSITION: 4 }; const state = { status: GameState.PLAYING, world: 1, maxWorlds: 6, timeRemaining: 30, lastTime: 0, accumulatedTime: 0, health: 100, tsunamiActive: false, tsunamiTimer: 0, chaosTimer: 240, // 4 minutes (per 3000 events description) scaled for gameplay chaosLog: [], brainrotActive: false, doorActive: false, entities: { player: { x: 0, y: 0, radius: 15, vx: 0, vy: 0 }, enemies: [], vents: [], door: null }, input: { active: false, originX: 0, originY: 0, currentX: 0, currentY: 0, dx: 0, dy: 0, pointerId: null }, dimensions: { w: 0, h: 0 } }; // DOM Elements - Defined lazily to prevent crash on load const elements = {}; function setupElements() { elements.canvas = document.getElementById('game-canvas'); elements.ctx = elements.canvas.getContext('2d'); elements.container = document.getElementById('game-container'); elements.healthFill = document.getElementById('health-fill'); elements.timerText = document.getElementById('timer-text'); elements.worldText = document.getElementById('world-text'); elements.adminBtn = document.getElementById('admin-btn'); elements.adminMenu = document.getElementById('admin-menu'); elements.closeAdmin = document.getElementById('close-admin'); elements.joystickArea = document.getElementById('joystick-area'); elements.joystickBase = document.getElementById('joystick-base'); elements.joystickStick = document.getElementById('joystick-stick'); elements.centerAlert = document.getElementById('center-alert'); elements.screenGameover = document.getElementById('screen-gameover'); elements.screenVictory = document.getElementById('screen-victory'); elements.btnRetry = document.getElementById('btn-retry'); elements.btnShare = document.getElementById('btn-share'); elements.btnRestart = document.getElementById('btn-restart'); elements.chaosCountdown = document.getElementById('chaos-countdown'); elements.btnCutTimer = document.getElementById('btn-cut-timer'); elements.btnReplays = document.getElementById('btn-replays'); elements.replayLibrary = document.getElementById('replay-library'); elements.replayList = document.getElementById('replay-list'); elements.closeReplays = document.getElementById('close-replays'); elements.imgPlayer = document.getElementById('img-player'); elements.imgEnemy = document.getElementById('img-enemy'); elements.imgEnemyFast = document.getElementById('img-enemy-fast'); elements.imgVent = document.getElementById('img-vent'); elements.imgDoor = document.getElementById('img-door'); elements.bootOverlay = document.getElementById('boot-overlay'); elements.btnBoot = document.getElementById('btn-boot'); } // World Configs const worlds = [ { id: 1, color: '#311', ventCount: 3, spawnRate: 2000, fastChance: 0 }, { id: 2, color: '#122', ventCount: 5, spawnRate: 1500, fastChance: 0.1 }, { id: 3, color: '#113', ventCount: 6, spawnRate: 1200, fastChance: 0.2 }, { id: 4, color: '#000', ventCount: 8, spawnRate: 1000, fastChance: 0.3 }, { id: 5, color: '#320', ventCount: 10, spawnRate: 800, fastChance: 0.5 }, { id: 6, color: '#020', ventCount: 15, spawnRate: 500, fastChance: 0.7 } ]; // --- Helpers --- function getConfig(category, id) { return window.sekaiEditable[category]?.find(i => i.id === id)?.value; } function setNestedValue(obj, path, value) { const parts = path.split('.'); let current = obj; for (let i = 0; i < parts.length - 1; i++) { if (!current[parts[i]]) current[parts[i]] = {}; current = current[parts[i]]; } current[parts[parts.length - 1]] = value; } function playSound(id, fallbackType = 'oscillator') { const el = document.getElementById(id); if (el && el.src) { el.currentTime = 0; el.play().catch(() => {}); return; } // Procedural fallback try { const AudioContext = window.AudioContext || window.webkitAudioContext; if (!AudioContext) return; const ctx = new AudioContext(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); if (id === 'sfx-alarm') { osc.type = 'square'; osc.frequency.setValueAtTime(400, ctx.currentTime); osc.frequency.linearRampToValueAtTime(800, ctx.currentTime + 0.5); gain.gain.setValueAtTime(0.1, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.5); osc.start(); osc.stop(ctx.currentTime + 0.5); } else if (id === 'sfx-jump') { osc.type = 'sawtooth'; osc.frequency.setValueAtTime(100, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(50, ctx.currentTime + 0.3); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3); osc.start(); osc.stop(ctx.currentTime + 0.3); } else if (id === 'sfx-vent') { osc.type = 'triangle'; osc.frequency.setValueAtTime(200, ctx.currentTime); gain.gain.setValueAtTime(0.1, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.1); osc.start(); osc.stop(ctx.currentTime + 0.1); } } catch(e) {} } function showAlert(text, duration = 2000) { elements.centerAlert.textContent = text; elements.centerAlert.style.opacity = '1'; setTimeout(() => { elements.centerAlert.style.opacity = '0'; }, duration); } function updateUI() { const hpPercent = Math.max(0, Math.min(100, (state.health / window.appState.config.maxHealth) * 100)); elements.healthFill.style.width = `${hpPercent}%`; if (hpPercent < 30) elements.healthFill.style.backgroundColor = '#f00'; else elements.healthFill.style.backgroundColor = 'var(--accent-color)'; elements.timerText.textContent = Math.ceil(state.timeRemaining); elements.worldText.textContent = `LEVEL ${state.world} / 6`; // Format 240 seconds as 4:00:00 (1 real sec = 1 game minute) const gameMinutesTotal = state.chaosTimer; const h = Math.floor(gameMinutesTotal / 60); const m = Math.floor(gameMinutesTotal % 60); const s = "00"; // Ticking by minutes for brainrot scale elements.chaosCountdown.textContent = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s}`; } function triggerAutoAbuse() { const eventId = Math.floor(Math.random() * 3000) + 1; const eventNames = [ "SKIBIDI TOILET OVERLOAD", "FANUM TAX GLITCH", "GYATT JUMPSCARE", "RIZZLER OVERRIDE", "TSUNAMI.EXE LOADED", "STIGMA MALE NUKE", "OHIO VENT SYSTEM", "GRIMACE SHAKE HAZARD", "MEWING STREAK" ]; const name = eventNames[Math.floor(Math.random() * eventNames.length)]; // Log for replay const logEntry = { id: eventId, name: name, time: new Date().toLocaleTimeString() }; state.chaosLog.unshift(logEntry); if (state.chaosLog.length > 25) state.chaosLog.pop(); renderReplayList(); // Execute actual chaos showAlert(`[ABUSE #${eventId}] ${name}`, 4000); state.brainrotActive = true; elements.container.classList.add('glitch-flash'); // Spawn 1-5 "New Characters" as requested const charCount = 1 + Math.floor(Math.random() * 5); for(let i=0; i window.appState.config.playerSpeed /= 2.5, 6000); } else if (effectType === 1) { // Immediate Tsunami Trigger triggerTsunami(); } else { // Screen Spin / Glitch intensity elements.container.style.filter = "invert(1) hue-rotate(180deg)"; setTimeout(() => elements.container.style.filter = "none", 2000); } setTimeout(() => { state.brainrotActive = false; elements.container.classList.remove('glitch-flash'); }, 5000); playSound('sfx-alarm'); state.chaosTimer = 240; // Reset to "4 Hours" } function renderReplayList() { if (state.chaosLog.length === 0) { elements.replayList.innerHTML = '
No records found... yet.
'; return; } elements.replayList.innerHTML = state.chaosLog.map(log => `
EVENT #${log.id} ${log.time}
${log.name}
PLAYBACK: SYSTEM ABUSIVE STATE CAPTURED
`).join(''); } // --- Core Engine --- function resize() { elements.dimensions.w = elements.container.clientWidth; elements.dimensions.h = elements.container.clientHeight; elements.canvas.width = elements.dimensions.w; elements.canvas.height = elements.dimensions.h; // Adjust flashlight size const vignette = document.getElementById('vignette'); if (state.entities.player) { const px = (state.entities.player.x / elements.dimensions.w) * 100; const py = (state.entities.player.y / elements.dimensions.h) * 100; vignette.style.background = `radial-gradient(circle 150px at ${px}% ${py}%, transparent 0%, rgba(0,0,0,0.85) 80%, #000 100%)`; } } function spawnVent() { const margin = 50; const x = margin + Math.random() * (elements.dimensions.w - margin * 2); const y = margin + Math.random() * (elements.dimensions.h - margin * 2); state.entities.vents.push({ x, y, radius: 25, active: false, timer: 0 }); playSound('sfx-vent'); } function triggerTsunami() { state.tsunamiActive = true; elements.container.classList.add('tsunami-active'); playSound('sfx-alarm'); showAlert("TSUNAMI WARNING!"); // Spawn extra vents for(let i=0; i<3; i++) spawnVent(); // Flash effect setTimeout(() => { state.tsunamiActive = false; elements.container.classList.remove('tsunami-active'); }, 3000); // Spawn wave state.entities.vents.forEach(v => { for(let i=0; i<3; i++) { spawnEnemyFromVent(v); } }); } function spawnEnemyFromVent(vent) { const w = worlds[state.world - 1]; const isFast = Math.random() < w.fastChance; state.entities.enemies.push({ x: vent.x + (Math.random() * 20 - 10), y: vent.y + (Math.random() * 20 - 10), radius: 12, speed: isFast ? window.appState.config.playerSpeed * 0.8 : window.appState.config.playerSpeed * 0.4, isFast: isFast }); } function startWorld(worldIndex) { state.world = worldIndex; const w = worlds[worldIndex - 1]; state.timeRemaining = window.appState.config.worldTime; state.doorActive = false; state.entities.enemies = []; state.entities.vents = []; state.entities.door = null; state.tsunamiTimer = 12; // Tsunami every 12s // Spawn player center state.entities.player.x = elements.dimensions.w / 2; state.entities.player.y = elements.dimensions.h / 2; for(let i=0; i < w.ventCount; i++) spawnVent(); updateUI(); showAlert(`WORLD ${worldIndex}`, 2000); } function spawnDoor() { if (state.doorActive) return; state.doorActive = true; playSound('sfx-win'); showAlert("EXIT DOOR OPEN!"); // Spawn door away from player let dx, dy, dist; let x, y; do { x = 50 + Math.random() * (elements.dimensions.w - 100); y = 50 + Math.random() * (elements.dimensions.h - 100); dx = x - state.entities.player.x; dy = y - state.entities.player.y; dist = Math.sqrt(dx*dx + dy*dy); } while (dist < 150); state.entities.door = { x, y, radius: 30 }; } function gameOver() { state.status = GameState.GAMEOVER; playSound('sfx-jump'); elements.screenGameover.classList.add('visible'); } function winGame() { state.status = GameState.VICTORY; playSound('sfx-win'); elements.screenVictory.classList.add('visible'); // Post message API for saving score try { const msg = { origin: 'sekai_gaming_iframe_api', type: 'save_app_result', taskId: crypto.randomUUID(), data: { score: state.health * state.world, public: true, result: { worldsCompleted: 6, remainingHealth: state.health } } }; window.parent.postMessage(msg, '*'); } catch(e){} } function update(dt) { if (state.status !== GameState.PLAYING) return; const p = state.entities.player; const w = worlds[state.world - 1]; // Timers state.accumulatedTime += dt; if (state.accumulatedTime >= 1.0) { state.accumulatedTime -= 1.0; if (!state.doorActive) { state.timeRemaining -= 1; if (state.timeRemaining <= 0) { state.timeRemaining = 0; spawnDoor(); } state.tsunamiTimer -= 1; if (state.tsunamiTimer <= 0) { triggerTsunami(); state.tsunamiTimer = 12; // Reset every 12s like Roblox } state.chaosTimer -= 1; if (state.chaosTimer <= 0) { triggerAutoAbuse(); } } // Regular vent spawns if (Math.random() < 0.5) { const vent = state.entities.vents[Math.floor(Math.random() * state.entities.vents.length)]; if (vent) spawnEnemyFromVent(vent); } updateUI(); } // Move Player if (state.input.active) { p.vx = state.input.dx * window.appState.config.playerSpeed; p.vy = state.input.dy * window.appState.config.playerSpeed; } else { p.vx *= 0.8; // friction p.vy *= 0.8; } p.x += p.vx; p.y += p.vy; // Bounds p.x = Math.max(p.radius, Math.min(elements.dimensions.w - p.radius, p.x)); p.y = Math.max(p.radius, Math.min(elements.dimensions.h - p.radius, p.y)); // Update Flashlight const px = (p.x / elements.dimensions.w) * 100; const py = (p.y / elements.dimensions.h) * 100; if (!state.tsunamiActive) { document.getElementById('vignette').style.background = `radial-gradient(circle 150px at ${px}% ${py}%, transparent 0%, rgba(0,0,0,0.85) 80%, #000 100%)`; } // Move Enemies & Collisions for (let i = state.entities.enemies.length - 1; i >= 0; i--) { const e = state.entities.enemies[i]; // Seek player const dx = p.x - e.x; const dy = p.y - e.y; const dist = Math.sqrt(dx*dx + dy*dy); if (dist > 0) { e.x += (dx / dist) * e.speed; e.y += (dy / dist) * e.speed; } // Collision with player if (dist < p.radius + e.radius) { state.health -= (e.isFast ? 15 : 10); state.entities.enemies.splice(i, 1); updateUI(); // Screen shake/flash feedback elements.container.style.transform = `translate(${(Math.random()-0.5)*10}px, ${(Math.random()-0.5)*10}px)`; setTimeout(() => elements.container.style.transform = 'none', 50); if (state.health <= 0) { state.health = 0; updateUI(); gameOver(); return; } } } // Door Collision if (state.doorActive && state.entities.door) { const dx = p.x - state.entities.door.x; const dy = p.y - state.entities.door.y; if (Math.sqrt(dx*dx + dy*dy) < p.radius + state.entities.door.radius) { if (state.world < state.maxWorlds) { startWorld(state.world + 1); } else { winGame(); } } } } function drawEntity(ctx, imgElement, fallbackColor, x, y, radius) { if (imgElement && imgElement.src && imgElement.complete && imgElement.naturalHeight !== 0) { ctx.drawImage(imgElement, x - radius, y - radius, radius*2, radius*2); } else { ctx.fillStyle = fallbackColor; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fill(); } } function draw() { const ctx = elements.ctx; const w = worlds[state.world - 1]; // Draw Background const bgImg = document.getElementById(`img-bg${state.world}`); if (bgImg && bgImg.src && bgImg.complete && bgImg.naturalHeight !== 0) { // Pattern fill const ptrn = ctx.createPattern(bgImg, 'repeat'); ctx.fillStyle = ptrn; ctx.fillRect(0, 0, elements.dimensions.w, elements.dimensions.h); } else { ctx.fillStyle = w ? w.color : '#111'; ctx.fillRect(0, 0, elements.dimensions.w, elements.dimensions.h); } // Grid lines for floor effect ctx.strokeStyle = 'rgba(0,0,0,0.3)'; ctx.lineWidth = 2; for(let x=0; x { drawEntity(ctx, elements.imgVent, '#444', v.x, v.y, v.radius); // Grate lines fallback if (!elements.imgVent.src) { ctx.strokeStyle = '#222'; ctx.lineWidth = 3; for(let i=-15; i<=15; i+=10) { ctx.beginPath(); ctx.moveTo(v.x + i, v.y - 20); ctx.lineTo(v.x + i, v.y + 20); ctx.stroke(); ctx.beginPath(); ctx.moveTo(v.x - 20, v.y + i); ctx.lineTo(v.x + 20, v.y + i); ctx.stroke(); } } }); // Draw Door if (state.doorActive && state.entities.door) { const d = state.entities.door; drawEntity(ctx, elements.imgDoor, '#0f0', d.x, d.y, d.radius); // Glow ctx.shadowColor = '#0f0'; ctx.shadowBlur = 20; ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(d.x, d.y, d.radius, 0, Math.PI*2); ctx.stroke(); ctx.shadowBlur = 0; } // Draw Enemies state.entities.enemies.forEach(e => { let img = e.isFast ? elements.imgEnemyFast : elements.imgEnemy; if (e.imgId) { img = document.getElementById(e.imgId); } const color = e.isFast ? '#f80' : '#888'; drawEntity(ctx, img, color, e.x, e.y, e.radius); // Glowing eyes fallback if (!img || !img.src) { ctx.fillStyle = '#f00'; ctx.beginPath(); ctx.arc(e.x - 4, e.y - 2, 2, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(e.x + 4, e.y - 2, 2, 0, Math.PI*2); ctx.fill(); } }); // Draw Player const p = state.entities.player; drawEntity(ctx, elements.imgPlayer, '#00f', p.x, p.y, p.radius); // Flashlight cone (visual hint in addition to vignette) ctx.fillStyle = 'rgba(255,255,200,0.1)'; ctx.beginPath(); let angle = Math.atan2(p.vy, p.vx); if (p.vx === 0 && p.vy === 0) angle = -Math.PI/2; // face up default ctx.arc(p.x, p.y, 100, angle - 0.5, angle + 0.5); ctx.lineTo(p.x, p.y); ctx.fill(); } function loop(timestamp) { if (!state.lastTime) state.lastTime = timestamp; const dt = (timestamp - state.lastTime) / 1000; state.lastTime = timestamp; update(dt); draw(); requestAnimationFrame(loop); } // --- Inputs --- function initInputs() { const area = elements.joystickArea; const base = elements.joystickBase; const stick = elements.joystickStick; area.addEventListener('pointerdown', e => { if (state.status !== GameState.PLAYING) return; if (e.target.closest('button')) return; state.input.pointerId = e.pointerId; state.input.active = true; area.setPointerCapture(e.pointerId); const rect = area.getBoundingClientRect(); state.input.originX = e.clientX - rect.left; state.input.originY = e.clientY - rect.top; base.style.display = 'block'; base.style.left = state.input.originX + 'px'; base.style.top = state.input.originY + 'px'; }); area.addEventListener('pointermove', e => { if (!state.input.active || e.pointerId !== state.input.pointerId) return; const rect = area.getBoundingClientRect(); const dx = (e.clientX - rect.left) - state.input.originX; const dy = (e.clientY - rect.top) - state.input.originY; const dist = Math.sqrt(dx*dx + dy*dy); const max = 40; const moveX = dist > 0 ? (dx/dist) * Math.min(dist, max) : 0; const moveY = dist > 0 ? (dy/dist) * Math.min(dist, max) : 0; stick.style.transform = `translate(calc(-50% + ${moveX}px), calc(-50% + ${moveY}px))`; state.input.dx = moveX / max; state.input.dy = moveY / max; }); const stopInput = (e) => { if (e.pointerId === state.input.pointerId) { state.input.active = false; state.input.dx = 0; state.input.dy = 0; base.style.display = 'none'; } }; area.addEventListener('pointerup', stopInput); area.addEventListener('pointercancel', stopInput); elements.adminBtn.addEventListener('click', () => { state.status = GameState.PAUSED; elements.adminMenu.classList.add('visible'); }); elements.closeAdmin.addEventListener('click', () => { state.status = GameState.PLAYING; elements.adminMenu.classList.remove('visible'); }); // Admin Cheats for 3D document.getElementById('cheat-nuke').addEventListener('click', () => { enemiesGroup.children.forEach(c => enemiesGroup.remove(c)); showAlert("SYSTEM WIPED"); }); document.getElementById('cheat-tsunami').addEventListener('click', () => { triggerTsunami(); elements.adminMenu.classList.remove('visible'); state.status = GameState.PLAYING; }); elements.btnRetry.addEventListener('click', () => location.reload()); elements.btnRestart.addEventListener('click', () => location.reload()); } function initApp() { setupElements(); applyAllEditableValues(); elements.btnBoot.addEventListener('click', () => { document.getElementById('boot-overlay').style.display = 'none'; init3D(); initInputs(); animate(); updateUI(); initBGM(); // Spawn some random vents visually in 3D for(let i=0; i<8; i++) { const vent = createBillboard(document.getElementById('img-vent').src, [4, 4]); vent.position.set((Math.random()-0.5)*200, 0.1, (Math.random()-0.5)*200); vent.rotation.x = -Math.PI/2; ventsGroup.add(vent); } }); } area.addEventListener('pointermove', e => { if (!state.input.active || e.pointerId !== state.input.pointerId) return; const rect = area.getBoundingClientRect(); let curX = e.clientX - rect.left; let curY = e.clientY - rect.top; let dx = curX - state.input.originX; let dy = curY - state.input.originY; const dist = Math.sqrt(dx*dx + dy*dy); const maxDist = 40; if (dist > maxDist) { dx = (dx / dist) * maxDist; dy = (dy / dist) * maxDist; } stick.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`; // Normalize for movement const moveDist = Math.min(dist, maxDist); if (moveDist > 5) { state.input.dx = dx / maxDist; state.input.dy = dy / maxDist; } else { state.input.dx = 0; state.input.dy = 0; } }); const endPointer = (e) => { if (e.pointerId === state.input.pointerId) { state.input.active = false; state.input.dx = 0; state.input.dy = 0; state.input.pointerId = null; base.style.display = 'none'; try { area.releasePointerCapture(e.pointerId); } catch(e){} } }; area.addEventListener('pointerup', endPointer); area.addEventListener('pointercancel', endPointer); // Admin Menu elements.adminBtn.addEventListener('click', () => { if (state.status === GameState.GAMEOVER || state.status === GameState.VICTORY) return; state.status = GameState.PAUSED; elements.adminMenu.classList.add('visible'); }); elements.closeAdmin.addEventListener('click', () => { elements.adminMenu.classList.remove('visible'); state.status = GameState.PLAYING; state.lastTime = performance.now(); // prevent huge dt jump }); // Admin Cheats document.getElementById('cheat-heal').addEventListener('click', () => { state.health = window.appState.config.maxHealth; updateUI(); showAlert("SYSTEM HEALED"); }); document.getElementById('cheat-nuke').addEventListener('click', () => { state.entities.enemies = []; showAlert("ENEMIES NUKED"); }); document.getElementById('cheat-tsunami').addEventListener('click', () => { elements.adminMenu.classList.remove('visible'); state.status = GameState.PLAYING; state.lastTime = performance.now(); triggerTsunami(); }); document.getElementById('cheat-skip').addEventListener('click', () => { elements.adminMenu.classList.remove('visible'); state.status = GameState.PLAYING; state.lastTime = performance.now(); if (state.world < state.maxWorlds) { startWorld(state.world + 1); } else { winGame(); } }); document.getElementById('cheat-speed').addEventListener('click', () => { window.appState.config.playerSpeed = 10; showAlert("SPEED MAXED"); }); document.getElementById('cheat-time').addEventListener('click', () => { state.timeRemaining = 0; updateUI(); showAlert("TIMER ZEROED"); }); // Chaos Features elements.btnCutTimer.addEventListener('click', () => { // Cut 1 "Hour" (60 game minutes = 60 real seconds) state.chaosTimer = Math.max(0, state.chaosTimer - 60); updateUI(); showAlert("SYSTEM HACKED: -1 HOUR!", 1500); if (state.chaosTimer === 0) triggerAutoAbuse(); }); elements.btnReplays.addEventListener('click', () => { elements.replayLibrary.classList.add('visible'); }); elements.closeReplays.addEventListener('click', () => { elements.replayLibrary.classList.remove('visible'); }); // Screens elements.btnRetry.addEventListener('click', () => { elements.screenGameover.classList.remove('visible'); state.health = window.appState.config.maxHealth; state.status = GameState.PLAYING; startWorld(state.world); // restart current world }); elements.btnRestart.addEventListener('click', () => { elements.screenVictory.classList.remove('visible'); state.health = window.appState.config.maxHealth; state.status = GameState.PLAYING; startWorld(1); }); elements.btnShare.addEventListener('click', async () => { const btn = elements.btnShare; if (btn.dataset.loading === 'true') return; btn.dataset.loading = 'true'; btn.disabled = true; btn.innerHTML = 'Capturing...'; try { const zone = document.getElementById('share-zone'); const jpgImg = await window.snapdom.toJpg(zone, { quality: 0.8, width: zone.offsetWidth * 2, height: zone.offsetHeight * 2, }); window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'invoke_share', taskId: crypto.randomUUID(), data: { title: `I survived 6 worlds in Animatronic Tsunami Escape!`, coverImageBase64: jpgImg.src } }, '*'); } catch (e) { console.error('Share error:', e); } finally { btn.dataset.loading = 'false'; btn.disabled = false; btn.innerHTML = 'Share'; } }); window.addEventListener('resize', resize); } // --- Initialization --- function applyAllEditableValues() { window.sekaiEditable.tune?.forEach(t => { if (t.path) setNestedValue(window, t.path, t.value); }); window.sekaiEditable.colors?.forEach(c => { if (c.cssVar) document.documentElement.style.setProperty(c.cssVar, c.value); }); window.sekaiEditable.images?.forEach(img => { if (img.selector) { const el = document.querySelector(img.selector); if (el) { if (img.property === 'src') el.src = img.value; else if (img.property === 'backgroundImage') el.style.backgroundImage = `url('${img.value}')`; } } }); window.sekaiEditable.music?.forEach(m => { if (m.selector) { const el = document.querySelector(m.selector); if (el && el.src !== m.value) { el.src = m.value; el.load(); } } }); window.sekaiEditable.sfx?.forEach(s => { if (s.selector) { const el = document.querySelector(s.selector); if (el && el.src !== s.value) { el.src = s.value; el.load(); } } }); } function initBGM() { const el = document.getElementById('bgm'); if (!el?.src) return; el.loop = true; el.volume = 0.5; const playBgm = () => { el.play().catch(() => { document.addEventListener('touchstart', playBgm, { once: true }); document.addEventListener('click', playBgm, { once: true }); }); }; playBgm(); } function initApp() { setupElements(); applyAllEditableValues(); elements.btnBoot.addEventListener('click', () => { elements.bootOverlay.style.opacity = '0'; elements.bootOverlay.style.visibility = 'hidden'; initBGM(); initInputs(); resize(); state.health = window.appState.config.maxHealth; startWorld(1); requestAnimationFrame(loop); }, { once: true }); // Initial call to ensure canvas isn't zeroed resize(); } document.addEventListener('DOMContentLoaded', initApp); // --- Preview Editing API --- (function setupPreviewEditingAPI() { window.addEventListener('message', (event) => { const msg = event.data; if (msg?.origin !== 'sekai_gaming_iframe_api') return; switch (msg.type) { case 'get_editable_metadata': window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'receive_editable_metadata', taskId: msg.taskId, data: window.sekaiEditable || {} }, '*'); break; case 'apply_change': const success = applySingleChange(msg.data.id, msg.data.value, msg.data.name, msg.data.description); window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'receive_apply_change', taskId: msg.taskId, data: { success } }, '*'); break; case 'get_current_html': window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'receive_current_html', taskId: msg.taskId, data: { html: document.documentElement.outerHTML } }, '*'); break; case 'reset_changes': location.reload(); break; } }); function applySingleChange(id, value, name, description) { const categories = ['tune', 'images', 'videos', 'music', 'sfx', 'colors', 'text', 'prompts', 'voices']; for (const cat of categories) { const item = window.sekaiEditable[cat]?.find(i => i.id === id); if (!item) continue; item.value = value; if (name) item.name = name; if (description) item.description = description; if (item.cssVar) { document.documentElement.style.setProperty(item.cssVar, value); } else if (item.path) { setNestedValue(window, item.path, value); } else if (item.selector) { const el = document.querySelector(item.selector); if (!el) return false; if (el.tagName === 'AUDIO') { const wasPlaying = !el.paused; el.src = value; el.load(); if (wasPlaying) el.play().catch(() => {}); } else if (el.tagName === 'VIDEO') { el.src = value; el.load(); } else if (item.property === 'src') { el.src = value; } else if (item.property === 'backgroundImage') { el.style.backgroundImage = `url('${value}')`; } else if (item.property === 'placeholder') { el.placeholder = value; } else { el.textContent = value; } } // For tune variables requiring UI update if (id === 'maxHealth') { state.health = value; updateUI(); } return true; } return false; } })();