// ===== 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助教 👋<br><br>试试问我：学数学对学医有什么用？',
  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助教 👋<br><br>试试问我：学数学对学医有什么用？',
   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 👋<br><br>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 👋<br><br>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='<div style="padding:12px;color:#94A3B8;font-size:12px">'+t('noResult')+'</div>';el.classList.add('show');return}
    el.innerHTML=results.slice(0,8).map(r=>'<div class="search-result-item" onclick="searchNavigate(\''+r.type+'\','+r.id+')"><span class="search-result-type">'+(r.t==='视频'?'Video':r.t==='知识'?'Card':r.t)+'</span>'+esc(r.title)+'</div>').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=>'<div class="ds-feature-card" onclick="'+c.click+'"><div class="ds-feature-card-icon">'+c.i+'</div><div class="ds-feature-card-name">'+c.n+'</div><div class="ds-feature-card-desc">'+c.d+'</div></div>').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=>'<div class="ds-feature-card" onclick="'+c.click+'"><div class="ds-feature-card-icon">'+c.i+'</div><div class="ds-feature-card-name">'+c.n+'</div><div class="ds-feature-card-desc">'+c.d+'</div></div>').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;i<w;i+=50){ctx.beginPath();ctx.moveTo(i,0);ctx.lineTo(i,h);ctx.stroke()}
  for(let i=0;i<h;i+=50){ctx.beginPath();ctx.moveTo(0,i);ctx.lineTo(w,i);ctx.stroke()}
  ctx.strokeStyle='#808C95';ctx.lineWidth=2.5;ctx.beginPath();
  for(let px=0;px<w;px++){const x=(px-w/2)/60;const y=Math.sin(x+t)*Math.cos(x*0.7+t*0.5)*50+h/2;if(px===0)ctx.moveTo(px,y);else ctx.lineTo(px,y)}ctx.stroke();
  ctx.strokeStyle='#9AA5AE';ctx.lineWidth=1.5;ctx.beginPath();
    for(let px=0;px<w;px++){const x=(px-w/2)/60;const y=Math.cos(x*1.3-t*0.7)*45+h/2;if(px===0)ctx.moveTo(px,y);else ctx.lineTo(px,y)}ctx.stroke();
    // 葫芦轮廓
    if(Math.sin(t*0.3)>0.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='<p style="color:#8A8580;text-align:center;padding:40px;grid-column:1/-1">'+t('loadFailVideo')+'</p>';
      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 '<line x1="'+d.x+'" y1="'+d.y+'" x2="'+n.x+'" y2="'+n.y+'"/>';
  }).join('');
  const spokes=DIM5.map(d=>'<line class="dim-map-spoke" x1="50" y1="50" x2="'+d.x+'" y2="'+d.y+'"/>').join('');
  el.innerHTML=
    '<div class="dim-map-head"><div><div class="dim-map-title">'+t('mapTitle')+'</div><div class="dim-map-sub">'+t('mapSub')+'</div></div><div class="dim-map-legend">'+DIM5.map(d=>'<span class="dim-map-legend-item" style="--dim-color:'+d.color+'"><i></i>'+esc(d.label)+' · '+dimVideoCount(d.key)+'</span>').join('')+'</div></div>'+
    '<div class="dim-map-board"><svg class="dim-map-svg" viewBox="0 0 100 100" preserveAspectRatio="none">'+spokes+lines+'</svg>'+
    '<div class="dim-map-center"><span class="dim-map-gourd">葫</span><span>数学小人</span></div>'+
    DIM5.map(d=>{const v=pet&&pet.dims?Math.min(100,Math.max(0,Number(pet.dims[d.key])||0)):null;return '<button class="dim-map-node'+(curDim===d.key?' active':'')+'" style="left:'+d.x+'%;top:'+d.y+'%;--dim-color:'+d.color+'" title="'+esc(d.desc)+' 代表人物：'+esc(d.rep)+'" onclick="fDim(\''+d.key+'\');scrollToVideos()"><span class="dim-node-icon">'+d.icon+'</span><span class="dim-node-name">'+esc(d.label)+'</span><span class="dim-node-value">'+(v===null?'--':v)+'</span></button>'}).join('')+
    '</div>'+
    '<div class="dim-map-foot">'+t('mapHint')+'</div>';
}
function rDimTabs(){
  const el=document.getElementById('dimTabs');if(!el)return;
  let h='<button class="dim-tab'+(curDim===''?' active':'')+'" onclick="fDim(\'\')">'+t('filterAll')+'<span>'+allV.length+'</span></button>';
  DIM5.forEach(d=>{h+='<button class="dim-tab'+(curDim===d.key?' active':'')+'" style="--dim-color:'+d.color+'" onclick="fDim(\''+d.key+'\')">'+d.icon+' '+esc(d.label)+'<span>'+dimVideoCount(d.key)+'</span></button>'});
  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='<button class="filter-btn'+(cD===''?' active':'')+'" onclick="fV(\'\')">'+t('filterAll')+'</button>';vD.forEach(d=>{h+='<button class="filter-btn'+(cD===d.discipline?' active':'')+'" onclick="fV(\''+esc(d.discipline)+'\')">'+(d.discipline_icon||'')+' '+esc(d.discipline)+'</button>'});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<s.length;i++){h=((h<<5)-h)+s.charCodeAt(i);h|=0}return Math.abs(h)}
