${escapeHtml(item.title)}
${item.description && item.description !== item.title ? `${escapeHtml(item.description)}
` : ''}Why: ${escapeHtml(item.reason)}.
Concern: ${escapeHtml(item.concern)}
${renderMetrics(item.metrics)}const sample = { idea: 'I’m building a lightweight product feedback tool for people with too many ideas and too many possible features. It should feel useful before becoming a workspace.', optionsText: `- Ranked feedback map for pasted feature lists - Expert reflections on the top options - Accounts and saved workspaces - Team voting on feature priority - Exportable decision brief for Slack or Notion - Custom criteria builder - Paid deeper product strategy pass`, context: 'MVP, solo builder, needs to feel valuable in under two minutes, avoid dashboard swamp.', mode: 'progress', }; const form = document.querySelector('#rankForm'); const results = document.querySelector('#results'); const toastEl = document.querySelector('#toast'); function escapeHtml(value) { return String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char])); } function metricPct(value) { return Math.max(0, Math.min(100, Math.round(Number(value || 0) * 10))); } function toast(message) { toastEl.textContent = message; toastEl.hidden = false; clearTimeout(toastEl._timer); toastEl._timer = setTimeout(() => { toastEl.hidden = true; }, 2600); } function fillSample() { form.idea.value = sample.idea; form.optionsText.value = sample.optionsText; form.context.value = sample.context; form.mode.value = sample.mode; document.querySelector('#try')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); toast('Sample loaded. Run it or replace it with your own mess.'); } function laneClass(lane) { return `lane-${lane?.id || 'defer'}`; } function renderMetrics(metrics) { const items = [ ['Value', metrics.value], ['Feasible', metrics.feasibility], ['Confidence', metrics.confidence], ['Revenue', metrics.revenue], ['Risk', metrics.risk], ]; return `
${escapeHtml(data.mode?.label || 'Ranked feedback')}
${escapeHtml(brief.summary || '')}
${escapeHtml(item.description)}
` : ''}Why: ${escapeHtml(item.reason)}.
Concern: ${escapeHtml(item.concern)}
${renderMetrics(item.metrics)}${escapeHtml(reflection.text)}
${escapeHtml(brief.caution || 'First-pass judgement, not an oracle.')}
`; results.scrollIntoView({ behavior: 'smooth', block: 'start' }); } async function createFeedbackMap(event) { event.preventDefault(); const submit = form.querySelector('button[type="submit"]'); const payload = Object.fromEntries(new FormData(form).entries()); submit.disabled = true; submit.textContent = 'Ranking…'; try { const response = await fetch('/api/rank-feedback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Could not rank this list.'); renderResults(data); } catch (error) { toast(error.message); } finally { submit.disabled = false; submit.textContent = 'Create ranked feedback map'; } } form.addEventListener('submit', createFeedbackMap); document.querySelector('#loadSample')?.addEventListener('click', fillSample); document.querySelector('#loadSampleTop')?.addEventListener('click', fillSample);