← 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 &amp; 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 => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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>