Files
carinha-control-panel/reacoes-render.js
T
julioandClaude Opus 5.5 69018c8b36 feat(reacoes): emojis e figurinhas no mesmo padrao do rosto novo
- rosto-motor.js desenha emoji e figurinha dentro dos olhos no estilo e
  formato escolhidos (brilho, piscada; na Lente no lugar da iris; na
  Matriz os LEDs usam as cores do desenho)
- MotorRosto.retrato desenha um retrato parado em qualquer canvas
- miniaturas da biblioteca no painel usam o mesmo motor, no rosto, formato
  e cor atuais do robo (3:2, com o simbolo "ao redor" no canto)
- emoji e figurinha "ao redor" ganham o brilho na cor da reacao
- cartoes da biblioteca com as acoes numa linha so

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 02:19:19 -03:00

55 lines
5.6 KiB
JavaScript

(function(){
const icons={nenhum:'',estrela:'✦',coracao:'♥',musica:'♫',cafe:'☕',ideia:'💡'};
const badge=document.createElement('div');badge.id='reacaoSimbolo';badge.style.cssText='position:fixed;left:5%;top:6%;z-index:15;color:#5cd6ff;font:clamp(30px,7vw,64px) sans-serif;pointer-events:none;white-space:pre-wrap';document.body.append(badge);
let timer,last=null,fetching=false,animation;let particles=[];
function clearVisual(){window.carinhaOlhosIcone=null;window.carinhaOlhosDesenho=null;if(animation)animation.cancel();for(const a of particles)a.cancel();particles=[];badge.replaceChildren();}
window.carinhaConstruirSVG=function(drawing){
if(!drawing||!Array.isArray(drawing.formas)||drawing.formas.length>16)return null;
const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');svg.setAttribute('viewBox','0 0 100 100');svg.setAttribute('class','figurinhaDinamica');
for(const f of drawing.formas){
if(typeof f.caminho!=='string'||f.caminho.length>600||!/^([MLCQZmlcqz0-9.,\s+-])+$/.test(f.caminho)||!/^#[0-9a-f]{6}$/i.test(f.cor))return null;
const path=document.createElementNS(svg.namespaceURI,'path');path.setAttribute('d',f.caminho);path.setAttribute('fill',f.cor);svg.append(path);
}
return svg;
};
function visual(recipe){
if(recipe.emoji){
const e=recipe.emoji;if(e.local==='olhos'){window.carinhaOlhosIcone=e.caractere;return;}
badge.style.cssText='position:fixed;right:5%;top:8%;z-index:15;font:clamp(42px,10vw,90px) sans-serif;pointer-events:none;filter:drop-shadow(0 0 14px '+(recipe.passos[0].cor||'#5cd6ff')+')';badge.textContent=e.caractere;
animation=badge.animate([{transform:'scale(.92)'},{transform:'scale(1.06)'},{transform:'scale(.92)'}],{duration:1400,iterations:Infinity});return;
}
if(recipe.figurinha){
const svg=window.carinhaConstruirSVG(recipe.figurinha);if(!svg)return;
if(recipe.figurinha.local==='olhos'){window.carinhaOlhosDesenho=recipe.figurinha;return;}
badge.style.cssText='position:fixed;right:5%;top:8%;width:24%;height:30%;z-index:15;pointer-events:none;filter:drop-shadow(0 0 14px '+(recipe.passos[0].cor||'#5cd6ff')+')';svg.style.cssText='width:100%;height:100%';badge.append(svg);
animation=svg.animate([{transform:'scale(.92) rotate(-3deg)'},{transform:'scale(1.05) rotate(3deg)'},{transform:'scale(.92) rotate(-3deg)'}],{duration:1500,iterations:Infinity});return;
}
if(['ideia','cafe'].includes(recipe.simbolo)){window.carinhaOlhosIcone=icons[recipe.simbolo];return;}
const scattered=['musica','estrela','coracao'].includes(recipe.simbolo);
badge.style.cssText=scattered?'position:fixed;inset:0;z-index:15;pointer-events:none;overflow:hidden':'position:fixed;left:5%;top:6%;z-index:15;font:clamp(30px,7vw,64px) sans-serif;pointer-events:none';
badge.style.color=recipe.passos[0].cor;
if(!scattered){badge.textContent=icons[recipe.simbolo];animation=badge.animate([{transform:'translateY(0)'},{transform:'translateY(-10px)'},{transform:'translateY(0)'}],{duration:1000,iterations:Infinity});return;}
for(let i=0;i<16;i++){
const p=document.createElement('span');p.className='particula';p.textContent=recipe.simbolo==='musica'?(i%2?'♫':'♪'):icons[recipe.simbolo];
p.style.cssText='position:absolute;top:'+(8+Math.random()*75)+'%;left:'+(5+Math.random()*85)+'%;font-size:'+(20+Math.floor(Math.random()*22))+'px;opacity:0;text-shadow:0 0 12px currentColor';badge.append(p);
const duration=1200+Math.random()*500;
particles.push(p.animate([{opacity:0},{opacity:.85,offset:.45},{opacity:0}],{duration,delay:Math.random()*1000,iterations:2,fill:'both'}));
}
}
function render(recipe){
if(recipe?.limpar){clearTimeout(timer);clearVisual();r.fila=[];r.kf=null;congelado=false;Object.assign(r,{forma:'aberto',forma2:null,boca:'neutra',esc:1,dx:0,dy:0,ab:1,ab2:1});desenhar();return;}
if(!recipe||!Array.isArray(recipe.passos)||recipe.passos.length<1||recipe.passos.length>6||!(recipe.simbolo in icons))return;
for(const s of recipe.passos)if(!['aberto','feliz','surpreso','coracao','triste'].includes(s.olhos)||!['neutra','sorriso','aberta','triste'].includes(s.boca)||!/^#[0-9a-f]{6}$/i.test(s.cor)||!Number.isFinite(s.duracao)||s.duracao<.15||s.duracao>2||!Number.isFinite(s.dx)||Math.abs(s.dx)>25||!Number.isFinite(s.dy)||Math.abs(s.dy)>12||!Number.isFinite(s.escala)||s.escala<.8||s.escala>1.3)return;
clearTimeout(timer);clearVisual();window.eilik.soltar();trata('acordar');MIC.ultimoSom=performance.now()/1000;
visual(recipe);
r.fila=recipe.passos.map(s=>[{forma:s.olhos,forma2:null,boca:s.boca,cor:[1,3,5].map(i=>parseInt(s.cor.slice(i,i+2),16)),dx:s.dx,dy:s.dy,esc:s.escala,ab:1,ab2:1},s.duracao]);
r.fila.push([{forma:'aberto',forma2:null,boca:'neutra',cor:CIANO.slice(),dx:0,dy:0,esc:1,ab:1,ab2:1},.4]);proximo();
timer=setTimeout(clearVisual,Math.max(recipe.figurinha?6000:0,['musica','estrela','coracao'].includes(recipe.simbolo)?4500:['ideia','cafe'].includes(recipe.simbolo)?2500:0,recipe.passos.reduce((n,s)=>n+s.duracao,0)*1000+400));
}
window.eilik.receita=render;
addEventListener('message',e=>{if(e.origin===location.origin&&e.source===parent&&e.data&&e.data.tipo==='previa-reacao')render(e.data.receita);});
if(new URLSearchParams(location.search).get('espelho')==='1'||location.protocol==='file:')return;
async function poll(){if(fetching||window.carinhaAtiva===false||window.carinhaSSE)return;fetching=true;try{const response=await fetch('/treinamento/expressao',{cache:'no-store',signal:AbortSignal.timeout(2000)});if(response.ok){const d=await response.json();if(d&&d.id!==last&&Date.now()/1000-d.horario<60){last=d.id;render(d.receita);}}}catch(e){}finally{fetching=false;}}
setInterval(poll,1000);
})();