feat(painel): Ao vivo em duas colunas e página "Ligações da placa"
Ao vivo no PC: a câmera deixa de ficar presa no topo e ganha cartões embaixo (Gestos, Celular, Câmera, Acontecimentos, Aparência e Personalidade); à direita ficam Vida e O que ele aprendeu. No celular segue uma coluna só, na mesma ordem de antes (display: contents + order). Ligações da placa (#/ligacoes): mapa do conector de 26 pinos do Orange Pi Zero 3 com os dispositivos registrados (lib/ligacoes.ts): - PIR: VCC no 5 V (2), OUT no pino que a placa informa (CARINHA_PIR, padrão gpiochip1:70 = PC6 = pino 11), GND no 9; avisos de 3,3 V nos GPIOs e da ordem dos terminais que muda entre módulos; - PCA9685: 3,3 V (1), SDA (3), SCL (5), GND (6); servos na fonte externa com terra comum; - tela SPI do overlay tft35: pinos 18, 19, 21, 22, 23 e 24 reservados. Estado ao vivo de cada um (sinal do PIR, PCA9685 no I²C), destaque dos pinos ao tocar no dispositivo, atalhos em Ajustes, Ao vivo e Ctrl+K e tópico no guia. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+77
-38
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Brain, Camera, Download, Eye, Footprints, Gamepad2, Hand, History, Palette, PenLine, RefreshCw, Repeat, ScanFace, Settings2, Smile, Sparkles, Video, Wand2,
|
||||
Brain, Cable, Camera, Download, Eye, Footprints, Gamepad2, Hand, History, Palette, PenLine, RefreshCw, Repeat, ScanFace, Settings2, Smile, Sparkles, Video, Wand2,
|
||||
} from '@lucide/svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { postar } from '../lib/api';
|
||||
@@ -125,16 +125,16 @@ const pirAtivo = $derived(
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="lista">
|
||||
<VidaCartao />
|
||||
{#if estado.vida?.config.ligado}<AprendizadoCartao />{/if}
|
||||
<!-- No PC: à direita Vida e o que ele aprendeu; embaixo da câmera, o resto.
|
||||
No celular as duas colunas se desfazem (display: contents) e a ordem vem do `order`. -->
|
||||
<div class="coluna direita">
|
||||
<div class="c-vida"><VidaCartao /></div>
|
||||
{#if estado.vida?.config.ligado}<div class="c-aprendizado"><AprendizadoCartao /></div>{/if}
|
||||
|
||||
<Grupo titulo="Celular" ajuda="celular">
|
||||
<Celular />
|
||||
<Linha icone={RefreshCw} cor="#0a84ff" rotulo="Recarregar tela do celular" onclick={recarregarCelular} disabled={!estado.celularOnline} />
|
||||
</Grupo>
|
||||
</div>
|
||||
|
||||
<Grupo titulo="Câmera" ajuda="camera" rodape={transmitindo && rastreio ? rastreio.texto : 'O vídeo vai direto do celular para este navegador (WebRTC); nada é gravado.'}>
|
||||
<div class="coluna esquerda">
|
||||
<div class="c-camera"><Grupo titulo="Câmera" ajuda="camera" rodape={transmitindo && rastreio ? rastreio.texto : 'O vídeo vai direto do celular para este navegador (WebRTC); nada é gravado.'}>
|
||||
<Linha icone={Video} cor="#34c759" rotulo="Transmitir câmera do celular" detalhe={video?.exibindo ? video.qualidade : ''}>
|
||||
{#snippet acessorio()}
|
||||
<Interruptor rotulo="Transmitir câmera do celular" checked={transmitindo} disabled={!cfg} onchange={(v) => salvarApp({ preview: v })} />
|
||||
@@ -163,9 +163,9 @@ const pirAtivo = $derived(
|
||||
</Linha>
|
||||
{/if}
|
||||
<Linha icone={Settings2} cor="#8e8e93" rotulo="Qualidade, câmera frontal/traseira e sensores" href="#/ajustes" />
|
||||
</Grupo>
|
||||
</Grupo></div>
|
||||
|
||||
<Grupo titulo="Gestos" ajuda="gestos" rodape="Teclas 1 a 9 disparam os nove primeiros. Ctrl+K busca qualquer gesto.">
|
||||
<div class="c-gestos"><Grupo titulo="Gestos" ajuda="gestos" rodape="Teclas 1 a 9 disparam os nove primeiros. Ctrl+K busca qualquer gesto.">
|
||||
<Linha icone={Smile} cor="#ffcc00" rotulo="Favoritos">
|
||||
<Gestos />
|
||||
</Linha>
|
||||
@@ -188,9 +188,31 @@ const pirAtivo = $derived(
|
||||
</Linha>
|
||||
<Linha icone={Wand2} cor="#af52de" rotulo="Criar com IA" detalhe="Expressões, atividades e figurinhas novas" href="#/criar" />
|
||||
<Linha icone={Sparkles} cor="#ff2d55" rotulo="Todos os gestos e reações" href="#/reacoes" />
|
||||
</Grupo>
|
||||
</Grupo></div>
|
||||
|
||||
<Grupo titulo="Aparência" ajuda="aparencia">
|
||||
<div class="c-acontecimentos"><Grupo titulo="Acontecimentos" ajuda="acontecimentos">
|
||||
<Linha icone={Footprints} cor="#34c759" rotulo="Sensor de presença (PIR)">
|
||||
{#snippet acessorio()}<PresencaPir />{/snippet}
|
||||
</Linha>
|
||||
<Linha icone={History} cor="#30b0c7" rotulo="Eventos" detalhe="Presença, movimento e som, com foto" href="#/eventos" />
|
||||
<Linha icone={Cable} cor="#8e8e93" rotulo="Ligações da placa" detalhe="Em que pino vai cada fio do PIR e dos servos" href="#/ligacoes" />
|
||||
{#if estado.feed.length}
|
||||
<ol class="feed">
|
||||
{#each estado.feed.slice(0, 12) as item (item.id)}
|
||||
<li class={item.tipo}><time class="mono">{hora(item.quando)}</time> <span>{item.texto}</span></li>
|
||||
{/each}
|
||||
</ol>
|
||||
{:else}
|
||||
<p class="vazio-feed">Gestos, respostas e reações aparecem aqui na hora.</p>
|
||||
{/if}
|
||||
</Grupo></div>
|
||||
|
||||
<div class="c-celular"><Grupo titulo="Celular" ajuda="celular">
|
||||
<Celular />
|
||||
<Linha icone={RefreshCw} cor="#0a84ff" rotulo="Recarregar tela do celular" onclick={recarregarCelular} disabled={!estado.celularOnline} />
|
||||
</Grupo></div>
|
||||
|
||||
<div class="c-aparencia"><Grupo titulo="Aparência" ajuda="aparencia">
|
||||
<Linha
|
||||
icone={Palette}
|
||||
cor="#ff9500"
|
||||
@@ -202,9 +224,9 @@ const pirAtivo = $derived(
|
||||
<span class="amostra-cor" style="--c: {hex(estado.aparencia.cor)}" aria-hidden="true"></span>
|
||||
{/snippet}
|
||||
</Linha>
|
||||
</Grupo>
|
||||
</Grupo></div>
|
||||
|
||||
<Grupo titulo="Personalidade" ajuda="personalidade" rodape={estado.ia?.erro || 'Qwen escolhe expressões a partir de sons e movimentos do celular.'}>
|
||||
<div class="c-personalidade"><Grupo titulo="Personalidade" ajuda="personalidade" rodape={estado.ia?.erro || 'Qwen escolhe expressões a partir de sons e movimentos do celular.'}>
|
||||
<Linha
|
||||
icone={Brain}
|
||||
cor="#5e5ce6"
|
||||
@@ -215,30 +237,14 @@ const pirAtivo = $derived(
|
||||
<Interruptor rotulo="Reagir sozinho com IA local" checked={!!estado.ia?.ativo} disabled={!estado.ia} onchange={iaLocal} />
|
||||
{/snippet}
|
||||
</Linha>
|
||||
</Grupo>
|
||||
|
||||
<Grupo titulo="Acontecimentos" ajuda="acontecimentos">
|
||||
<Linha icone={Footprints} cor="#34c759" rotulo="Sensor de presença (PIR)">
|
||||
{#snippet acessorio()}<PresencaPir />{/snippet}
|
||||
</Linha>
|
||||
<Linha icone={History} cor="#30b0c7" rotulo="Eventos" detalhe="Presença, movimento e som, com foto" href="#/eventos" />
|
||||
{#if estado.feed.length}
|
||||
<ol class="feed">
|
||||
{#each estado.feed.slice(0, 12) as item (item.id)}
|
||||
<li class={item.tipo}><time class="mono">{hora(item.quando)}</time> <span>{item.texto}</span></li>
|
||||
{/each}
|
||||
</ol>
|
||||
{:else}
|
||||
<p class="vazio-feed">Gestos, respostas e reações aparecem aqui na hora.</p>
|
||||
{/if}
|
||||
</Grupo>
|
||||
</Grupo></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.ao-vivo {
|
||||
display: grid;
|
||||
gap: var(--e-4);
|
||||
gap: var(--e-5);
|
||||
align-items: start;
|
||||
}
|
||||
.atividades {
|
||||
@@ -380,12 +386,22 @@ const pirAtivo = $derived(
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgb(52 199 89 / 0.7); }
|
||||
50% { box-shadow: 0 0 0 7px rgb(52 199 89 / 0); }
|
||||
}
|
||||
/* ---------- lista */
|
||||
.lista {
|
||||
display: grid;
|
||||
gap: var(--e-5);
|
||||
/* ---------- cartões: uma coluna no celular (na ordem abaixo), duas no PC */
|
||||
.coluna {
|
||||
display: contents;
|
||||
}
|
||||
.coluna > div {
|
||||
min-width: 0;
|
||||
}
|
||||
.palco { order: 0; }
|
||||
.c-vida { order: 1; }
|
||||
.c-aprendizado { order: 2; }
|
||||
.c-celular { order: 3; }
|
||||
.c-camera { order: 4; }
|
||||
.c-gestos { order: 5; }
|
||||
.c-aparencia { order: 6; }
|
||||
.c-personalidade { order: 7; }
|
||||
.c-acontecimentos { order: 8; }
|
||||
.amostra-cor {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
@@ -429,10 +445,33 @@ const pirAtivo = $derived(
|
||||
@media (min-width: 1024px) {
|
||||
.ao-vivo {
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(340px, 1fr);
|
||||
grid-template-areas: 'palco direita' 'esquerda direita';
|
||||
grid-template-rows: auto 1fr;
|
||||
gap: var(--e-5);
|
||||
}
|
||||
/* com cartões embaixo, a câmera deixa de ficar presa no topo (rolaria por cima deles) */
|
||||
.palco {
|
||||
top: calc(var(--topo-altura) + var(--e-5));
|
||||
grid-area: palco;
|
||||
position: relative;
|
||||
top: auto;
|
||||
}
|
||||
.coluna {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: var(--e-5);
|
||||
min-width: 0;
|
||||
}
|
||||
.esquerda {
|
||||
grid-area: esquerda;
|
||||
}
|
||||
.direita {
|
||||
grid-area: direita;
|
||||
}
|
||||
.esquerda > .c-gestos { order: 1; }
|
||||
.esquerda > .c-celular { order: 2; }
|
||||
.esquerda > .c-camera { order: 3; }
|
||||
.esquerda > .c-acontecimentos { order: 4; }
|
||||
.esquerda > .c-aparencia { order: 5; }
|
||||
.esquerda > .c-personalidade { order: 6; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user