Cursed Clash

SELECT YOUR SORCERER

Loading...

PLAYER CURSED TOKYO AREA
00:00.00 SIMULATION TIME
000 KM/H
CURSED TURBO
BRAKE
GAS
LISTENING...

DOMAIN
EXPANSION

MALEVOLENT SHRINE

DRIVING RECORD

RECORD SET

Tokyo Expressway

01:45.32

Driving Grade

SPECIAL GRADE

function switchBGM(type) { const bgm = document.getElementById('music-bgm'); const boss = document.getElementById('music-boss'); if(bgm) bgm.pause(); if(boss) boss.pause(); const target = type === 'boss' ? boss : bgm; if(target && target.src) { target.currentTime = 0; target.play().catch(()=>{}); } } function playSound(type) { const customSound = document.getElementById(`sfx-${type}`); if (customSound && customSound.src && customSound.src !== window.location.href) { customSound.currentTime = 0; customSound.play().catch(()=>{}); return; } // Procedural Fallbacks if (audioCtx.state === 'suspended') audioCtx.resume(); const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.connect(gain); gain.connect(audioCtx.destination); const now = audioCtx.currentTime; if (type === 'hit') { osc.type = 'square'; osc.frequency.setValueAtTime(150, now); osc.frequency.exponentialRampToValueAtTime(40, now + 0.1); gain.gain.setValueAtTime(0.5, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.1); osc.start(now); osc.stop(now + 0.1); } else if (type === 'block') { osc.type = 'triangle'; osc.frequency.setValueAtTime(200, now); osc.frequency.linearRampToValueAtTime(100, now + 0.2); gain.gain.setValueAtTime(0.3, now); gain.gain.linearRampToValueAtTime(0.01, now + 0.2); osc.start(now); osc.stop(now + 0.2); } else if (type === 'energy') { osc.type = 'sine'; osc.frequency.setValueAtTime(300, now); osc.frequency.linearRampToValueAtTime(800, now + 0.5); gain.gain.setValueAtTime(0, now); gain.gain.linearRampToValueAtTime(0.3, now + 0.5); osc.start(now); osc.stop(now + 0.5); } else if (type === 'ultimate') { osc.type = 'sawtooth'; osc.frequency.setValueAtTime(50, now); osc.frequency.linearRampToValueAtTime(20, now + 1.5); gain.gain.setValueAtTime(0.8, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 1.5); // Add noise const bufferSize = audioCtx.sampleRate * 1.5; const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1; const noise = audioCtx.createBufferSource(); noise.buffer = buffer; const noiseFilter = audioCtx.createBiquadFilter(); noiseFilter.type = 'lowpass'; noiseFilter.frequency.setValueAtTime(1000, now); noiseFilter.frequency.linearRampToValueAtTime(100, now + 1.5); const noiseGain = audioCtx.createGain(); noiseGain.gain.setValueAtTime(0.5, now); noiseGain.gain.exponentialRampToValueAtTime(0.01, now + 1.5); noise.connect(noiseFilter); noiseFilter.connect(noiseGain); noiseGain.connect(audioCtx.destination); osc.start(now); osc.stop(now + 1.5); noise.start(now); } } // ========================================== // 5. CORE LOGIC // ========================================== function getImageUrl(id) { const configUrl = getConfig('images', id); if (configUrl) return configUrl; // Fallbacks if (id.startsWith('bg_')) return DEFAULT_BGS[id] || DEFAULT_BGS.bg_tokyo; const charId = id.replace('char_', ''); return DEFAULT_AVATARS[charId] || DEFAULT_AVATARS.gojo; } function showScreen(screenId) { ['screen-select', 'screen-battle', 'cinematic-overlay', 'screen-result'].forEach(id => { if(els[id]) els[id].classList.add('hidden-screen'); }); if(els[screenId]) els[screenId].classList.remove('hidden-screen'); } // --- Selection Screen --- let selectedCharIndex = 0; const charKeys = Object.keys(CHARACTERS); function initSelectScreen() { window.appState.gameState = 'select'; showScreen('screen-select'); els['char-carousel'].innerHTML = ''; charKeys.forEach((key, index) => { const char = CHARACTERS[key]; const div = document.createElement('div'); div.className = `char-card absolute inset-0 border-4 rounded-xl overflow-hidden cursor-pointer ${index === 0 ? 'active z-20' : 'z-10'}`; div.style.backgroundImage = `url('${getImageUrl(char.imgId)}')`; div.style.backgroundSize = 'cover'; div.style.backgroundPosition = 'center'; // Stack them visually if (index !== 0) div.style.transform = `translateX(${(index) * 110}%) scale(0.8)`; div.addEventListener('pointerdown', () => selectCharacter(index)); els['char-carousel'].appendChild(div); }); updateSelectUI(); } function selectCharacter(index) { playSound('block'); selectedCharIndex = index; const cards = els['char-carousel'].children; for(let i=0; i index ? 110 : -110; cards[i].style.transform = `translateX(${offset}%) scale(0.8)`; } } updateSelectUI(); } function updateSelectUI() { const char = CHARACTERS[charKeys[selectedCharIndex]]; els['select-char-name'].textContent = char.name; els['select-char-name'].style.color = char.color; document.documentElement.style.setProperty('--cursed-energy', char.color); } // --- Battle Logic --- let battleLoopId; function startLadder() { window.appState.ladderStage = 0; const charDef = CHARACTERS[charKeys[selectedCharIndex]]; window.appState.player.id = charDef.id; // Re-apply max HP from config in case it changed const maxHp = window.appState.config.playerMaxHp; window.appState.player.maxHp = maxHp; window.appState.player.hp = maxHp; window.appState.player.energy = 0; els['sprite-player'].style.backgroundImage = `url('${getImageUrl(charDef.imgId)}')`; els['player-name'].textContent = charDef.name.toUpperCase(); els['player-name'].style.color = charDef.color; startStage(); } function startStage() { const stage = window.appState.ladderStage; if (stage >= ENEMIES.length) { showResult(true); return; } const enemyDef = ENEMIES[stage]; window.appState.enemy.id = enemyDef.id; window.appState.enemy.maxHp = enemyDef.hp; window.appState.enemy.hp = enemyDef.hp; window.appState.enemy.state = 'idle'; els['sprite-enemy'].style.backgroundImage = `url('${getImageUrl(enemyDef.imgId)}')`; els['enemy-name'].textContent = enemyDef.name.toUpperCase(); els['hud-ladder'].textContent = `STAGE ${stage + 1}`; els['battle-bg'].style.backgroundImage = `url('${getImageUrl(enemyDef.bg)}')`; if (stage === ENEMIES.length - 1) switchBGM('boss'); else switchBGM('battle'); updateBars(); showScreen('screen-battle'); window.appState.gameState = 'battle'; els['combat-status'].textContent = "FIGHT!"; gsap.fromTo(els['combat-status'], {scale: 2, opacity: 0}, {scale: 1, opacity: 1, duration: 0.5, yoyo: true, repeat: 1, onComplete: () => els['combat-status'].textContent = ""}); scheduleEnemyAttack(); } function updateBars() { const p = window.appState.player; const e = window.appState.enemy; els['player-hp-fill'].style.transform = `scaleX(${Math.max(0, p.hp / p.maxHp)})`; els['enemy-hp-fill'].style.transform = `scaleX(${Math.max(0, e.hp / e.maxHp)})`; const energyPercent = Math.min(100, p.energy); els['player-energy-fill'].style.width = `${energyPercent}%`; if (energyPercent >= 100) { els['btn-mic'].classList.remove('opacity-50', 'pointer-events-none'); els['btn-mic'].classList.add('ready'); } else { els['btn-mic'].classList.add('opacity-50', 'pointer-events-none'); els['btn-mic'].classList.remove('ready'); } } function spawnFloatingText(text, containerId, color = 'white', scale = 1) { const container = document.getElementById(containerId); if(!container) return; const el = document.createElement('div'); el.className = 'damage-text'; el.textContent = text; el.style.color = color; el.style.left = `${20 + Math.random() * 40}%`; el.style.top = `${30 + Math.random() * 40}%`; container.appendChild(el); gsap.to(el, { y: -100, opacity: 0, scale: scale, duration: 0.8, ease: "power2.out", onComplete: () => el.remove() }); } // --- Player Actions --- function handlePlayerAttack() { if (window.appState.gameState !== 'battle') return; // Animation const sprite = els['sprite-player']; sprite.classList.remove('attacking-player'); void sprite.offsetWidth; // trigger reflow sprite.classList.add('attacking-player'); // Damage const dmg = window.appState.config.baseDamage + Math.floor(Math.random() * 5); window.appState.enemy.hp -= dmg; // Energy window.appState.player.energy += window.appState.config.energyPerHit; playSound('hit'); triggerHitEffect('enemy', dmg); checkWinLoss(); } function setBlocking(isBlocking) { if (window.appState.gameState !== 'battle') return; window.appState.player.isBlocking = isBlocking; if(isBlocking) { els['sprite-player'].style.filter = 'brightness(0.7) sepia(1) hue-rotate(200deg)'; els['btn-block'].style.opacity = '0.8'; } else { els['sprite-player'].style.filter = ''; els['btn-block'].style.opacity = '0.3'; } } // --- Enemy AI --- function scheduleEnemyAttack() { if (window.appState.gameState !== 'battle') return; const enemyDef = ENEMIES[window.appState.ladderStage]; const delay = enemyDef.attackRate * (0.8 + Math.random() * 0.4); // Add variance battleLoopId = setTimeout(() => { if (window.appState.gameState !== 'battle') return; // Show intent els['enemy-intent'].style.opacity = 1; playSound('energy'); setTimeout(() => { if (window.appState.gameState !== 'battle') return; els['enemy-intent'].style.opacity = 0; executeEnemyAttack(enemyDef.damage); scheduleEnemyAttack(); }, 600); // Reaction time window }, delay); } function executeEnemyAttack(baseDamage) { const sprite = els['sprite-enemy']; sprite.classList.remove('attacking-enemy'); void sprite.offsetWidth; sprite.classList.add('attacking-enemy'); let dmg = baseDamage; let color = '#ef4444'; let txt = `-${dmg}`; if (window.appState.player.isBlocking) { dmg = Math.floor(dmg * 0.2); // 80% reduction playSound('block'); color = '#9ca3af'; txt = 'BLOCKED'; } else { playSound('hit'); } window.appState.player.hp -= dmg; triggerHitEffect('player', txt, color); checkWinLoss(); } function triggerHitEffect(target, text, color = 'white') { const spriteId = target === 'enemy' ? 'sprite-enemy' : 'sprite-player'; const zoneId = target === 'enemy' ? 'zone-enemy' : 'zone-player'; const sprite = els[spriteId]; sprite.classList.remove('hit'); void sprite.offsetWidth; sprite.classList.add('hit'); document.getElementById('app-container').classList.remove('shake'); void document.getElementById('app-container').offsetWidth; document.getElementById('app-container').classList.add('shake'); spawnFloatingText(text, zoneId, color); updateBars(); } function checkWinLoss() { if (window.appState.enemy.hp <= 0) { window.appState.gameState = 'transition'; clearTimeout(battleLoopId); window.appState.enemy.hp = 0; updateBars(); els['combat-status'].textContent = "PURIFIED"; els['combat-status'].style.color = '#10b981'; // Explode enemy gsap.to(els['sprite-enemy'], { scale: 1.5, opacity: 0, filter: 'brightness(5)', duration: 0.5, onComplete: () => { window.appState.ladderStage++; setTimeout(startStage, 1000); } }); } else if (window.appState.player.hp <= 0) { window.appState.gameState = 'transition'; clearTimeout(battleLoopId); window.appState.player.hp = 0; updateBars(); els['combat-status'].textContent = "DEFEATED"; els['combat-status'].style.color = '#ef4444'; gsap.to(els['sprite-player'], { y: 100, opacity: 0, rotation: -20, duration: 0.5, onComplete: () => setTimeout(() => showResult(false), 1000) }); } } // ========================================== // 6. VOICE / ULTIMATE SYSTEM // ========================================== async function startRecording() { if(window.appState.player.energy < 100) return; if(window.appState.recording) return; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : 'audio/mp4'; window.appState.mediaRecorder = new MediaRecorder(stream, { mimeType }); window.appState.audioChunks = []; window.appState.mediaRecorder.ondataavailable = e => { if (e.data.size > 0) window.appState.audioChunks.push(e.data); }; window.appState.mediaRecorder.onstop = processAudio; window.appState.mediaRecorder.start(); window.appState.recording = true; els['btn-mic'].classList.add('recording'); els['btn-mic'].classList.remove('ready'); els['mic-status'].style.opacity = '1'; // Slow down time/enemy attacks while recording clearTimeout(battleLoopId); } catch (err) { console.error('Mic access denied', err); // Fallback: If mic fails, just execute ultimate immediately executeUltimate(); } } function stopRecording() { if(!window.appState.recording || !window.appState.mediaRecorder) return; window.appState.mediaRecorder.stop(); window.appState.mediaRecorder.stream.getTracks().forEach(t => t.stop()); window.appState.recording = false; els['btn-mic'].classList.remove('recording'); els['mic-status'].style.opacity = '0'; } async function processAudio() { els['mic-status'].textContent = 'PROCESSING...'; els['mic-status'].style.opacity = '1'; const blob = new Blob(window.appState.audioChunks, { type: 'audio/webm' }); const reader = new FileReader(); reader.onloadend = async () => { const base64Audio = reader.result; const charDef = CHARACTERS[window.appState.player.id]; try { // Send to parent API const taskId = crypto.randomUUID(); window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'gen_transcript', taskId: taskId, data: { audio: base64Audio, initialPrompt: charDef.ultimateWords.join(' ') } }, '*'); // Wait for response via event listener setup in init() const handleResponse = (e) => { const msg = e.data; if (msg?.origin === 'sekai_gaming_iframe_api' && msg?.type === 'receive_gen_transcript' && msg.taskId === taskId) { window.removeEventListener('message', handleResponse); els['mic-status'].style.opacity = '0'; els['mic-status'].textContent = 'LISTENING...'; if (msg.error) { console.error('Transcription error', msg.error); executeUltimate(); // Fallback on error so game doesn't break return; } const text = msg.data.text.toLowerCase(); console.log("Transcribed:", text); // Fuzzy match const matched = charDef.ultimateWords.some(word => text.includes(word)); if (matched) { executeUltimate(); } else { // Whiffed spawnFloatingText("FAILED", 'zone-player', '#ef4444'); window.appState.player.energy = 0; updateBars(); scheduleEnemyAttack(); // Resume enemy } } }; window.addEventListener('message', handleResponse); } catch (e) { console.error("PostMessage failed", e); executeUltimate(); // Fallback } }; reader.readAsDataURL(blob); } function executeUltimate() { window.appState.gameState = 'cinematic'; const charDef = CHARACTERS[window.appState.player.id]; window.appState.player.energy = 0; updateBars(); playSound('ultimate'); // Setup Cinematic Screen els['cinematic-text'].innerHTML = charDef.ultName.replace(': ', '
'); els['cinematic-text'].style.color = charDef.color; els['cinematic-bg'].style.backgroundImage = `url('${getImageUrl(charDef.imgId)}')`; showScreen('cinematic-overlay'); // GSAP Animation Sequence const tl = gsap.timeline(); tl.fromTo(els['cinematic-text'], { scale: 0.5, opacity: 0, filter: 'blur(10px)' }, { scale: 1, opacity: 1, filter: 'blur(0px)', duration: 0.5, ease: 'back.out(1.7)' } ) .to(els['cinematic-bg'], { scale: 1, duration: 2, ease: 'power1.out' }, '<') .to('#cinematic-overlay', { backgroundColor: charDef.color, duration: 0.1, yoyo: true, repeat: 3 }, '+=0.5') .to('#cinematic-overlay', { opacity: 0, duration: 0.3 }, '+=0.2') .call(() => { showScreen('screen-battle'); // Apply massive damage const dmg = 9999; window.appState.enemy.hp -= dmg; triggerHitEffect('enemy', "ANNIHILATED", charDef.color); checkWinLoss(); }); } // ========================================== // 7. RESULT / SHARE SYSTEM // ========================================== function showResult(isWin) { window.appState.gameState = 'result'; showScreen('screen-result'); const charDef = CHARACTERS[window.appState.player.id]; els['result-avatar'].style.backgroundImage = `url('${getImageUrl(charDef.imgId)}')`; els['result-avatar'].style.borderColor = charDef.color; if (isWin) { playSound('win'); els['result-title'].textContent = "VICTORY"; els['result-title'].style.color = '#22c55e'; els['result-desc'].textContent = "You defeated"; els['result-enemy'].textContent = ENEMIES[ENEMIES.length-1].name.toUpperCase(); els['result-rank'].textContent = "SPECIAL GRADE"; } else { els['result-title'].textContent = "DEFEATED"; els['result-title'].style.color = '#ef4444'; els['result-desc'].textContent = "Felled by"; els['result-enemy'].textContent = ENEMIES[window.appState.ladderStage].name.toUpperCase(); els['result-rank'].textContent = `STAGE ${window.appState.ladderStage + 1}`; } // Report App Result (Fire-and-forget) const score = isWin ? 1000 + (window.appState.player.hp) : window.appState.ladderStage * 100; try { window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'save_app_result', taskId: crypto.randomUUID(), data: { score: score, public: true, result: { win: isWin, character: charDef.name, stage: window.appState.ladderStage } } }, '*'); } catch(e){} } async function handleShare() { const shareBtn = els['btn-share']; if (shareBtn.dataset.loading === 'true') return; shareBtn.dataset.loading = 'true'; shareBtn.disabled = true; shareBtn.innerHTML = ' GENERATING...'; lucide.createIcons(); try { const element = document.getElementById('share-zone'); const jpgImg = await window.snapdom.toJpg(element, { quality: 0.8, width: element.offsetWidth * 2, height: element.offsetHeight * 2, }); window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'invoke_share', taskId: crypto.randomUUID(), data: { title: `I reached ${els['result-rank'].textContent} in Cursed Clash!`, coverImageBase64: jpgImg.src } }, '*'); } catch (e) { console.error('Share failed:', e); } finally { shareBtn.dataset.loading = 'false'; shareBtn.disabled = false; shareBtn.innerHTML = ' SHARE RESULT'; lucide.createIcons(); } } // ========================================== // 8. INITIALIZATION & EVENTS // ========================================== function bindEvents() { // UI Buttons els['btn-start'].addEventListener('click', startLadder); els['btn-restart'].addEventListener('click', initSelectScreen); els['btn-share'].addEventListener('click', handleShare); // Combat Controls const attackZone = els['btn-attack']; const blockZone = els['btn-block']; const micBtn = els['btn-mic']; // Touch events for mobile attackZone.addEventListener('pointerdown', handlePlayerAttack); blockZone.addEventListener('pointerdown', () => setBlocking(true)); blockZone.addEventListener('pointerup', () => setBlocking(false)); blockZone.addEventListener('pointerleave', () => setBlocking(false)); micBtn.addEventListener('pointerdown', (e) => { e.preventDefault(); startRecording(); }); micBtn.addEventListener('pointerup', (e) => { e.preventDefault(); stopRecording(); }); micBtn.addEventListener('pointerleave', (e) => { e.preventDefault(); stopRecording(); }); } function initApp() { applyAllEditableValues(); initBGM(); cacheDOM(); bindEvents(); lucide.createIcons(); initSelectScreen(); } document.addEventListener('DOMContentLoaded', initApp); // ========================================== // 9. 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 } }, '*'); // Refresh UI if needed if(window.appState.gameState === 'select') initSelectScreen(); 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; } } return true; } return false; } })();