| <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> |