feat(web): cria painel Svelte com shell, Ao vivo e Ajustes
- web/ com Vite + Svelte 5 + TypeScript; servidor entrega web/dist em "/" (telas antigas seguem em /dashboard, /painel, /braco...) - design system em tokens CSS, tema escuro e claro, fontes locais - estado unico: foto de /estado + um EventSource em /eventos, sem polling - shell com navegacao lateral/inferior, selos de status reais e E-Stop fixo - command palette (Ctrl+K), gestos favoritos com atalhos 1-9, g+letra navega - Ao vivo: camera WebRTC e rastreamento portados para TS, rosto, aparencia, sensores do celular, IA local e feed de acontecimentos - Ajustes: campos do celular agrupados com busca, salvamento otimista e restaurar padrao (GET /app/padrao); motor da conversa, Gemini e token - carinha.html ganha modo ?painel=1 dirigido por postMessage (sem polling) - teste Playwright de ponta a ponta do painel novo Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
<script lang="ts">
|
||||
import { Star } from '@lucide/svelte';
|
||||
import { favoritos, gesto, GESTOS_INFO, gestosDisponiveis } from '../comandos.svelte';
|
||||
|
||||
// compacto: só os favoritos com atalho 1–9 (dock). Completo: todos, com estrela para fixar.
|
||||
let { completo = false }: { completo?: boolean } = $props();
|
||||
const lista = $derived(completo ? gestosDisponiveis() : favoritos.lista.filter((g) => g in GESTOS_INFO));
|
||||
</script>
|
||||
|
||||
<div class="gestos" class:completo>
|
||||
{#each lista as g, i (g)}
|
||||
{@const info = GESTOS_INFO[g]}
|
||||
<div class="item">
|
||||
<button class="gesto" onclick={() => gesto(g)} title={completo ? info.nome : `${info.nome} (tecla ${i + 1})`}>
|
||||
<span class="emoji" aria-hidden="true">{info.emoji}</span>
|
||||
<span class="nome">{info.nome}</span>
|
||||
{#if !completo}<kbd>{i + 1}</kbd>{/if}
|
||||
</button>
|
||||
{#if completo}
|
||||
<button
|
||||
class="estrela"
|
||||
class:ativa={favoritos.lista.includes(g)}
|
||||
aria-label={favoritos.lista.includes(g) ? `Tirar ${info.nome} dos favoritos` : `Fixar ${info.nome} nos favoritos`}
|
||||
aria-pressed={favoritos.lista.includes(g)}
|
||||
onclick={() => favoritos.alternar(g)}
|
||||
>
|
||||
<Star size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.gestos {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
|
||||
gap: var(--e-2);
|
||||
}
|
||||
.item {
|
||||
position: relative;
|
||||
}
|
||||
.gesto {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 2px;
|
||||
padding: var(--e-3) var(--e-1) var(--e-2);
|
||||
border: 1px solid var(--borda);
|
||||
border-radius: var(--raio);
|
||||
background: var(--superficie-2);
|
||||
color: var(--texto);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: background var(--transicao), border-color var(--transicao), transform var(--transicao);
|
||||
}
|
||||
.gesto:hover {
|
||||
border-color: var(--destaque);
|
||||
background: var(--destaque-suave);
|
||||
}
|
||||
.gesto:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
.emoji {
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.nome {
|
||||
font-size: var(--t-xs);
|
||||
color: var(--texto-2);
|
||||
text-align: center;
|
||||
}
|
||||
kbd {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
font: 500 10px var(--fonte-mono);
|
||||
color: var(--texto-3);
|
||||
}
|
||||
.estrela {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--texto-3);
|
||||
cursor: pointer;
|
||||
}
|
||||
.estrela.ativa {
|
||||
color: var(--aviso);
|
||||
}
|
||||
.estrela.ativa :global(svg) {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user