feat(rosto): motor de palpebras, 4 rostos novos, formato do olho e tela Rosto

- rosto-motor.js: emocoes como posicao de palpebra (cima, inclinacao, baixo)
  com transicao suave; respiracao, sacadas e piscada que achata o olho
- rostos novos Carinha Viva, Bloco, Matriz (LEDs) e Lente (iris/pupila);
  os classicos continuam; emoji/figurinha nos olhos seguem no SVG
- formato do olho (padrao, redondo, pilula, quadrado, largo) para Viva,
  Bloco e Matriz; salvo na aparencia e enviado como ordem "formato X"
- toque na tela do S10: cutucar = susto e risada, deslizar = carinho,
  tocar dormindo acorda
- painel: tela Rosto (celular em destaque, cartoes de rosto, formatos,
  cores, testar emocoes so na previa ou no celular, lista do que ja esta
  pronto); Ao vivo leva a ela; paleta ganha rostos e formatos
- corrige /comando rosto que nao salvava a aparencia

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 01:33:56 -03:00
co-authored by Claude Opus 5.5
parent b6b8e2efd5
commit 972dd8dd66
18 changed files with 1054 additions and 95 deletions
+15 -71
View File
@@ -6,7 +6,7 @@
import { postar } from '../lib/api';
import { salvarApp } from '../lib/ajustes.svelte';
import { tentar } from '../lib/avisos.svelte';
import { cor, CORES, hex, recarregarCelular, rgb, rosto } from '../lib/comandos.svelte';
import { FORMATOS_INFO, hex, nomeRosto, recarregarCelular, ROSTOS_INFO } from '../lib/comandos.svelte';
import Celular from '../lib/componentes/Celular.svelte';
import Gestos from '../lib/componentes/Gestos.svelte';
import Grupo from '../lib/componentes/Grupo.svelte';
@@ -154,29 +154,16 @@
</Grupo>
<Grupo titulo="Aparência">
<Linha icone={Smile} cor="#5ac8fa" rotulo="Rosto">
<div class="segmentado" role="radiogroup" aria-label="Rosto">
{#each estado.rostos as r (r)}
<button role="radio" aria-checked={estado.aparencia.rosto === r} onclick={() => rosto(r)}>{r}</button>
{/each}
</div>
</Linha>
<Linha icone={Palette} cor="#ff9500" rotulo="Cor dos olhos">
<div class="cores">
{#each CORES as c (c.nome)}
<button
class="cor"
style="--c: {hex(c.rgb)}"
aria-label={c.nome}
aria-pressed={hex(estado.aparencia.cor) === hex(c.rgb)}
onclick={() => cor(c.rgb)}
></button>
{/each}
<label class="cor livre" title="Outra cor">
<input type="color" value={hex(estado.aparencia.cor)} onchange={(e) => cor(rgb(e.currentTarget.value))} />
<span class="sr-only">Escolher outra cor</span>
</label>
</div>
<Linha
icone={Palette}
cor="#ff9500"
rotulo="Rosto, formato e cor"
detalhe={nomeRosto(estado.aparencia.rosto) + (ROSTOS_INFO[estado.aparencia.rosto]?.formato ? ' · ' + (FORMATOS_INFO[estado.aparencia.formato ?? 'padrao'] ?? '') : '')}
href="#/rosto"
>
{#snippet acessorio()}
<span class="amostra-cor" style="--c: {hex(estado.aparencia.cor)}" aria-hidden="true"></span>
{/snippet}
</Linha>
</Grupo>
@@ -314,55 +301,12 @@
gap: var(--e-5);
min-width: 0;
}
.segmentado {
display: flex;
background: var(--superficie-2);
border-radius: 9px;
padding: 2px;
gap: 2px;
}
.segmentado button {
flex: 1;
min-height: 32px;
border: 0;
border-radius: 7px;
background: transparent;
color: var(--texto-2);
font: 500 var(--t-sm) var(--fonte);
text-transform: capitalize;
cursor: pointer;
}
.segmentado button[aria-checked='true'] {
background: var(--superficie);
color: var(--texto);
box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
}
.cores {
display: flex;
flex-wrap: wrap;
gap: var(--e-2);
}
.cor {
width: 34px;
height: 34px;
.amostra-cor {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid var(--borda-forte);
background: var(--c);
cursor: pointer;
position: relative;
}
.cor[aria-pressed='true'] {
outline: 2px solid var(--texto);
outline-offset: 2px;
}
.livre {
background: conic-gradient(red, yellow, lime, cyan, blue, magenta, red);
}
.livre input {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
border: 2px solid var(--borda-forte);
}
.feed {
list-style: none;