feat(ao-vivo): camera sempre visivel, lista estilo iOS e dados do aparelho

- palco fixo no topo: camera grande com o rosto num quadrinho no canto
  (toque troca os dois); botao "Ligar camera" direto no palco
- controles em lista agrupada estilo Ajustes do iPhone (Grupo, Linha,
  Interruptor): camera e rastreamento, gestos, aparencia, personalidade
- cartao do celular com modelo, Android, versao do app, bateria, Wi-Fi,
  IP e estado do rosto
- APK 0.8.1 informa ip/modelo/android/app/wifi (ACCESS_WIFI_STATE, so o
  nivel do sinal); servidor usa o IP informado pelo app em vez do
  endereco da conexao

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-05 23:07:52 -03:00
co-authored by Claude Opus 5.5
parent 3e51266644
commit a058c6f453
13 changed files with 705 additions and 192 deletions
+141
View File
@@ -0,0 +1,141 @@
<script lang="ts">
import { ChevronRight } from '@lucide/svelte';
import type { Component, Snippet } from 'svelte';
// Uma linha da lista: ícone colorido, rótulo, detalhe e um acessório (interruptor, valor ou seta).
// Com href ou onclick a linha inteira é clicável e mostra a seta.
let {
icone,
cor = 'var(--destaque)',
rotulo,
detalhe = '',
href,
onclick,
disabled = false,
acessorio,
children,
}: {
icone?: Component<any>;
cor?: string;
rotulo: string;
detalhe?: string;
href?: string;
onclick?: () => void;
disabled?: boolean;
acessorio?: Snippet;
children?: Snippet;
} = $props();
const clicavel = $derived(!!href || !!onclick);
</script>
{#snippet corpo()}
{#if icone}
{@const Icone = icone}
<span class="icone" style="--cor: {cor}" aria-hidden="true"><Icone size={16} strokeWidth={2.4} /></span>
{/if}
<span class="texto">
<span class="rotulo">{rotulo}</span>
{#if detalhe}<span class="detalhe">{detalhe}</span>{/if}
</span>
{#if acessorio}<span class="acessorio">{@render acessorio()}</span>{/if}
{#if clicavel}<ChevronRight size={18} class="seta" aria-hidden="true" />{/if}
{/snippet}
<div class="linha-ios" class:com-icone={!!icone} class:desativada={disabled}>
{#if href}
<a class="conteudo" {href}>{@render corpo()}</a>
{:else if onclick}
<button class="conteudo" {onclick} {disabled}>{@render corpo()}</button>
{:else}
<div class="conteudo">{@render corpo()}</div>
{/if}
{#if children}<div class="extra">{@render children()}</div>{/if}
</div>
<style>
.linha-ios {
position: relative;
}
/* separador fino que começa depois do ícone, como no iOS */
.linha-ios:not(:first-child)::before {
content: '';
position: absolute;
top: 0;
right: 0;
left: var(--e-4);
border-top: 1px solid var(--borda);
}
.linha-ios.com-icone:not(:first-child)::before {
left: calc(var(--e-4) + 30px + var(--e-3));
}
.conteudo {
display: flex;
align-items: center;
gap: var(--e-3);
width: 100%;
min-height: 52px;
padding: var(--e-2) var(--e-4);
border: 0;
background: transparent;
color: var(--texto);
font: inherit;
text-align: left;
text-decoration: none;
}
a.conteudo,
button.conteudo {
cursor: pointer;
}
a.conteudo:hover,
button.conteudo:hover:not(:disabled) {
background: var(--superficie-2);
}
a.conteudo:active,
button.conteudo:active:not(:disabled) {
background: var(--superficie-3);
}
.icone {
flex: none;
display: grid;
place-items: center;
width: 30px;
height: 30px;
border-radius: 8px;
background: var(--cor);
color: #fff;
}
.texto {
flex: 1;
display: grid;
min-width: 0;
}
.rotulo {
font-size: var(--t-sm);
font-weight: 500;
}
.detalhe {
font-size: var(--t-xs);
color: var(--texto-3);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.acessorio {
flex: none;
display: flex;
align-items: center;
gap: var(--e-2);
color: var(--texto-3);
font-size: var(--t-sm);
}
.conteudo :global(.seta) {
flex: none;
color: var(--texto-3);
}
.extra {
padding: 0 var(--e-4) var(--e-4);
}
.desativada {
opacity: 0.5;
}
</style>