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>
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- mesmo motor de rosto do celular, usado nas miniaturas das reações -->
|
||||
<script src="/rosto-motor.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,56 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { rgb, ROSTOS_INFO } from '../comandos.svelte';
|
||||
import { estado } from '../estado.svelte';
|
||||
import { SIMBOLO_EMOJI } from '../treinamento.svelte';
|
||||
|
||||
// Prévia estática de uma reação (sem iframe): figurinha SVG, emoji ou olhos do 1º passo.
|
||||
// Retrato parado da reação, desenhado pelo mesmo motor do celular (rosto-motor.js),
|
||||
// no rosto, formato e cor que o robô está usando agora.
|
||||
let { receita, tamanho = 64 }: { receita: any; tamanho?: number } = $props();
|
||||
let tela: HTMLCanvasElement;
|
||||
|
||||
const passo = $derived(receita?.passos?.[0]);
|
||||
const simbolo = $derived(receita?.emoji?.caractere || SIMBOLO_EMOJI[receita?.simbolo] || '');
|
||||
const nosOlhos = $derived({
|
||||
icone:
|
||||
receita?.emoji?.local === 'olhos'
|
||||
? receita.emoji.caractere
|
||||
: ['ideia', 'cafe'].includes(receita?.simbolo) && !receita?.emoji && !receita?.figurinha
|
||||
? SIMBOLO_EMOJI[receita.simbolo]
|
||||
: null,
|
||||
desenho: receita?.figurinha?.local === 'olhos' ? receita.figurinha : null,
|
||||
});
|
||||
// O que aparece fora dos olhos (emoji/figurinha "ao redor", notas, estrelas, corações).
|
||||
const aoRedor = $derived.by(() => {
|
||||
if (receita?.emoji && receita.emoji.local !== 'olhos') return { emoji: receita.emoji.caractere };
|
||||
if (receita?.figurinha && receita.figurinha.local !== 'olhos') return { figurinha: receita.figurinha };
|
||||
if (['musica', 'estrela', 'coracao'].includes(receita?.simbolo)) return { emoji: SIMBOLO_EMOJI[receita.simbolo] };
|
||||
return null;
|
||||
});
|
||||
const estilo = $derived(ROSTOS_INFO[estado.aparencia.rosto]?.novo ? estado.aparencia.rosto : 'vivo');
|
||||
|
||||
$effect(() => {
|
||||
const motor = (window as any).MotorRosto;
|
||||
if (!motor || !tela) return;
|
||||
const d = Math.min(2, devicePixelRatio || 1);
|
||||
tela.width = Math.round(tamanho * 1.5 * d); // 3:2, cabe os dois olhos com folga
|
||||
tela.height = Math.round(tamanho * d);
|
||||
motor.retrato(tela, {
|
||||
estilo,
|
||||
formato: estado.aparencia.formato ?? 'padrao',
|
||||
cor: /^#[0-9a-f]{6}$/i.test(passo?.cor ?? '') ? rgb(passo.cor) : estado.aparencia.cor,
|
||||
forma: passo?.olhos ?? 'aberto',
|
||||
icone: nosOlhos.icone,
|
||||
desenho: nosOlhos.desenho ? $state.snapshot(nosOlhos.desenho) : null,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="miniatura" style="--t: {tamanho}px" aria-hidden="true">
|
||||
{#if receita?.figurinha?.formas}
|
||||
<svg viewBox="0 0 100 100" width={tamanho} height={tamanho}>
|
||||
{#each receita.figurinha.formas as forma, i (i)}
|
||||
<path d={forma.caminho} fill={forma.cor} />
|
||||
{/each}
|
||||
<canvas bind:this={tela}></canvas>
|
||||
{#if aoRedor?.emoji}
|
||||
<span class="extra">{aoRedor.emoji}</span>
|
||||
{:else if aoRedor?.figurinha}
|
||||
<svg class="extra" viewBox="0 0 100 100">
|
||||
{#each aoRedor.figurinha.formas as forma, i (i)}<path d={forma.caminho} fill={forma.cor} />{/each}
|
||||
</svg>
|
||||
{:else if receita?.emoji}
|
||||
<span class="emoji">{receita.emoji.caractere}</span>
|
||||
{:else}
|
||||
<svg viewBox="0 0 100 60" width={tamanho} height={tamanho * 0.6}>
|
||||
{#if passo?.olhos === 'feliz'}
|
||||
<path d="M18 38 Q30 18 42 38 M58 38 Q70 18 82 38" stroke={passo.cor} stroke-width="8" fill="none" stroke-linecap="round" />
|
||||
{:else if passo?.olhos === 'triste'}
|
||||
<path d="M18 26 Q30 44 42 26 M58 26 Q70 44 82 26" stroke={passo.cor} stroke-width="8" fill="none" stroke-linecap="round" />
|
||||
{:else}
|
||||
{@const ry = passo?.olhos === 'surpreso' ? 20 : 16}
|
||||
<ellipse cx="30" cy="30" rx="12" {ry} fill={passo?.cor ?? '#5cd6ff'} />
|
||||
<ellipse cx="70" cy="30" rx="12" {ry} fill={passo?.cor ?? '#5cd6ff'} />
|
||||
{/if}
|
||||
</svg>
|
||||
{#if simbolo}<span class="simbolo">{simbolo}</span>{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.miniatura {
|
||||
position: relative;
|
||||
width: var(--t);
|
||||
width: calc(var(--t) * 1.5);
|
||||
height: var(--t);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--raio);
|
||||
background: #05080c;
|
||||
background: #000;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
.emoji {
|
||||
font-size: calc(var(--t) * 0.55);
|
||||
line-height: 1;
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.simbolo {
|
||||
.extra {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 0;
|
||||
font-size: calc(var(--t) * 0.3);
|
||||
right: 4px;
|
||||
top: 3px;
|
||||
width: calc(var(--t) * 0.34);
|
||||
height: calc(var(--t) * 0.34);
|
||||
font-size: calc(var(--t) * 0.26);
|
||||
line-height: calc(var(--t) * 0.34);
|
||||
text-align: center;
|
||||
filter: drop-shadow(0 0 4px rgb(92 214 255 / 0.6));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -364,6 +364,14 @@
|
||||
.espaco {
|
||||
flex: 1;
|
||||
}
|
||||
/* seis ações pequenas + Mostrar cabem numa linha num cartão de 300 px */
|
||||
.acoes :global(.btn.pequeno.icone) {
|
||||
width: 28px;
|
||||
}
|
||||
.acoes {
|
||||
flex-wrap: nowrap;
|
||||
gap: 2px;
|
||||
}
|
||||
.marcado {
|
||||
color: var(--ok);
|
||||
border-color: var(--ok);
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
const alvo = process.env.CARINHA_SERVIDOR || 'http://127.0.0.1:8100';
|
||||
const api = ['/eventos', '/estado', '/comando', '/parar-tudo', '/rearmar', '/fila', '/config', '/faces', '/gestos',
|
||||
'/app', '/rtc', '/camera', '/conversa', '/treinamento', '/braco', '/servos', '/ia', '/api', '/gesto', '/face',
|
||||
'/carinha.html', '/reacoes-render.js', '/rastreador-worker.js', '/vendor', '/qualidade-auto.js', '/app.apk'];
|
||||
'/carinha.html', '/reacoes-render.js', '/rosto-motor.js', '/rastreador-worker.js', '/vendor', '/qualidade-auto.js', '/app.apk'];
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
|
||||
Reference in New Issue
Block a user