piclets / src /lib /components /Battle /ActionViewSelector.svelte
Fraser's picture
show act type
ea83c78
<script context="module" lang="ts">
export type ActionView = 'main' | 'moves' | 'piclets' | 'items' | 'stats' | 'forcedSwap';
</script>
<script lang="ts">
import type { PicletInstance, BattleMove } from '$lib/db/schema';
import type { BattleState } from '$lib/battle-engine/types';
export let currentView: ActionView = 'main';
export let onViewChange: (view: ActionView) => void;
export let moves: BattleMove[] = [];
export let availablePiclets: PicletInstance[] = [];
export let enemyPiclet: PicletInstance | null = null;
export let isWildBattle: boolean = false;
export let battleState: BattleState | undefined = undefined;
export let onMoveSelected: (move: BattleMove) => void = () => {};
export let onPicletSelected: (piclet: PicletInstance) => void = () => {};
export let onCaptureAttempt: () => void = () => {};
export let currentPicletId: number | null = null;
export let processingTurn: boolean = false;
// Enhanced move information from battle state
$: enhancedMoves = battleState?.playerPiclet?.moves || [];
// Helper function to get type logo path
function getTypeLogo(type: string): string {
return `/classes/${type}.png`;
}
// Main action items
const actions = [
{ title: 'Act', icon: 'βš”οΈ', view: 'moves' as ActionView },
{ title: 'Piclets', icon: 'πŸ”„', view: 'piclets' as ActionView },
{ title: 'Items', icon: 'πŸŽ’', view: 'items' as ActionView },
// { title: 'Stats', icon: 'πŸ“Š', view: 'stats' as ActionView }, // Debug only
];
function handleActionClick(targetView: ActionView) {
if (currentView === targetView) {
onViewChange('main');
} else {
onViewChange(targetView);
}
}
</script>
<div class="action-view-container">
<!-- Main action list -->
{#if currentView !== 'forcedSwap'}
<div class="action-list main-actions">
{#each actions as action}
<button
class="action-item"
class:active={currentView === action.view}
on:click={() => handleActionClick(action.view)}
disabled={processingTurn}
>
<span class="action-icon" class:active={currentView === action.view}>
{action.icon}
</span>
<span class="action-title">{action.title}</span>
<span class="chevron">β€Ί</span>
</button>
{/each}
</div>
{/if}
<!-- Overlay sub-views -->
{#if currentView !== 'main'}
<div class="sub-view-overlay" class:forced={currentView === 'forcedSwap'}>
<div class="sub-view-content">
{#if currentView === 'moves'}
<div class="sub-view-list">
{#each moves as move, index}
{@const isDisabled = move.currentPp <= 0}
{@const enhancedMove = enhancedMoves[index]}
{@const battleMove = enhancedMove?.move}
<button
class="sub-item move-item"
on:click={() => !isDisabled && onMoveSelected(move)}
disabled={isDisabled || processingTurn}
>
<img
src={getTypeLogo(move.type)}
alt={move.type}
class="type-logo"
class:disabled={isDisabled}
/>
<div class="move-info">
<div class="move-name" class:disabled={isDisabled}>
{move.name}
{#if battleMove?.power > 0}
<span class="move-power">⚑{battleMove.power}</span>
{/if}
</div>
<div class="move-desc" class:disabled={isDisabled}>
{#if battleMove?.effects && battleMove.effects.length > 0}
{battleMove.effects.length} effects β€’ {move.description}
{:else}
{move.description}
{/if}
</div>
{#if battleMove?.flags && battleMove.flags.length > 0}
<div class="move-flags">
{#each battleMove.flags as flag}
<span class="flag-badge">{flag}</span>
{/each}
</div>
{/if}
</div>
<div class="move-stats" class:disabled={isDisabled}>
<div class="move-pp">PP: {move.currentPp}/{move.pp}</div>
{#if battleMove}
<div class="move-accuracy">Acc: {battleMove.accuracy}%</div>
{/if}
</div>
</button>
{/each}
</div>
{:else if currentView === 'piclets'}
{@const availableHealthyPiclets = availablePiclets.filter(p =>
p.currentHp > 0 && p.id !== currentPicletId
)}
<div class="sub-view-list">
{#if availableHealthyPiclets.length === 0}
<div class="empty-message">
No other healthy piclets available
</div>
{:else}
{#each availableHealthyPiclets as piclet}
<button
class="sub-item piclet-item"
on:click={() => onPicletSelected(piclet)}
disabled={processingTurn}
>
<img
src={piclet.imageData || piclet.imageUrl}
alt={piclet.nickname}
class="piclet-thumb"
/>
<div class="piclet-info">
<div class="piclet-header">
<span class="piclet-name">{piclet.nickname}</span>
<img
src={getTypeLogo(piclet.primaryType)}
alt={piclet.primaryType}
class="type-logo-small"
/>
<span class="level-badge">Lv.{piclet.level}</span>
</div>
<div class="hp-row">
<div class="hp-bar-small">
<div
class="hp-fill-small"
style="width: {(piclet.currentHp / piclet.maxHp) * 100}%"
></div>
</div>
<span class="hp-text-small">{piclet.currentHp}/{piclet.maxHp}</span>
</div>
</div>
</button>
{/each}
{/if}
</div>
{:else if currentView === 'items'}
<div class="sub-view-list">
{#if isWildBattle && enemyPiclet}
<button
class="sub-item item-item"
on:click={onCaptureAttempt}
disabled={processingTurn}
>
<span class="item-icon">πŸ“Έ</span>
<div class="item-info">
<div class="item-name">Capture</div>
<div class="item-desc">Snap to capture {enemyPiclet.nickname}</div>
</div>
</button>
{:else}
<div class="empty-message">
<span class="empty-icon">πŸŽ’</span>
<div>Items coming soon!</div>
<div class="empty-subtitle">This feature is currently under development.</div>
</div>
{/if}
</div>
{/if}
</div>
</div>
{/if}
<!-- Processing overlay -->
{#if processingTurn}
<div class="processing-overlay"></div>
{/if}
</div>
<style>
.action-view-container {
position: relative;
height: 100%;
}
/* Main action list */
.action-list {
background: white;
border-radius: 12px;
border: 0.5px solid #c6c6c8;
overflow: hidden;
}
.action-item {
display: flex;
align-items: center;
width: 100%;
padding: 12px 16px;
background: none;
border: none;
cursor: pointer;
text-align: left;
transition: background-color 0.2s;
position: relative;
}
.action-item:not(:last-child)::after {
content: '';
position: absolute;
bottom: 0;
left: 16px;
right: 0;
height: 0.5px;
background: #c6c6c8;
}
.action-item:active:not(:disabled) {
background: #f2f2f7;
}
.action-item:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.action-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
border-radius: 8px;
margin-right: 12px;
}
.action-icon.active {
background: #e5f3ff;
}
.action-title {
flex: 1;
font-size: 17px;
font-weight: 400;
color: #000;
}
.chevron {
color: #8e8e93;
font-size: 16px;
}
/* Sub-view overlay */
.sub-view-overlay {
position: absolute;
top: 0;
left: 64px; /* Icon width + padding */
right: 0;
bottom: 0;
background: white;
border-radius: 12px;
border: 0.5px solid #c6c6c8;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
animation: slideIn 0.2s ease-out;
}
.sub-view-overlay.forced {
left: 0;
}
@keyframes slideIn {
from {
transform: translateX(20px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.sub-view-content {
height: 100%;
overflow-y: auto;
}
.sub-view-list {
padding: 0;
}
/* Sub-items */
.sub-item {
display: flex;
align-items: center;
width: 100%;
padding: 12px 16px;
background: none;
border: none;
cursor: pointer;
text-align: left;
transition: background-color 0.2s;
position: relative;
}
.sub-item:not(:last-child)::after {
content: '';
position: absolute;
bottom: 0;
left: 16px;
right: 0;
height: 0.5px;
background: #c6c6c8;
}
.sub-item:active:not(:disabled) {
background: #f2f2f7;
}
.sub-item:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Move items */
.type-logo {
width: 24px;
height: 24px;
margin-right: 12px;
object-fit: contain;
}
.type-logo.disabled {
opacity: 0.3;
}
.type-logo-small {
width: 16px;
height: 16px;
object-fit: contain;
}
.move-info {
flex: 1;
}
.move-name {
font-size: 16px;
font-weight: 500;
color: #000;
margin-bottom: 2px;
}
.move-name.disabled {
color: #8e8e93;
}
.move-desc {
font-size: 13px;
color: #8e8e93;
}
.move-desc.disabled {
color: #c7c7cc;
}
.move-power {
font-size: 11px;
color: #ff6b35;
font-weight: bold;
margin-left: 6px;
}
.move-flags {
display: flex;
gap: 4px;
margin-top: 4px;
flex-wrap: wrap;
}
.flag-badge {
font-size: 10px;
padding: 2px 6px;
background: #e3f2fd;
color: #1976d2;
border-radius: 8px;
font-weight: 500;
}
.move-stats {
display: flex;
flex-direction: column;
gap: 2px;
align-items: flex-end;
}
.move-stats.disabled .move-pp,
.move-stats.disabled .move-accuracy {
background: #f2f2f7;
color: #8e8e93;
}
.move-pp {
font-size: 12px;
padding: 4px 8px;
background: #f2f2f7;
border-radius: 12px;
color: #000;
white-space: nowrap;
}
.move-accuracy {
font-size: 11px;
padding: 2px 6px;
background: #e8f5e8;
color: #2e7d32;
border-radius: 8px;
white-space: nowrap;
}
/* Piclet items */
.piclet-thumb {
width: 48px;
height: 48px;
object-fit: contain;
border-radius: 8px;
margin-right: 12px;
}
.piclet-info {
flex: 1;
}
.piclet-header {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 4px;
}
.piclet-name {
font-size: 16px;
font-weight: 500;
color: #000;
}
.level-badge {
font-size: 11px;
font-weight: 700;
padding: 2px 6px;
background: #e5e5ea;
border-radius: 12px;
margin-left: auto;
}
.hp-row {
display: flex;
align-items: center;
gap: 8px;
}
.hp-bar-small {
flex: 1;
height: 6px;
background: #e5e5ea;
border-radius: 3px;
overflow: hidden;
}
.hp-fill-small {
height: 100%;
background: #4cd964;
transition: width 0.3s ease;
}
.hp-text-small {
font-size: 11px;
color: #8e8e93;
white-space: nowrap;
}
/* Item items */
.item-icon {
font-size: 24px;
margin-right: 12px;
width: 32px;
text-align: center;
}
.item-info {
flex: 1;
}
.item-name {
font-size: 16px;
font-weight: 500;
color: #000;
margin-bottom: 2px;
}
.item-desc {
font-size: 13px;
color: #8e8e93;
}
/* Empty states */
.empty-message {
padding: 24px;
text-align: center;
color: #8e8e93;
font-size: 16px;
}
.empty-icon {
font-size: 32px;
display: block;
margin-bottom: 8px;
opacity: 0.5;
}
.empty-subtitle {
font-size: 14px;
color: #c7c7cc;
margin-top: 4px;
}
/* Processing overlay */
.processing-overlay {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.8);
border-radius: 12px;
}
</style>