Fraser's picture
rm unused
45182b7
raw
history blame
11.2 kB
<script lang="ts">
import type { MonsterWorkflowState } from '$lib/types';
import { saveMonster } from '$lib/db/monsters';
interface Props {
workflowState: MonsterWorkflowState;
onReset: () => void;
}
let { workflowState, onReset }: Props = $props();
let isSaving = $state(false);
let isSaved = $state(false);
let saveError: string | null = $state(null);
function downloadImage() {
if (!workflowState.monsterImage) return;
const link = document.createElement('a');
// Use transparent image if available, otherwise original
link.href = workflowState.monsterImage.imageData || workflowState.monsterImage.imageUrl;
link.download = `monster-${Date.now()}.png`;
link.click();
}
function copyPrompt() {
if (!workflowState.imagePrompt) return;
navigator.clipboard.writeText(workflowState.imagePrompt);
alert('Prompt copied to clipboard!');
}
function getStatColor(value: number): string {
if (value >= 80) return '#007bff'; // very high - blue
if (value >= 60) return '#28a745'; // high - green
if (value >= 40) return '#ffc107'; // medium - yellow
if (value >= 20) return '#fd7e14'; // low - orange
return '#dc3545'; // very low - red
}
async function saveToCollection() {
if (!workflowState.monsterImage || !workflowState.imageCaption || !workflowState.monsterConcept || !workflowState.imagePrompt || !workflowState.monsterStats) {
saveError = 'Missing monster data';
return;
}
isSaving = true;
saveError = null;
try {
// Extract monster name from concept (usually first line or after "Monster Name:")
let monsterName = 'Unknown Monster';
const conceptLines = workflowState.monsterConcept.split('\n');
for (const line of conceptLines) {
if (line.includes('Monster Name:') || line.includes('**Monster Name:**')) {
monsterName = line.replace(/\*\*Monster Name:\*\*|Monster Name:/g, '').trim();
break;
} else if (line.trim() && !line.includes(':')) {
// First non-empty line without colon might be the name
monsterName = line.trim();
break;
}
}
// Use stats name if available, otherwise use extracted name
if (workflowState.monsterStats?.name) {
monsterName = workflowState.monsterStats.name;
}
const monsterData = {
name: monsterName,
imageUrl: workflowState.monsterImage.imageUrl,
imageData: workflowState.monsterImage.imageData,
imageCaption: workflowState.imageCaption,
concept: workflowState.monsterConcept,
imagePrompt: workflowState.imagePrompt,
stats: workflowState.monsterStats
};
console.log('Saving monster with data:', {
...monsterData,
imageData: monsterData.imageData ? `${monsterData.imageData.substring(0, 50)}... (length: ${monsterData.imageData.length})` : 'null'
});
await saveMonster(monsterData);
isSaved = true;
} catch (err) {
console.error('Failed to save monster:', err);
saveError = 'Failed to save monster to collection';
} finally {
isSaving = false;
}
}
</script>
<div class="result-container">
<h3>{workflowState.monsterStats?.name || 'Your Monster Has Been Created!'}</h3>
<p class="saved-message">✓ Automatically saved to your collection</p>
{#if workflowState.monsterImage}
<div class="monster-image-container">
<img
src={workflowState.monsterImage.imageData || workflowState.monsterImage.imageUrl}
alt="Generated Monster"
class="monster-image"
/>
</div>
{/if}
{#if workflowState.monsterStats?.description}
<p class="monster-description">{workflowState.monsterStats.description}</p>
{/if}
{#if workflowState.monsterStats}
<div class="result-section">
<h4>Battle Stats</h4>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-bar" style="width: {workflowState.monsterStats.rarity}%; background-color: {getStatColor(workflowState.monsterStats.rarity)}20"></div>
<span class="stat-label">Rarity</span>
<span class="stat-value" style="color: {getStatColor(workflowState.monsterStats.rarity)}">{workflowState.monsterStats.rarity}</span>
</div>
<div class="stat-item">
<div class="stat-bar" style="width: {workflowState.monsterStats.HP}%; background-color: {getStatColor(workflowState.monsterStats.HP)}20"></div>
<span class="stat-label">HP</span>
<span class="stat-value" style="color: {getStatColor(workflowState.monsterStats.HP)}">{workflowState.monsterStats.HP}</span>
</div>
<div class="stat-item">
<div class="stat-bar" style="width: {workflowState.monsterStats.attack}%; background-color: {getStatColor(workflowState.monsterStats.attack)}20"></div>
<span class="stat-label">Attack</span>
<span class="stat-value" style="color: {getStatColor(workflowState.monsterStats.attack)}">{workflowState.monsterStats.attack}</span>
</div>
<div class="stat-item">
<div class="stat-bar" style="width: {workflowState.monsterStats.defence}%; background-color: {getStatColor(workflowState.monsterStats.defence)}20"></div>
<span class="stat-label">Defence</span>
<span class="stat-value" style="color: {getStatColor(workflowState.monsterStats.defence)}">{workflowState.monsterStats.defence}</span>
</div>
<div class="stat-item">
<div class="stat-bar" style="width: {workflowState.monsterStats.speed}%; background-color: {getStatColor(workflowState.monsterStats.speed)}20"></div>
<span class="stat-label">Speed</span>
<span class="stat-value" style="color: {getStatColor(workflowState.monsterStats.speed)}">{workflowState.monsterStats.speed}</span>
</div>
</div>
<div class="abilities-section">
<div class="ability-item">
<h5>Special Passive Trait</h5>
<p>{workflowState.monsterStats.specialPassiveTrait}</p>
</div>
<div class="ability-item">
<h5>Attack: {workflowState.monsterStats.attackActionName}</h5>
<p>{workflowState.monsterStats.attackActionDescription}</p>
</div>
<div class="ability-item">
<h5>Buff: {workflowState.monsterStats.buffActionName}</h5>
<p>{workflowState.monsterStats.buffActionDescription}</p>
</div>
<div class="ability-item">
<h5>Debuff: {workflowState.monsterStats.debuffActionName}</h5>
<p>{workflowState.monsterStats.debuffActionDescription}</p>
</div>
<div class="ability-item">
<h5>Ultimate: {workflowState.monsterStats.specialActionName}</h5>
<p>{workflowState.monsterStats.specialActionDescription}</p>
</div>
</div>
</div>
{/if}
<div class="action-buttons">
<button class="action-button download" onclick={downloadImage}>
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/>
</svg>
Download Monster
</button>
<button class="action-button reset" onclick={onReset}>
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.058 7.293a1 1 0 01-1.414 1.414l-2.35-2.35A1 1 0 011 5.648V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.943 13H13a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"/>
</svg>
Create Another Monster
</button>
</div>
{#if saveError}
<div class="error-message">{saveError}</div>
{/if}
</div>
<style>
.result-container {
max-width: 900px;
margin: 0 auto;
padding: 2rem;
}
h3 {
text-align: center;
color: #333;
margin-bottom: 2rem;
}
.monster-image-container {
text-align: center;
margin-bottom: 3rem;
}
.monster-image {
max-width: 100%;
max-height: 600px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.result-section {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.result-section h4 {
margin: 0 0 1rem 0;
color: #495057;
font-size: 1.1rem;
}
.action-buttons {
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
}
.action-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.8rem 1.5rem;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.action-button.download {
background: #28a745;
color: white;
}
.action-button.download:hover {
background: #218838;
}
.action-button.reset {
background: #6c757d;
color: white;
}
.action-button.reset:hover {
background: #5a6268;
}
.action-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.error-message {
margin-top: 1rem;
padding: 0.5rem;
background: #f8d7da;
color: #721c24;
border-radius: 4px;
text-align: center;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.stat-item {
display: flex;
flex-direction: column;
padding: 0.75rem;
background: white;
border-radius: 6px;
border: 1px solid #dee2e6;
position: relative;
overflow: hidden;
}
.stat-bar {
position: absolute;
top: 0;
left: 0;
height: 100%;
transition: width 0.3s ease;
z-index: 0;
}
.stat-label {
font-size: 0.85rem;
color: #6c757d;
margin-bottom: 0.25rem;
position: relative;
z-index: 1;
}
.stat-value {
font-weight: 600;
font-size: 1.1rem;
position: relative;
z-index: 1;
}
.abilities-section {
display: grid;
gap: 1rem;
}
.ability-item {
background: white;
padding: 1rem;
border-radius: 6px;
border: 1px solid #dee2e6;
}
.ability-item h5 {
margin: 0 0 0.5rem 0;
color: #495057;
font-size: 0.95rem;
}
.ability-item p {
margin: 0;
font-size: 0.85rem;
line-height: 1.4;
}
.saved-message {
color: #28a745;
font-size: 0.9rem;
margin: -0.5rem 0 1.5rem;
text-align: center;
}
.monster-description {
text-align: center;
color: #555;
font-size: 1rem;
line-height: 1.5;
margin: 1rem auto 2rem;
max-width: 600px;
font-style: italic;
}
@media (max-width: 768px) {
.result-container {
padding: 1rem;
}
.action-buttons {
flex-direction: column;
}
.action-button {
width: 100%;
justify-content: center;
}
}
</style>