piclets / src /lib /components /Battle /BattleField.svelte
Fraser's picture
flip x
b16a79f
raw
history blame
8.05 kB
<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;
// Animation states
let playerVisible = false;
let enemyVisible = false;
let trainerVisible = true;
// Calculate player XP percentage (0-100% of current level)
const playerXpPercentage = (playerPiclet.xp / 100) * 100; // Simplified, should use actual XP curve
onMount(() => {
if (showIntro) {
// Intro animation sequence
setTimeout(() => {
trainerVisible = false;
enemyVisible = true;
}, 500);
setTimeout(() => {
playerVisible = true;
}, 1000);
} else {
// Skip intro
playerVisible = true;
enemyVisible = true;
trainerVisible = false;
}
});
</script>
<div class="battle-field">
<!-- Trainer intro image -->
{#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">
<!-- Enemy Row -->
<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>
<!-- Player Row -->
<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 */
.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 */
.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 fallbacks */
.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 images */
.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;
}
/* Animations */
.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;
}
}
/* Info box animations */
.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>