Repository navigation
Expand file tree
/
Copy pathapp.js
More file actions
15 lines (15 loc) · 8.93 KB
/
Copy pathapp.js
File metadata and controls
15 lines (15 loc) · 8.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const DATA=window.GROKBOT_ORIGINAL;const bot=document.querySelector('#bot');const viewer=document.querySelector('#viewer');const status=document.querySelector('#status');const hint=document.querySelector('#hint');const auto=document.querySelector('#auto');const eyeEls=[document.querySelector('#eye-0'),document.querySelector('#eye-1')];
const groupNames={'Cycle de vie':'生命周期','Réactions':'情绪反应','Morphes agent':'代理形态','Cycle produit':'产品流程'};const stateNames={sleeping:'睡眠',waking:'唤醒',idle:'待机',listening:'倾听',thinking:'思考',searching:'搜索',working:'工作',excited:'兴奋',surprised:'惊讶',suspicious:'怀疑',angry:'生气',drowsy:'困倦',happy:'开心',curious:'好奇',confused:'困惑',bored:'无聊',proud:'得意',shy:'害羞',sad:'难过',laughing:'大笑',scared:'害怕',playful:'调皮',celebrate:'庆祝',orbit:'轨道',radar:'雷达',progress:'进度',spawning:'诞生',humming:'哼唱',loading:'加载',dictating:'听写',writing:'书写',sending:'发送',receiving:'接收',uploading:'上传',notifying:'通知',alerting:'警报',dragging:'拖拽',bouncing:'弹跳','powering-down':'关机'};
let expression=0,current=DATA.EXPRESSIONS[0].map(r=>r.map(p=>[...p])),target=DATA.EXPRESSIONS[0],morph=1,velocity=0,last=performance.now(),blinkStart=0,gazeX=0,gazeY=0,turn=0,activeState='idle',exprPage=0,groupIndex=0,statePage=0,stateTimer=0,autoTimer=0;const exprPerPage=5,statePerPage=6;
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));const centroid=ring=>ring.reduce((a,p)=>[a[0]+p[0]/ring.length,a[1]+p[1]/ring.length],[0,0]);const path=ring=>'M'+ring.map(p=>`${p[0].toFixed(2)} ${p[1].toFixed(2)}`).join('L')+'Z';
function rings(){return current.map((ring,e)=>ring.map((p,i)=>[p[0]+(target[e][i][0]-p[0])*clamp(morph,0,1),p[1]+(target[e][i][1]-p[1])*clamp(morph,0,1)]))}function chooseExpression(index){current=rings();target=DATA.EXPRESSIONS[index];expression=index;morph=0;velocity=0;exprPage=Math.floor(index/exprPerPage);renderExpressions();status.textContent=`表情 ${String(index).padStart(2,'0')} · ${activeState}`}
function blinkScale(now){if(!blinkStart)return 1;const t=(now-blinkStart)/320;if(t>=1){blinkStart=0;return 1}return Math.max(t<.42?1-t/.42:(t-.42)/.58,.04)}function blink(){blinkStart=performance.now()}
function frame(now){const dt=Math.min((now-last)/1000,.1);last=now;velocity+=(-14*velocity-49*(morph-1))*dt;morph+=velocity*dt;if(!Number.isFinite(morph)){morph=1;velocity=0}const shown=rings(),bs=blinkScale(now),rad=turn*Math.PI/180;shown.forEach((ring,i)=>{const c=centroid(ring),base=Math.asin(clamp((c[0]-114.2705)/105,-1,1)),longitude=base+rad,depth=Math.cos(longitude),perspective=Math.max(depth,.02)/Math.max(Math.cos(base),.02),x=114.2705+105*Math.sin(longitude)+gazeX,y=c[1]+gazeY;eyeEls[i].setAttribute('d',path(ring));eyeEls[i].setAttribute('transform',`translate(${x} ${y}) scale(${clamp(perspective,.02,2.4)} ${bs}) translate(${-c[0]} ${-c[1]})`);eyeEls[i].style.opacity=depth>.02?'1':'0'});requestAnimationFrame(frame)}
function renderExpressions(){const root=document.querySelector('#expressions');root.innerHTML='';const start=exprPage*exprPerPage;DATA.EXPRESSIONS.slice(start,start+exprPerPage).forEach((_,offset)=>{const index=start+offset,button=document.createElement('button');button.type='button';button.dataset.index=index;button.setAttribute('aria-pressed',String(index===expression));button.innerHTML=`<svg viewBox="0 0 229 229" aria-hidden="true"><path d="${path(DATA.EXPRESSIONS[index][0])}"/><path d="${path(DATA.EXPRESSIONS[index][1])}"/></svg><span>表情 ${String(index).padStart(2,'0')}</span>`;root.appendChild(button)});document.querySelector('#expression-count').textContent=`${DATA.EXPRESSIONS.length} 种 · ${exprPage+1}/${Math.ceil(DATA.EXPRESSIONS.length/exprPerPage)} 页`;const dots=document.querySelector('#expr-dots');dots.innerHTML='';for(let i=0;i<Math.ceil(DATA.EXPRESSIONS.length/exprPerPage);i++){const dot=document.createElement('i');if(i===exprPage)dot.className='active';dots.appendChild(dot)}document.querySelector('#expr-prev').disabled=exprPage===0;document.querySelector('#expr-next').disabled=exprPage===Math.ceil(DATA.EXPRESSIONS.length/exprPerPage)-1}
function renderCategories(){const root=document.querySelector('#category-tabs');root.innerHTML='';Object.keys(DATA.GROUPS).forEach((name,index)=>{const b=document.createElement('button');b.type='button';b.textContent=groupNames[name];b.setAttribute('aria-pressed',String(index===groupIndex));b.addEventListener('click',()=>{groupIndex=index;statePage=0;renderCategories();renderStates()});root.appendChild(b)})}
function renderStates(){const keys=Object.keys(DATA.GROUPS),states=DATA.GROUPS[keys[groupIndex]],root=document.querySelector('#actions');root.innerHTML='';const start=statePage*statePerPage;states.slice(start,start+statePerPage).forEach(name=>{const b=document.createElement('button');b.type='button';b.dataset.state=name;b.setAttribute('aria-pressed',String(name===activeState));b.innerHTML=`<b>${String(DATA.POOLS[name][0]).padStart(2,'0')}</b><span><strong>${stateNames[name]}</strong><small>${name}</small></span>`;root.appendChild(b)});const pages=Math.ceil(states.length/statePerPage);document.querySelector('#state-page').textContent=`${statePage+1} / ${pages}`;document.querySelector('#state-count').textContent=`共 ${Object.values(DATA.GROUPS).flat().length} 种`;document.querySelector('#state-prev').disabled=statePage===0;document.querySelector('#state-next').disabled=statePage===pages-1}
function stateMotion(name){bot.className='bot';viewer.classList.remove('guides');const bounce=['excited','happy','laughing','playful','celebrate','bouncing'],tilt=['listening','thinking','curious','confused','shy','dragging'],scan=['searching','working','radar','dictating','writing','uploading'],turning=['orbit','spawning','sending','receiving'],pulse=['sleeping','drowsy','bored','humming','loading','progress','powering-down'];let cls='glitch';if(bounce.includes(name))cls='bounce';else if(tilt.includes(name))cls='tilt';else if(scan.includes(name))cls='scan';else if(turning.includes(name)){cls='turn';viewer.classList.add('guides')}else if(pulse.includes(name))cls='pulse';bot.classList.add(`act-${cls}`);setTimeout(()=>{bot.classList.remove(`act-${cls}`);viewer.classList.remove('guides')},1100)}
function applyState(name){clearTimeout(stateTimer);activeState=name;const pool=DATA.POOLS[name],next=pool.find(i=>i!==expression)??pool[0];chooseExpression(next);stateMotion(name);status.textContent=`表情 ${String(next).padStart(2,'0')} · ${stateNames[name]}`;hint.textContent=`${stateNames[name]}状态:使用原版表情池 ${pool.map(i=>String(i).padStart(2,'0')).join(' / ')}`;renderStates();const cadence=DATA.EXPR_CADENCE[name];if(auto.getAttribute('aria-pressed')==='true'&&cadence)stateTimer=setTimeout(()=>{const choice=pool[Math.floor(Math.random()*pool.length)];chooseExpression(choice)},cadence[0])}
document.querySelector('#expressions').addEventListener('click',e=>{const b=e.target.closest('[data-index]');if(b)chooseExpression(Number(b.dataset.index))});document.querySelector('#actions').addEventListener('click',e=>{const b=e.target.closest('[data-state]');if(b)applyState(b.dataset.state)});document.querySelector('#expr-prev').addEventListener('click',()=>{exprPage=Math.max(0,exprPage-1);renderExpressions()});document.querySelector('#expr-next').addEventListener('click',()=>{exprPage=Math.min(4,exprPage+1);renderExpressions()});document.querySelector('#state-prev').addEventListener('click',()=>{statePage=Math.max(0,statePage-1);renderStates()});document.querySelector('#state-next').addEventListener('click',()=>{const pages=Math.ceil(DATA.GROUPS[Object.keys(DATA.GROUPS)[groupIndex]].length/statePerPage);statePage=Math.min(pages-1,statePage+1);renderStates()});
viewer.addEventListener('pointermove',e=>{const box=viewer.getBoundingClientRect();gazeX=clamp((e.clientX-box.left)/box.width*2-1,-.6,.6)*22;gazeY=clamp((e.clientY-box.top)/box.height*2-1,-.6,.6)*14});viewer.addEventListener('pointerleave',()=>{gazeX=0;gazeY=0});viewer.addEventListener('pointerdown',()=>blink());auto.addEventListener('click',()=>{const active=auto.getAttribute('aria-pressed')!=='true';auto.setAttribute('aria-pressed',String(active));clearInterval(autoTimer);if(active){const all=Object.values(DATA.GROUPS).flat();let i=all.indexOf(activeState);autoTimer=setInterval(()=>{i=(i+1)%all.length;applyState(all[i])},2200);hint.textContent='正在依次巡演全部 39 种状态'}else{clearTimeout(stateTimer);hint.textContent='自动巡演已暂停'}});setInterval(()=>{if(auto.getAttribute('aria-pressed')!=='true'&&DATA.BLINK[activeState])blink()},4600);renderExpressions();renderCategories();renderStates();requestAnimationFrame(frame);
window.getGrokbotSnapshot=()=>({expression,stateKey:activeState,stateName:stateNames[activeState],rings:DATA.EXPRESSIONS[expression]});window.GrokbotCore={randomize(){const states=Object.values(DATA.GROUPS).flat(),state=states[Math.floor(Math.random()*states.length)];applyState(state);chooseExpression(Math.floor(Math.random()*DATA.EXPRESSIONS.length))}};