Files
Projeto-Portaria/docs/04-DESIGN-SYSTEM-UX.md
2026-07-22 15:55:55 -03:00

187 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 — Design system e UX
> Material 3 Expressive nos apps Compose; os mesmos tokens exportados como CSS custom properties nos webs. Uma linguagem visual, duas implementações.
## 1. Princípios
**1. O visitante está no sol, com pressa, e não vai instalar nada.** Cada tela dele tem uma ação. Tipografia grande, contraste alto, área de toque generosa.
**2. Espera precisa ter narração.** Um spinner de 35 segundos é indistinguível de um app quebrado. O visitante sempre lê o que está acontecendo: "Chamando o morador", "Tentando outros moradores", "Transferindo para a portaria".
**3. O morador decide em um toque.** A maior parte das decisões acontece na notificação, sem abrir o app.
**4. Densidade é para o admin, não para o campo.** Painel administrativo pode ter tabela densa e filtro complexo. Tela de portaria, nunca.
## 2. Tokens
Fonte única em `web/shared-ui/tokens.json`, gerada para os dois mundos no build:
```
tokens.json ──► tokens.css (custom properties) → web/visitor, web/admin
└─► Theme.kt (Material 3 Color) → apps Compose
```
### Cor
```
brand/primary #2563EB ações principais, foco
brand/on-primary #FFFFFF
semantic/success #15803D autorizado, dentro do geofence
semantic/danger #B91C1C negado, expirado
semantic/warning #B45309 fora do geofence, degradado
semantic/info #0369A1 fila, estado transitório
neutral/0..1000 escala de superfície e texto
```
**Contraste mínimo 4.5:1 em tema claro e escuro** — verificado em CI, não no olho. A tela do visitante frequentemente é lida sob sol direto, onde o contraste efetivo despenca; por isso ela usa apenas os extremos da escala neutra, nunca tons médios.
Decisão nunca depende só de cor: autorizado é **verde + ícone de check + a palavra "Autorizado"**. Daltonismo e sol forte quebram codificação puramente cromática.
### Tipografia
`Inter` (web) / `Roboto Flex` (Compose), variáveis, com fallback de sistema.
| Papel | Tamanho | Uso |
|---|---|---|
| `display` | 3240 | Estado da chamada na tela do visitante |
| `headline` | 2428 | Título de tela |
| `title` | 1820 | Nome do morador, unidade |
| `body` | 16 | **Mínimo absoluto no fluxo do visitante** |
| `label` | 14 | Rótulos de admin |
| `caption` | 12 | Só metadados no admin. Nunca na portaria |
### Espaço, raio, elevação
Escala 4px (`0,1,2,3,4,6,8,12,16,24` × 4px). Raios: `sm 8` · `md 12` · `lg 16` · `full 999`. Elevação por sombra suave no web e `tonalElevation` no Compose — nunca sombra dura.
**Alvo de toque mínimo 48×48dp** em todo o fluxo do visitante, sem exceção. Mão trêmula, luva de entregador, celular escorregando.
## 3. Telas do visitante
Cinco telas, uma ação cada.
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Res. Bela Vista │ │ ┌───────────┐ │ │ Quem procura? │
│ │ │ │ câmera │ │ │ │
│ Este acesso │ │ │ frontal │ │ │ Seu nome │
│ registra sua │ │ └───────────┘ │ │ [____________] │
│ imagem e │ │ │ │ │
│ localização │ │ Centralize o │ │ Bloco Unidade │
│ para segurança. │ │ rosto │ │ [__] [_____] │
│ │ │ │ │ │
│ [Como funciona] │ │ ( ◎ tirar ) │ │ [ Continuar ] │
│ [ Continuar ] │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
1. Aviso LGPD 3. Foto 4. Destino
(2 = permissões, (após permissão) (busca cega)
nativa do browser)
┌─────────────────┐ ┌─────────────────┐
│ ◐ │ │ ✓ │
│ │ │ │
│ Chamando o │ │ Autorizado │
│ morador... │ │ │
│ │ │ Mostre o código│
│ ▓▓▓▓▓▓░░░░ 12s │ │ │
│ │ │ 4 8 2 9 1 7 │
│ Aguarde na │ │ │
│ entrada │ │ válido 4:52 │
│ │ │ │
│ [ Cancelar ] │ │ [QR grande] │
└─────────────────┘ └─────────────────┘
5. Espera narrada 6. Resultado
```
**Tela 1** não é um modal de consentimento com checkbox. A base legal é legítimo interesse — o que se deve é **informar com clareza**, não colher aceite. Ver `06-LGPD-E-SEGURANCA.md`.
**Tela 5** troca o texto conforme o estado real (`TOCANDO``ESCALONADA``FILA_OPERADOR`), com barra de progresso ligada ao tempo restante. É a tela onde o produto se ganha ou se perde.
### Entrega — dois toques a menos
```
┌─────────────────┐
│ O que você faz │
│ aqui hoje? │
│ │
│ ┌─────────────┐ │
│ │ 📦 Entrega │ │ ← primeiro, é a maioria
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ 👤 Visita │ │
│ └─────────────┘ │
└─────────────────┘
```
Entrega pula a foto do rosto (fotografa o pacote) e vai direto ao destino. Meta de 10s do QR à resposta.
## 4. App do morador
### A notificação é a interface principal
```
Android — CallStyle iOS — CallKit (tela cheia)
┌──────────────────────────┐ ┌──────────────────────────┐
│ 📦 Entrega · Apto 101-A │ │ Portaria │
│ [foto do pacote] │ │ Entrega · Apto 101-A │
│ │ │ │
│ [Autorizar] [Chamar] │ │ [Recusar] [Aceitar] │
└──────────────────────────┘ └──────────────────────────┘
```
No iOS, chamada de **visita** usa PushKit + CallKit e ocupa a tela como ligação telefônica — o único caminho para tocar de verdade com o app fechado. **Entrega** usa notificação comum com ações, porque CallKit para uma entrega seria intrusivo e a Apple pode reprovar o uso.
### Tela de chamada
Vídeo do visitante em tela cheia; auto-preview pequeno no canto. Sobreposto: nome informado, unidade de destino, e **selo de geofence**`✓ Na entrada` (verde) ou `⚠ A 340m da portaria` (âmbar). Esse selo é a informação de segurança mais útil da tela: alguém tentando entrar de longe é sinal claro.
Ações: `Autorizar` (verde, primária), `Negar` (contorno vermelho), `Mudo`, `Encerrar`.
## 5. App do operador
Densidade maior, feito para várias visitas em paralelo. Fila à esquerda com foto, unidade, tempo de espera e por que escalonou; contexto à direita antes de assumir — histórico da unidade, visitas recentes, regra de entrega. **O operador nunca atende sem contexto.**
## 6. Painel admin
Layout de aplicação: navegação lateral, conteúdo denso, filtros persistentes na URL.
A tela mais importante é a **auditoria de visitas**: tabela virtualizada, filtro por período/unidade/estado/tipo/geofence, e detalhe com foto, mapa (Leaflet) do ponto do visitante contra o raio do portão, timeline de tentativas (`visit_attempts`), e player do recado. Todo acesso a mídia gera linha em `audit_log` e usa URL assinada de 5 minutos.
## 7. Movimento
| Transição | Duração | Curva |
|---|---|---|
| Troca de tela do visitante | 280ms | `emphasized` |
| Estado de chamada | 200ms | `standard` |
| Entrada de card na fila | 180ms | `decelerate` |
| Feedback de toque | 100ms | `linear` |
Compose usa `spring(dampingRatio = 0.8f)`; web usa `cubic-bezier(0.2, 0, 0, 1)`.
**`prefers-reduced-motion` respeitado em todos os webs** e `Settings.Global.ANIMATOR_DURATION_SCALE` no Android. Pulso e transição viram fade.
## 8. Acessibilidade
- Contraste ≥ 4.5:1 verificado em CI, claro e escuro
- Alvos ≥ 48×48dp no fluxo do visitante
- Rótulos semânticos em todo controle (`contentDescription` / `aria-label`)
- Foco visível e ordem de tabulação correta no web
- Suporte a fonte ampliada até 200% sem quebra de layout
- Nenhuma informação transmitida só por cor
- `lang="pt-BR"` e textos prontos para i18n desde o início (visitante estrangeiro é caso real)
## 9. Estados vazios, de erro e degradado
Todo erro exibido ao visitante vem do campo `detail` do `problem+json`, em português e acionável: **"Aproxime-se da entrada e tente novamente"**, nunca "OUTSIDE_GEOFENCE".
Modo degradado é comunicado, não escondido:
```
┌──────────────────────────────┐
│ ⚠ Vídeo indisponível │
│ Continuando por áudio. │
└──────────────────────────────┘
```
Esconder a degradação faz o usuário achar que o produto está quebrado. Nomear a degradação faz o produto parecer resiliente — que é o que ele é.