diff --git a/web/src/App.svelte b/web/src/App.svelte index 3778fe9..47681de 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -21,7 +21,6 @@ import Guia from './telas/Guia.svelte'; import Criar from './telas/Criar.svelte'; import DiaADia from './telas/DiaADia.svelte'; - import Ligacoes from './telas/Ligacoes.svelte'; import Ajuda from './lib/componentes/Ajuda.svelte'; import { TOPICO_DA_TELA } from './lib/guia'; @@ -36,7 +35,6 @@ guia: Guia, criar: Criar, 'dia-a-dia': DiaADia, - ligacoes: Ligacoes, }; const ICONES: Record> = { 'ao-vivo': Video, diff --git a/web/src/telas/Ligacoes.svelte b/web/src/lib/componentes/LigacoesPlaca.svelte similarity index 87% rename from web/src/telas/Ligacoes.svelte rename to web/src/lib/componentes/LigacoesPlaca.svelte index 471c98d..51376dc 100644 --- a/web/src/telas/Ligacoes.svelte +++ b/web/src/lib/componentes/LigacoesPlaca.svelte @@ -1,9 +1,9 @@ -
- - {#snippet acoes()} - - {#if confirmarPadrao} - Restaurar todos? - - - {:else} - - {/if} - {/snippet} - - {#if !estado.appConfig} -

Carregando ajustes…

+{#snippet campos(lista: Campo[])} + {#each lista as c (c.chave)} + {#if c.tipo === 'toggle'} + salvarApp({ [c.chave]: v })} /> + {:else if c.tipo === 'slider'} + salvarApp({ [c.chave]: v })} + /> {:else} -
- {#each grupos as g (g.titulo)} -
- - {g.titulo} - {g.descricao} - - {#each g.campos as c (c.chave)} - {#if c.tipo === 'toggle'} - salvarApp({ [c.chave]: v })} /> - {:else if c.tipo === 'slider'} - salvarApp({ [c.chave]: v })} - /> - {:else} - - {/if} - {/each} -
- {:else} -

Nenhum ajuste com “{busca}”.

+ + {/if} + {/each} +{/snippet} + +{#snippet restaurar()} + {#if confirmarPadrao} + Restaurar todos os ajustes do celular? + + + {:else} + + {/if} +{/snippet} + +
+ -
- -
- Motor da conversa -
- - +
+ {#if busca} + + {#if !estado.appConfig} +

Carregando ajustes…

+ {:else} + {#each achados as g (g.titulo)} +
+ {g.titulo} + {@render campos(g.campos)} +
+ {:else} +

Nenhum ajuste com “{busca}”.

+ {/each} + {/if} +
+ {:else if grupoAtual} + + {#snippet acoes()}{@render restaurar()}{/snippet} + {#if !estado.appConfig} +

Carregando ajustes…

+ {:else} +
{@render campos(grupoAtual.campos)}
+ {/if} +
+ {:else if secao === 'ia'} + +
+ Motor da conversa +
+ + +
+ Local roda no celular/placa, sem custo. Gemini responde melhor e cria reações.
- Local roda no celular/placa, sem custo. Gemini responde melhor e cria reações. -
-
-
- Gemini - {estado.api?.configurada ? 'Chave configurada' : 'Sem chave'} -
- - + +
+ Gemini + {estado.api?.configurada ? 'Chave configurada' : 'Sem chave'} +
+ + +
+ + +
+ {#if resultadoTeste} +

+ {resultadoTeste.mensagem}{resultadoTeste.segundos ? ` (${resultadoTeste.segundos}s)` : ''} +

+ {/if} +
+ + {:else if secao === 'ligacoes'} + + {:else if secao === 'seguranca'} +
- - + {#if estado.autenticacao} + Servidor exige token + {:else} + Servidor aberto na rede + {/if}
- {#if resultadoTeste} -

- {resultadoTeste.mensagem}{resultadoTeste.segundos ? ` (${resultadoTeste.segundos}s)` : ''} -

+

+ {#if estado.autenticacao} + Só quem tem o token controla o robô. Ele fica guardado apenas neste navegador. + {:else} + Qualquer aparelho da rede local pode controlar o robô. Defina CARINHA_TOKEN na placa + (veja docs/SEGURANCA.md) depois de atualizar o app do celular. + {/if} +

+ {#if tokenLocal} + {/if} - -
- - -
- {#if estado.autenticacao} - Servidor exige token - {:else} - Servidor aberto na rede - {/if} -
-

- {#if estado.autenticacao} - Só quem tem o token controla o robô. Ele fica guardado apenas neste navegador. - {:else} - Qualquer aparelho da rede local pode controlar o robô. Defina CARINHA_TOKEN na placa - (veja docs/SEGURANCA.md) depois de atualizar o app do celular. - {/if} -

- {#if tokenLocal} - - {/if} -
- - -
-
Versão do servidor
{estado.versao || '—'}
-
Celular
{estado.app?.celular_ip ?? 'offline'}
-
Servidor
{location.host}
-
-
- - Baixar app Android - Guia do painel - Ligações da placa -
-
+ + {:else} + +
+
Versão do servidor
{estado.versao || '—'}
+
Celular
{estado.app?.celular_ip ?? 'offline'}
+
Servidor
{location.host}
+
+
+ + Baixar app Android + Guia do painel +
+
+ {/if} + {busca ? '' : atual.titulo}
@@ -237,14 +314,63 @@ gap: var(--e-4); align-items: start; } - .coluna { + .conteudo { + min-width: 0; display: grid; gap: var(--e-4); } + /* ---------- submenu: fileira rolável no celular, coluna fixa no PC */ + .menu { + display: flex; + gap: var(--e-2); + align-items: center; + overflow-x: auto; + scrollbar-width: none; + padding-bottom: var(--e-1); + } + .grupo-menu { + display: contents; + } + .rotulo-grupo { + display: none; + } + .item { + display: inline-flex; + align-items: center; + gap: var(--e-2); + flex: none; + padding: 6px 12px 6px 6px; + border: 1px solid var(--borda); + border-radius: var(--raio-pill); + background: var(--superficie); + color: var(--texto-2); + font-size: var(--t-sm); + text-decoration: none; + white-space: nowrap; + } + .item.ativo { + border-color: var(--destaque); + background: var(--destaque-suave); + color: var(--texto); + } + .item small { + display: none; + } + .icone { + display: grid; + place-items: center; + width: 26px; + height: 26px; + flex: none; + border-radius: 7px; + background: var(--c); + color: #fff; + } .busca { display: flex; align-items: center; gap: var(--e-2); + flex: none; padding-left: var(--e-3); border: 1px solid var(--borda-forte); border-radius: var(--raio-sm); @@ -254,17 +380,73 @@ .busca input { border: 0; background: transparent; - min-height: 32px; - width: 160px; + min-height: 34px; + width: 150px; } - .grupos { + @media (min-width: 900px) { + .ajustes { + grid-template-columns: 250px minmax(0, 1fr); + } + .menu { + position: sticky; + top: calc(var(--topo-altura) + var(--e-4)); + flex-direction: column; + align-items: stretch; + gap: var(--e-4); + overflow: visible; + } + .busca input { + width: 100%; + } + .grupo-menu { + display: grid; + gap: 2px; + } + .rotulo-grupo { + display: block; + padding: 0 var(--e-2) var(--e-1); + font-size: var(--t-xs); + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--texto-3); + } + .item { + padding: var(--e-2); + border: 0; + border-radius: var(--raio-sm); + background: transparent; + white-space: normal; + } + .item:hover { + background: var(--superficie-2); + } + .item.ativo { + background: var(--superficie); + box-shadow: var(--sombra); + } + .txt { + display: grid; + gap: 1px; + min-width: 0; + } + .item small { + display: block; + color: var(--texto-3); + font-size: var(--t-xs); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + .campos { display: grid; - gap: var(--e-5); - grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); + gap: var(--e-2); + max-width: 640px; } fieldset { border: 0; - margin: 0; + margin: 0 0 var(--e-4); padding: 0; display: grid; gap: var(--e-2); @@ -317,6 +499,7 @@ gap: var(--e-3); padding-top: var(--e-3); border-top: 1px solid var(--borda); + max-width: 560px; } .ok { color: var(--ok); @@ -328,6 +511,7 @@ display: grid; gap: var(--e-2); margin: 0; + max-width: 480px; } .info div { display: flex; @@ -341,9 +525,11 @@ dd { margin: 0; } - @media (min-width: 1200px) { - .ajustes { - grid-template-columns: 2fr 1fr; - } + .sr { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); } diff --git a/web/src/telas/AoVivo.svelte b/web/src/telas/AoVivo.svelte index 8329125..cbf3d91 100644 --- a/web/src/telas/AoVivo.svelte +++ b/web/src/telas/AoVivo.svelte @@ -162,7 +162,7 @@ const pirAtivo = $derived( {/snippet} {/if} - +
@@ -195,7 +195,7 @@ const pirAtivo = $derived( {#snippet acessorio()}{/snippet} - + {#if estado.feed.length}
    {#each estado.feed.slice(0, 12) as item (item.id)} diff --git a/web/src/telas/Conversa.svelte b/web/src/telas/Conversa.svelte index dd519dd..8a66650 100644 --- a/web/src/telas/Conversa.svelte +++ b/web/src/telas/Conversa.svelte @@ -128,7 +128,7 @@ {#snippet acoes()} {#if conversa} {conversa.provedor === 'gemini' ? 'Gemini' : 'Qwen local'} · {conversa.modelo} - trocar + trocar {/if} {/snippet}