- 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>
106 lines
4.8 KiB
TypeScript
106 lines
4.8 KiB
TypeScript
/** Ações que qualquer tela pode disparar: gestos, rosto, cor, E-Stop. */
|
|
import { postar } from './api';
|
|
import { avisos, tentar } from './avisos.svelte';
|
|
import { estado } from './estado.svelte';
|
|
|
|
/** Nome amigável e emoji de cada gesto do rosto ('parar' fecha o kiosk: não vira botão). */
|
|
export const GESTOS_INFO: Record<string, { nome: string; emoji: string }> = {
|
|
feliz: { nome: 'Feliz', emoji: '😊' },
|
|
alegre: { nome: 'Alegre', emoji: '😄' },
|
|
coracao: { nome: 'Coração', emoji: '❤️' },
|
|
piscar: { nome: 'Piscar', emoji: '😌' },
|
|
piscar2: { nome: 'Piscar duplo', emoji: '😑' },
|
|
piscadinha: { nome: 'Piscadinha', emoji: '😉' },
|
|
surpreso: { nome: 'Surpreso', emoji: '😮' },
|
|
triste: { nome: 'Triste', emoji: '😢' },
|
|
bravo: { nome: 'Bravo', emoji: '😠' },
|
|
olhar_esq: { nome: 'Olhar à esquerda', emoji: '👈' },
|
|
olhar_dir: { nome: 'Olhar à direita', emoji: '👉' },
|
|
olhar_baixo: { nome: 'Olhar para baixo', emoji: '👇' },
|
|
dormir: { nome: 'Dormir', emoji: '😴' },
|
|
acordar: { nome: 'Acordar', emoji: '🌅' },
|
|
aleatorio: { nome: 'Aleatório', emoji: '🎲' },
|
|
};
|
|
|
|
export const gestosDisponiveis = () => estado.gestos.filter((g) => g in GESTOS_INFO);
|
|
|
|
const CHAVE_FAVORITOS = 'carinha_favoritos';
|
|
const PADRAO_FAVORITOS = ['feliz', 'coracao', 'piscadinha', 'surpreso', 'triste', 'olhar_esq', 'olhar_dir', 'dormir', 'acordar'];
|
|
|
|
function lerFavoritos(): string[] {
|
|
try {
|
|
const v = JSON.parse(localStorage.getItem(CHAVE_FAVORITOS) || 'null');
|
|
if (Array.isArray(v) && v.every((x) => typeof x === 'string')) return v.slice(0, 9);
|
|
} catch {
|
|
/* sem armazenamento */
|
|
}
|
|
return PADRAO_FAVORITOS;
|
|
}
|
|
|
|
class Favoritos {
|
|
lista = $state<string[]>(lerFavoritos());
|
|
alternar(g: string) {
|
|
this.lista = this.lista.includes(g) ? this.lista.filter((x) => x !== g) : [...this.lista, g].slice(0, 9);
|
|
try {
|
|
localStorage.setItem(CHAVE_FAVORITOS, JSON.stringify(this.lista));
|
|
} catch {
|
|
/* ok */
|
|
}
|
|
}
|
|
}
|
|
export const favoritos = new Favoritos();
|
|
|
|
export const gesto = (nome: string) => tentar(() => postar('/comando', { tipo: 'gesto', valor: nome }));
|
|
export const rosto = (nome: string) => tentar(() => postar('/comando', { tipo: 'rosto', valor: nome }));
|
|
export const cor = (rgb: [number, number, number]) => tentar(() => postar('/comando', { tipo: 'cor', valor: rgb }));
|
|
export const formato = (nome: string) => tentar(() => postar('/comando', { tipo: 'formato', valor: nome }));
|
|
|
|
/** Rostos: os novos usam o motor de pálpebras (rosto-motor.js) e aceitam formato de olho. */
|
|
export const ROSTOS_INFO: Record<string, { nome: string; descricao: string; novo: boolean; formato: boolean }> = {
|
|
vivo: { nome: 'Carinha Viva', descricao: 'O rosto de sempre com pálpebras, brilho e micro-movimentos.', novo: true, formato: true },
|
|
bloco: { nome: 'Bloco', descricao: 'Olhos quadrados que se recortam, estilo robô de mesa.', novo: true, formato: true },
|
|
matriz: { nome: 'Matriz', descricao: 'Painel de LEDs retrô; cada olho é feito de pontos.', novo: true, formato: true },
|
|
lente: { nome: 'Lente', descricao: 'Íris e pupila que dilata, pálpebras de metal.', novo: true, formato: false },
|
|
carinha: { nome: 'Clássico', descricao: 'O desenho original, com boca.', novo: false, formato: false },
|
|
miudo: { nome: 'Miúdo', descricao: 'Clássico com olhos menores.', novo: false, formato: false },
|
|
eilik: { nome: 'Eilik', descricao: 'Clássico sem boca.', novo: false, formato: false },
|
|
};
|
|
export const FORMATOS_INFO: Record<string, string> = {
|
|
padrao: 'Padrão', redondo: 'Redondo', pilula: 'Pílula', quadrado: 'Quadrado', largo: 'Largo',
|
|
};
|
|
export const nomeRosto = (id: string) => ROSTOS_INFO[id]?.nome ?? id;
|
|
export const recarregarCelular = () =>
|
|
tentar(async () => {
|
|
const r = await postar<{ mensagem: string }>('/comando', { tipo: 'recarregar' });
|
|
avisos.ok(r.mensagem);
|
|
});
|
|
|
|
export async function pararTudo() {
|
|
try {
|
|
await postar('/parar-tudo');
|
|
avisos.mostrar('E-Stop acionado: motores desligados e travados.', 'erro');
|
|
} catch (e) {
|
|
avisos.erro(e);
|
|
}
|
|
}
|
|
|
|
export const rearmar = () => tentar(() => postar('/rearmar'), 'Motores rearmados.');
|
|
|
|
export function hex(rgb: number[]) {
|
|
return '#' + rgb.map((v) => v.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
export function rgb(hexCor: string): [number, number, number] {
|
|
const n = parseInt(hexCor.slice(1), 16);
|
|
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
}
|
|
export const CORES: { nome: string; rgb: [number, number, number] }[] = [
|
|
{ nome: 'Ciano', rgb: [92, 214, 255] },
|
|
{ nome: 'Verde', rgb: [74, 222, 128] },
|
|
{ nome: 'Amarelo', rgb: [251, 191, 36] },
|
|
{ nome: 'Laranja', rgb: [255, 140, 50] },
|
|
{ nome: 'Rosa', rgb: [255, 110, 190] },
|
|
{ nome: 'Roxo', rgb: [167, 139, 250] },
|
|
{ nome: 'Vermelho', rgb: [248, 90, 90] },
|
|
{ nome: 'Branco', rgb: [235, 240, 245] },
|
|
];
|