Quem entregou esta página?
Toda resposta HTTP carrega cabeçalhos que contam quem a produziu — e muitos sites deixam pistas: qual servidor web, se passou por uma CDN, há quanto tempo o arquivo está em cache. Hoje você vira detetive: escolha três…
Todos os desafios das aulas em um só lugar, mais projetos integradores. ⭐ leva uma ou duas horas; ⭐⭐ uma tarde; ⭐⭐⭐ um fim de semana; 🔥 é o boss da unidade. Marque Feito e acompanhe: 0 concluídos neste navegador.
Toda resposta HTTP carrega cabeçalhos que contam quem a produziu — e muitos sites deixam pistas: qual servidor web, se passou por uma CDN, há quanto tempo o arquivo está em cache. Hoje você vira detetive: escolha três…
A tabela da §2 resume meio século em dez linhas. Ela merece mais: crie uma página HTML com uma linha do tempo de pelo menos 10 marcos da história da Web (1969 até hoje), cada um com ano, título e um parágrafo de 2 a 3…
Escolha um domínio real (o site de uma universidade, um jornal, uma loja). Quantos computadores um pacote atravessa entre a sua máquina e o servidor dele? A resposta está em um comando que existe em todo sistema opera…
O navegador esconde tudo o que a §4 descreve. Hoje você faz o trabalho dele manualmente: dispara uma requisição HTTP sem navegador, lê a resposta crua e compara protocolos. A ferramenta é o curl, que já vem instalado …
O trecho abaixo "funciona" — abre no navegador e mostra tudo. Mesmo assim, ele tem pelo menos oito problemas: alguns o validador do W3C encontra, outros só um olho humano treinado percebe. Encontre todos, classifique-…
Boa parte das pessoas que usam a Web não usa mouse: quem navega por teclado, quem usa leitor de tela, quem tem lesão por esforço repetitivo. E buscadores não "veem" CSS nenhum. Hoje você vai usar o site do evento (e u…
A tabela de status HTTP da Aula 01 tem cinco linhas. Um desenvolvedor consulta esses códigos toda semana — e a maioria dos sites de referência é em inglês. Produza http-referencia.html: uma página que documente os cód…
Você aprendeu camadas, MPA/SPA e APIs em teoria. Agora aplique em um sistema que você usa todo dia: escolha um (SIGAA, um e-commerce, uma rede social, um app de banco no navegador) e produza um relatório de 2 páginas …
A Wikipédia é um dos sites mais bem estruturados da Web: sumário com âncoras, infobox em tabela, seções hierárquicas, notas de rodapé com links de ida e volta. Escolha um artigo em português (sugestão: "HTML" ou "Tim …
Todo mundo já desistiu de preencher um cadastro. Qual é o menor formulário capaz de inscrever alguém em um evento? Pegue a inscricao.html que você acabou de construir e faça o caminho inverso: corte tudo que não é ind…
Escolha um formulário público de verdade: matrícula de uma universidade, agendamento de uma prefeitura, cadastro de uma loja, abertura de conta de um banco. Abra o DevTools e leia o HTML dele. Quantos campos têm <labe…
Um mesmo campo "telefone", sem restrição nenhuma, recebeu na vida real: 66999990000, (66) 99999-0000, 66 9 9999 0000, +55 66 99999-0000, 9999-0000 (falar com a Maria) e não tenho. Nenhum está errado do ponto de vista …
Formulários longos assustam. A solução clássica é dividir em etapas ("Etapa 2 de 4") — e quase todo mundo faz isso com JavaScript. Mas dá para construir um formulário de várias etapas usando apenas HTML, com uma págin…
Alguém entregou a galeria abaixo dizendo que "abre normal no navegador". Abre — mas o validador do W3C acusa erros, o leitor de tela lê nomes de arquivo e o vídeo nunca mostra legenda. Há oito problemas no trecho. Enc…
Uma página de galeria com doze fotos de celular pesa em média 40 MB. Quanto tempo ela leva para abrir no 4G lento? Você vai medir. Monte uma página com doze fotos originais (sem tratamento), meça o peso e o tempo de c…
A WCAG exige, para vídeo pré-gravado: legendas (critério 1.2.2), audiodescrição ou alternativa em texto (1.2.3) e, para áudio, transcrição (1.2.1). Quase nenhum site de evento cumpre isso — o seu vai cumprir. Pegue um…
Alguém passou uma hora tentando descobrir por que "o CSS não pega". O arquivo está abaixo, e o HTML o referencia com <link rel="stylesheet" href="estilo.css"> — mas o arquivo está salvo em css/estilo.css. Além desse, …
Abra o perfil de qualquer pessoa no GitHub e olhe os cartões de "repositórios fixados" (pinned): ícone, nome em azul, descrição em cinza, etiqueta da linguagem com uma bolinha colorida, contador de estrelas. Parece si…
Explicar o box model para outra pessoa é a melhor forma de descobrir se você entendeu. Construa uma página didática, box-model.html, que demonstre visualmente cada conceito desta aula com exemplos vivos: a mesma caixa…
Sites institucionais brasileiros — de prefeituras, secretarias, campi — costumam ter dezenas de <div>, imagens sem alt, formulários sem <label> e tabelas usadas para layout. Você agora sabe reconhecer tudo isso. Escol…
Existe um estilo que "não pega" em praticamente todo projeto de CSS — e quase sempre a culpa é da especificidade. Neste desafio você vai treinar o olho: dada uma lista de conflitos reais, prever quem vence antes de ab…
Escolha três sites reais que você usa toda semana — um portal público, uma loja e uma rede social — e audite o contraste deles. Você vai encontrar texto cinza-claro sobre branco, botão desabilitado ilegível e link que…
Pegue este HTML sujo, cheio de classes que descrevem aparência, e reconstrua o mesmo visual sem tocar no HTML — usando apenas seletores de tipo, de atributo, combinadores e pseudoclasses. Depois reescreva o HTML como …
Escolha a página inicial de um site cuja identidade visual você admira e reproduza a identidade — paleta, tipografia, escala de espaçamentos, estilo de botões, tratamento de links — em uma página com conteúdo totalmen…
Você consegue levar 24 sapos para as vitórias-régias certas usando só justify-content, align-items e flex-wrap? E regar 28 cenouras com grid-column, grid-area e grid-template-areas? Os dois jogos foram criados para fi…
O código abaixo era para produzir um cabeçalho fixo com menu à direita, uma página com lateral à esquerda e conteúdo à direita, e um selo "Esgotado" no canto do cartão. Nada disso acontece: o cabeçalho rola junto com …
A página inicial do site do evento precisa de um bloco de destaques em formato de revista: sete cartões de tamanhos diferentes — um grande (2 × 2), dois largos (2 × 1), um alto (1 × 2) e três pequenos (1 × 1) — encaix…
Você consegue reproduzir a página inicial de um site que usa todo dia? Escolha a home de um site conhecido (o portal da sua universidade ou escola, um jornal, uma loja, a página inicial do GitHub) e reproduza seu layo…
Você escreveu repeat(auto-fit, minmax()) hoje e talvez ainda não tenha certeza de por que funciona — porque decorar a linha não é o mesmo que enxergar as linhas-guia da grade. O Grid Garden é um jogo de 28 fases em qu…
Alguém jura que "o site está responsivo, testei no meu monitor". No celular ele aparece minúsculo, com rolagem horizontal, imagem estourando e um menu que nunca vira hambúrguer. Há seis erros entre o HTML e o CSS abai…
O menu.js de hoje tem oito linhas — mas dá para ter zero. Um <input type="checkbox"> escondido, um <label> que faz papel de botão e a pseudoclasse :checked da Aula 06 conseguem abrir e fechar o menu só com CSS. Constr…
Rode o Lighthouse Mobile no index.html do seu projeto autoral agora, antes de mexer em qualquer coisa, e guarde o relatório. É comum a primeira nota de Performance ficar abaixo de 60 em um site com fotos grandes, e a …
Alguém enviou o CSS abaixo dizendo "nada anima, o CSS deve estar bugado". Não está — o navegador está fazendo exatamente o que foi pedido. Há quatro erros conceituais, cada um de um tipo diferente visto nesta aula. En…
Ative prefers-reduced-motion: reduce no sistema (ou emule no DevTools) e abra qualquer site grande: a maioria simplesmente remove tudo, e junto some o feedback — o painel que deslizava agora aparece do nada, o toast q…
Esta aula afirma que animar transform é mais barato que animar left. Você vai provar isso com números, do jeito que um engenheiro de front-end faz antes de defender a escolha para outra pessoa. Implemente o mesmo pain…
Bibliotecas como a Animate.css têm milhões de downloads por semana — e cabem em um arquivo CSS que você já sabe ler. Crie o seu animacoes.css com no mínimo 20 animações reutilizáveis, organizadas em quatro categorias …
Quatro aulas atrás o seu projeto autoral era HTML puro. Hoje ele tem um sistema de design, um layout de verdade, responde a qualquer tela e se move com propósito. O Boss desta unidade é fechar tudo isso em um site que…
Você acabou de ver que "5" + 3 é "53" e "5" - 3 é 2. Alguém afirma que "com prática dá para prever qualquer coerção sem testar". Vamos ver. Abaixo estão doze expressões; escreva a sua previsão para cada uma antes de d…
O script abaixo deveria mostrar, na página de inscrição, o nome do evento e as vagas restantes. Ele tem cinco defeitos: um impede o arquivo inteiro de rodar, um faz a página quebrar antes de o HTML existir, um produz …
Um sistema de inscrição cobra R$ 0,10 de taxa por SMS e envia três. No Console, 0.1 3 dá 0.30000000000000004; o cliente vê "R$ 0,30000000000000004" no boleto e liga furioso. Bancos, lojas e sistemas de ingressos não …
Quantas vezes você precisa sortear um número de 1 a 6 até que os seis valores tenham saído pelo menos uma vez? A intuição diz "umas dez"; a matemática diz 14,7 em média; e o seu computador pode dizer a verdade em três…
O script abaixo deveria classificar as inscrições do evento e mostrar o preço final. Ele roda sem nenhuma linha vermelha no Console — e mesmo assim está errado em cinco pontos: uma faixa nunca é alcançada, um desconto…
A organização da Semana Acadêmica precisa saber, a qualquer momento, em que fase está: falta mais de um mês, está na reta final, é hoje, ou já acabou? E precisa que a página inicial diga isso em português correto — "f…
Todo conjunto de dados esconde uma informação que ninguém tinha pensado em perguntar. Com os arrays palestras e palestrantes da Mão na massa, quantas perguntas interessantes você consegue responder em uma linha cada? …
O script abaixo deveria montar o resumo da programação. Ele roda, imprime números — e todos estão errados. São seis defeitos: um erro "por um", um for…in usado onde deveria ser for…of, um sort que ordena como texto, u…
Você já agrupou atividades por dia e contou por área. Agora repare: os dois códigos são quase idênticos — muda apenas a propriedade usada como chave. Programadores experientes sentem coceira ao ver isso e escrevem uma…
Alguém enviou o código abaixo dizendo que "o contador funciona uma vez e depois para, e às vezes nem começa". Rode-o, reproduza os dois sintomas e encontre os quatro bugs plantados — sem reescrever do zero. Cada um é …
Uma lista de tarefas é o "olá, mundo" das interfaces dinâmicas — e o exercício que mais revela se você entendeu o fluxo estado → renderização → eventos. Construa a sua, do zero, seguindo a arquitetura da seção 18, sem…
Sites de evento costumam ter um "quiz de conhecimentos" para engajar quem se inscreve. Construa um quiz de 10 perguntas de múltipla escolha sobre a Unidade 3 (você escreve as perguntas), com uma pergunta por tela, cro…
O arquivo abaixo deveria validar uma inscrição, mas aceita nome vazio, e-mail sem arroba e CPF com uma letra no meio — e, quando reclama, ninguém que use leitor de tela fica sabendo. São seis defeitos: quatro de lógic…
Digite "minicurso git" na busca da programação: nada aparece, porque nenhum título contém exatamente essa sequência. Digite "seguranças": também nada. Usuários não digitam do jeito que os seus dados foram escritos — e…
O validarCampo da Mão na massa tem um switch com sete casos. Funciona, mas cada campo novo exige mexer nessa função — e, num formulário de vinte campos, ela vira um monstro. Existe uma alternativa que os frameworks us…
Um formulário pode passar em todos os testes automáticos e ainda ser impossível de preencher sem o mouse. Nesta missão você vai auditar o formulário do seu projeto autoral como um usuário que não enxerga a tela, corri…
O repositório abaixo quase funciona no Live Server do Windows e chega ao GitHub Pages sem estilo, sem imagens e com o JavaScript morto. São cinco linhas com defeito, e nenhum defeito é erro de sintaxe: todos são conse…
O Lighthouse dá nota 100 em acessibilidade para páginas que um usuário de leitor de tela não consegue usar — ele testa o que é automatizável, e isso é menos da metade do problema. Neste desafio você faz as duas coisas…
A página inicial de um site de evento não deveria pesar mais que um aplicativo de celular. Neste desafio, a meta é objetiva e medida por ferramenta: a página inicial do seu site, carregada pela primeira vez em um nave…
Este é o desafio que fecha a Unidade 3 e esta trilha. A regra é simples e desconfortável: um tema novo, do zero, publicado e auditado em três horas. Sem reaproveitar o código do site do evento nem o do seu projeto aut…
Um recrutador abre o seu GitHub e olha um repositório por, em média, alguns segundos. Nesse tempo ele decide se você sabe trabalhar ou se apenas entregou uma tarefa. A diferença raramente está no código: está no READM…
O navegador esconde tudo o que a §2.2 descreve. Hoje você faz o trabalho dele na mão: dispara requisições HTTP sem navegador, lê a resposta crua e mede onde o tempo é gasto. A ferramenta é o curl, que já vem no Window…
Toda resposta HTTP carrega pistas sobre a infraestrutura que a produziu: qual software serviu, se passou por uma CDN, quanto tempo o arquivo pode ficar em cache. Vire detetive: escolha quatro sites (o da sua universid…
O valor real do Git não aparece quando tudo dá certo — aparece às 23h30 do dia da entrega, quando você apaga o arquivo errado. Este desafio é um treino de emergência: você vai quebrar o próprio projeto de quatro forma…
Um cliente pede: "gostei do site, mas a marca agora é verde-oliva, e queremos um modo escuro". Quanto tempo isso custa? Em um CSS bem escrito, cinco minutos; em um CSS com cores espalhadas por 300 linhas, uma tarde. D…
Por que um site com pouquíssimo conteúdo demora dois segundos para aparecer? Quase sempre porque alguma coisa está bloqueando o caminho até a primeira pintura. Hoje você vira perito: mede, identifica o culpado e compr…
Um teste brutal e revelador: desligue a folha de estilo do seu site e leia a página resultante. Se ela continuar fazendo sentido — títulos na ordem certa, links com texto claro, conteúdo antes do rodapé — a estrutura …
O GitHub Pages e o Live Server fazem a mesma coisa que você vai programar na Unidade 3: receber um caminho, achar um arquivo, devolver com o cabeçalho certo. Antecipe o assunto. Escreva, em Node.js puro (sem Express, …
Quantas regiões um leitor de tela consegue enxergar no seu site? E no site da sua faculdade? Sem instalar nada, o DevTools mostra a mesma árvore que o leitor de tela usa — e a diferença entre um site bem marcado e um …
Existe um formulário famoso por ser insuportável: o de cadastro que rejeita o seu telefone porque você digitou parênteses, recusa a senha porque tem um caractere "inválido" que ele não diz qual é, e apaga tudo quando …
O arquivo abaixo abre no navegador sem nenhum erro visível no console e parece funcionar. Mas ele tem sete problemas: alguns o validador do W3C aponta, outros só aparecem quando você navega por teclado ou clica nos ró…
Escolha uma página de um site que você usa toda semana — um portal de notícias, uma loja, o sistema acadêmico, um blog técnico — e reproduza a estrutura semântica dela: só HTML, sem uma linha de CSS, com conteúdo escr…
Você usou col-md-6 a tarde inteira sem nunca ter visto o CSS que faz aquilo acontecer. Isso é confortável e é perigoso: no dia em que a coluna não obedecer, você não vai ter para onde olhar. Abra a caixa preta. O boot…
Discussões sobre framework na internet duram anos e quase nunca têm números. Você vai encerrar a sua em uma tarde: construa o mesmo componente — um card de produto com imagem, título, texto, preço e botão — nos três f…
Existe um jogo cruel entre desenvolvedores: abrir um site e adivinhar o framework em dois segundos. Sites feitos com Bootstrap sem customização são os mais fáceis — azul #0d6efd, cantos de 6 px, a mesma sombra, a mesm…
A melhor forma de entender um framework é escrever um. Não um clone do Bootstrap — um micro-framework honesto, de no máximo 200 linhas de CSS, com o mínimo necessário para montar o site do Café Cerrado sem nenhuma dep…
Um colega mandou o CSS abaixo dizendo "não anima nada, o navegador deve estar bugado". Não está: o navegador está fazendo exatamente o que foi pedido. Há cinco erros conceituais, cada um de um tipo diferente visto hoj…
Todo projeto autoral desta trilha precisa de uma marca. É tentador baixar um PNG genérico de 80 KB que fica borrado na tela do celular. Você vai desenhar a sua — no código, com formas geométricas — e ela vai pesar men…
"SVG é mais leve" é uma frase que todo mundo repete e quase ninguém mediu. Você vai medir — e vai descobrir que a frase tem exceções importantes. O objetivo não é provar que SVG ganha: é aprender a decidir com números.
A página inicial do Café Cerrado vai ganhar uma seção "O café em números" com três indicadores. A tentação é usar uma biblioteca de gráficos com 90 KB de JavaScript. Você não pode: a Unidade 1 é sem JavaScript. Vai de…
Desconecte o mouse. Fisicamente, do computador — ou desative o touchpad. Por vinte minutos você vai usar apenas o teclado: o seu site, o site da sua universidade ou escola, o SIGAA e um site de comércio à sua escolha.…
Um "desenvolvedor sênior" entregou este acordeão de perguntas frequentes para o Café Cerrado, orgulhoso do ARIA que usou. O componente é uma coleção de mentiras: os atributos afirmam coisas que o código não cumpre, e …
A Lei Brasileira de Inclusão exige acessibilidade em sites de órgãos públicos desde 2015. Você vai verificar, com método, se um deles cumpre — e produzir um documento que poderia, de fato, ser protocolado. Escolha um …
Seis aulas atrás o seu projeto era uma pasta vazia e um repositório recém-criado. Hoje ele é um site de três páginas, semântico, responsivo, com framework CSS, identidade visual em SVG, movimento com propósito e utili…
Um colega mexeu no app.js do Café Cerrado antes de sair de férias e agora o cardápio não aparece. O Console mostra Uncaught TypeError: Cannot read properties of null (reading 'content') e mais nada. Ele plantou três d…
Troque textContent por innerHTML em criarCardProduto e adicione ao array um produto chamado <img src=x onerror="document.body.style.filter='invert(1)'">. Recarregue a página. O que acontece é uma versão inofensiva de …
Delegação parece um detalhe de estilo até você medir. Gere 200 produtos falsos, renderize duas versões do cardápio — uma registrando addEventListener em cada card, outra com um único ouvinte no contêiner — e compare t…
Quem entra no Café Cerrado toda semana sempre pede as mesmas duas coisas. Dê a essa pessoa um jeito de marcar favoritos que sobreviva ao fechamento do navegador, funcione só com teclado, seja anunciado corretamente po…
Um colega escreveu a ordenação por preço assim e jurou que testou: produtos.sort((a, b) => a.preco > b.preco). Com três produtos, funcionou. Com os nove do Café Cerrado, a lista sai quase ordenada — mas não totalmente…
Este relatório do Café Cerrado funciona, mas percorre o array cinco vezes e repete a mesma estrutura em todas. Refatore-o com métodos de array, meça as duas versões e decida com dados — não com gosto — qual entra no p…
Sua busca funciona lindamente com o mouse. Agora feche os olhos: como alguém que usa leitor de tela sabe que a lista mudou? Que 2 de 10 itens sobraram? Que a busca não achou nada? Faça o cardápio filtrado ser tão util…
Alguém filtra o cardápio por "Doces, ordenados por preço", acha o combo perfeito e quer mandar no grupo da família. Copia a URL, cola, e a outra pessoa abre o cardápio inteiro sem filtro nenhum — porque o estado só ex…
Você trocou dois await em sequência por um Promise.all e alguém disse que ficou mais rápido. Mas "mais rápido" é opinião até virar número. Meça: quanto tempo, exatamente, o seu cardápio leva para aparecer nas duas ver…
Ative "Slow 4G" na aba Network, digite a, depois ca, depois café na busca do cardápio — rápido, como qualquer pessoa digita. De vez em quando a tela mostra o resultado de a embora o campo diga café. O bug não está na …
Feche os olhos e navegue no seu cardápio usando só o teclado e o leitor de tela do sistema (NVDA no Windows, Orca no Linux, VoiceOver no macOS). Enquanto os dados carregam, você provavelmente ouve silêncio — e quando …
Promise.all com 30 operações dispara as 30 ao mesmo tempo. Em um navegador real isso esbarra no limite de conexões simultâneas por domínio (historicamente 6 no HTTP/1.1) e, em um servidor de verdade, é um jeito rápido…
Um colega "simplificou" o pegarJson do projeto e agora a tela do cardápio mostra "Carregando…" para sempre quando o arquivo não existe — em vez da mensagem de erro. Pior: o console mostra um SyntaxError falando de <!D…
Cada vez que alguém entra na tela do cardápio, você busca produtos.json de novo. Hoje isso é rápido porque o arquivo é seu e pequeno. Amanhã será uma API na internet, e a mesma resposta viajará dezenas de vezes por se…
O roteador da aula é um Map de caminhos fixos. Frameworks reais aceitam parâmetros (/produto/:id), rota coringa para 404, e proteção de rota (entrar em /admin só se estiver logado). Escreva o seu, genérico o suficient…
Este é o Boss da Unidade 2: um fluxo completo de pedido, em quatro telas, usando tudo o que você aprendeu desde a Aula 07 — DOM e eventos, map/filter/reduce, assíncrono com estados, fetch de arquivo e de API, POST, fo…
O front-end do Café Cerrado precisa montar os botões de filtro por categoria, mas hoje ele só sabe pedir a lista inteira de produtos e deduzir as categorias no navegador — o que significa baixar seis produtos completo…
Abra data/produtos.json, apague uma vírgula qualquer no meio e salve. Agora recarregue o site. O que você vê? Provavelmente a página em branco, um erro feio no terminal e um usuário que nunca vai saber o que aconteceu…
"Por que instalar um framework se o Node já tem um servidor embutido?" É uma pergunta legítima, e a resposta "porque todo mundo usa" não vale nada em uma prova ou em uma entrevista. Reimplemente a sua API inteira — as…
Abra o DevTools na aba Network, recarregue o site e olhe a coluna Size do estilo.css: em vez do tamanho em kB, aparece (disk cache) ou o status 304. O Express fez isso sozinho para os arquivos estáticos — mas a sua ro…
Duas pessoas abrem o cardápio ao mesmo tempo e o seu terminal cospe seis linhas de log embaralhadas: não dá para saber quais linhas pertencem a qual visita. Serviços de verdade resolvem isso dando um identificador úni…
Um colega mexeu no server.js sem avisar e agora o site "não abre mais": a aba do navegador fica girando indefinidamente, o terminal não mostra erro nenhum e o processo Node continua vivo, sem consumir CPU. O curl tamb…
Hoje a validação do POST /api/produtos são vinte linhas de if dentro do handler. Na próxima aula chega o PUT, que precisa das mesmas regras. Na aula seguinte, o recurso de categorias. Copiar e colar esses if três veze…
app.use(express.json()) parece uma linha mágica: o corpo cru vira objeto e ninguém pergunta como. Não é mágica — é um stream. O corpo de uma requisição HTTP chega em pedaços, e alguém precisa juntá-los, decidir quando…
O Café Cerrado quer uma vitrine de destaques na página inicial. Você acrescenta router.get('/destaques', controlador.destaques) no fim do arquivo de rotas, sobe o servidor, abre http://localhost:3000/api/produtos/dest…
O PUT que você escreveu hoje mente: pelo RFC 9110, PUT substitui o recurso inteiro, então mandar {"preco": 12.9} deveria apagar nome, categoria e descrição. O nosso preserva — comportamento de PATCH usando o nome erra…
A dona do Café Cerrado pediu um resumo do cardápio: quantos produtos existem, quanto custa o mais caro e o mais barato, qual o preço médio por categoria. Ela não sabe o que é JSON, mas o seu front vai saber. Construa …
Seu criar faz três coisas em sequência: lê o arquivo, acrescenta um item, grava o arquivo. Entre a leitura e a gravação existe uma janela de alguns milissegundos. Se duas requisições entrarem nessa janela juntas, as d…
Abra o site do Café Cerrado pelo Live Server do VS Code, em http://127.0.0.1:5500, em vez de pelo seu servidor Express. O botão do Google some, e o console solta uma mensagem que menciona origin. Antes de consertar, e…
Hoje qualquer pessoa com uma conta Google — o planeta inteiro — vira um usuário capaz de mexer no cardápio do Café Cerrado. Isso está certo para um blog e errado para um sistema interno. Implemente autorização de verd…
O ID token do Google e o token de sessão que você emitiu têm prazos de validade diferentes, definidos por gente diferente, por motivos diferentes. Poucos desenvolvedores sabem dizer quais são. Descubra os dois experim…
Se um único script malicioso rodar na sua página, ele lê o sessionStorage e leva a sessão embora. É assim que contas são roubadas de verdade. A defesa padrão da indústria é entregar a sessão em um cookie httpOnly: o n…
Um colega "otimizou" a exclusão para não precisar buscar a lista de novo no servidor: em vez de recarregar, ele remove o item direto do array local. Funciona lindamente — até você digitar algo na busca. Com o filtro b…
Ligue o throttling "Slow 3G" na aba Network, digite cafe rápido no campo de busca e observe: a resposta de caf pode chegar depois da resposta de cafe, e a tela termina mostrando o resultado errado, para um termo que j…
O servidor já faz a parte dele: desde a Aula 13, o 400 devolve detalhes como lista de objetos { "campo": "preco", "mensagem": "…" }. A tela é que desperdiça essa informação — ela junta tudo num parágrafo só: "Dados in…
O confirm() do navegador congela a aba inteira, não pode ser estilizado, não cabe na identidade visual do Café Cerrado e some sem deixar rastro em capturas de tela. Toda aplicação séria usa um diálogo próprio — e a pl…
Dizer "minha API está protegida" é fácil; provar é outra coisa. Monte a evidência: um único arquivo com as requisições que demonstram, sem margem de dúvida, que a sua API distingue os três casos — visitante (401), pes…
O Café Cerrado cresceu e agora tem um gerente, que precisa corrigir o preço de qualquer produto — inclusive os cadastrados por outras pessoas. Sem inventar um sistema de cadastro de usuários: uma lista de e-mails admi…
Todo sistema sério registra quem fez o quê. Sem isso, quando um preço aparecer errado, ninguém consegue dizer se foi engano, sabotagem ou bug. Construa uma trilha de auditoria para o Café Cerrado: cada criação, altera…
Este é o mini-projeto que fecha a Unidade 3 e usa tudo: rotas, controladores, middlewares, autenticação, autorização, persistência e front assíncrono. O cardápio está no ar e as pessoas querem pedir. Crie um segundo r…
Um colega escreveu vagasParaExibir(evento) para mostrar o número de vagas no card do evento, usando o padrão que a Seção 3.7 alertou ser perigoso: evento.vagasDisponiveis || 10. Um evento com exatamente zero vagas est…
O código da Seção 2.1 (DOM manual) apaga listaEl.innerHTML e reconstrói tudo a cada tecla digitada na busca — funciona bem com 4 eventos, mas e com mil? Gere uma lista grande, meça o custo real de renderizar() a cada …
Toda a Seção 3 revisou ferramentas de JavaScript moderno usando eventos como exemplo — mas o seu projeto autoral (Seção 1.6) tem outro domínio. Construa o dominio.js real do seu projeto, aplicando classes, módulos, os…
O formulário de criação de evento do UniEventos usa <input v-model.number="vagas" type="text"> para o campo de vagas. Um colega testou digitando "quarenta" em vez de 40 — e o card passou a mostrar "NaN vaga(s) restant…
Guarde o mouse. Navegue pela tela de eventos construída hoje usando só Tab, Shift+Tab, Enter e as setas. Encontre pelo menos três barreiras de acessibilidade por teclado — foco que desaparece visualmente, campo sem ró…
Aplique tudo desta aula — ref/reactive, v-bind/v-on/v-model, v-if/v-show, v-for com :key estável e um hook de ciclo de vida real — na entidade principal do seu projeto autoral, com a mesma profundidade da Seção "Mão n…
A store de filtros do seu colega usa um objeto reactive com busca, categoria e apenasComVaga, observado por um único watch(filtros, callback) (sem a opção da Seção 4.2). Ele jura que testou e "funcionava" — mas agora,…
A Seção 3 provou, com um console.log, que um computed roda uma vez só, mesmo usado três vezes no template. Mas quanto tempo isso realmente economiza quando a lista é grande? Meça com 5.000 eventos e descubra a partir …
Leve todo o padrão desta aula — computed encadeadas, watch persistindo uma preferência, e os três estados de tela (carregando/erro/vazio) — para o domínio do seu projeto autoral.
O botão de sol/lua da seção 5 troca o tema na hora — mas dê um F5: a aplicação volta para 'light', mesmo que você tenha deixado no escuro há dois segundos. Ninguém espera reconfigurar a aparência do site toda vez que …
Desconecte o mouse (ou apenas prometa a si mesmo não tocar nele) e tente abrir o menu lateral do UniEventos só com o teclado: Tab até o ícone de hambúrguer, Enter para abrir, Tab pelos itens, Esc para fechar. Em quant…
A seção 7 recomenda lazy loading (() => import(...)) para toda view, "porque o padrão é esse". Mas quanto isso realmente economiza no UniEventos, hoje, com só quatro views? Meça de verdade antes de confiar na recomend…
A Unidade 1 terminou: você tem uma SPA com Vuetify, rotas, tema e grid responsivo. Antes de a Unidade 2 trazer componentização séria e a Unidade 3 trazer um back-end de verdade, prove que consegue montar uma funcional…
Um colega criou DialogoConfirmacao.vue reaproveitando o desta aula, mas trocou um detalhe sem perceber. Ao clicar em "Excluir" no diálogo, nada acontece — o evento aparentemente nunca chega ao componente pai. Este é o…
O v-menu de ações rápidas do Laboratório B4 funciona perfeitamente no mouse. Agora teste só com teclado: Tab até o botão de três pontinhos, Enter para abrir, Tab/setas para navegar pelas opções, Enter para escolher, E…
O código abaixo passa o usuário logado por três componentes até chegar a quem realmente precisa dele — clássico prop drilling. PainelAdmin e CabecalhoSecao não usam usuarioLogado para nada além de repassar adiante:
Um colega "simplificou" o interceptor de request removendo a checagem, para deixar o código mais enxuto:
inscricoesStore (e, se você fez o Laboratório B4, preferenciasStore também) implementam persistência em localStorage cada uma com sua própria lógica de leitura/escrita repetida. Extraia isso para uma função reutilizáv…
Toda vez que o usuário volta para a Home vindo do detalhe de um evento, carregarEventos() dispara um novo GET /eventos — mesmo que a lista não tenha mudado nos últimos segundos. Em uma API de verdade (não o json-serve…
A Unidade 2 terminou. Você sabe componentizar de verdade, sincronizar filtros com a URL (Aula 05), e agora consumir uma API real com Axios e Pinia (hoje). Prove que tudo isso funciona junto, numa única funcionalidade …
Abra o DevTools (F12) no seu unieventos-web rodando com npm run dev, vá na aba Sources e procure pelo valor de uma das variáveis VITEFIREBASE que você configurou no .env. Ela aparece ali, legível, para qualquer pessoa…
Um colega registrou duas rotas parecidas sem perceber a colisão: GET /api/eventos/:id e, mais abaixo no arquivo, GET /api/eventos/destaque (pensada para devolver o evento em destaque do momento). Toda chamada para /ap…
A seção 2 desta aula prometeu que o Node atende milhares de conexões com uma única thread — desde que o trabalho seja I/O, não cálculo. Prove isso (e o limite disso) na prática: meça quanto uma rota "pesada" de CPU (s…
Teste no seu unieventos-api: GET /api/eventos/abc (um id que não é número). O que a rota devolve? Compare com o que a tabela de status codes desta aula promete para "recurso inexistente". Investigue por que Number('ab…
O medidorDeTempo desta aula usa process.hrtime.bigint() e o evento 'finish' do objeto res. Implemente uma segunda versão que, além de logar o tempo no console, guarda em um array em memória as últimas 100 durações de …
O envelope { "erro": { "mensagem", "codigo" } } desta aula não chega pronto ao usuário final — alguém no front precisa transformá-lo em algo visível. Implemente, no front-end do seu projeto autoral, um interceptor de …
Toda tabela do schema.sql desta aula tem pelo menos um índice — mas nem toda consulta que você vai escrever no seu projeto autoral necessariamente usa esses índices do jeito que você espera. Rode EXPLAIN numa consulta…
A transação desta aula usa FOR UPDATE para travar a linha do evento — mas o que acontece se você remover essa trava de propósito e disparar duas inscrições simultâneas para um evento com exatamente 1 vaga? Reproduza a…
A promessa central desta aula é que migrar de memória para MySQL não deveria quebrar nenhum contrato de API. Prove isso formalmente no seu projeto autoral: grave as respostas de todo o requests.http rodando contra a v…
Você já viu que dar F5 não desloga o usuário — mas onde exatamente o SDK guarda essa informação para sobreviver ao recarregamento da página? Abra o DevTools no UniEventos já logado e investigue, sem ler a documentação…
Ao dar F5 numa página do UniEventos com o usuário já logado, por uma fração de segundo aparecem os botões "Entrar"/"Cadastrar" antes de trocarem para o menu de usuário logado. É rápido demais para notar em conexão boa…
Hoje, qualquer conta criada por e-mail/senha pode criar um evento imediatamente — mesmo com um e-mail inventado (fulano@empresa-que-nao-existe.com) que a pessoa nem é dona de verdade. Ainda não confirmamos que o e-mai…
Um colega "melhorou" a EventosListaView.vue e agora acontece algo estranho: navegue até a página 3 da tabela e digite "docker" na busca — a tabela mostra "Nenhum evento encontrado", mas o rodapé insiste que existem 2 …
A store desta aula é pessimista de propósito (seção 5). Mas abra o DevTools, ative o throttling "Slow 3G" na aba Network e exclua um evento: o botão gira por três segundos antes de a linha sumir. Gmail e Trello remove…
O botão de editar só aparece para quem está logado, e o de excluir só para admin. Mas botão escondido não é permissão: qualquer pessoa com um token válido consegue montar um PUT /api/eventos/3 no curl e editar o event…
LIMIT 10 OFFSET 99990 obriga o MySQL a ler e descartar 99.990 linhas antes de devolver 10 — a última página é sempre a mais lenta. Gere 100 mil eventos falsos, meça o tempo das primeiras e das últimas páginas e depois…
Um colega criou a tabela de comentários de eventos com o SQL abaixo e reclamou de dois "bugs do Supabase": visitantes deslogados não veem comentário nenhum (a página fica vazia, sem erro), e um usuário conseguiu "roub…
Rode npm run build e procure eyJ dentro de dist/assets/.js: a sua chave anon está lá, legível para qualquer pessoa que abrir o site. Isso é esperado — mas o que exatamente alguém consegue fazer com ela e um terminal? …
O card de evento mostra vagas, mas não quantas já foram ocupadas — e o botão "Inscrever-se" nem existe na versão Supabase. Construa o fluxo completo usando só o que a aula ensinou: contagem de relacionados no select, …
Na Aula 11, a última vaga foi protegida com uma transação e FOR UPDATE dentro do Express. Com Supabase não existe "seu servidor" para colocar essa lógica — e um select de contagem seguido de insert no front deixa a po…
Um colega reorganizou o app.js "para ficar mais legível" e agora os testes de rota falham de um jeito curioso: POST /api/eventos válido devolve 422 dizendo que todos os campos são obrigatórios, e GET /api/eventos/999 …
Em uma rede compartilhada — um laboratório, um escritório, ou até um provedor com NAT/CGNAT — várias pessoas saem para a internet pelo mesmo IP. Com limit: 100 por IP a cada 15 minutos, bastam quatro pessoas testando …
Na Aula 12 a servicerole era proibida porque o código rodava no navegador. Aqui é diferente: o back-end é um ambiente de servidor, e a chave pode ficar no .env. Implemente criarRepositorioDeEventosSupabase() com a mes…
Sete testes dão confiança — mas confiança em quê, exatamente? Meça: instale @vitest/coverage-v8, rode npx vitest run --coverage e olhe o relatório linha a linha. Você vai descobrir que remover, atualizar, o 413 do lim…
Um colega documentou um novo endpoint PATCH /api/eventos/{id}/destaque (marca um evento como destaque na home) com uma anotação @openapi completa — mas o Swagger UI insiste em mostrar essa operação isolada, fora dos g…
Reproduza no seu repositório uma divergência que acontece o tempo todo em projeto real. Renomeie, só no linhaParaEvento do repositório, o campo imagemUrl para urlDaImagem, e ajuste a anotação Swagger de GET /api/event…
Na Seção 1 você leu que "documentação vira uma fonte de verdade verificável" quando existe uma ferramenta de teste de contrato comparando a resposta real com o que foi documentado. Hoje isso ainda é só teoria no UniEv…
Planos gratuitos de hospedagem "dormem" o processo depois de um tempo sem tráfego (Seção 3.2) — mas quanto tempo, exatamente, uma requisição demora para acordar um serviço adormecido, comparado com uma requisição norm…
Um colega jura que testou tudo: npm run preview local funciona perfeitamente, F5 em qualquer rota interna funciona. Mas depois de publicado, o mesmo F5 em /eventos/3 devolve uma página de erro genérica do provedor de …
Hoje, se um deploy quebrar (uma variável de ambiente errada, uma migration esquecida), você só descobre quando alguém tenta usar a aplicação e encontra um erro. Implemente um smoke test pós-deploy: um passo automático…
Chegamos ao fim das três unidades. Este é o desafio que fecha a trilha: seu projeto autoral publicado, de ponta a ponta, com todas as camadas construídas ao longo das aulas funcionando juntas em produção — e não só "f…
Você instalou um pacote — prettier — e a pasta nodemodules apareceu. Quantos arquivos ela tem? E se instalar vite? A resposta explica, melhor do que qualquer regra, por que nodemodules nunca entra em um .zip, em um e-…
Formatou o computador? Vai usar a máquina do laboratório? Profissionais guardam suas configurações em um repositório de dotfiles e reinstalam tudo com um script. Monte o seu: uma pasta bancada com um instalar.sh que, …
Por que um site de notícias demora oito segundos no 4G e o WebLab demora um? Hoje você deixa de adivinhar. Escolha três sites reais (um portal de notícias, uma loja online e um site de universidade), audite cada um no…
O git status diz que a pasta está limpa, mas a .git tem centenas de arquivos. O que exatamente está lá dentro, e por que um repositório com 20 commits de um site de 200 KB pode ocupar menos espaço do que os próprios a…
Pegue o repositório mais bagunçado que você tem (aquele com commits chamados "alteracoes", "update2" e "agora vai") e transforme-o em um repositório que você mostraria numa entrevista. O objetivo não é maquiar: é apre…
Um site que funcionava parou de funcionar, e ninguém sabe quando. Em vez de ler cinquenta commits, o Git faz uma busca binária: você diz um ponto bom e um ruim, e ele te leva ao commit exato em oito passos. Hoje você …
Este é o Boss da Unidade 1: ele junta tudo o que os Capítulos 01 e 02 ensinaram. A ideia é simples de enunciar e trabalhosa de executar bem: uma máquina nova deve ficar pronta para trabalhar rodando um comando do seu …
É comum alguém publicar o site e receber de volta uma página de texto sem estilo, sem imagem e sem menu — e concluir que "o GitHub Pages não funciona". Ele funciona. O que não funciona é um caminho que começa com barr…
As quatro plataformas deste capítulo prometem a mesma coisa: seu site no ar, de graça, em uma CDN. Elas entregam a mesma coisa? Descubra medindo, e não lendo página de marketing. O mesmo site, publicado quatro vezes, …
Nota de Lighthouse é fácil de melhorar quando você sabe o que ela mede — e impossível quando você chuta. O trabalho aqui é científico: medir, mudar uma coisa, medir de novo, registrar o efeito. No fim, você vai saber …
Quantos servidores diferentes precisam ser consultados para que o seu navegador descubra o IP de weblab.aprendabit.com? E de www.uol.com.br? A resposta muda de um nome para outro — e o dig +trace mostra cada parada, c…
Um único domínio é suficiente para o portfólio inteiro do semestre: o apex para a sua página pessoal, um subdomínio para cada projeto, cada um em uma plataforma diferente. Monte essa estrutura de verdade: apex (seudom…
Você tem dez subdomínios de projetos e não quer emitir dez certificados. Um certificado curinga .seudominio.dev cobre todos — mas a Let's Encrypt só o emite pelo desafio DNS-01, porque não existe como "servir um arqui…
O /api/saude do Passo a passo responde ok mesmo que o arquivo de dados tenha sumido, que o disco esteja cheio ou que a API não consiga mais ler o cardápio. Ele diz apenas "o processo está vivo" — o que é útil, mas é p…
Todo time profissional trabalha com três ambientes: local (a sua máquina), homologação (onde se testa sem medo) e produção (onde os usuários estão). Hoje a sua API tem local e produção, e cada push na main vai direto …
Faça o teste que ninguém faz antes de entregar: crie três produtos pela API publicada, force um novo deploy e recarregue o cardápio. Eles sumiram. O dados/produtos.json voltou ao estado do repositório, porque o disco …
O seu VPS tem poucas horas de vida e já é alvo de milhares de tentativas de acesso. Elas vêm de robôs que varrem faixas inteiras de IP procurando senhas fracas — e ficam todas registradas. Faça a auditoria: descubra q…
O certbot deixa o seu site funcionando, mas com a configuração TLS padrão. Submeta https://eventos.seudominio.dev ao teste do SSL Labs (<https://www.ssllabs.com/ssltest/>) e veja a nota. Depois melhore a configuração …
Hoje o seu deploy é uma sequência de comandos digitados à mão que deixa o site fora do ar por alguns segundos — e, se algo falhar no meio, deixa o servidor num estado híbrido, com o front novo e a API antiga. Escreva …
Um servidor. Um domínio. Três projetos do semestre, cada um em um subdomínio, todos com cadeado — e um README.md que qualquer pessoa consegue seguir para reconstruir tudo do zero em uma máquina nova. É o fechamento da…
A imagem da API construída no Passo a passo pesa quanto? docker images ghcr.io/seu-usuario/unieventos-api responde. Se passou de 150 MB, algo desnecessário entrou: devDependencies, cache do npm, arquivos que o .docker…
A cafe-cerrado-api do Nível 2 é mais simples que a do UniEventos: um Express 5 que serve a pasta public/ e expõe /api/produtos, gravando em um arquivo JSON. Justamente por gravar em arquivo ela tem um problema que a u…
Hoje o site chama a API em outra origem e o CORS precisa liberar o domínio do front. Se o mesmo nginx que serve o site fizer proxy de /api/ para a API, front e back passam a ter a mesma origem — e o CORS deixa de exis…
Sua tela de eventos parece rápida no npm run dev, com o banco em Docker na mesma máquina, e fica arrastada com o banco na nuvem. Nada no código mudou — mudou a distância. Descubra exatamente quanto da lentidão é rede …
Todo mundo tem backup até precisar restaurar. Monte a rotina completa do seu projeto autoral — gerar, guardar fora do servidor, restaurar em um banco descartável e comparar — e execute-a até o fim pelo menos uma vez, …
O projeto nasceu em MySQL (Aula 09 do Nível 3) e agora vai para um Postgres gerenciado. O schema muda, os tipos mudam, as consultas mudam — e os dados que já existem precisam chegar do outro lado inteiros, com as chav…
Uma CI que nunca ficou vermelha não provou nada — talvez ela não esteja rodando o que você pensa. Comprove que a sua esteira reprova de verdade, nas três formas de errar que mais acontecem, e documente o que cada uma …
Revisar um PR lendo o diff é uma coisa; abrir o site do PR no navegador é outra. Faça com que todo pull request do unieventos-web publique uma versão temporária e comente no próprio PR o endereço onde ela pode ser vista.
Testes com repositório falso não pegam erro de SQL. Faça a CI validar as consultas contra um Postgres real, aplicando as migrations do zero a cada execução — e, de quebra, provar que toda migration nova também desfaz …
Você tem CI, imagem publicada e deploy por SSH. Falta o que separa um pipeline de aula de um pipeline de produção: saber que o deploy deu errado e desfazê-lo sozinho. Monte a esteira completa do UniEventos (front e ba…
Todo mundo diz que "otimizou o site". Quase ninguém consegue mostrar o número. Hoje você mostra. Pegue o seu projeto autoral publicado, meça, aplique exatamente três correções de performance e meça de novo — provando …
O seu projeto autoral não termina no primeiro deploy: se der certo, você (ou outra pessoa) vai voltar a mexer nele daqui a alguns meses. Sem testes, cada mudança futura é uma aposta. Construa a rede de segurança: uma …
Sistemas profissionais têm uma página que responde, em cinco segundos de olhada, "está tudo bem?". Construa a sua para o projeto autoral: uma página /status que reúne disponibilidade, desempenho e erros — e que você c…
Você provavelmente não faz ideia de quanto do seu código vem de sugestão aceita sem leitura. Ninguém faz — e é justamente por isso que a conta assusta quando aparece. Durante uma semana de trabalho no projeto autoral,…
Quem consegue medir uma ferramenta para de discutir sobre ela. Monte uma bateria de perguntas cuja resposta certa está na documentação oficial das versões que usamos, aplique em dois assistentes diferentes e publique …
Toda API construída nesta trilha tem pelo menos uma falha de segurança real. A sua também. Use o assistente como um revisor incansável, mas prove cada coisa: aqui, um problema só conta depois que você escreveu o teste…
Este é o encerramento da trilha, e ele é simples de enunciar: o seu projeto autoral, inteiro, funcionando na internet, com tudo o que os onze capítulos ensinaram — e você capaz de defender cada decisão em dez minutos,…
Um recrutador abre o link do seu perfil antes de abrir o currículo e decide em trinta segundos se continua lendo. Uma página de uma tela só, feita por você, diz mais do que dez linhas de "conhecimentos em HTML" num PD…
O QR code na mesa do restaurante abre um PDF de 8 MB que não cabe na tela do celular — você já passou por isso. O dono da lanchonete perto do campus sofre com o mesmo problema e não sabe que a solução são três arquivo…
Todo desenvolvedor já fez uma lista de tarefas, e é exatamente por isso que ela é um bom projeto: você vai comparar a sua com centenas de outras e descobrir o que separa uma lista de brinquedo de uma que você usaria d…
Fim de semestre, três notas lançadas no SIGAA e a mesma pergunta em todo grupo da turma: "quanto eu preciso tirar na última pra passar?". Responda de uma vez por todas com uma calculadora que o colega consegue usar no…
Um quiz é um jogo de estado: qual pergunta está na tela, o que já foi respondido, quantos pontos, se acabou. Todo bug de quiz ("a pergunta 3 apareceu duas vezes", "o botão continuou clicável depois da resposta") é um …
O centro acadêmico anuncia palestras num grupo de WhatsApp, e ninguém acha a da semana passada nem sabe se a de amanhã é às 19h ou às 19h30. Uma agenda no navegador — sem servidor, sem login — já resolve a maior parte…
Por que o total do carrinho nunca está escrito no HTML? Porque o carrinho é dado, e a tela é só um reflexo dele. Construa a vitrine de uma loja fictícia — o brechó, a livraria, a Café Cerrado — e um carrinho que sobre…
Some 0,1 com 0,2 no console do navegador. O resultado não é 0,3 — e é por isso que todo sistema financeiro sério guarda dinheiro em centavos inteiros. Neste projeto você vai controlar receitas e despesas de verdade (a…
O laboratório tem três projetores e cinco kits de Arduino, e a "reserva" é um caderno na secretaria. Duas turmas chegam com o mesmo projetor reservado, e o caderno não diz quem está com o kit que não voltou. Este é o …
Até aqui todos os dados estavam no seu arquivo. Agora eles estão em outro computador, chegam quando querem, às vezes não chegam e às vezes chegam num formato diferente do que a documentação prometia. Escolha uma API p…
O sistema de reservas em localStorage funciona só no navegador de quem cadastrou; no dia em que duas pessoas precisam ver a mesma agenda, ele vira um caderno digital. Este Boss leva o projeto anterior para onde ele pe…
O painel do projeto de API pública chama a fonte direto do navegador; com duzentos usuários abrindo a página, a fonte bloqueia o IP e o painel morre. Este Boss coloca um servidor seu no meio: uma API Express que consu…
Projetos maiores, para juntar tudo o que você aprendeu em uma trilha. Cada um tem critérios de pronto e evoluções sugeridas — escolha um, publique e coloque no portfólio.
Os desafios das aulas treinam um músculo de cada vez; os projetos desta página pedem o corpo inteiro. Cada um parte de um problema que existe fora da sala e exige que você modele os dados, implemente as regras e explique as decisões. Estão em ordem crescente de complexidade: os dez primeiros cabem em três arquivos (index.html, style.css e script.js), sem framework e sem back-end; os dois últimos são o passo seguinte, com API, login, banco, testes e deploy. Entregue sempre a versão mínima funcionando mais uma evolução da sua escolha, acompanhadas de uma demonstração de três minutos e de um texto curto explicando a estrutura do HTML, a estratégia de CSS e a lógica do JavaScript. A trilha sugerida diz a partir de qual aula você já tem o que precisa para começar.
Trilha sugerida: Nível 1 (a partir da aula 08) · Tempo estimado: 4–6 h
Um recrutador abre o link do seu perfil antes de abrir o currículo e decide em trinta segundos se continua lendo. Uma página de uma tela só, feita por você, diz mais do que dez linhas de "conhecimentos em HTML" num PDF. Construa a sua: quem você é, o que estuda, o que sabe fazer, o que já construiu e como falar com você. É o cartão de visitas que cresce junto com o curso — cada projeto desta lista vira um item novo na sua seção de projetos.
Funcionalidades mínimas
Critérios de pronto
header, nav, main, section e footer (nenhum div fazendo papel de seção).alt descritivo; a foto de perfil tem width e height declarados.Evolução (escolha pelo menos uma)
localStorage (a partir da aula 13 do Nível 1) e respeitando prefers-color-scheme na primeira visita.@media print) que vira um currículo de uma página em PDF.flex-direction: column como padrão e mude para row a partir de 720 px com uma @media; celular primeiro dá menos código do que o contrário (aula 08).--cor-fundo, --cor-texto) no :root deixam o tema escuro da evolução em cinco linhas: basta trocar os valores dentro de um seletor [data-tema="escuro"].localStorage.setItem e getItem guardam uma string; leia-a no início do script, antes de pintar a página, para não piscar o tema errado.Trilha sugerida: Nível 1 (a partir da aula 08; evolução a partir da aula 13) · Tempo estimado: 5–8 h
O QR code na mesa do restaurante abre um PDF de 8 MB que não cabe na tela do celular — você já passou por isso. O dono da lanchonete perto do campus sofre com o mesmo problema e não sabe que a solução são três arquivos. Monte um cardápio que carrega rápido, organiza os produtos por categoria e funciona com uma mão só, enquanto a outra segura o celular na fila.
Funcionalidades mínimas
Critérios de pronto
alt, loading="lazy" e dimensões declaradas; nenhuma passa de 200 KB.R$ e duas casas decimais.Evolução (escolha pelo menos uma)
localStorage.article dentro de uma section por categoria; a estrutura semântica é o que vai facilitar os filtros depois (aula 05 do Nível 1).grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)) resolve a responsividade da grade sem uma única media query — leia sobre auto-fill e auto-fit na MDN (aula 08).data-nome e compare com includes() em minúsculas; esconder é adicionar uma classe, não remover o elemento (aula 14).localStorage e, ao carregar a página, aplique a classe favorito nos cards cujo id está na lista.Trilha sugerida: Nível 1 (a partir da aula 14) ou Nível 2 (a partir da aula 07) · Tempo estimado: 6–8 h
Todo desenvolvedor já fez uma lista de tarefas, e é exatamente por isso que ela é um bom projeto: você vai comparar a sua com centenas de outras e descobrir o que separa uma lista de brinquedo de uma que você usaria de verdade. Ela parece trivial até você precisar decidir o que acontece com uma tarefa vazia, com duas iguais, com a página recarregada, com o Enter pressionado no lugar do clique.
Funcionalidades mínimas
Critérios de pronto
alert().localStorage).id duplicado no DOM; cada tarefa tem um identificador único que não é a posição no array.Evolução (escolha pelo menos uma)
{ id, titulo, concluida, criadaEm }. Uma função renderizar() que esvazia a lista e desenha o array inteiro é mais simples do que atualizar item por item (aula 14 do Nível 1 ou aula 07 do Nível 2).ul, lendo event.target.closest('button') e um atributo data-acao, substitui um ouvinte por botão — pesquise "event delegation" na MDN.crypto.randomUUID() gera ids únicos sem biblioteca; Date.now() também serve enquanto ninguém cria duas tarefas no mesmo milissegundo.localStorage só guarda strings: JSON.stringify ao salvar, JSON.parse ao carregar, com um try/catch para o caso de o conteúdo estar corrompido.Trilha sugerida: Nível 1 (a partir da aula 14) · Tempo estimado: 6–8 h
Fim de semestre, três notas lançadas no SIGAA e a mesma pergunta em todo grupo da turma: "quanto eu preciso tirar na última pra passar?". Responda de uma vez por todas com uma calculadora que o colega consegue usar no celular, no corredor, antes da prova. A conta é fácil; o difícil é não deixar ninguém digitar 15, deixar um campo em branco ou ler "aprovado" em verde quando a média foi 4,9.
Funcionalidades mínimas
Critérios de pronto
alert().aria-live).media([7, 8, 9], [1, 1, 2]) devolve o valor sem tocar no DOM.Evolução (escolha pelo menos uma)
localStorage e resumo exportado em CSV.input.valueAsNumber devolve NaN para campo vazio — use Number.isNaN na validação, e min, max e step no HTML como primeira linha de defesa (aula 03).toFixed(1), e classifique usando o mesmo número arredondado que você mostra.Trilha sugerida: Nível 2 (a partir da aula 08) · Tempo estimado: 8–10 h
Um quiz é um jogo de estado: qual pergunta está na tela, o que já foi respondido, quantos pontos, se acabou. Todo bug de quiz ("a pergunta 3 apareceu duas vezes", "o botão continuou clicável depois da resposta") é um estado que alguém esqueceu de controlar. Faça um quiz sobre o tema que quiser — a disciplina, o Pantanal, futebol — e aprenda a pensar a interface como uma função do estado.
Funcionalidades mínimas
estado, nunca espalhado em variáveis soltas.Critérios de pronto
button de verdade, focáveis e acionáveis por teclado; a resposta correta não é identificável no HTML antes de responder.perguntas.js), sem lógica dentro dele.Evolução (escolha pelo menos uma)
localStorage.fetch (a partir da aula 10).renderizar(estado) antes de qualquer evento: dado o estado, qual HTML aparece? Cada clique só altera o estado e chama renderizar de novo — é o padrão que o Vue automatiza no Nível 3.data-correta="true" no botão; compare o índice clicado com pergunta.correta em JavaScript (aulas 07 e 08 do Nível 2).for de trás para a frente e uma troca. sort(() => Math.random() - 0.5) parece funcionar, mas é enviesado.setInterval precisa ser encerrado com clearInterval sempre que a pergunta muda — guarde o id do intervalo no estado.Trilha sugerida: Nível 2 (a partir da aula 08) · Tempo estimado: 8–12 h
O centro acadêmico anuncia palestras num grupo de WhatsApp, e ninguém acha a da semana passada nem sabe se a de amanhã é às 19h ou às 19h30. Uma agenda no navegador — sem servidor, sem login — já resolve a maior parte do problema, e usa o mesmo modelo de dados que você vai levar para uma API no fim do Nível 2 e para o UniEventos no Nível 3. A parte traiçoeira aqui é a data: formato, fuso, comparação e ordenação.
Funcionalidades mínimas
localStorage.Critérios de pronto
alert().id; não cria duplicata.Evolução (escolha pelo menos uma)
.ics para o Google Agenda.input type="datetime-local" devolve (AAAA-MM-DDTHH:mm) e converta com new Date() só para comparar e formatar; strings nesse formato ordenam corretamente até com sort() simples.Intl.DateTimeFormat('pt-BR', { dateStyle: 'medium', timeStyle: 'short' }) formata sem biblioteca — leia as opções weekday e month na MDN.id, substitua no array (findIndex); senão, gere um id e faça push (aula 08 do Nível 2).reduce sobre a lista ordenada criando um objeto cuja chave é data.slice(0, 10).Trilha sugerida: Nível 2 (a partir da aula 08) · Tempo estimado: 8–12 h
Por que o total do carrinho nunca está escrito no HTML? Porque o carrinho é dado, e a tela é só um reflexo dele. Construa a vitrine de uma loja fictícia — o brechó, a livraria, a Café Cerrado — e um carrinho que sobrevive ao recarregar da página. Você vai reproduzir o raciocínio das lojas reais: uma lista de produtos, uma lista de itens escolhidos e funções que juntam as duas.
Funcionalidades mínimas
localStorage.Critérios de pronto
R$ com duas casas.{ id, quantidade }; preço e nome vêm sempre do catálogo (mudar o preço de um produto altera o total na próxima renderização).Evolução (escolha pelo menos uma)
fetch, com estados de carregamento e de erro (aula 10).produtos (imutável, vem do catálogo) e carrinho (muda com os cliques). Toda função do carrinho recebe as duas e devolve um número ou uma nova lista (aula 08 do Nível 2).reduce calcula o total em uma linha; find acha o produto pelo id; map e filter renderizam. Se você está escrevendo um for com índice, pare e pense.R$, crie new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }) uma vez e reutilize o formatador.dialog nativo: showModal() já cuida do Esc e do foco; pesquise "HTMLDialogElement" na MDN.Trilha sugerida: Nível 2 (a partir da aula 08) · Tempo estimado: 12–16 h
Some 0,1 com 0,2 no console do navegador. O resultado não é 0,3 — e é por isso que todo sistema financeiro sério guarda dinheiro em centavos inteiros. Neste projeto você vai controlar receitas e despesas de verdade (as suas ou as de um personagem), com categorias, saldo e filtros por mês, e descobrir na prática por que dinheiro e datas são os dois assuntos que mais geram bugs em produção.
Funcionalidades mínimas
localStorage.Critérios de pronto
0,10 + 0,20 mostra R$ 0,30, e a soma de cem lançamentos de R$ 0,10 dá exatamente R$ 10,00.1.234,56 e 1234.56 e rejeita letras, valores negativos ou zero, com mensagem no campo.Evolução (escolha pelo menos uma)
{ id, descricao, valorCentavos, tipo, categoriaId, data }. Converta na entrada (Math.round(valor * 100)) e formate na saída (Intl.NumberFormat com currency: 'BRL'); no meio, só inteiros.1.234,56, remova os pontos de milhar e troque a vírgula por ponto antes do Number() — uma expressão regular de duas linhas; escreva a sua e teste com os dois formatos.filter encadeados sobre o array original; nunca filtre em cima do resultado anterior, ou o usuário não consegue voltar.display: block nas linhas com td::before { content: attr(data-rotulo) } transforma cada linha em um card no celular — pesquise "responsive tables" no CSS-Tricks.Trilha sugerida: Nível 2 (a partir da aula 08) · Tempo estimado: 12–16 h
O laboratório tem três projetores e cinco kits de Arduino, e a "reserva" é um caderno na secretaria. Duas turmas chegam com o mesmo projetor reservado, e o caderno não diz quem está com o kit que não voltou. Este é o projeto recomendado como fechamento dos dez: junta formulários, regras de negócio, estados, datas e persistência, e é a base direta do Boss "Sistema de reservas com API e login".
Funcionalidades mínimas
localStorage e máquina de estados explícita (quais transições são permitidas).Critérios de pronto
haConflito(reservas, nova), podeTransitar(de, para)), testáveis no console.README.md descreve as regras com uma tabela de estados e transições.Evolução (escolha pelo menos uma)
inicioA < fimB && inicioB < fimA. Só isso. Desenhe os quatro casos numa linha do tempo antes de acreditar.{ disponivel: ['reservado', 'manutencao'], reservado: ['em_uso', 'disponivel'] }, e uma função podeTransitar(de, para) que o consulta — é o padrão State disfarçado, que o Nível 3 apresenta.equipamentoId; o estado atual do equipamento pode ser calculado a partir das reservas em vez de guardado — pense em qual escolha gera menos inconsistência.getTime() e formate só na hora de mostrar (as pistas do projeto de eventos valem aqui).Trilha sugerida: Nível 2 (a partir da aula 10) · Tempo estimado: 10–14 h
Até aqui todos os dados estavam no seu arquivo. Agora eles estão em outro computador, chegam quando querem, às vezes não chegam e às vezes chegam num formato diferente do que a documentação prometia. Escolha uma API pública sem chave — ViaCEP, BrasilAPI, REST Countries, Open-Meteo, PokéAPI — e construa um painel que consulta, apresenta e compara resultados. O que se avalia aqui não é o dado bonito na tela: é o que acontece nos 300 ms antes de ele chegar e no dia em que a API cai.
Funcionalidades mínimas
fetch e async/await.cardResultado(dado) usada em toda a página.Critérios de pronto
00000-000, país "Nárnia") mostra "não encontrado", diferente do erro de rede.async/await com try/catch; resposta com response.ok === false é tratada como erro.Evolução (escolha pelo menos uma)
localStorage, com página própria.erro e status 200).fetch só rejeita em falha de rede; 404 e 500 resolvem normalmente — teste response.ok e lance um Error com o status (aulas 09 e 10 do Nível 2).AbortController e cancele a anterior.{ chave: { dados, em } }: a chave é o termo normalizado e em é Date.now(); antes de chamar fetch, veja se existe e se ainda é recente.Trilha sugerida: Nível 2 (a partir da aula 16) ou Nível 3 (a partir da aula 12), com Deploy (capítulos 04, 05 e 08) · Tempo estimado: 24–32 h
O sistema de reservas em localStorage funciona só no navegador de quem cadastrou; no dia em que duas pessoas precisam ver a mesma agenda, ele vira um caderno digital. Este Boss leva o projeto anterior para onde ele pede para ir: um front SPA que fala com uma API Express 5, usuários que entram com a conta Google (ou Firebase), dados em MySQL ou Supabase, regra de conflito verificada no servidor — porque o cliente pode ser burlado — e tudo no ar, com HTTPS, numa URL que você manda para o coordenador do laboratório.
Funcionalidades mínimas
201, 400, 401, 403, 404, 409).google-auth-library, ou Firebase Auth com firebase-admin; rotas de escrita exigem token.mysql2/promise ou Supabase com RLS) com tabelas usuarios, equipamentos e reservas.fetch no Nível 2; Vue 3 + Vuetify + Pinia + Axios no Nível 3. Estados de carregamento e de erro em toda chamada.409 com a reserva conflitante.Critérios de pronto
curl ou Insomnia: POST /api/reservas sem token devolve 401; usuário comum tentando cancelar reserva de outro recebe 403.fetch simultâneos), resultam em uma 201 e uma 409 — nunca duas 201..env no .gitignore, chaves lidas de process.env; a chave service_role do Supabase nunca chega ao front.index.html), e a sessão sobrevive ao recarregar.https:// em front e API, cadeado válido, sem aviso de conteúdo misto; CORS restrito à origem do front.README.md com diagrama da arquitetura, instruções de instalação, variáveis de ambiente documentadas e a URL pública.Evolução (escolha pelo menos uma)
swagger-jsdoc e swagger-ui-express, publicada em /docs (aula 14 do Nível 3).docker compose com banco local (capítulo 07 de Deploy).curl. Só depois monte a tela — o front do projeto anterior é reaproveitável trocando localStorage por chamadas HTTP (aulas 11 a 13 do Nível 2 ou 08 e 09 do Nível 3).BEGIN, SELECT das reservas sobrepostas, INSERT, COMMIT) ou uma restrição no banco. Um if em JavaScript antes do INSERT deixa a janela aberta para a corrida.Authorization: Bearer, verifica o token, coloca req.usuario e chama next(); um segundo middleware exigirAdmin compara o perfil (aula 14 do Nível 2 ou aula 10 do Nível 3). Em Express 5, erros em handlers async caem sozinhos no tratador de erros.Trilha sugerida: Nível 3 (a partir da aula 08), com Deploy (capítulos 09 e 10) · Tempo estimado: 24–32 h
O painel do projeto de API pública chama a fonte direto do navegador; com duzentos usuários abrindo a página, a fonte bloqueia o IP e o painel morre. Este Boss coloca um servidor seu no meio: uma API Express que consulta a fonte, guarda em cache, expõe endpoints limpos e é testada, publicada e monitorada por um pipeline que roda sozinho a cada git push. É o ciclo completo do trabalho profissional: código, teste, integração contínua, deploy e observação.
Funcionalidades mínimas
/api/clima/:cidade, /api/indicadores) com resposta normalizada.localStorage ou Cache API), com indicação na resposta (cabeçalho X-Cache: HIT ou MISS).node --test ou Vitest, com Supertest).main, publica front e API./health, verificação externa (UptimeRobot ou similar) e log estruturado de erros.Critérios de pronto
X-Cache: MISS; a segunda responde HIT em menos de 50 ms (mostre no Insomnia ou com curl -w '%{time_total}').503 com mensagem clara — nunca 500 genérico nem travamento.README.md; um pull request com teste quebrado fica vermelho e não é publicado.prefers-reduced-motion desativa a animação do Chart.js.vite build sem aviso de tamanho de bundle./health público e monitorado, com alerta configurado; URL pública com HTTPS.Evolução (escolha pelo menos uma)
fontes/ (fala com a API externa), servicos/ (normaliza e cacheia) e rotas/ (HTTP). Os testes unitários cobrem servicos/ sem rede; os de integração usam Supertest com uma fonte falsa (aula 13 do Nível 3).Map de chave para { dados, expiraEm }; antes de ir à fonte, verifique Date.now() < expiraEm. Comece assim e só troque por algo externo se precisar de mais de uma instância.labels e datasets por props e destruindo a instância em onUnmounted — senão o gráfico duplica ao trocar de filtro (aulas 05 e 06 do Nível 3).checkout, setup-node com cache do npm, npm ci && npm test); faça-o ficar verde antes de adicionar o deploy (capítulo 09 de Deploy). Segredos entram em Settings → Secrets do repositório, nunca no YAML.💡 Dica Desafio final, válido para qualquer projeto desta página: escolha um contexto real — o laboratório da sua faculdade, a biblioteca do bairro, uma escola, o comércio da família — e entreviste ao menos uma pessoa que usaria o sistema. Pergunte o que ela faz hoje, onde dói, o que é proibido e o que acontece quando dá errado. Depois adapte as regras do projeto ao que ouviu: talvez a reserva precise de aprovação, talvez o cardápio mude por turno, talvez a nota mínima seja outra. Nesse momento você deixa de reproduzir uma interface e passa a resolver um problema, que é o que se paga a um desenvolvedor.
📌 Vale gravar Qualquer projeto desta página pode ser o seu projeto autoral — aquele que você constrói em paralelo às aulas e leva para o portfólio —, desde que siga a mesma arquitetura do projeto fio-condutor da trilha (Nível 1: site estático de várias páginas; Nível 2: SPA com API Express e login; Nível 3: Vue, Pinia, Express, banco e Firebase). Se você cursa a disciplina, combine a escolha com o professor antes de começar a unidade. Para saber se o projeto está no ponto, use o marco da unidade, na aula que a fecha: ele lista os requisitos e o checklist de qualidade.