piclets / src /lib /components /Piclets /PicletDetail.svelte
Fraser's picture
more stuff
b4531e8
raw
history blame
7.33 kB
<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>