feat(web): migra Conversa e Reacoes para o painel novo

- Conversa: chat empurrado por SSE, indicador de "pensando", gesto da
  resposta com repetir, voz com aviso claro quando o contexto nao e seguro
  e leitura em voz alta so de respostas novas
- Memoria unificada: lembrancas da conversa + rotina/preferencias em abas;
  apagar tudo com confirmacao inline (sem confirm nativo)
- Reacoes: biblioteca com miniaturas estaticas (sem iframe por cartao),
  busca, mostrar no celular, feedback, editar em <dialog>, duplicar,
  ativar e excluir com Desfazer no toast; criar com IA; diario; grade de
  gestos com estrela para favoritos; uma unica previa lateral
- treinamento recarrega por evento SSE ao terminar, sem polling
- corrige icones encolhidos em botoes .pequeno.icone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-05 19:51:45 -03:00
co-authored by Claude Opus 5.5
parent c4b53a8976
commit 947c0a3127
9 changed files with 1137 additions and 9 deletions
+56
View File
@@ -0,0 +1,56 @@
<script lang="ts">
import { SIMBOLO_EMOJI } from '../treinamento.svelte';
// Prévia estática de uma reação (sem iframe): figurinha SVG, emoji ou olhos do 1º passo.
let { receita, tamanho = 64 }: { receita: any; tamanho?: number } = $props();
const passo = $derived(receita?.passos?.[0]);
const simbolo = $derived(receita?.emoji?.caractere || SIMBOLO_EMOJI[receita?.simbolo] || '');
</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}
</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);
height: var(--t);
display: grid;
place-items: center;
border-radius: var(--raio);
background: #05080c;
flex: none;
}
.emoji {
font-size: calc(var(--t) * 0.55);
line-height: 1;
}
.simbolo {
position: absolute;
right: 2px;
bottom: 0;
font-size: calc(var(--t) * 0.3);
}
</style>