feat(web): guia do painel com botão ⓘ em cada seção
Um ⓘ ao lado do título de cada seção (Ao vivo, Vida, Eventos, Ajustes, Reações, Conversa, Movimento) abre a explicação daquela parte: o que faz cada opção, onde fica e uma dica. No cartão Vida também há ⓘ para humores, cuidados e ritmo. O ⓘ do topo explica a tela atual. A página Guia (#/guia) junta todos os 21 tópicos em ordem de tutorial, com índice e busca; cada tópico também aparece no Ctrl+K e há atalho em Ajustes › Sistema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { BookOpen, Info } from '@lucide/svelte';
|
||||
import { TOPICOS } from '../guia';
|
||||
import Modal from './Modal.svelte';
|
||||
import TopicoGuia from './TopicoGuia.svelte';
|
||||
|
||||
// Botão ⓘ: abre a explicação de um tópico do guia num balão, com atalho para o guia completo.
|
||||
let { topico, tamanho = 15 }: { topico: string; tamanho?: number } = $props();
|
||||
const t = $derived(TOPICOS[topico]);
|
||||
let aberto = $state(false);
|
||||
</script>
|
||||
|
||||
{#if t}
|
||||
<button
|
||||
type="button"
|
||||
class="ajuda"
|
||||
aria-label={'O que é: ' + t.titulo}
|
||||
title="O que é isto?"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
aberto = true;
|
||||
}}
|
||||
>
|
||||
<Info size={tamanho} />
|
||||
</button>
|
||||
<Modal bind:aberto titulo={t.emoji + ' ' + t.titulo}>
|
||||
<TopicoGuia topico={t} />
|
||||
<a class="btn pequeno guia" href={'#/guia?t=' + topico} onclick={() => (aberto = false)}>
|
||||
<BookOpen size={14} /> Ver o guia completo
|
||||
</a>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.ajuda {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--texto-3);
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.ajuda:hover {
|
||||
background: var(--superficie-2);
|
||||
color: var(--destaque);
|
||||
}
|
||||
.ajuda:focus-visible {
|
||||
outline: 2px solid var(--destaque);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.guia {
|
||||
justify-self: start;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
let { titulo = '', descricao = '', acoes, children, class: classe = '' }: {
|
||||
import Ajuda from './Ajuda.svelte';
|
||||
let { titulo = '', descricao = '', ajuda = '', acoes, children, class: classe = '' }: {
|
||||
titulo?: string;
|
||||
descricao?: string;
|
||||
/** Tópico do guia: mostra um ⓘ ao lado do título. */
|
||||
ajuda?: string;
|
||||
acoes?: Snippet;
|
||||
children: Snippet;
|
||||
class?: string;
|
||||
@@ -13,7 +16,7 @@
|
||||
{#if titulo || acoes}
|
||||
<header>
|
||||
<div>
|
||||
{#if titulo}<h2>{titulo}</h2>{/if}
|
||||
{#if titulo}<h2>{titulo}{#if ajuda}<Ajuda topico={ajuda} />{/if}</h2>{/if}
|
||||
{#if descricao}<p class="fraco pequeno-texto">{descricao}</p>{/if}
|
||||
</div>
|
||||
{#if acoes}<div class="linha">{@render acoes()}</div>{/if}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import Ajuda from './Ajuda.svelte';
|
||||
// Lista agrupada no estilo dos Ajustes do iPhone: título pequeno, cartão arredondado, rodapé de ajuda.
|
||||
let { titulo = '', rodape = '', children }: { titulo?: string; rodape?: string; children: Snippet } = $props();
|
||||
// `ajuda` = tópico do guia; aparece um ⓘ ao lado do título.
|
||||
let { titulo = '', rodape = '', ajuda = '', children }: { titulo?: string; rodape?: string; ajuda?: string; children: Snippet } =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<section class="grupo">
|
||||
{#if titulo}<h2>{titulo}</h2>{/if}
|
||||
{#if titulo}<h2>{titulo}{#if ajuda}<Ajuda topico={ajuda} />{/if}</h2>{/if}
|
||||
<div class="itens">{@render children()}</div>
|
||||
{#if rodape}<p class="rodape">{rodape}</p>{/if}
|
||||
</section>
|
||||
@@ -17,6 +20,10 @@
|
||||
min-width: 0;
|
||||
}
|
||||
h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-height: 26px;
|
||||
padding: 0 var(--e-4);
|
||||
font-size: var(--t-xs);
|
||||
font-weight: 600;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import type { TopicoGuia } from '../guia';
|
||||
// Conteúdo de um tópico do guia (no balão do ⓘ e na página Guia).
|
||||
let { topico }: { topico: TopicoGuia } = $props();
|
||||
</script>
|
||||
|
||||
<div class="topico">
|
||||
<p class="onde">{topico.onde}</p>
|
||||
<p class="resumo">{topico.resumo}</p>
|
||||
{#if topico.itens?.length}
|
||||
<dl>
|
||||
{#each topico.itens as item (item.nome)}
|
||||
<div class="item">
|
||||
<dt>
|
||||
{#if item.emoji}<span class="emoji" aria-hidden="true">{item.emoji}</span>{/if}
|
||||
{item.nome}
|
||||
</dt>
|
||||
<dd>{item.texto}</dd>
|
||||
</div>
|
||||
{/each}
|
||||
</dl>
|
||||
{/if}
|
||||
{#if topico.dica}<p class="dica"><strong>Dica:</strong> {topico.dica}</p>{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.topico {
|
||||
display: grid;
|
||||
gap: var(--e-3);
|
||||
}
|
||||
.onde {
|
||||
font-size: var(--t-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--texto-3);
|
||||
}
|
||||
.resumo {
|
||||
color: var(--texto);
|
||||
line-height: 1.5;
|
||||
max-width: 65ch;
|
||||
}
|
||||
dl {
|
||||
display: grid;
|
||||
gap: var(--e-3);
|
||||
margin: 0;
|
||||
}
|
||||
.item {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
dt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--e-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
.emoji {
|
||||
width: 1.4em;
|
||||
text-align: center;
|
||||
}
|
||||
dd {
|
||||
margin: 0 0 0 calc(1.4em + var(--e-2));
|
||||
color: var(--texto-2);
|
||||
font-size: var(--t-sm);
|
||||
line-height: 1.45;
|
||||
max-width: 65ch;
|
||||
}
|
||||
.dica {
|
||||
padding: var(--e-2) var(--e-3);
|
||||
border-radius: var(--raio-sm);
|
||||
background: var(--destaque-suave);
|
||||
font-size: var(--t-sm);
|
||||
}
|
||||
</style>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Gauge, Moon, Sparkles } from '@lucide/svelte';
|
||||
import { estado } from '../estado.svelte';
|
||||
import { configurarVida, cuidar, CUIDADOS, NECESSIDADES } from '../vida.svelte';
|
||||
import Ajuda from './Ajuda.svelte';
|
||||
import Grupo from './Grupo.svelte';
|
||||
import Interruptor from './Interruptor.svelte';
|
||||
import Linha from './Linha.svelte';
|
||||
@@ -10,11 +11,11 @@
|
||||
const v = $derived(estado.vida);
|
||||
let diarioAberto = $state(false);
|
||||
|
||||
// O ritmo também é a sensibilidade dos sensores no celular.
|
||||
// O ritmo também é a sensibilidade dos sensores no celular (detalhes no ⓘ).
|
||||
const RITMOS = [
|
||||
{ valor: 'calmo', nome: 'Calmo', descricao: 'Age pouco; só reage e acorda com vários sons ou movimentos seguidos' },
|
||||
{ valor: 'normal', nome: 'Normal', descricao: 'Reage a barulho alto e movimento forte' },
|
||||
{ valor: 'agitado', nome: 'Agitado', descricao: 'Age bastante e reage a qualquer som ou movimento' },
|
||||
{ valor: 'calmo', nome: 'Calmo', descricao: 'Reage pouco' },
|
||||
{ valor: 'normal', nome: 'Normal', descricao: 'Equilibrado' },
|
||||
{ valor: 'agitado', nome: 'Agitado', descricao: 'Reage a tudo' },
|
||||
] as const;
|
||||
|
||||
const MOTIVOS: Record<string, string> = {
|
||||
@@ -44,6 +45,7 @@
|
||||
|
||||
<Grupo
|
||||
titulo="Vida"
|
||||
ajuda="vida"
|
||||
rodape={v?.config.ligado
|
||||
? 'Ele decide sozinho o que fazer pelo humor. Tocar ou deslizar o dedo na tela do celular também conta como carinho.'
|
||||
: 'Ligado, ele tem necessidades, humor, cochilos e inventa figurinhas, como um bichinho virtual.'}
|
||||
@@ -54,7 +56,7 @@
|
||||
<div class="bicho" class:dormindo={v.dormindo}>
|
||||
<span class="humor" aria-hidden="true">{v.config.ligado ? v.emoji : '🤖'}</span>
|
||||
<div class="quem">
|
||||
<strong>{v.config.ligado ? v.nome_humor : 'Modo autônomo desligado'}</strong>
|
||||
<strong>{v.config.ligado ? v.nome_humor : 'Modo autônomo desligado'}{#if v.config.ligado}<Ajuda topico="humores" />{/if}</strong>
|
||||
<span>{idade(v.idade_dias)}{v.config.ligado && v.diario[0] ? ' · ' + v.diario[0].texto : ''}</span>
|
||||
</div>
|
||||
<Interruptor rotulo="Modo autônomo" checked={v.config.ligado} onchange={(l) => configurarVida({ ligado: l })} />
|
||||
@@ -82,6 +84,7 @@
|
||||
</div>
|
||||
|
||||
<div class="cuidados">
|
||||
<span class="cuidar">Cuidar <Ajuda topico="cuidados" /></span>
|
||||
{#each CUIDADOS as c (c.acao)}
|
||||
<button class="btn pequeno" onclick={() => cuidar(c.acao)}><span aria-hidden="true">{c.emoji}</span> {c.nome}</button>
|
||||
{/each}
|
||||
@@ -99,6 +102,7 @@
|
||||
</Linha>
|
||||
<Linha icone={Gauge} cor="#ff9500" rotulo="Ritmo" detalhe={RITMOS.find((r) => r.valor === v.config.ritmo)?.descricao ?? ''}>
|
||||
{#snippet acessorio()}
|
||||
<Ajuda topico="ritmo" />
|
||||
<span class="segmentos" role="radiogroup" aria-label="Ritmo">
|
||||
{#each RITMOS as r (r.valor)}
|
||||
<button
|
||||
@@ -173,8 +177,23 @@
|
||||
min-width: 0;
|
||||
}
|
||||
.quem strong {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
font-size: var(--t-md);
|
||||
}
|
||||
.cuidar {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-bottom: -4px;
|
||||
font-size: var(--t-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--texto-3);
|
||||
}
|
||||
.quem span {
|
||||
color: var(--texto-3);
|
||||
font-size: var(--t-xs);
|
||||
|
||||
Reference in New Issue
Block a user