|
<script lang="ts"> |
|
import { onMount } from 'svelte'; |
|
import { fade } from 'svelte/transition'; |
|
import type { PicletInstance } from '$lib/db/schema'; |
|
import PicletInfo from './PicletInfo.svelte'; |
|
|
|
export let playerPiclet: PicletInstance; |
|
export let enemyPiclet: PicletInstance; |
|
export let playerHpPercentage: number; |
|
export let enemyHpPercentage: number; |
|
export let showIntro: boolean = false; |
|
|
|
|
|
let playerVisible = false; |
|
let enemyVisible = false; |
|
let trainerVisible = true; |
|
|
|
|
|
const playerXpPercentage = (playerPiclet.xp / 100) * 100; |
|
|
|
onMount(() => { |
|
if (showIntro) { |
|
|
|
setTimeout(() => { |
|
trainerVisible = false; |
|
enemyVisible = true; |
|
}, 500); |
|
|
|
setTimeout(() => { |
|
playerVisible = true; |
|
}, 1000); |
|
} else { |
|
|
|
playerVisible = true; |
|
enemyVisible = true; |
|
trainerVisible = false; |
|
} |
|
}); |
|
</script> |
|
|
|
<div class="battle-field"> |
|
|
|
{#if showIntro && trainerVisible} |
|
<div class="trainer-intro" transition:fade={{ duration: 300 }}> |
|
<img src="/assets/default_trainer.png" alt="Trainer" /> |
|
</div> |
|
{/if} |
|
|
|
<div class="battle-content"> |
|
|
|
<div class="enemy-row"> |
|
<div class="enemy-stack" class:intro-animations={showIntro}> |
|
<PicletInfo |
|
piclet={enemyPiclet} |
|
hpPercentage={enemyHpPercentage} |
|
xpPercentage={0} |
|
isPlayer={false} |
|
/> |
|
|
|
{#if enemyVisible} |
|
<div class="enemy-piclet-wrapper" class:animate-in={showIntro}> |
|
<img |
|
class="piclet-image enemy-image" |
|
src={enemyPiclet.imageData || enemyPiclet.imageUrl} |
|
alt={enemyPiclet.nickname} |
|
on:error={(e) => e.currentTarget.src = 'https://via.placeholder.com/120x120?text=Piclet'} |
|
/> |
|
<img |
|
class="platform enemy-platform" |
|
src="/assets/grass.PNG" |
|
alt="Platform" |
|
on:error={(e) => { |
|
e.currentTarget.style.display = 'none'; |
|
e.currentTarget.nextElementSibling.style.display = 'block'; |
|
}} |
|
/> |
|
<div class="platform-fallback enemy-platform-fallback" style="display: none;"></div> |
|
</div> |
|
{/if} |
|
</div> |
|
</div> |
|
|
|
<div class="spacer"></div> |
|
|
|
|
|
<div class="player-row"> |
|
<div class="player-stack" class:intro-animations={showIntro}> |
|
{#if playerVisible} |
|
<div class="player-piclet-wrapper" class:animate-in={showIntro}> |
|
<img |
|
class="piclet-image player-image" |
|
src={playerPiclet.imageData || playerPiclet.imageUrl} |
|
alt={playerPiclet.nickname} |
|
on:error={(e) => e.currentTarget.src = 'https://via.placeholder.com/120x120?text=Piclet'} |
|
/> |
|
<img |
|
class="platform player-platform" |
|
src="/assets/grass.PNG" |
|
alt="Platform" |
|
on:error={(e) => { |
|
e.currentTarget.style.display = 'none'; |
|
e.currentTarget.nextElementSibling.style.display = 'block'; |
|
}} |
|
/> |
|
<div class="platform-fallback player-platform-fallback" style="display: none;"></div> |
|
</div> |
|
{/if} |
|
|
|
<PicletInfo |
|
piclet={playerPiclet} |
|
hpPercentage={playerHpPercentage} |
|
xpPercentage={playerXpPercentage} |
|
isPlayer={true} |
|
/> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<style> |
|
.battle-field { |
|
height: 280px; |
|
position: relative; |
|
overflow: hidden; |
|
background: repeating-linear-gradient( |
|
to bottom, |
|
rgba(76, 175, 80, 0.2) 0px, |
|
rgba(76, 175, 80, 0.2) 5px, |
|
rgba(76, 175, 80, 0.1) 5px, |
|
rgba(76, 175, 80, 0.1) 10px |
|
); |
|
} |
|
|
|
.trainer-intro { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
z-index: 10; |
|
} |
|
|
|
.trainer-intro img { |
|
width: 200px; |
|
height: auto; |
|
animation: trainerPulse 0.5s ease-in-out; |
|
} |
|
|
|
@keyframes trainerPulse { |
|
0% { |
|
transform: scale(1); |
|
} |
|
50% { |
|
transform: scale(1.05); |
|
} |
|
100% { |
|
transform: scale(1); |
|
} |
|
} |
|
|
|
.battle-content { |
|
display: flex; |
|
flex-direction: column; |
|
height: 100%; |
|
} |
|
|
|
|
|
.enemy-row { |
|
flex: 1; |
|
position: relative; |
|
} |
|
|
|
.enemy-stack { |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
left: 0; |
|
bottom: 0; |
|
} |
|
|
|
.enemy-piclet-wrapper { |
|
position: absolute; |
|
right: 40px; |
|
top: 0; |
|
} |
|
|
|
.enemy-image { |
|
width: 120px; |
|
height: 120px; |
|
object-fit: contain; |
|
display: block; |
|
} |
|
|
|
.enemy-platform { |
|
width: 160px; |
|
height: 160px; |
|
position: absolute; |
|
bottom: -60px; |
|
left: -20px; |
|
z-index: 0; |
|
object-fit: cover; |
|
} |
|
|
|
|
|
.player-row { |
|
height: 140px; |
|
position: relative; |
|
} |
|
|
|
.player-stack { |
|
position: relative; |
|
width: 100%; |
|
height: 100%; |
|
} |
|
|
|
.player-piclet-wrapper { |
|
position: absolute; |
|
left: 40px; |
|
bottom: 0; |
|
} |
|
|
|
.player-image { |
|
width: 120px; |
|
height: 120px; |
|
object-fit: contain; |
|
display: block; |
|
transform: scaleX(-1); |
|
} |
|
|
|
.player-platform { |
|
width: 160px; |
|
height: 160px; |
|
position: absolute; |
|
bottom: -80px; |
|
left: -20px; |
|
z-index: 0; |
|
object-fit: cover; |
|
} |
|
|
|
|
|
.platform-fallback { |
|
position: absolute; |
|
background: rgba(76, 175, 80, 0.3); |
|
border-radius: 50%; |
|
} |
|
|
|
.enemy-platform-fallback { |
|
width: 160px; |
|
height: 160px; |
|
bottom: -80px; |
|
left: -20px; |
|
} |
|
|
|
.player-platform-fallback { |
|
width: 160px; |
|
height: 160px; |
|
bottom: -80px; |
|
left: -20px; |
|
} |
|
|
|
|
|
.piclet-image { |
|
image-rendering: auto; |
|
filter: drop-shadow(-2px 0 4px rgba(0, 0, 0, 0.1)); |
|
position: relative; |
|
z-index: 1; |
|
} |
|
|
|
.spacer { |
|
flex: 1; |
|
} |
|
|
|
|
|
.enemy-piclet-wrapper { |
|
animation-fill-mode: both; |
|
} |
|
|
|
.enemy-piclet-wrapper.animate-in { |
|
animation: enemySlideIn 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
|
} |
|
|
|
.player-piclet-wrapper { |
|
animation-fill-mode: both; |
|
} |
|
|
|
.player-piclet-wrapper.animate-in { |
|
animation: playerSlideIn 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
|
} |
|
|
|
@keyframes enemySlideIn { |
|
0% { |
|
transform: translateX(150px) translateY(-50px) scale(1.5); |
|
opacity: 0; |
|
} |
|
50% { |
|
opacity: 1; |
|
} |
|
100% { |
|
transform: translateX(0) translateY(0) scale(1); |
|
opacity: 1; |
|
} |
|
} |
|
|
|
@keyframes playerSlideIn { |
|
0% { |
|
transform: translateX(-150px) translateY(50px) scale(0.5); |
|
opacity: 0; |
|
} |
|
50% { |
|
opacity: 1; |
|
} |
|
100% { |
|
transform: translateX(0) translateY(0) scale(1); |
|
opacity: 1; |
|
} |
|
} |
|
|
|
|
|
.enemy-stack.intro-animations :global(.piclet-info-wrapper) { |
|
animation: fadeSlideDown 0.5s ease-out 0.3s both; |
|
} |
|
|
|
.player-stack.intro-animations :global(.piclet-info-wrapper) { |
|
animation: fadeSlideUp 0.5s ease-out 0.3s both; |
|
} |
|
|
|
@keyframes fadeSlideDown { |
|
0% { |
|
opacity: 0; |
|
transform: translateY(-20px); |
|
} |
|
100% { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
} |
|
|
|
@keyframes fadeSlideUp { |
|
0% { |
|
opacity: 0; |
|
transform: translateY(20px); |
|
} |
|
100% { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.enemy-image { |
|
width: 120px; |
|
height: 120px; |
|
} |
|
|
|
.player-image { |
|
width: 120px; |
|
height: 120px; |
|
} |
|
|
|
.enemy-piclet-wrapper { |
|
right: 40px; |
|
} |
|
|
|
.player-piclet-wrapper { |
|
left: 40px; |
|
} |
|
} |
|
</style> |