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:
@@ -25,21 +25,49 @@ export const GESTOS_INFO: Record<string, { nome: string; emoji: string }> = {
|
||||
espiar: { nome: 'Espiar', emoji: '🫣' },
|
||||
pensar: { nome: 'Pensar', emoji: '🤔' },
|
||||
musica: { nome: 'Dançar', emoji: '🎵' },
|
||||
rir: { nome: 'Rir', emoji: '😂' },
|
||||
comemorar: { nome: 'Comemorar', emoji: '🥳' },
|
||||
timido: { nome: 'Tímido', emoji: '😳' },
|
||||
confuso: { nome: 'Confuso', emoji: '😕' },
|
||||
bocejar: { nome: 'Bocejar', emoji: '🥱' },
|
||||
tonto: { nome: 'Tonto', emoji: '😵💫' },
|
||||
};
|
||||
|
||||
/** Atividades (momentos dele): o bonequinho do Carinha faz algo embaixo dos olhos. */
|
||||
export const ATIVIDADES_INFO: Record<string, { nome: string; emoji: string }> = {
|
||||
ler: { nome: 'Ler', emoji: '📖' },
|
||||
cantar: { nome: 'Cantar', emoji: '🎤' },
|
||||
bola: { nome: 'Jogar bola', emoji: '⚽' },
|
||||
videogame: { nome: 'Videogame', emoji: '🎮' },
|
||||
pintar: { nome: 'Pintar', emoji: '🎨' },
|
||||
malhar: { nome: 'Malhar', emoji: '🏋️' },
|
||||
};
|
||||
export const atividade = (nome: string) => tentar(() => postar('/comando', { tipo: 'atividade', valor: nome }));
|
||||
|
||||
export const gestosDisponiveis = () => estado.gestos.filter((g) => g in GESTOS_INFO);
|
||||
|
||||
const CHAVE_FAVORITOS = 'carinha_favoritos';
|
||||
const CHAVE_MAOS = 'carinha_favoritos_maos'; // marca que os gestos com mãos já entraram
|
||||
/** Até 12 favoritos; as teclas 1 a 9 valem para os nove primeiros. */
|
||||
export const MAX_FAVORITOS = 12;
|
||||
const CHAVE_V3 = 'carinha_favoritos_v3'; // marca que o bravo e os gestos novos já entraram
|
||||
/** Até 20 favoritos; as teclas 1 a 9 valem para os nove primeiros. */
|
||||
export const MAX_FAVORITOS = 20;
|
||||
const GESTOS_MAOS = ['acenar', 'esconder', 'espiar', 'pensar', 'musica'];
|
||||
const PADRAO_FAVORITOS = ['feliz', 'coracao', 'acenar', 'esconder', 'espiar', 'pensar', 'musica', 'piscadinha', 'surpreso', 'triste', 'dormir', 'acordar'];
|
||||
const GESTOS_V3 = ['rir', 'comemorar', 'timido', 'confuso', 'bocejar', 'tonto', 'bravo'];
|
||||
const PADRAO_FAVORITOS = ['feliz', 'coracao', 'acenar', 'esconder', 'espiar', 'pensar', 'musica', 'piscadinha', 'surpreso', 'triste',
|
||||
'dormir', 'acordar', ...GESTOS_V3];
|
||||
|
||||
function lerFavoritos(): string[] {
|
||||
try {
|
||||
const v = JSON.parse(localStorage.getItem(CHAVE_FAVORITOS) || 'null');
|
||||
if (Array.isArray(v) && v.every((x) => typeof x === 'string')) {
|
||||
if (!localStorage.getItem(CHAVE_V3)) {
|
||||
// Quem já tinha favoritos ganha o bravo de volta e os gestos novos no fim da lista.
|
||||
const lista = [...v, ...GESTOS_V3.filter((g) => !v.includes(g))].slice(0, MAX_FAVORITOS);
|
||||
localStorage.setItem(CHAVE_FAVORITOS, JSON.stringify(lista));
|
||||
localStorage.setItem(CHAVE_V3, '1');
|
||||
localStorage.setItem(CHAVE_MAOS, '1');
|
||||
return lista;
|
||||
}
|
||||
if (localStorage.getItem(CHAVE_MAOS)) return v.slice(0, MAX_FAVORITOS);
|
||||
// Quem já tinha favoritos salvos ganha os gestos com mãos logo depois dos dois primeiros.
|
||||
const novos = GESTOS_MAOS.filter((g) => !v.includes(g));
|
||||
@@ -61,6 +89,7 @@ class Favoritos {
|
||||
try {
|
||||
localStorage.setItem(CHAVE_FAVORITOS, JSON.stringify(this.lista));
|
||||
localStorage.setItem(CHAVE_MAOS, '1');
|
||||
localStorage.setItem(CHAVE_V3, '1');
|
||||
} catch {
|
||||
/* ok */
|
||||
}
|
||||
|
||||
+26
-3
@@ -134,9 +134,15 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
||||
titulo: 'Gestos',
|
||||
emoji: '😉',
|
||||
onde: 'Ao vivo › Gestos e Reações › Gestos do rosto',
|
||||
resumo: 'Animações prontas do rosto: feliz, surpreso, acenar, esconder o rosto com as mãos, dançar e outras.',
|
||||
resumo: 'Animações prontas do rosto: feliz, surpreso, bravo, acenar, esconder o rosto com as mãos, dançar e outras.',
|
||||
itens: [
|
||||
{ emoji: '⭐', nome: 'Favoritos', texto: 'Até 12 gestos fixados com a estrela. Aparecem no Ao vivo.' },
|
||||
{ emoji: '⭐', nome: 'Favoritos', texto: 'Até 20 gestos fixados com a estrela. Aparecem no Ao vivo.' },
|
||||
{ emoji: '😂', nome: 'Rir', texto: 'Olhos fechadinhos e boca aberta, pulando de rir.' },
|
||||
{ emoji: '🥳', nome: 'Comemorar', texto: 'Pula trocando de cor.' },
|
||||
{ emoji: '😳', nome: 'Tímido', texto: 'Olha para baixo e para o lado, cor-de-rosa.' },
|
||||
{ emoji: '😕', nome: 'Confuso', texto: 'Um olho meio fechado e a cabeça indo de um lado para o outro.' },
|
||||
{ emoji: '🥱', nome: 'Bocejar', texto: 'Olhos pesados e boca aberta. Aparece sozinho quando ele está cansado ou entediado.' },
|
||||
{ emoji: '😵💫', nome: 'Tonto', texto: 'Os olhos dão voltas.' },
|
||||
{ emoji: '⌨️', nome: 'Teclas 1 a 9', texto: 'Disparam os nove primeiros favoritos.' },
|
||||
{ emoji: '🔍', nome: 'Ctrl+K', texto: 'Busca qualquer gesto, rosto, cor ou tela pelo nome.' },
|
||||
],
|
||||
@@ -157,6 +163,23 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
||||
],
|
||||
dica: 'Boca, sobrancelhas e íris ligam e desligam em Ajustes › Tela e personalidade.',
|
||||
},
|
||||
atividades: {
|
||||
titulo: 'Atividades (momentos dele)',
|
||||
emoji: '🎮',
|
||||
onde: 'Ao vivo › Gestos › Atividades',
|
||||
resumo:
|
||||
'De vez em quando ele faz alguma coisa: os olhos sobem e encolhem, e embaixo aparece um bonequinho dele, na cor dos olhos, fazendo a atividade. Os olhos grandes acompanham (seguem a bola, leem as linhas, olham a TV).',
|
||||
itens: [
|
||||
{ emoji: '📖', nome: 'Ler', texto: 'Sentado com um livro; vira a página de vez em quando e os olhos leem linha a linha.' },
|
||||
{ emoji: '🎤', nome: 'Cantar', texto: 'Com microfone, balançando, notas subindo e boca cantando.' },
|
||||
{ emoji: '⚽', nome: 'Jogar bola', texto: 'Faz embaixadinha; os olhos seguem a bola.' },
|
||||
{ emoji: '🎮', nome: 'Videogame', texto: 'Sentado com o controle na frente de uma TV com um joguinho; comemora quando ganha.' },
|
||||
{ emoji: '🎨', nome: 'Pintar', texto: 'No cavalete; o quadro vai ganhando pinceladas coloridas.' },
|
||||
{ emoji: '🏋️', nome: 'Malhar', texto: 'Levanta halteres e sua.' },
|
||||
{ emoji: '🐣', nome: 'No modo vida', texto: 'Ele escolhe sozinho pelo humor: feliz canta e joga bola; tranquilo lê e pinta; entediado joga videogame. Nunca de madrugada.' },
|
||||
{ emoji: '👆', nome: 'Parar', texto: 'Botão Parar no painel ou um toque na tela do celular. Dormir também encerra a atividade.' },
|
||||
],
|
||||
},
|
||||
personalidade: {
|
||||
titulo: 'IA local (Qwen)',
|
||||
emoji: '🧠',
|
||||
@@ -280,7 +303,7 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
||||
|
||||
/** Ordem do tutorial (página Guia). */
|
||||
export const ORDEM_GUIA = [
|
||||
'inicio', 'vida', 'cuidados', 'humores', 'ritmo', 'sono', 'celular', 'camera', 'gestos', 'aparencia',
|
||||
'inicio', 'vida', 'cuidados', 'humores', 'ritmo', 'sono', 'celular', 'camera', 'gestos', 'atividades', 'aparencia',
|
||||
'personalidade', 'acontecimentos', 'eventos', 'presenca', 'tela', 'sensores', 'sensibilidade',
|
||||
'reacoes', 'conversa', 'movimento', 'atalhos',
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Registro da command palette (Ctrl+K): cada parte do painel contribui com itens. */
|
||||
import {
|
||||
cor, CORES, formato, FORMATOS_INFO, gesto, GESTOS_INFO, gestosDisponiveis, nomeRosto, pararTudo, rearmar, recarregarCelular, rosto,
|
||||
atividade, ATIVIDADES_INFO, cor, CORES, formato, FORMATOS_INFO, gesto, GESTOS_INFO, gestosDisponiveis, nomeRosto, pararTudo, rearmar, recarregarCelular, rosto,
|
||||
} from './comandos.svelte';
|
||||
import { estado } from './estado.svelte';
|
||||
import { ORDEM_GUIA, TOPICOS } from './guia';
|
||||
@@ -37,6 +37,10 @@ export function itensDaPaleta(): ItemPaleta[] {
|
||||
registrarNaPaleta(() => [
|
||||
...ROTAS.map((r) => ({ id: 'ir-' + r.id, grupo: 'Ir para', titulo: r.titulo, fazer: () => nav.ir(r.id) })),
|
||||
{ id: 'ir-rosto', grupo: 'Ir para', titulo: 'Rosto (estilo, formato e cor)', fazer: () => nav.ir('rosto') },
|
||||
...Object.entries(ATIVIDADES_INFO).map(([id, a]) => ({
|
||||
id: 'atividade-' + id, grupo: 'Atividades', titulo: a.emoji + ' ' + a.nome, fazer: () => atividade(id),
|
||||
})),
|
||||
{ id: 'atividade-parar', grupo: 'Atividades', titulo: '⏹ Parar a atividade', fazer: () => atividade('parar') },
|
||||
...ORDEM_GUIA.map((t) => ({
|
||||
id: 'guia-' + t,
|
||||
grupo: 'Guia',
|
||||
|
||||
@@ -99,5 +99,5 @@ export interface Vida {
|
||||
ultimo_emoji: number | null;
|
||||
/** '' quando pode inventar figurinha agora; senão, o motivo. */
|
||||
pode_criar: string;
|
||||
diario: { horario: number; tipo: 'acao' | 'sono' | 'emoji' | 'cuidado' | 'vida'; texto: string }[];
|
||||
diario: { horario: number; tipo: 'acao' | 'sono' | 'emoji' | 'cuidado' | 'vida' | 'atividade'; texto: string }[];
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Brain, Camera, Download, Eye, Footprints, Hand, History, Palette, PenLine, RefreshCw, Repeat, ScanFace, Settings2, Smile, Sparkles, Video,
|
||||
Brain, Camera, Download, Eye, Footprints, Gamepad2, Hand, History, Palette, PenLine, RefreshCw, Repeat, ScanFace, Settings2, Smile, Sparkles, Video,
|
||||
} from '@lucide/svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { postar } from '../lib/api';
|
||||
import { salvarApp } from '../lib/ajustes.svelte';
|
||||
import { tentar } from '../lib/avisos.svelte';
|
||||
import { FORMATOS_INFO, hex, nomeRosto, recarregarCelular, ROSTOS_INFO } from '../lib/comandos.svelte';
|
||||
import { atividade, ATIVIDADES_INFO, 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';
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
// Palco: câmera grande e rosto no quadrinho do canto; tocar no quadrinho troca os dois.
|
||||
let rostoGrande = $state(false);
|
||||
// Atividade que o celular está fazendo agora (vem na telemetria).
|
||||
const atividadeAgora = $derived((estado.app?.online && (estado.app.estado?.atividade as string | null)) || null);
|
||||
|
||||
onMount(() => {
|
||||
video = new VideoAoVivo(elVideo);
|
||||
@@ -165,6 +167,23 @@ const pirAtivo = $derived(
|
||||
<Linha icone={Smile} cor="#ffcc00" rotulo="Favoritos">
|
||||
<Gestos />
|
||||
</Linha>
|
||||
<Linha
|
||||
icone={Gamepad2}
|
||||
cor="#30b0c7"
|
||||
rotulo="Atividades"
|
||||
detalhe={atividadeAgora ? `Agora: ${ATIVIDADES_INFO[atividadeAgora]?.emoji ?? ''} ${ATIVIDADES_INFO[atividadeAgora]?.nome ?? atividadeAgora}` : 'Um bonequinho dele faz algo embaixo dos olhos'}
|
||||
>
|
||||
<div class="atividades">
|
||||
{#each Object.entries(ATIVIDADES_INFO) as [id, a] (id)}
|
||||
<button class="btn pequeno" class:ativo={atividadeAgora === id} onclick={() => atividade(id)}>
|
||||
<span aria-hidden="true">{a.emoji}</span> {a.nome}
|
||||
</button>
|
||||
{/each}
|
||||
{#if atividadeAgora}
|
||||
<button class="btn pequeno" onclick={() => atividade('parar')}>⏹ Parar</button>
|
||||
{/if}
|
||||
</div>
|
||||
</Linha>
|
||||
<Linha icone={Sparkles} cor="#ff2d55" rotulo="Todos os gestos e reações" href="#/reacoes" />
|
||||
</Grupo>
|
||||
|
||||
@@ -219,6 +238,15 @@ const pirAtivo = $derived(
|
||||
gap: var(--e-4);
|
||||
align-items: start;
|
||||
}
|
||||
.atividades {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--e-2);
|
||||
}
|
||||
.atividades .btn.ativo {
|
||||
border-color: var(--destaque);
|
||||
background: var(--destaque-suave);
|
||||
}
|
||||
/* ---------- palco: sempre visível */
|
||||
.palco {
|
||||
position: sticky;
|
||||
|
||||
Reference in New Issue
Block a user