function fmtNum(n){return n>=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='<p style="color:#94A3B8;text-align:center;padding:40px;grid-column:1/-1">'+t('noVideo')+'</p>';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<vcList.length){
    c.insertAdjacentHTML('beforeend','<div class="np-load-hint" id="npLoadHint">'+t('loadMore')+'</div>');
    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','<div class="np-load-hint">'+t('loadEnd')+vcList.length+t('loadEndSuf')+'</div>');
  }
}
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
    ?'<div class="np-cover-inner"><img class="np-cover-img" src="'+esc(v.cover_url)+'" alt="" loading="lazy" onerror="this.parentNode.style.background=\''+coverBg+'\'"></div>'
    :'<div class="np-cover-inner" style="background:'+coverBg+'"><span class="np-cover-icon">'+icon+'</span></div>';
  return '<div class="np-card" '+click+'>'+
    '<div class="np-cover">'+cover+
    '<span class="np-cover-duration">'+fmtDur(dur)+'</span>'+
    '<span class="np-cover-play">▶</span>'+
    '<button class="np-cover-fav'+(faved?' faved':'')+'" onclick="event.stopPropagation();tF(\'video\','+v.id+',\''+esc(v.title)+'\',\''+esc(icon)+'\')">'+(faved?'❤️':'🤍')+'</button></div>'+
    '<div class="np-body"><div class="np-dim-badge" style="--dim-color:'+dim.color+'">'+dim.icon+' '+esc(dim.label)+'</div><div class="np-card-title">'+esc(v.title)+'</div>'+
    '<div class="np-stats"><span class="np-stat">👁 '+fmtNum(views)+'</span><span class="np-stat">💬 '+fmtNum(dms)+'</span></div>'+
    '<div class="np-uploader"><span class="np-up-avatar" style="background:'+upColor+'">'+esc(initial)+'</span><span class="np-up-name">'+esc(author)+'</span></div>'+
    '</div></div>';
}
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
    ?'<div class="video-claim-box" style="--dim-color:'+dim.color+'">'+(claimed
      ?'<button class="video-claim-btn claimed" disabled>✅ '+t('claimDone')+' · '+esc(dim.label)+' +3</button>'
      :'<button class="video-claim-btn" onclick="claimVideo('+id+',\''+esc(dim.key)+'\',\''+esc(dim.label)+'\')">🎯 '+t('claimVideo')+' · '+esc(dim.label)+' +3</button>')+'</div>'
    :'<div class="video-claim-box"><div class="video-claim-hint">'+t('claimNeedPet')+'</div></div>';
  let h='<div class="video-modal-top"><span class="video-modal-dim" style="--dim-color:'+dim.color+'">'+dim.icon+' '+esc(dim.label)+'</span><h2 class="video-modal-title">'+esc(title)+'</h2></div>';
  h+=url?'<div class="video-modal-player"><iframe src="'+esc(url)+'" title="'+esc(title)+'" allowfullscreen></iframe></div>':'<div class="video-modal-player empty"><span>▶</span></div>';
  h+='<p class="video-modal-desc">'+esc(desc||'No description')+'</p><div class="video-modal-meta"><span>'+esc(author)+'</span><span>'+esc(disc)+'</span><span>'+esc(dim.label)+'</span></div>';
  h+=claimBox+'<div style="margin-top:12px"><button onclick="closeToolModal()" style="padding:8px 20px;border-radius:10px;border:1.5px solid #E2E8F0;background:#fff;cursor:pointer;font-size:13px">'+t('close')+'</button></div>';
  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='<p style="color:#94A3B8;text-align:center;padding:40px;grid-column:1/-1">卡片加载失败</p>'}}
