Competições Senac RS · Seletiva 26

Módulo 05 · Apostila teórica

Módulo 05 — Front-end web

BaseUC12 (Desenvolver front-end web, 96h)Peso na prova40% (o maior de todos)NaturezaRevisão
Ocupação: Desenvolvimento de Sistemas Treinador: Diogo Roehrs
💡Nota

Por que esse é o módulo mais gordo. O front vale 40% da nota, mais que back e banco somados. É o Módulo C da prova (4 horas, o mais longo). É a parte que a banca vê funcionando, e "o que a banca vê" é metade do jogo (lembra do módulo 01). Aqui é onde 2 vira 3 ou vira 1. Natureza 🔄 revisão, as duas já fizeram front na Regional, mas é aqui que a gente coloca mais energia de treino, porque é onde tem mais nota pra ganhar e mais pra perder. O combinado do módulo: uma stack só no front (HTML + CSS + JavaScript puro, sem framework), porque é o que a prova permite com segurança e o que dá controle total sob pressão. Todo código desse módulo conversa com a API que vocês construíram no módulo 04: mesmas rotas, mesmo token, mesmo formato de erro.


1. As três camadas e a organização do projeto

Todo front é feito de três linguagens com papéis distintos. Misturar os papéis é a origem de metade da bagunça:

🛠️Gambiarra boa

🛠️ Gambiarra boa: separação de responsabilidades. Estrutura no HTML, estilo no CSS, lógica no JS. Não jogar estilo dentro do HTML (style= no meio da tag) nem lógica embolada com aparência. Código separado é código que a banca lê fácil (ponto de julgamento) e que você conserta rápido sob pressão.

1.1 A pasta do front

A separação de papéis vira separação de arquivos. O projeto do Módulo C nasce assim nos primeiros minutos:

front/
├── index.html          ← login (a porta de entrada)
├── convidados.html     ← lista + cadastro
├── checkin.html        ← a tela da recepção
├── dashboard.html      ← indicadores da gestão
├── css/
│   └── style.css       ← um CSS só, compartilhado por todas as páginas
└── js/
    ├── api.js          ← comunicação com a API (seção 5.6)
    ├── auth.js         ← token, guarda de página, perfil (seção 8)
    ├── convidados.js   ← lógica da tela de convidados
    ├── checkin.js
    └── dashboard.js

Decisões embutidas aí:

Os scripts entram no fim com defer, que garante que o HTML já existe quando o JS rodar:

<script src="js/api.js" defer></script>
<script src="js/auth.js" defer></script>
<script src="js/convidados.js" defer></script>
⚠️Pega-ratão

⚠️ Pega-ratão: script no <head> sem defer roda antes do HTML existir. Aí o querySelector devolve null e o console grita Cannot read properties of null. É um dos erros mais comuns de front e se resolve com uma palavra.


2. HTML semântico: estrutura que significa algo

HTML semântico é usar a tag certa pro sentido certo, em vez de div pra tudo. Um cabeçalho é header, uma navegação é nav, o conteúdo principal é main, um botão é button.

Por que importa:

2.1 O esqueleto de toda página

Esse bloco abre todo HTML da prova. Vale digitar de memória:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Wedding Pass · Convidados</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <header class="navbar"> ... </header>
  <main> ... </main>
  <script src="js/api.js" defer></script>
</body>
</html>
⚠️Pega-ratão

⚠️ Pega-ratão clássico da prova: tela "responsiva" que funciona no DevTools do notebook e quebra no celular de verdade. Causa número 1: esqueceu a meta viewport. É a segunda linha do <head>, sempre.

2.2 As tags que a prova usa

Não precisa decorar a especificação inteira. Essa tabela cobre o Wedding Pass:

Tag Pra quê Onde aparece na prova
header topo da página ou de uma seção barra superior com logo e navegação
nav bloco de navegação menu entre as telas
main conteúdo principal (um por página) tudo que não é topo nem rodapé
section agrupamento temático com título bloco de indicadores, bloco de busca
article item independente e completo um card de convidado
footer rodapé créditos, versão
h1...h3 hierarquia de títulos (sem pular níveis) h1 o título da tela, h2 as seções
form, label, input, select, button entrada de dados cadastro, login, busca
table, thead, tbody, th, td dados tabulares lista de convidados em tela larga
ul, li listas menu, lista de erros de validação
strong, span ênfase e gancho de estilo destaque de números, badges
⚠️Pega-ratão

⚠️ Pega-ratão: a "div soup". Página inteira feita de div genérica, botão que é um div com clique. Funciona visualmente, mas é frágil, inacessível e nível baixo de qualidade. Um div com onclick não recebe foco por Tab, não dispara com Enter e é invisível pra leitor de tela. button faz tudo isso de graça. div e span existem, mas são o último recurso (agrupar pra estilizar), não o primeiro.

2.3 O formulário bem feito

Formulário é onde marcação semântica vira nota mais rápido. O cadastro de convidado, do jeito certo:

<form id="form-convidado" novalidate>
  <div class="campo">
    <label for="nome">Nome completo</label>
    <input type="text" id="nome" name="nome" required minlength="3"
           placeholder="Maria da Silva">
    <p class="campo-erro" hidden></p>
  </div>

  <div class="campo">
    <label for="email">E-mail</label>
    <input type="email" id="email" name="email" placeholder="maria@email.com">
    <p class="campo-erro" hidden></p>
  </div>

  <div class="campo">
    <label for="telefone">Telefone</label>
    <input type="tel" id="telefone" name="telefone" placeholder="(51) 99999-9999">
    <p class="campo-erro" hidden></p>
  </div>

  <div class="campo">
    <label for="acompanhantes">Acompanhantes (máx. 5)</label>
    <input type="number" id="acompanhantes" name="acompanhantes" min="0" max="5" value="0">
    <p class="campo-erro" hidden></p>
  </div>

  <div class="acoes">
    <button type="button" class="botao-secundario" id="btn-cancelar">Cancelar</button>
    <button type="submit" class="botao-primario">Salvar convidado</button>
  </div>
</form>

O que esse bloco carrega:


3. CSS de verdade: do seletor ao layout

CSS é onde os 40% mais aparecem. A diferença entre uma tela nota 2 e nota 3 quase nunca é "sabia a propriedade"; é controle: saber por que a regra aplicou (ou não), espaçar com ritmo, alinhar sem gambiarra de margem.

3.1 Seletores, especificidade e cascata

Os seletores que resolvem a prova:

