// ResultScreen — shown when the adaptive flow ends. Sections: // 0. Header row (title + small itinerary mandala + email box) // 1. Headline recommendation // 2. Hypothesis templates the researcher can adopt // 3. Conditions to defend // 4. Top candidate techniques // 5. Assumptions // 6. Suggested software // 7. Recommended authors with clickable links + foundational references // 8. Replicable methods recipe (existing component) // 9. Comparison to alternatives // 10. Reasoning trail const { useState } = React; // Build the list of "transformation conditions". For the recommended sector, // we walk every answered question and flag those whose chosen option // contributes a NON-POSITIVE weight (0, absent or negative) to that sector, // while OTHER options of the same question contribute positively. Those are // the design choices the researcher would have to revisit to actually be able // to apply the recommended technique. function buildTransformationConditions(history, winner) { if (!winner || !history || history.length === 0) return []; const bank = window.QUESTIONS || []; const conditions = []; for (const h of history) { const q = bank.find(x => x.id === h.questionId); if (!q || !Array.isArray(q.options)) continue; const chosenWeight = (h.weights && h.weights[winner.id]) || 0; if (chosenWeight > 0) continue; // chosen answer already supports the winner // Any option of the same question that is STRICTLY MORE COMPATIBLE // with the winner than the chosen one. This guarantees we usually // surface at least two alternatives even when only one option carries // a positive weight (the neighbouring 0-weight option is still // "less incompatible" than a -2 chosen option). const allBetter = q.options .map((o, i) => ({ idx: i, label: o.label, weight: (o.weights && o.weights[winner.id]) || 0, })) .filter(o => o.idx !== h.optionIdx && o.weight > chosenWeight) .sort((a, b) => b.weight - a.weight); if (allBetter.length === 0) continue; // Keep up to three alternatives. If positive-weight options exist, // prefer those; otherwise fall back to the best non-positive ones so // the section always offers at least two alternatives when possible. const positives = allBetter.filter(o => o.weight > 0); const better = (positives.length >= 2 ? positives : allBetter).slice(0, 3); conditions.push({ questionId: h.questionId, questionText: h.questionText, chosen: h.answerLabel, better, }); } return conditions; } function buildEmailHtml({ winner, history, scores, transformations, top3, total }) { const esc = (s) => String(s == null ? '' : s) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); const css = ` body { font-family: Georgia, 'EB Garamond', serif; color: #1a1a1a; background: #fafaf6; margin: 0; padding: 0; } .wrap { max-width: 640px; margin: 0 auto; padding: 28px 24px; } h1 { font-size: 26px; margin: 0 0 4px; color: #0e1530; font-weight: 500; } h2 { font-size: 16px; letter-spacing: 0.18em; text-transform: uppercase; color: #5e6680; margin: 28px 0 10px; font-weight: 500; font-style: italic; } .sub { font-style: italic; color: #5e6680; font-size: 16px; margin: 0 0 18px; } p { line-height: 1.55; } ul { padding-left: 20px; margin: 0; } li { margin: 4px 0; line-height: 1.5; } .card { background: #fff; border: 1px solid rgba(26,37,64,0.15); border-radius: 6px; padding: 16px 18px; margin: 10px 0; } .trail { border-left: 2px solid rgba(26,37,64,0.2); padding-left: 14px; margin: 6px 0 14px; } .trail-q { font-style: italic; color: #5e6680; font-size: 13px; } .trail-a { font-size: 14px; color: #0e1530; margin-top: 2px; } .author { display:block; padding: 8px 0; border-top: 1px solid rgba(26,37,64,0.1); } .author:first-child { border-top:none; } .author-name { font-weight: 500; color: #0e1530; } .author-work { font-style: italic; color: #5e6680; font-size: 13px; } a { color: #1a2540; } .footer { margin-top: 28px; font-size: 12px; color: #5e6680; font-style: italic; border-top: 1px solid rgba(26,37,64,0.15); padding-top: 14px; } `; const hyps = (winner.hypotheses || []).map(h => `
  • ${esc(h)}
  • `).join(''); const conds = (winner.conditions || []).map(c => `
  • ${esc(c)}
  • `).join(''); const techs = (winner.techniques || []).map(t => `
  • ${esc(t)}
  • `).join(''); const ass = (winner.assumes || []).map(a => `
  • ${esc(a)}
  • `).join(''); const sw = (winner.software || []).map(s => `
  • ${esc(s)}
  • `).join(''); const proc = (winner.procedure || []).map(p => `
  • ${esc(p)}
  • `).join(''); const auths = (winner.authors || []).map(a => `
    ${esc(a.name)}${a.url ? `  ` : ''}
    ${esc(a.work || '')}
    ${a.why ? `
    ${esc(a.why)}
    ` : ''}
    `).join(''); const refs = (winner.keyRefs || []).map(r => `
  • ${esc(r)}
  • `).join(''); const trail = (history || []).map(h => `
    ${esc(h.questionId)} · ${esc(h.questionText)}
    → ${esc(h.answerLabel)}
    `).join(''); const trans = (transformations || []).length ? `

    Conditions for switching to ${esc(winner.label)}

    Some of your answers carry a 0 weight for ${esc(winner.label)}. To apply this technique you would need to revisit the design points below.

    ${transformations.map(t => `
    ${esc(t.questionId)} · ${esc(t.questionText)}
    You chose: ${esc(t.chosen)}
    The recommendation would be:
    `).join('')} ` : ''; const cmp = (top3 || []).map((c, i) => `
    0${i + 1}
    ${esc(c.label)}
    ${esc(c.sub)} — ${(c.score / total * 100).toFixed(0)}% match
    `).join(''); return `${esc(winner.label)}

    Method Atlas — Recommendation

    ${esc(winner.label)}

    ${esc(winner.goal || winner.sub || '')}

    ${winner.description ? `

    ${esc(winner.description)}

    ` : ''} ${hyps ? `

    Hypothesis templates you can adopt

    ` : ''} ${conds ? `

    When this technique is the right call

    ` : ''} ${techs ? `

    Top candidate techniques

    ` : ''} ${ass ? `

    Assumptions to defend

    ` : ''} ${sw ? `

    Suggested software

    ` : ''} ${proc ? `

    Procedural checklist

      ${proc}
    ` : ''} ${auths ? `

    Recommended authors

    ${auths}
    ` : ''} ${refs ? `

    Foundational references

    ` : ''} ${trans} ${cmp ? `

    Alternative candidates

    ${cmp}` : ''} ${trail ? `

    Your reasoning trail

    ${trail}` : ''}
    `; } function buildEmailText({ winner, history, top3, total }) { const lines = []; lines.push(`METHOD ATLAS — RECOMMENDATION`); lines.push('='.repeat(40)); lines.push(winner.label); if (winner.goal) lines.push(winner.goal); if (winner.description) lines.push('\n' + winner.description); if ((winner.hypotheses || []).length) { lines.push('\nHypothesis templates:'); winner.hypotheses.forEach((h, i) => lines.push(` H${i + 1}. ${h}`)); } if ((winner.techniques || []).length) { lines.push('\nTop candidate techniques:'); winner.techniques.forEach(t => lines.push(` - ${t}`)); } if ((winner.assumes || []).length) { lines.push('\nAssumptions to defend:'); winner.assumes.forEach(a => lines.push(` - ${a}`)); } if ((winner.authors || []).length) { lines.push('\nRecommended authors:'); winner.authors.forEach(a => lines.push(` - ${a.name} — ${a.work || ''} ${a.url ? '(' + a.url + ')' : ''}`)); } if ((history || []).length) { lines.push('\nYour reasoning trail:'); history.forEach(h => lines.push(` ${h.questionId}: ${h.questionText}\n → ${h.answerLabel}`)); } if ((top3 || []).length) { lines.push('\nAlternatives:'); top3.forEach((c, i) => lines.push(` 0${i + 1}. ${c.label} — ${(c.score / total * 100).toFixed(0)}% match`)); } lines.push('\n— Method Atlas · Entrepreneur Research · upo.digital'); return lines.join('\n'); } window.ResultScreen = function ResultScreen({ scores, history, onRestart, onAlternativeClick, recHistory = [], onRevertRecommendation, onTrack, sessionId, tone = 'serif-dark' }) { const sectors = window.TAXONOMY.sectors; const ranked = [...sectors] .map(s => ({ ...s, score: scores[s.id] || 0 })) .sort((a, b) => b.score - a.score); const winner = ranked[0]; const top3 = ranked.slice(0, 3); const total = top3.reduce((a, b) => a + b.score, 0) || 1; const transformations = buildTransformationConditions(history, winner); const [email, setEmail] = useState(''); const [emailBusy, setEmailBusy] = useState(false); const [emailMsg, setEmailMsg] = useState(null); const sendEmail = async () => { if (emailBusy) return; const value = (email || '').trim(); if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) { setEmailMsg({ ok: false, text: 'Please enter a valid email address.' }); return; } setEmailBusy(true); setEmailMsg(null); try { const html = buildEmailHtml({ winner, history, scores, transformations, top3, total }); const text = buildEmailText({ winner, history, top3, total }); const subject = `Method Atlas — Your recommendation: ${winner.label}`; const res = await fetch('./api.php?action=send_email', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sessionId || null, email: value, winner_id: winner.id, winner_label: winner.label, subject, html_body: html, text_body: text, payload: { history: (history || []).map(h => ({ questionId: h.questionId, answerLabel: h.answerLabel, optionIdx: h.optionIdx, })), top3: top3.map(c => ({ id: c.id, label: c.label, score: c.score })), scores, }, }), }); const j = await res.json().catch(() => ({})); if (!res.ok) throw new Error(j.error || `HTTP ${res.status}`); setEmailMsg({ ok: true, text: 'Email sent. Check your inbox (and spam folder if you do not see it).' }); if (onTrack) onTrack('email_send', { email: value, winner_id: winner.id, status: 'sent' }); } catch (e) { setEmailMsg({ ok: false, text: 'Could not send: ' + (e.message || String(e)) }); if (onTrack) onTrack('email_send_error', { email: value, error: String(e.message || e) }); } finally { setEmailBusy(false); } }; return (
    {/* === Top header: title block on the left, mandala + email on the right === */}
    Recommendation

    {winner.label}

    {winner.goal}
    {winner.description &&

    {winner.description}

    } {recHistory.length > 0 && (
    Previous recommendation{recHistory.length === 1 ? '' : 's'} — click to recover or pick another:
    {recHistory.map((snap, i) => ( ))}
    )}
    {/* === Hypotheses & conditions === */}
    Hypothesis templates you can adopt
      {(winner.hypotheses || []).map((h, i) => (
    • H{i + 1}{h}
    • ))}
    When this technique is the right call
      {(winner.conditions || []).map((c, i) => (
    • {c}
    • ))}
    {/* === Two-column grid: techniques + assumptions + software === */}
    Top candidate techniques
      {winner.techniques.map(t =>
    • ·{t}
    • )}
    Assumptions to defend
      {winner.assumes.map(a =>
    • ·{a}
    • )}
    Suggested software
      {(winner.software || []).map(s =>
    • ·{s}
    • )}
    {winner.procedure && winner.procedure.length > 0 && (
    Procedural checklist
      {winner.procedure.map((p, i) =>
    1. {p}
    2. )}
    )}
    {/* === Recommended authors with links === */}
    Recommended authors to consult

    Drawn from the bibliography you supplied. Each link opens the most useful entry point — DOI, repository or canonical source — for the recommended technique.

    {(winner.authors || []).map((a, i) => (
    {a.name}
    {a.work}
    {a.why &&
    {a.why}
    }
    Open ↗
    ))}
    {/* === Foundational references === */} {winner.keyRefs && winner.keyRefs.length > 0 && (
    Foundational references
      {winner.keyRefs.map((r, i) =>
    • {r}
    • )}
    )} {/* === Transformation conditions: design changes the researcher would need to make in order to actually apply the recommended technique. Clicking an alternative re-runs the recommendation as if the user had picked that option. === */} {transformations.length > 0 && (
    Conditions for switching to {winner.label}

    The recommendation above reflects the design choices you made. Some of those choices carry a 0 weight for{' '} {winner.label} — meaning, in their current form, they are not compatible with this technique. Click any alternative below and we will recompute the recommendation as if you had chosen that option instead.

      {transformations.map((t, i) => (
    • {t.questionId} {' · '} {t.questionText}
      You chose:{' '} "{t.chosen}"
      The recommendation would be:
        {t.better.map((b, j) => (
      • ))}
    • ))}
    )} {/* === Alternatives === */}
    Alternative candidates
    {top3.map((c, i) => (
    0{i + 1}
    {c.label}
    {(c.score / total * 100).toFixed(0)}% match
    {c.sub}
    ))}
    {/* === Reasoning trail === */}
    Your reasoning trail
    {history.map((h, i) => (
    {h.questionId}   {h.questionText}
    → {h.answerLabel}
    {h.note &&
    "{h.note}"
    }
    ))}
    ); };