function rKCFilters(){let h='<button class="filter-btn'+(cT===''?' active':'')+'" onclick="fKC(\'\')">'+t('filterAll')+'</button>';kT.forEach(t=>{h+='<button class="filter-btn'+(cT===t?' active':'')+'" onclick="fKC(\''+esc(t)+'\')">'+esc(t)+'</button>'});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='<p style="color:#94A3B8;text-align:center;padding:40px;grid-column:1/-1">'+t('noCard')+'</p>';return}g.innerHTML=cards.map(c=>'<div class="knowledge-card" onclick="openKnowledgeDetail('+c.id+')"><div class="knowledge-card-visual" style="background:linear-gradient(135deg,'+c.color+','+adj(c.color,30)+')"><span style="font-size:42px;z-index:1">'+c.icon+'</span><div class="knowledge-card-question">'+esc(c.question)+'</div></div><div class="knowledge-card-body"><div class="knowledge-card-title">'+esc(c.title)+'</div><div class="knowledge-card-desc">'+esc(c.description)+'</div><span class="knowledge-card-time">'+esc(c.read_time)+'</span></div></div>').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='<div style="width:100%;height:80px;border-radius:12px;background:linear-gradient(135deg,'+c.color+','+adj(c.color,30)+');display:flex;align-items:center;justify-content:center;font-size:40px;margin-bottom:16px">'+c.icon+'</div><h2 style="font-size:20px;font-weight:700;margin-bottom:4px">'+esc(c.title)+'</h2><p style="font-size:13px;color:#5B6AF0;font-weight:600;margin-bottom:12px">'+esc(c.question)+'</p><p style="font-size:14px;color:#475569;line-height:1.8">'+esc(c.detail||c.description)+'</p><div style="margin-top:16px;display:flex;justify-content:space-between;align-items:center"><span style="font-size:12px;color:#94A3B8">'+esc(c.read_time)+' · '+esc(c.topic||'')+'</span><button onclick="qA(\''+esc(c.title)+t('aiAskSuffix')+'\')" style="padding:6px 14px;border-radius:20px;border:none;background:#5B6AF0;color:#fff;font-size:12px;cursor:pointer">🤖 '+t('aiAskBtn')+'</button></div>'}}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'<div class="math-race"><div class="timer-bar"><div class="timer-fill" id="mrTimer" style="width:100%"></div></div><div class="stats"><span>✅ <b id="mrScore">0</b></span><span>⏱ <b id="mrTime">60s</b></span><span>📝 <b id="mrTotal">0</b></span></div><div class="question" id="mrQuestion">准备好了吗？</div><div class="answer-area"><input type="number" id="mrAnswer" onkeydown="if(event.key===\'Enter\')checkMR()" placeholder="输入答案" autofocus></div><div class="result" id="mrResult"></div></div>'}
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='⏰ 时间到！得分：<b>'+toolState.score+'</b> 正确：'+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='<span style="color:#8AA68A">✅ '+t('mrRight')+'</span>'}else{document.getElementById('mrResult').innerHTML='<span style="color:#C49595">❌ '+toolState.answer+'</span>'}document.getElementById('mrScore').textContent=toolState.score;if(toolState.time>0)setTimeout(genMR,600)}

function rCalc(){return'<div style="padding:20px"><div style="display:flex;gap:8px;margin-bottom:16px"><input id="calcInput" style="flex:1;padding:12px;border:1.5px solid #E2E8F0;border-radius:12px;font-size:18px;text-align:right;outline:none" placeholder="输入表达式 如 2+3*4" onkeydown="if(event.key===\'Enter\')doCalc()"><button onclick="doCalc()" style="padding:12px 24px;border-radius:12px;border:none;background:#5B6AF0;color:#fff;font-size:16px;cursor:pointer">=</button></div><div style="text-align:right;font-size:24px;font-weight:800;min-height:36px" id="calcResult"></div><div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:16px">'+['(',')','C','÷','7','8','9','×','4','5','6','-','1','2','3','+','0','.','sin(','√('].map(k=>'<button onclick="calcKey(\''+k+'\')" style="padding:12px;border:1.5px solid #E2E8F0;border-radius:10px;background:#FFF;cursor:pointer;font-size:14px;font-weight:600;transition:all .15s">'+k+'</button>').join('')+'</div></div>'}
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'<div class="symbol-grid">'+syms.map(s=>'<button class="symbol-btn" onclick="sS(\''+s[0]+'\',\''+s[1]+'\',\''+s[2]+'\')">'+s[0]+'<div style="font-size:10px;color:#94A3B8">'+s[1]+'</div></button>').join('')+'</div><div class="symbol-detail" id="symDetail">👆 点击符号查看详情</div>'
}
function sS(sym,name,latex){document.getElementById('symDetail').innerHTML='<b style="font-size:28px">'+sym+'</b><div>'+name+'</div><div style="color:#5B6AF0;font-family:monospace">LaTeX: '+latex+'</div>'}

function rGraph(){return'<div class="graph-container"><canvas id="graphCanvas" width="500" height="300"></canvas></div><div class="graph-inputs"><input id="graphFunc" value="Math.sin(x)" placeholder="如 Math.sin(x) 或 x*x"><button onclick="plotGraph()">绘制</button></div>'}
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<c.width;px++){const x=(px-sx)/scale;try{const y=fn(x);const py=sy-y*scale;if(py>-100&&py<c.height+100){if(px===0)ctx.moveTo(px,py);else ctx.lineTo(px,py)}}catch(e){}}ctx.stroke()}