Seletor Pega o quê Exemplo de uso
.card classe o dia a dia: quase tudo
button elemento estilos de base (reset, fonte)
.card .badge descendente badge só quando está dentro de card
.card > h3 filho direto título imediato do card
input[type="email"] atributo estilizar tipos específicos
:hover, :focus, :active, :disabled pseudo-classes de estado botões e campos reagindo
:nth-child(even) posição zebrar linhas de tabela
:not(.ativo) negação todos menos o selecionado
::before, ::after pseudo-elementos ícones decorativos, setas
::placeholder o texto de exemplo do input placeholder mais suave

Quando duas regras disputam o mesmo elemento, ganha a mais específica. A ordem de força:

  1. style= inline (que a gente não usa)
  2. #id
  3. .classe, [atributo], :pseudo-classe
  4. elemento, ::pseudo-elemento

Empatou a especificidade? Ganha a que aparece por último no arquivo (a cascata).

🛠️Gambiarra boa

🛠️ Gambiarra boa: especificidade plana. Na prova, estilizar só com classes (#id fica pro JS achar elementos, não pro CSS). Com tudo no mesmo nível de força, a cascata fica previsível: a última regra ganha, ponto. Nunca usar !important: ele "resolve" agora e cobra depois, porque a próxima correção vai precisar de outro !important, e em 20 minutos o CSS vira um cabo de guerra. Se você sentiu necessidade de !important, tem uma regra mais específica escondida: acha ela no DevTools (aba Styles mostra riscado quem perdeu a disputa).

:focus não é opcional. Campo e botão precisam mostrar onde o teclado está:

button:focus-visible,
input:focus {
  outline: 2px solid var(--cor-acao);
  outline-offset: 2px;
}

Tirar o outline sem pôr nada no lugar (outline: none seco) é erro de acessibilidade que o módulo 06 pontua.

3.2 O modelo de caixa e a escala de espaçamento

Todo elemento é uma caixa: conteúdo, preenchimento interno (padding), borda e margem externa. O detalhe que muda tudo é como a largura é contada. No modo padrão (content-box), width: 300px + padding: 20px = caixa real de 340px, e seu layout estoura sem você entender por quê. A correção é a primeira regra de todo CSS:

/* reset mínimo de prova: as três linhas que abrem o style.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

Com border-box, width: 300px é 300px de verdade, padding e borda já contando. Fim das contas de padaria no meio da prova.

Espaçamento com ritmo. Espaço não se inventa a cada elemento; se escolhe de uma escala. A escala prática: múltiplos de 8px (com 4px pra ajustes finos): 4, 8, 16, 24, 32, 48. Todo padding, margin e gap da tela sai desse conjunto.

🎯Foco de prova

🎯 Espaçamento consistente é sinal de nível 3. Telas onde os espaços seguem um ritmo (sempre múltiplos de um valor base) parecem profissionais. Telas com espaçamento aleatório parecem amadoras, mesmo com as mesmas cores. A banca sente isso, mesmo sem saber nomear. Na seção 3.4 a escala vira variável e o ritmo vira automático.

3.3 Unidades: qual usar onde

Unidade O que é Usar em
px pixel fixo borda, sombra, detalhes finos
rem múltiplo da fonte raiz (16px por padrão) fonte, padding, margin, gap
em múltiplo da fonte do próprio elemento raramente; padding interno de botão que cresce com o texto
% proporção do pai larguras fluidas
vh / vw proporção da tela min-height: 100vh pra centralizar o login
fr fração do espaço livre (só no grid) colunas de grid

A regra de bolso: rem pra tamanhos que devem escalar junto (texto e espaçamento), px pro que é fino e fixo (borda de 1px é borda de 1px), % e fr pra layout. 1rem = 16px, 0.5rem = 8px, 1.5rem = 24px: a escala de espaçamento da seção 3.2 em rem.

Usar rem no texto respeita o usuário que aumentou a fonte do navegador. É acessibilidade de graça e é o padrão de mercado.

3.4 Custom properties: o design system em 20 linhas

Variáveis de CSS transformam decisões soltas em sistema. Declaradas no :root, valem na página inteira:

:root {
  /* cores: uma de ação, neutros, três de status */
  --cor-acao: #7c3aed;
  --cor-acao-hover: #6d28d9;
  --cor-texto: #1f2937;
  --cor-texto-suave: #6b7280;
  --cor-fundo: #f4f4f5;
  --cor-superficie: #ffffff;
  --cor-borda: #e4e4e7;
  --cor-sucesso: #16a34a;
  --cor-erro: #dc2626;
  --cor-alerta: #d97706;

  /* espaçamento: a escala da seção 3.2 */
  --espaco-1: 0.25rem;   /*  4px */
  --espaco-2: 0.5rem;    /*  8px */
  --espaco-3: 1rem;      /* 16px */
  --espaco-4: 1.5rem;    /* 24px */
  --espaco-5: 2rem;      /* 32px */
  --espaco-6: 3rem;      /* 48px */

  /* acabamento */
  --raio: 8px;
  --sombra: 0 1px 3px rgba(0, 0, 0, 0.12);
}

.botao-primario {
  background: var(--cor-acao);
  padding: var(--espaco-2) var(--espaco-4);
  border-radius: var(--raio);
}

O ganho é triplo:

  1. Consistência automática: todo botão, card e título bebe da mesma fonte. Não existe "esse verde é um pouco diferente daquele".
  2. Mudança global em um lugar: a banca achou o roxo escuro demais? Troca uma linha e a tela inteira acompanha. Sem variável, é caça ao #7c3aed em 400 linhas.
  3. Ponte com o módulo 06: os tokens de cor e espaço definidos no wireframe (design system mínimo) viram literalmente esse bloco :root. Papel vira código em 5 minutos.
🛠️Gambiarra boa

🛠️ Gambiarra boa: o :root é a primeira coisa que se escreve no Módulo C. Antes de qualquer tela: reset (3.2) + :root com tokens + estilos de base (fonte, botões, campos). Uns 15 minutos que compram consistência pelas 4 horas inteiras. E quando bater vontade de inventar uma cor nova no meio da prova, a pergunta é: "isso merece virar token?". Quase sempre a resposta é usar um que já existe.

3.5 Flexbox: alinhar num eixo

Flexbox distribui itens numa direção (linha ou coluna). É a ferramenta de todos os alinhamentos pequenos da tela:

.navbar {
  display: flex;
  justify-content: space-between;  /* logo pra um lado, menu pro outro */
  align-items: center;             /* tudo alinhado na vertical */
  padding: var(--espaco-3) var(--espaco-4);
}

As cinco propriedades que resolvem 95% dos casos:

No filho, uma vale ouro: flex: 1 (cresce e ocupa o espaço livre). Campo de busca que estica com botão fixo do lado:

