- 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>
102 lines
2.6 KiB
Svelte
102 lines
2.6 KiB
Svelte
<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>
|