Archived
643 lines
59 KiB
JavaScript
643 lines
59 KiB
JavaScript
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='',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';
|
||
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:'模拟大学数学系4年,选择方向走向不同结局 →',
|
||
videoName:'发现视频',videoDesc:'12个学科分区',cardName:'知识卡片',cardDesc:'8个数学话题',
|
||
aiName:'AI 助教',aiDesc:'随时解答数学疑问',
|
||
petTip:'每天使用工具箱和模拟器获取经验升级',
|
||
dailyFactPrefix:'💡 数学冷知识:',
|
||
discoverGreet:'Hello there. 这里收集了来自各专业志愿者的数学科普视频。',
|
||
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:'模拟大学数学系4年 · 选择方向 · 走向不同结局',
|
||
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:'模拟大学数学系4年 · 选择方向 · 走向不同结局',
|
||
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:'Simulator',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:'Live four years as a math major. Choose your path. →',
|
||
videoName:'Discover Videos',videoDesc:'12 subject areas',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.',
|
||
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 Major Simulator',simSub:'Four years. Real choices. Multiple endings. What kind of mathematician will you become?',
|
||
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 Major Simulator',staticSimSub:'Four years. Real choices. Multiple endings. What kind of mathematician will you become?',
|
||
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(()=>{});
|
||
}
|
||
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')}
|
||
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')});
|
||
function toggleAIPanel(){var p=document.getElementById('aiPanel');if(!p)return;var m=document.getElementById('toolModal');if(m)m.classList.remove('show');if(p.classList.contains('open')){p.classList.remove('open')}else{p.classList.add('open')}}
|
||
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}
|
||
function navigateTo(p){
|
||
CP=p;closeSidebar();closeToolModal();document.getElementById('searchResults').classList.remove('show');
|
||
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();
|
||
}
|
||
let heroAnimId=null;
|
||
async function loadHome(){
|
||
chatCtx={};
|
||
setTimeout(drawHeroCanvas,100);
|
||
const pet=JSON.parse(localStorage.getItem('mathbrain_pet')||'null');
|
||
const pa=document.getElementById('homePetArea');
|
||
if(pet){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-level">'+t('petTip')+'</div></div></div>';}
|
||
else{pa.innerHTML='';}
|
||
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('');
|
||
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('');
|
||
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();
|
||
}
|
||
async function loadDiscover(){
|
||
chatCtx={};
|
||
if(favP){await favP;favP=null}
|
||
if(allV.length){rDF();rVCNP('discoverVideos',allV);return}
|
||
renderSkeletons('discoverVideos',6);
|
||
try{const r=await api('/api/videos?module=数人');if(r.code===0){allV=r.data||[];vD=r.disciplines||[];rDF();rVCNP('discoverVideos',allV)}}catch(e){document.getElementById('discoverVideos').innerHTML='<p style="color:#8A8580;text-align:center;padding:40px;grid-column:1/-1">'+t('loadFailVideo')+'</p>'}
|
||
}
|
||
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||'';
|
||
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)+'\')"';
|
||
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-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>';
|
||
}
|
||
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)}
|
||
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',d?allV.filter(v=>v.discipline===d):allV)}
|
||
function openVideo(id,title,desc,url,author,disc){chatCtx={type:'video',title:title};rH('video',id,title);let h='<h2 style="font-size:18px;font-weight:700;margin-bottom:12px">'+esc(title)+'</h2>';h+=url?'<div style="aspect-ratio:16/9;background:#111;border-radius:12px;margin-bottom:12px;display:flex;align-items:center;justify-content:center"><iframe src="'+esc(url)+'" style="width:100%;height:100%;border-radius:12px" frameborder="0" allowfullscreen></iframe></div>':'<div style="aspect-ratio:16/9;background:#F1F5F9;border-radius:12px;margin-bottom:12px;display:flex;align-items:center;justify-content:center;font-size:48px;color:#CBD5E1">▶</div>';h+='<p style="font-size:14px;color:#64748B;line-height:1.7;margin-bottom:8px">'+esc(desc||'No description')+'</p><div style="display:flex;gap:8px;font-size:12px;color:#94A3B8"><span>'+esc(author)+'</span><span>'+esc(disc)+'</span></div><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')}
|
||
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')}
|
||
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()}
|
||
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}
|
||
async function loadSimulator(){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}};loadSimulator()}
|
||
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()}
|
||
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){}}
|
||
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('保存失败')}}
|
||
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')+'">'+(info?info.icon:'?')+'</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>'
|
||
:'<div class="cc-card-icon locked-icon">?</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;
|
||
}
|
||
function addPetExp(amt){
|
||
let pet=JSON.parse(localStorage.getItem('mathbrain_pet')||'null');
|
||
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}
|
||
localStorage.setItem('mathbrain_pet',JSON.stringify(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'))}
|
||
}
|
||
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()}
|
||
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')}
|
||
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')});
|
||
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=[];
|
||
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;
|
||
}
|
||
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){
|
||
if(!localStorage.getItem('mathbrain_pet')){
|
||
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')+'">'+(info?info.icon:'?')+'</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';
|
||
}
|
||
}
|
||
}
|
||
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();
|
||
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();
|
||
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>'+
|
||
(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;
|
||
});
|
||
}
|
||
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">代表数学家 · '+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)}
|
||
}
|
||
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>')}
|
||
}
|
||
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(()=>{});
|
||
});
|