// ===== Story =====
async function loadStory(){chatCtx={};if(tt)clearTimeout(tt);try{const r=await api('/api/story');if(r.code===0){sD=r.data;const saved=localStorage.getItem('mb_story_save');if(saved){try{sVN=JSON.parse(saved)}catch(e){}}const sn=sD.start_node||'ch1_start';const ni=(sVN.currentNode&&sD.nodes&&sD.nodes[sVN.currentNode])?sVN.currentNode:sn;sVN.currentNode=ni;rSN(ni)}}catch(e){document.getElementById('storyScene').innerHTML='<p style="color:#EF4444">故事加载失败</p>'}}
function rSN(ni){if(!sD||!sD.nodes||!sD.nodes[ni]){document.getElementById('storyScene').innerHTML='<p style="color:#EF4444">节点未找到</p>';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='<span class="story-stat" style="background:#808C95">📐 '+(sVN.stats.shuli||10)+'</span><span class="story-stat" style="background:#C4A467">⚔️ '+(sVN.stats.xiayi||10)+'</span><span class="story-stat" style="background:#8AA68A">🧘 '+(sVN.stats.xinzhi||10)+'</span><span class="story-stat" style="background:#657078">'+ch+' · '+(cN[ch]||'')+'</span><button onclick="resetStory()" style="margin-left:auto;padding:6px 14px;border-radius:8px;border:1px solid #D6D1CA;background:#FDFCFA;cursor:pointer;font-size:12px;color:#8A8580">🔄 '+t('simReset')+'</button>';document.getElementById('storyScene').innerHTML=(n.character?'<div class="story-character">'+esc(n.character)+'</div>':'')+'<div class="story-text" id="storyText"></div>';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<tx.length){te.textContent+=tx.charAt(i);i++;tt=setTimeout(ty,18)}else{rSCs(n.choices||[])}}ty()}
function rSCs(choices){document.getElementById('storyChoices').innerHTML=choices.map((c,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='<span style="font-size:11px;color:#8B5CF6;font-weight:500">['+p.join(' | ')+']</span>'}return'<button class="story-choice" onclick="sC(\''+c.next+'\','+JSON.stringify(c.effects||{})+')">'+esc(c.text)+h+'</button>'}).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='<p style="color:#EF4444">模拟器加载失败</p>'}}
function rSiN(ni){if(!simD||!simD.nodes||!simD.nodes[ni]){document.getElementById('simScene').innerHTML='<p style="color:#EF4444">节点未找到</p>';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='<div class="sim-stat"><div class="sim-stat-val" style="color:#808C95">'+(simS.stats.gpa||3).toFixed(1)+'</div><div class="sim-stat-label">GPA</div></div><div class="sim-stat"><div class="sim-stat-val" style="color:#C4A467">'+(simS.stats.interest||0)+'</div><div class="sim-stat-label">'+t('interestLabel')+'</div></div><div class="sim-stat"><div class="sim-stat-val" style="color:#8AA68A">'+(simS.stats.skill||0)+'</div><div class="sim-stat-label">'+t('skillLabel')+'</div></div><button onclick="resetSim()" style="margin-left:auto;padding:6px 14px;border-radius:8px;border:1px solid #D6D1CA;background:#FDFCFA;cursor:pointer;font-size:12px;color:#8A8580">🔄 '+t('simReset')+'</button>';document.getElementById('simScene').innerHTML=(n.stage?'<div style="font-size:12px;color:#5B6AF0;font-weight:600;margin-bottom:8px">📅 '+esc(n.stage)+'</div>':'')+(n.character?'<div class="story-character">'+esc(n.character)+'</div>':'')+'<div class="story-text">'+esc(n.scene||'')+'</div>';document.getElementById('simChoices').innerHTML=(n.choices||[]).map(c=>'<button class="story-choice" onclick="siC(\''+c.next+'\','+JSON.stringify(c.effects||{})+')">'+esc(c.text)+(c.hint?'<span style="font-size:11px;color:#94A3B8;font-weight:400;display:block;margin-top:4px">'+esc(c.hint)+'</span>':'')+'</button>').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='<p style="color:#94A3B8;text-align:center;padding:60px;grid-column:1/-1">暂无收藏<br><span style="font-size:12px;margin-top:8px;display:block">在发现页点击 🤍 即可收藏</span></p>';return}g.innerHTML=f.map(fv=>'<div class="fav-card"><button class="fav-card-remove" onclick="rF(\''+fv.target_type+'\','+fv.target_id+')">✕</button><div style="font-size:11px;color:#94A3B8;margin-bottom:4px">'+(fv.target_type==='video'?'📺 视频':'📚 知识卡片')+'</div><div style="font-size:14px;font-weight:600">'+esc(fv.title)+'</div></div>').join('')}}catch(e){document.getElementById('favGrid').innerHTML='<p style="color:#94A3B8;text-align:center;padding:40px">加载失败</p>'}}
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='<div class="profile-card"><div class="profile-avatar-lg" style="background:rgba(255,255,255,.2)">'+esc(p.avatar_emoji||'葫')+'</div><div><div class="profile-name-lg">'+esc(p.nickname||'游客用户')+'</div><div class="profile-id-lg">ID: '+USER_ID+'</div></div></div><div class="profile-stats-grid"><div class="profile-stat-card"><div class="num">'+s.watch_count+'</div><div class="label">观看次数</div></div><div class="profile-stat-card"><div class="num">'+s.favorite_count+'</div><div class="label">收藏数量</div></div></div><div style="background:#fff;border-radius:16px;padding:16px 20px;box-shadow:0 1px 3px rgba(0,0,0,.04)"><div style="font-weight:600;margin-bottom:12px">编辑资料</div><div style="display:flex;gap:12px;align-items:center;margin-bottom:12px"><span style="font-size:13px;color:#64748B">头像:</span><input id="editAvatar" style="padding:8px 12px;border:1.5px solid #E2E8F0;border-radius:10px;font-size:14px;width:80px" value="'+esc(p.avatar_emoji||'葫')+'"></div><div style="display:flex;gap:12px;align-items:center;margin-bottom:16px"><span style="font-size:13px;color:#64748B">昵称:</span><input id="editNickname" style="flex:1;padding:8px 12px;border:1.5px solid #E2E8F0;border-radius:10px;font-size:14px" value="'+esc(p.nickname||'')+'"></div><button onclick="sP()" style="padding:10px 28px;border-radius:12px;border:none;background:#5B6AF0;color:#fff;font-size:14px;font-weight:600;cursor:pointer">保存</button></div>'}catch(e){document.getElementById('profileContent').innerHTML='<p style="color:#94A3B8">加载失败</p>'}}
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='<p style="color:#8A8580;text-align:center;padding:40px;grid-column:1/-1">卡片数据加载中…</p>';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+='<div class="cc-card'+(owned?' owned':' locked')+'" style="--clan-color:'+clanColor+'"'+(owned?' onclick="openCardDetail(\''+code+'\')"':'')+'>'+
      (owned
        ?'<div class="cc-card-icon" style="background:'+(info?info.icon_bg:'#F0F0F0')+'">'+portraitHTML(info,24)+'</div><div class="cc-card-name">'+(info?info.name:'???')+'</div><div class="cc-card-type">'+code+'</div><div class="cc-card-clan" style="background:'+clanColor+'15;color:'+clanColor+'">'+(clan?clan.emoji+' '+clan.name:'')+'</div>'+stats5HTML(info)
        :'<div class="cc-card-icon locked-icon">'+portraitHTML(info,24)+'</div><div class="cc-card-name">???</div><div class="cc-card-type">未获得</div><div class="cc-card-clan" style="color:#ABA6A1">'+(clan?clan.emoji+' '+clan.name:'未知')+'</div>')+
      '</div>';
  });
  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='<div class="pet-card"><div class="pet-avatar">'+pet.icon+'</div><div class="pet-info"><div class="pet-name">'+esc(pet.name)+'</div><div class="pet-type">🎓 '+esc(pet.type)+' · Lv.'+pet.level+' · '+pet.exp+'/100 exp</div><div class="pet-exp-bar"><div class="pet-exp-fill" style="width:'+(pet.exp%100)+'%"></div></div><div class="pet-dims">'+DIM5.map(d=>{const v=Math.min(100,Math.max(0,Number((pet.dims||{})[d.key])||0));return '<div class="pet-dim" title="'+esc(d.label)+' '+v+'"><span style="color:'+d.color+'">'+d.icon+'</span><span class="pet-dim-name">'+esc(d.short)+'</span><div class="pet-dim-track"><i style="width:'+v+'%;background:'+d.color+'"></i></div><b>'+v+'</b></div>'}).join('')+'</div><div class="pet-level">'+t('petTip')+'</div></div></div>';
}
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='<div class="chat-bubble">'+esc(text)+'</div>';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='<div class="chat-bubble"><span style="display:flex;gap:4px"><span style="width:6px;height:6px;border-radius:50%;background:#CBD5E1;animation:bounce 1s infinite"></span><span style="width:6px;height:6px;border-radius:50%;background:#CBD5E1;animation:bounce 1s infinite .2s"></span><span style="width:6px;height:6px;border-radius:50%;background:#CBD5E1;animation:bounce 1s infinite .4s"></span></span></div>';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 '<div class="stats5">'+STAT5_LABELS.map(k=>{
    const v=Math.min(100,Math.max(0,Number(s[k])||0));
    return '<div class="stats5-row"><span>'+k+'</span><div class="stats5-track"><i style="width:'+v+'%"></i></div><b>'+(s[k]||0)+'</b></div>';
  }).join('')+'</div>';
}
function portraitHTML(info,size){
  if(!info)return'?';
  const fs=size||24;
  if(info.portrait)return '<img src="'+esc(info.portrait)+'" alt="" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><span style="display:none;font-size:'+fs+'px">'+(info.icon||'?')+'</span>';
  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='<div class="card-gallery-empty"><span class="cg-empty-icon">🏮</span>去做MathBTI<br>领取你的第一张数学人物小卡</div>';
    }else{
      scroll.innerHTML='<div class="card-gallery-empty"><span class="cg-empty-icon">🏮</span>还没获得卡片<br>去知识卡片页阅读攒抽卡机会吧</div>';
    }
    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+='<div class="card-gallery-item" onclick="openCardDetail(\''+code+'\')" style="--clan-color:'+clanColor+'">'+
        '<div class="cg-icon" style="background:'+(info?info.icon_bg:'#F0F0F0')+'">'+portraitHTML(info,24)+'</div>'+
        '<div class="cg-name" title="'+(info?info.name:'')+'">'+(info?info.name:'')+'</div></div>';
    });
    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=
      '<div class="gacha-card-reveal card-flip-in" style="--clan-color:'+clanColor+'">'+
      (info&&info.portrait
        ?'<div class="gacha-card-portrait"><img src="'+esc(info.portrait)+'" alt="" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'" style="width:100%;height:100%;object-fit:cover"><span style="display:none;font-size:48px">'+(info.icon||'?')+'</span></div>'
        :'<div class="gacha-card-portrait"><span style="font-size:48px">'+(info?info.icon:'?')+'</span></div>')+
      '<div class="gacha-card-name">'+(info?info.name:'???')+'</div>'+
      '<div class="gacha-card-type">'+code+'</div>'+
      (clan?'<div class="gacha-card-clan" style="background:'+clanColor+'15;color:'+clanColor+';border:1px solid '+clanColor+'30">'+clan.emoji+' '+clan.name+' · '+(info?info.mathematician:'')+'</div>':'')+
      '<div class="gacha-card-tagline">'+(info?info.tagline:'')+'</div>'+
      (info?stats5HTML(info):'')+
      (isNew?'':'<div class="gacha-card-dupe">(重复获得 · 已自动分解)</div>')+
      '<button class="gacha-card-detail-btn" onclick="openCardDetail(\''+code+'\')">📋 查看卡片详情</button>'+
      '<div style="margin-top:12px"><button class="draw-btn" onclick="openGacha();doDraw()" style="font-size:12px;padding:8px 20px"'+(cardState.draws<=0?' disabled':'')+'>再抽一次 (剩余'+cardState.draws+'次)</button></div>'+
      '</div>';
    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=
      '<div class="card-detail-header">'+
      '<div class="card-detail-bg" style="background:'+clanColor+'"></div>'+
      '<button class="card-detail-close" onclick="closeCardDetail()">✕</button>'+
      (info.portrait
        ?'<div class="card-detail-portrait"><img src="'+esc(info.portrait)+'" alt="" onerror="this.style.display=\'none\'"><span style="font-size:40px;display:none">'+(info.icon||'?')+'</span></div>'
        :'<div class="card-detail-portrait"><span style="font-size:40px">'+(info.icon||'?')+'</span></div>')+
      '<div class="card-detail-name">'+esc(info.name)+'</div>'+
      '<div class="card-detail-binary">'+code+'</div>'+
      (clan?'<div class="card-detail-clan" style="background:'+clanColor+'15;color:'+clanColor+';border:1px solid '+clanColor+'30">'+clan.emoji+' '+clan.name+' · '+esc(info.mathematician)+'</div>':'')+
      '<div class="card-detail-tagline">'+esc(info.tagline||'')+'</div>'+
      '</div>'+
      '<div class="card-detail-body">'+
      '<div class="card-detail-section"><div class="card-detail-section-title">人物简介</div><p>'+esc(info.description||'')+'</p></div>'+
      '<div class="card-detail-section"><div class="card-detail-section-title">五维能力</div>'+stats5HTML(info)+'</div>'+
      '<div class="card-detail-section"><div class="card-detail-section-title">代表数学家 · '+esc(info.mathematician)+'</div><p>'+esc(info.bio||'')+'</p></div>'+
      '<div class="card-detail-fact"><span class="fact-emoji">💡</span><span class="fact-text">'+esc(info.bio_fact||'')+'</span></div>'+
      '<div class="card-detail-obtain"><span class="obtain-icon">🎴</span><span>'+obtainInfo+'</span></div>'+
      '</div>';
    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<count;i++){c.insertAdjacentHTML('beforeend','<div class="skeleton-card"><div class="skeleton skeleton-line w80"></div><div class="skeleton skeleton-line w60"></div><div class="skeleton skeleton-line w40"></div><div style="display:flex;align-items:center;gap:8px;margin-top:auto"><div class="skeleton skeleton-avatar"></div><div class="skeleton skeleton-line" style="width:50%;height:10px"></div></div></div>')}
}

