← back to Animals
public/dogpark/breed-avatars.html
171 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Pick your dog-park avatar</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root { --bg:#0e0e10; --ink:#e8e6e0; --rule:#26252a; --mute:#7a7873; --accent:#d4a04a; }
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.5 -apple-system,system-ui,sans-serif;font-weight:300}
header{padding:32px 40px;border-bottom:1px solid var(--rule)}
h1{margin:0 0 6px;font-weight:300;font-size:28px;letter-spacing:-.01em}
h1 em{font-style:normal;color:var(--accent)}
.lede{color:var(--mute);max-width:60ch;margin:0}
main{padding:32px 40px;max-width:1200px;margin:0 auto}
.breed-form{display:flex;gap:12px;align-items:end;margin-bottom:32px;flex-wrap:wrap}
.breed-form label{display:flex;flex-direction:column;gap:6px;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--mute)}
.breed-form input,.breed-form select{background:#161618;border:1px solid var(--rule);color:var(--ink);padding:10px 14px;border-radius:6px;font-family:inherit;font-size:14px;min-width:240px}
.breed-form button{background:var(--accent);color:#0e0e10;border:0;padding:10px 24px;font-weight:600;font-size:13px;letter-spacing:.14em;text-transform:uppercase;border-radius:6px;cursor:pointer}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-top:18px}
@media (max-width:720px){.grid{grid-template-columns:1fr}}
.card{background:#161618;border:1px solid var(--rule);border-radius:10px;overflow:hidden;cursor:pointer;transition:border-color 180ms,transform 180ms;position:relative}
.card:hover{border-color:var(--accent);transform:translateY(-2px)}
.card.selected{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent)}
.card.selected::after{content:'✓';position:absolute;top:10px;right:12px;background:var(--accent);color:#0e0e10;width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700}
.card img{display:block;width:100%;aspect-ratio:1;object-fit:cover;background:#1a1a1d}
.card .meta{padding:14px 16px;border-top:1px solid var(--rule)}
.card .title{font-size:14px;color:var(--ink);margin-bottom:4px;font-weight:400}
.card .credit{font-size:11px;color:var(--mute);line-height:1.5}
.card .credit a{color:var(--mute);text-decoration:underline}
.card .credit a:hover{color:var(--accent)}
.card .lic{display:inline-block;padding:2px 8px;border:1px solid var(--rule);border-radius:4px;font-size:10px;letter-spacing:.1em;margin-top:6px}
#pick-bar{position:fixed;bottom:0;left:0;right:0;background:#0e0e10;border-top:1px solid var(--accent);padding:18px 40px;display:none;align-items:center;gap:18px;z-index:50;box-shadow:0 -8px 24px rgba(0,0,0,.6)}
#pick-bar.show{display:flex}
#pick-bar .preview{width:48px;height:48px;border-radius:6px;background-size:cover;background-position:center;border:1px solid var(--rule);flex-shrink:0}
#pick-bar .info{flex:1;font-size:14px;color:var(--ink)}
#pick-bar .info b{color:var(--accent);font-weight:500}
#pick-bar input{background:#161618;border:1px solid var(--rule);color:var(--ink);padding:10px 14px;border-radius:6px;font-family:inherit;font-size:14px;width:160px}
#pick-bar button{background:var(--accent);color:#0e0e10;border:0;padding:12px 24px;font-weight:600;font-size:13px;letter-spacing:.14em;text-transform:uppercase;border-radius:6px;cursor:pointer;font-family:inherit}
#empty{color:var(--mute);font-style:italic;padding:40px 0;text-align:center}
#note{margin-top:32px;font-size:12px;color:var(--mute);max-width:60ch}
</style>
</head>
<body>
<header>
<h1>Pick your <em>·</em> dog-park avatar</h1>
<p class="lede">Type a breed name and we'll show 3 photographer-credited photos curated from public-domain & CC-BY sources. Choose one — your dog runs into the park.</p>
</header>
<main>
<form class="breed-form" id="breedForm">
<label>Breed slug
<input id="slug" placeholder="e.g. labrador-retriever" autocomplete="off" required>
</label>
<button type="submit">Load avatars</button>
</form>
<div id="root"></div>
<p id="note">Don't see your breed? The picker covers 140+ breeds across dogs, cats, and horses.
URL hint: append <code>?slug=labrador-retriever</code> to jump straight to a breed.</p>
</main>
<div id="pick-bar">
<div class="preview" id="pb-preview"></div>
<div class="info">Selected · <b id="pb-name">—</b></div>
<input id="pb-dogname" placeholder="Dog name" maxlength="20" value="Buddy">
<button id="pb-go">Enter dog park →</button>
</div>
<script>
let chosen = null;
async function loadBreed(slug) {
const root = document.getElementById('root');
root.innerHTML = '<p id="empty">Loading…</p>';
try {
const r = await fetch('/api/breeds/' + encodeURIComponent(slug) + '/dogpark-avatars');
if (!r.ok) {
root.innerHTML = `<p id="empty">No breed matches "${slug}". Try the slug from the breed page URL.</p>`;
return;
}
const data = await r.json();
if (!data.avatars?.length) {
root.innerHTML = '<p id="empty">No avatars curated yet for this breed.</p>';
return;
}
document.title = `Pick your ${data.breed.common_name} avatar`;
root.innerHTML = `
<h2 style="font-weight:300;margin-bottom:6px;font-size:22px">${escapeHtml(data.breed.common_name)}</h2>
<p style="color:#7a7873;font-size:13px;margin-bottom:18px">3 suggested avatars · all photos PD / CC0 / CC-BY / CC-BY-SA</p>
<div class="grid">${data.avatars.map(a => cardHtml(a, data.breed)).join('')}</div>
`;
for (const card of root.querySelectorAll('.card')) {
card.addEventListener('click', () => {
document.querySelectorAll('.card.selected').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
chosen = {
url: card.dataset.url,
author: card.dataset.author,
license: card.dataset.license,
source: card.dataset.source,
breed: data.breed.common_name,
breedSlug: data.breed.slug,
};
document.getElementById('pb-preview').style.backgroundImage = `url('${escapeAttr(chosen.url)}')`;
document.getElementById('pb-name').textContent = `${data.breed.common_name} · ${chosen.author || 'unknown'}`;
document.getElementById('pick-bar').classList.add('show');
});
}
} catch (e) {
root.innerHTML = '<p id="empty">Failed to load avatars. Try again?</p>';
}
}
function cardHtml(a, breed) {
const img = a.thumb_url || a.image_url;
const author = a.author || 'Unknown photographer';
const lic = a.license_short || 'CC';
const licUrl = a.license_url || a.source_url || '';
return `
<div class="card"
data-url="${escapeAttr(a.image_url)}"
data-author="${escapeAttr(author)}"
data-license="${escapeAttr(lic)}"
data-source="${escapeAttr(a.source_url || '')}">
<img src="${escapeAttr(img)}" alt="${escapeAttr(breed.common_name)} avatar slot ${a.slot}" loading="lazy">
<div class="meta">
<div class="title">Slot ${a.slot}${a.pinned ? ' · pinned' : ''}</div>
<div class="credit">
${escapeHtml(author)}
${a.author_url ? ` · <a href="${escapeAttr(a.author_url)}" target="_blank" rel="noopener">profile</a>` : ''}
<br><a href="${escapeAttr(licUrl)}" target="_blank" rel="noopener"><span class="lic">${escapeHtml(lic)}</span></a>
</div>
</div>
</div>`;
}
function escapeHtml(s) {
return String(s ?? '').replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
}
function escapeAttr(s) { return escapeHtml(s); }
document.getElementById('breedForm').addEventListener('submit', e => {
e.preventDefault();
const slug = document.getElementById('slug').value.trim().toLowerCase();
if (slug) {
history.replaceState(null, '', `?slug=${encodeURIComponent(slug)}`);
loadBreed(slug);
}
});
document.getElementById('pb-go').addEventListener('click', () => {
if (!chosen) return;
const dogName = document.getElementById('pb-dogname').value.trim() || 'Buddy';
localStorage.setItem('dogpark_avatar_url', chosen.url);
localStorage.setItem('dogpark_dog_name', dogName);
localStorage.setItem('dogpark_breed_name', chosen.breed);
localStorage.setItem('dogpark_breed_slug', chosen.breedSlug);
// Hand off to the 5-second 3D running intro, which then redirects to /dog-park
window.location.href = '/dog-park/intro';
});
// Auto-load if ?slug=… is in the URL
const u = new URL(location.href);
const slug = u.searchParams.get('slug');
if (slug) {
document.getElementById('slug').value = slug;
loadBreed(slug);
}
</script>
</body>
</html>