260 lines
15 KiB
PHP
260 lines
15 KiB
PHP
<div id="critterModalOverlay" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
|
|
<div class="relative w-full max-w-4xl max-h-[90vh] overflow-y-auto rounded-3xl bg-white shadow-2xl grid md:grid-cols-2">
|
|
<button type="button" onclick="closeCritterModal()" aria-label="Close" class="absolute top-4 right-4 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-white/90 shadow hover:bg-white text-gray-500">
|
|
<i data-lucide="x" class="h-5 w-5"></i>
|
|
</button>
|
|
|
|
<div class="p-6 bg-gradient-to-br from-pink-50 via-white to-amber-50">
|
|
<div class="flex items-start justify-between mb-4 pr-10">
|
|
<div>
|
|
<h2 id="critterModalTitleLeft" class="text-2xl font-extrabold text-gray-900"></h2>
|
|
<p id="critterModalIdLeft" class="text-sm text-gray-400"></p>
|
|
</div>
|
|
<span id="critterModalRarityLeft" class="inline-flex items-center gap-1 rounded-full text-xs font-semibold px-3 py-1 shrink-0"></span>
|
|
</div>
|
|
<div class="rounded-2xl overflow-hidden ring-1 ring-gray-100 shadow-sm">
|
|
<img id="critterModalImage" src="" alt="" class="w-full aspect-square object-cover">
|
|
</div>
|
|
<div class="mt-4 grid grid-cols-4 gap-2 text-center">
|
|
<div>
|
|
<div class="mx-auto flex h-9 w-9 items-center justify-center rounded-full bg-pink-100 text-pink-600 mb-1"><i data-lucide="wand" class="h-4 w-4"></i></div>
|
|
<p id="critterModalAdjective" class="text-xs font-semibold text-gray-800 truncate"></p>
|
|
<p class="text-[11px] text-gray-400">Adjective</p>
|
|
</div>
|
|
<div>
|
|
<div class="mx-auto flex h-9 w-9 items-center justify-center rounded-full bg-blue-100 text-blue-600 mb-1"><i data-lucide="paw-print" class="h-4 w-4"></i></div>
|
|
<p id="critterModalNoun" class="text-xs font-semibold text-gray-800 truncate"></p>
|
|
<p class="text-[11px] text-gray-400">Animal</p>
|
|
</div>
|
|
<div>
|
|
<div class="mx-auto flex h-9 w-9 items-center justify-center rounded-full bg-green-100 text-green-600 mb-1"><i data-lucide="play" class="h-4 w-4"></i></div>
|
|
<p id="critterModalVerb" class="text-xs font-semibold text-gray-800 truncate"></p>
|
|
<p class="text-[11px] text-gray-400">Action</p>
|
|
</div>
|
|
<div>
|
|
<div class="mx-auto flex h-9 w-9 items-center justify-center rounded-full bg-purple-100 text-purple-600 mb-1"><i data-lucide="map-pin" class="h-4 w-4"></i></div>
|
|
<p id="critterModalLocation" class="text-xs font-semibold text-gray-800 truncate"></p>
|
|
<p class="text-[11px] text-gray-400">Location</p>
|
|
</div>
|
|
</div>
|
|
<p id="critterModalFlourish" class="mt-4 text-center italic text-sm text-pink-600"></p>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
<h2 id="critterModalTitleRight" class="text-2xl font-extrabold text-gray-900 pr-10"></h2>
|
|
<div class="mt-1 flex items-center gap-3">
|
|
<span id="critterModalIdRight" class="text-sm text-gray-400"></span>
|
|
<span id="critterModalRarityRight" class="inline-flex items-center gap-1 rounded-full text-xs font-semibold px-3 py-1"></span>
|
|
<button type="button" id="critterModalFavoriteBtn" onclick="toggleModalFavorite()" aria-label="Toggle favorite" class="ml-auto flex h-9 w-9 items-center justify-center rounded-full ring-1 ring-gray-200 text-gray-400 hover:bg-gray-50">
|
|
<i data-lucide="heart" class="h-4 w-4"></i>
|
|
</button>
|
|
</div>
|
|
<p id="critterModalDescription" class="mt-4 text-sm text-gray-600 leading-relaxed"></p>
|
|
|
|
<div class="mt-4 divide-y divide-gray-100 border-y border-gray-100">
|
|
<div class="flex items-center gap-2 py-2">
|
|
<i data-lucide="wand" class="h-4 w-4 text-pink-500 shrink-0"></i>
|
|
<span class="text-sm text-gray-500 w-20 shrink-0">Adjective</span>
|
|
<span id="critterModalAdjectivePill" class="inline-flex items-center rounded-full bg-pink-50 text-pink-700 text-xs font-medium px-2.5 py-1"></span>
|
|
</div>
|
|
<div class="flex items-center gap-2 py-2">
|
|
<i data-lucide="paw-print" class="h-4 w-4 text-blue-500 shrink-0"></i>
|
|
<span class="text-sm text-gray-500 w-20 shrink-0">Animal</span>
|
|
<span id="critterModalNounPill" class="inline-flex items-center rounded-full bg-blue-50 text-blue-700 text-xs font-medium px-2.5 py-1"></span>
|
|
</div>
|
|
<div class="flex items-center gap-2 py-2">
|
|
<i data-lucide="play" class="h-4 w-4 text-green-500 shrink-0"></i>
|
|
<span class="text-sm text-gray-500 w-20 shrink-0">Action</span>
|
|
<span id="critterModalVerbPill" class="inline-flex items-center rounded-full bg-green-50 text-green-700 text-xs font-medium px-2.5 py-1"></span>
|
|
</div>
|
|
<div class="flex items-center gap-2 py-2">
|
|
<i data-lucide="map-pin" class="h-4 w-4 text-purple-500 shrink-0"></i>
|
|
<span class="text-sm text-gray-500 w-20 shrink-0">Location</span>
|
|
<span id="critterModalLocationPill" class="inline-flex items-center rounded-full bg-purple-50 text-purple-700 text-xs font-medium px-2.5 py-1"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 grid grid-cols-3 gap-3 text-xs text-gray-500">
|
|
<div class="flex items-center gap-1.5">
|
|
<i data-lucide="calendar" class="h-4 w-4 shrink-0"></i>
|
|
<div>
|
|
<p class="text-gray-400">Created</p>
|
|
<p id="critterModalCreated" class="text-gray-700 font-medium"></p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-1.5">
|
|
<i data-lucide="scaling" class="h-4 w-4 shrink-0"></i>
|
|
<div>
|
|
<p class="text-gray-400">Resolution</p>
|
|
<p id="critterModalResolution" class="text-gray-700 font-medium"></p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-1.5">
|
|
<i data-lucide="hard-drive" class="h-4 w-4 shrink-0"></i>
|
|
<div>
|
|
<p class="text-gray-400">File Size</p>
|
|
<p id="critterModalFileSize" class="text-gray-700 font-medium"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 rounded-xl bg-blue-50 ring-1 ring-blue-100 p-3">
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="inline-flex items-center gap-1.5 text-xs font-semibold text-blue-700">
|
|
<i data-lucide="sparkles" class="h-4 w-4"></i> Prompt Used
|
|
</span>
|
|
<button type="button" onclick="copyModalPrompt()" aria-label="Copy prompt" class="text-blue-400 hover:text-blue-600">
|
|
<i data-lucide="copy" class="h-4 w-4"></i>
|
|
</button>
|
|
</div>
|
|
<p id="critterModalPrompt" class="text-xs italic text-gray-600 leading-relaxed"></p>
|
|
</div>
|
|
|
|
<div class="mt-4 grid grid-cols-3 gap-2">
|
|
<a id="critterModalDownloadBtn" href="#" download class="flex items-center justify-center gap-1.5 rounded-xl bg-gray-900 text-white text-sm font-semibold py-2.5 hover:bg-gray-800">
|
|
<i data-lucide="download" class="h-4 w-4"></i> Download
|
|
</a>
|
|
<button type="button" id="critterModalShareBtn" onclick="shareModalCritter()" class="flex items-center justify-center gap-1.5 rounded-xl bg-gray-100 text-gray-700 text-sm font-semibold py-2.5 hover:bg-gray-200">
|
|
<i data-lucide="share-2" class="h-4 w-4"></i> Share
|
|
</button>
|
|
<a id="critterModalCreateSimilarBtn" href="#" class="flex items-center justify-center gap-1.5 rounded-xl bg-gray-100 text-gray-700 text-sm font-semibold py-2.5 hover:bg-gray-200">
|
|
<i data-lucide="refresh-cw" class="h-4 w-4"></i> Create Similar
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentCritterModalId = null;
|
|
|
|
function updateModalFavoriteButton(isFavorite) {
|
|
const btn = document.getElementById('critterModalFavoriteBtn');
|
|
const icon = btn.querySelector('[data-lucide]');
|
|
icon.classList.toggle('text-pink-500', isFavorite);
|
|
icon.classList.toggle('fill-pink-500', isFavorite);
|
|
btn.classList.toggle('ring-pink-200', isFavorite);
|
|
}
|
|
|
|
function renderCritterModal(data) {
|
|
currentCritterModalId = data.id;
|
|
const idText = '#' + String(data.id).padStart(4, '0');
|
|
|
|
document.getElementById('critterModalTitleLeft').textContent = data.title;
|
|
document.getElementById('critterModalTitleRight').textContent = data.title;
|
|
document.getElementById('critterModalIdLeft').textContent = idText;
|
|
document.getElementById('critterModalIdRight').textContent = idText;
|
|
|
|
const rarityClasses = 'inline-flex items-center gap-1 rounded-full text-xs font-semibold px-3 py-1 shrink-0 ' + data.rarity.classes;
|
|
const rarityLeft = document.getElementById('critterModalRarityLeft');
|
|
const rarityRight = document.getElementById('critterModalRarityRight');
|
|
rarityLeft.textContent = data.rarity.label;
|
|
rarityRight.textContent = data.rarity.label;
|
|
rarityLeft.className = rarityClasses;
|
|
rarityRight.className = rarityClasses.replace(' shrink-0', '');
|
|
|
|
const img = document.getElementById('critterModalImage');
|
|
img.src = data.imageUrl || '';
|
|
img.alt = data.title;
|
|
|
|
document.getElementById('critterModalAdjective').textContent = data.adjective;
|
|
document.getElementById('critterModalNoun').textContent = data.noun;
|
|
document.getElementById('critterModalVerb').textContent = data.verb;
|
|
document.getElementById('critterModalLocation').textContent = data.location;
|
|
document.getElementById('critterModalAdjectivePill').textContent = data.adjective;
|
|
document.getElementById('critterModalNounPill').textContent = data.noun;
|
|
document.getElementById('critterModalVerbPill').textContent = data.verb;
|
|
document.getElementById('critterModalLocationPill').textContent = data.location;
|
|
|
|
const sentences = data.description.split('. ');
|
|
document.getElementById('critterModalFlourish').textContent = '"' + sentences[sentences.length - 1].replace(/\.$/, '') + '"';
|
|
document.getElementById('critterModalDescription').textContent = data.description;
|
|
|
|
document.getElementById('critterModalCreated').textContent = data.createdAt || '—';
|
|
document.getElementById('critterModalResolution').textContent = data.resolution || '—';
|
|
document.getElementById('critterModalFileSize').textContent = data.fileSize || '—';
|
|
document.getElementById('critterModalPrompt').textContent = data.prompt;
|
|
|
|
document.getElementById('critterModalDownloadBtn').href = data.imageUrl || '#';
|
|
document.getElementById('critterModalCreateSimilarBtn').href = data.createSimilarUrl;
|
|
|
|
updateModalFavoriteButton(data.isFavorite);
|
|
|
|
window.__critterModalShareUrl = window.location.origin + '/' + data.shareUrl;
|
|
window.__critterModalPromptText = data.prompt;
|
|
}
|
|
|
|
function openCritterModal(id) {
|
|
fetch('api/critter-details.php?id=' + encodeURIComponent(id))
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
if (data.error) return;
|
|
renderCritterModal(data);
|
|
document.getElementById('critterModalOverlay').classList.remove('hidden');
|
|
if (window.lucide) lucide.createIcons();
|
|
});
|
|
}
|
|
|
|
function closeCritterModal() {
|
|
document.getElementById('critterModalOverlay').classList.add('hidden');
|
|
}
|
|
|
|
document.getElementById('critterModalOverlay').addEventListener('click', function (event) {
|
|
if (event.target === this) closeCritterModal();
|
|
});
|
|
|
|
document.addEventListener('keydown', function (event) {
|
|
if (event.key === 'Escape') closeCritterModal();
|
|
});
|
|
|
|
function getCsrfToken() {
|
|
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
return meta ? meta.content : '';
|
|
}
|
|
|
|
function toggleModalFavorite() {
|
|
if (!currentCritterModalId) return;
|
|
fetch('api/critter-details.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: new URLSearchParams({ id: currentCritterModalId, action: 'toggle_favorite', csrf_token: getCsrfToken() }),
|
|
})
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
if (data.error) return;
|
|
updateModalFavoriteButton(data.isFavorite);
|
|
const card = document.querySelector('[data-critter-card="' + currentCritterModalId + '"]');
|
|
if (card) {
|
|
card.querySelectorAll('[data-favorite-icon]').forEach((icon) => {
|
|
icon.classList.toggle('text-pink-500', data.isFavorite);
|
|
icon.classList.toggle('fill-pink-500', data.isFavorite);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function copyModalPrompt() {
|
|
if (window.__critterModalPromptText && navigator.clipboard) {
|
|
navigator.clipboard.writeText(window.__critterModalPromptText);
|
|
}
|
|
}
|
|
|
|
function shareModalCritter() {
|
|
const url = window.__critterModalShareUrl;
|
|
if (navigator.share) {
|
|
navigator.share({ title: 'Check out my critter!', url }).catch(() => {});
|
|
return;
|
|
}
|
|
if (navigator.clipboard) {
|
|
navigator.clipboard.writeText(url);
|
|
const btn = document.getElementById('critterModalShareBtn');
|
|
const original = btn.innerHTML;
|
|
btn.textContent = 'Link copied!';
|
|
setTimeout(() => {
|
|
btn.innerHTML = original;
|
|
if (window.lucide) lucide.createIcons();
|
|
}, 1500);
|
|
}
|
|
}
|
|
</script>
|