# 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` | 32–40 | Estado da chamada na tela do visitante | | `headline` | 24–28 | Título de tela | | `title` | 18–20 | 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 é.