← back to Animals
public/dogpark/avatars-gallery.html
137 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Choose your dog park avatar · AnimalsDirectory</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;display:flex;flex-direction:column;gap:48px;max-width:1500px;margin:0 auto}
.dog{}
.dog-head{display:flex;align-items:flex-start;gap:24px;margin-bottom:18px;padding-bottom:18px;border-bottom:1px solid var(--rule)}
.source{width:160px;height:160px;border-radius:8px;background:#1a1a1d;background-size:cover;background-position:center;flex-shrink:0;border:1px solid var(--rule)}
.meta h2{margin:0 0 4px;font-size:22px;font-weight:400}
.meta .desc{color:var(--mute);font-size:13px;max-width:64ch;margin:0;font-style:italic}
.meta .warn{color:#d87a7a;font-size:13px;margin-top:8px}
.grid{display:grid;grid-template-columns:repeat(6,minmax(180px,1fr));gap:14px}
@media (max-width:1280px){.grid{grid-template-columns:repeat(3,1fr)}}
@media (max-width:720px){.grid{grid-template-columns:repeat(2,1fr)}}
.card{background:#161618;border:1px solid var(--rule);border-radius:8px;overflow:hidden;cursor:pointer;transition:border-color 180ms,transform 180ms;position:relative;color:inherit;font:inherit;padding:0;text-align:left;width:100%}
.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:8px;right:10px;background:var(--accent);color:#0e0e10;width:26px;height:26px;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}
.card .label{padding:8px 12px;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--mute);border-top:1px solid var(--rule)}
footer{padding:32px 40px 120px;text-align:center;color:var(--mute);font-size:11px;letter-spacing:.14em;text-transform:uppercase}
#pick-bar{position:fixed;bottom:0;left:0;right:0;background:#0e0e10;border-top:1px solid var(--accent);padding:18px 40px;display:flex;align-items:center;gap:18px;z-index:50;box-shadow:0 -8px 24px rgba(0,0,0,.6);transform:translateY(100%);transition:transform 240ms cubic-bezier(.2,.7,.2,1)}
#pick-bar.show{transform:translateY(0)}
#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 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}
#pick-bar button:hover{background:#e6b86a}
</style>
</head>
<body>
<header>
<h1>Choose your <em>avatar</em></h1>
<p class="lede">Pick a pre-made avatar for the dog park — three dogs, six art styles each. Or <a href="/dog-park/avatars" style="color:var(--accent);text-decoration:underline">upload your own dog</a> to generate fresh ones.</p>
</header>
<main id="root"></main>
<div id="pick-bar">
<div class="preview" id="pb-preview"></div>
<div class="info">Selected · <b id="pb-name">—</b></div>
<button id="pb-go">Enter dog park →</button>
</div>
<script>
const VARIANT_LABELS = {
hero_pbr: 'Hero · golden hour',
studio_portrait: 'Studio portrait',
chibi: 'Stylized chibi',
sticker: 'Sticker',
action: 'Action · running',
royal: 'Royal portrait',
};
const VARIANT_ORDER = ['hero_pbr','studio_portrait','chibi','sticker','action','royal'];
const DOGS = [
{ name:'Bowie', token:'test-bowie', sourceUrl:'/static/uploads/owner-pets/pet-1-1FOch8NsG08AhQGf_kP8rdr486yrvjTEQ.jpeg' },
{ name:'Humphrey', token:'test-humphrey', sourceUrl:'/static/uploads/owner-pets/pet-2-1_epsqjZqpgJdBGhJQXtRI5w8AsuIEhgX.jpg' },
{ name:'Madison', token:'test-madison', sourceUrl:'/static/uploads/owner-pets/pet-2-1Zccoy7SYOM7dgvvVaeLw7T7gqclP4Upi.jpg', sourceNote:'(crop: chocolate Lab on right; cream Lab is Humphrey)' },
];
async function loadSubject(token) {
try {
const r = await fetch(`/static/uploads/avatars/${token}/subject.json`);
if (!r.ok) return null;
return await r.json();
} catch { return null; }
}
async function render() {
const root = document.getElementById('root');
for (const d of DOGS) {
const subject = await loadSubject(d.token);
const desc = subject?.one_sentence_subject || '(no subject description — vision returned empty; provide a clearer photo)';
const breed = subject?.breed_guess || '';
const isEmpty = !subject?.one_sentence_subject;
const section = document.createElement('section');
section.className = 'dog';
section.innerHTML = `
<div class="dog-head">
<div class="source" style="background-image:url('${d.sourceUrl}')"></div>
<div class="meta">
<h2>${d.name}${breed ? ' · <span style="color:var(--mute);font-weight:300;font-size:14px">' + breed + '</span>' : ''}</h2>
<p class="desc">"${desc}"</p>
${isEmpty ? '<p class="warn">⚠ vision could not extract subject — source photo is too distant / dark / cluttered. Variants below are not anchored to this dog.</p>' : ''}
</div>
</div>
<div class="grid">
${VARIANT_ORDER.map(v => `
<button type="button" class="card" data-url="/static/uploads/avatars/${d.token}/${v}.png" data-name="${d.name} · ${VARIANT_LABELS[v]}" data-dogname="${d.name}" aria-label="Pick ${d.name} · ${VARIANT_LABELS[v]}">
<img src="/static/uploads/avatars/${d.token}/${v}.png" alt="${VARIANT_LABELS[v]}" loading="lazy">
<div class="label">${VARIANT_LABELS[v]}</div>
</button>
`).join('')}
</div>
`;
root.appendChild(section);
}
// Wire selection
const pickBar = document.getElementById('pick-bar');
const pbPreview = document.getElementById('pb-preview');
const pbName = document.getElementById('pb-name');
const pbGo = document.getElementById('pb-go');
let chosen = null;
for (const card of document.querySelectorAll('.card')) {
card.addEventListener('click', (e) => {
e.preventDefault();
document.querySelectorAll('.card.selected').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
chosen = { url: card.dataset.url, name: card.dataset.name, dogName: card.dataset.dogname };
pbPreview.style.backgroundImage = `url('${chosen.url}')`;
pbName.textContent = chosen.name;
pickBar.classList.add('show');
});
}
pbGo.addEventListener('click', () => {
if (!chosen) return;
localStorage.setItem('dogpark_avatar_url', chosen.url);
localStorage.setItem('dogpark_dog_name', chosen.dogName);
window.location.href = '/dog-park';
});
}
render();
</script>
</body>
</html>