feat(vida): aprendizado com o dia a dia (gosto, rotina da casa e sono)
O Carinha passa a melhorar as interações com o que vive em casa, sem IA e sem internet (aprendizado.py): - gosto: gesto ou atividade feito com alguém por perto é uma tentativa; carinho, brincadeira ou conversa até 60 s depois contam como "gostaram" (cutucada vale meio). A chance de cada ato vai de 0,5x a 2x, relativa à média, e só depois de 3 tentativas; - rotina: fração de minutos com gente em cada hora (média móvel entre dias). Antes de uma hora movimentada, acorda do cochilo 15 min antes; sono pesado e madrugada continuam respeitados; - sono: PIR acordou e ninguém ficou nem interagiu em 3 min = à toa. Se repete na mesma hora, ali pede 1 ou 2 disparos a mais para acordar; - à meia-noite fecha o dia: lições em primeira pessoa no diário e o passado pesa 15% menos a cada dia (o gosto muda); - rotas /aprendizado/status|config|esquecer e SSE 'aprendizado'; - painel: cartão "O que ele aprendeu" no Ao vivo (rotina em 24 barras, faz mais / faz menos, lições, esquecer com confirmação) e tópico no guia. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
/** Aprendizado com o dia a dia: ligar/desligar e esquecer. O estado chega pelo SSE 'aprendizado'. */
|
||||
import { postar } from './api';
|
||||
import { avisos } from './avisos.svelte';
|
||||
import { estado } from './estado.svelte';
|
||||
import type { Aprendizado } from './tipos';
|
||||
|
||||
export async function configurarAprendizado(ligado: boolean) {
|
||||
const antes = estado.aprendizado;
|
||||
if (antes) estado.aprendizado = { ...antes, config: { ...antes.config, ligado } };
|
||||
try {
|
||||
estado.aprendizado = await postar<Aprendizado>('/aprendizado/config', { ligado });
|
||||
} catch (e) {
|
||||
estado.aprendizado = antes;
|
||||
avisos.erro(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function esquecerAprendizado() {
|
||||
try {
|
||||
estado.aprendizado = await postar<Aprendizado>('/aprendizado/esquecer', {});
|
||||
avisos.ok('Ele esqueceu o que tinha aprendido e começa do zero');
|
||||
} catch (e) {
|
||||
avisos.erro(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<script lang="ts">
|
||||
import { GraduationCap } from '@lucide/svelte';
|
||||
import { configurarAprendizado, esquecerAprendizado } from '../aprendizado.svelte';
|
||||
import { estado } from '../estado.svelte';
|
||||
import Grupo from './Grupo.svelte';
|
||||
import Interruptor from './Interruptor.svelte';
|
||||
import Linha from './Linha.svelte';
|
||||
|
||||
// O que ele aprendeu com o dia a dia: rotina da casa (24 h), do que gostam, lições e o sono.
|
||||
const a = $derived(estado.aprendizado);
|
||||
let confirmar = $state(false);
|
||||
|
||||
const horaAgora = $derived(new Date(estado.agora).getHours());
|
||||
const temRotina = $derived(!!a && a.rotina.some((v) => v !== null));
|
||||
const faz_mais = $derived(a ? a.gosto.filter((g) => g.peso > 1.05).slice(0, 4) : []);
|
||||
const faz_menos = $derived(a ? [...a.gosto].reverse().filter((g) => g.peso < 0.95).slice(0, 3) : []);
|
||||
const respostas = $derived(a ? Object.values(a.hoje.respostas).reduce((s, n) => s + n, 0) : 0);
|
||||
|
||||
const detalhe = $derived.by(() => {
|
||||
if (!a) return '';
|
||||
if (!a.config.ligado) return 'Desligado: ele faz tudo como veio de fábrica';
|
||||
const dias = a.dias === 0 ? 'primeiro dia observando' : a.dias === 1 ? '1 dia observado' : `${a.dias} dias observados`;
|
||||
return `${dias} · hoje: ${a.hoje.atos} ${a.hoje.atos === 1 ? 'gesto' : 'gestos'} com alguém perto, ${respostas} ${respostas === 1 ? 'carinho ou conversa' : 'carinhos ou conversas'}`;
|
||||
});
|
||||
|
||||
const resumoRotina = $derived.by(() => {
|
||||
if (!a) return '';
|
||||
const cheias = a.rotina.flatMap((v, h) => (v !== null && v >= 0.5 ? [h] : []));
|
||||
return cheias.length ? 'Costuma ter gente às ' + cheias.map((h) => h + 'h').join(', ') : 'Ainda sem um horário marcado';
|
||||
});
|
||||
|
||||
function dia(iso: string) {
|
||||
const [, m, d] = iso.split('-');
|
||||
return `${d}/${m}`;
|
||||
}
|
||||
function vezes(peso: number) {
|
||||
return '×' + peso.toLocaleString('pt-BR', { maximumFractionDigits: 1 });
|
||||
}
|
||||
</script>
|
||||
|
||||
<Grupo
|
||||
titulo="O que ele aprendeu"
|
||||
ajuda="aprendizado"
|
||||
rodape="Ele conta gestos feitos com alguém por perto e o que veio logo depois (carinho, conversa). À meia-noite junta o dia e escreve as lições."
|
||||
>
|
||||
{#if !a}
|
||||
<p class="vazio">Carregando…</p>
|
||||
{:else}
|
||||
<Linha icone={GraduationCap} cor="#30b0c7" rotulo="Aprender com o dia a dia" {detalhe}>
|
||||
{#snippet acessorio()}
|
||||
<Interruptor rotulo="Aprender com o dia a dia" checked={a.config.ligado} onchange={(l) => configurarAprendizado(l)} />
|
||||
{/snippet}
|
||||
</Linha>
|
||||
|
||||
{#if a.config.ligado}
|
||||
<section class="bloco" aria-labelledby="ap-rotina">
|
||||
<h3 id="ap-rotina">Rotina da casa <span>{temRotina ? resumoRotina : 'aparece depois da primeira meia-noite'}</span></h3>
|
||||
<div class="rotina" role="img" aria-label={resumoRotina}>
|
||||
{#each a.rotina as v, h (h)}
|
||||
<span
|
||||
class="hora"
|
||||
class:agora={h === horaAgora}
|
||||
class:sem={v === null}
|
||||
title={`${h}h: ${v === null ? 'sem dados' : Math.round(v * 100) + '% do tempo com gente'}${a.sono[h] ? ' · acordou à toa aqui' : ''}`}
|
||||
>
|
||||
<span class="barra" style="--v: {v ?? 0}"></span>
|
||||
{#if a.sono[h]}<span class="lua" aria-hidden="true">☾</span>{/if}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="eixo mono" aria-hidden="true"><span>0h</span><span>6h</span><span>12h</span><span>18h</span><span>24h</span></div>
|
||||
{#if a.sono.some((n) => n > 0)}
|
||||
<p class="nota"><span aria-hidden="true">☾</span> Horas em que acordou à toa: ali o sensor precisa de mais movimento para acordá-lo.</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if faz_mais.length || faz_menos.length}
|
||||
<section class="bloco gosto" aria-labelledby="ap-gosto">
|
||||
<h3 id="ap-gosto">Do que vocês gostam</h3>
|
||||
<div class="colunas">
|
||||
<ul>
|
||||
<li class="titulo">Faz mais</li>
|
||||
{#each faz_mais as g (g.id)}
|
||||
<li class="mais"><span>{g.nome}</span><b class="mono">{vezes(g.peso)}</b></li>
|
||||
{:else}
|
||||
<li class="nada">Nada se destacou ainda</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="titulo">Faz menos</li>
|
||||
{#each faz_menos as g (g.id)}
|
||||
<li class="menos"><span>{g.nome}</span><b class="mono">{vezes(g.peso)}</b></li>
|
||||
{:else}
|
||||
<li class="nada">Nada foi ignorado</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="bloco" aria-labelledby="ap-licoes">
|
||||
<h3 id="ap-licoes">Lições</h3>
|
||||
{#if a.licoes.length}
|
||||
<ol class="licoes">
|
||||
{#each a.licoes.slice(0, 5) as l (l.dia + l.texto)}
|
||||
<li><time class="mono">{dia(l.dia)}</time> <span>{l.texto}</span></li>
|
||||
{/each}
|
||||
</ol>
|
||||
{:else}
|
||||
<p class="nota">A primeira lição chega à meia-noite. Faça carinho ou converse logo depois de um gesto que você gostou.</p>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<div class="esquecer">
|
||||
{#if confirmar}
|
||||
<span>Apagar rotina, gostos e lições?</span>
|
||||
<button class="btn pequeno perigo" onclick={() => { confirmar = false; esquecerAprendizado(); }}>Esquecer tudo</button>
|
||||
<button class="btn fantasma pequeno" onclick={() => (confirmar = false)}>Cancelar</button>
|
||||
{:else}
|
||||
<button class="btn fantasma pequeno" onclick={() => (confirmar = true)} disabled={!a.dias && !a.gosto.length && !a.licoes.length}>
|
||||
Esquecer o que aprendeu
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Grupo>
|
||||
|
||||
<style>
|
||||
.vazio,
|
||||
.nota {
|
||||
margin: 0;
|
||||
color: var(--texto-3);
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
.vazio {
|
||||
padding: var(--e-3) var(--e-4);
|
||||
}
|
||||
.bloco {
|
||||
display: grid;
|
||||
gap: var(--e-2);
|
||||
padding: var(--e-3) var(--e-4);
|
||||
border-top: 1px solid var(--borda);
|
||||
}
|
||||
h3 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: var(--e-2);
|
||||
margin: 0;
|
||||
font-size: var(--t-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--texto-3);
|
||||
}
|
||||
h3 span {
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
.rotina {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(24, minmax(0, 1fr));
|
||||
align-items: end;
|
||||
gap: 2px;
|
||||
height: 44px;
|
||||
}
|
||||
.hora {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
background: var(--superficie-2);
|
||||
}
|
||||
.barra {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(var(--v) * 100%);
|
||||
min-height: 2px;
|
||||
border-radius: 3px;
|
||||
background: #30b0c7;
|
||||
transition: height 600ms ease;
|
||||
}
|
||||
.sem .barra {
|
||||
background: transparent;
|
||||
}
|
||||
.agora {
|
||||
outline: 1.5px solid var(--texto-2);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.lua {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
color: #5e5ce6;
|
||||
}
|
||||
.eixo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--texto-3);
|
||||
font-size: 10px;
|
||||
}
|
||||
.colunas {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
gap: var(--e-2) var(--e-4);
|
||||
}
|
||||
.colunas ul {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
font-size: var(--t-sm);
|
||||
}
|
||||
.colunas li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--e-2);
|
||||
color: var(--texto-2);
|
||||
}
|
||||
.colunas b {
|
||||
font-weight: 600;
|
||||
font-size: var(--t-xs);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.mais b {
|
||||
color: var(--ok, #34c759);
|
||||
}
|
||||
.menos b {
|
||||
color: var(--texto-3);
|
||||
}
|
||||
.colunas .titulo {
|
||||
color: var(--texto-3);
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
.nada {
|
||||
color: var(--texto-3);
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
.licoes {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
font-size: var(--t-sm);
|
||||
}
|
||||
.licoes li {
|
||||
display: flex;
|
||||
gap: var(--e-2);
|
||||
color: var(--texto);
|
||||
}
|
||||
.licoes time {
|
||||
flex: none;
|
||||
padding-top: 2px;
|
||||
color: var(--texto-3);
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
.esquecer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--e-2);
|
||||
padding: var(--e-2) var(--e-3) var(--e-3);
|
||||
border-top: 1px solid var(--borda);
|
||||
font-size: var(--t-sm);
|
||||
color: var(--texto-2);
|
||||
}
|
||||
.esquecer span {
|
||||
padding-left: var(--e-1);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.barra {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -296,6 +296,9 @@
|
||||
.diario li.emoji {
|
||||
color: #af52de;
|
||||
}
|
||||
.diario li.aprendizado {
|
||||
color: #30b0c7;
|
||||
}
|
||||
.diario li.cuidado {
|
||||
color: var(--texto);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { ErroApi, pedir, quandoNegado, urlEventos } from './api';
|
||||
import { avisos } from './avisos.svelte';
|
||||
import type { AppConfig, Conversa, EstadoServidor, IA, Motores, Reacao, Telemetria, Vida } from './tipos';
|
||||
import type { Aprendizado, AppConfig, Conversa, EstadoServidor, IA, Motores, Reacao, Telemetria, Vida } from './tipos';
|
||||
|
||||
export type Conexao = 'conectando' | 'aberta' | 'caiu' | 'token';
|
||||
|
||||
@@ -34,6 +34,7 @@ class Estado {
|
||||
treinamento = $state({ ocupado: false, erro: '' });
|
||||
api = $state<EstadoServidor['api'] | null>(null);
|
||||
vida = $state<Vida | null>(null);
|
||||
aprendizado = $state<Aprendizado | null>(null);
|
||||
feed = $state<ItemFeed[]>([]);
|
||||
/** Sensor PIR da placa: último disparo (s, epoch) e se está ligado. */
|
||||
pir = $state<{ disponivel: boolean; ultimo: number | null; ativo?: boolean; presenca?: { estado: 'presente' | 'ausente'; desde: number; ultimo: number } } | null>(null);
|
||||
@@ -110,6 +111,7 @@ function aplicarFoto(f: EstadoServidor) {
|
||||
if (f.treinamento) estado.treinamento = f.treinamento;
|
||||
if (f.api) estado.api = f.api;
|
||||
if (f.vida) estado.vida = f.vida;
|
||||
if (f.aprendizado) estado.aprendizado = f.aprendizado;
|
||||
if (f.pir) estado.pir = { disponivel: f.pir.disponivel, ultimo: f.pir.ultimo, ativo: (f.pir as any).ativo, presenca: (f.pir as any).presenca };
|
||||
}
|
||||
|
||||
@@ -158,6 +160,7 @@ const tratadores: Record<string, (d: any) => void> = {
|
||||
if (novo && novo.horario !== estado.vida?.diario[0]?.horario && novo.tipo !== 'acao') noFeed('vida', d.emoji + ' ' + novo.texto);
|
||||
estado.vida = d;
|
||||
},
|
||||
aprendizado: (d: Aprendizado) => (estado.aprendizado = d),
|
||||
pir: (d: { ultimo: number; ativo?: boolean; presenca?: any }) => (estado.pir = { disponivel: true, ultimo: d.ultimo, ativo: d.ativo, presenca: d.presenca }),
|
||||
historico: (d) => {
|
||||
const n = d?.novo;
|
||||
@@ -185,7 +188,7 @@ function abrirEventos() {
|
||||
if (estado.conexao !== 'token') estado.conexao = 'caiu';
|
||||
};
|
||||
for (const tipo of ['expressao', 'telemetria', 'app_config', 'aparencia', 'motores', 'estop', 'conversa', 'ia',
|
||||
'reacao', 'treinamento', 'historico', 'pir', 'vida', 'criacao', 'inspiracao']) {
|
||||
'reacao', 'treinamento', 'historico', 'pir', 'vida', 'aprendizado', 'criacao', 'inspiracao']) {
|
||||
fonte.addEventListener(tipo, (e) => {
|
||||
const dados = JSON.parse((e as MessageEvent).data);
|
||||
tratadores[tipo]?.(dados);
|
||||
|
||||
+16
-1
@@ -55,6 +55,21 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
||||
],
|
||||
dica: 'Desligar o modo vida volta o celular aos gestos soltos de sempre e à sensibilidade dos Ajustes.',
|
||||
},
|
||||
aprendizado: {
|
||||
titulo: 'O que ele aprendeu',
|
||||
emoji: '🎓',
|
||||
onde: 'Ao vivo › O que ele aprendeu (com o modo vida ligado)',
|
||||
resumo:
|
||||
'Ele observa o próprio dia e melhora aos poucos, sem IA e sem internet: conta o que acontece em casa e ajusta o modo vida. À meia-noite fecha o dia, escreve as lições no diário e o que ficou para trás passa a pesar menos, porque o gosto das pessoas muda.',
|
||||
itens: [
|
||||
{ emoji: '❤️', nome: 'Do que vocês gostam', texto: 'Cada gesto ou atividade que ele faz com alguém por perto é uma tentativa. Carinho, brincadeira ou conversa até 1 minuto depois contam como "gostaram" (cutucada conta meio). Os que agradam passam a sair até 2 vezes mais; os ignorados, até a metade. Precisa de pelo menos 3 tentativas para ele formar opinião.' },
|
||||
{ emoji: '🏠', nome: 'Rotina da casa', texto: 'Em que horas costuma ter gente por perto (pelo PIR e pela câmera). As 24 barras mostram cada hora. Quando a próxima hora costuma ser movimentada e a atual vazia, ele acorda do cochilo 15 minutos antes para estar de pé quando você chegar. Sono pesado e madrugada continuam respeitados.' },
|
||||
{ emoji: '☾', nome: 'Acordar à toa', texto: 'Se o PIR acorda o rosto e ninguém fica nem interage em 3 minutos, foi à toa. Quando isso se repete na mesma hora em dias diferentes, ali ele passa a precisar de 1 ou 2 disparos a mais para acordar.' },
|
||||
{ emoji: '📖', nome: 'Lições', texto: 'O que ele concluiu em cada dia, em primeira pessoa. Também aparecem no diário do cartão Vida.' },
|
||||
{ emoji: '🧽', nome: 'Esquecer', texto: 'Apaga rotina, gostos e lições, e ele começa do zero. Desligar o aprendizado só para de usar o que aprendeu, sem apagar.' },
|
||||
],
|
||||
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.',
|
||||
},
|
||||
cuidados: {
|
||||
titulo: 'Cuidar do Carinha',
|
||||
emoji: '❤️',
|
||||
@@ -334,7 +349,7 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
||||
|
||||
/** Ordem do tutorial (página Guia). */
|
||||
export const ORDEM_GUIA = [
|
||||
'inicio', 'vida', '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',
|
||||
'reacoes', 'conversa', 'movimento', 'atalhos',
|
||||
];
|
||||
|
||||
+17
-1
@@ -83,6 +83,7 @@ export interface EstadoServidor {
|
||||
api?: { provedor: string; modelo: string; configurada: boolean; ultimo_teste: unknown };
|
||||
pir?: { disponivel: boolean; erro: string; linha: string; ultimo: number | null };
|
||||
vida?: Vida;
|
||||
aprendizado?: Aprendizado;
|
||||
}
|
||||
|
||||
/** Modo vida (/vida/status e SSE 'vida'): necessidades de 0 a 100, 100 = satisfeito. */
|
||||
@@ -103,5 +104,20 @@ export interface Vida {
|
||||
sono_profundo?: boolean;
|
||||
/** '' quando pode inventar figurinha agora; senão, o motivo. */
|
||||
pode_criar: string;
|
||||
diario: { horario: number; tipo: 'acao' | 'sono' | 'emoji' | 'cuidado' | 'vida' | 'atividade'; texto: string }[];
|
||||
diario: { horario: number; tipo: 'acao' | 'sono' | 'emoji' | 'cuidado' | 'vida' | 'atividade' | 'aprendizado' | 'aprendi'; texto: string }[];
|
||||
}
|
||||
|
||||
/** O que ele aprendeu com o dia a dia (aprendizado.py). */
|
||||
export interface Aprendizado {
|
||||
config: { ligado: boolean };
|
||||
/** Dias inteiros observados. */
|
||||
dias: number;
|
||||
/** 24 horas: chance de ter gente por perto (null = ainda sem dados nessa hora). */
|
||||
rotina: (number | null)[];
|
||||
/** 24 horas: disparos a mais do PIR para acordar (0 a 2), por ter acordado à toa ali. */
|
||||
sono: number[];
|
||||
/** Atos com opinião formada, do mais querido ao mais ignorado. peso multiplica a chance (0,5 a 2). */
|
||||
gosto: { id: string; nome: string; tentativas: number; agradou: number; peso: number }[];
|
||||
licoes: { dia: string; texto: string }[];
|
||||
hoje: { dia: string; atos: number; respostas: Record<string, number>; acordou: number; a_toa: number };
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import PresencaPir from '../lib/componentes/PresencaPir.svelte';
|
||||
import RostoPrevia from '../lib/componentes/RostoPrevia.svelte';
|
||||
import VidaCartao from '../lib/componentes/VidaCartao.svelte';
|
||||
import AprendizadoCartao from '../lib/componentes/AprendizadoCartao.svelte';
|
||||
import { estado } from '../lib/estado.svelte';
|
||||
import { Rastreamento } from '../lib/rastreamento.svelte';
|
||||
import { VideoAoVivo } from '../lib/video.svelte';
|
||||
@@ -126,6 +127,7 @@ const pirAtivo = $derived(
|
||||
|
||||
<div class="lista">
|
||||
<VidaCartao />
|
||||
{#if estado.vida?.config.ligado}<AprendizadoCartao />{/if}
|
||||
|
||||
<Grupo titulo="Celular" ajuda="celular">
|
||||
<Celular />
|
||||
|
||||
Reference in New Issue
Block a user