let testData = null, answers = [], currentQ = 0, myResult = null;
async function init() {
try {
const r = await fetch('/api/math_personality');
const j = await r.json();
if (j.code !== 0) { showToast('测试数据加载失败'); return; }
testData = j.data;
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('');
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;
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;
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;
}
const btn = document.getElementById('btnNext');
btn.disabled = true;
btn.textContent = '计算中…';
try {
const r = await fetch('/api/math_personality/calculate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ answers })
});
const j = await r.json();
if (j.code !== 0) { showToast('计算结果失败'); return; }
myResult = j.data;
const mbtiCode=j.data.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=j.data.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 renderResult() {
const res = myResult.result;
if (!res) return;
const portraitEl = document.getElementById('resultPortrait');
if (res.portrait) {
portraitEl.innerHTML = '
' + (res.icon || 'Σ') + ' ';
} else {
portraitEl.innerHTML = '' + (res.icon || 'Σ') + '
';
}
document.getElementById('resultName').innerHTML = res.name + ' ' + myResult.binary_code + '';
document.getElementById('resultTagline').textContent = res.tagline || '';
const clanData = testData.clans[res.clan];
document.getElementById('resultMathematician').innerHTML =
(clanData ? '' + clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician + '' : '') +
'代表数学家:' + (res.mathematician || '—') + '';
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';
}
document.getElementById('resultDesc').textContent = res.description || '';
document.getElementById('resultShort').textContent = res.short_desc || '';
document.getElementById('bioTitle').textContent = '关于 ' + (res.mathematician || '这位数学家');
document.getElementById('bioText').textContent = res.bio || '';
document.getElementById('bioFact').textContent = res.bio_fact || '';
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';
}
const petData = JSON.parse(localStorage.getItem('mb_pet') || '{}');
const adoptBtn = document.querySelector('.btn-adopt');
if (petData.type) {
adoptBtn.textContent = '✅ 已领养 · 我的数学精灵';
adoptBtn.classList.add('adopted');
}
if (clanData) {
document.getElementById('resultScreen').style.setProperty('--clan-color', clanData.color);
}
}
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 = '▴';
}
}
function adoptPet() {
if (!myResult) return;
const res = myResult.result;
const pet = {
type: res.name || '—',
icon: res.icon || 'Σ',
color: res.color || '#5B6AF0',
mathematician: res.mathematician || '',
clan: res.clan || '',
binary_code: myResult.binary_code
};
localStorage.setItem('mb_pet', JSON.stringify(pet));
const btn = document.querySelector('.btn-adopt');
btn.textContent = '✅ 已领养 · 我的数学精灵';
btn.classList.add('adopted');
showToast('🧬 数学精灵已领养!每天来葫芦数学 App 喂它成长');
}
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;
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);
ctx.fillStyle = res.color || '#5B6AF0';
ctx.fillRect(0, 0, 600, 6);
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);
ctx.fillStyle = '#1E293B';
ctx.font = 'bold 36px "PingFang SC", sans-serif';
ctx.fillText(res.name, 300, 280);
ctx.fillStyle = '#64748B';
ctx.font = '18px "PingFang SC", sans-serif';
if (clanData) {
ctx.fillText(clanData.emoji + ' ' + clanData.name + ' · ' + res.mathematician, 300, 315);
}
ctx.fillStyle = res.color || '#5B6AF0';
ctx.font = 'italic 15px "PingFang SC", sans-serif';
const tagline = res.tagline || '';
wrapText(ctx, tagline, 300, 350, 480, 22);
ctx.fillStyle = '#E2E8F0';
ctx.fillRect(60, 380, 480, 1);
ctx.fillStyle = '#334155';
ctx.font = '15px "PingFang SC", sans-serif';
const desc = res.short_desc || '';
wrapText(ctx, desc, 300, 410, 480, 24);
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;
});
}
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);
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);
});
}
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');
}
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);
}
document.addEventListener('DOMContentLoaded', init);