// ===== State =====
const API_BASE='',USER_ID=localStorage.getItem('mb_user_id')||('mb_'+Math.random().toString(36).slice(2,10));
localStorage.setItem('mb_user_id',USER_ID);
let CP='home',allV=[],allK=[],kT=[],vD=[],cD='',curDim='',cT='',sD=null,sVN={currentNode:null,stats:{shuli:10,xiayi:10,xinzhi:10},fav:{}},simD=null,simS={currentNode:null,stats:{gpa:3,interest:0,skill:0}},favC={},chatCtx={},tt=null;
let cardData=null,cardCache=null,_gachaBusy=false;
let cardState={owned:[],draws:0,lastDaily:'',knowledgeRead:0,drawHistory:[]};
let _lang=localStorage.getItem('mb_lang')||'zh';
// ===== i18n =====
const I18N={zh:{home:'首页',discover:'发现',toolbox:'工具箱',knowledge:'知识卡片',story:'葫芦侠行',simulator:'数学人生',favorites:'我的收藏',profile:'个人中心',search:'搜索内容...',guest:'游客用户',
heroTitle:'葫芦数学 · 用玩的方式打开数学',heroSub:'数学不是公式,是你理解世界的方式',heroCTA:'开始探索 →',
exploreTitle:'探索数学的万千可能',tryTitle:'试试这些',
mbtiName:'MathBTI 数学人格',mbtiDesc:'12题发现你的数学人格,领养专属数学小人 →',
toolboxName:'数学工具箱',toolboxDesc:'口算竞赛 · 计算器 · 符号查询 · 函数绘图 →',
storyName:'葫芦侠行',storyDesc:'北宋数学武侠 · 8章93节点 · 8种结局 →',
simName:'数学人生',simDesc:'四大主题 · 更多数学人生敬请期待 →',
videoName:'发现视频',videoDesc:'五维能力地图 · 看视频点亮成长',cardName:'知识卡片',cardDesc:'8个数学话题',
aiName:'AI 助教',aiDesc:'随时解答数学疑问',
petTip:'每天使用工具箱和模拟器获取经验升级',
dailyFactPrefix:'💡 数学冷知识:',
discoverGreet:'Hello there. 这里收集了来自各专业志愿者的数学科普视频。',
mapTitle:'五维能力地图',mapSub:'观看视频,点亮数学小人的五种能力',mapHint:'点击地图节点,进入对应维度的视频流',
dimAll:'全部视频',dimVision:'数学眼光',dimHumanity:'数学人文',dimDetective:'数学侦探',dimModeling:'数学建模',dimConnection:'数学联结',
claimVideo:'看完打卡',claimDone:'已学习',claimNeedPet:'先做 MathBTI 领养数学小人,再看视频点亮能力',
filterAll:'全部',noVideo:'暂无视频',noCard:'暂无卡片',noResult:'无结果',
loadFailVideo:'视频加载失败',loadFailCard:'卡片加载失败',loadFailProfile:'加载失败',
loadMore:'下拉加载更多…',loadEnd:'— 已经到底啦 · 共 ',loadEndSuf:' 个视频 —',
close:'关闭',save:'保存',saveOK:'保存成功',saveFail:'保存失败',
toolboxHeader:'工具箱',toolboxSub:'动手做数学——口算、计算、查询、绘图',
toolMathrace:'口算竞赛',toolMathraceDesc:'限时60秒,挑战你的心算速度',
toolCalc:'计算器',toolCalcDesc:'支持加减乘除、幂运算、三角函数',
toolSym:'数学符号查询',toolSymDesc:'点击符号查看含义和LaTeX写法',
toolGraph:'函数图形绘图',toolGraphDesc:'输入 f(x),实时绘制函数曲线',
knowledgeHeader:'知识卡片',knowledgeSub:'轻量数学发现,每天一个新概念',
storyHeader:'葫芦侠行',storySub:'北宋数学武侠互动文字冒险 · 8章93节点 · 8+种结局',
storyLoadFail:'故事加载失败',storyNodeNotFound:'节点未找到',
simHeader:'数学人生',simSub:'四大主题 · 更多数学人生敬请期待',
simLoadFail:'模拟器加载失败',simReset:'重新开始',
favHeader:'我的收藏',favSub:'已收藏的视频和知识卡片',
favEmpty:'暂无收藏',favEmptyHint:'在发现页点击 🤍 即可收藏',
profileHeader:'个人中心',profileEdit:'编辑资料',
profileAvatar:'头像:',profileNick:'昵称:',
profileWatchCount:'观看次数',profileFavCount:'收藏数量',
aiHeader:'AI 数学助教',aiWelcome:'你好!我是葫芦数学的AI助教 👋 试试问我:学数学对学医有什么用?',
aiPlaceholder:'输入问题...',aiOffline:'抱歉,AI暂时无法回复',aiNetworkErr:'网络异常,请稍后重试',
calcErr:'表达式有误',calcPlaceholder:'输入表达式 如 2+3*4',
graphPlaceholder:'如 Math.sin(x) 或 x*x',graphBtn:'绘制',
mrTitle:'准备好了吗?',mrPlaceholder:'输入答案',mrTimeout:'⏰ 时间到!得分:',mrCorrect:'正确',mrRight:'✅ 正确!+10分',
symDetail:'👆 点击符号查看详情',
mbtiFloat:'测测你的数学人格',
aiAskBtn:'问问AI',aiAskSuffix:'是什么?',
petLevelUp:'升级啦!Lv.{n}',
staticHeroTitle:'葫芦数学 · 用玩的方式打开数学',staticHeroSub:'数学不是公式,是你理解世界的方式',staticHeroCTA:'开始探索 →',
staticExploreTitle:'探索数学的万千可能',staticTryTitle:'试试这些',
staticDiscoverGreet:'Hello there. 这里收集了来自各专业志愿者的数学科普视频。',
staticToolboxTitle:'🧰 工具箱',staticToolboxSub:'动手做数学——口算、计算、查询、绘图',
staticToolMathrace:'口算竞赛',staticToolMathraceDesc:'限时60秒,挑战你的心算速度',
staticToolCalc:'计算器',staticToolCalcDesc:'支持加减乘除、幂运算、三角函数',
staticToolSym:'数学符号查询',staticToolSymDesc:'点击符号查看含义和LaTeX写法',
staticToolGraph:'函数图形绘图',staticToolGraphDesc:'输入 f(x),实时绘制函数曲线',
staticKnowledgeTitle:'📚 知识卡片',staticKnowledgeSub:'轻量数学发现,每天一个新概念',
staticStoryTitle:'⚔️ 葫芦侠行',staticStorySub:'北宋数学武侠互动文字冒险 · 8章93节点 · 8+种结局',
staticSimTitle:'🧬 数学人生',staticSimSub:'四大主题 · 更多数学人生敬请期待',
staticFavTitle:'❤️ 我的收藏',staticFavSub:'已收藏的视频和知识卡片',
staticProfileTitle:'👤 个人中心',
staticAIToggleTitle:'AI数学助教',staticAIHeader:'💬 AI 数学助教',
staticAIWelcome:'你好!我是葫芦数学的AI助教 👋 试试问我:学数学对学医有什么用?',
staticAIPlaceholder:'输入问题...',staticMbtiTooltip:'测测你的数学人格'},
en:{home:'Home',discover:'Discover',toolbox:'Toolbox',knowledge:'Cards',story:'HuLu Quest',simulator:'Math Life',favorites:'Favorites',profile:'Profile',search:'Search...',guest:'Guest',
heroTitle:'Hulu Math · Play your way into math',heroSub:'Math isn\'t formulas. It\'s how you make sense of things.',heroCTA:'Start Exploring →',
exploreTitle:'Explore Math',tryTitle:'Try These',
mbtiName:'MathBTI Personality Test',mbtiDesc:'12 questions. One math soul. Meet your sprite. →',
toolboxName:'Math Toolbox',toolboxDesc:'Mental Math · Calculator · Symbol Lookup · Graph →',
storyName:'HuLu Quest',storyDesc:'A Song Dynasty math wuxia · 8 chapters · 8+ endings →',
simName:'Math Life',simDesc:'Four themes. More math lives coming soon. →',
videoName:'Discover Videos',videoDesc:'Five-dimension map · watch and grow',cardName:'Knowledge Cards',cardDesc:'8 math topics',
aiName:'AI Tutor',aiDesc:'Ask anything. No judgment.',
petTip:'Come back daily to earn EXP and level up your sprite.',
dailyFactPrefix:'💡 Math Trivia: ',
discoverGreet:'A hand-picked collection of math videos, curated by volunteers from across the sciences.',
mapTitle:'Five-Dimension Ability Map',mapSub:'Watch videos and light up your math sprite\'s five abilities',mapHint:'Tap a map node to enter its video stream',
dimAll:'All videos',dimVision:'Math Vision',dimHumanity:'Math Humanity',dimDetective:'Math Detective',dimModeling:'Math Modeling',dimConnection:'Math Connection',
claimVideo:'Finish & earn',claimDone:'Learned',claimNeedPet:'Take MathBTI first to adopt a math sprite, then watch videos to grow it',
filterAll:'All',noVideo:'No videos yet',noCard:'No cards yet',noResult:'No results',
loadFailVideo:'Couldn\'t load videos',loadFailCard:'Couldn\'t load cards',loadFailProfile:'Couldn\'t load',
loadMore:'Scroll for more…',loadEnd:'— That\'s all of them · ',loadEndSuf:' videos —',
close:'Close',save:'Save',saveOK:'Saved',saveFail:'Couldn\'t save',
toolboxHeader:'Toolbox',toolboxSub:'Get your hands dirty — race the clock, crunch numbers, explore symbols, draw curves',
toolMathrace:'Mental Math',toolMathraceDesc:'60 seconds. How high can you score?',
toolCalc:'Calculator',toolCalcDesc:'Quick arithmetic, powers, trig — whatever you need.',
toolSym:'Symbol Lookup',toolSymDesc:'Tap a symbol to see what it means and how to write it in LaTeX.',
toolGraph:'Function Grapher',toolGraphDesc:'Type any f(x) and watch its curve appear in real time.',
knowledgeHeader:'Knowledge Cards',knowledgeSub:'One idea at a time. Bite-sized math, built for curiosity.',
storyHeader:'HuLu Quest',storySub:'A math-fueled wuxia set in the Song Dynasty. 8 chapters. 8+ endings.',
storyLoadFail:'Couldn\'t load the story',storyNodeNotFound:'Node not found',
simHeader:'Math Life',simSub:'Four themes. More math lives coming soon.',
simLoadFail:'Couldn\'t load the simulator',simReset:'Start fresh',
favHeader:'My Favorites',favSub:'Your saved videos and cards',
favEmpty:'Nothing saved yet',favEmptyHint:'Tap 🤍 on any video or card to save it here.',
profileHeader:'Profile',profileEdit:'Edit Profile',
profileAvatar:'Avatar:',profileNick:'Nickname:',
profileWatchCount:'Videos watched',profileFavCount:'Favorites',
aiHeader:'AI Math Tutor',aiWelcome:'Hey! I\'m your Hulu Math AI tutor 👋 Try asking: "Why do we even need calculus?"',
aiPlaceholder:'Ask anything...',aiOffline:'The AI is taking a quick break. Try again in a moment.',aiNetworkErr:'Looks like you\'re offline. Check your connection and try again.',
calcErr:'That doesn\'t look right',calcPlaceholder:'e.g. 2+3*4',
graphPlaceholder:'e.g. Math.sin(x) or x*x',graphBtn:'Plot',
mrTitle:'Ready?',mrPlaceholder:'Type your answer',mrTimeout:'⏰ Time\'s up! You scored ',mrCorrect:'right',mrRight:'✅ Correct! +10',
symDetail:'👆 Tap any symbol to learn more',
mbtiFloat:'Discover your Math Personality',
aiAskBtn:'Ask AI',aiAskSuffix:'?',
petLevelUp:'Level up! Now Lv.{n}',
staticHeroTitle:'Hulu Math · Play your way into math',staticHeroSub:'Math isn\'t formulas. It\'s how you make sense of things.',staticHeroCTA:'Start Exploring →',
staticExploreTitle:'Explore Math',staticTryTitle:'Try These',
staticDiscoverGreet:'A hand-picked collection of math videos, curated by volunteers from across the sciences.',
staticToolboxTitle:'🧰 Toolbox',staticToolboxSub:'Get your hands dirty — race the clock, crunch numbers, explore symbols, draw curves',
staticToolMathrace:'Mental Math',staticToolMathraceDesc:'60 seconds. How high can you score?',
staticToolCalc:'Calculator',staticToolCalcDesc:'Quick arithmetic, powers, trig — whatever you need.',
staticToolSym:'Symbol Lookup',staticToolSymDesc:'Tap a symbol to see what it means and how to write it in LaTeX.',
staticToolGraph:'Function Grapher',staticToolGraphDesc:'Type any f(x) and watch its curve appear in real time.',
staticKnowledgeTitle:'📚 Knowledge Cards',staticKnowledgeSub:'One idea at a time. Bite-sized math, built for curiosity.',
staticStoryTitle:'⚔️ HuLu Quest',staticStorySub:'A math-fueled wuxia set in the Song Dynasty. 8 chapters. 8+ endings.',
staticSimTitle:'🧬 Math Life',staticSimSub:'Four themes. More math lives coming soon.',
staticFavTitle:'❤️ My Favorites',staticFavSub:'Your saved videos and cards',
staticProfileTitle:'👤 Profile',
staticAIToggleTitle:'AI Math Tutor',staticAIHeader:'💬 AI Math Tutor',
staticAIWelcome:'Hey! I\'m your Hulu Math AI tutor 👋 Try asking: "Why do we even need calculus?"',
staticAIPlaceholder:'Ask anything...',
staticMbtiTooltip:'Discover your Math Personality'}};
function t(k){const d=I18N[_lang];return (d&&d[k])||(I18N.zh[k]||k);}
function rI18n(){
document.querySelectorAll('[data-i18n]').forEach(el=>{
const k=el.getAttribute('data-i18n');
const v=t(k);
if(el.tagName==='INPUT'){el.placeholder=v}
else if(el.hasAttribute('title')&&el.getAttribute('data-i18n-type')==='title'){el.title=v}
else{el.innerHTML=v}
});
}
function switchLang(l){
_lang=l;localStorage.setItem('mb_lang',l);
document.querySelectorAll('.lang-btn').forEach(b=>b.classList.toggle('active',b.dataset.lang===l));
document.getElementById('globalSearch').placeholder=t('search');
rI18n();
navigateTo(CP);
api('/api/user/profile?user_id='+USER_ID).then(r=>{if(r.code===0&&r.data){document.getElementById('sidebarName').textContent=r.data.nickname||t('guest')}}).catch(()=>{});
}
// ===== Sidebar =====
function toggleSidebar(){document.getElementById('sidebar').classList.toggle('open');document.getElementById('sidebarOverlay').classList.toggle('active')}
function closeSidebar(){document.getElementById('sidebar').classList.remove('open');document.getElementById('sidebarOverlay').classList.remove('active')}
// ===== Global Search =====
let searchTimeout;
function handleGlobalSearch(){
clearTimeout(searchTimeout);
searchTimeout=setTimeout(()=>{
const q=document.getElementById('globalSearch').value.trim().toLowerCase();
const el=document.getElementById('searchResults');
if(!q||q.length<1){el.classList.remove('show');return}
const results=[];
allV.forEach(v=>{if((v.title||'').toLowerCase().includes(q)||(v.author||'').toLowerCase().includes(q)||(v.discipline||'').toLowerCase().includes(q))results.push({t:t('videoTitle')||'Video',title:v.title,id:v.id,type:'video'})});
allK.forEach(k=>{if((k.title||'').toLowerCase().includes(q)||(k.description||'').toLowerCase().includes(q))results.push({t:t('cardLabel')||'Card',title:k.title,id:k.id,type:'knowledge'})});
if(results.length===0){el.innerHTML='
'+t('noResult')+'
';el.classList.add('show');return}
el.innerHTML=results.slice(0,8).map(r=>''+(r.t==='视频'?'Video':r.t==='知识'?'Card':r.t)+' '+esc(r.title)+'
').join('');
el.classList.add('show');
},200);
}
function searchNavigate(type,id){
document.getElementById('globalSearch').value='';document.getElementById('searchResults').classList.remove('show');
if(type==='video'){navigateTo('discover');setTimeout(()=>openVideo(id,'','','','',''),500)}
else if(type==='knowledge'){navigateTo('discover');setTimeout(()=>openKnowledgeDetail(id),500)}
}
document.addEventListener('click',e=>{if(!e.target.closest('.sidebar-search'))document.getElementById('searchResults').classList.remove('show')});
// ===== AI Panel =====
function toggleAIPanel(){
var panel=document.getElementById('aiPanel');
if(!panel)return;
var modal=document.getElementById('toolModal');
if(modal)modal.classList.remove('show');
if(panel.classList.contains('open')){
panel.classList.remove('open');
}else{
panel.classList.add('open');
}
}
// ===== API =====
const _apiCache={},_CACHEABLE=['/api/videos','/api/knowledge','/api/story','/api/simulator','/api/math_personality'];
async function api(p,o={}){const u=API_BASE+p,c={headers:{'Content-Type':'application/json'},...o};if(c.body&&typeof c.body==='object')c.body=JSON.stringify(c.body);const isGet=!c.method;if(isGet&&_CACHEABLE.some(x=>p.indexOf(x)===0)&&_apiCache[u])return _apiCache[u];const r=await fetch(u,c);const j=await r.json();if(isGet&&_CACHEABLE.some(x=>p.indexOf(x)===0))_apiCache[u]=j;return j}
// ===== Route =====
function navigateTo(p){
CP=p;closeSidebar();closeToolModal();document.getElementById('searchResults').classList.remove('show');
// 离开首页时停止 Hero 动画循环,避免空转耗电
if(p!=='home'&&heroAnimId){cancelAnimationFrame(heroAnimId);heroAnimId=null}
document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('active',b.dataset.page===p));
document.querySelectorAll('.page').forEach(pg=>pg.classList.remove('active'));
const t=document.getElementById('page-'+p);if(t)t.classList.add('active');
document.getElementById('mainContent').scrollTop=0;
if(p==='home')loadHome();else if(p==='discover')loadDiscover();else if(p==='toolbox')loadToolbox();
else if(p==='knowledge')loadKnowledgePage();else if(p==='story')loadStory();else if(p==='simulator')loadSimulator();else if(p==='favorites')loadFavorites();else if(p==='profile')loadProfile();else if(p==='cards')loadCardsPage();
}
// ===== Home (Desmos-style) =====
let heroAnimId=null;
async function loadHome(){
chatCtx={};
// Hero canvas animation
setTimeout(drawHeroCanvas,100);
// Pet
renderPetCard();
// Feature cards (2x2)
document.getElementById('homeFeatureCards').innerHTML=[
{i:'🧬',n:t('mbtiName'),d:t('mbtiDesc'),click:"window.open('/mathbti','_blank')"},
{i:'🧰',n:t('toolboxName'),d:t('toolboxDesc'),click:"navigateTo('toolbox')"},
{i:'⚔️',n:t('storyName'),d:t('storyDesc'),click:"navigateTo('story')"},
{i:'🎮',n:t('simName'),d:t('simDesc'),click:"navigateTo('simulator')"}
].map(c=>'').join('');
// Extra cards (1x3)
document.getElementById('homeExtraCards').innerHTML=[
{i:'📺',n:t('videoName'),d:t('videoDesc'),click:"navigateTo('discover')"},
{i:'📚',n:t('cardName'),d:t('cardDesc'),click:"navigateTo('knowledge')"},
{i:'💬',n:t('aiName'),d:t('aiDesc'),click:"toggleAIPanel()"}
].map(c=>'').join('');
// Daily fact
const facts=['π的小数点后第762位开始连续出现六个9,被称为「费曼点」。','世界上最大的已知质数有超过4100万位数字。','哥德巴赫猜想:每个大于2的偶数都可以写成两个质数之和——至今未被证明。','四色定理:任何地图只需4种颜色就能让相邻区域颜色不同。1976年用计算机证明。','莫比乌斯带只有一面——拿一条纸带,扭转180°后粘起来试试。'];
document.getElementById('homeDailyFact').innerHTML=t('dailyFactPrefix')+facts[Math.floor(Math.random()*facts.length)];
rCG();
}
function drawHeroCanvas(){
const c=document.getElementById('heroCanvas');if(!c)return;
// 防止重复进入首页叠加多个动画循环
if(heroAnimId){cancelAnimationFrame(heroAnimId);heroAnimId=null}
const ctx=c.getContext('2d'),w=c.width,h=c.height;
let t=0;
function frame(){
ctx.clearRect(0,0,w,h);
ctx.strokeStyle='#D6D1CA';ctx.lineWidth=1;
for(let i=0;i0.7){
ctx.save();ctx.globalAlpha=Math.min(1,(Math.sin(t*0.3)-0.7)/0.3*0.5);
ctx.strokeStyle='#A8907E';ctx.lineWidth=1;
const gx=w-60,gy=h-30;
ctx.beginPath();
// 上葫芦 (小)
ctx.ellipse(gx,gy-28,12,9,0,0,Math.PI*2);
// 下葫芦 (大)
ctx.ellipse(gx,gy,16,13,0,0,Math.PI*2);
ctx.stroke();
ctx.restore();
}
t+=0.02;heroAnimId=requestAnimationFrame(frame);
}
frame();
}
// ===== Discover: Five-Dimension Ability Map =====
const DIM5=[
{id:'vision',key:'眼光',label:'数学眼光',short:'眼光',icon:'👁',color:'#5B6AF0',x:50,y:12,desc:'洞察本质的直觉,一眼看穿背后的数学结构。',rep:'庞加莱'},
{id:'humanity',key:'人文',label:'数学人文',short:'人文',icon:'📜',color:'#B06A3B',x:17,y:36,desc:'思想溯源的厚度,理解数学背后的人与史。',rep:'希帕提娅'},
{id:'detective',key:'侦探',label:'数学侦探',short:'侦探',icon:'🔍',color:'#2E7D72',x:24,y:86,desc:'逻辑溯源的严谨,在迷雾中找到唯一真相。',rep:'图灵'},
{id:'modeling',key:'建模',label:'数学建模',short:'建模',icon:'🏗️',color:'#C47F3A',x:76,y:86,desc:'解决现实的力量,把公式变成改变世界的武器。',rep:'冯·诺依曼'},
{id:'connection',key:'联结',label:'数学联结',short:'联结',icon:'🔗',color:'#7B5EA7',x:83,y:36,desc:'跨界融合的通感,连接数学内外的一切。',rep:'牛顿'}
];
const DIM_EXTRA_VIDEOS=[
{id:9101,module:'数人',sub_category:'数学人文',discipline:'数学人文',discipline_icon:'📜',title:'赵爽弦图:《周髀算经》里的人类智慧',video_url:'https://www.bilibili.com/video/BV1example301',description:'从勾股定理的证明看数学如何成为文明的火种。赵爽的弦图,是数学人文的第一张草图。',author:'数学人文馆',duration_min:6},
{id:9102,module:'数人',sub_category:'数学人文',discipline:'数学人文',discipline_icon:'📜',title:'希帕提娅:为理性光芒守护到最后一刻',video_url:'https://www.bilibili.com/video/BV1example302',description:'一位女数学家在动荡年代坚持讲授数学,理解她,就理解了数学背后的信仰。',author:'数学人文馆',duration_min:7},
{id:9103,module:'数人',sub_category:'数学人文',discipline:'数学人文',discipline_icon:'📜',title:'图灵与恩尼格玛:密码背后的人与史',video_url:'https://www.bilibili.com/video/BV1example303',description:'从密码战争到计算机科学,图灵用逻辑改变了历史走向。',author:'数学人文馆',duration_min:8},
{id:9104,module:'数说',sub_category:'几何直觉',discipline:'数学眼光',discipline_icon:'👁',title:'向日葵、贝壳与斐波那契:用数学眼光看自然',video_url:'https://www.bilibili.com/video/BV1example304',description:'当你看一朵花不再只看到美,而是看到数列与结构,数学眼光就亮了。',author:'数学眼光馆',duration_min:5},
{id:9105,module:'数说',sub_category:'几何直觉',discipline:'数学眼光',discipline_icon:'👁',title:'城市建筑里的对称与黄金比例',video_url:'https://www.bilibili.com/video/BV1example305',description:'从古典建筑到现代地标,用数学眼光捕捉城市里隐藏的几何秩序。',author:'数学眼光馆',duration_min:5},
{id:9106,module:'数说',sub_category:'几何直觉',discipline:'数学眼光',discipline_icon:'👁',title:'庞加莱猜想:直觉如何带人类走进更高维度',video_url:'https://www.bilibili.com/video/BV1example306',description:'像庞加莱一样,在踏上公交车的一瞬间,看见隐藏在空间里的结构。',author:'数学眼光馆',duration_min:6}
];
function videoDimKey(v){
const t=(v.title||'')+(v.sub_category||'')+(v.discipline||'');
if(/人文|赵爽|周髀|希帕提娅|数学史|恩尼格玛|战火|信仰|守护/.test(t))return'人文';
if(/地图|画不准|高斯绝妙|斐波那契|向日葵|黄金比例|对称|眼光|直觉|庞加莱/.test(t))return'眼光';
if(/强化学习|AlphaGo|人工智能|神经网络|深度学习|矩阵|GPU/.test(t))return'联结';
if(/密码|网络安全|统计|平均数|彩票|费米|图论|六度|二进制|卡尔曼|PLC|布尔|集成电路|雷达|多普勒|编译|形式语言|B\+树|数据库/.test(t))return'侦探';
if(/金融|风险|经济|市场|供应链|线性规划|物流|回归|建模|数学模型|反应动力学|传质|材料|气候变化|污水处理|质量平衡|种群|扩散|高斯模型|中子|AHP|Matlab|Python|数模|最优控制|机器人控制|自行车|齿轮|振动|有限元/.test(t))return'建模';
if(/傅里叶|麦克斯韦|采样|拉东|心电|DNA|信息论|水平集|渐开线|薛定谔|边界层|熵增|太阳能|空间群|光刻|半导体|计算机视觉|几何变换/.test(t))return'联结';
return'联结';
}
function dimInfo(v){return DIM5.find(d=>d.key===videoDimKey(v))||DIM5[4]}
function mergeAllV(list){
const arr=(list||[]).slice();
DIM_EXTRA_VIDEOS.forEach(e=>{if(!arr.some(v=>v.id===e.id))arr.push(e)});
const dm={};
arr.forEach(v=>{if(v.discipline&&!dm[v.discipline])dm[v.discipline]={discipline:v.discipline,discipline_icon:v.discipline_icon||'·'}});
vD=Object.values(dm);
return arr;
}
async function loadDiscover(){
chatCtx={};
if(favP){await favP;favP=null}
if(!allV.length){
renderSkeletons('discoverVideos',6);
try{
const r=await api('/api/videos');
if(r.code===0)allV=mergeAllV(r.data||[]);
}catch(e){
document.getElementById('discoverVideos').innerHTML=''+t('loadFailVideo')+'
';
allV=mergeAllV([]);
}
}else{
allV=mergeAllV(allV);
}
rDF();rDimTabs();rDMap();rVCNP('discoverVideos',filterVideos());
}
function dimVideoCount(k){return allV.filter(v=>dimInfo(v).key===k).length}
function filterVideos(){return allV.filter(v=>(!curDim||dimInfo(v).key===curDim)&&(!cD||v.discipline===cD))}
function rDMap(){
const el=document.getElementById('dimMap');if(!el)return;
const pet=getPet();
const lines=DIM5.map((d,i)=>{
const n=DIM5[(i+1)%DIM5.length];
return ' ';
}).join('');
const spokes=DIM5.map(d=>' ').join('');
el.innerHTML=
''+t('mapTitle')+'
'+t('mapSub')+'
'+DIM5.map(d=>' '+esc(d.label)+' · '+dimVideoCount(d.key)+' ').join('')+'
'+
''+spokes+lines+' '+
'
葫 数学小人
'+
DIM5.map(d=>{const v=pet&&pet.dims?Math.min(100,Math.max(0,Number(pet.dims[d.key])||0)):null;return '
'+d.icon+' '+esc(d.label)+' '+(v===null?'--':v)+' '}).join('')+
'
'+
'';
}
function rDimTabs(){
const el=document.getElementById('dimTabs');if(!el)return;
let h=''+t('filterAll')+''+allV.length+' ';
DIM5.forEach(d=>{h+=''+d.icon+' '+esc(d.label)+''+dimVideoCount(d.key)+' '});
el.innerHTML=h;
}
function fDim(d){curDim=d;rDimTabs();rDMap();rVCNP('discoverVideos',filterVideos())}
function scrollToVideos(){const el=document.getElementById('discoverVideos');if(el)el.scrollIntoView({behavior:'smooth',block:'start'})}
function rDF(){let h=''+t('filterAll')+' ';vD.forEach(d=>{h+=''+(d.discipline_icon||'')+' '+esc(d.discipline)+' '});document.getElementById('discoverFilters').innerHTML=h}
function fV(d){cD=d;rDF();rVCNP('discoverVideos',filterVideos())}
// ===== 视频卡片网格:无限滚动(每次12条) =====
const VC_PAGE=12;
let vcList=[],vcCount=0,vcObserver=null;
const UP_COLORS=['#5B6AF0','#818CF8','#F59E0B','#10B981','#EF4444','#8B5CF6','#EC4899','#14B8A6'];
function hashStr(s){let h=0;for(let i=0;i=10000?(n/10000).toFixed(1)+'万':''+n}
function fmtDur(m){m=parseInt(m)||0;return(m<10?'0'+m:''+m)+':00'}
function rVCNP(cid,vids){
const c=document.getElementById(cid);
vcList=vids||[];vcCount=0;c.innerHTML='';
if(!vcList.length){c.innerHTML=''+t('noVideo')+'
';return}
appendVC(cid);
}
function appendVC(cid){
const c=document.getElementById(cid);
const batch=vcList.slice(vcCount,vcCount+VC_PAGE);
if(!batch.length)return;
vcCount+=batch.length;
const old=document.getElementById('npLoadHint');if(old)old.remove();
c.insertAdjacentHTML('beforeend',batch.map(buildVC).join(''));
if(vcCount'+t('loadMore')+'');
if(!vcObserver){vcObserver=new IntersectionObserver(es=>{es.forEach(en=>{if(en.isIntersecting)appendVC('discoverVideos')})},{root:document.getElementById('mainContent'),rootMargin:'240px'})}
vcObserver.observe(document.getElementById('npLoadHint'));
}else if(vcList.length>VC_PAGE){
c.insertAdjacentHTML('beforeend',''+t('loadEnd')+vcList.length+t('loadEndSuf')+'
');
}
}
function buildVC(v){
const dur=v.duration_min||5,disc=v.discipline||'',author=v.author||'',dim=dimInfo(v);
const icon=v.discipline_icon&&v.discipline_icon!=='·'?v.discipline_icon:'📺';
const h=hashStr(disc||v.title);
const coverBg='linear-gradient(135deg,hsl('+(h%360)+',45%,90%),hsl('+((h+30)%360)+',45%,84%))';
const views=hashStr(v.title)%50000+300,dms=hashStr(v.title+'#dm')%900;
const upColor=UP_COLORS[hashStr(author)%UP_COLORS.length];
const initial=(author||'?').replace(/学院|·|同学|系/g,'').slice(0,1)||'?';
const faved=favC['video_'+v.id];
const click='onclick="openVideo('+v.id+',\''+esc(v.title)+'\',\''+esc(v.description||'')+'\',\''+esc(v.video_url||'')+'\',\''+esc(author)+'\',\''+esc(disc)+'\',\''+esc(dim.key)+'\')"';
const cover=v.cover_url
?''
:''+icon+'
';
return ''+
'
'+cover+
''+fmtDur(dur)+' '+
'▶ '+
''+(faved?'❤️':'🤍')+'
'+
'
'+dim.icon+' '+esc(dim.label)+'
'+esc(v.title)+'
'+
'
👁 '+fmtNum(views)+' 💬 '+fmtNum(dms)+'
'+
'
'+esc(initial)+' '+esc(author)+'
'+
'
';
}
function openVideo(id,title,desc,url,author,disc,dimKey){
chatCtx={type:'video',title:title};rH('video',id,title);
const found=allV.find(v=>v.id===id);
if(!title&&found)title=found.title;
if(!desc&&found)desc=found.description;
const dim=dimKey?DIM5.find(d=>d.key===dimKey):(found?dimInfo(found):dimInfo({title:title,discipline:disc}));
const pet=getPet();
const claims=getVideoClaims();
const claimed=!!claims['video_'+id];
const claimBox=pet
?''+(claimed
?'✅ '+t('claimDone')+' · '+esc(dim.label)+' +3 '
:'🎯 '+t('claimVideo')+' · '+esc(dim.label)+' +3 ')+'
'
:'';
let h=''+dim.icon+' '+esc(dim.label)+'
'+esc(title)+' ';
h+=url?'
':'▶
';
h+=''+esc(desc||'No description')+'
'+esc(author)+' '+esc(disc)+' '+esc(dim.label)+'
';
h+=claimBox+''+t('close')+'
';
document.getElementById('toolModalContent').innerHTML=h;
document.getElementById('toolModal').classList.add('show');
}
function getVideoClaims(){try{return JSON.parse(localStorage.getItem('mb_video_claims')||'{}')||{}}catch(e){return{}}}
function claimVideo(id,dimKey,label){
if(!getPet())return;
const claims=getVideoClaims();
if(claims['video_'+id])return;
claims['video_'+id]=true;
localStorage.setItem('mb_video_claims',JSON.stringify(claims));
addPetDim(dimKey,3);
addPetExp(10);
const btn=document.querySelector('.video-claim-btn');
if(btn){btn.disabled=true;btn.classList.add('claimed');btn.innerHTML='✅ '+t('claimDone')+' · '+esc(label)+' +3'}
showToast(esc(label)+' +3 · 数学小人成长中');
rDMap();
}
// ===== Knowledge Cards (standalone page) =====
async function loadKnowledgePage(){
chatCtx={type:'knowledge'};
if(allK.length){rKCFilters();rKCCards();rKP();return}
try{const r=await api('/api/knowledge');if(r.code===0){allK=r.data||[];kT=r.topics||[];rKCFilters();rKCCards()}}catch(e){document.getElementById('knowledgeGrid').innerHTML='卡片加载失败
'}}
function rKCFilters(){let h=''+t('filterAll')+' ';kT.forEach(t=>{h+=''+esc(t)+' '});document.getElementById('knowledgeFilters').innerHTML=h}
function fKC(t){cT=t;rKCFilters();rKCCards()}
function rKP(){
const bar=document.getElementById('knowledgeDrawProgress');
if(!bar)return;
const readThisSession=(cardState.knowledgeRead||0)%3;
const needMore=3-readThisSession;
bar.style.display='flex';
if(needMore<=0){
document.getElementById('drawProgressText').textContent='🎉 可以抽卡了!点击去抽卡';
document.getElementById('drawProgressFill').style.width='100%';
bar.classList.add('draw-progress-ready');
}else{
document.getElementById('drawProgressText').textContent='再读 '+needMore+' 张知识卡片即可抽卡';
document.getElementById('drawProgressFill').style.width=(readThisSession/3*100)+'%';
bar.classList.remove('draw-progress-ready');
}
}
function rKCCards(){const cards=cT?allK.filter(c=>c.topic===cT):allK;const g=document.getElementById('knowledgeGrid');if(!cards||cards.length===0){g.innerHTML=''+t('noCard')+'
';return}g.innerHTML=cards.map(c=>''+c.icon+' '+esc(c.question)+'
'+esc(c.title)+'
'+esc(c.description)+'
'+esc(c.read_time)+' ').join('');setTimeout(rKP,100)}
async function openKnowledgeDetail(id){chatCtx={type:'knowledge',title:''};
// 记录知识卡片阅读
cardState.knowledgeRead=(cardState.knowledgeRead||0)+1;
if(cardState.knowledgeRead>=3&&cardState.knowledgeRead%3===0){
cardState.draws+=1;
saveCardState();
}else{
saveCardState();
}
rKP();
try{const r=await api('/api/knowledge/'+id);if(r.code===0){const c=r.data;chatCtx.title=c.title;document.getElementById('toolModalContent').innerHTML=''+c.icon+'
'+esc(c.title)+' '+esc(c.question)+'
'+esc(c.detail||c.description)+'
'+esc(c.read_time)+' · '+esc(c.topic||'')+' 🤖 '+t('aiAskBtn')+'
'}}catch(e){}document.getElementById('toolModal').classList.add('show')}
function closeToolModal(){var m=document.getElementById('toolModal');if(m)m.classList.remove('show')}
// ===== Toolbox =====
function loadToolbox(){chatCtx={}}
let toolState={};
function openTool(name){
let h='';
if(name==='mathrace'){toolState={score:0,total:0,time:60,timer:null,correct:0};h=rMR()}
else if(name==='calculator')h=rCalc();
else if(name==='symbols')h=rSym();
else if(name==='grapher')h=rGraph();
document.getElementById('toolModalContent').innerHTML=h;
document.getElementById('toolModal').classList.add('show');
if(name==='mathrace')startMRLoop();
if(name==='grapher')setTimeout(plotGraph,200);
}
function rMR(){return''}
function startMRLoop(){toolState={score:0,total:0,time:60,timer:null,correct:0};document.getElementById('mrScore').textContent='0';document.getElementById('mrTime').textContent='60s';document.getElementById('mrTotal').textContent='0';document.getElementById('mrResult').textContent='';document.getElementById('mrAnswer').value='';document.getElementById('mrAnswer').disabled=false;document.getElementById('mrAnswer').focus();genMR();toolState.timer=setInterval(()=>{toolState.time--;document.getElementById('mrTime').textContent=toolState.time+'s';document.getElementById('mrTimer').style.width=(toolState.time/60*100)+'%';if(toolState.time<=0){clearInterval(toolState.timer);document.getElementById('mrAnswer').disabled=true;document.getElementById('mrResult').innerHTML='⏰ 时间到!得分:'+toolState.score+' 正确:'+toolState.correct+'/'+toolState.total;addPetExp(toolState.score)}},1000);genMR()}
function genMR(){const ops=['+','-','×'];const op=ops[Math.floor(Math.random()*3)];let a,b;if(op==='+'){a=Math.floor(Math.random()*90)+10;b=Math.floor(Math.random()*90)+10}else if(op==='-'){a=Math.floor(Math.random()*90)+10;b=Math.floor(Math.random()*a)}else{a=Math.floor(Math.random()*12)+2;b=Math.floor(Math.random()*12)+2}toolState.answer=op==='+'?a+b:op==='-'?a-b:a*b;toolState.total++;document.getElementById('mrTotal').textContent=toolState.total;document.getElementById('mrQuestion').textContent=a+' '+op+' '+b+' = ?';document.getElementById('mrAnswer').value='';document.getElementById('mrAnswer').focus()}
function checkMR(){const ans=parseInt(document.getElementById('mrAnswer').value);if(isNaN(ans))return;if(ans===toolState.answer){toolState.score+=10;toolState.correct++;document.getElementById('mrResult').innerHTML='✅ '+t('mrRight')+' '}else{document.getElementById('mrResult').innerHTML='❌ '+toolState.answer+' '}document.getElementById('mrScore').textContent=toolState.score;if(toolState.time>0)setTimeout(genMR,600)}
function rCalc(){return'=
'+['(',')','C','÷','7','8','9','×','4','5','6','-','1','2','3','+','0','.','sin(','√('].map(k=>''+k+' ').join('')+'
'}
function calcKey(k){const i=document.getElementById('calcInput');if(k==='C')i.value='';else if(k==='÷')i.value+='/';else if(k==='×')i.value+='*';else if(k==='sin(')i.value+='Math.sin(';else if(k==='√(')i.value+='Math.sqrt(';else i.value+=k;i.focus()}
function doCalc(){try{const v=eval(document.getElementById('calcInput').value);document.getElementById('calcResult').textContent=Number.isInteger(v)?v:v.toFixed(6)}catch(e){document.getElementById('calcResult').textContent='表达式有误'}}
function rSym(){
const syms=[['π','Pi · 圆周率','3.14159... · \\pi'],['∞','无穷大','没有边界 · \\infty'],['Σ','求和符号','累加 · \\sum'],['∫','积分符号','面积/累积 · \\int'],['√','平方根','x²=a → x=√a · \\sqrt{}'],['Δ','变化量/判别式','差值△y/△x · \\Delta'],['∂','偏导数','多元函数求导 · \\partial'],['lim','极限','趋近于 · \\lim_{x \\to 0}'],['e','自然常数','≈2.71828 · e'],['i','虚数单位','i² = -1 · i'],['∏','连乘符号','累乘 · \\prod'],['∈','属于','元素∈集合 · \\in']];
return''+syms.map(s=>'
'+s[0]+''+s[1]+'
').join('')+'
👆 点击符号查看详情
'
}
function sS(sym,name,latex){document.getElementById('symDetail').innerHTML=''+sym+' '+name+'
LaTeX: '+latex+'
'}
function rGraph(){return'
绘制
'}
function plotGraph(){const c=document.getElementById('graphCanvas');if(!c)return;const ctx=c.getContext('2d');ctx.clearRect(0,0,c.width,c.height);ctx.strokeStyle='#D6D1CA';ctx.lineWidth=1;for(let i=0;i<=c.width;i+=50){ctx.beginPath();ctx.moveTo(i,0);ctx.lineTo(i,c.height);ctx.stroke()}for(let i=0;i<=c.height;i+=50){ctx.beginPath();ctx.moveTo(0,i);ctx.lineTo(c.width,i);ctx.stroke()}ctx.strokeStyle='#808C95';ctx.lineWidth=2;ctx.beginPath();const fn=new Function('x','return '+document.getElementById('graphFunc').value);const sx=c.width/2,sy=c.height/2,scale=30;for(let px=0;px-100&&py故事加载失败'}}
function rSN(ni){if(!sD||!sD.nodes||!sD.nodes[ni]){document.getElementById('storyScene').innerHTML='节点未找到
';return}const n=sD.nodes[ni];sVN.currentNode=ni;localStorage.setItem('mb_story_save',JSON.stringify(sVN));const ch=dC(ni),cN=['','初入江湖','西部教习','数理论道','脑学初创','报国之路','暗流涌动','陋室修炼','真相之门'];document.getElementById('storyStats').innerHTML='📐 '+(sVN.stats.shuli||10)+' ⚔️ '+(sVN.stats.xiayi||10)+' 🧘 '+(sVN.stats.xinzhi||10)+' '+ch+' · '+(cN[ch]||'')+' 🔄 '+t('simReset')+' ';document.getElementById('storyScene').innerHTML=(n.character?''+esc(n.character)+'
':'')+'
';document.getElementById('storyChoices').innerHTML='';if(tt)clearTimeout(tt);const te=document.getElementById('storyText'),tx=n.scene||'';let i=0;te.textContent='';function ty(){if(i{let h='';if(c.effects){const p=[];if(c.effects.shuli)p.push('数理根基'+(c.effects.shuli>0?'+':'')+c.effects.shuli);if(c.effects.xiayi)p.push('侠义声望'+(c.effects.xiayi>0?'+':'')+c.effects.xiayi);if(c.effects.xinzhi)p.push('心志韧性'+(c.effects.xinzhi>0?'+':'')+c.effects.xinzhi);if(p.length)h='['+p.join(' | ')+'] '}return''+esc(c.text)+h+' '}).join('')}
function sC(next,ef){aSE(ef);rSN(next)}
function aSE(ef){if(!ef)return;if(ef.shuli)sVN.stats.shuli=(sVN.stats.shuli||10)+ef.shuli;if(ef.xiayi)sVN.stats.xiayi=(sVN.stats.xiayi||10)+ef.xiayi;if(ef.xinzhi)sVN.stats.xinzhi=(sVN.stats.xinzhi||10)+ef.xinzhi;if(ef.favorability){if(!sVN.fav)sVN.fav={};for(const k in ef.favorability)sVN.fav[k]=(sVN.fav[k]||0)+ef.favorability[k]}}
function resetStory(){if(tt)clearTimeout(tt);sVN={currentNode:null,stats:{shuli:10,xiayi:10,xinzhi:10},fav:{}};localStorage.removeItem('mb_story_save');loadStory()}
function dC(ni){if(!ni)return 1;const m=ni.match(/^ch(\d+)/);return m?parseInt(m[1]):1}
// ===== Simulator =====
async function loadSimulator(){loadMathLife()}
async function loadLegacySimulator(){chatCtx={type:'simulator'};try{const r=await api('/api/simulator');if(r.code===0){simD=r.data;try{const sr=await api('/api/simulator/save?user_id='+USER_ID);if(sr.code===0&&sr.data)simS=sr.data}catch(e){}const sn=simD.start_node||'y1_welcome';if(!simS.currentNode||!simD.nodes||!simD.nodes[simS.currentNode])simS.currentNode=sn;rSiN(simS.currentNode)}}catch(e){document.getElementById('simScene').innerHTML='模拟器加载失败
'}}
function rSiN(ni){if(!simD||!simD.nodes||!simD.nodes[ni]){document.getElementById('simScene').innerHTML='节点未找到
';return}const n=simD.nodes[ni];simS.currentNode=ni;api('/api/simulator/save',{method:'POST',body:{user_id:USER_ID,state:simS}}).catch(()=>{});document.getElementById('simStats').innerHTML=''+(simS.stats.gpa||3).toFixed(1)+'
GPA
'+(simS.stats.interest||0)+'
'+t('interestLabel')+'
'+(simS.stats.skill||0)+'
'+t('skillLabel')+'
🔄 '+t('simReset')+' ';document.getElementById('simScene').innerHTML=(n.stage?'📅 '+esc(n.stage)+'
':'')+(n.character?''+esc(n.character)+'
':'')+''+esc(n.scene||'')+'
';document.getElementById('simChoices').innerHTML=(n.choices||[]).map(c=>''+esc(c.text)+(c.hint?''+esc(c.hint)+' ':'')+' ').join('')}
function siC(next,ef){if(ef){if(ef.gpa)simS.stats.gpa=Math.max(0,Math.min(4,(simS.stats.gpa||3)+ef.gpa*.1));if(ef.interest)simS.stats.interest=(simS.stats.interest||0)+ef.interest;if(ef.skill)simS.stats.skill=(simS.stats.skill||0)+ef.skill}rSiN(next);addPetExp(5)}
function resetSim(){simS={currentNode:null,stats:{gpa:3,interest:0,skill:0}};loadLegacySimulator()}
// ===== Favorites =====
async function loadFavorites(){chatCtx={};try{await rFC()}catch(e){}try{const r=await api('/api/favorites?user_id='+USER_ID);const g=document.getElementById('favGrid');if(r.code===0){const f=r.data||[];if(f.length===0){g.innerHTML='暂无收藏在发现页点击 🤍 即可收藏
';return}g.innerHTML=f.map(fv=>'✕ '+(fv.target_type==='video'?'📺 视频':'📚 知识卡片')+'
'+esc(fv.title)+'
').join('')}}catch(e){document.getElementById('favGrid').innerHTML='加载失败
'}}
async function rFC(){try{const r=await api('/api/favorites?user_id='+USER_ID);if(r.code===0){favC={};(r.data||[]).forEach(f=>{favC[f.target_type+'_'+f.target_id]=true})}}catch(e){}}
async function tF(type,id,title,cover){if(favC[type+'_'+id]){await api('/api/favorites',{method:'DELETE',body:{user_id:USER_ID,target_type:type,target_id:id}});favC[type+'_'+id]=false;showToast('🗑 '+t('close')+' ✔')}else{await api('/api/favorites',{method:'POST',body:{user_id:USER_ID,target_type:type,target_id:id,title:title,cover:cover}});favC[type+'_'+id]=true;showToast('❤ '+t('saveOK'))}if(CP==='discover')fV(cD);else if(CP==='favorites')loadFavorites()}
async function rF(type,id){await api('/api/favorites',{method:'DELETE',body:{user_id:USER_ID,target_type:type,target_id:id}});loadFavorites()}
// ===== History =====
async function rH(type,id,title){try{await api('/api/history',{method:'POST',body:{user_id:USER_ID,target_type:type,target_id:id,title:title}})}catch(e){}}
// ===== Profile =====
async function loadProfile(){chatCtx={};try{const[pr,sr]=await Promise.all([api('/api/user/profile?user_id='+USER_ID),api('/api/user/stats?user_id='+USER_ID)]);const p=pr.code===0?pr.data:{nickname:'游客用户',avatar_emoji:'葫',avatar_color:'#5B6AF0'};const s=sr.code===0?sr.data:{watch_count:0,favorite_count:0};document.getElementById('profileContent').innerHTML=''+esc(p.avatar_emoji||'葫')+'
'+esc(p.nickname||'游客用户')+'
ID: '+USER_ID+'
'}catch(e){document.getElementById('profileContent').innerHTML='加载失败
'}}
async function sP(){const n=document.getElementById('editNickname').value.trim();const a=document.getElementById('editAvatar').value.trim();try{await api('/api/user/profile',{method:'POST',body:{user_id:USER_ID,nickname:n,avatar_emoji:a}});document.getElementById('sidebarName').textContent=n||'游客用户';document.getElementById('sidebarAvatar').textContent=a||'葫';alert('保存成功');loadProfile()}catch(e){alert('保存失败')}}
// ===== Card Collection Page =====
async function loadCardsPage(){
chatCtx={};
document.getElementById('cardsProgress').textContent=cardState.owned.length+'/16';
const drawBtn=document.getElementById('cardsDrawBtn');
const drawCount=document.getElementById('cardsDrawCount');
drawCount.textContent=cardState.draws;
drawBtn.style.display=cardState.draws>0?'inline-flex':'none';
const grid=document.getElementById('cardsCollectionGrid');
await loadCardData();
const allCodes=getAllCardCodes();
if(!allCodes.length){grid.innerHTML='卡片数据加载中…
';return}
let h='';
allCodes.forEach(code=>{
const info=getCardInfo(code);
const clan=info?getClanInfo(info.clan):null;
const clanColor=clan?clan.color:'#A8907E';
const owned=cardState.owned.indexOf(code)!==-1;
h+=''+
(owned
?'
'+portraitHTML(info,24)+'
'+(info?info.name:'???')+'
'+code+'
'+(clan?clan.emoji+' '+clan.name:'')+'
'+stats5HTML(info)
:'
'+portraitHTML(info,24)+'
???
未获得
'+(clan?clan.emoji+' '+clan.name:'未知')+'
')+
'
';
});
grid.innerHTML=h;
}
// ===== Pet Experience =====
function getPet(){
let pet=null;
try{
const a=localStorage.getItem('mb_pet');
if(a)pet=JSON.parse(a);
if(!pet||!pet.type){
const b=localStorage.getItem('mathbrain_pet');
if(b)pet=JSON.parse(b);
}
}catch(e){}
if(!pet||!pet.type)return null;
pet.name=pet.name||pet.type;
pet.level=pet.level||1;
pet.exp=pet.exp||0;
pet.dims=pet.dims||{眼光:10,人文:10,侦探:10,建模:10,联结:10};
return pet;
}
function savePet(pet){if(!pet)return;localStorage.setItem('mb_pet',JSON.stringify(pet));localStorage.setItem('mathbrain_pet',JSON.stringify(pet))}
function renderPetCard(){
const pet=getPet(),pa=document.getElementById('homePetArea');
if(!pa)return;
if(!pet){pa.innerHTML='';return}
pa.innerHTML=''+pet.icon+'
'+esc(pet.name)+'
🎓 '+esc(pet.type)+' · Lv.'+pet.level+' · '+pet.exp+'/100 exp
'+DIM5.map(d=>{const v=Math.min(100,Math.max(0,Number((pet.dims||{})[d.key])||0));return '
'+d.icon+' '+esc(d.short)+'
'+v+' '}).join('')+'
'+t('petTip')+'
';
}
function addPetDim(dimKey,amt){
const pet=getPet();if(!pet)return false;
pet.dims=pet.dims||{眼光:10,人文:10,侦探:10,建模:10,联结:10};
pet.dims[dimKey]=Math.min(100,Math.max(0,(Number(pet.dims[dimKey])||0)+amt));
savePet(pet);
renderPetCard();
return true;
}
function addPetExp(amt){
const pet=getPet();
if(!pet)return;
pet.exp=(pet.exp||0)+amt;
let lvlU=false;
while(pet.exp>=100){pet.exp-=100;pet.level=(pet.level||1)+1;lvlU=true}
savePet(pet);
// 经验条跳动动画
const bar=document.querySelector('.pet-exp-fill');
if(bar){const pct=pet.exp%100;bar.style.transition='none';bar.style.width='0%';requestAnimationFrame(()=>{requestAnimationFrame(()=>{bar.style.transition='width .4s cubic-bezier(.34,1.56,.64,1)';bar.style.width=pct+'%'})})}
if(lvlU){showToast('🎉 '+t('petLevelUp').replace('{n}',pet.level));burstConfetti(document.querySelector('.pet-card'))}
}
// ===== AI Chat =====
async function sendChat(){const i=document.getElementById('chatInput');const m=i.value.trim();if(!m)return;aCM('user',m);i.value='';const tid=aCT();try{const r=await api('/api/chat',{method:'POST',body:{message:m,context:chatCtx}});rCT(tid);aCM('assistant',r.code===0?r.reply:'抱歉,AI暂时无法回复')}catch(e){rCT(tid);aCM('assistant','网络异常,请稍后重试')}}
function qA(q){document.getElementById('chatInput').value=q;closeToolModal();toggleAIPanel();setTimeout(()=>sendChat(),300)}
function aCM(role,text){const c=document.getElementById('aiMessages');const d=document.createElement('div');d.className='chat-msg '+role;d.innerHTML=''+esc(text)+'
';c.appendChild(d);c.scrollTop=c.scrollHeight}
function aCT(){const c=document.getElementById('aiMessages');const d=document.createElement('div');d.className='chat-msg assistant';d.innerHTML='
';c.appendChild(d);c.scrollTop=c.scrollHeight;return d}
function rCT(el){if(el&&el.parentNode)el.remove()}
// ===== Utils =====
function esc(t){if(!t)return'';esc._d=esc._d||document.createElement('div');esc._d.textContent=t;return esc._d.innerHTML}
function adj(hex,amt){const n=parseInt(hex.replace('#',''),16);const r=Math.min(255,(n>>16)+amt);const g=Math.min(255,((n>>8)&0xFF)+amt);const b=Math.min(255,(n&0xFF)+amt);return '#'+((r<<16)|(g<<8)|b).toString(16).padStart(6,'0')}
// ===== Float Hub =====
function toggleHub(){document.getElementById('floatHub').classList.toggle('open')}
function closeHub(){document.getElementById('floatHub').classList.remove('open')}
document.addEventListener('click',e=>{if(!e.target.closest('.float-hub'))document.getElementById('floatHub').classList.remove('open')});
// ===== Card System =====
function initCardState(){
const raw=localStorage.getItem('hulu_card_state');
if(raw){try{cardState=JSON.parse(raw)}catch(e){}}
if(!cardState.owned)cardState.owned=[];
if(!cardState.draws&&cardState.draws!==0)cardState.draws=0;
if(!cardState.lastDaily)cardState.lastDaily='';
if(!cardState.knowledgeRead&&cardState.knowledgeRead!==0)cardState.knowledgeRead=0;
if(!cardState.drawHistory)cardState.drawHistory=[];
// Daily login
const today=new Date().toISOString().slice(0,10);
if(cardState.lastDaily!==today){
cardState.draws+=1;
cardState.lastDaily=today;
saveCardState();
}
}
function saveCardState(){localStorage.setItem('hulu_card_state',JSON.stringify(cardState));rCG();}
function ownedCount(){return cardState.owned.length}
function grantCard(code,method){
if(!code)return;
if(cardState.owned.indexOf(code)===-1){
cardState.owned.push(code);
cardState.drawHistory.push({code:code,date:new Date().toISOString().slice(0,10),method:method});
saveCardState();
return true;
}
return false;
}
async function loadCardData(){
if(cardData)return cardData;
try{
const r=await api('/api/math_personality');
if(r.code===0&&r.data&&r.data.results){cardData=r.data;cardCache=r.data;return cardData}
}catch(e){}
return null;
}
function getCardInfo(code){
if(!cardCache||!cardCache.results)return null;
return cardCache.results[code]||null;
}
function getAllCardCodes(){
if(!cardCache||!cardCache.results)return[];
return Object.keys(cardCache.results);
}
function getClanInfo(clanId){
if(!cardCache||!cardCache.clans)return null;
return cardCache.clans[clanId]||null;
}
const STAT5_LABELS=['眼光','人文','侦探','建模','联结'];
function stats5HTML(info){
const s=(info&&info.stats5)||{};
return ''+STAT5_LABELS.map(k=>{
const v=Math.min(100,Math.max(0,Number(s[k])||0));
return '
';
}).join('')+'
';
}
function portraitHTML(info,size){
if(!info)return'?';
const fs=size||24;
if(info.portrait)return ''+(info.icon||'?')+' ';
return info.icon||'?';
}
// Render card gallery on home page
function rCG(){
const wrap=document.getElementById('homeCardGallery');
const btn=document.getElementById('homeGachaBtn');
const cnt=document.getElementById('cardCountText');
const scroll=document.getElementById('cardGalleryScroll');
if(!wrap||!scroll)return;
const owned=cardState.owned;
if(cnt)cnt.textContent=owned.length+'/16';
if(owned.length===0){
// 还没测过 MathBTI
if(!getPet()){
scroll.innerHTML='🏮 去做MathBTI 领取你的第一张数学人物小卡
';
}else{
scroll.innerHTML='🏮 还没获得卡片 去知识卡片页阅读攒抽卡机会吧
';
}
wrap.style.display='block';
// 即使没有卡片,有抽卡次数也要显示按钮
if(btn){
if(cardState.draws>0){
btn.style.display='inline-flex';
document.getElementById('homeDrawCount').textContent=cardState.draws;
}else{
btn.style.display='none';
}
}
return;
}
wrap.style.display='block';
loadCardData().then(()=>{
let h='';
owned.forEach(code=>{
const info=getCardInfo(code);
const clan=info?getClanInfo(info.clan):null;
const clanColor=clan?clan.color:'#A8907E';
h+=''+
'
'+portraitHTML(info,24)+'
'+
'
'+(info?info.name:'')+'
';
});
scroll.innerHTML=h;
});
// 抽卡按钮
if(btn){
if(cardState.draws>0){
btn.style.display='inline-flex';
document.getElementById('homeDrawCount').textContent=cardState.draws;
}else{
btn.style.display='none';
}
}
}
// Gacha draw
function openGacha(){
document.getElementById('gachaOverlay').style.display='flex';
document.getElementById('gachaIdle').style.display='block';
document.getElementById('gachaResult').style.display='none';
document.getElementById('gachaDrawCount').textContent=cardState.draws;
const all=cardData?Object.keys(cardData.results).length:16;
const missing=all-cardState.owned.length;
let pityHint='';
if(cardState.drawHistory.length>0){
const sinceNew=cardState.drawHistory.slice().reverse().findIndex(d=>cardState.owned.indexOf(d.code)===-1);
if(sinceNew>=4)pityHint='⭐ 下次抽卡必出新卡!';
else if(sinceNew>=0)pityHint='再抽'+(4-sinceNew)+'次保底出新卡';
}
document.getElementById('gachaPityHint').textContent=pityHint||'剩余未收集:'+missing+'/16';
}
function closeGacha(){document.getElementById('gachaOverlay').style.display='none'}
function doDraw(){
if(_gachaBusy)return;
if(cardState.draws<=0){showToast('没有抽卡次数了!去读知识卡片获取吧');return}
_gachaBusy=true;
const gourd=document.getElementById('gachaGourd');
gourd.classList.add('gourd-shaking');
document.getElementById('gachaIdle').style.display='block';
document.getElementById('gachaResult').style.display='none';
setTimeout(()=>{
gourd.classList.remove('gourd-shaking');
cardState.draws--;
saveCardState();
// Pity: every 5th draw guaranteed new
let code;
const allCodes=getAllCardCodes();
const sinceNewIndex=cardState.drawHistory.slice().reverse().findIndex(d=>!cardState.owned.includes(d.code));
if(sinceNewIndex>=4||(cardState.drawHistory.length>0&&sinceNewIndex===-1)){
// 保底:一定出新卡
const missing2=allCodes.filter(c=>!cardState.owned.includes(c));
code=missing2[Math.floor(Math.random()*missing2.length)];
}else{
code=allCodes[Math.floor(Math.random()*allCodes.length)];
}
const isNew=cardState.owned.indexOf(code)===-1;
if(isNew){
cardState.owned.push(code);
}
cardState.drawHistory.push({code:code,date:new Date().toISOString().slice(0,10),method:'gacha'});
saveCardState();
// Show result
showDrawResult(code,isNew);
// 粒子爆发
const cr=document.querySelector('.gacha-card-reveal');
if(cr)setTimeout(()=>burstGachaConfetti(cr),300);
_gachaBusy=false;
rCG();
},900);
}
function showDrawResult(code,isNew){
loadCardData().then(()=>{
const info=getCardInfo(code);
const clan=info?getClanInfo(info.clan):null;
const clanColor=clan?clan.color:'#A8907E';
document.getElementById('gachaIdle').style.display='none';
const res=document.getElementById('gachaResult');
res.style.display='block';
res.innerHTML=
''+
(info&&info.portrait
?'
'+(info.icon||'?')+' '
:'
'+(info?info.icon:'?')+'
')+
'
'+(info?info.name:'???')+'
'+
'
'+code+'
'+
(clan?'
'+clan.emoji+' '+clan.name+' · '+(info?info.mathematician:'')+'
':'')+
'
'+(info?info.tagline:'')+'
'+
(info?stats5HTML(info):'')+
(isNew?'':'
(重复获得 · 已自动分解)
')+
'
📋 查看卡片详情 '+
'
再抽一次 (剩余'+cardState.draws+'次)
'+
'
';
document.getElementById('gachaDrawCount').textContent=cardState.draws;
});
}
// Card detail modal
function openCardDetail(code){
loadCardData().then(()=>{
const info=getCardInfo(code);
if(!info)return;
const clan=getClanInfo(info.clan);
const clanColor=clan?clan.color:'#A8907E';
const hist=cardState.drawHistory.filter(d=>d.code===code);
const obtainInfo=hist.length>0
?(hist[0].method==='mathbti'?'📋 MathBTI测试获得':'🏮 葫芦抽卡获得')+' · '+hist[0].date
:'未知途径';
const modal=document.getElementById('cardDetailModal');
modal.innerHTML=
''+
''+
'
人物简介
'+esc(info.description||'')+'
'+
'
'+
'
代表数学家 · '+esc(info.mathematician)+'
'+esc(info.bio||'')+'
'+
'
💡 '+esc(info.bio_fact||'')+'
'+
'
🎴 '+obtainInfo+'
'+
'
';
document.getElementById('cardDetailOverlay').style.display='flex';
});
}
function closeCardDetail(){document.getElementById('cardDetailOverlay').style.display='none'}
function burstGachaConfetti(el){
if(!el)return;const r=el.getBoundingClientRect();const cx=r.left+r.width/2,cy=r.top+r.height/2;
const colors=['#A8907E','#C47F3A','#D4A574','#8A9BA8','#C49595','#8AA68A'];
for(let i=0;i<18;i++){const p=document.createElement('div');p.className='gacha-particle';const a=Math.random()*Math.PI*2,d=40+Math.random()*60;p.style.cssText='left:'+cx+'px;top:'+cy+'px;--gpx:'+(Math.cos(a)*d)+'px;--gpy:'+(Math.sin(a)*d)+'px;background:'+colors[Math.floor(Math.random()*colors.length)];document.body.appendChild(p);setTimeout(()=>p.remove(),1100)}
}
// ===== Toast =====
function showToast(msg){
let c=document.getElementById('toastBox');if(!c){c=document.createElement('div');c.id='toastBox';c.className='toast-container';document.body.appendChild(c)}
const t=document.createElement('div');t.className='toast';t.innerHTML=msg;c.appendChild(t);setTimeout(()=>t.remove(),2400);
}
// ===== 粒子爆发 =====
function burstConfetti(el){
if(!el)return;const r=el.getBoundingClientRect();const cx=r.left+r.width/2,cy=r.top+r.height/2;
const colors=['#808C95','#A8907E','#8AA68A','#C4A467','#C49595','#9AA5AE'];
for(let i=0;i<14;i++){const p=document.createElement('div');p.className='particle';const a=Math.random()*Math.PI*2,d=30+Math.random()*50;p.style.cssText='left:'+cx+'px;top:'+cy+'px;width:6px;height:6px;border-radius:2px;background:'+colors[Math.floor(Math.random()*colors.length)]+';--px:'+(Math.cos(a)*d)+'px;--py:'+(Math.sin(a)*d)+'px';document.body.appendChild(p);setTimeout(()=>p.remove(),750)}
}
// ===== 骨架屏 =====
function renderSkeletons(cid,count){
const c=document.getElementById(cid);c.innerHTML='';
for(let i=0;i
')}
}
// ===== Init =====
let favP=null;
document.addEventListener('DOMContentLoaded',()=>{
// 语言初始化
document.querySelectorAll('.lang-btn').forEach(b=>b.classList.toggle('active',b.dataset.lang===_lang));
document.getElementById('globalSearch').placeholder=t('search');
rI18n();
// 首屏:首页纯本地渲染,立即呈现;数据请求并行后台预取
loadHome();
initCardState();
loadCardData();
setTimeout(rCG,300);
favP=rFC();
api('/api/videos?module=数人').then(r=>{if(r.code===0){allV=r.data||[];vD=r.disciplines||[]}}).catch(()=>{});
api('/api/knowledge').then(r=>{if(r.code===0){allK=r.data||[];kT=r.topics||[]}}).catch(()=>{});
api('/api/user/profile?user_id='+USER_ID).then(r=>{if(r.code===0&&r.data){document.getElementById('sidebarName').textContent=r.data.nickname||'游客用户';document.getElementById('sidebarAvatar').textContent=r.data.avatar_emoji||'葫';document.getElementById('sidebarAvatar').style.background=r.data.avatar_color||'#808C95'}}).catch(()=>{});
});
// ===== Math Life =====
let mtD=null,mtS={currentNode:null,stats:{shuli:10,xiayi:10,xinzhi:10},fav:{}};
const MT_STAGE_MAP={c1:'第一幕 · 大一上:小镇少年走进数学系',c2:'第二幕 · 大一下:第一次完整证明',c3:'第三幕 · 大二上:讨论班与诺特',c4:'第四幕 · 大二下:数学与生活',c5:'第五幕 · 大三上:方向与高斯',c6:'第六幕 · 大三下:科研低谷与诺特',c7:'第七幕 · 大四上:直博申请',c8:'第八幕 · 毕业前夜与直博第一年'};
let alumniList=[],alumniStory=null,alumniStoryIndex=0;
function mtStage(id){const k=(id||'').slice(0,2);return MT_STAGE_MAP[k]||'数学少年线'}
function mtStatColor(k){if(k==='shuli')return '#3F6C8F';if(k==='xinzhi')return '#2E7D72';return '#B8893A'}
function mtStatLabel(k){if(k==='shuli')return '数理根基';if(k==='xinzhi')return '心志韧性';return '同行温度'}
function mtStatIcon(k){if(k==='shuli')return '∑';if(k==='xinzhi')return 'θ';return 'π'}
function mtEffectsHint(ef){if(!ef)return '';const p=[];if(ef.shuli)p.push('数理根基 '+(ef.shuli>0?'+':'')+ef.shuli);if(ef.xiayi)p.push('同行温度 '+(ef.xiayi>0?'+':'')+ef.xiayi);if(ef.xinzhi)p.push('心志韧性 '+(ef.xinzhi>0?'+':'')+ef.xinzhi);if(ef.favorability){for(const k in ef.favorability)p.push(k+'好感 '+(ef.favorability[k]>0?'+':'')+ef.favorability[k])}return p.length?'['+p.join(' | ')+'] ':''}
function loadMathLife(){
chatCtx={};
const hub=document.getElementById('mathLifeHub'),teen=document.getElementById('mathTeenPlayer'),legacy=document.getElementById('legacySimPlayer'),alumni=document.getElementById('mathAlumniPlayer');
if(hub)hub.style.display='';
if(teen)teen.style.display='none';
if(legacy)legacy.style.display='none';
if(alumni)alumni.style.display='none';
const themes=[
{icon:'🔱',name:'信仰者',desc:'数学少年线 · 高斯 / 祖冲之 / 欧拉 / 诺特',open:'openMathTeen()',ready:true},
{icon:'📣',name:'传播者',desc:'斐波那契 / 华罗庚 / 伽罗瓦 / Ada Lovelace',open:'',ready:false},
{icon:'🛠️',name:'应用者',desc:'秦九韶 / 牛顿 / 图灵 / 冯·诺依曼',open:'',ready:false},
{icon:'👁️',name:'直觉者',desc:'希帕提娅 / 赵爽 / 庞加莱 / 凯瑟琳·约翰逊',open:'',ready:false}
];
hub.innerHTML=
'四大主题
'+
''+
themes.map(t=>'
'+
'
'+t.icon+'
'+
'
'+esc(t.name)+'
'+
'
'+esc(t.desc)+'
'+
(t.ready?'':'
敬请期待
')+
'
').join('')+
'
'+
''+
'🎓 '+
'数学校友访谈 真实校友的大学四年 · Skill 剧本包 '+
'→ '+
' '+
''+
'
更多数学人生 · 敬请期待
'+
'
更多主题正在创作中,敬请期待
'+
'
'+
[
{icon:'📣',name:'传播者',desc:'斐波那契 / 华罗庚 / 伽罗瓦 / Ada Lovelace'},
{icon:'🛠️',name:'应用者',desc:'秦九韶 / 牛顿 / 图灵 / 冯·诺依曼'},
{icon:'👁️',name:'直觉者',desc:'希帕提娅 / 赵爽 / 庞加莱 / 凯瑟琳·约翰逊'}
].map(m=>'
'+m.icon+'
'+esc(m.name)+'
'+esc(m.desc)+'
敬请期待
').join('')+
'
'+
'
';
}
async function openMathTeen(){
const hub=document.getElementById('mathLifeHub'),teen=document.getElementById('mathTeenPlayer'),legacy=document.getElementById('legacySimPlayer');
if(hub)hub.style.display='none';
if(legacy)legacy.style.display='none';
if(teen){teen.style.display='';teen.innerHTML=''}
try{const r=await api('/api/story/math_teen');if(r.code===0){mtD=r.data;const saved=localStorage.getItem('mb_math_teen_save');if(saved){try{mtS=JSON.parse(saved)}catch(e){mtS={currentNode:null,stats:{shuli:10,xiayi:10,xinzhi:10},fav:{}}}}const sn=mtD.start_node||'c1_notice';const ni=(mtS.currentNode&&mtD.nodes&&mtD.nodes[mtS.currentNode])?mtS.currentNode:sn;mtS.currentNode=ni;rMTNode(ni)}else throw new Error('load failed')}catch(e){const b=document.getElementById('mtStoryBox');if(b)b.innerHTML='数学少年线加载失败
'}}
function rMTNode(ni){
if(!mtD||!mtD.nodes||!mtD.nodes[ni]){document.getElementById('mtStoryBox').innerHTML='节点未找到
';return}
const n=mtD.nodes[ni];mtS.currentNode=ni;localStorage.setItem('mb_math_teen_save',JSON.stringify(mtS));
const st=document.getElementById('mtStage');if(st)st.textContent=mtStage(ni);
const stats=document.getElementById('mtStats');if(stats)stats.innerHTML=
['shuli','xiayi','xinzhi'].map(k=>''+mtStatIcon(k)+'
'+(mtS.stats[k]||0)+' '+mtStatLabel(k)+' ').join('')+
'4
4 位数学家 高斯 · 祖冲之 · 欧拉 · 诺特 '+
'🔄 重新开始 ';
const box=document.getElementById('mtStoryBox');if(box)box.innerHTML=(n.character?''+esc(n.character)+'
':'')+''+esc(n.scene||'')+'
';
rMTChoices(n.choices||[]);
}
function rMTChoices(choices){
const area=document.getElementById('mtChoices');if(!area)return;
if(!choices.length){area.innerHTML='故事结束 · 你的数学,刚刚开始
再走一次这段路 ';const rb=document.getElementById('mtRestartBtn');if(rb)rb.addEventListener('click',resetMathTeen);return}
area.innerHTML=choices.map((c,i)=>''+esc(c.text)+mtEffectsHint(c.effects)+' ').join('');
area.querySelectorAll('.story-choice').forEach(btn=>{
const c=choices[Number(btn.getAttribute('data-mt-index'))];
btn.addEventListener('click',()=>mTC(c.next,c.effects));
});
}
function mTC(next,ef){if(ef){if(ef.shuli)mtS.stats.shuli=(mtS.stats.shuli||10)+ef.shuli;if(ef.xiayi)mtS.stats.xiayi=(mtS.stats.xiayi||10)+ef.xiayi;if(ef.xinzhi)mtS.stats.xinzhi=(mtS.stats.xinzhi||10)+ef.xinzhi;if(ef.favorability){if(!mtS.fav)mtS.fav={};for(const k in ef.favorability)mtS.fav[k]=(mtS.fav[k]||0)+ef.favorability[k]}}rMTNode(next);addPetExp(5)}
function resetMathTeen(){localStorage.removeItem('mb_math_teen_save');mtS={currentNode:null,stats:{shuli:10,xiayi:10,xinzhi:10},fav:{}};if(mtD&&mtD.start_node)rMTNode(mtD.start_node);else openMathTeen()}
function backToMathLife(){loadMathLife()}
async function openMathAlumni(){
const hub=document.getElementById('mathLifeHub'),teen=document.getElementById('mathTeenPlayer'),legacy=document.getElementById('legacySimPlayer'),alumni=document.getElementById('mathAlumniPlayer');
if(hub)hub.style.display='none';
if(teen)teen.style.display='none';
if(legacy)legacy.style.display='none';
if(!alumni)return;
alumni.style.display='';
alumni.innerHTML='
';
loadAlumniCards();
}
async function loadAlumniCards(){
try{const r=await api('/api/alumni');alumniList=(r&&r.code===0&&Array.isArray(r.data))?r.data:[]}catch(e){alumniList=[]}
const box=document.getElementById('alumniCards');if(!box)return;
const cards=alumniList.length
?alumniList.map(alumniCardHTML).join('')
:[1,2,3,4,5,6].map(i=>'🎓
AL-'+(String(i).padStart(3,'0'))+'
校友档案 '+i+'
大一 → 大四 · 访谈整理中
Skill 剧本待产出 ').join('');
box.innerHTML=cards+'+
成为校友访谈嘉宾
报名与访谈流程即将开放
';
}
function alumniCardHTML(a){
const skill=a.skill||{},tags=(skill.tags&&skill.tags.length)?skill.tags:['数学人生'];
return ''+
''+esc(a.icon||'🎓')+'
'+
''+esc(a.id||'AL-000')+'
'+
''+esc(a.name||'未命名校友')+'
'+
''+esc((a.major||'数学')+' · '+(a.graduation_year||'已毕业'))+'
'+
''+tags.map(tg=>''+esc(tg)+' ').join('')+'
'+
''+DIM5.map(d=>{const v=(a.dims&&a.dims[d.key])||0;return ''+d.icon+' '+v+' '}).join('')+'
'+
'线性体验四年 → '+
' ';
}
function openAlumniStory(id){
alumniStory=alumniList.find(a=>a.id===id)||null;
alumniStoryIndex=0;
const list=document.getElementById('alumniListView'),story=document.getElementById('alumniStoryView');
if(list)list.style.display='none';
if(story){
story.style.display='';
story.innerHTML='
';
renderAlumniStory();
}
}
function backToAlumniList(){
const list=document.getElementById('alumniListView'),story=document.getElementById('alumniStoryView');
if(list)list.style.display='';
if(story)story.style.display='none';
}
function renderAlumniStory(){
const body=document.getElementById('alumniStoryBody');if(!body)return;
const nodes=alumniStory&&alumniStory.four_years&&alumniStory.four_years.nodes?alumniStory.four_years.nodes:[];
if(!nodes.length){
body.innerHTML='该校友的 Skill 剧本正在整理中。
'+['大一','大二','大三','大四'].map((s,i)=>'
'+(i+1)+' '+s+' 剧情节点整理中
').join('')+'
';
return;
}
const n=nodes[Math.min(alumniStoryIndex,nodes.length-1)];
const pct=Math.round((alumniStoryIndex+1)/nodes.length*100);
body.innerHTML='
'+esc(n.stage||('第 '+(alumniStoryIndex+1)+' 幕'))+'
'+esc(n.scene||'')+'
'+(n.effect?'
'+fiveDimEffectHTML(n.effect)+'
':'')+
(alumniStoryIndex
下一幕 →'
:''+esc(n.ending||'这就是这位校友的数学人生。')+'
返回校友卡片 ')+
' ';
}
function nextAlumniNode(){alumniStoryIndex++;renderAlumniStory()}
function fiveDimEffectHTML(ef){
if(!ef)return'';
const labels={眼光:'数学眼光',人文:'数学人文',侦探:'数学侦探',建模:'数学建模',联结:'数学联结'};
const arr=Object.keys(ef||{}).filter(k=>labels[k]&&ef[k]);
if(!arr.length)return'';
return '五维能力关联:'+arr.map(k=>labels[k]+' '+(ef[k]>0?'+':'')+ef[k]).join(' · ')+' ';
}
function openLegacySim(){const hub=document.getElementById('mathLifeHub'),teen=document.getElementById('mathTeenPlayer'),legacy=document.getElementById('legacySimPlayer');if(hub)hub.style.display='none';if(teen)teen.style.display='none';if(legacy)legacy.style.display='';loadLegacySimulator()}