feat(rosto): atividades (momentos dele) e seis gestos novos
Atividades: a tela se divide, o rosto vai para a esquerda e encolhe, e
à direita um bonequinho do Carinha (silhueta na cor dos olhos) faz algo:
ler (vira a página), cantar (microfone e notas), jogar bola
(embaixadinha), videogame (TV com joguinho), pintar (cavalete que ganha
pinceladas) e malhar (halteres e suor). Os olhos grandes acompanham:
leem as linhas, seguem a bola, olham a TV. Camada própria atrás dos
olhos, vale para todos os rostos; em pé (retrato) a cena vai para baixo.
- Comando {tipo: "atividade", valor: nome|parar}; toque na tela, dormir
e E-Stop encerram; o celular informa a atividade na telemetria.
- Modo vida escolhe pelo humor (feliz canta e joga bola, tranquilo lê
e pinta, entediado joga videogame), nunca de madrugada, e não
interrompe uma atividade em andamento.
- Gestos novos: rir, comemorar, tímido, confuso, bocejar e tonto; o
humor e o modo vida também os usam.
- Painel: favoritos vão até 20 e o bravo volta junto com os novos;
botões de atividade no Ao vivo, no Ctrl+K e tópico no guia.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,13 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
html,body{margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:#000}
|
||||
svg{display:block;width:100%;height:100%}
|
||||
svg{display:block;width:100%;height:100%;position:relative;z-index:2}
|
||||
/* atividade (momentos dele): a tela se divide. Deitado, o rosto vai para a esquerda e o bonequinho
|
||||
aparece à direita; em pé, o rosto sobe e ele aparece embaixo. */
|
||||
#tela,#rostoMotor{transition:transform .8s cubic-bezier(.2,.8,.2,1);transform-origin:50% 50%}
|
||||
body.atividade #tela,body.atividade #rostoMotor{transform:translateX(-23%) scale(.74)}
|
||||
@media (orientation:portrait){body.atividade #tela,body.atividade #rostoMotor{transform:translateY(-20%) scale(.8)}}
|
||||
body.atividade #tela>rect{opacity:0} /* o fundo preto do SVG não pode tapar o bonequinho */
|
||||
.corpo{fill-rule:evenodd}
|
||||
.traco{fill:none;stroke-linecap:round}
|
||||
.corpo,.traco{filter:drop-shadow(0 0 2px currentColor)}
|
||||
@@ -361,7 +367,23 @@ function sequencias() {
|
||||
[{ dy: -5 }, 0.25], [{ dy: 5 }, 0.25], [{ dy: -5 }, 0.25], [{ dy: 5 }, 0.25], [{ forma: "aberto", dy: 0 }, 0.3]];
|
||||
const alegre = [[{ forma: "feliz", cor: AMARELO, boca: "sorriso" }, 0.14], [{ cor: VERDE }, 0.14],
|
||||
[{ cor: CIANO }, 0.14], [{ forma: "aberto" }, 0.2]];
|
||||
return { esconder, espiar, acenar, pensar, musica,
|
||||
// Mais expressões: risada, bocejo, confusão, tontura, timidez e comemoração.
|
||||
const pula = (n, alto, baixo, d) => Array.from({ length: n }, (_, i) => [{ dy: i % 2 ? baixo : alto }, d]);
|
||||
const rir = [[{ forma: "feliz", boca: "aberta", esc: 1.08, dy: -4 }, 0.12], ...pula(8, -6, 2, 0.1),
|
||||
[{ forma: "feliz", boca: "sorriso", dy: 0 }, 0.25], [{ forma: "aberto", boca: "neutra", esc: 1.0 }, 0.3]];
|
||||
const bocejar = [[{ ab: 0.5, boca: "aberta", dy: -4, esc: 1.05 }, 0.5], [{ ab: 0.3, dy: -5 }, 1.0],
|
||||
[{ ab: 0.0, boca: "neutra", dy: 2, esc: 1.0 }, 0.25], [{ ab: 1.0, dy: 0 }, 0.35]];
|
||||
const confuso = [[{ ab2: 0.55, dx: -8, esc: 0.98 }, 0.25], [{ dx: 8 }, 0.45], [{ dx: -6 }, 0.4], [{}, 0.4],
|
||||
[{ ab2: 1.0, dx: 0, esc: 1.0 }, 0.3]];
|
||||
const roda = Array.from({ length: 16 }, (_, i) => [{ dx: 14 * Math.cos(i * Math.PI / 4), dy: 9 * Math.sin(i * Math.PI / 4) }, 0.1]);
|
||||
const tonto = [[{ esc: 0.94, boca: "aberta" }, 0.15], ...roda, [{ dx: 0, dy: 0, esc: 1.0, boca: "neutra" }, 0.35]];
|
||||
const timido = [[{ forma: "feliz", boca: "sorriso", dx: -14, dy: 9, esc: 0.9, cor: ROSA }, 0.35], [{}, 1.2],
|
||||
[{ dx: 0, dy: 0, esc: 1.0, forma: "aberto", cor: CIANO }, 0.4]];
|
||||
const comemorar = [[{ forma: "feliz", boca: "sorriso", cor: AMARELO, dy: -10, esc: 1.15 }, 0.15], [{ dy: 2, cor: ROSA }, 0.15],
|
||||
[{ dy: -10, cor: VERDE }, 0.15], [{ dy: 2, cor: CIANO }, 0.15], [{ dy: -10, cor: AMARELO }, 0.15],
|
||||
[{ dy: 0, cor: CIANO }, 0.2], [{ forma: "aberto", esc: 1.0 }, 0.3]];
|
||||
return { rir, bocejar, confuso, tonto, timido, comemorar,
|
||||
esconder, espiar, acenar, pensar, musica,
|
||||
piscar, piscar2, olhar_esq, olhar_dir, olhar_baixo, feliz, surpreso,
|
||||
bravo, coracao, triste, piscadinha, alegre };
|
||||
}
|
||||
@@ -378,11 +400,11 @@ const LIVRES = ["feliz", "olhar_esq", "olhar_dir", "olhar_baixo",
|
||||
/* Modo vida (servidor): o humor do bichinho escolhe os gestos soltos entre um ato e outro. */
|
||||
const LIVRES_HUMOR = {
|
||||
feliz: ["feliz", "alegre", "piscadinha", "coracao", "olhar_esq", "olhar_dir"],
|
||||
tranquilo: ["olhar_esq", "olhar_dir", "feliz", "piscadinha", "olhar_baixo"],
|
||||
entediado: ["olhar_esq", "olhar_dir", "olhar_baixo", "pensar"],
|
||||
carente: ["olhar_baixo", "triste", "olhar_esq", "olhar_dir"],
|
||||
tranquilo: ["olhar_esq", "olhar_dir", "feliz", "piscadinha", "olhar_baixo", "timido"],
|
||||
entediado: ["olhar_esq", "olhar_dir", "olhar_baixo", "pensar", "bocejar", "confuso"],
|
||||
carente: ["olhar_baixo", "triste", "olhar_esq", "olhar_dir", "timido"],
|
||||
triste: ["triste", "olhar_baixo", "olhar_baixo"],
|
||||
cansado: ["olhar_baixo", "piscar2"],
|
||||
cansado: ["olhar_baixo", "piscar2", "bocejar"],
|
||||
};
|
||||
let HUMOR = "normal";
|
||||
|
||||
@@ -471,6 +493,15 @@ function passo(dt) { // avanca a fila; true se ha gesto em
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
|
||||
let ATIV = null;
|
||||
function atividade(nome) {
|
||||
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
|
||||
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
|
||||
if (r.dormindo) { r.dormindo = false; animar("surpreso"); }
|
||||
if (window.MotorRosto.atividade(nome) && nome === "cantar") tocarSom("musica");
|
||||
}
|
||||
|
||||
function repouso(t) { // pose calma: olha devagar, "respira"
|
||||
if (r.dormindo) {
|
||||
r.forma = "dormir"; r.forma2 = "dormir"; r.boca = "sono";
|
||||
@@ -488,6 +519,14 @@ function repouso(t) { // pose calma: olha devagar, "respira
|
||||
// o vaivem de sempre + a direcao de quem se mexeu na frente (se houver)
|
||||
r.dx = 8.0 * Math.sin(t * 0.55) + 3.0 * Math.sin(t * 1.7) + MOV.olharX;
|
||||
r.dy = 3.5 * Math.sin(t * 0.9) + 1.5 * Math.sin(t * 2.3) + MOV.olharY;
|
||||
if (ATIV) { // fazendo algo: olha para a atividade
|
||||
const v = ATIV.v;
|
||||
r.dx = r.dx * (1 - v) * 0.3 + ATIV.x * v;
|
||||
r.dy = r.dy * (1 - v) * 0.3 + ATIV.y * v;
|
||||
if (ATIV.forma) r.forma = ATIV.forma;
|
||||
if (ATIV.boca) r.boca = ATIV.boca;
|
||||
if (ATIV.ab) r.ab = ATIV.ab;
|
||||
}
|
||||
}
|
||||
|
||||
function desenhar() {
|
||||
@@ -499,7 +538,7 @@ function desenhar() {
|
||||
// desliza na direcao do movimento; parado, volta ao repouso discreto
|
||||
const tAgora = performance.now() / 1000;
|
||||
const seguindo = TRACK.alvo && tAgora - TRACK.visto < .35 && !r.dormindo;
|
||||
const mirando = MOV.ativo && (tAgora - MOV.ultimoMov) < 1.2;
|
||||
const mirando = (MOV.ativo && (tAgora - MOV.ultimoMov) < 1.2) || !!ATIV;
|
||||
const mover = seguindo ? .55 : (r.kf !== null || r.dormindo) ? 0.25 : (mirando ? 0.25 : 0.0);
|
||||
const dx = seguindo ? MOV.olharX : r.dx, dy = seguindo ? MOV.olharY : r.dy;
|
||||
const olhos = [[E1, ab, r.forma, +1], [E2, ab2, f2, -1]];
|
||||
@@ -594,8 +633,11 @@ function trata(ordem) {
|
||||
HUMOR = ordem.slice(6).trim();
|
||||
} else if (ordem.indexOf("ritmo ") === 0) {
|
||||
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
|
||||
} else if (ordem.indexOf("atividade ") === 0) {
|
||||
atividade(ordem.slice(10).trim());
|
||||
} else if (ordem === "dormir" || ordem === "sono") {
|
||||
r.dormindo = true;
|
||||
atividade(null);
|
||||
} else if (ordem === "acordar") {
|
||||
r.dormindo = false; animar("surpreso");
|
||||
} else if (ordem === "aleatorio") {
|
||||
@@ -609,6 +651,7 @@ function trata(ordem) {
|
||||
} else if (ordem === "calma") {
|
||||
// E-Stop do painel: interrompe a animação e volta ao rosto neutro.
|
||||
r.fila = []; r.kf = null; r.boca = "neutra"; r.dormindo = false;
|
||||
atividade(null);
|
||||
} else if (ordem === "parar") {
|
||||
try { window.close(); } catch (e) { r.fila = []; }
|
||||
}
|
||||
@@ -695,6 +738,7 @@ if (MODO_PAINEL) {
|
||||
const dx = e.clientX - inicio.x, dy = e.clientY - inicio.y, dur = performance.now() - inicio.t;
|
||||
inicio = null;
|
||||
if (r.dormindo) { trata('acordar'); cuidar('cutucar'); return; }
|
||||
if (ATIV && !ATIV.saindo) { atividade(null); animar('surpreso'); cuidar('cutucar'); return; } // interrompeu
|
||||
if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) && dur < 900) { animar('coracao'); cuidar('carinho'); return; }
|
||||
if (dur < 350 && Math.hypot(dx, dy) < 15) {
|
||||
cuidar('cutucar');
|
||||
@@ -1128,6 +1172,7 @@ async function sincronizarApp() {
|
||||
centro: [MOV.cx, MOV.cy], config: configRecebida,
|
||||
brilho: window.carinhaBrilho,
|
||||
tela_apagada: !!window.carinhaTelaApagada,
|
||||
atividade: window.MotorRosto && window.MotorRosto.atividadeAtual ? window.MotorRosto.atividadeAtual() : null,
|
||||
bateria: window.carinhaBateria,
|
||||
aparelho: window.carinhaAparelho,
|
||||
video: window.carinhaVideo,
|
||||
@@ -1227,6 +1272,10 @@ function laco(t) {
|
||||
checarSilencio(t / 1000);
|
||||
atualizarFeicao(t / 1000);
|
||||
|
||||
ATIV = window.MotorRosto && window.MotorRosto.olharAtividade && !r.dormindo ? window.MotorRosto.olharAtividade(t / 1000) : null;
|
||||
if (r.dormindo && window.MotorRosto && window.MotorRosto.atividadeAtual && window.MotorRosto.atividadeAtual()) atividade(null);
|
||||
document.body.classList.toggle("atividade", !!ATIV && !ATIV.saindo);
|
||||
|
||||
const ocupado = congelado ? true : passo(dt);
|
||||
if (!ocupado) {
|
||||
descanso += dt;
|
||||
@@ -1236,7 +1285,7 @@ function laco(t) {
|
||||
} else if (t >= proxPiscar) {
|
||||
animar("piscar");
|
||||
proxPiscar = t + 1600 + Math.random() * 3500;
|
||||
} else if (AUTO && t >= proxGesto) {
|
||||
} else if (AUTO && t >= proxGesto && !ATIV) {
|
||||
animar(gestoSolto());
|
||||
// cansado e triste mexem menos; feliz, mais
|
||||
const ritmo = { cansado: 1.8, triste: 1.5, feliz: 0.8 }[HUMOR] || 1;
|
||||
@@ -1247,6 +1296,7 @@ function laco(t) {
|
||||
}
|
||||
|
||||
desenhar();
|
||||
if (window.MotorRosto && window.MotorRosto.palco) window.MotorRosto.palco({ t: t / 1000, cor: CIANO });
|
||||
|
||||
contador++;
|
||||
if (t - marco >= 1000) {
|
||||
|
||||
Reference in New Issue
Block a user