feat(ao-vivo): pontinho do PIR sobre a imagem do palco

Verde pulsando por 8 s depois de cada disparo, cinza sem presença;
some quando o sensor não está disponível.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 03:56:48 -03:00
co-authored by Claude Opus 5.5
parent 499a4ae479
commit cf54a93b53
+43
View File
@@ -48,6 +48,8 @@
}); });
const cfg = $derived(estado.appConfig); const cfg = $derived(estado.appConfig);
// Pontinho do PIR sobre a imagem: verde pulsando nos 8 s depois de cada disparo.
const pirAtivo = $derived(!!estado.pir?.ultimo && estado.agora - estado.pir.ultimo * 1000 < 8000);
const transmitindo = $derived(!!cfg?.preview); const transmitindo = $derived(!!cfg?.preview);
const hora = (t: number) => new Date(t).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); const hora = (t: number) => new Date(t).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
@@ -70,6 +72,12 @@
<div class="ao-vivo"> <div class="ao-vivo">
<!-- Palco fixo: a câmera continua visível enquanto a lista rola --> <!-- Palco fixo: a câmera continua visível enquanto a lista rola -->
<section class="palco" aria-label="Câmera e rosto"> <section class="palco" aria-label="Câmera e rosto">
{#if estado.pir?.disponivel}
<span class="ponto-pir" class:ativo={pirAtivo} title="Sensor de presença (PIR)" aria-live="polite">
<span class="luz" aria-hidden="true"></span>
PIR{pirAtivo ? ' · presença' : ''}
</span>
{/if}
<div class="quadro camera" class:mini={rostoGrande}> <div class="quadro camera" class:mini={rostoGrande}>
<!-- svelte-ignore a11y_media_has_caption --> <!-- svelte-ignore a11y_media_has_caption -->
<video bind:this={elVideo} muted playsinline autoplay hidden={!video?.exibindo}></video> <video bind:this={elVideo} muted playsinline autoplay hidden={!video?.exibindo}></video>
@@ -300,6 +308,41 @@
.espaco { .espaco {
flex: 1; flex: 1;
} }
.ponto-pir {
position: absolute;
z-index: 3;
left: var(--e-3);
top: var(--e-3);
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px 4px 7px;
border-radius: var(--raio-pill);
background: rgb(0 0 0 / 0.55);
color: #c7cdd4;
font: 600 11px var(--fonte);
letter-spacing: 0.04em;
pointer-events: none;
transition: color var(--transicao);
}
.ponto-pir .luz {
width: 10px;
height: 10px;
border-radius: 50%;
background: #6b7280;
transition: background var(--transicao);
}
.ponto-pir.ativo {
color: #b8f5c9;
}
.ponto-pir.ativo .luz {
background: #34c759;
animation: pulsoPir 1s ease-in-out infinite;
}
@keyframes pulsoPir {
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 */
.lista { .lista {
display: grid; display: grid;