|
<script lang="ts"> |
|
import type { PicletInstance } from '$lib/db/schema'; |
|
import { deletePicletInstance } from '$lib/db/piclets'; |
|
|
|
interface Props { |
|
instance: PicletInstance; |
|
onClose: () => void; |
|
onDeleted?: () => void; |
|
} |
|
|
|
let { instance, onClose, onDeleted }: Props = $props(); |
|
let showDeleteConfirm = $state(false); |
|
|
|
async function handleDelete() { |
|
if (!instance.id) return; |
|
|
|
try { |
|
await deletePicletInstance(instance.id); |
|
onDeleted?.(); |
|
onClose(); |
|
} catch (err) { |
|
console.error('Failed to delete piclet:', err); |
|
} |
|
} |
|
|
|
function getStatPercentage(value: number, max: number = 255): number { |
|
return Math.round((value / max) * 100); |
|
} |
|
</script> |
|
|
|
<div class="detail-modal" onclick={(e) => e.target === e.currentTarget && onClose()}> |
|
<div class="detail-content"> |
|
<header class="detail-header"> |
|
<button class="close-btn" onclick={onClose} aria-label="Close"> |
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
<path d="M6 18L18 6M6 6l12 12"></path> |
|
</svg> |
|
</button> |
|
<h2>{instance.nickname || instance.typeId}</h2> |
|
</header> |
|
|
|
<div class="detail-body"> |
|
<div class="image-section"> |
|
<img |
|
src={instance.imageData || instance.imageUrl} |
|
alt={instance.nickname || instance.typeId} |
|
class="piclet-image" |
|
/> |
|
<div class="basic-info"> |
|
<span class="level">Lv. {instance.level}</span> |
|
<span class="type">{instance.primaryTypeString}</span> |
|
{#if instance.secondaryTypeString} |
|
<span class="type">{instance.secondaryTypeString}</span> |
|
{/if} |
|
</div> |
|
</div> |
|
|
|
<div class="stats-section"> |
|
<h3>Stats</h3> |
|
<div class="stat-grid"> |
|
<div class="stat"> |
|
<span class="stat-label">HP</span> |
|
<div class="stat-bar"> |
|
<div class="stat-fill" style="width: {getStatPercentage(instance.currentHp, instance.maxHp)}%"></div> |
|
</div> |
|
<span class="stat-value">{instance.currentHp}/{instance.maxHp}</span> |
|
</div> |
|
|
|
<div class="stat"> |
|
<span class="stat-label">Attack</span> |
|
<div class="stat-bar"> |
|
<div class="stat-fill" style="width: {getStatPercentage(instance.attack)}%"></div> |
|
</div> |
|
<span class="stat-value">{instance.attack}</span> |
|
</div> |
|
|
|
<div class="stat"> |
|
<span class="stat-label">Defense</span> |
|
<div class="stat-bar"> |
|
<div class="stat-fill" style="width: {getStatPercentage(instance.defense)}%"></div> |
|
</div> |
|
<span class="stat-value">{instance.defense}</span> |
|
</div> |
|
|
|
<div class="stat"> |
|
<span class="stat-label">Speed</span> |
|
<div class="stat-bar"> |
|
<div class="stat-fill" style="width: {getStatPercentage(instance.speed)}%"></div> |
|
</div> |
|
<span class="stat-value">{instance.speed}</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="moves-section"> |
|
<h3>Moves</h3> |
|
<div class="moves-grid"> |
|
{#each instance.moves as move} |
|
<div class="move"> |
|
<div class="move-name">{move.name}</div> |
|
<div class="move-pp">PP: {move.currentPp}/{move.pp}</div> |
|
</div> |
|
{/each} |
|
</div> |
|
</div> |
|
|
|
<div class="concept-section"> |
|
<h3>Concept</h3> |
|
<p>{instance.concept}</p> |
|
</div> |
|
|
|
<div class="actions"> |
|
{#if showDeleteConfirm} |
|
<p class="delete-confirm">Are you sure you want to release this piclet?</p> |
|
<button class="btn btn-danger" onclick={handleDelete}>Yes, Release</button> |
|
<button class="btn btn-secondary" onclick={() => showDeleteConfirm = false}>Cancel</button> |
|
{:else} |
|
<button class="btn btn-danger" onclick={() => showDeleteConfirm = true}>Release</button> |
|
{/if} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<style> |
|
.detail-modal { |
|
position: fixed; |
|
inset: 0; |
|
background: rgba(0, 0, 0, 0.5); |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
z-index: 1000; |
|
padding: 1rem; |
|
} |
|
|
|
.detail-content { |
|
background: white; |
|
border-radius: 16px; |
|
width: 100%; |
|
max-width: 500px; |
|
max-height: 90vh; |
|
overflow: hidden; |
|
display: flex; |
|
flex-direction: column; |
|
} |
|
|
|
.detail-header { |
|
padding: 1rem; |
|
border-bottom: 1px solid #e5e5ea; |
|
position: relative; |
|
} |
|
|
|
.detail-header h2 { |
|
margin: 0; |
|
text-align: center; |
|
font-size: 1.25rem; |
|
} |
|
|
|
.close-btn { |
|
position: absolute; |
|
top: 1rem; |
|
right: 1rem; |
|
background: none; |
|
border: none; |
|
padding: 0; |
|
width: 24px; |
|
height: 24px; |
|
cursor: pointer; |
|
color: #8e8e93; |
|
} |
|
|
|
.detail-body { |
|
flex: 1; |
|
overflow-y: auto; |
|
padding: 1rem; |
|
} |
|
|
|
.image-section { |
|
text-align: center; |
|
margin-bottom: 1.5rem; |
|
} |
|
|
|
.piclet-image { |
|
width: 200px; |
|
height: 200px; |
|
object-fit: contain; |
|
margin-bottom: 0.5rem; |
|
} |
|
|
|
.basic-info { |
|
display: flex; |
|
gap: 0.5rem; |
|
justify-content: center; |
|
align-items: center; |
|
} |
|
|
|
.level { |
|
font-weight: 600; |
|
color: #333; |
|
} |
|
|
|
.type { |
|
background: #e5e5ea; |
|
padding: 0.25rem 0.75rem; |
|
border-radius: 12px; |
|
font-size: 0.875rem; |
|
text-transform: capitalize; |
|
} |
|
|
|
.stats-section, |
|
.moves-section, |
|
.concept-section { |
|
margin-bottom: 1.5rem; |
|
} |
|
|
|
h3 { |
|
margin: 0 0 0.75rem; |
|
color: #8e8e93; |
|
font-size: 1rem; |
|
font-weight: 600; |
|
} |
|
|
|
.stat-grid { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 0.75rem; |
|
} |
|
|
|
.stat { |
|
display: grid; |
|
grid-template-columns: 60px 1fr 60px; |
|
align-items: center; |
|
gap: 0.5rem; |
|
} |
|
|
|
.stat-label { |
|
font-size: 0.875rem; |
|
color: #666; |
|
} |
|
|
|
.stat-bar { |
|
height: 8px; |
|
background: #f1f1f1; |
|
border-radius: 4px; |
|
overflow: hidden; |
|
} |
|
|
|
.stat-fill { |
|
height: 100%; |
|
background: #007bff; |
|
transition: width 0.3s; |
|
} |
|
|
|
.stat-value { |
|
text-align: right; |
|
font-size: 0.875rem; |
|
font-weight: 600; |
|
} |
|
|
|
.moves-grid { |
|
display: grid; |
|
grid-template-columns: 1fr 1fr; |
|
gap: 0.5rem; |
|
} |
|
|
|
.move { |
|
background: #f8f8f8; |
|
padding: 0.75rem; |
|
border-radius: 8px; |
|
} |
|
|
|
.move-name { |
|
font-weight: 600; |
|
margin-bottom: 0.25rem; |
|
} |
|
|
|
.move-pp { |
|
font-size: 0.75rem; |
|
color: #666; |
|
} |
|
|
|
.concept-section p { |
|
margin: 0; |
|
color: #666; |
|
line-height: 1.5; |
|
} |
|
|
|
.actions { |
|
padding-top: 1rem; |
|
border-top: 1px solid #e5e5ea; |
|
text-align: center; |
|
} |
|
|
|
.delete-confirm { |
|
margin: 0 0 1rem; |
|
color: #d73502; |
|
} |
|
|
|
.btn { |
|
padding: 0.75rem 1.5rem; |
|
border: none; |
|
border-radius: 8px; |
|
font-size: 1rem; |
|
font-weight: 600; |
|
cursor: pointer; |
|
transition: transform 0.2s; |
|
} |
|
|
|
.btn:active { |
|
transform: scale(0.95); |
|
} |
|
|
|
.btn-danger { |
|
background: #d73502; |
|
color: white; |
|
} |
|
|
|
.btn-secondary { |
|
background: #e5e5ea; |
|
color: #333; |
|
margin-left: 0.5rem; |
|
} |
|
</style> |