|
<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; |
|
|
|
|
|
$: enhancedMoves = battleState?.playerPiclet?.moves || []; |
|
|
|
|
|
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 }, |
|
]; |
|
|
|
function handleActionClick(targetView: ActionView) { |
|
if (currentView === targetView) { |
|
onViewChange('main'); |
|
} else { |
|
onViewChange(targetView); |
|
} |
|
} |
|
</script> |
|
|
|
<div class="action-view-container"> |
|
|
|
{#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%; |
|
} |
|
|
|
|
|
.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 { |
|
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-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; |
|
} |
|
|
|
|
|
.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-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-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-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 { |
|
position: absolute; |
|
inset: 0; |
|
background: rgba(255, 255, 255, 0.8); |
|
border-radius: 12px; |
|
} |
|
</style> |