// ===== 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?'<span style="font-size:11px;color:#8B5CF6;font-weight:500;display:block;margin-top:4px">['+p.join(' | ')+']</span>':''}
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=
    '<div class="ds-section-title">四大主题</div>'+
    '<div class="ds-card-grid ml-theme-grid">'+
      themes.map(t=>'<div class="ds-feature-card ml-theme-card'+(t.ready?'':' ml-theme-coming')+'"'+(t.ready?' onclick="'+t.open+'"':'')+'>'+
        '<div class="ds-feature-card-icon">'+t.icon+'</div>'+
        '<div class="ds-feature-card-name">'+esc(t.name)+'</div>'+
        '<div class="ds-feature-card-desc">'+esc(t.desc)+'</div>'+
        (t.ready?'':'<div class="ml-coming-badge">敬请期待</div>')+
      '</div>').join('')+
    '</div>'+
    '<button class="ml-alumni-entry" onclick="openMathAlumni()">'+
      '<span class="ml-alumni-entry-icon">🎓</span>'+
      '<span class="ml-alumni-entry-body"><b>数学校友访谈</b><small>真实校友的大学四年 · Skill 剧本包</small></span>'+
      '<span class="ml-alumni-entry-arrow">→</span>'+
    '</button>'+
    '<div class="ml-more">'+
      '<div class="ml-more-title">更多数学人生 · 敬请期待</div>'+
      '<div class="ml-more-sub">更多主题正在创作中，敬请期待</div>'+
      '<div class="ml-more-grid">'+
        [
          {icon:'📣',name:'传播者',desc:'斐波那契 / 华罗庚 / 伽罗瓦 / Ada Lovelace'},
          {icon:'🛠️',name:'应用者',desc:'秦九韶 / 牛顿 / 图灵 / 冯·诺依曼'},
          {icon:'👁️',name:'直觉者',desc:'希帕提娅 / 赵爽 / 庞加莱 / 凯瑟琳·约翰逊'}
        ].map(m=>'<div class="ml-more-card"><div class="ml-more-icon">'+m.icon+'</div><div class="ml-more-name">'+esc(m.name)+'</div><div class="ml-more-desc">'+esc(m.desc)+'</div><div class="ml-more-tag">敬请期待</div></div>').join('')+
      '</div>'+
    '</div>';
}
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='<div class="ml-player-header"><button class="ml-back-btn" onclick="backToMathLife()">← 返回数学人生</button><div class="ml-player-title">🧑‍🎓 数学少年线</div><div class="ml-player-sub" id="mtStage">正在加载剧本…</div></div><div class="sim-container"><div class="ml-stats" id="mtStats"></div><div id="mtStoryBox"><div class="spinner"></div></div><div id="mtChoices" class="story-choices"></div></div>'}
  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='<p style="color:#EF4444">数学少年线加载失败</p>'}}