.barra-busca { display: flex; gap: var(--espaco-2); }
.barra-busca input { flex: 1; }

Os flex do Wedding Pass: a navbar, a linha de botões do formulário (justify-content: flex-end), o topo do card (nome à esquerda, badge à direita), e o login centralizado na tela:

.tela-login {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}
🛠️Gambiarra boa

🛠️ Gambiarra boa: gap em vez de margem nos filhos. Margem em item de lista cria o clássico "espaço sobrando na ponta" que precisa de :last-child pra consertar. gap só existe entre os itens. Uma propriedade, zero caso especial.

3.6 Grid: linhas e colunas ao mesmo tempo

Grid organiza em duas dimensões. É a ferramenta do layout da página e das malhas de cards:

.malha-indicadores {
  display: grid;
  grid-template-columns: repeat(4, 1fr);  /* 4 colunas iguais */
  gap: var(--espaco-3);
}

O vocabulário essencial:

.layout {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-areas:
    "menu topo"
    "menu conteudo";
}
.menu     { grid-area: menu; }
.topo     { grid-area: topo; }
.conteudo { grid-area: conteudo; }

E a linha mais valiosa do grid pra prova:

.malha-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--espaco-3);
}

Tradução: "cria quantas colunas couberem, cada uma com no mínimo 220px, dividindo o resto igual". A malha de cards vira responsiva sem nenhuma media query: 4 colunas no monitor, 2 no tablet, 1 no celular, sozinha.

🛠️Gambiarra boa

🛠️ Gambiarra boa: decorar o repeat(auto-fit, minmax(220px, 1fr)) como uma frase. É um terço da responsividade da prova em uma linha.

3.7 Flexbox ou Grid? Os dois, cada um no seu lugar

A dúvida clássica tem resposta curta: flexbox é unidimensional (o conteúdo dita o tamanho, os itens se acomodam num eixo), grid é bidimensional (o layout dita a malha, o conteúdo entra nas células). Eles não competem; se compõem: grid desenha a página, flex alinha por dentro.

O mapa de decisão do Wedding Pass:

Situação Ferramenta Por quê
Navbar (logo + menu + usuário) flex uma linha, distribuir e alinhar
Linha de botões do form flex um eixo, alinhar à direita
Interior do card (nome + badge) flex um eixo
Centralizar o login na tela flex center nos dois eixos
Malha de indicadores do dashboard grid colunas e linhas de verdade
Página com menu lateral grid áreas nomeadas
Formulário em duas colunas grid campos alinhando em malha

Pra fixar a diferença, o mesmo dashboard resolvido nos dois. Com grid (a escolha certa):

.malha-indicadores {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--espaco-3);
}

Com flex (dá, mas se nota o remendo):

.malha-indicadores { display: flex; flex-wrap: wrap; gap: var(--espaco-3); }
.malha-indicadores .card { flex: 1 1 220px; }

O flex quebra linha, mas a última linha estica os cards que sobraram (3 cards numa linha de 4 ficam mais largos que os de cima, desalinhado com a malha). O grid mantém as colunas travadas. Regra de bolso final: se você quer que as linhas e colunas se alinhem entre si, é grid; se cada item pode ter seu tamanho, é flex.

🛠️Gambiarra boa

🛠️ Gambiarra boa: não sofrer com posicionamento antigo (float, margens negativas, position: absolute pra layout). Flexbox resolve os alinhamentos e Grid resolve as malhas. position: absolute fica pro que realmente flutua: badge em cima de card, toast no canto da tela (seção 6).

3.8 Hierarquia visual, cor e tipografia

O olho precisa saber pra onde olhar primeiro. Isso se cria com três alavancas:

Tamanho e peso (a escala tipográfica). Poucos tamanhos, sempre os mesmos:

Papel Tamanho Peso
Título da tela (h1) 1.75rem 700
Título de seção (h2) 1.25rem 600
Título de card (h3) 1rem 600
Corpo 1rem 400
Texto de apoio 0.875rem 400, cor suave
Número de indicador 2.5rem 700

E uma fonte só, a do sistema, que não precisa baixar nada e fica boa em qualquer máquina:

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
  color: var(--cor-texto);
  background: var(--cor-fundo);
  line-height: 1.5;
}

Cor com propósito. Uma cor principal pra ação (todo botão primário, todo link: a mesma), tons neutros pro resto, e cores de status (verde confirmou, vermelho erro, amarelo atenção). Lembra da avaliação por dedução de cores do módulo 01: usar poucas cores é zero; uma paleta bem resolvida com 4+ usos coerentes é pontuação total. O :root da seção 3.4 já é essa paleta.

Contraste que se lê. Texto sobre fundo precisa de contraste mínimo 4.5:1 (texto grande, 3:1). Cinza claro sobre branco reprova. O DevTools mostra a razão de contraste ao inspecionar qualquer texto (e o módulo 06 volta nisso com o olhar de acessibilidade).

⚠️Pega-ratão

⚠️ Pega-ratão: o "arco-íris". Cada botão de uma cor, texto em cinco tamanhos aleatórios, tudo competindo por atenção. Menos é mais: uma paleta enxuta e consistente parece muito mais profissional que um monte de cor. Isso é literalmente critério pontuado.

3.9 Movimento: transition, transform e o spinner

Movimento sutil é polimento barato. Dois usos e só:

Micro-resposta em hover e foco (150 a 200ms, nunca mais que 300ms em interação):

.botao-primario {
  transition: background-color 150ms ease, transform 100ms ease;
}
.botao-primario:hover  { background: var(--cor-acao-hover); }
.botao-primario:active { transform: scale(0.98); }

.card { transition: box-shadow 150ms ease; }
.card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }

O spinner de carregamento (vai servir os estados da seção 5.5):

@keyframes girar { to { transform: rotate(360deg); } }

.spinner {
  width: 24px;
  height: 24px;
  border: 3px solid var(--cor-borda);
  border-top-color: var(--cor-acao);
  border-radius: 50%;
  animation: girar 0.8s linear infinite;
}
🛠️Gambiarra boa

🛠️ Gambiarra boa: transition de 150ms nos botões e cards é o acabamento com melhor custo-benefício da prova: quatro linhas de CSS e a interface inteira parece "viva". Já animação chamativa (coisas voando, pulsando, girando sem motivo) joga contra: distrai e parece enfeite de quem não terminou o resto.


4. Responsividade e mobile-first

O descritivo pede interface responsiva: que funcione bem em telas de tamanhos diferentes, do celular ao monitor. A UC15 inteira é sobre isso.

