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:
@@ -21,6 +21,7 @@
|
|||||||
import Guia from './telas/Guia.svelte';
|
import Guia from './telas/Guia.svelte';
|
||||||
import Criar from './telas/Criar.svelte';
|
import Criar from './telas/Criar.svelte';
|
||||||
import DiaADia from './telas/DiaADia.svelte';
|
import DiaADia from './telas/DiaADia.svelte';
|
||||||
|
import Ligacoes from './telas/Ligacoes.svelte';
|
||||||
import Ajuda from './lib/componentes/Ajuda.svelte';
|
import Ajuda from './lib/componentes/Ajuda.svelte';
|
||||||
import { TOPICO_DA_TELA } from './lib/guia';
|
import { TOPICO_DA_TELA } from './lib/guia';
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
guia: Guia,
|
guia: Guia,
|
||||||
criar: Criar,
|
criar: Criar,
|
||||||
'dia-a-dia': DiaADia,
|
'dia-a-dia': DiaADia,
|
||||||
|
ligacoes: Ligacoes,
|
||||||
};
|
};
|
||||||
const ICONES: Record<string, Component<any>> = {
|
const ICONES: Record<string, Component<any>> = {
|
||||||
'ao-vivo': Video,
|
'ao-vivo': Video,
|
||||||
|
|||||||
+16
-2
@@ -74,6 +74,20 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
|||||||
],
|
],
|
||||||
dica: 'Para ensinar rápido: quando ele fizer algo que você gostou, toque na tela do celular (vale como carinho) ou fale com ele logo em seguida.',
|
dica: 'Para ensinar rápido: quando ele fizer algo que você gostou, toque na tela do celular (vale como carinho) ou fale com ele logo em seguida.',
|
||||||
},
|
},
|
||||||
|
ligacoes: {
|
||||||
|
titulo: 'Ligações da placa',
|
||||||
|
emoji: '🔌',
|
||||||
|
onde: 'Ajustes › Ligações da placa (também em Ao vivo › Acontecimentos)',
|
||||||
|
resumo:
|
||||||
|
'Mapa do conector de 26 pinos do Orange Pi Zero 3 com o que está ligado em cada pino: o sensor de presença (PIR), a placa dos servos (PCA9685) e os pinos reservados para a tela SPI. O pino do PIR vem da própria placa, então o desenho acompanha a configuração.',
|
||||||
|
itens: [
|
||||||
|
{ emoji: '🚶', nome: 'PIR', texto: 'VCC no 5 V (pino 2 ou 4), OUT no pino 11 (PC6) e GND num terra (pino 9, por exemplo). Nunca ligue o VCC no pino 11: os GPIOs são de 3,3 V. A ordem dos terminais muda entre módulos; leia o que está escrito no sensor.' },
|
||||||
|
{ emoji: '🦾', nome: 'PCA9685', texto: 'VCC no 3,3 V (pino 1), SDA no 3, SCL no 5 e GND no 6. Os servos usam a fonte externa no borne V+, com terra comum com a placa.' },
|
||||||
|
{ emoji: '🖥️', nome: 'Tela SPI (tft35)', texto: 'O overlay tft35 reserva os pinos 18, 19, 21, 22, 23 e 24. Não ligue sensores neles.' },
|
||||||
|
{ emoji: '🟢', nome: 'Estado ao vivo', texto: 'Cada dispositivo mostra se a placa está lendo: o PIR diz se o sinal está alto agora, e a PCA9685 se responde no I²C.' },
|
||||||
|
],
|
||||||
|
dica: 'Sinal do PIR alto o tempo todo, por muito tempo, costuma ser fio trocado (VCC e OUT invertidos), sensor apontado para uma fonte de calor ou o ajuste de tempo do HC-SR501 no máximo.',
|
||||||
|
},
|
||||||
cuidados: {
|
cuidados: {
|
||||||
titulo: 'Cuidar do Carinha',
|
titulo: 'Cuidar do Carinha',
|
||||||
emoji: '❤️',
|
emoji: '❤️',
|
||||||
@@ -354,12 +368,12 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
|||||||
/** Ordem do tutorial (página Guia). */
|
/** Ordem do tutorial (página Guia). */
|
||||||
export const ORDEM_GUIA = [
|
export const ORDEM_GUIA = [
|
||||||
'inicio', 'vida', 'aprendizado', 'cuidados', 'humores', 'ritmo', 'sono', 'celular', 'camera', 'gestos', 'atividades', 'criar', 'ideias', 'aparencia',
|
'inicio', 'vida', 'aprendizado', 'cuidados', 'humores', 'ritmo', 'sono', 'celular', 'camera', 'gestos', 'atividades', 'criar', 'ideias', 'aparencia',
|
||||||
'personalidade', 'acontecimentos', 'eventos', 'presenca', 'tela', 'sensores', 'sensibilidade',
|
'personalidade', 'acontecimentos', 'ligacoes', 'eventos', 'presenca', 'tela', 'sensores', 'sensibilidade',
|
||||||
'reacoes', 'conversa', 'movimento', 'atalhos',
|
'reacoes', 'conversa', 'movimento', 'atalhos',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Tópico de cada tela, para o ⓘ do topo. */
|
/** Tópico de cada tela, para o ⓘ do topo. */
|
||||||
export const TOPICO_DA_TELA: Record<string, string> = {
|
export const TOPICO_DA_TELA: Record<string, string> = {
|
||||||
'ao-vivo': 'inicio', eventos: 'eventos', conversa: 'conversa', reacoes: 'reacoes',
|
'ao-vivo': 'inicio', eventos: 'eventos', conversa: 'conversa', reacoes: 'reacoes',
|
||||||
movimento: 'movimento', ajustes: 'tela', rosto: 'aparencia', guia: 'inicio', criar: 'criar', 'dia-a-dia': 'aprendizado',
|
movimento: 'movimento', ajustes: 'tela', rosto: 'aparencia', guia: 'inicio', criar: 'criar', 'dia-a-dia': 'aprendizado', ligacoes: 'ligacoes',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Ligações da placa: o conector de 26 pinos do Orange Pi Zero 3 e o que está ligado em cada pino.
|
||||||
|
* O pino do PIR vem ao vivo da placa (CARINHA_PIR, padrão gpiochip1:70 = PC6 = pino 11).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type TipoPino = '5v' | '3v3' | 'gnd' | 'gpio' | 'i2c' | 'spi' | 'uart';
|
||||||
|
|
||||||
|
export interface Pino {
|
||||||
|
num: number;
|
||||||
|
nome: string;
|
||||||
|
tipo: TipoPino;
|
||||||
|
/** Linha no gpiochip1 (libgpiod), quando é um GPIO. */
|
||||||
|
linha?: number;
|
||||||
|
funcao?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conector de 26 pinos (2 fileiras): ímpares de um lado, pares do outro.
|
||||||
|
export const PINOS: Pino[] = [
|
||||||
|
{ num: 1, nome: '3,3 V', tipo: '3v3' },
|
||||||
|
{ num: 2, nome: '5 V', tipo: '5v' },
|
||||||
|
{ num: 3, nome: 'PH5', tipo: 'i2c', linha: 229, funcao: 'I²C3 SDA' },
|
||||||
|
{ num: 4, nome: '5 V', tipo: '5v' },
|
||||||
|
{ num: 5, nome: 'PH4', tipo: 'i2c', linha: 228, funcao: 'I²C3 SCL' },
|
||||||
|
{ num: 6, nome: 'GND', tipo: 'gnd' },
|
||||||
|
{ num: 7, nome: 'PC9', tipo: 'gpio', linha: 73 },
|
||||||
|
{ num: 8, nome: 'PH2', tipo: 'uart', linha: 226, funcao: 'UART5 TX' },
|
||||||
|
{ num: 9, nome: 'GND', tipo: 'gnd' },
|
||||||
|
{ num: 10, nome: 'PH3', tipo: 'uart', linha: 227, funcao: 'UART5 RX' },
|
||||||
|
{ num: 11, nome: 'PC6', tipo: 'gpio', linha: 70 },
|
||||||
|
{ num: 12, nome: 'PC11', tipo: 'gpio', linha: 75 },
|
||||||
|
{ num: 13, nome: 'PC5', tipo: 'gpio', linha: 69 },
|
||||||
|
{ num: 14, nome: 'GND', tipo: 'gnd' },
|
||||||
|
{ num: 15, nome: 'PC8', tipo: 'gpio', linha: 72 },
|
||||||
|
{ num: 16, nome: 'PC15', tipo: 'gpio', linha: 79 },
|
||||||
|
{ num: 17, nome: '3,3 V', tipo: '3v3' },
|
||||||
|
{ num: 18, nome: 'PC14', tipo: 'gpio', linha: 78 },
|
||||||
|
{ num: 19, nome: 'PH7', tipo: 'spi', linha: 231, funcao: 'SPI1 MOSI' },
|
||||||
|
{ num: 20, nome: 'GND', tipo: 'gnd' },
|
||||||
|
{ num: 21, nome: 'PH8', tipo: 'spi', linha: 232, funcao: 'SPI1 MISO' },
|
||||||
|
{ num: 22, nome: 'PC7', tipo: 'gpio', linha: 71 },
|
||||||
|
{ num: 23, nome: 'PH6', tipo: 'spi', linha: 230, funcao: 'SPI1 CLK' },
|
||||||
|
{ num: 24, nome: 'PH9', tipo: 'spi', linha: 233, funcao: 'SPI1 CS0' },
|
||||||
|
{ num: 25, nome: 'GND', tipo: 'gnd' },
|
||||||
|
{ num: 26, nome: 'PC10', tipo: 'gpio', linha: 74 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const NOME_TIPO: Record<TipoPino, string> = {
|
||||||
|
'5v': '5 V', '3v3': '3,3 V', gnd: 'Terra (GND)', gpio: 'GPIO (3,3 V)', i2c: 'I²C', spi: 'SPI', uart: 'Serial',
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Fio {
|
||||||
|
pino: number;
|
||||||
|
/** Nome do terminal no dispositivo (VCC, OUT, GND, SDA...). */
|
||||||
|
terminal: string;
|
||||||
|
cor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Dispositivo {
|
||||||
|
id: string;
|
||||||
|
nome: string;
|
||||||
|
emoji: string;
|
||||||
|
cor: string;
|
||||||
|
descricao: string;
|
||||||
|
fios: Fio[];
|
||||||
|
avisos: string[];
|
||||||
|
/** 'em uso' = o servidor usa; 'reservado' = a placa reserva os pinos (overlay), mas o Carinha não usa. */
|
||||||
|
situacao: 'em uso' | 'reservado';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pinoDaLinha(linha: number) {
|
||||||
|
return PINOS.find((p) => p.linha === linha)?.num ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dispositivos registrados. O pino do PIR é o que a placa informa (CARINHA_PIR). */
|
||||||
|
export function dispositivos(pinoPir: number | null): Dispositivo[] {
|
||||||
|
const saida = pinoPir ?? 11;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'pir',
|
||||||
|
nome: 'Sensor de presença (PIR)',
|
||||||
|
emoji: '🚶',
|
||||||
|
cor: '#34c759',
|
||||||
|
situacao: 'em uso',
|
||||||
|
descricao: 'Módulo PIR (tipo HC-SR501 ou AM312). Avisa quando alguém se mexe na frente: acorda o rosto e liga a câmera no modo presença.',
|
||||||
|
fios: [
|
||||||
|
{ pino: 2, terminal: 'VCC', cor: '#ff3b30' },
|
||||||
|
{ pino: saida, terminal: 'OUT', cor: '#ffcc00' },
|
||||||
|
{ pino: 9, terminal: 'GND', cor: '#8e8e93' },
|
||||||
|
],
|
||||||
|
avisos: [
|
||||||
|
'A ordem dos 3 terminais muda de um módulo para outro: leia o que está escrito na plaquinha do sensor (no HC-SR501, tire a cúpula branca para ver VCC, OUT e GND).',
|
||||||
|
`Nunca ligue o VCC (5 V) no pino ${saida}: os GPIOs da placa são de 3,3 V e queimam com 5 V.`,
|
||||||
|
'A saída do HC-SR501 e do AM312 é de 3,3 V, segura para o GPIO. Se o seu sensor soltar 5 V na saída, use um divisor de tensão.',
|
||||||
|
'O VCC pode ir em qualquer 5 V (pino 2 ou 4) e o GND em qualquer terra (6, 9, 14, 20 ou 25).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pca9685',
|
||||||
|
nome: 'Placa dos servos (PCA9685)',
|
||||||
|
emoji: '🦾',
|
||||||
|
cor: '#0a84ff',
|
||||||
|
situacao: 'em uso',
|
||||||
|
descricao: 'Controla os servos do braço por I²C (barramento /dev/i2c-2, endereço 0x40).',
|
||||||
|
fios: [
|
||||||
|
{ pino: 1, terminal: 'VCC', cor: '#ff9500' },
|
||||||
|
{ pino: 3, terminal: 'SDA', cor: '#0a84ff' },
|
||||||
|
{ pino: 5, terminal: 'SCL', cor: '#5ac8fa' },
|
||||||
|
{ pino: 6, terminal: 'GND', cor: '#8e8e93' },
|
||||||
|
],
|
||||||
|
avisos: [
|
||||||
|
'O VCC da PCA9685 vai no 3,3 V (pino 1): é só a lógica.',
|
||||||
|
'Os servos NÃO são alimentados pela placa: o borne V+ da PCA9685 recebe a fonte externa (6 V), com o GND da fonte ligado ao GND da placa (terra comum).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tela',
|
||||||
|
nome: 'Tela SPI 3,5" (overlay tft35)',
|
||||||
|
emoji: '🖥️',
|
||||||
|
cor: '#af52de',
|
||||||
|
situacao: 'reservado',
|
||||||
|
descricao: 'O overlay tft35 está ativo no boot e reserva estes pinos para uma tela SPI. O Carinha não usa essa tela: não ligue outros sensores neles.',
|
||||||
|
fios: [
|
||||||
|
{ pino: 18, terminal: 'DC', cor: '#af52de' },
|
||||||
|
{ pino: 19, terminal: 'MOSI', cor: '#af52de' },
|
||||||
|
{ pino: 21, terminal: 'MISO', cor: '#af52de' },
|
||||||
|
{ pino: 22, terminal: 'RESET', cor: '#af52de' },
|
||||||
|
{ pino: 23, terminal: 'CLK', cor: '#af52de' },
|
||||||
|
{ pino: 24, terminal: 'CS', cor: '#af52de' },
|
||||||
|
],
|
||||||
|
avisos: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ export function itensDaPaleta(): ItemPaleta[] {
|
|||||||
registrarNaPaleta(() => [
|
registrarNaPaleta(() => [
|
||||||
...ROTAS.map((r) => ({ id: 'ir-' + r.id, grupo: 'Ir para', titulo: r.titulo, fazer: () => nav.ir(r.id) })),
|
...ROTAS.map((r) => ({ id: 'ir-' + r.id, grupo: 'Ir para', titulo: r.titulo, fazer: () => nav.ir(r.id) })),
|
||||||
{ id: 'ir-rosto', grupo: 'Ir para', titulo: 'Rosto (estilo, formato e cor)', fazer: () => nav.ir('rosto') },
|
{ id: 'ir-rosto', grupo: 'Ir para', titulo: 'Rosto (estilo, formato e cor)', fazer: () => nav.ir('rosto') },
|
||||||
|
{ id: 'ir-ligacoes', grupo: 'Ir para', titulo: 'Ligações da placa (pinos do PIR, servos e tela)', fazer: () => nav.ir('ligacoes') },
|
||||||
{ id: 'ir-dia-a-dia', grupo: 'Ir para', titulo: 'Dia a dia (rotina e decisões do Carinha)', fazer: () => nav.ir('dia-a-dia') },
|
{ id: 'ir-dia-a-dia', grupo: 'Ir para', titulo: 'Dia a dia (rotina e decisões do Carinha)', fazer: () => nav.ir('dia-a-dia') },
|
||||||
{ id: 'ir-criar', grupo: 'Ir para', titulo: 'Criar com IA (expressões, atividades, figurinhas)', fazer: () => nav.ir('criar') },
|
{ id: 'ir-criar', grupo: 'Ir para', titulo: 'Criar com IA (expressões, atividades, figurinhas)', fazer: () => nav.ir('criar') },
|
||||||
...Object.entries(ATIVIDADES_INFO).map(([id, a]) => ({
|
...Object.entries(ATIVIDADES_INFO).map(([id, a]) => ({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/** Rotas por hash (#/conversa): o servidor só precisa entregar index.html em "/". */
|
/** Rotas por hash (#/conversa): o servidor só precisa entregar index.html em "/". */
|
||||||
|
|
||||||
export type Rota = 'ao-vivo' | 'eventos' | 'conversa' | 'reacoes' | 'movimento' | 'ajustes' | 'rosto' | 'guia' | 'criar' | 'dia-a-dia';
|
export type Rota = 'ao-vivo' | 'eventos' | 'conversa' | 'reacoes' | 'movimento' | 'ajustes' | 'rosto' | 'guia' | 'criar' | 'dia-a-dia' | 'ligacoes';
|
||||||
|
|
||||||
export const ROTAS: { id: Rota; titulo: string; atalho: string }[] = [
|
export const ROTAS: { id: Rota; titulo: string; atalho: string }[] = [
|
||||||
{ id: 'ao-vivo', titulo: 'Ao vivo', atalho: 'g v' },
|
{ id: 'ao-vivo', titulo: 'Ao vivo', atalho: 'g v' },
|
||||||
@@ -17,6 +17,7 @@ export const SECUNDARIAS: { id: Rota; titulo: string; volta: Rota }[] = [
|
|||||||
{ id: 'guia', titulo: 'Guia', volta: 'ajustes' },
|
{ id: 'guia', titulo: 'Guia', volta: 'ajustes' },
|
||||||
{ id: 'criar', titulo: 'Criar com IA', volta: 'reacoes' },
|
{ id: 'criar', titulo: 'Criar com IA', volta: 'reacoes' },
|
||||||
{ id: 'dia-a-dia', titulo: 'Dia a dia', volta: 'ao-vivo' },
|
{ id: 'dia-a-dia', titulo: 'Dia a dia', volta: 'ao-vivo' },
|
||||||
|
{ id: 'ligacoes', titulo: 'Ligações da placa', volta: 'ajustes' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const tituloDa = (id: Rota) => [...ROTAS, ...SECUNDARIAS].find((r) => r.id === id)?.titulo ?? '';
|
export const tituloDa = (id: Rota) => [...ROTAS, ...SECUNDARIAS].find((r) => r.id === id)?.titulo ?? '';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BookOpen, Download, KeyRound, RefreshCw, RotateCcw, Search, ShieldCheck, ShieldOff } from '@lucide/svelte';
|
import { BookOpen, Cable, Download, KeyRound, RefreshCw, RotateCcw, Search, ShieldCheck, ShieldOff } from '@lucide/svelte';
|
||||||
import { gravarToken, lerToken, postar } from '../lib/api';
|
import { gravarToken, lerToken, postar } from '../lib/api';
|
||||||
import { GRUPOS, restaurarPadrao, salvarApp, type Campo } from '../lib/ajustes.svelte';
|
import { GRUPOS, restaurarPadrao, salvarApp, type Campo } from '../lib/ajustes.svelte';
|
||||||
import { avisos, tentar } from '../lib/avisos.svelte';
|
import { avisos, tentar } from '../lib/avisos.svelte';
|
||||||
@@ -225,6 +225,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<a class="btn pequeno" href="/app.apk" download><Download size={14} /> Baixar app Android</a>
|
<a class="btn pequeno" href="/app.apk" download><Download size={14} /> Baixar app Android</a>
|
||||||
<a class="btn pequeno" href="#/guia"><BookOpen size={14} /> Guia do painel</a>
|
<a class="btn pequeno" href="#/guia"><BookOpen size={14} /> Guia do painel</a>
|
||||||
|
<a class="btn pequeno" href="#/ligacoes"><Cable size={14} /> Ligações da placa</a>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+77
-38
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
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';
|
} from '@lucide/svelte';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { postar } from '../lib/api';
|
import { postar } from '../lib/api';
|
||||||
@@ -125,16 +125,16 @@ const pirAtivo = $derived(
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="lista">
|
<!-- No PC: à direita Vida e o que ele aprendeu; embaixo da câmera, o resto.
|
||||||
<VidaCartao />
|
No celular as duas colunas se desfazem (display: contents) e a ordem vem do `order`. -->
|
||||||
{#if estado.vida?.config.ligado}<AprendizadoCartao />{/if}
|
<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">
|
</div>
|
||||||
<Celular />
|
|
||||||
<Linha icone={RefreshCw} cor="#0a84ff" rotulo="Recarregar tela do celular" onclick={recarregarCelular} disabled={!estado.celularOnline} />
|
|
||||||
</Grupo>
|
|
||||||
|
|
||||||
<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 : ''}>
|
<Linha icone={Video} cor="#34c759" rotulo="Transmitir câmera do celular" detalhe={video?.exibindo ? video.qualidade : ''}>
|
||||||
{#snippet acessorio()}
|
{#snippet acessorio()}
|
||||||
<Interruptor rotulo="Transmitir câmera do celular" checked={transmitindo} disabled={!cfg} onchange={(v) => salvarApp({ preview: v })} />
|
<Interruptor rotulo="Transmitir câmera do celular" checked={transmitindo} disabled={!cfg} onchange={(v) => salvarApp({ preview: v })} />
|
||||||
@@ -163,9 +163,9 @@ const pirAtivo = $derived(
|
|||||||
</Linha>
|
</Linha>
|
||||||
{/if}
|
{/if}
|
||||||
<Linha icone={Settings2} cor="#8e8e93" rotulo="Qualidade, câmera frontal/traseira e sensores" href="#/ajustes" />
|
<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">
|
<Linha icone={Smile} cor="#ffcc00" rotulo="Favoritos">
|
||||||
<Gestos />
|
<Gestos />
|
||||||
</Linha>
|
</Linha>
|
||||||
@@ -188,9 +188,31 @@ const pirAtivo = $derived(
|
|||||||
</Linha>
|
</Linha>
|
||||||
<Linha icone={Wand2} cor="#af52de" rotulo="Criar com IA" detalhe="Expressões, atividades e figurinhas novas" href="#/criar" />
|
<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" />
|
<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
|
<Linha
|
||||||
icone={Palette}
|
icone={Palette}
|
||||||
cor="#ff9500"
|
cor="#ff9500"
|
||||||
@@ -202,9 +224,9 @@ const pirAtivo = $derived(
|
|||||||
<span class="amostra-cor" style="--c: {hex(estado.aparencia.cor)}" aria-hidden="true"></span>
|
<span class="amostra-cor" style="--c: {hex(estado.aparencia.cor)}" aria-hidden="true"></span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Linha>
|
</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
|
<Linha
|
||||||
icone={Brain}
|
icone={Brain}
|
||||||
cor="#5e5ce6"
|
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} />
|
<Interruptor rotulo="Reagir sozinho com IA local" checked={!!estado.ia?.ativo} disabled={!estado.ia} onchange={iaLocal} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Linha>
|
</Linha>
|
||||||
</Grupo>
|
</Grupo></div>
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.ao-vivo {
|
.ao-vivo {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--e-4);
|
gap: var(--e-5);
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
.atividades {
|
.atividades {
|
||||||
@@ -380,12 +386,22 @@ const pirAtivo = $derived(
|
|||||||
0%, 100% { box-shadow: 0 0 0 0 rgb(52 199 89 / 0.7); }
|
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); }
|
50% { box-shadow: 0 0 0 7px rgb(52 199 89 / 0); }
|
||||||
}
|
}
|
||||||
/* ---------- lista */
|
/* ---------- cartões: uma coluna no celular (na ordem abaixo), duas no PC */
|
||||||
.lista {
|
.coluna {
|
||||||
display: grid;
|
display: contents;
|
||||||
gap: var(--e-5);
|
}
|
||||||
|
.coluna > div {
|
||||||
min-width: 0;
|
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 {
|
.amostra-cor {
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
@@ -429,10 +445,33 @@ const pirAtivo = $derived(
|
|||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.ao-vivo {
|
.ao-vivo {
|
||||||
grid-template-columns: minmax(0, 1.5fr) minmax(340px, 1fr);
|
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);
|
gap: var(--e-5);
|
||||||
}
|
}
|
||||||
|
/* com cartões embaixo, a câmera deixa de ficar presa no topo (rolaria por cima deles) */
|
||||||
.palco {
|
.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>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { pedir } from '../lib/api';
|
||||||
|
import Card from '../lib/componentes/Card.svelte';
|
||||||
|
import { estado } from '../lib/estado.svelte';
|
||||||
|
import { dispositivos, NOME_TIPO, pinoDaLinha, PINOS, type Dispositivo, type Pino, type TipoPino } from '../lib/ligacoes';
|
||||||
|
|
||||||
|
// Conector de 26 pinos do Orange Pi Zero 3 com o que está ligado em cada pino. O pino do PIR
|
||||||
|
// vem da placa (CARINHA_PIR); o estado ao vivo do PIR e da PCA9685 vem do servidor.
|
||||||
|
let pir = $state<{ linha: string; disponivel: boolean; erro: string; ativo?: boolean } | null>(null);
|
||||||
|
let foco = $state<string | null>(null);
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
pir = (await pedir<{ pir: typeof pir }>('/historico/status')).pir;
|
||||||
|
} catch {
|
||||||
|
pir = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const linhaPir = $derived(pir ? Number(pir.linha.split(':')[1]) : 70);
|
||||||
|
const pinoPir = $derived(pinoDaLinha(linhaPir));
|
||||||
|
const lista = $derived(dispositivos(pinoPir));
|
||||||
|
const pirAtivo = $derived(estado.pir?.ativo ?? pir?.ativo ?? false);
|
||||||
|
|
||||||
|
// pino -> fios ligados nele (um pino pode ter mais de um dispositivo só no caso dos GND/5 V)
|
||||||
|
const fiosPorPino = $derived.by(() => {
|
||||||
|
const m = new Map<number, { d: Dispositivo; terminal: string; cor: string }[]>();
|
||||||
|
for (const d of lista) for (const f of d.fios) m.set(f.pino, [...(m.get(f.pino) ?? []), { d, terminal: f.terminal, cor: f.cor }]);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
const linhas = $derived(Array.from({ length: 13 }, (_, i) => [PINOS[i * 2], PINOS[i * 2 + 1]] as [Pino, Pino]));
|
||||||
|
|
||||||
|
function apagado(p: Pino) {
|
||||||
|
if (!foco) return false;
|
||||||
|
return !(fiosPorPino.get(p.num) ?? []).some((f) => f.d.id === foco);
|
||||||
|
}
|
||||||
|
function estadoDe(d: Dispositivo): { texto: string; tom: 'ok' | 'aviso' | 'neutro' } {
|
||||||
|
if (d.id === 'pir') {
|
||||||
|
if (!pir) return { texto: 'sem resposta da placa', tom: 'neutro' };
|
||||||
|
if (!pir.disponivel) return { texto: pir.erro || 'sensor indisponível', tom: 'aviso' };
|
||||||
|
return pirAtivo ? { texto: 'sinal alto agora (vendo movimento)', tom: 'ok' } : { texto: 'ligado, sem movimento agora', tom: 'ok' };
|
||||||
|
}
|
||||||
|
if (d.id === 'pca9685') {
|
||||||
|
const m = estado.motores;
|
||||||
|
if (!m) return { texto: 'sem resposta da placa', tom: 'neutro' };
|
||||||
|
return m.disponivel ? { texto: 'respondendo no I²C', tom: 'ok' } : { texto: m.erro || 'não encontrada no I²C', tom: 'aviso' };
|
||||||
|
}
|
||||||
|
return { texto: 'pinos reservados', tom: 'neutro' };
|
||||||
|
}
|
||||||
|
const CORES: Record<TipoPino, string> = {
|
||||||
|
'5v': '#ff3b30', '3v3': '#ff9500', gnd: '#636366', gpio: '#34c759', i2c: '#0a84ff', spi: '#af52de', uart: '#30b0c7',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="pagina">
|
||||||
|
<Card titulo="Conector da placa" ajuda="ligacoes" descricao="Orange Pi Zero 3, conector de 26 pinos em 2 fileiras: ímpares de um lado, pares do outro. Para achar o pino 1, procure o número 1 impresso na placa ou a ilha de solda quadrada no verso. Toque num dispositivo para destacar os pinos dele.">
|
||||||
|
<div class="rolagem">
|
||||||
|
<div class="conector" role="img" aria-label="Mapa do conector de 26 pinos e o que está ligado em cada pino">
|
||||||
|
{#each linhas as [impar, par] (impar.num)}
|
||||||
|
{@const fe = fiosPorPino.get(impar.num) ?? []}
|
||||||
|
{@const fd = fiosPorPino.get(par.num) ?? []}
|
||||||
|
<div class="fios esq" class:apagado={apagado(impar)}>
|
||||||
|
{#each fe as f (f.d.id)}
|
||||||
|
<span class="fio" style="--c: {f.cor}" title={f.d.nome}>{f.d.emoji} {f.terminal}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="pino esq" class:apagado={apagado(impar)} class:usado={fe.length} title={impar.funcao ?? NOME_TIPO[impar.tipo]}>
|
||||||
|
<span class="nome">{impar.nome}</span>
|
||||||
|
<span class="bola" style="--c: {CORES[impar.tipo]}" class:quadrada={impar.num === 1}>{impar.num}</span>
|
||||||
|
</div>
|
||||||
|
<div class="pino dir" class:apagado={apagado(par)} class:usado={fd.length} title={par.funcao ?? NOME_TIPO[par.tipo]}>
|
||||||
|
<span class="bola" style="--c: {CORES[par.tipo]}">{par.num}</span>
|
||||||
|
<span class="nome">{par.nome}</span>
|
||||||
|
</div>
|
||||||
|
<div class="fios dir" class:apagado={apagado(par)}>
|
||||||
|
{#each fd as f (f.d.id)}
|
||||||
|
<span class="fio" style="--c: {f.cor}" title={f.d.nome}>{f.d.emoji} {f.terminal}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="legenda">
|
||||||
|
{#each Object.entries(NOME_TIPO) as [tipo, nome] (tipo)}
|
||||||
|
<span><i style="--c: {CORES[tipo as TipoPino]}"></i>{nome}</span>
|
||||||
|
{/each}
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div class="lista">
|
||||||
|
{#each lista as d (d.id)}
|
||||||
|
{@const st = estadoDe(d)}
|
||||||
|
<Card>
|
||||||
|
<button
|
||||||
|
class="cabeca"
|
||||||
|
class:ativo={foco === d.id}
|
||||||
|
onclick={() => (foco = foco === d.id ? null : d.id)}
|
||||||
|
aria-pressed={foco === d.id}
|
||||||
|
title="Destacar os pinos deste dispositivo"
|
||||||
|
>
|
||||||
|
<span class="emoji" aria-hidden="true">{d.emoji}</span>
|
||||||
|
<span class="txt">
|
||||||
|
<strong>{d.nome}</strong>
|
||||||
|
<span class="st {st.tom}">{d.situacao === 'reservado' ? 'reservado' : 'em uso'} · {st.texto}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<p class="desc">{d.descricao}</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>No dispositivo</th><th>Pino da placa</th><th>O que é</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{#each d.fios as f (f.pino + f.terminal)}
|
||||||
|
{@const p = PINOS[f.pino - 1]}
|
||||||
|
<tr>
|
||||||
|
<td><span class="ponto" style="--c: {f.cor}"></span>{f.terminal}</td>
|
||||||
|
<td class="mono">{f.pino}</td>
|
||||||
|
<td>{p.funcao ?? NOME_TIPO[p.tipo]}{p.tipo === 'gpio' ? ` (${p.nome})` : ''}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{#if d.avisos.length}
|
||||||
|
<ul class="avisos">
|
||||||
|
{#each d.avisos as a (a)}<li>{a}</li>{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
{#if d.id === 'pir' && pir}
|
||||||
|
<p class="nota mono">Configuração da placa: CARINHA_PIR={pir.linha} → pino {pinoPir ?? '?'}</p>
|
||||||
|
{/if}
|
||||||
|
</Card>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pagina {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--e-4);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.pagina {
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lista {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--e-4);
|
||||||
|
}
|
||||||
|
.rolagem {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.conector {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(6.5em, 1fr) auto auto minmax(6.5em, 1fr);
|
||||||
|
gap: 3px 6px;
|
||||||
|
align-items: center;
|
||||||
|
width: max-content;
|
||||||
|
min-width: 100%;
|
||||||
|
margin-top: var(--e-3);
|
||||||
|
}
|
||||||
|
.pino {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: var(--t-xs);
|
||||||
|
color: var(--texto-2);
|
||||||
|
transition: opacity var(--transicao);
|
||||||
|
}
|
||||||
|
.pino.esq {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.pino.usado .nome {
|
||||||
|
color: var(--texto);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.bola {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: color-mix(in srgb, var(--c) 22%, var(--superficie-2));
|
||||||
|
border: 2px solid var(--c);
|
||||||
|
color: var(--texto);
|
||||||
|
font: 600 10px var(--fonte-mono, monospace);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.bola.quadrada {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.fios {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
transition: opacity var(--transicao);
|
||||||
|
}
|
||||||
|
.fios.esq {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.fio {
|
||||||
|
padding: 1px 7px;
|
||||||
|
border-radius: var(--raio-pill);
|
||||||
|
background: color-mix(in srgb, var(--c) 18%, transparent);
|
||||||
|
border: 1px solid var(--c);
|
||||||
|
color: var(--texto);
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.apagado {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
.legenda {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px var(--e-3);
|
||||||
|
margin: var(--e-3) 0 0;
|
||||||
|
font-size: var(--t-xs);
|
||||||
|
color: var(--texto-3);
|
||||||
|
}
|
||||||
|
.legenda span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.legenda i {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--c);
|
||||||
|
}
|
||||||
|
.cabeca {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--e-3);
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.cabeca .emoji {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
flex: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--superficie-2);
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
.cabeca.ativo .emoji {
|
||||||
|
outline: 2px solid var(--destaque);
|
||||||
|
}
|
||||||
|
.txt {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.st {
|
||||||
|
font-size: var(--t-xs);
|
||||||
|
color: var(--texto-3);
|
||||||
|
}
|
||||||
|
.st.ok {
|
||||||
|
color: var(--ok);
|
||||||
|
}
|
||||||
|
.st.aviso {
|
||||||
|
color: var(--aviso, #ff9500);
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
margin: var(--e-3) 0 var(--e-2);
|
||||||
|
font-size: var(--t-sm);
|
||||||
|
color: var(--texto-2);
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: var(--t-sm);
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: var(--t-xs);
|
||||||
|
color: var(--texto-3);
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-bottom: 1px solid var(--borda);
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 5px 6px;
|
||||||
|
border-bottom: 1px solid var(--borda);
|
||||||
|
}
|
||||||
|
td .ponto {
|
||||||
|
display: inline-block;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--c);
|
||||||
|
}
|
||||||
|
.avisos {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
margin: var(--e-3) 0 0;
|
||||||
|
padding-left: 1.1em;
|
||||||
|
font-size: var(--t-sm);
|
||||||
|
color: var(--texto-2);
|
||||||
|
}
|
||||||
|
.nota {
|
||||||
|
margin: var(--e-2) 0 0;
|
||||||
|
font-size: var(--t-xs);
|
||||||
|
color: var(--texto-3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user