feat(rosto): iris, boca e sobrancelhas na Matriz; emocoes da tela Rosto vao ao celular

- opcao "iris" (app config, Ajustes e tela Rosto): anel mais escuro com
  pupila que acompanha o olhar, dilata na alegria e contrai no susto; na
  Matriz vira LEDs apagados
- Matriz desenha boca e sobrancelhas em LEDs (antes a tela de pontos
  cobria o SVG); Bloco e Lente passam a obedecer o ajuste de boca
- tela Rosto: boca, sobrancelhas e iris ali mesmo; as teclas de emocao
  agora vao ao celular por padrao ("testar so na previa" e opcional)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 03:15:39 -03:00
co-authored by Claude Opus 5.5
parent a67c5ea9af
commit 1029b5fa99
7 changed files with 126 additions and 19 deletions
+22 -1
View File
@@ -132,7 +132,21 @@
c.beginPath(); c.ellipse(p.cx, y + h * (1.62 - e.bot), w * 0.75, h * 0.62, 0, 0, Math.PI * 2); c.fill();
c.globalCompositeOperation = 'source-over';
if (temConteudo && fecha < 0.6) conteudo(c, o, p.cx, p.cy, Math.min(w, h) * 0.74);
else if (est.brilho && fecha < 0.55 && e.bot < 0.3) { // reflexo que acompanha o olhar
else if (o.iris && fecha < 0.6 && e.bot < 0.35) {
const R = Math.min(w, h) * 0.3;
const ix = Math.max(x + R * 1.1, Math.min(x + w - R * 1.1, p.cx + (p.lx + sacada.x) * w * 0.016));
const iy = Math.max(y + R * 1.1, Math.min(y + h - R * 1.1, p.cy + h * 0.04 + (p.ly + sacada.y) * h * 0.016));
c.fillStyle = css(o.cor.map((v) => v * 0.42));
c.beginPath(); c.arc(ix, iy, R, 0, Math.PI * 2); c.fill();
c.globalCompositeOperation = 'destination-out';
const pupila = p.ab > 1.05 ? 0.38 : e.bot > 0.2 ? 0.62 : 0.5; // susto contrai, alegria dilata
c.beginPath(); c.arc(ix, iy, R * pupila, 0, Math.PI * 2); c.fill();
c.globalCompositeOperation = 'source-over';
if (est.brilho) {
c.fillStyle = 'rgba(245,250,255,0.9)';
c.beginPath(); c.arc(ix - R * 0.42, iy - R * 0.42, R * 0.2, 0, Math.PI * 2); c.fill();
}
} else if (est.brilho && fecha < 0.55 && e.bot < 0.3) { // reflexo que acompanha o olhar
const hx = Math.max(x + w * 0.18, Math.min(x + w * 0.62, x + w * 0.3 + (p.lx + sacada.x) * w * 0.012));
const hy = Math.max(y + h * 0.18, Math.min(y + h * 0.5, y + h * 0.26 + (p.ly + sacada.y) * h * 0.012));
c.fillStyle = 'rgba(245,250,255,0.9)';
@@ -205,6 +219,13 @@
bctx.clearRect(0, 0, colunas, linhas);
bctx.save(); bctx.scale(f, f);
olhos.forEach((p, i) => olhoPlano(bctx, o, p, estilo, estado[i]));
for (const traco of o.tracos || []) {
if (typeof traco.d !== 'string') continue;
const caminho = new Path2D(traco.d);
bctx.strokeStyle = bctx.fillStyle = css(o.cor);
bctx.lineWidth = traco.largura; bctx.lineCap = 'round'; bctx.lineJoin = 'round';
if (traco.cheio) bctx.fill(caminho); else bctx.stroke(caminho);
}
desenharMaos(bctx, o, olhos, tela.height / d);
bctx.restore();
const px = bctx.getImageData(0, 0, colunas, linhas).data;