Mobile-first na prática: o CSS base descreve a tela pequena (uma coluna, tudo empilhado), e as media queries adicionam colunas conforme o espaço aparece:

/* base: celular, uma coluna, sem media query nenhuma */
.malha-indicadores {
  display: grid;
  gap: var(--espaco-3);
}

/* tablet: duas colunas */
@media (min-width: 768px) {
  .malha-indicadores { grid-template-columns: repeat(2, 1fr); }
}

/* desktop: quatro colunas */
@media (min-width: 1024px) {
  .malha-indicadores { grid-template-columns: repeat(4, 1fr); }
}

Por que essa ordem (e não o contrário com max-width): a versão simples é a base, e complexidade só entra quando cabe. Esquecer um breakpoint deixa a tela empilhada porém funcional, nunca quebrada. Dois breakpoints bastam pra prova: 768px e 1024px.

O kit completo da responsividade:

  1. Meta viewport no HTML (seção 2.1). Sem ela, nada disso liga.
  2. Media queries min-width nos dois breakpoints.
  3. repeat(auto-fit, minmax(...)) nas malhas de cards (seção 3.6): responsivo sem media query.
  4. Imagem que não estoura: img { max-width: 100%; height: auto; } no reset.
  5. Tabela em tela pequena: tabela não espreme; ela rola pro lado dentro de um invólucro:
.tabela-wrapper { overflow-x: auto; }
<div class="tabela-wrapper">
  <table> ... </table>
</div>

(Alternativa mais elegante: em telas pequenas, mostrar cards em vez da tabela. Mais trabalho; o wrapper resolve em 30 segundos.)

  1. Alvos de toque: botão com padding generoso (mínimo ~44px de altura clicável) pra dedo, não só pra mouse.
🎯Foco de prova

🎯 Foco de prova: responsividade é ponto que a banca testa fácil, é só apertar a janela do navegador. Se a tela quebra (texto sai pra fora, botão some, tabela estoura a página), cai nota na hora. Testar em tela pequena durante o desenvolvimento, não no fim.

🛠️ Gambiarra boa: DevTools com o modo device (Ctrl+Shift+M) aberto num canto enquanto desenvolve, simulando um celular de ~375px. Cada tela nova, um olhar de 10 segundos: cabe, rola, aperta? Consertar na hora custa um minuto; descobrir na apresentação custa nota.


5. JavaScript: do clique até a API

Aqui os dois mundos se juntam. O front pede dados pra API (módulo 04) e mostra na tela. O descritivo diz assíncrono: a página não trava enquanto espera a resposta.

5.1 DOM e eventos: a rampa

O DOM é a página viva na memória do navegador; o JS lê e mexe nela. O ciclo de toda tela é: achar elemento → reagir a evento → atualizar a tela.

// achar elementos (uma vez, no topo do arquivo)
const form  = document.querySelector('#form-convidado');
const lista = document.querySelector('#lista-convidados');

// reagir ao envio do formulário
form.addEventListener('submit', (evento) => {
  evento.preventDefault(); // impede o recarregamento da página
  const dados = Object.fromEntries(new FormData(form));
  // dados = { nome: '...', email: '...', telefone: '...', acompanhantes: '0' }
  salvarConvidado(dados);
});
⚠️Pega-ratão

⚠️ Pega-ratão: esquecer o preventDefault. O sintoma é fantasmagórico: a página "pisca", a requisição morre pela metade, às vezes o dado até salva mas a tela não mostra. Se a tela pisca ao enviar, é isso.

Pra escrever na tela, o caminho da prova é gerar HTML com template literals (crases) e innerHTML. Com uma regra de segurança inegociável:

// escapa texto vindo do usuário antes de pôr no HTML
function escapar(texto) {
  const div = document.createElement('div');
  div.textContent = texto ?? '';
  return div.innerHTML;
}

lista.innerHTML = `<h3>${escapar(convidado.nome)}</h3>`;
⚠️Pega-ratão

⚠️ Pega-ratão: XSS, a irmã do SQL injection. innerHTML com dado do usuário sem escapar executa o que vier: um convidado cadastrado como <script>...</script> roda na tela da recepção. Mesmo raciocínio do módulo 04 (§6): dado de usuário nunca é código. Lá, prepared statement; aqui, escapar() em todo campo que veio de gente.

5.2 Síncrono, assíncrono e promises

Buscar dados na API leva tempo (rede). Se o JS esperasse parado, a página congelaria: sem clique, sem rolagem, sem digitação. Por isso toda operação de rede é assíncrona: o JS dispara o pedido, segue a vida, e trata a resposta quando ela chegar.

O objeto que representa "uma resposta que ainda vai chegar" é a Promise. Ela tem três estados: pendente (esperando), resolvida (deu certo, tem valor) ou rejeitada (deu erro). Dá pra consumir de dois jeitos:

// jeito antigo: encadear .then()
fetch(url)
  .then((resposta) => resposta.json())
  .then((dados) => mostrar(dados))
  .catch((erro) => avisar(erro.message));

// jeito da prova: async/await (lê de cima pra baixo, como código normal)
async function carregar() {
  try {
    const resposta = await fetch(url);
    const dados = await resposta.json();
    mostrar(dados);
  } catch (erro) {
    avisar(erro.message);
  }
}

São equivalentes. O async/await ganha porque parece código síncrono (fácil de ler e de debugar) e o erro cai num try/catch normal. Regras do jogo: await só dentro de função async, e todo await esquecido devolve uma Promise em vez do valor (se aparecer [object Promise] na tela, faltou um await).

5.3 A anatomia do fetch

fetch é a função do navegador pra chamar a API. Os dois formatos que cobrem a prova inteira, batendo nas rotas que vocês escreveram no módulo 04 (§2.4):

GET (buscar dados):

const resposta = await fetch('http://localhost:3000/convidados', {
  headers: { 'Authorization': `Bearer ${token}` },
});
const convidados = await resposta.json();

POST/PUT (enviar dados):

const resposta = await fetch('http://localhost:3000/convidados', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({ nome, email, telefone, acompanhantes }),
});

As quatro peças: URL (a rota do mapa de endpoints), method (GET é o padrão; POST/PUT/DELETE se declara), headers (o Content-Type avisando que vai JSON + o token do módulo 04 §8), body (o objeto passado por JSON.stringify, nunca cru).

⚠️Pega-ratão

⚠️ Os três esquecimentos do fetch (decorar como checklist mental): 1. Esqueceu Content-Type: application/json → o back recebe body vazio. É exatamente o sintoma req.body undefined do módulo 04: agora você conhece os dois lados dele. 2. Esqueceu JSON.stringify → o body vira a string [object Object]. 3. Esqueceu o header Authorization → 401 em tudo, mesmo logada.

