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