← back to Costa Rica

public/logo-agent.html

160 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Logo Agent — Costa Rica Directory</title>
<style>
  :root { --bg:#0f2e1d; --fg:#eaf5ee; --accent:#2fb673; --card:#16301f; --line:rgba(255,255,255,.14); }
  * { box-sizing: border-box; }
  body { margin:0; font:15px/1.5 Inter,system-ui,sans-serif; background:var(--bg); color:var(--fg); }
  header { display:flex; align-items:center; gap:16px; padding:14px 22px; border-bottom:1px solid var(--line); position:sticky; top:0; background:var(--bg); z-index:5; }
  header h1 { font-size:16px; margin:0; font-weight:600; letter-spacing:.02em; }
  .spacer { flex:1; }
  .chip { font-size:12px; padding:4px 10px; border:1px solid var(--line); border-radius:999px; }
  .streak { color:var(--accent); font-weight:700; }
  main { max-width:1080px; margin:0 auto; padding:22px; }
  .stepbar { display:flex; gap:6px; margin-bottom:18px; flex-wrap:wrap; }
  .step { font-size:11px; padding:5px 10px; border-radius:6px; border:1px solid var(--line); opacity:.55; text-transform:capitalize; }
  .step.done { opacity:1; border-color:var(--accent); color:var(--accent); }
  .step.active { opacity:1; background:var(--accent); color:#05160d; border-color:var(--accent); font-weight:700; }
  h2.prompt { font-size:22px; margin:6px 0 4px; }
  .sub { opacity:.7; margin:0 0 20px; font-size:13px; }
  .grid { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; }
  .variant { background:var(--card); border:1px solid var(--line); border-radius:14px; padding:18px; cursor:pointer; transition:.15s; position:relative; user-select:none; }
  .variant:hover { transform:translateY(-3px); border-color:var(--accent); }
  .variant.ranked { border-color:var(--accent); }
  .rankbadge { position:absolute; top:10px; right:10px; width:26px; height:26px; border-radius:50%; background:var(--accent); color:#05160d; font-weight:800; display:none; align-items:center; justify-content:center; font-size:13px; }
  .variant.ranked .rankbadge { display:flex; }
  .preview { border-radius:10px; padding:22px; min-height:150px; display:flex; align-items:center; justify-content:center; }
  .lockrow { display:flex; gap:10px; margin-top:18px; flex-wrap:wrap; }
  button { font:inherit; border:1px solid var(--line); background:transparent; color:var(--fg); padding:10px 16px; border-radius:9px; cursor:pointer; }
  button.primary { background:var(--accent); color:#05160d; border-color:var(--accent); font-weight:700; }
  button:disabled { opacity:.4; cursor:not-allowed; }
  .hint { font-size:12px; opacity:.6; margin-top:10px; }
  .badges { display:flex; gap:6px; }
  .badge { font-size:16px; }
  .final { text-align:center; padding:40px; }
  .swatch { display:inline-block; width:22px; height:22px; border-radius:5px; margin:0 2px; border:1px solid var(--line); vertical-align:middle; }
  #confetti { position:fixed; inset:0; pointer-events:none; z-index:50; }
</style>
</head>
<body>
<header>
  <h1>🎨 Logo Agent — Costa Rica Directory</h1>
  <span class="chip">Round <b id="round">1</b></span>
  <span class="chip">Streak <span class="streak" id="streak">0</span>🔥</span>
  <span class="chip">XP <b id="xp">0</b></span>
  <span class="badges" id="badges"></span>
  <div class="spacer"></div>
  <button id="restart">New tournament</button>
</header>
<main>
  <div class="stepbar" id="stepbar"></div>
  <h2 class="prompt" id="prompt">Loading…</h2>
  <p class="sub" id="sub">Rank the three variants 1 → 2 → 3 by clicking them in order of preference.</p>
  <div class="grid" id="grid"></div>
  <div class="lockrow">
    <button class="primary" id="submit" disabled>Submit ranking (+10 XP)</button>
    <button id="lock" disabled>🔒 Lock in the winner</button>
    <button id="clear">Clear ranking</button>
  </div>
  <p class="hint" id="conv"></p>
</main>
<canvas id="confetti"></canvas>
<script>
const STEP_LABELS = { glyph:'Glyph', palette:'Palette', typography:'Typography', tagline:'Tagline', layout:'Layout', openspace:'Open space' };
const PROMPTS = {
  glyph:'Pick the mark', palette:'Pick the palette', typography:'Pick the type',
  tagline:'Pick the tagline', layout:'Pick the lockup', openspace:'Pick the density'
};
let sid=null, state=null, ranking=[];
const $ = (id)=>document.getElementById(id);
const api = (u,b)=>fetch('/api/logo-agent'+u,{method:'POST',headers:{'Content-Type':'application/json'},body:b?JSON.stringify(b):undefined}).then(r=>r.json());

function lockup(v){
  const p=v.palette, t=v.type, dens=v.density??50;
  const gap = Math.round(6 + dens/6);
  const svgBox = `<div style="width:64px;height:64px;color:${p.accent}">${v.svg}</div>`;
  const word = `<div style="font-family:${t.display};font-size:22px;line-height:1.05;color:${p.fg}">Costa&nbsp;Rica<br><span style="font-family:${t.ui};font-size:11px;letter-spacing:.18em;text-transform:uppercase;opacity:.8">Directory</span></div>`;
  const tag = `<div style="font-family:${t.ui};font-size:11px;opacity:.7;margin-top:6px;color:${p.fg}">${v.tagline}</div>`;
  let body;
  if(v.layout==='icon-only') body = svgBox;
  else if(v.layout==='wordmark-only') body = word;
  else if(v.layout==='icon-top') body = `<div style="display:flex;flex-direction:column;align-items:center;gap:${gap}px">${svgBox}${word}</div>`;
  else body = `<div style="display:flex;align-items:center;gap:${gap}px">${svgBox}${word}</div>`;
  return `<div class="preview" style="background:${p.bg}"><div style="text-align:center">${body}${tag}</div></div>`;
}

function render(){
  $('round').textContent=state.round; $('streak').textContent=state.winStreak; $('xp').textContent=state.xp;
  $('badges').innerHTML=(state.badges||[]).map(b=>`<span class="badge" title="${b}">${({'first-critic':'🥉','decisive':'🥈','tastemaker':'🥇','convergence':'🔥'})[b]||'⭐'}</span>`).join('');
  $('stepbar').innerHTML=Object.keys(STEP_LABELS).map((k,i)=>{
    const cls = i<state.componentIdx?'done':(i===state.componentIdx?'active':'');
    return `<span class="step ${cls}">${STEP_LABELS[k]}</span>`;}).join('');
  $('prompt').textContent=PROMPTS[state.component]||'Pick one';
  $('conv').textContent = state.converged ? '💎 Converged — the same winner held 3 rounds. Lock it in?' : '';
  ranking=[];
  $('grid').innerHTML=state.variants.map(v=>`<div class="variant" data-vid="${v.vid}"><div class="rankbadge"></div>${lockup(v)}</div>`).join('');
  [...document.querySelectorAll('.variant')].forEach(el=>el.onclick=()=>toggle(el));
  syncButtons();
}
function toggle(el){
  const vid=el.dataset.vid; const i=ranking.indexOf(vid);
  if(i>=0) ranking.splice(i,1); else if(ranking.length<3) ranking.push(vid);
  [...document.querySelectorAll('.variant')].forEach(v=>{
    const r=ranking.indexOf(v.dataset.vid);
    v.classList.toggle('ranked', r>=0);
    v.querySelector('.rankbadge').textContent = r>=0 ? r+1 : '';
  });
  syncButtons();
}
function syncButtons(){
  $('submit').disabled = ranking.length<2;
  $('lock').disabled = ranking.length<1;
}
$('clear').onclick=()=>render();
$('submit').onclick=async()=>{ if(ranking.length<2)return; state=await api('/rank/'+sid,{ranks:ranking}); confetti(12); render(); };
$('lock').onclick=async()=>{
  const winner = ranking[0] || (state.variants[0]&&state.variants[0].vid); if(!winner)return;
  const r=await api('/lock/'+sid,{winner_id:winner});
  confetti(60);
  if(r.done){ await finalize(); return; }
  state=r; render();
};
$('restart').onclick=start;

async function finalize(){
  const f=await api('/finalize/'+sid);
  const b=f.brand, p=b.palette;
  document.querySelector('main').innerHTML = `<div class="final">
    <h2 class="prompt">🥇 Brand locked & written to disk</h2>
    <div style="max-width:420px;margin:20px auto">${lockup(b)}</div>
    <p class="sub">Palette
      <span class="swatch" style="background:${p.bg}"></span>
      <span class="swatch" style="background:${p.accent}"></span>
      <span class="swatch" style="background:${p.card}"></span>
      <span class="swatch" style="background:${p.fg}"></span>
      · <b>${b.tagline}</b> · ${b.layout}
    </p>
    <p class="hint">Wrote <code>public/img/cr-logo.svg</code> + <code>data/logo-agent-final.json</code> (brand spec + CSS vars).</p>
    <button class="primary" onclick="location.reload()">Run another tournament</button>
  </div>`;
  confetti(120);
}

// tiny confetti
const cv=$('confetti'), cx=cv.getContext('2d'); let parts=[];
function resize(){cv.width=innerWidth;cv.height=innerHeight;} resize(); onresize=resize;
function confetti(n){ const C=['#2fb673','#12a5c7','#ff6b4a','#e0b34a','#eaf5ee'];
  for(let i=0;i<n;i++) parts.push({x:innerWidth/2,y:innerHeight/3,vx:(Math.random()-.5)*8,vy:Math.random()*-9-2,g:.3,c:C[i%C.length],r:3+Math.random()*4,life:60+Math.random()*40}); }
(function loop(){ cx.clearRect(0,0,cv.width,cv.height); parts=parts.filter(p=>p.life>0);
  parts.forEach(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=p.g;p.life--;cx.fillStyle=p.c;cx.fillRect(p.x,p.y,p.r,p.r);});
  requestAnimationFrame(loop); })();

async function start(){ const r=await api('/session/start'); sid=r.session_id; state=r; render(); }
start();
</script>
</body>
</html>