E o quarto primo: se o navegador reclamar de CORS no console, o problema é no back (módulo 04 §10), não no front. Não perder 40 minutos mexendo no fetch.

5.4 Tratamento de erro: o que o fetch NÃO faz por você

A pegadinha mais importante do módulo: o fetch só rejeita a Promise em erro de rede (API fora do ar, sem internet). Resposta 404, 409, 500? Pra ele, "deu certo": chegou uma resposta. Quem não sabe disso escreve try/catch, acha que está protegida, e os erros do servidor passam batidos pela tela.

A verificação é sua, via resposta.ok (verdadeiro só pra status 2xx):

async function buscarConvidados() {
  const resposta = await fetch(url, { headers });

  if (!resposta.ok) {
    // lê o corpo de erro padronizado do módulo 04: { erro, detalhes }
    const corpo = await resposta.json().catch(() => ({}));
    throw new Error(corpo.erro || `Erro ${resposta.status}`);
  }

  return resposta.json();
}

Dois caminhos de falha, dois comportamentos:

🎯Foco de prova

🎯 Foco de prova: a banca vai derrubar o caminho feliz: parar a API, cadastrar duplicado, buscar id inexistente (é literalmente o roteiro de Postman do módulo 04 §15, agora visto da tela). Front nota 3 é o que mostra mensagem clara nesses momentos em vez de tela travada em "Carregando..." pra sempre.

5.5 Os quatro estados de toda tela

Toda tela que consome API vive em um de quatro estados, e os quatro precisam existir no código:

  1. Carregando: esperando a resposta (spinner ou "Carregando...").
  2. Sucesso: chegaram dados, mostra.
  3. Vazio: deu certo, mas não tem nada ("Nenhum convidado cadastrado ainda"). Sucesso sem tratamento de vazio vira tela em branco confusa.
  4. Erro: falhou, avisa com mensagem legível e, ideal, um jeito de tentar de novo.

Os quatro implementados, no padrão que serve pra qualquer lista da prova:

async function carregarConvidados() {
  lista.innerHTML = '<div class="spinner"></div>';                       // 1. carregando

  try {
    const convidados = await api('/convidados');                          // seção 5.6

    if (convidados.length === 0) {                                        // 3. vazio
      lista.innerHTML = '<p class="estado-vazio">Nenhum convidado cadastrado ainda.</p>';
      return;
    }

    lista.innerHTML = convidados.map(cardConvidado).join('');             // 2. sucesso
  } catch (erro) {                                                        // 4. erro
    lista.innerHTML = `
      <p class="estado-erro">
        Não deu pra carregar a lista: ${escapar(erro.message)}
        <button type="button" onclick="carregarConvidados()">Tentar de novo</button>
      </p>`;
  }
}
🎯Foco de prova

🎯 Os estados que todo mundo esquece. Fazer só o caso de sucesso é nível 2. Tratar os quatro é nível 3. E é fácil esquecer o de erro porque, quando você testa, dá tudo certo. O teste honesto: desligar a API e abrir cada tela. Toda tela precisa reagir com dignidade. 30 segundos de teste, e é exatamente o que a banca faz.

5.6 O helper api(): escreva o fetch uma vez

Repetir aquele fetch de 15 linhas em cada chamada é convite pra esquecer um header e criar bug. O padrão profissional (e o maior economizador de tempo do Módulo C) é centralizar tudo num helper:

// js/api.js: toda a conversa com o back passa por aqui
const API_URL = 'http://localhost:3000';

async function api(rota, opcoes = {}) {
  const token = localStorage.getItem('token');

  const resposta = await fetch(API_URL + rota, {
    ...opcoes,
    headers: {
      'Content-Type': 'application/json',
      ...(token && { 'Authorization': `Bearer ${token}` }),
    },
    body: opcoes.body ? JSON.stringify(opcoes.body) : undefined,
  });

  // 401 = não autenticado (sem token, vencido ou inválido) → volta pro login
  if (resposta.status === 401) {
    localStorage.removeItem('token');
    localStorage.removeItem('usuario');
    window.location.href = 'index.html';
    return new Promise(() => {}); // página vai trocar; ninguém mais precisa da resposta
  }

  const corpo = resposta.status === 204 ? null : await resposta.json().catch(() => null);

  if (!resposta.ok) {
    // formato padronizado do módulo 04 (§12): { erro, detalhes }
    const erro = new Error(corpo?.erro || `Erro ${resposta.status}`);
    erro.status = resposta.status;
    erro.detalhes = corpo?.detalhes;
    throw erro;
  }

  return corpo;
}

E cada tela vira uma linha por chamada:

const convidados = await api('/convidados');
const novo = await api('/convidados', { method: 'POST', body: dados });
await api(`/convidados/${id}`, { method: 'PUT', body: dados });
await api(`/convidados/${id}/checkin`, { method: 'POST' });
const capacidade = await api(`/casamentos/${casamentoId}/capacidade`);

O que esse helper resolve de uma vez por todas:

🛠️Gambiarra boa

🛠️ Gambiarra boa: o api.js se escreve nos primeiros 10 minutos do Módulo C, junto com o :root do CSS. É o par de fundações. Vale treinar até sair de memória: são ~30 linhas que transformam toda integração do resto da prova em chamadas de uma linha.


6. Feedback visual: a interface conversando

O descritivo pede, no check-in, feedback visual imediato. Isso é um princípio geral: toda ação do usuário merece uma resposta visível. Três padrões implementados cobrem a prova inteira:

1. O toast (aviso que aparece e some). Serve pra confirmar qualquer ação: salvou, editou, apagou.

function avisar(mensagem, tipo = 'sucesso') {
  const aviso = document.createElement('div');
  aviso.className = `toast toast-${tipo}`;
  aviso.textContent = mensagem;
  document.body.append(aviso);
  setTimeout(() => aviso.remove(), 3500);
}

// uso: avisar('Convidado salvo!');  avisar(erro.message, 'erro');
.toast {
  position: fixed;
  top: var(--espaco-3);
  right: var(--espaco-3);
  padding: var(--espaco-3) var(--espaco-4);
  background: var(--cor-superficie);
  border-left: 4px solid var(--cor-sucesso);
  border-radius: var(--raio);
  box-shadow: var(--sombra);
}
.toast-erro { border-left-color: var(--cor-erro); }

2. O botão que mostra que está trabalhando. Durante o await, o botão trava e avisa:

