const sample = { idea: 'Scattermind clarified a messy course idea. Now I need the first build order, not a dashboard.', optionsText: `- Manual build-order preview from one Concept Map - Copyable decision brief with Do first / Validate next / Defer / Park - Evidence questions beside each next move - Accounts and saved workspaces - Team voting on roadmap priority - Subscription billing layer - Polished export for sharing the defended order`, context: 'Snapshot / Concept Map handoff, solo builder, tired non-AI-native user, avoid auth/workspaces/billing before proof.', mode: 'mvp', }; 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 `
${items.map(([label, value]) => `
${label}
`).join('')}
`; } function renderResults(data) { const ranked = data.ranked || []; const brief = data.brief || {}; results.hidden = false; results.innerHTML = `

${escapeHtml(data.mode?.label || 'Ranked feedback')}

${escapeHtml(brief.headline || 'Ranked feedback map')}

${escapeHtml(brief.summary || '')}

${ranked.map((item) => `
#${item.rank} ${escapeHtml(item.lane?.label || 'Ranked')} ${item.metrics.score}

${escapeHtml(item.title)}

${item.description && item.description !== item.title ? `

${escapeHtml(item.description)}

` : ''}

Why: ${escapeHtml(item.reason)}.

Concern: ${escapeHtml(item.concern)}

${renderMetrics(item.metrics)}
`).join('')}
Next 48 hours
    ${(brief.next48Hours || []).map((step) => `
  1. ${escapeHtml(step)}
  2. `).join('')}
${(brief.whatWouldChangeRanking || []).length ? `
What would change the order
    ${brief.whatWouldChangeRanking.map((change) => `
  1. ${escapeHtml(change)}
  2. `).join('')}
` : ''} ${(brief.assumptions || []).length ? `
Context carried forward
` : ''} ${(brief.expertReflections || []).map((reflection) => `
${escapeHtml(reflection.lens)}

${escapeHtml(reflection.text)}

`).join('')}

${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);