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:
+307
-1
@@ -518,6 +518,307 @@
|
||||
c.restore();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ atividades
|
||||
// "Momentos dele": um bonequinho do Carinha (silhueta na cor dos olhos) faz algo na parte de
|
||||
// baixo da tela enquanto os olhos sobem, encolhem e acompanham. Camada própria, atrás dos olhos,
|
||||
// vale para qualquer rosto (SVG ou motor). O carinha.html pergunta para onde olhar
|
||||
// (olharAtividade) e chama palco() a cada quadro.
|
||||
const ATIVIDADES = {
|
||||
ler: { nome: 'Lendo', emoji: '📖', dur: [25, 45] },
|
||||
cantar: { nome: 'Cantando', emoji: '🎤', dur: [18, 30] },
|
||||
bola: { nome: 'Jogando bola', emoji: '⚽', dur: [20, 35] },
|
||||
videogame: { nome: 'Videogame', emoji: '🎮', dur: [25, 45] },
|
||||
pintar: { nome: 'Pintando', emoji: '🎨', dur: [25, 40] },
|
||||
malhar: { nome: 'Malhando', emoji: '🏋️', dur: [15, 25] },
|
||||
};
|
||||
const ativ = { nome: null, t0: 0, dur: 0, fim: 0 };
|
||||
let palcoTela = null, pctx = null;
|
||||
const agoraS = () => performance.now() / 1000;
|
||||
|
||||
function iniciarAtividade(nome, segundos) {
|
||||
if (!ATIVIDADES[nome]) { pararAtividade(); return false; }
|
||||
const [a, b] = ATIVIDADES[nome].dur;
|
||||
ativ.nome = nome; ativ.t0 = agoraS(); ativ.fim = 0;
|
||||
ativ.dur = segundos || a + Math.random() * (b - a);
|
||||
return true;
|
||||
}
|
||||
function pararAtividade() { if (ativ.nome && !ativ.fim) ativ.fim = agoraS(); }
|
||||
|
||||
// 0..1: entra e sai em 0,6 s; termina sozinha depois da duração
|
||||
function visibilidade(t) {
|
||||
if (!ativ.nome) return 0;
|
||||
if (!ativ.fim && t - ativ.t0 >= ativ.dur) ativ.fim = t;
|
||||
const entra = Math.min(1, Math.max(0, (t - ativ.t0) / 0.6));
|
||||
if (!ativ.fim) return entra;
|
||||
const sai = 1 - (t - ativ.fim) / 0.6;
|
||||
if (sai <= 0) { ativ.nome = null; return 0; }
|
||||
return Math.min(entra, sai);
|
||||
}
|
||||
|
||||
// movimentos de cada cena, em função do tempo k (s) desde o início
|
||||
const bola = (k) => { const p = (k % 1.1) / 1.1; return { p, h: 4 * p * (1 - p), x: 0.35 * Math.sin(k * 0.7) }; };
|
||||
const pincel = (k) => ({ x: Math.sin(k * 3.1) * 0.6 + Math.sin(k * 1.3) * 0.4, y: Math.cos(k * 2.3) * 0.7 });
|
||||
const forca = (k) => (1 - Math.cos(k * 2.2)) / 2; // 0 = halteres no ombro, 1 = braços em cima
|
||||
|
||||
/** Para onde os olhos grandes olham (unidades do rosto: dx ±24, dy ±12) e que cara fazem. */
|
||||
function olharAtividade(t) {
|
||||
const v = visibilidade(t);
|
||||
if (!v) return null;
|
||||
const k = t - ativ.t0, info = { nome: ativ.nome, v, x: 0, y: 0, saindo: !!ativ.fim };
|
||||
const lado = innerWidth >= innerHeight * 1.2 ? 1 : 0; // deitado: a cena fica à direita
|
||||
if (ativ.nome === 'ler') { // lê linha a linha: varre da esquerda para a direita
|
||||
const linha = (k % 2.4) / 2.4;
|
||||
info.x = (linha < 0.9 ? -8 + 16 * (linha / 0.9) : 8 - 16 * ((linha - 0.9) / 0.1)) + lado * 13;
|
||||
info.y = 7 + (Math.floor(k / 2.4) % 3) * 1.5;
|
||||
} else if (ativ.nome === 'cantar') {
|
||||
info.x = 6 * Math.sin(k * 1.3) + lado * 12; info.y = -4 + 4 * Math.sin(k * 4);
|
||||
info.forma = 'feliz'; info.boca = Math.sin(k * 7) > -0.2 ? 'aberta' : 'sorriso';
|
||||
} else if (ativ.nome === 'bola') { // segue a bola
|
||||
const b = bola(k); info.x = b.x * 16 + lado * 14; info.y = 9 - b.h * 18;
|
||||
} else if (ativ.nome === 'videogame') { // vidrado na TV; de vez em quando comemora
|
||||
info.x = 18 + lado * 5 + Math.sin(k * 9) * 1.5; info.y = 1;
|
||||
if (k % 9 > 7.6) { info.forma = 'feliz'; info.boca = 'sorriso'; }
|
||||
} else if (ativ.nome === 'pintar') {
|
||||
const p = pincel(k); info.x = 16 + lado * 5 + p.x * 4; info.y = -1 + p.y * 5;
|
||||
} else if (ativ.nome === 'malhar') {
|
||||
const f = forca(k); info.x = lado * 14; info.y = 6 - f * 12; info.ab = f > 0.85 ? 0.55 : 1;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
// --- o bonequinho: cabeça-tela com olhinhos, antena, tronco, braços e pernas articulados.
|
||||
// Ângulos em radianos: 0 = apontando para baixo; positivo gira para a direita da tela.
|
||||
function membro(c, x0, y0, a1, a2, l1, l2, w) {
|
||||
const x1 = x0 + Math.sin(a1) * l1, y1 = y0 + Math.cos(a1) * l1;
|
||||
const x2 = x1 + Math.sin(a1 + a2) * l2, y2 = y1 + Math.cos(a1 + a2) * l2;
|
||||
c.beginPath(); capsula(c, x0, y0, x1, y1, w / 2); c.fill(); c.stroke();
|
||||
c.beginPath(); capsula(c, x1, y1, x2, y2, w / 2); c.fill(); c.stroke();
|
||||
return { x: x2, y: y2 };
|
||||
}
|
||||
|
||||
function boneco(c, x, y, s, p, cor, k) {
|
||||
c.fillStyle = css(cor.map((v) => v * 0.22), 0.96); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||
c.lineJoin = 'round'; c.lineCap = 'round';
|
||||
const w = s * 0.27, braco = [s * 0.62, s * 0.58];
|
||||
const perna = p.sentado ? [s * 0.38, s * 0.62] : [s * 0.72, s * 0.7];
|
||||
const hx = x + (p.dx || 0) * s, hy = p.sentado ? y - perna[1] - s * 0.12 : y - (perna[0] + perna[1]) * 0.97;
|
||||
const pE = p.pE || [-0.08, 0], pD = p.pD || [0.08, 0];
|
||||
membro(c, hx - s * 0.25, hy, pE[0], pE[1], perna[0], perna[1], w * 1.1);
|
||||
membro(c, hx + s * 0.25, hy, pD[0], pD[1], perna[0], perna[1], w * 1.1);
|
||||
c.save();
|
||||
c.translate(hx, hy); c.rotate(-(p.inclina || 0));
|
||||
// tronco
|
||||
caixa(c, -s * 0.5, -s * 1.12, s, s * 1.12, s * 0.25); c.fill(); c.stroke();
|
||||
c.save(); c.fillStyle = css(cor, 0.85); // luzinha no peito
|
||||
c.beginPath(); c.arc(0, -s * 0.62, s * 0.09 * (0.8 + 0.2 * Math.sin(k * 3)), 0, Math.PI * 2); c.fill(); c.restore();
|
||||
// cabeça (uma telinha, como o celular) com olhinhos e antena
|
||||
c.save(); c.translate(0, -s * 1.25); c.rotate(-(p.cabeca || 0));
|
||||
c.beginPath(); c.moveTo(0, -s * 0.95); c.lineTo(0, -s * 1.25); c.stroke();
|
||||
c.save(); c.fillStyle = css(cor); c.beginPath(); c.arc(0, -s * 1.3, s * 0.1, 0, Math.PI * 2); c.fill(); c.restore();
|
||||
caixa(c, -s * 0.66, -s * 0.98, s * 1.32, s * 0.98, s * 0.28); c.fill(); c.stroke();
|
||||
const ox = (p.olhoX || 0) * s * 0.12, oy = (p.olhoY || 0) * s * 0.1;
|
||||
const pisca = (k % 3.7) < 0.12 ? 0.15 : 1;
|
||||
c.save(); c.fillStyle = css(cor);
|
||||
for (const lado of [-1, 1]) {
|
||||
c.beginPath(); c.ellipse(lado * s * 0.27 + ox, -s * 0.5 + oy, s * 0.11, s * 0.16 * pisca, 0, 0, Math.PI * 2); c.fill();
|
||||
}
|
||||
c.restore(); c.restore();
|
||||
// braços (no mesmo quadro do tronco inclinado); as mãos voltam para segurar os objetos
|
||||
const bE = p.bE || [-0.25, 0.15], bD = p.bD || [0.25, -0.15];
|
||||
const mE = membro(c, -s * 0.58, -s * 0.98, bE[0], bE[1], braco[0], braco[1], w);
|
||||
const mD = membro(c, s * 0.58, -s * 0.98, bD[0], bD[1], braco[0], braco[1], w);
|
||||
c.restore();
|
||||
const rot = (pt) => { // do quadro do tronco para a tela
|
||||
const a = -(p.inclina || 0), cs = Math.cos(a), sn = Math.sin(a);
|
||||
return { x: hx + pt.x * cs - pt.y * sn, y: hy + pt.x * sn + pt.y * cs };
|
||||
};
|
||||
return { mE: rot(mE), mD: rot(mD), quadril: { x: hx, y: hy }, cabeca: rot({ x: 0, y: -s * 1.75 }) };
|
||||
}
|
||||
|
||||
const claro = (cor, a) => css(cor.map((v) => v + (255 - v) * 0.55), a);
|
||||
const escuro = (cor) => css(cor.map((v) => v * 0.25));
|
||||
|
||||
function nota(c, x, y, s) { // ♪
|
||||
c.beginPath(); c.ellipse(x, y, s * 0.2, s * 0.15, -0.4, 0, Math.PI * 2); c.fill();
|
||||
c.fillRect(x + s * 0.14, y - s * 0.7, s * 0.06, s * 0.7);
|
||||
c.beginPath(); c.moveTo(x + s * 0.2, y - s * 0.7); c.quadraticCurveTo(x + s * 0.5, y - s * 0.55, x + s * 0.42, y - s * 0.3); c.stroke();
|
||||
}
|
||||
|
||||
const CENAS = {
|
||||
ler(c, x, y, s, k, cor) {
|
||||
const b = boneco(c, x, y, s, {
|
||||
sentado: true, pE: [-1.35, 1.35], pD: [1.35, -1.35], cabeca: -0.08, olhoY: 0.8,
|
||||
olhoX: -0.8 + 1.6 * ((k % 2.4) / 2.4), bE: [0.2, 2.4], bD: [-0.2, -2.4],
|
||||
}, cor, k);
|
||||
// livro aberto entre as mãos, com uma página virando de vez em quando
|
||||
const cx = (b.mE.x + b.mD.x) / 2, cy = (b.mE.y + b.mD.y) / 2 + s * 0.14, lw = s * 0.72, lh = s * 0.55;
|
||||
c.fillStyle = claro(cor, 0.95); c.strokeStyle = css(cor);
|
||||
for (const lado of [-1, 1]) {
|
||||
c.beginPath(); c.moveTo(cx, cy + lh * 0.1); c.lineTo(cx + lado * lw, cy); c.lineTo(cx + lado * lw, cy - lh);
|
||||
c.lineTo(cx, cy - lh * 0.9); c.closePath(); c.fill(); c.stroke();
|
||||
}
|
||||
c.strokeStyle = css(cor, 0.5); c.lineWidth = Math.max(1, s * 0.03);
|
||||
for (let i = 1; i <= 3; i++) for (const lado of [-1, 1]) {
|
||||
const yy = cy - lh * (0.25 * i); c.beginPath(); c.moveTo(cx + lado * lw * 0.15, yy); c.lineTo(cx + lado * lw * 0.85, yy - lh * 0.03); c.stroke();
|
||||
}
|
||||
const virar = (k % 6) / 0.7; // vira em 0,7 s a cada 6 s
|
||||
if (virar < 1) {
|
||||
const px = cx + lw * Math.cos(virar * Math.PI), sobe = Math.sin(virar * Math.PI) * lh * 0.25;
|
||||
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||
c.beginPath(); c.moveTo(cx, cy + lh * 0.1); c.lineTo(px, cy - sobe); c.lineTo(px, cy - lh - sobe);
|
||||
c.lineTo(cx, cy - lh * 0.9); c.closePath(); c.fill(); c.stroke();
|
||||
}
|
||||
},
|
||||
cantar(c, x, y, s, k, cor) {
|
||||
const balanco = Math.sin(k * 2.2);
|
||||
const b = boneco(c, x, y, s, {
|
||||
dx: balanco * 0.12, inclina: balanco * 0.08, cabeca: balanco * 0.1, olhoY: -0.3,
|
||||
pE: [-0.1 - Math.max(0, balanco) * 0.15, 0], pD: [0.1 - Math.min(0, balanco) * 0.15, 0],
|
||||
bD: [2.45, 0.9], bE: [-0.9 - 0.4 * Math.sin(k * 4.4), -0.4],
|
||||
}, cor, k);
|
||||
// microfone na mão direita, perto da boca
|
||||
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||
c.beginPath(); capsula(c, b.mD.x, b.mD.y, b.mD.x - s * 0.1, b.mD.y - s * 0.4, s * 0.07); c.fill(); c.stroke();
|
||||
c.fillStyle = claro(cor, 1); c.beginPath(); c.arc(b.mD.x - s * 0.12, b.mD.y - s * 0.5, s * 0.14, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||
// notas subindo
|
||||
c.fillStyle = css(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||
const alfa = c.globalAlpha;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const f = (k * 0.45 + i / 3) % 1;
|
||||
c.globalAlpha = alfa * Math.min(1, f / 0.15) * (1 - f);
|
||||
nota(c, b.cabeca.x + s * (0.9 + 0.5 * i) + Math.sin(f * 6 + i) * s * 0.2, b.cabeca.y + s * 0.4 - f * s * 1.6, s * 0.75);
|
||||
}
|
||||
c.globalAlpha = alfa;
|
||||
},
|
||||
bola(c, x, y, s, k, cor) {
|
||||
const bl = bola(k), chute = Math.max(0, 1 - bl.p / 0.18) + Math.max(0, (bl.p - 0.9) / 0.1);
|
||||
const b = boneco(c, x, y, s, {
|
||||
dx: bl.x * 0.5, olhoX: bl.x * 2, olhoY: 0.6 - bl.h * 1.4,
|
||||
pD: [0.08 + chute * 0.9, -chute * 0.6], pE: [-0.08, 0], bE: [-0.6, 0.3], bD: [0.6, -0.3],
|
||||
}, cor, k);
|
||||
const bx = b.quadril.x + s * 0.55 + bl.x * s * 0.3, by = y - s * 0.55 - bl.h * s * 2.6, r = s * 0.3;
|
||||
const alfa = c.globalAlpha;
|
||||
c.save(); c.globalAlpha = alfa * 0.35 * (1 - bl.h); c.fillStyle = css(cor); // sombra no chão
|
||||
c.beginPath(); c.ellipse(bx, y, r * (1.1 - bl.h * 0.5), r * 0.18, 0, 0, Math.PI * 2); c.fill(); c.restore();
|
||||
c.save(); c.translate(bx, by); c.rotate(k * 5);
|
||||
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||
c.beginPath(); c.arc(0, 0, r, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||
c.fillStyle = css(cor.map((v) => v * 0.3)); // gomo da bola
|
||||
c.beginPath(); for (let i = 0; i < 5; i++) { const a = i * 1.2566; c.lineTo(Math.cos(a) * r * 0.38, Math.sin(a) * r * 0.38); } c.closePath(); c.fill();
|
||||
c.restore();
|
||||
},
|
||||
videogame(c, x, y, s, k, cor) {
|
||||
const aperta = Math.sin(k * 23) * 0.05;
|
||||
const b = boneco(c, x - s * 1.3, y, s, {
|
||||
sentado: true, pE: [-1.35, 1.35], pD: [1.35, -1.35], olhoX: 1, olhoY: 0.1, cabeca: -0.05,
|
||||
bE: [0.15 + aperta, 0.95], bD: [-0.15 - aperta, -0.95],
|
||||
}, cor, k);
|
||||
// controle
|
||||
const cx = (b.mE.x + b.mD.x) / 2, cy = (b.mE.y + b.mD.y) / 2;
|
||||
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||
caixa(c, cx - s * 0.42, cy - s * 0.16, s * 0.84, s * 0.34, s * 0.16); c.fill(); c.stroke();
|
||||
c.fillStyle = css(cor.map((v) => v * 0.3));
|
||||
c.fillRect(cx - s * 0.3, cy - s * 0.02, s * 0.16, s * 0.05); c.fillRect(cx - s * 0.245, cy - s * 0.08, s * 0.05, s * 0.17);
|
||||
for (const [dx, dy] of [[0.22, -0.05], [0.3, 0.04]]) { c.beginPath(); c.arc(cx + dx * s, cy + dy * s, s * 0.04, 0, Math.PI * 2); c.fill(); }
|
||||
// TV com um joguinho: bonequinho pulando obstáculos
|
||||
const tx = x + s * 1.15, tw = s * 2.1, th = s * 1.45, ty = y - s * 0.55 - th;
|
||||
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||
c.beginPath(); c.moveTo(tx + tw / 2, ty + th); c.lineTo(tx + tw / 2, y); c.moveTo(tx + tw * 0.3, y); c.lineTo(tx + tw * 0.7, y); c.stroke();
|
||||
caixa(c, tx, ty, tw, th, s * 0.12); c.fill(); c.stroke();
|
||||
c.save(); caixa(c, tx + s * 0.1, ty + s * 0.1, tw - s * 0.2, th - s * 0.2, s * 0.06); c.clip();
|
||||
const chao = ty + th - s * 0.32, px = s * 0.11;
|
||||
c.fillStyle = css(cor, 0.6); c.fillRect(tx, chao, tw, s * 0.05);
|
||||
const obst = tx + tw - ((k * 1.4) % 1) * (tw + s * 0.4);
|
||||
c.fillStyle = css(cor); c.fillRect(obst, chao - px * 2, px * 1.4, px * 2);
|
||||
const ox = tx + tw * 0.3, perto = (ox + px * 2 - obst) / (s * 0.9) + 0.5;
|
||||
const pulo = perto > 0 && perto < 1 ? Math.sin(perto * Math.PI) : 0;
|
||||
c.fillStyle = claro(cor, 1); c.fillRect(ox, chao - px * 2.4 - pulo * s * 0.45, px * 1.6, px * 2.4);
|
||||
if (k % 9 > 7.6) { // vitória: estrelinhas na tela
|
||||
c.fillStyle = claro(cor, 1); c.font = Math.round(s * 0.4) + 'px sans-serif'; c.textAlign = 'center';
|
||||
c.fillText('★ ★ ★', tx + tw / 2, ty + th * 0.4);
|
||||
}
|
||||
c.restore();
|
||||
},
|
||||
pintar(c, x, y, s, k, cor) {
|
||||
const pc = pincel(k);
|
||||
const ex = x + s * 0.9, ew = s * 1.6, eh = s * 1.35, ey = y - s * 1.0 - eh;
|
||||
// cavalete
|
||||
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||
c.beginPath(); c.moveTo(ex + ew * 0.2, y); c.lineTo(ex + ew * 0.5, ey - s * 0.15); c.lineTo(ex + ew * 0.8, y); c.stroke();
|
||||
c.fillStyle = claro(cor, 0.95); caixa(c, ex, ey, ew, eh, s * 0.05); c.fill(); c.stroke();
|
||||
// pinceladas que vão aparecendo (sempre as mesmas para a mesma pintura)
|
||||
const tintas = [cor, [255, 112, 176], [255, 218, 96], [120, 255, 160]];
|
||||
const n = Math.min(40, Math.floor(k / 0.7));
|
||||
c.save(); caixa(c, ex, ey, ew, eh, s * 0.05); c.clip();
|
||||
for (let i = 0; i < n; i++) {
|
||||
const a = Math.sin(i * 12.9898 + ativ.t0) * 43758.5453, rnd = a - Math.floor(a);
|
||||
const b2 = Math.sin(i * 78.233 + ativ.t0) * 12345.678, rnd2 = b2 - Math.floor(b2);
|
||||
c.fillStyle = css(tintas[i % 4], 0.9);
|
||||
c.beginPath(); c.ellipse(ex + ew * (0.12 + rnd * 0.76), ey + eh * (0.12 + rnd2 * 0.76), s * 0.16, s * 0.07, rnd * 3, 0, Math.PI * 2); c.fill();
|
||||
}
|
||||
c.restore();
|
||||
const b = boneco(c, x - s * 0.5, y, s, {
|
||||
inclina: -0.06, olhoX: 1, olhoY: pc.y * 0.5, cabeca: -0.08,
|
||||
bD: [1.75 + pc.y * 0.25, -0.35 + pc.x * 0.3], bE: [-0.7, 0.9], pE: [-0.12, 0], pD: [0.08, 0],
|
||||
}, cor, k);
|
||||
// pincel numa mão, paleta na outra
|
||||
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||
c.beginPath(); c.moveTo(b.mD.x - s * 0.1, b.mD.y + s * 0.08); c.lineTo(b.mD.x + s * 0.4, b.mD.y - s * 0.12); c.stroke();
|
||||
c.fillStyle = css(tintas[Math.floor(k / 0.7) % 4]); c.beginPath(); c.arc(b.mD.x + s * 0.43, b.mD.y - s * 0.13, s * 0.07, 0, Math.PI * 2); c.fill();
|
||||
c.fillStyle = claro(cor, 0.95); c.beginPath(); c.ellipse(b.mE.x, b.mE.y, s * 0.32, s * 0.2, -0.3, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||
for (let i = 0; i < 3; i++) { c.fillStyle = css(tintas[i + 1]); c.beginPath(); c.arc(b.mE.x - s * 0.15 + i * s * 0.13, b.mE.y - s * 0.03, s * 0.05, 0, Math.PI * 2); c.fill(); }
|
||||
},
|
||||
malhar(c, x, y, s, k, cor) {
|
||||
const f = suave(forca(k));
|
||||
const b = boneco(c, x, y, s, {
|
||||
olhoY: -0.4 + (1 - f) * 0.5, pE: [-0.22, 0.08], pD: [0.22, -0.08],
|
||||
bE: [-1.25 - f * 1.75, -1.9 + f * 1.75], bD: [1.25 + f * 1.75, 1.9 - f * 1.75],
|
||||
}, cor, k);
|
||||
// halteres
|
||||
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||
for (const m of [b.mE, b.mD]) {
|
||||
c.fillRect(m.x - s * 0.25, m.y - s * 0.035, s * 0.5, s * 0.07);
|
||||
for (const lado of [-1, 1]) { caixa(c, m.x + lado * s * 0.28 - s * 0.07, m.y - s * 0.17, s * 0.14, s * 0.34, s * 0.04); c.fill(); c.stroke(); }
|
||||
}
|
||||
if (f > 0.85) { // gotinha de suor
|
||||
const g = (k * 2) % 1;
|
||||
c.fillStyle = claro(cor, 1 - g);
|
||||
c.beginPath(); c.arc(b.cabeca.x + s * 0.72, b.cabeca.y + s * 0.3 + g * s * 0.5, s * 0.07, 0, Math.PI * 2); c.fill();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/** Desenha a cena da atividade (camada atrás dos olhos). o = { t, cor }. */
|
||||
function desenharPalco(o) {
|
||||
const v = visibilidade(o.t);
|
||||
if (!v) {
|
||||
if (palcoTela && palcoTela.style.display !== 'none') palcoTela.style.display = 'none';
|
||||
return false;
|
||||
}
|
||||
if (!palcoTela) {
|
||||
palcoTela = document.createElement('canvas'); palcoTela.id = 'palcoAtividade';
|
||||
palcoTela.style.cssText = 'position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:1';
|
||||
document.body.append(palcoTela); pctx = palcoTela.getContext('2d');
|
||||
}
|
||||
palcoTela.style.display = '';
|
||||
const d = Math.min(1.5, window.devicePixelRatio || 1), W = Math.round(innerWidth * d), H = Math.round(innerHeight * d);
|
||||
if (palcoTela.width !== W || palcoTela.height !== H) { palcoTela.width = W; palcoTela.height = H; }
|
||||
const deitado = W >= H * 1.2;
|
||||
const s = deitado ? H * 0.145 : Math.min(W * 0.075, H * 0.075);
|
||||
const x = deitado ? W * 0.7 : W / 2, y = deitado ? H * 0.9 : H * 0.95;
|
||||
const c = pctx, k = o.t - ativ.t0;
|
||||
c.clearRect(0, 0, W, H);
|
||||
c.save();
|
||||
c.globalAlpha = v;
|
||||
c.translate((1 - v) * (deitado ? W * 0.06 : 0), (1 - v) * (deitado ? 0 : H * 0.08)); // desliza ao entrar
|
||||
c.strokeStyle = css(o.cor, 0.35); c.lineWidth = Math.max(1, s * 0.04); // chão
|
||||
c.beginPath(); c.moveTo(x - s * 2.6, y); c.lineTo(Math.min(W * 0.98, x + s * 3.6), y); c.stroke();
|
||||
c.shadowColor = css(o.cor, 0.55); c.shadowBlur = s * 0.35;
|
||||
CENAS[ativ.nome](c, x, y, s, k, o.cor);
|
||||
c.restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
function esconder() { if (tela) tela.style.display = 'none'; antes = 0; }
|
||||
|
||||
window.MotorRosto = {
|
||||
@@ -529,5 +830,10 @@
|
||||
return ((!base.lente && FORMATOS[formato]) || base).h / 2;
|
||||
},
|
||||
maos: (modo) => { if (DUR_MAOS[modo]) { maos.modo = modo; maos.t0 = performance.now() / 1000; } },
|
||||
faixa: definirFaixa, estilos: Object.keys(ESTILOS), formatos: Object.keys(FORMATOS) };
|
||||
faixa: definirFaixa, estilos: Object.keys(ESTILOS), formatos: Object.keys(FORMATOS),
|
||||
/** Atividades (momentos dele): iniciar/parar, para onde olhar e a camada da cena. */
|
||||
atividade: (nome, segundos) => (nome ? iniciarAtividade(nome, segundos) : (pararAtividade(), false)),
|
||||
atividadeAtual: () => (ativ.nome && !ativ.fim ? ativ.nome : null),
|
||||
olharAtividade, palco: desenharPalco,
|
||||
atividades: Object.fromEntries(Object.entries(ATIVIDADES).map(([k, v]) => [k, { nome: v.nome, emoji: v.emoji }])) };
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user