form.addEventListener('submit', async (evento) => {
  evento.preventDefault();
  const botao = form.querySelector('button[type="submit"]');

  botao.disabled = true;
  botao.textContent = 'Salvando...';
  try {
    await api('/convidados', { method: 'POST', body: lerFormulario() });
    avisar('Convidado salvo!');
    form.reset();
  } catch (erro) {
    avisar(erro.message, 'erro');
  } finally {
    botao.disabled = false;
    botao.textContent = 'Salvar convidado';
  }
});
⚠️Pega-ratão

⚠️ Pega-ratão: o clique duplo. Botão sem disabled durante o await deixa o usuário clicar duas vezes e cadastrar dois convidados iguais (ou dois check-ins: a corrida do módulo 04 §11.2, provocada pela própria tela). O banco segura com a UNIQUE, mas a experiência fica feia e a banca vê. disabled = true na primeira linha, finally devolvendo, sempre.

3. O resultado gigante do check-in. A tela vitrine merece o feedback mais visível do sistema:

<div id="resultado-checkin" class="resultado" hidden></div>
async function fazerCheckin(convidadoId, nome) {
  const painel = document.querySelector('#resultado-checkin');
  try {
    await api(`/convidados/${convidadoId}/checkin`, { method: 'POST' });
    painel.className = 'resultado resultado-ok';
    painel.textContent = `✔ Bem-vinda, ${nome}!`;
  } catch (erro) {
    // o 409 do módulo 04: check-in duplicado
    painel.className = 'resultado resultado-erro';
    painel.textContent = `✖ ${erro.message}`;
  }
  painel.hidden = false;
}
.resultado {
  padding: var(--espaco-5);
  border-radius: var(--raio);
  font-size: 2rem;
  font-weight: 700;
  text-align: center;
  color: #fff;
}
.resultado-ok   { background: var(--cor-sucesso); }
.resultado-erro { background: var(--cor-erro); }
🎯Foco de prova

🎯 O check-in ágil é vitrine. O descritivo destaca "check-in ágil com feedback visual". É uma das telas que a banca mais olha. Feedback grande, colorido e imediato ali não é enfeite, é o critério: uma recepcionista real precisa bater o olho de longe e saber se pode deixar entrar. Verde gigante com o nome, ou vermelho gigante com o motivo. Fonte de 2rem pra cima, sem medo.


7. As telas da prova, com cara de nota 3

O descritivo nomeia telas específicas. O que cada uma cobra e como se resolve:

7.1 Login: a porta

Simples, mas se não funciona, nada funciona. O fluxo completo:

form.addEventListener('submit', async (evento) => {
  evento.preventDefault();
  try {
    const { token, usuario } = await api('/auth/login', {
      method: 'POST',
      body: Object.fromEntries(new FormData(form)),
    });

    localStorage.setItem('token', token);
    localStorage.setItem('usuario', JSON.stringify(usuario));

    // cada perfil cai direto na sua tela de trabalho
    window.location.href = usuario.perfil === 'recepcao' ? 'checkin.html' : 'dashboard.html';
  } catch {
    mostrarErro('E-mail ou senha inválidos');  // mensagem no form, nunca alert()
  }
});

A resposta do login é a que o módulo 04 (§8.2) definiu: { token, usuario: { id, nome, perfil } }. Guardar os dois no localStorage: o token pras chamadas, o usuário pra tela saber o nome e o perfil sem decodificar nada.

Detalhes que somam: input de senha com type="password", erro mostrado dentro do form perto dos campos, botão em "Entrando..." durante o await, e o redirecionamento por perfil (a recepção cai direto no check-in, que é onde ela vive).

7.2 Dashboard gerencial: números que respiram

Indicadores de lotação em tempo real: quantos confirmaram, quantos já entraram, quanto da capacidade foi usada, e o alerta de 90%. Os números vêm da rota de capacidade que vocês escreveram no módulo 04 (§11.1); o front só apresenta:

async function carregarIndicadores() {
  const dados = await api(`/casamentos/${casamentoId}/capacidade`);
  // os nomes dos campos são os que a rota de vocês devolve; o padrão do treino:
  // { capacidade_total, confirmados, presentes, percentual, alerta }

  document.querySelector('#num-confirmados').textContent = dados.confirmados;
  document.querySelector('#num-presentes').textContent = dados.presentes;
  document.querySelector('#barra-ocupacao').style.width = `${dados.percentual}%`;
  document.querySelector('#alerta-90').hidden = !dados.alerta;
}

carregarIndicadores();
setInterval(carregarIndicadores, 15000); // atualiza sozinho a cada 15s

A malha de cards usa o grid da seção 3.6, os números em 2.5rem peso 700 (a escala da 3.8), e o alerta de 90% é um banner que só perde o hidden quando a rota mandar alerta: true: fundo --cor-alerta, texto direto ("⚠ Capacidade acima de 90%").

🎯Foco de prova

🎯 "Tempo real" na prática. Não precisa de tecnologia complicada. Basta a tela buscar o número atualizado quando abre e quando algo muda (recarregar os indicadores depois de cada check-in confirmado). O setInterval de 15s é a gambiarra honesta que fecha o resto. O que a banca quer ver é o número refletindo a realidade do banco: ela faz um check-in e olha o dashboard; se o número andou, é tempo real o suficiente. WebSocket é bala de canhão pra esse pardal.

7.3 Check-in ágil: a vitrine

O fluxo da recepcionista tem que caber em segundos: digitar um pedaço do nome (ou o código do convite), ver o convidado, confirmar, feedback gigante. Componentes já construídos nesse módulo: busca com a lista filtrando enquanto digita (seção 9.3), botão de confirmar com disabled durante o await (seção 6), painel verde/vermelho de resultado (seção 6). O erro 409 de dupla entrada vem do back com a mensagem pronta (erro.message), o front só exibe no vermelho.

Um detalhe de agilidade que a banca sente: o campo de busca já nasce focado (autofocus no input, ou campo.focus() ao carregar). A recepcionista não pega o mouse.

7.4 Os três perfis na interface

Os perfis são os do ENUM do módulo 03: admin, organizador e recepcao. Cada um enxerga um sistema diferente:

Perfil O que vê
admin tudo: dashboard, convidados (com excluir), mesas, check-in
organizador dashboard, convidados e mesas (sem excluir convidado)
recepcao busca + check-in (a tela dela é o sistema inteiro)

Como esconder e mostrar é a seção 8. O que importa aqui: a experiência de cada perfil precisa fazer sentido sozinha: a recepção não deve nem ver menu pra telas que vão dar 403.


8. Controle de acesso na interface

Duas camadas no front, as duas em js/auth.js, importado por toda página interna:

1. A guarda de página (as primeiras linhas do arquivo): sem token, nem carrega a tela:

