// MathBTI 前端逻辑 — 12题 → 16结果 → 4族 let testData = null, answers = [], currentQ = 0, myResult = null; async function init() { try { const r = await fetch('/data/seed_mathbti.json'); testData = await r.json(); renderIntro(); } catch (e) { document.getElementById('introScreen').innerHTML = '

加载失败,请检查网络

'; } } function renderIntro() { const axesEl = document.getElementById('introAxes'); if (axesEl && testData.axes) { axesEl.innerHTML = testData.axes.map(a => '
' + '' + (a.emoji_left || '') + '' + '' + a.name + '' + '' + (a.emoji_right || '') + '' + '
' ).join(''); } document.querySelector('.intro-meta').textContent = '⏱ 约2分钟 · 4选项 · 16种人格'; } function startQuiz() { answers = new Array(testData.questions.length).fill(-1); currentQ = 0; switchScreen('quizScreen'); renderQuestion(); } function renderQuestion() { const q = testData.questions[currentQ]; document.getElementById('quizQuestion').textContent = q.question; const optEl = document.getElementById('quizOptions'); const sel = answers[currentQ]; optEl.innerHTML = q.options.map((o, i) => '' ).join(''); // Progress const pct = Math.round((currentQ + 1) / testData.questions.length * 100); document.getElementById('progressFill').style.width = pct + '%'; // 最后一题时进度条末尾显示葫芦图标 const isLast=currentQ===testData.questions.length-1; document.getElementById('progressFill').style.setProperty('--is-last',isLast?'1':'0'); document.getElementById('progressText').textContent = (currentQ + 1) + '/' + testData.questions.length; // Next button const btn = document.getElementById('btnNext'); btn.disabled = answers[currentQ] === -1; btn.textContent = currentQ === testData.questions.length - 1 ? '查看结果' : '下一题'; } function selectOption(idx) { answers[currentQ] = idx; document.getElementById('btnNext').disabled = false; // Update visual document.querySelectorAll('.quiz-option').forEach((el, i) => { el.classList.toggle('selected', i === idx); }); } async function nextQuestion() { if (answers[currentQ] === -1) return; if (currentQ < testData.questions.length - 1) { currentQ++; renderQuestion(); return; } // Last question → calculate locally const btn = document.getElementById('btnNext'); btn.disabled = true; btn.textContent = '计算中…'; try { myResult = calculateResult(answers); // 自动获得对应的数学人物卡 const mbtiCode=myResult.binary_code; let gotNewCard=false; if(mbtiCode){ let cardState2=JSON.parse(localStorage.getItem('hulu_card_state')||'{}'); if(!cardState2.owned)cardState2.owned=[]; if(!cardState2.drawHistory)cardState2.drawHistory=[]; if(cardState2.owned.indexOf(mbtiCode)===-1){ cardState2.owned.push(mbtiCode); cardState2.drawHistory.push({code:mbtiCode,date:new Date().toISOString().slice(0,10),method:'mathbti'}); localStorage.setItem('hulu_card_state',JSON.stringify(cardState2)); gotNewCard=true; } } renderResult(); // 卡片领取提示 if(gotNewCard){ const res=myResult.result; const notice=document.getElementById('cardNotice'); if(notice){notice.style.display='block';document.getElementById('cardNoticeName').textContent=res?res.name:'—'} showToast('🎴 获得数学人物小卡!前往葫芦数学查看卡册'); } switchScreen('resultScreen'); } catch (e) { showToast('提交失败,请重试'); btn.disabled = false; btn.textContent = currentQ === testData.questions.length - 1 ? '查看结果' : '下一题'; } } function calculateResult(answers) { const axes = testData.scoring.axes; // ["style","purpose","era","social"] const maxPerAxis = testData.scoring.max_per_axis; const cutoff = testData.scoring.cutoff; // 计算每个轴的得分 const axisScores = {}; axes.forEach(axis => { axisScores[axis] = 0; }); testData.questions.forEach((q, i) => { const score = q.options[answers[i]].score; axisScores[q.axis] += score; }); // 生成二进制代码 (≤cutoff=0左极, ≥cutoff+1=1右极) const binaryCode = axes.map(axis => axisScores[axis] <= cutoff ? '0' : '1').join(''); // 获取结果 const result = testData.results[binaryCode]; return { binary_code: binaryCode, axis_scores: axisScores, result: result }; } function renderResult() { const res = myResult.result; if (!res) return; // Portrait const portraitEl = document.getElementById('resultPortrait'); if (res.portrait) { portraitEl.innerHTML = '
'; } else { portraitEl.innerHTML = '
' + (res.icon || 'Σ') + '
'; } // Name + Clan document.getElementById('resultName').innerHTML = res.name + ' ' + myResult.binary_code + ''; document.getElementById('resultTagline').textContent = res.tagline || ''; // Clan badge const clanData = testData.clans[res.clan]; document.getElementById('resultMathematician').innerHTML = (clanData ? '' + clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician + '' : '') + '代表数学家:' + (res.mathematician || '—') + ''; // Keywords const kwEl = document.getElementById('resultKeywords'); if (res.keywords) { kwEl.innerHTML = res.keywords.map(k => '' + k + '').join(''); kwEl.style.display = 'flex'; } else { kwEl.style.display = 'none'; } // Description document.getElementById('resultDesc').textContent = res.description || ''; document.getElementById('resultShort').textContent = res.short_desc || ''; // Bio document.getElementById('bioTitle').textContent = '关于 ' + (res.mathematician || '这位数学家'); document.getElementById('bioText').textContent = res.bio || ''; document.getElementById('bioFact').textContent = res.bio_fact || ''; // Axes bars const axesEl = document.getElementById('resultAxes'); if (testData.axes) { axesEl.innerHTML = testData.axes.map(a => { const score = myResult.axis_scores[a.id] || 0; const maxScore = testData.scoring.max_per_axis || 9; const pct = Math.round(score / maxScore * 100); return '
' + '' + (a.emoji_left || '') + ' ' + a.left + '' + '
' + '' + a.right + ' ' + (a.emoji_right || '') + '' + '
'; }).join(''); axesEl.style.display = 'block'; } // Five-dimension ability card from 16位数学家五维能力表 const s5El = document.getElementById('resultStats5'); if (s5El && res.stats5) { const labels = ['眼光', '人文', '侦探', '建模', '联结']; s5El.innerHTML = '
五维能力
' + labels.map(k => { const v = res.stats5[k] || 0; return '
' + '' + k + '' + '
' + '' + v + '' + '
'; }).join(''); s5El.style.display = 'block'; } // Adopt pet const petData = JSON.parse(localStorage.getItem('mb_pet') || '{}'); const adoptBtn = document.querySelector('.btn-adopt'); if (petData.type) { adoptBtn.textContent = '✅ 已领养 · 我的数学精灵'; adoptBtn.classList.add('adopted'); } // Apply clan color if (clanData) { document.getElementById('resultScreen').style.setProperty('--clan-color', clanData.color); } } // ---- Bio Expander ---- function toggleBio() { const body = document.getElementById('bioBody'); const arrow = document.getElementById('bioArrow'); const expanded = body.style.maxHeight && body.style.maxHeight !== '0px'; if (expanded) { body.style.maxHeight = '0px'; arrow.textContent = '▾'; } else { body.style.maxHeight = body.scrollHeight + 'px'; arrow.textContent = '▴'; } } // ---- Adopt Pet ---- function adoptPet() { if (!myResult) return; const res = myResult.result; const dims = {}; ['眼光', '人文', '侦探', '建模', '联结'].forEach(k => { dims[k] = Math.max(10, Math.min(100, Number((res.stats5 && res.stats5[k]) || 0))); }); const pet = { name: res.name || '—', type: res.name || '—', icon: res.icon || 'Σ', color: res.color || '#5B6AF0', mathematician: res.mathematician || '', clan: res.clan || '', binary_code: myResult.binary_code, level: 1, exp: 0, dims: dims }; localStorage.setItem('mb_pet', JSON.stringify(pet)); localStorage.setItem('mathbrain_pet', JSON.stringify(pet)); const btn = document.querySelector('.btn-adopt'); btn.textContent = '✅ 已领养 · 我的数学精灵'; btn.classList.add('adopted'); showToast('🧬 数学精灵已领养!每天来葫芦数学 App 喂它成长'); } // ---- Share ---- function copyShare() { if (!myResult) return; const res = myResult.result; const clanData = testData.clans[res.clan]; const text = '我的数学人格是「' + res.name + '」' + (res.icon || '') + '\n' + (clanData ? clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician : '') + '\n' + (res.short_desc || '') + '\n\n' + '👉 你也来测测:' + window.location.href + '\n\n' + '#数学人格测试 #MathBTI'; navigator.clipboard.writeText(text).then(() => showToast('已复制分享文案!')); } function shareResult() { if (!myResult) return; const res = myResult.result; const text = '我的数学人格是「' + res.name + '」' + (res.icon || '') + '!' + (res.short_desc || ''); if (navigator.share) { navigator.share({ title: 'MathBTI · 数学人格测试', text: text, url: window.location.href }).catch(() => {}); } else { copyShare(); } } function generateShareCard() { if (!myResult) return; const res = myResult.result; const clanData = testData.clans[res.clan]; const canvas = document.getElementById('shareCardCanvas'); const ctx = canvas.getContext('2d'); canvas.width = 600; canvas.height = 800; // Background const gradient = ctx.createLinearGradient(0, 0, 600, 800); gradient.addColorStop(0, (res.color || '#5B6AF0') + '22'); gradient.addColorStop(1, (res.color || '#5B6AF0') + '08'); ctx.fillStyle = '#FAFBFC'; ctx.fillRect(0, 0, 600, 800); ctx.fillStyle = gradient; ctx.fillRect(0, 0, 600, 800); // Top accent bar ctx.fillStyle = res.color || '#5B6AF0'; ctx.fillRect(0, 0, 600, 6); // Portrait placeholder ctx.fillStyle = res.icon_bg || '#F0F0F0'; ctx.beginPath(); ctx.arc(300, 180, 70, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#334155'; ctx.font = '60px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(res.icon || 'Σ', 300, 180); // Name ctx.fillStyle = '#1E293B'; ctx.font = 'bold 36px "PingFang SC", sans-serif'; ctx.fillText(res.name, 300, 280); // Clan + Mathematician ctx.fillStyle = '#64748B'; ctx.font = '18px "PingFang SC", sans-serif'; if (clanData) { ctx.fillText(clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician, 300, 315); } // Tagline ctx.fillStyle = res.color || '#5B6AF0'; ctx.font = 'italic 15px "PingFang SC", sans-serif'; const tagline = res.tagline || ''; wrapText(ctx, tagline, 300, 350, 480, 22); // Separator ctx.fillStyle = '#E2E8F0'; ctx.fillRect(60, 380, 480, 1); // Short desc ctx.fillStyle = '#334155'; ctx.font = '15px "PingFang SC", sans-serif'; const desc = res.short_desc || ''; wrapText(ctx, desc, 300, 410, 480, 24); // Axes let ay = 450; if (testData.axes && myResult.axis_scores) { ctx.fillStyle = '#64748B'; ctx.font = '14px "PingFang SC", sans-serif'; testData.axes.forEach(a => { const s = myResult.axis_scores[a.id] || 0; const maxS = testData.scoring.max_per_axis || 9; const pct = Math.round(s / maxS * 100); ctx.fillText(a.emoji_left + ' ' + a.left + ' — ' + pct + '% — ' + a.right + ' ' + a.emoji_right, 300, ay); ay += 28; }); } // Bottom ctx.fillStyle = '#94A3B8'; ctx.font = '13px "PingFang SC", sans-serif'; ctx.fillText('葫芦数学 · MathBTI', 300, 730); // 葫芦水印 ctx.save();ctx.globalAlpha=.06;ctx.font='80px sans-serif';ctx.fillStyle='#A8907E'; ctx.fillText('🏮',480,680); ctx.restore(); ctx.fillText('你也来测测你的数学人格类型 →', 300, 755); // QR placeholder ctx.fillStyle = '#E2E8F0'; ctx.fillRect(255, 640, 90, 90); ctx.fillStyle = '#94A3B8'; ctx.font = '11px sans-serif'; ctx.fillText('扫码测', 300, 685); document.getElementById('shareCardPreview').style.display = 'block'; document.getElementById('shareCardPreview').scrollIntoView({ behavior: 'smooth' }); } function downloadShareCard() { const canvas = document.getElementById('shareCardCanvas'); const link = document.createElement('a'); link.download = 'mathbti_' + (myResult ? myResult.binary_code : 'result') + '.png'; link.href = canvas.toDataURL('image/png'); link.click(); showToast('📸 图片已保存!'); } function wrapText(ctx, text, x, y, maxWidth, lineHeight) { const lines = []; let current = ''; for (let i = 0; i < text.length; i++) { const test = current + text[i]; if (ctx.measureText(test).width > maxWidth && current.length > 0) { lines.push(current); current = text[i]; } else { current = test; } } if (current) lines.push(current); lines.forEach((line, idx) => { ctx.fillText(line, x, y + idx * lineHeight); }); } // ---- Retry ---- function retryQuiz() { answers = []; currentQ = 0; myResult = null; document.getElementById('bioBody').style.maxHeight = '0px'; document.getElementById('bioArrow').textContent = '▾'; document.getElementById('shareCardPreview').style.display = 'none'; switchScreen('introScreen'); } // ---- Helpers ---- function switchScreen(id) { document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); document.getElementById(id).classList.add('active'); window.scrollTo({ top: 0, behavior: 'smooth' }); } let toastTimer = null; function showToast(msg) { const el = document.getElementById('toast'); el.textContent = msg; el.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => el.classList.remove('show'), 2200); } // Bootstrap document.addEventListener('DOMContentLoaded', init);