'use strict'; let preview=null, previewPose=Array(7).fill(90), previewAt=0, previous=performance.now(); const armDrawing=document.querySelector('.arm'); const svgNS='http://www.w3.org/2000/svg'; const shell=armDrawing.querySelector('g[stroke]'); const parts=Array.from(shell.children); function group(id){const g=document.createElementNS(svgNS,'g');g.id=id;return g;} const baseVisual=group('armBase'),lowerVisual=group('armLower'),upperVisual=group('armUpper'),neckVisual=group('armNeck'),handVisual=group('armHand'); parts.forEach((part,i)=>{(i<2?baseVisual:i<8?lowerVisual:i<13?upperVisual:i<17?neckVisual:handVisual).append(part);}); neckVisual.append(handVisual);upperVisual.append(neckVisual);lowerVisual.append(upperVisual);baseVisual.append(lowerVisual);shell.append(baseVisual); const bolts=armDrawing.querySelector(':scope > g:not([stroke]):not([fill])'); if(bolts){Array.from(bolts.children).forEach(bolt=>{const y=Number(bolt.getAttribute('y'));(y>420?baseVisual:y>300?lowerVisual:y>170?upperVisual:neckVisual).append(bolt);});bolts.remove();} const guideLines=armDrawing.querySelector('g[fill="none"]');if(guideLines)guideLines.remove(); function drawPose(p){ baseVisual.removeAttribute('transform'); baseVisual.style.transformOrigin='122px 450px'; baseVisual.style.transform=`perspective(650px) rotateY(${p[6]-90}deg)`; baseVisual.style.transformBox='view-box'; lowerVisual.setAttribute('transform',`rotate(${p[4]-90},122,339)`); upperVisual.setAttribute('transform',`rotate(${p[3]-90},122,211)`); neckVisual.setAttribute('transform',`rotate(${p[5]-90},122,125)`); handVisual.setAttribute('transform',`rotate(${p[1]-90},122,90)`); const fingers=handVisual.querySelectorAll('path')[2]; handVisual.style.transformOrigin='122px 78px'; handVisual.style.transformBox='view-box'; handVisual.style.transform=`perspective(450px) rotateX(${p[2]-90}deg)`; if(fingers){fingers.setAttribute('transform',`translate(0,${(p[0]-90)*-.06}) scale(1,${1+(p[0]-90)*.003})`);} } function stopPreview(){preview=null;document.getElementById('animationStatus').textContent='Prévia parada · posição nominal';} document.getElementById('stopPreview').onclick=()=>{stopPreview();BracoUI.stop().catch(e=>document.getElementById('animationStatus').textContent=e.message);}; const init=setInterval(()=>{if(!BracoUI.ready())return;clearInterval(init); Movimentos.sequencias(BracoUI.bounds()).forEach((move,i)=>{const card=document.createElement('article');card.style.cssText='border:1px solid var(--borda);border-radius:12px;padding:16px;flex:1;min-width:170px';const title=document.createElement('h3');title.textContent=move.nome;title.style.cssText='font-size:14px;margin:0 0 12px';card.append(title); for(const [text,physical] of [['Prévia',false],['Executar',true]]){const button=document.createElement('button');button.textContent=text;if(physical){button.dataset.execute='1';button.disabled=!BracoUI.canExecute();button.title='Marque Ativar movimento para executar';}button.style.cssText='font-size:12px;padding:10px 14px;margin:3px';button.onclick=async()=>{try{const steps=Movimentos.sequencias(BracoUI.bounds())[i].passos;if(physical){stopPreview();BracoUI.execute(steps);}else{await BracoUI.stop();preview={steps,index:0,hold:0};previewPose=BracoUI.pose();previewAt=performance.now();document.getElementById('animationStatus').textContent='Prévia: '+move.nome+' · sem movimento físico';}}catch(e){document.getElementById('animationStatus').textContent=e.message;}};card.append(button);} document.getElementById('movimentos').append(card); });},100); function animate(now){const dt=Math.min(.1,(now-previous)/1000);previous=now; if(preview){const goal=preview.steps[preview.index],step=Number(document.getElementById('speed').value)*dt;const distance=Math.max(...previewPose.map((v,i)=>Math.abs(goal[i]-v))),fraction=distance?Math.min(1,step/distance):1;previewPose=previewPose.map((v,i)=>v+(goal[i]-v)*fraction);if(previewPose.every((v,i)=>Math.abs(v-goal[i])<.01)){if(!preview.hold)preview.hold=now+700;if(now>=preview.hold){preview.index++;preview.hold=0;if(preview.index===preview.steps.length){preview=null;document.getElementById('animationStatus').textContent='Prévia concluída · sem movimento físico';}}}drawPose(previewPose);}else if(BracoUI.ready())drawPose(BracoUI.pose());requestAnimationFrame(animate);} document.getElementById('stop').addEventListener('click',stopPreview); document.addEventListener('visibilitychange',()=>{if(document.hidden)stopPreview();}); requestAnimationFrame(animate);