// js/auth.js
if (!localStorage.getItem('token')) {
  window.location.href = 'index.html';
}
const usuario = JSON.parse(localStorage.getItem('usuario') || 'null');

2. Render condicional por perfil: o HTML marca o que é restrito com classes, e uma linha de JS limpa o que não é do perfil:

<button class="botao-perigo so-admin" data-acao="excluir">Excluir</button>
<a href="dashboard.html" class="so-gestao">Dashboard</a>
if (usuario.perfil !== 'admin') {
  document.querySelectorAll('.so-admin').forEach((el) => el.remove());
}
if (usuario.perfil === 'recepcao') {
  document.querySelectorAll('.so-gestao').forEach((el) => el.remove());
}

remove() em vez de esconder com CSS: o elemento sai do DOM de verdade (não fica um botão "invisível" que um Ctrl+F no inspetor acha em 5 segundos).

⚠️Pega-ratão

⚠️ Pega-ratão (o mesmo de sempre, do outro lado): achar que esconder o botão é segurança. Não é. Esconder é experiência (não confundir o usuário com opções que ele não pode usar). A segurança é o back barrar com 403 (módulo 04 §9), porque o token dá pra editar, o JS dá pra desligar e a rota dá pra chamar por Postman. Os dois trabalham juntos: front esconde pra ficar limpo, back barra pra ficar seguro. Na apresentação, falar essa frase vale ponto de julgamento.


9. SPA, componentes e estado

O descritivo cita Full Stack / SPA (Single Page Application): navegação sem recarregar a página, trocando só o conteúdo. Antes da arquitetura, os dois conceitos que valem em qualquer front:

9.1 Componente: a função que rende HTML

Um componente, em vanilla JS, é só uma função que recebe dados e devolve HTML:

function cardConvidado(convidado) {
  const status = convidado.status_convite ?? 'pendente';
  return `
    <article class="card" data-id="${convidado.id}">
      <div class="card-topo">
        <h3>${escapar(convidado.nome)}</h3>
        <span class="badge badge-${status}">${status}</span>
      </div>
      <p class="texto-suave">
        Mesa ${convidado.mesa_numero ?? 'sem mesa'} · ${convidado.acompanhantes} acompanhante(s)
      </p>
      <div class="acoes">
        <button type="button" data-acao="editar">Editar</button>
        <button type="button" class="so-admin" data-acao="excluir">Excluir</button>
      </div>
    </article>`;
}

// uma lista inteira é um map + join:
lista.innerHTML = convidados.map(cardConvidado).join('');
🛠️Gambiarra boa

🛠️ Gambiarra boa: componentizar economiza tempo e nota. O cardConvidado aparece na lista, no resultado da busca do check-in e no detalhe. Escrito uma vez. Menos código, menos bug, e consistência visual automática (todos os cards idênticos = tela profissional). É trabalho no começo que paga no meio da prova, quando o tempo aperta.

Os cliques nos botões dos cards usam delegação: um listener na lista resolve todos os cards, inclusive os que ainda vão nascer:

lista.addEventListener('click', (evento) => {
  const botao = evento.target.closest('[data-acao]');
  if (!botao) return;
  const id = botao.closest('.card').dataset.id;
  if (botao.dataset.acao === 'editar') abrirEdicao(id);
  if (botao.dataset.acao === 'excluir') confirmarExclusao(id);
});

(Listener em cada card se perde toda vez que o innerHTML re-renderiza. Delegação na lista sobrevive.)

9.2 Estado: os dados que a tela mostra

Estado é o que a tela está exibindo agora: a lista de convidados carregada, o texto digitado na busca. O padrão que organiza qualquer tela da prova: um objeto de estado + uma função render() que desenha a partir dele:

const estado = { convidados: [], busca: '' };

function render() {
  const filtrados = estado.convidados.filter((c) =>
    c.nome.toLowerCase().includes(estado.busca.toLowerCase())
  );
  lista.innerHTML = filtrados.length
    ? filtrados.map(cardConvidado).join('')
    : '<p class="estado-vazio">Nenhum convidado encontrado.</p>';
}

9.3 A regra de ouro: mudou o estado, chama o render

// carregou da API → estado muda → render
estado.convidados = await api('/convidados');
render();

// digitou na busca → estado muda → render (busca instantânea, sem ir ao servidor)
campoBusca.addEventListener('input', () => {
  estado.busca = campoBusca.value;
  render();
});

A busca local filtrando a cada tecla dá a sensação de agilidade que o check-in pede. (Pra listas grandes, a API do módulo 04 §13 já aceita ?busca=; com o volume da prova, filtrar o que já veio é mais rápido e mais simples.)

9.4 E o SPA?

Multi-página (um HTML por tela) é o caminho recomendado: mais simples, F5 sempre funciona, menos risco. Se a prova exigir SPA explícito, a versão vanilla honesta: um HTML só com uma <section> por tela, navegação mostrando uma e escondendo as outras:

function navegar(tela) {
  document.querySelectorAll('main > section').forEach((s) => (s.hidden = true));
  document.querySelector(`#tela-${tela}`).hidden = false;
}

O resto (componentes, estado, render) é idêntico. É por isso que essa seção veio antes: quem domina componente + estado troca de arquitetura em 20 minutos.


10. Validação no client: resposta imediata

O sistema valida em três camadas, e cada uma tem um papel:

  1. Atributos HTML (required, type, minlength, min/max): de graça, sem JS.
  2. JS antes do fetch: mensagens melhores, no lugar certo, na hora.
  3. O servidor (módulo 04 §5): a única que é segurança. As duas primeiras são experiência.
⚠️Pega-ratão

⚠️ Pega-ratão conceitual (de novo, porque a banca pergunta): validação só no front é porta destrancada, o Postman passa reto por ela. O front valida pra responder na hora sem viagem ao servidor; o back valida pra proteger. As duas existem, com mensagens parecidas, e você sabe explicar por quê.

A validação de JS no submit, usando os espaços de erro do form da seção 2.3:

function validarConvidado(dados) {
  const erros = {};
  if (!dados.nome || dados.nome.trim().length < 3) {
    erros.nome = 'Informe o nome completo (mínimo 3 letras)';
  }
  if (dados.email && !dados.email.includes('@')) {
    erros.email = 'E-mail inválido';
  }
  const acomp = Number(dados.acompanhantes);
  if (Number.isNaN(acomp) || acomp < 0 || acomp > 5) {
    erros.acompanhantes = 'Acompanhantes: entre 0 e 5';
  }
  return erros;
}

