|
<script lang="ts"> |
|
import { onMount } from 'svelte'; |
|
import { fade } from 'svelte/transition'; |
|
import type { PicletInstance, BattleMove } from '$lib/db/schema'; |
|
import BattleField from '../Battle/BattleField.svelte'; |
|
import BattleControls from '../Battle/BattleControls.svelte'; |
|
import { BattleEngine } from '$lib/battle-engine/BattleEngine'; |
|
import type { BattleState, MoveAction, SwitchAction } from '$lib/battle-engine/types'; |
|
import { picletInstanceToBattleDefinition, battlePicletToInstance, stripBattlePrefix } from '$lib/utils/battleConversion'; |
|
import { calculateBattleXp, processAllLevelUps } from '$lib/services/levelingService'; |
|
import { db } from '$lib/db/index'; |
|
import { getEffectivenessText, getEffectivenessColor } from '$lib/types/picletTypes'; |
|
|
|
export let playerPiclet: PicletInstance; |
|
export let enemyPiclet: PicletInstance; |
|
export let isWildBattle: boolean = true; |
|
export let onBattleEnd: (result: any) => void = () => {}; |
|
export let rosterPiclets: PicletInstance[] = []; |
|
|
|
|
|
let battleEngine: BattleEngine; |
|
let battleState: BattleState; |
|
let currentPlayerPiclet = playerPiclet; |
|
let currentEnemyPiclet = enemyPiclet; |
|
|
|
|
|
let currentMessage = isWildBattle |
|
? `A wild ${enemyPiclet.nickname} appeared!` |
|
: `Trainer wants to battle!`; |
|
let battlePhase: 'intro' | 'main' | 'moveSelect' | 'picletSelect' | 'ended' = 'intro'; |
|
let processingTurn = false; |
|
let battleEnded = false; |
|
|
|
|
|
let showWhiteFlash = false; |
|
let playerTrainerVisible = false; |
|
let enemyTrainerVisible = false; |
|
let playerTrainerSlideOut = false; |
|
let enemyTrainerSlideOut = false; |
|
|
|
|
|
let waitingForContinue = false; |
|
let messageQueue: string[] = []; |
|
let currentMessageIndex = 0; |
|
let continueCallback: (() => void) | null = null; |
|
|
|
|
|
let playerHpPercentage = playerPiclet.currentHp / playerPiclet.maxHp; |
|
let enemyHpPercentage = enemyPiclet.currentHp / enemyPiclet.maxHp; |
|
|
|
|
|
let playerEffects: Array<{type: string, emoji: string, duration: number}> = []; |
|
let enemyEffects: Array<{type: string, emoji: string, duration: number}> = []; |
|
let playerFlash = false; |
|
let enemyFlash = false; |
|
let playerFaint = false; |
|
let enemyFaint = false; |
|
let playerLunge = false; |
|
let enemyLunge = false; |
|
|
|
|
|
let battleResultsVisible = false; |
|
let battleResults = { |
|
victory: false, |
|
xpGained: 0, |
|
levelUps: [], |
|
newLevel: 0 |
|
}; |
|
|
|
|
|
onMount(() => { |
|
|
|
|
|
const playerRosterDefinitions = rosterPiclets.map(p => picletInstanceToBattleDefinition(p)); |
|
const enemyDefinition = picletInstanceToBattleDefinition(enemyPiclet); |
|
|
|
|
|
const startingPlayerIndex = rosterPiclets.findIndex(p => p.id === playerPiclet.id); |
|
|
|
|
|
battleEngine = new BattleEngine(playerRosterDefinitions, enemyDefinition, playerPiclet.level, enemyPiclet.level); |
|
|
|
|
|
if (startingPlayerIndex > 0) { |
|
const initialSwitchAction: SwitchAction = { |
|
type: 'switch', |
|
piclet: 'player', |
|
newPicletIndex: startingPlayerIndex |
|
}; |
|
battleEngine.executeAction(initialSwitchAction, 'player'); |
|
} |
|
|
|
battleState = battleEngine.getState(); |
|
|
|
|
|
setTimeout(() => { |
|
if (!isWildBattle) { |
|
|
|
currentMessage = `Go, ${enemyPiclet.nickname}!`; |
|
enemyTrainerSlideOut = true; |
|
setTimeout(() => { |
|
currentMessage = `Go, ${playerPiclet.nickname}!`; |
|
playerTrainerSlideOut = true; |
|
setTimeout(() => { |
|
currentMessage = `What will ${playerPiclet.nickname} do?`; |
|
battlePhase = 'main'; |
|
}, 2000); |
|
}, 2000); |
|
} else { |
|
|
|
currentMessage = `Go, ${playerPiclet.nickname}!`; |
|
playerTrainerSlideOut = true; |
|
setTimeout(() => { |
|
currentMessage = `What will ${playerPiclet.nickname} do?`; |
|
battlePhase = 'main'; |
|
}, 2000); |
|
} |
|
}, 2000); |
|
}); |
|
|
|
function handleAction(action: string) { |
|
if (processingTurn || battleEnded) return; |
|
|
|
switch (action) { |
|
case 'catch': |
|
if (isWildBattle) { |
|
processingTurn = true; |
|
currentMessage = 'You threw a Piclet Ball!'; |
|
setTimeout(() => { |
|
currentMessage = 'The wild piclet broke free!'; |
|
processingTurn = false; |
|
}, 2000); |
|
} |
|
break; |
|
case 'run': |
|
if (isWildBattle) { |
|
currentMessage = 'Got away safely!'; |
|
battleEnded = true; |
|
setTimeout(() => onBattleEnd(false), 1500); |
|
} else { |
|
currentMessage = "You can't run from a trainer battle!"; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
function handleMoveSelect(move: BattleMove) { |
|
if (!battleEngine) return; |
|
|
|
battlePhase = 'main'; |
|
processingTurn = true; |
|
|
|
|
|
const battleMove = battleState.playerPiclet.moves.find(m => m.move.name === move.name); |
|
if (!battleMove) return; |
|
|
|
const moveAction: MoveAction = { |
|
type: 'move', |
|
moveIndex: battleState.playerPiclet.moves.indexOf(battleMove) |
|
}; |
|
|
|
try { |
|
|
|
const availableEnemyMoves = battleState.opponentPiclet.moves.filter(m => m.currentPP > 0); |
|
if (availableEnemyMoves.length === 0) { |
|
currentMessage = `${currentEnemyPiclet.nickname} has no moves left!`; |
|
processingTurn = false; |
|
return; |
|
} |
|
|
|
const randomEnemyMove = availableEnemyMoves[Math.floor(Math.random() * availableEnemyMoves.length)]; |
|
const enemyMoveIndex = battleState.opponentPiclet.moves.indexOf(randomEnemyMove); |
|
const enemyAction: MoveAction = { |
|
type: 'move', |
|
moveIndex: enemyMoveIndex |
|
}; |
|
|
|
|
|
const logBefore = battleEngine.getLog(); |
|
|
|
|
|
battleEngine.executeActions(moveAction, enemyAction); |
|
battleState = battleEngine.getState(); |
|
|
|
|
|
const logAfter = battleEngine.getLog(); |
|
const newLogEntries = logAfter.slice(logBefore.length); |
|
|
|
const filteredLogEntries = newLogEntries.filter(message => !message.includes('fainted')); |
|
const result = { log: filteredLogEntries }; |
|
|
|
|
|
if (result.log && result.log.length > 0) { |
|
showMessageSequence(result.log, finalizeTurn); |
|
} else { |
|
finalizeTurn(); |
|
} |
|
|
|
function finalizeTurn() { |
|
|
|
updateUIFromBattleState(); |
|
|
|
|
|
if (battleState.winner) { |
|
battleEnded = true; |
|
const defeatedPiclet = battleState.winner === 'player' ? currentEnemyPiclet : currentPlayerPiclet; |
|
|
|
|
|
currentMessage = `${defeatedPiclet.nickname} fainted!`; |
|
|
|
|
|
if (battleState.winner === 'player') { |
|
enemyFaint = true; |
|
} else { |
|
playerFaint = true; |
|
} |
|
|
|
|
|
setTimeout(async () => { |
|
await handleBattleResults(battleState.winner === 'player'); |
|
}, 2500); |
|
} else { |
|
|
|
const newPlayerPiclet = battlePicletToInstance(battleState.playerPiclet, currentPlayerPiclet); |
|
const playerPicletChanged = currentPlayerPiclet.id !== newPlayerPiclet.id; |
|
|
|
if (playerPicletChanged) { |
|
|
|
const faintedPiclet = currentPlayerPiclet; |
|
currentMessage = `${faintedPiclet.nickname} fainted!`; |
|
playerFaint = true; |
|
|
|
|
|
setTimeout(() => { |
|
currentMessage = `Go, ${newPlayerPiclet.nickname}!`; |
|
|
|
setTimeout(() => { |
|
currentMessage = `What will ${currentPlayerPiclet.nickname} do?`; |
|
processingTurn = false; |
|
}, 1000); |
|
}, 2500); |
|
} else { |
|
|
|
setTimeout(() => { |
|
currentMessage = `What will ${currentPlayerPiclet.nickname} do?`; |
|
processingTurn = false; |
|
}, 1000); |
|
} |
|
} |
|
} |
|
} catch (error) { |
|
console.error('Battle engine error:', error); |
|
currentMessage = 'Something went wrong in battle!'; |
|
processingTurn = false; |
|
} |
|
} |
|
|
|
|
|
function triggerVisualEffectsFromMessage(message: string) { |
|
|
|
const playerName = stripBattlePrefix(battleState?.playerPiclet?.definition?.name || ''); |
|
const enemyName = stripBattlePrefix(battleState?.opponentPiclet?.definition?.name || ''); |
|
|
|
|
|
if (message.includes(' used ')) { |
|
if (message.includes(playerName)) { |
|
triggerLungeAnimation('player'); |
|
} else if (message.includes(enemyName)) { |
|
triggerLungeAnimation('enemy'); |
|
} |
|
} |
|
|
|
|
|
if (message.includes('took') && message.includes('damage')) { |
|
if (message.includes(playerName)) { |
|
triggerDamageFlash('player'); |
|
updateUIFromBattleState(); |
|
} else if (message.includes(enemyName)) { |
|
triggerDamageFlash('enemy'); |
|
updateUIFromBattleState(); |
|
} |
|
} |
|
|
|
|
|
if (message.includes('critical hit')) { |
|
triggerEffect('both', 'critical', '💥', 1000); |
|
} |
|
|
|
|
|
if (message.includes("It's super effective")) { |
|
triggerEffect('both', 'superEffective', '⚡', 800); |
|
} else if (message.includes("not very effective")) { |
|
triggerEffect('both', 'notVeryEffective', '💨', 800); |
|
} |
|
|
|
|
|
if (message.includes('was burned')) { |
|
const target = message.includes(playerName) ? 'player' : 'enemy'; |
|
triggerEffect(target, 'burn', '🔥', 1200); |
|
} else if (message.includes('was poisoned')) { |
|
const target = message.includes(playerName) ? 'player' : 'enemy'; |
|
triggerEffect(target, 'poison', '☠️', 1200); |
|
} else if (message.includes('was paralyzed')) { |
|
const target = message.includes(playerName) ? 'player' : 'enemy'; |
|
triggerEffect(target, 'paralyze', '⚡', 1200); |
|
} else if (message.includes('fell asleep')) { |
|
const target = message.includes(playerName) ? 'player' : 'enemy'; |
|
triggerEffect(target, 'sleep', '😴', 1200); |
|
} else if (message.includes('was frozen')) { |
|
const target = message.includes(playerName) ? 'player' : 'enemy'; |
|
triggerEffect(target, 'freeze', '❄️', 1200); |
|
} |
|
|
|
|
|
if (message.includes("'s") && (message.includes('rose') || message.includes('fell'))) { |
|
const target = message.includes(playerName) ? 'player' : 'enemy'; |
|
const isIncrease = message.includes('rose'); |
|
|
|
if (message.includes('attack')) { |
|
triggerEffect(target, isIncrease ? 'attackUp' : 'attackDown', isIncrease ? '⚔️' : '🔻', 1000); |
|
} else if (message.includes('defense')) { |
|
triggerEffect(target, isIncrease ? 'defenseUp' : 'defenseDown', isIncrease ? '🛡️' : '🔻', 1000); |
|
} else if (message.includes('speed')) { |
|
triggerEffect(target, isIncrease ? 'speedUp' : 'speedDown', isIncrease ? '💨' : '🐌', 1000); |
|
} else if (message.includes('accuracy')) { |
|
triggerEffect(target, isIncrease ? 'accuracyUp' : 'accuracyDown', isIncrease ? '🎯' : '👁️', 1000); |
|
} |
|
} |
|
|
|
|
|
if (message.includes('recovered') && message.includes('HP')) { |
|
const target = message.includes(playerName) ? 'player' : 'enemy'; |
|
triggerEffect(target, 'heal', '💚', 1000); |
|
|
|
updateUIFromBattleState(); |
|
} |
|
|
|
|
|
if (message.includes('missed')) { |
|
triggerEffect('both', 'miss', '💫', 800); |
|
} |
|
|
|
|
|
|
|
} |
|
|
|
function triggerDamageFlash(target: 'player' | 'enemy') { |
|
if (target === 'player') { |
|
playerFlash = true; |
|
setTimeout(() => playerFlash = false, 1000); |
|
} else { |
|
enemyFlash = true; |
|
setTimeout(() => enemyFlash = false, 1000); |
|
} |
|
} |
|
|
|
function triggerFaintAnimation(target: 'player' | 'enemy') { |
|
if (target === 'player') { |
|
playerFaint = true; |
|
|
|
} else { |
|
enemyFaint = true; |
|
|
|
} |
|
} |
|
|
|
function triggerLungeAnimation(target: 'player' | 'enemy') { |
|
if (target === 'player') { |
|
playerLunge = true; |
|
setTimeout(() => playerLunge = false, 600); |
|
} else { |
|
enemyLunge = true; |
|
setTimeout(() => enemyLunge = false, 600); |
|
} |
|
} |
|
|
|
function triggerEffect(target: 'player' | 'enemy' | 'both', type: string, emoji: string, duration: number) { |
|
const effect = { type, emoji, duration }; |
|
|
|
if (target === 'player' || target === 'both') { |
|
playerEffects = [...playerEffects, effect]; |
|
setTimeout(() => { |
|
playerEffects = playerEffects.filter(e => e !== effect); |
|
}, duration); |
|
} |
|
|
|
if (target === 'enemy' || target === 'both') { |
|
enemyEffects = [...enemyEffects, effect]; |
|
setTimeout(() => { |
|
enemyEffects = enemyEffects.filter(e => e !== effect); |
|
}, duration); |
|
} |
|
} |
|
|
|
function showMessageSequence(messages: string[], callback: () => void) { |
|
if (!messages || messages.length === 0) { |
|
callback(); |
|
return; |
|
} |
|
|
|
messageQueue = messages; |
|
currentMessageIndex = 0; |
|
continueCallback = callback; |
|
|
|
|
|
currentMessage = messageQueue[0]; |
|
waitingForContinue = true; |
|
} |
|
|
|
function handleContinueTap() { |
|
if (!waitingForContinue || !messageQueue.length) return; |
|
|
|
|
|
triggerVisualEffectsFromMessage(currentMessage); |
|
|
|
currentMessageIndex++; |
|
|
|
if (currentMessageIndex >= messageQueue.length) { |
|
|
|
waitingForContinue = false; |
|
messageQueue = []; |
|
currentMessageIndex = 0; |
|
|
|
if (continueCallback) { |
|
continueCallback(); |
|
continueCallback = null; |
|
} |
|
} else { |
|
|
|
currentMessage = messageQueue[currentMessageIndex]; |
|
} |
|
} |
|
|
|
function updateUIFromBattleState() { |
|
if (!battleState) return; |
|
|
|
|
|
const newPlayerPiclet = battlePicletToInstance(battleState.playerPiclet, currentPlayerPiclet); |
|
const playerPicletChanged = currentPlayerPiclet.id !== newPlayerPiclet.id; |
|
|
|
if (playerPicletChanged) { |
|
|
|
showWhiteFlash = true; |
|
setTimeout(() => { |
|
currentPlayerPiclet = newPlayerPiclet; |
|
playerHpPercentage = battleState.playerPiclet.currentHp / battleState.playerPiclet.maxHp; |
|
showWhiteFlash = false; |
|
}, 300); |
|
} else { |
|
|
|
currentPlayerPiclet = newPlayerPiclet; |
|
playerHpPercentage = battleState.playerPiclet.currentHp / battleState.playerPiclet.maxHp; |
|
} |
|
|
|
|
|
currentEnemyPiclet = battlePicletToInstance(battleState.opponentPiclet, currentEnemyPiclet); |
|
enemyHpPercentage = battleState.opponentPiclet.currentHp / battleState.opponentPiclet.maxHp; |
|
} |
|
|
|
function handlePicletSelect(piclet: PicletInstance) { |
|
if (!battleEngine) return; |
|
|
|
battlePhase = 'main'; |
|
processingTurn = true; |
|
|
|
|
|
const picletIndex = rosterPiclets.findIndex(p => p.id === piclet.id); |
|
if (picletIndex === -1) { |
|
console.error('Selected piclet not found in roster'); |
|
processingTurn = false; |
|
return; |
|
} |
|
|
|
|
|
currentMessage = `Go, ${piclet.nickname}!`; |
|
showWhiteFlash = true; |
|
|
|
|
|
setTimeout(() => { |
|
currentPlayerPiclet = piclet; |
|
playerHpPercentage = piclet.currentHp / piclet.maxHp; |
|
showWhiteFlash = false; |
|
}, 300); |
|
|
|
const switchAction: SwitchAction = { |
|
type: 'switch', |
|
piclet: 'player', |
|
newPicletIndex: picletIndex |
|
}; |
|
|
|
try { |
|
|
|
const availableEnemyMoves = battleState.opponentPiclet.moves.filter(m => m.currentPP > 0); |
|
if (availableEnemyMoves.length === 0) { |
|
currentMessage = `${currentEnemyPiclet.nickname} has no moves left!`; |
|
processingTurn = false; |
|
return; |
|
} |
|
|
|
const randomEnemyMove = availableEnemyMoves[Math.floor(Math.random() * availableEnemyMoves.length)]; |
|
const enemyMoveIndex = battleState.opponentPiclet.moves.indexOf(randomEnemyMove); |
|
const enemyAction: MoveAction = { |
|
type: 'move', |
|
moveIndex: enemyMoveIndex |
|
}; |
|
|
|
|
|
setTimeout(() => { |
|
|
|
const logBefore = battleEngine.getLog(); |
|
|
|
|
|
battleEngine.executeActions(switchAction, enemyAction); |
|
battleState = battleEngine.getState(); |
|
|
|
processAfterSwitchTurn(logBefore); |
|
}, 1000); |
|
} catch (error) { |
|
console.error('Battle engine error:', error); |
|
currentMessage = 'Unable to switch Piclets!'; |
|
processingTurn = false; |
|
} |
|
} |
|
|
|
function processAfterSwitchTurn(logBefore: string[]) { |
|
try { |
|
|
|
|
|
const logAfter = battleEngine.getLog(); |
|
const newLogEntries = logAfter.slice(logBefore.length); |
|
|
|
const filteredLogEntries = newLogEntries.filter(message => !message.includes('fainted')); |
|
const result = { log: filteredLogEntries }; |
|
|
|
|
|
if (result.log && result.log.length > 0) { |
|
showMessageSequence(result.log, finalizeSwitchTurn); |
|
} else { |
|
finalizeSwitchTurn(); |
|
} |
|
|
|
function finalizeSwitchTurn() { |
|
|
|
updateUIFromBattleState(); |
|
|
|
|
|
if (battleState.winner) { |
|
battleEnded = true; |
|
const defeatedPiclet = battleState.winner === 'player' ? currentEnemyPiclet : currentPlayerPiclet; |
|
|
|
|
|
currentMessage = `${defeatedPiclet.nickname} fainted!`; |
|
|
|
|
|
if (battleState.winner === 'player') { |
|
enemyFaint = true; |
|
} else { |
|
playerFaint = true; |
|
} |
|
|
|
|
|
setTimeout(async () => { |
|
await handleBattleResults(battleState.winner === 'player'); |
|
}, 2500); |
|
} else { |
|
setTimeout(() => { |
|
currentMessage = `What will ${currentPlayerPiclet.nickname} do?`; |
|
processingTurn = false; |
|
}, 1000); |
|
} |
|
} |
|
} catch (error) { |
|
console.error('Switch error:', error); |
|
currentMessage = 'Unable to switch Piclets!'; |
|
processingTurn = false; |
|
} |
|
} |
|
|
|
function handleBack() { |
|
battlePhase = 'main'; |
|
} |
|
|
|
async function handleBattleResults(playerWon: boolean) { |
|
if (playerWon) { |
|
|
|
const xpGained = calculateBattleXp(currentEnemyPiclet, 1); |
|
|
|
if (xpGained > 0) { |
|
|
|
const updatedPlayerPiclet = { |
|
...currentPlayerPiclet, |
|
xp: currentPlayerPiclet.xp + xpGained |
|
}; |
|
currentPlayerPiclet = updatedPlayerPiclet; |
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1500)); |
|
|
|
|
|
const { newInstance, levelUpInfo } = processAllLevelUps(updatedPlayerPiclet); |
|
|
|
|
|
if (newInstance.id) { |
|
await db.picletInstances.update(newInstance.id, newInstance); |
|
} |
|
|
|
|
|
currentPlayerPiclet = newInstance; |
|
|
|
|
|
if (levelUpInfo.length > 0) { |
|
battleResults = { |
|
victory: true, |
|
xpGained, |
|
levelUps: levelUpInfo, |
|
newLevel: newInstance.level |
|
}; |
|
|
|
battleResultsVisible = true; |
|
|
|
|
|
setTimeout(() => { |
|
battleResultsVisible = false; |
|
onBattleEnd(true); |
|
}, 4000); |
|
} else { |
|
|
|
onBattleEnd(true); |
|
} |
|
} else { |
|
onBattleEnd(true); |
|
} |
|
} else { |
|
|
|
onBattleEnd(false); |
|
} |
|
} |
|
</script> |
|
|
|
<div class="battle-page" transition:fade={{ duration: 300 }}> |
|
<nav class="battle-nav"> |
|
<button class="back-button" on:click={() => onBattleEnd('cancelled')} style="display: none;"> |
|
← Back |
|
</button> |
|
<h1>{isWildBattle ? 'Wild Battle' : 'Battle'}</h1> |
|
<div class="nav-spacer"></div> |
|
</nav> |
|
|
|
<div class="battle-content"> |
|
<BattleField |
|
playerPiclet={currentPlayerPiclet} |
|
enemyPiclet={currentEnemyPiclet} |
|
{playerHpPercentage} |
|
{enemyHpPercentage} |
|
showIntro={battlePhase === 'intro'} |
|
{battleState} |
|
{playerEffects} |
|
{enemyEffects} |
|
{playerFlash} |
|
{enemyFlash} |
|
{playerFaint} |
|
{enemyFaint} |
|
{playerLunge} |
|
{enemyLunge} |
|
{isWildBattle} |
|
{showWhiteFlash} |
|
{playerTrainerVisible} |
|
{enemyTrainerVisible} |
|
{playerTrainerSlideOut} |
|
{enemyTrainerSlideOut} |
|
/> |
|
|
|
<BattleControls |
|
{currentMessage} |
|
{battlePhase} |
|
{processingTurn} |
|
{battleEnded} |
|
{isWildBattle} |
|
playerPiclet={currentPlayerPiclet} |
|
enemyPiclet={currentEnemyPiclet} |
|
{rosterPiclets} |
|
{battleState} |
|
{waitingForContinue} |
|
onAction={handleAction} |
|
onMoveSelect={handleMoveSelect} |
|
onPicletSelect={handlePicletSelect} |
|
onBack={handleBack} |
|
onContinueTap={handleContinueTap} |
|
/> |
|
</div> |
|
|
|
|
|
{#if battleResultsVisible} |
|
<div class="battle-results-overlay" transition:fade={{ duration: 300 }}> |
|
<div class="battle-results-card"> |
|
<h2>{battleResults.victory ? 'Victory!' : 'Defeat!'}</h2> |
|
|
|
|
|
{#if battleResults.levelUps.length > 0} |
|
{#each battleResults.levelUps as levelUp} |
|
<div class="level-up" transition:fade={{ duration: 500 }}> |
|
<h3>🎉 Level Up! 🎉</h3> |
|
<p><strong>{currentPlayerPiclet.nickname}</strong> grew to level <strong>{levelUp.newLevel}</strong>!</p> |
|
|
|
<div class="stat-changes"> |
|
{#if levelUp.statChanges.hp > 0} |
|
<div class="stat-change">HP +{levelUp.statChanges.hp}</div> |
|
{/if} |
|
{#if levelUp.statChanges.attack > 0} |
|
<div class="stat-change">Attack +{levelUp.statChanges.attack}</div> |
|
{/if} |
|
{#if levelUp.statChanges.defense > 0} |
|
<div class="stat-change">Defense +{levelUp.statChanges.defense}</div> |
|
{/if} |
|
{#if levelUp.statChanges.speed > 0} |
|
<div class="stat-change">Speed +{levelUp.statChanges.speed}</div> |
|
{/if} |
|
</div> |
|
</div> |
|
{/each} |
|
{/if} |
|
</div> |
|
</div> |
|
{/if} |
|
</div> |
|
|
|
<style> |
|
.battle-page { |
|
position: fixed; |
|
inset: 0; |
|
z-index: 1000; |
|
height: 100vh; |
|
display: flex; |
|
flex-direction: column; |
|
background: #f8f9fa; |
|
overflow: hidden; |
|
padding-top: env(safe-area-inset-top); |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.battle-page { |
|
background: white; |
|
} |
|
|
|
.battle-page::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
height: env(safe-area-inset-top); |
|
background: white; |
|
z-index: 1; |
|
} |
|
} |
|
|
|
.battle-nav { |
|
display: none; |
|
} |
|
|
|
.back-button { |
|
background: none; |
|
border: none; |
|
color: #007bff; |
|
font-size: 1rem; |
|
cursor: pointer; |
|
padding: 0.5rem; |
|
} |
|
|
|
.battle-nav h1 { |
|
margin: 0; |
|
font-size: 1.25rem; |
|
font-weight: 600; |
|
color: #1a1a1a; |
|
position: absolute; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
} |
|
|
|
.nav-spacer { |
|
width: 60px; |
|
} |
|
|
|
.battle-content { |
|
flex: 1; |
|
display: flex; |
|
flex-direction: column; |
|
overflow: hidden; |
|
position: relative; |
|
background: #f8f9fa; |
|
} |
|
|
|
|
|
.battle-results-overlay { |
|
position: fixed; |
|
inset: 0; |
|
background: rgba(0, 0, 0, 0.8); |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
z-index: 2000; |
|
} |
|
|
|
.battle-results-card { |
|
background: white; |
|
border-radius: 16px; |
|
padding: 2rem; |
|
max-width: 400px; |
|
width: 90%; |
|
text-align: center; |
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.battle-results-card h2 { |
|
margin: 0 0 1rem 0; |
|
font-size: 1.8rem; |
|
font-weight: 700; |
|
color: #1a1a1a; |
|
} |
|
|
|
|
|
.level-up { |
|
background: linear-gradient(135deg, #fff3e0 0%, #ffcc02 100%); |
|
border-radius: 12px; |
|
padding: 1.5rem; |
|
margin: 1rem 0; |
|
border: 3px solid #ff6f00; |
|
animation: levelUpPulse 0.6s ease-in-out; |
|
} |
|
|
|
.level-up h3 { |
|
margin: 0 0 0.5rem 0; |
|
font-size: 1.4rem; |
|
color: #e65100; |
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.level-up p { |
|
margin: 0 0 1rem 0; |
|
font-size: 1.2rem; |
|
color: #bf360c; |
|
} |
|
|
|
.stat-changes { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 0.5rem; |
|
justify-content: center; |
|
} |
|
|
|
.stat-change { |
|
background: rgba(76, 175, 80, 0.2); |
|
border: 1px solid #4caf50; |
|
border-radius: 20px; |
|
padding: 0.25rem 0.75rem; |
|
font-size: 0.9rem; |
|
font-weight: 600; |
|
color: #2e7d32; |
|
} |
|
|
|
@keyframes levelUpPulse { |
|
0% { |
|
transform: scale(0.9); |
|
opacity: 0; |
|
} |
|
50% { |
|
transform: scale(1.05); |
|
} |
|
100% { |
|
transform: scale(1); |
|
opacity: 1; |
|
} |
|
} |
|
</style> |