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