function rMTNode(ni){
  if(!mtD||!mtD.nodes||!mtD.nodes[ni]){document.getElementById('mtStoryBox').innerHTML='<p style="color:#EF4444">节点未找到</p>';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=>'<div class="ml-stat"><div class="ml-stat-icon" style="background:'+mtStatColor(k)+'">'+mtStatIcon(k)+'</div><b>'+(mtS.stats[k]||0)+'</b><span>'+mtStatLabel(k)+'</span></div>').join('')+
    '<div class="ml-stat"><div class="ml-stat-icon" style="background:#A64A44">4</div><b>4 位数学家</b><span>高斯 · 祖冲之 · 欧拉 · 诺特</span></div>'+
    '<button onclick="resetMathTeen()" style="margin-left:auto;padding:6px 14px;border-radius:8px;border:1px solid #D6D1CA;background:#FDFCFA;cursor:pointer;font-size:12px;color:#8A8580">🔄 重新开始</button>';
  const box=document.getElementById('mtStoryBox');if(box)box.innerHTML=(n.character?'<div class="story-character">'+esc(n.character)+'</div>':'')+'<div class="story-text">'+esc(n.scene||'')+'</div>';
  rMTChoices(n.choices||[]);
}
function rMTChoices(choices){
  const area=document.getElementById('mtChoices');if(!area)return;
  if(!choices.length){area.innerHTML='<div class="end-note">故事结束 · 你的数学，刚刚开始</div><button class="story-choice" id="mtRestartBtn">再走一次这段路</button>';const rb=document.getElementById('mtRestartBtn');if(rb)rb.addEventListener('click',resetMathTeen);return}
  area.innerHTML=choices.map((c,i)=>'<button class="story-choice" data-mt-index="'+i+'">'+esc(c.text)+mtEffectsHint(c.effects)+'</button>').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='<div class="ml-player-header"><button class="ml-back-btn" onclick="backToMathLife()">← 返回数学人生</button><div class="ml-player-title">数学校友访谈</div><div class="ml-player-sub">真实校友的数学人生 · Skill 剧本包</div></div><div id="alumniListView"><div class="ml-alumni-list-head"><div class="ml-alumni-title">校友卡片</div><div class="ml-alumni-sub">点进一位校友，线性体验他/她的大学四年</div></div><div id="alumniCards"><div class="spinner"></div></div></div><div id="alumniStoryView" style="display:none"></div>';
  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=>'<button class="ml-alumni-card placeholder" onclick="openAlumniStory(\'PLACEHOLDER-'+i+'\')"><div class="ml-alumni-avatar">🎓</div><div class="ml-alumni-code">AL-'+(String(i).padStart(3,'0'))+'</div><div class="ml-alumni-name">校友档案 '+i+'</div><div class="ml-alumni-meta">大一 → 大四 · 访谈整理中</div><span class="ml-alumni-skill-tag">Skill 剧本待产出</span></button>').join('');
  box.innerHTML=cards+'<button class="ml-alumni-slot join" onclick="showToast(\'校友访谈计划筹备中，欢迎提供校友线索\')"><div class="ml-alumni-avatar">＋</div><div class="ml-alumni-name">成为校友访谈嘉宾</div><div class="ml-alumni-meta">报名与访谈流程即将开放</div></button>';
}
function alumniCardHTML(a){
  const skill=a.skill||{},tags=(skill.tags&&skill.tags.length)?skill.tags:['数学人生'];
  return '<button class="ml-alumni-card" onclick="openAlumniStory(\''+esc(a.id)+'\')">'+
    '<div class="ml-alumni-avatar">'+esc(a.icon||'🎓')+'</div>'+
    '<div class="ml-alumni-code">'+esc(a.id||'AL-000')+'</div>'+
    '<div class="ml-alumni-name">'+esc(a.name||'未命名校友')+'</div>'+
    '<div class="ml-alumni-meta">'+esc((a.major||'数学')+' · '+(a.graduation_year||'已毕业'))+'</div>'+
    '<div class="ml-alumni-skill-tags">'+tags.map(tg=>'<span class="ml-alumni-skill-tag">'+esc(tg)+'</span>').join('')+'</div>'+
    '<div class="ml-alumni-dim-row">'+DIM5.map(d=>{const v=(a.dims&&a.dims[d.key])||0;return '<span title="'+esc(d.label)+'">'+d.icon+' '+v+'</span>'}).join('')+'</div>'+
    '<span class="ml-alumni-card-cta">线性体验四年 →</span>'+
  '</button>';
}
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='<div class="ml-player-header"><button class="ml-back-btn" onclick="backToAlumniList()">← 返回校友卡片</button><div class="ml-player-title">'+esc(alumniStory?alumniStory.name:'校友剧本')+'</div><div class="ml-player-sub">'+esc(alumniStory?(alumniStory.career||'大学四年的数学人生'):'剧本整理中')+'</div></div><div id="alumniStoryBody"></div>';
    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='<div class="ml-story-linear"><div class="ml-alumni-empty">该校友的 Skill 剧本正在整理中。</div><div class="ml-story-timeline">'+['大一','大二','大三','大四'].map((s,i)=>'<div class="ml-story-stage"><span>'+(i+1)+'</span><b>'+s+'</b><small>剧情节点整理中</small></div>').join('')+'</div></div>';
    return;
  }
  const n=nodes[Math.min(alumniStoryIndex,nodes.length-1)];
  const pct=Math.round((alumniStoryIndex+1)/nodes.length*100);
  body.innerHTML='<div class="ml-story-linear"><div class="ml-story-progress"><i style="width:'+pct+'%"></i></div><div class="ml-story-stage-name">'+esc(n.stage||('第 '+(alumniStoryIndex+1)+' 幕'))+'</div><div class="ml-story-scene">'+esc(n.scene||'')+'</div>'+(n.effect?'<div class="ml-story-effect">'+fiveDimEffectHTML(n.effect)+'</div>':'')+
    (alumniStoryIndex<nodes.length-1
      ?'<button class="ml-story-next" onclick="nextAlumniNode()">下一幕 →</button>'
      :'<div class="ml-story-ending">'+esc(n.ending||'这就是这位校友的数学人生。')+'</div><button class="ml-story-next" onclick="backToAlumniList()">返回校友卡片</button>')+
  '</div>';
}
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 '<span>五维能力关联：'+arr.map(k=>labels[k]+' '+(ef[k]>0?'+':'')+ef[k]).join(' · ')+'</span>';
}
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()}
