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,87 @@
|
||||
<script lang="ts">
|
||||
import { ChevronDown, ChevronUp } from '@lucide/svelte';
|
||||
import { estado } from '../estado.svelte';
|
||||
import RostoPrevia from './RostoPrevia.svelte';
|
||||
|
||||
// Um só mini-rosto em todas as telas (exceto Ao vivo, que já tem o rosto grande).
|
||||
let aberto = $state(true);
|
||||
try {
|
||||
aberto = localStorage.getItem('carinha_mini') !== '0';
|
||||
} catch {
|
||||
/* ok */
|
||||
}
|
||||
function alternar() {
|
||||
aberto = !aberto;
|
||||
try {
|
||||
localStorage.setItem('carinha_mini', aberto ? '1' : '0');
|
||||
} catch {
|
||||
/* ok */
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<aside class="mini" class:aberto aria-label="Rosto ao vivo">
|
||||
<button class="barra" onclick={alternar} aria-expanded={aberto}>
|
||||
<span class="ponto" class:online={estado.celularOnline} aria-hidden="true"></span>
|
||||
<span>Rosto {estado.celularOnline ? 'ao vivo' : '· celular offline'}</span>
|
||||
{#if aberto}<ChevronDown size={16} />{:else}<ChevronUp size={16} />{/if}
|
||||
</button>
|
||||
{#if aberto}
|
||||
<div class="tela"><RostoPrevia /></div>
|
||||
{/if}
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.mini {
|
||||
position: fixed;
|
||||
z-index: 55;
|
||||
right: var(--e-4);
|
||||
bottom: calc(84px + env(safe-area-inset-bottom, 0px));
|
||||
width: 220px;
|
||||
border-radius: var(--raio);
|
||||
overflow: hidden;
|
||||
background: var(--vidro);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--borda-forte);
|
||||
box-shadow: var(--sombra-flutuante);
|
||||
}
|
||||
.barra {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--e-2);
|
||||
padding: var(--e-2) var(--e-3);
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--texto-2);
|
||||
font: 500 var(--t-xs) var(--fonte);
|
||||
cursor: pointer;
|
||||
}
|
||||
.barra span:nth-child(2) {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
.ponto {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--texto-3);
|
||||
}
|
||||
.ponto.online {
|
||||
background: var(--ok);
|
||||
}
|
||||
.tela {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
@media (max-width: 639px) {
|
||||
.mini {
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.mini {
|
||||
bottom: var(--e-4);
|
||||
width: 260px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user