HEY...
VEX SPAWNED
DRONE OVERHEAD
// --- THREE.JS GLOBALS --- let scene, camera, renderer, clock, raycaster; const pointer = new THREE.Vector2(); const textures = {}; const meshes = {}; // --- SEKAI IFRAME API HELPERS --- function sendMessage(type, data) { window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type, taskId: crypto.randomUUID(), data }, '*'); } function getConfig(category, id) { return window.sekaiEditable[category]?.find(i => i.id === id)?.value; } function applyAllEditableValues() { window.sekaiEditable.tune?.forEach(t => { if (t.path) { const parts = t.path.split('.'); let current = window; for (let i = 0; i < parts.length - 1; i++) current = current[parts[i]]; current[parts[parts.length - 1]] = t.value; } }); window.sekaiEditable.colors?.forEach(c => { if (c.cssVar) document.documentElement.style.setProperty(c.cssVar, c.value); }); } // --- AUDIO ENGINE --- const AudioEngine = { ctx: null, init() { try { const AudioCtx = window.AudioContext || window.webkitAudioContext; if (AudioCtx) this.ctx = new AudioCtx(); } catch(e) {} }, playSound(id) { const el = document.getElementById(`sfx-${id}`); if (el?.src) { el.currentTime = 0; el.play().catch(()=>{}); } else { this.playProcedural(id); } }, playProcedural(type) { if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); const now = this.ctx.currentTime; if (type === 'door') { osc.frequency.setValueAtTime(100, now); osc.frequency.linearRampToValueAtTime(10, now+0.5); gain.gain.setTargetAtTime(0, now, 0.1); osc.start(); osc.stop(now+0.5); } else if (type === 'roar') { osc.type = 'sawtooth'; osc.frequency.setValueAtTime(80, now); osc.frequency.exponentialRampToValueAtTime(30, now+1); gain.gain.setValueAtTime(0.5, now); osc.start(); osc.stop(now+1); } } }; function initBGM() { const el = document.getElementById('music-ambient'); if (el?.src) { el.loop = true; const play = () => el.play().catch(() => { document.addEventListener('touchstart', play, {once:true}); }); play(); } } // --- 3D SETUP --- async function initThree() { scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById('three-container').appendChild(renderer.domElement); raycaster = new THREE.Raycaster(); clock = new THREE.Clock(); // Lights const ambient = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambient); const mainLight = new THREE.PointLight(0xffffff, 100, 50); mainLight.position.set(0, 5, 0); scene.add(mainLight); meshes.light = mainLight; // Loader const loader = new THREE.TextureLoader(); const loadTex = (id) => { const url = getConfig('images', id); return url ? loader.load(url) : null; }; textures.room1 = loadTex('room1_texture'); textures.room2 = loadTex('room2_texture'); textures.monster = loadTex('monster_face'); // Room 1 (Bright) const room1Geo = new THREE.BoxGeometry(10, 8, 10); const room1Mat = new THREE.MeshStandardMaterial({ color: 0xffffff, map: textures.room1, side: THREE.BackSide, emissive: 0xffffff, emissiveIntensity: 0.2 }); const room1 = new THREE.Mesh(room1Geo, room1Mat); room1.position.set(0, 4, 0); scene.add(room1); // Door to Room 2 const doorGeo = new THREE.PlaneGeometry(2, 4); const doorMat = new THREE.MeshStandardMaterial({ color: 0x333333, side: THREE.DoubleSide }); const door = new THREE.Mesh(doorGeo, doorMat); door.position.set(0, 2, -4.9); door.name = "door1"; scene.add(door); window.appState.interactiveObjects.push(door); // Room 2 (Dark) const room2Geo = new THREE.BoxGeometry(10, 8, 15); const room2Mat = new THREE.MeshStandardMaterial({ color: 0x222222, map: textures.room2, side: THREE.BackSide }); const room2 = new THREE.Mesh(room2Geo, room2Mat); room2.position.set(0, 4, -17.5); scene.add(room2); // Table & Remote const tableGeo = new THREE.BoxGeometry(2, 0.8, 1); const tableMat = new THREE.MeshStandardMaterial({ color: 0x444444 }); const table = new THREE.Mesh(tableGeo, tableMat); table.position.set(0, 0.4, -20); scene.add(table); const remoteGeo = new THREE.BoxGeometry(0.2, 0.05, 0.4); const remoteMat = new THREE.MeshStandardMaterial({ color: 0x0000ff }); const remote = new THREE.Mesh(remoteGeo, remoteMat); remote.position.set(0, 0.85, -20); remote.name = "remote"; scene.add(remote); window.appState.interactiveObjects.push(remote); // Monster (Hidden) const monsterMat = new THREE.SpriteMaterial({ map: textures.monster, transparent: true }); const monster = new THREE.Sprite(monsterMat); monster.scale.set(4, 4, 1); monster.position.copy(window.appState.monster.pos); scene.add(monster); meshes.monster = monster; // Vent const ventGeo = new THREE.PlaneGeometry(1.5, 1.5); const ventMat = new THREE.MeshStandardMaterial({ color: 0x111111, side: THREE.DoubleSide }); const vent = new THREE.Mesh(ventGeo, ventMat); vent.position.set(4.5, 0.75, -24.5); vent.rotation.y = -Math.PI/2; vent.name = "vent"; scene.add(vent); window.appState.interactiveObjects.push(vent); window.addEventListener('resize', onWindowResize); document.addEventListener('pointerdown', onPointerDown); initJoystick(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function onPointerDown(event) { if (window.appState.gameState === 'cutscene') return; pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, camera); const intersects = raycaster.intersectObjects(window.appState.interactiveObjects); if (intersects.length > 0) { const obj = intersects[0].object; const dist = camera.position.distanceTo(obj.position); if (dist < 4) handleInteraction(obj.name); } } function handleInteraction(name) { if (name === 'door1' && window.appState.gameState === 'room1') { window.appState.gameState = 'room2'; AudioEngine.playSound('door'); setStatus("ENTERED SECOND ROOM"); } else if (name === 'remote' && window.appState.gameState === 'room2') { pickUpRemote(); } else if (name === 'vent' && window.appState.gameState === 'chase') { enterVent(); } } function pickUpRemote() { window.appState.gameState = 'cutscene'; AudioEngine.playSound('drone'); document.getElementById('drone-hud').classList.add('active'); // Logic: Drone stays above you (we simulate by just showing HUD) // Sequence starts in 1s setTimeout(triggerMonsterCutscene, 1000); } function triggerMonsterCutscene() { // Force look at wall const targetPos = new THREE.Vector3(0, 1.6, -24.9); camera.lookAt(targetPos); AudioEngine.playSound('smash'); meshes.monster.position.set(0, 1.6, -24.5); window.appState.monster.active = true; const monsterHey = document.getElementById('monster-hey'); monsterHey.style.opacity = '1'; monsterHey.style.transform = 'translate(-50%, -50%) scale(1.5)'; AudioEngine.playSound('roar'); document.getElementById('vignette').classList.add('danger'); // Stare for 2 seconds setTimeout(() => { // Look away / snap back camera.rotation.y += Math.PI; // Turn around monsterHey.style.opacity = '0'; startChase(); }, 2000); } function startChase() { window.appState.gameState = 'chase'; setStatus("RUN TO THE VENT!"); const container = document.getElementById('timer-bar-container'); const bar = document.getElementById('timer-bar'); container.style.opacity = 1; bar.style.transition = `width ${window.appState.config.chaseTime}ms linear`; bar.style.width = '0%'; setTimeout(() => { if (window.appState.gameState === 'chase') triggerDeath(); }, window.appState.config.chaseTime); } function triggerDeath() { window.appState.gameState = 'lose'; document.getElementById('scene-lose').classList.add('visible'); AudioEngine.playSound('roar'); } function enterVent() { window.appState.gameState = 'win'; AudioEngine.playSound('vent'); document.getElementById('scene-win').classList.add('visible'); document.getElementById('timer-bar-container').style.opacity = 0; } function setStatus(text) { const el = document.getElementById('status-text'); el.textContent = text; el.style.opacity = 1; setTimeout(() => el.style.opacity = 0, 2000); } function initJoystick() { const zone = document.getElementById('joystick-zone'); const handle = document.getElementById('joystick-handle'); document.addEventListener('touchstart', (e) => { if (window.appState.gameState === 'cutscene') return; window.appState.joystick.active = true; window.appState.joystick.start = { x: e.touches[0].clientX, y: e.touches[0].clientY }; zone.style.display = 'block'; }); document.addEventListener('touchmove', (e) => { if (!window.appState.joystick.active) return; const dx = e.touches[0].clientX - window.appState.joystick.start.x; const dy = e.touches[0].clientY - window.appState.joystick.start.y; const dist = Math.min(Math.sqrt(dx*dx + dy*dy), 50); const angle = Math.atan2(dy, dx); window.appState.joystick.vector = { x: Math.cos(angle) * (dist/50), y: Math.sin(angle) * (dist/50) }; handle.style.transform = `translate(calc(-50% + ${Math.cos(angle)*dist}px), calc(-50% + ${Math.sin(angle)*dist}px))`; }); document.addEventListener('touchend', () => { window.appState.joystick.active = false; window.appState.joystick.vector = { x: 0, y: 0 }; }); } function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); if (window.appState.gameState !== 'cutscene' && !window.appState.gameState.includes('win') && !window.appState.gameState.includes('lose')) { // Movement const moveSpeed = 4 * delta; const dirX = -window.appState.joystick.vector.y; const dirZ = window.appState.joystick.vector.x; camera.position.x += dirZ * moveSpeed; camera.position.z += dirX * moveSpeed; // Collision with room bounds camera.position.x = Math.max(-4.5, Math.min(4.5, camera.position.x)); if (window.appState.gameState === 'room1') { camera.position.z = Math.max(-4.5, Math.min(4.5, camera.position.z)); } else { camera.position.z = Math.max(-24.5, Math.min(-5.5, camera.position.z)); } } // Monster Chase if (window.appState.gameState === 'chase') { const mSpeed = 2.5 * delta; const mDir = new THREE.Vector3().subVectors(camera.position, meshes.monster.position).normalize(); meshes.monster.position.addScaledVector(mDir, mSpeed); if (camera.position.distanceTo(meshes.monster.position) < 1.2) triggerDeath(); } renderer.render(scene, camera); } async function initApp() { applyAllEditableValues(); AudioEngine.init(); initBGM(); await initThree(); animate(); lucide.createIcons(); document.getElementById('btn-share-win').addEventListener('click', () => sendMessage('invoke_share', { title: "I Survived Vex 3D!" })); document.getElementById('btn-share-lose').addEventListener('click', () => sendMessage('invoke_share', { title: "Vex 3D caught me..." })); } document.addEventListener('DOMContentLoaded', initApp); // --- PREVIEW EDITING API --- window.addEventListener('message', (event) => { const msg = event.data; if (msg?.origin !== 'sekai_gaming_iframe_api') return; if (msg.type === 'get_editable_metadata') { window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'receive_editable_metadata', taskId: msg.taskId, data: window.sekaiEditable }, '*'); } else if (msg.type === 'apply_change') { location.reload(); // Complex 3D re-init best handled by reload for simple preview } });