form.addEventListener('submit', async (evento) => {
  evento.preventDefault();
  const dados = Object.fromEntries(new FormData(form));

  const erros = validarConvidado(dados);
  mostrarErrosNoForm(erros);                 // escreve em cada .campo-erro
  if (Object.keys(erros).length > 0) return; // não chama a API com dado inválido

  // ... segue pro api() com botão travado (seção 6)
});

function mostrarErrosNoForm(erros) {
  form.querySelectorAll('.campo').forEach((campo) => {
    const input = campo.querySelector('input, select');
    const aviso = campo.querySelector('.campo-erro');
    const mensagem = erros[input.name];
    aviso.textContent = mensagem ?? '';
    aviso.hidden = !mensagem;
    input.classList.toggle('campo-invalido', Boolean(mensagem));
  });
}
.campo-erro { color: var(--cor-erro); font-size: 0.875rem; }
.campo-invalido { border-color: var(--cor-erro); }
🎯Foco de prova

🎯 Onde a nota mora: mensagem perto do campo, em português claro, dizendo o que fazer ("Informe o nome completo", não "erro no formulário"). E se o back devolver detalhes no erro (o formato do módulo 04 §12), mostrar essas mensagens também: o helper api() já as entrega em erro.detalhes.

E as máscaras de telefone e afins? São a cereja da lapidação e têm módulo próprio: o padrão formata-mostra/guarda-limpo está no módulo 08, com código pronto. Aqui, o essencial: máscara é conforto visual; validação é outra coisa; e campo mascarado se manda limpo pra API.


11. Peso da página: otimização que dá pra ver

A UC15 cobra "otimização para dispositivos móveis". Na prova isso se traduz em: a página abre rápido e nada trava. O checklist enxuto:

  1. Imagem é o vilão número 1. Foto de fundo de 4000px no login = página lenta na frente da banca. Antes de usar: redimensionar pro tamanho real de exibição e comprimir (Squoosh faz os dois no navegador). Formato: foto em JPG/WebP, ícone e logo em SVG ou PNG pequeno.
  2. loading="lazy" em imagem que aparece em lista (só carrega quando entra na tela): uma palavra no HTML.
  3. Fonte de sistema (seção 3.8): zero download de fonte. A página mais rápida é a que não pede.
  4. Sem biblioteca desnecessária: cada framework/plugin que "ajudaria" custa peso, risco e tempo de aprender na hora. Vanilla resolve a prova inteira: é a decisão que esse módulo inteiro treina.
  5. Minificação (saber explicar): remover espaços e comentários do CSS/JS pra reduzir bytes. Em produção real, ferramenta de build; na prova, não vale o tempo. O que vale: um CSS e poucos JS (menos requisições), e saber dizer o que minificação é se a banca perguntar (a UC15 cita).
  6. Conferir no DevTools, aba Network: recarregar e olhar o total de KB e o tempo. Se a página passa de uns poucos MB, tem imagem gorda escondida.
🛠️Gambiarra boa

🛠️ Gambiarra boa: a otimização com melhor retorno na prova são imagens tratadas + zero dependência. Se a página abre instantânea no notebook da banca, otimização cumprida. Lighthouse (no próprio DevTools) dá um relatório com nota se sobrar tempo de curiosidade no treino.


12. Autoavaliação do módulo

  1. Qual o papel de HTML, CSS e JavaScript, e por que separar os três (em papéis e em arquivos)?
  2. O que a meta viewport faz? O que acontece com as media queries sem ela?
  3. Por que button de verdade ganha de div com clique? Cita duas coisas que a tag dá de graça.
  4. O que label for + id faz e por que é o ponto de acessibilidade mais barato do form?
  5. Numa disputa entre .card .titulo e .titulo, quem ganha e por quê? Qual a estratégia de especificidade da prova e por que !important é armadilha?
  6. O que box-sizing: border-box muda na conta da largura? Quais as três linhas do reset mínimo?
  7. rem ou px: qual usar em fonte e espaçamento, e por quê?
  8. Por que o :root com custom properties é a primeira coisa que se escreve no CSS da prova? Que ligação ele tem com o módulo 06?
  9. Flexbox ou grid: qual o critério de escolha? Dá um exemplo do Wedding Pass de cada.
  10. O que repeat(auto-fit, minmax(220px, 1fr)) faz e por que economiza media query?
  11. O que é mobile-first e quais os dois breakpoints do treino? Como uma tabela sobrevive à tela pequena?
  12. O que o fetch NÃO faz quando o servidor responde 500? O que é resposta.ok e por que checar?
  13. Quais os quatro estados de uma tela que consome API? Qual o teste de 30 segundos que revela se estão tratados?
  14. Cita três problemas que o helper api() resolve de uma vez. Qual a diferença de tratamento entre 401 e 403 no front?
  15. Por que innerHTML com dado de usuário sem escapar é perigoso? Qual o paralelo com o módulo 04?
  16. Por que o botão trava (disabled) durante o await? Que bug do módulo 04 isso evita de provocar?
  17. Se o back já valida, por que validar no front também? O que cada camada garante?
  18. De onde vem o número de "lotação em tempo real" e o que "tempo real" exige de fato na prova?
  19. Duas otimizações de página com melhor retorno na prova: quais e por quê?
💡Nota

Front no lugar? Agora a gente aprofunda no que faz uma interface ser boa de usar, não só bonita: 06 — UX e interface.


Referências do módulo

Bibliografia do plano de curso (UC12 e UC15): - SILVA, Maurício Samy. CSS Grid Layout. Novatec. - DUCKETT, Jon. HTML e CSS: projete e construa websites. Alta Books. - ZEMEL, Tárcio. CSS eficiente: técnicas e ferramentas que fazem a diferença nos seus estilos. Casa do Código. - LOPES, Sérgio. A Web Mobile: programe para um mundo de muitos dispositivos. Casa do Código. - SOUZA, Weiller. Bootstrap 4: conheça o framework front-end mais utilizado no mundo (leitura de contexto; a prova é vanilla).

Documentação e mercado: - MDN: conceitos básicos de flexbox - MDN: grids CSS - MDN: usando propriedades personalizadas (custom properties) - MDN: media queries - MDN: usando a Fetch API - MDN: async e await - web.dev: tratamento de erros com a Fetch API - Dmitri Pavlutin: How to Use Fetch with async/await - Alura: quando utilizar Grid e quando utilizar Flexbox - Alura: praticando CSS com Grid e Flexbox - Le Wagon: CSS Grid ou Flexbox - Impacta: Flexbox ou CSS Grid, quando utilizar cada ferramenta

Seletiva 26 · Desenvolvimento de Sistemas · Senac RS