← Programação Web

Programação Web I

Professor Ronaldo Lavestein
Web I • 4 horas

Capítulo 1 — Da necessidade ao primeiro arquivo

Fundamentos da Web, primeiro experimento e preparação do projeto.

Uma situação real para começar

O Café Aurora é um pequeno café de bairro. Hoje, informações como endereço, horário e produtos aparecem apenas em publicações de redes sociais e acabam se perdendo.

A proprietária quer um lugar simples e confiável onde qualquer pessoa possa conhecer o café. Antes de pensar em cores ou animações, precisamos responder: o que é um site, quais arquivos o formam e como o navegador consegue mostrá-los?

O que você vai aprender

  • Diferenciar Internet, Web, site e página.
  • Entender o papel do navegador, do servidor e do endereço.
  • Fazer um primeiro experimento com HTML.
  • Criar e abrir uma pasta de projeto no VS Code.
  • Criar o arquivo index.html com a estrutura mínima correta.
  • Visualizar, alterar e salvar a primeira página do Café Aurora.

Aula 1 — Como a Web funciona

Internet

É a infraestrutura que conecta computadores e redes ao redor do mundo.

Web

É um dos serviços que usa a Internet para disponibilizar páginas interligadas.

Site

É um conjunto organizado de páginas e recursos relacionados.

Página

É um documento individual que o navegador interpreta e apresenta.

As três tecnologias que encontraremos

  • HTML: organiza e dá significado ao conteúdo.
  • CSS: cuida da apresentação visual e do layout.
  • JavaScript: acrescenta comportamento e interação.

Neste começo, nosso foco será o HTML. Assim, cada tecnologia entra quando surge uma necessidade real no projeto.

O caminho de uma página

Pratique

Abra três sites que você usa. Em cada um, identifique o endereço, o navegador utilizado e pelo menos duas páginas diferentes do mesmo site.

Aula 2 — Seu primeiro contato com HTML

Antes de organizar um projeto completo, vamos experimentar duas marcações no Playground da MDN (abre em nova aba). Ele permite alterar um pequeno exemplo e observar o resultado imediatamente.

<h1>Café Aurora</h1>
<p>Café, encontros e bons momentos no coração do bairro.</p>

As marcações <h1> e <p> são chamadas de tags. A primeira indica o título principal; a segunda, um parágrafo. A maior parte dos elementos possui uma tag de abertura, conteúdo e uma tag de fechamento.

Experimente

  1. Troque o nome do café pelo nome de outro pequeno negócio.
  2. Altere a frase do parágrafo.
  3. Apague apenas uma tag de fechamento e observe o que acontece. Depois, corrija.
Por que não faremos todo o site aqui?

Esse ambiente é excelente para testar trechos curtos. Um site real precisa de pasta própria, documento completo, imagens, várias páginas e arquivos separados. Por isso, agora passaremos ao ambiente principal do projeto.

Aula 3 — Preparando o projeto no VS Code

  1. Instale o VS Code para Windows. Em computador compartilhado, siga a orientação do professor antes de instalar.
  2. Crie uma pasta chamada cafe-aurora.
  3. No VS Code, use Arquivo → Abrir Pasta e selecione essa pasta.
Um nome simples evita problemas.

Em nomes de arquivos e pastas para a Web, prefira letras minúsculas, sem espaços e sem acentos. Quando precisar separar palavras, use hífen: cafe-aurora.

No painel lateral do VS Code, crie o arquivo index.html. O nome index é tradicionalmente usado para a página inicial de uma pasta ou de um site.

VS Code com uma pasta de projeto aberta no painel Explorer à esquerda
A pasta aberta aparece no Explorer. Fonte: documentação oficial do VS Code.
Botão New File destacado no Explorer do VS Code para criar o arquivo index.html
Use o botão de novo arquivo e digite index.html. Fonte: documentação oficial do VS Code.
Pasta aberta não é arquivo aberto.

A pasta representa o projeto inteiro. Dentro dela ficarão os documentos, imagens, estilos e scripts que construiremos nos próximos capítulos.

Aula 4 — O primeiro documento completo

Digite no arquivo index.html:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Café Aurora</title>
</head>
<body>
  <h1>Café Aurora</h1>
  <p>Café, encontros e bons momentos no coração do bairro.</p>
</body>
</html>
TrechoFunção
<!DOCTYPE html>Informa que o documento usa o HTML atual.
<html lang="pt-BR">É o elemento principal e informa o idioma da página.
<head>Reúne configurações e informações sobre o documento.
charsetPermite exibir corretamente acentos e outros caracteres.
viewportPrepara a página para diferentes larguras de tela.
<title>Define o texto mostrado na aba do navegador.
<body>Contém o que será apresentado na página.

Visualizando a página

No painel de extensões do VS Code, procure Live Preview e confirme que o publicador é Microsoft. A página oficial da extensão está no Visual Studio Marketplace. Depois de instalada, clique com o botão direito em index.html e escolha Show Preview.

Visualização local não é publicação.

Neste momento, a página funciona apenas no seu computador. Colocar o site na Internet será uma etapa posterior do projeto.

Experimente

  1. Troque o texto de <title> e observe a aba.
  2. Altere o conteúdo de <h1> e <p>.
  3. Salve o arquivo e recarregue a visualização, se necessário.

Primeiro contato com DevTools

Na página aberta no navegador, clique com o botão direito sobre o título e escolha Inspecionar. No painel Elements, localize o h1, dê dois cliques em seu texto e troque-o temporariamente por “Café Aurora — teste”.

Chrome DevTools no painel Elements com o texto de um elemento HTML em edição
O painel Elements permite localizar e experimentar mudanças no HTML exibido. Fonte: documentação oficial do Chrome DevTools.
Inspecionar não altera seu arquivo.

A mudança existe somente naquela visualização. Recarregue a página e o texto original voltará. Para tornar a alteração permanente, edite e salve o index.html no VS Code.

Erros comuns

  • Salvar como index.html.txt.
  • Criar o arquivo fora da pasta cafe-aurora.
  • Esquecer o fechamento de uma tag.
  • Alterar o arquivo e não salvá-lo antes de verificar o resultado.
  • Digitar conteúdo visível fora do elemento <body>.

Prática aplicada

Antes de </body>, acrescente:

<h2>Uma pausa especial no seu dia</h2>
<p>Conheça cafés selecionados, receitas artesanais e um ambiente acolhedor.</p>

Desafio: reescreva o parágrafo como se você estivesse recebendo uma pessoa que nunca visitou o Café Aurora.

Verifique sua aprendizagem

  • Consigo explicar a diferença entre Internet, Web, site e página.
  • Sei qual é o papel do navegador e do servidor.
  • Criei e abri corretamente a pasta do projeto.
  • Meu arquivo se chama index.html.
  • A página possui a estrutura mínima e abre no navegador.
  • Consigo alterar um texto, salvar e visualizar o resultado.
Até aqui

Temos um primeiro documento correto e funcional. Ainda não usamos cores, imagens, menus, CSS ou JavaScript. A página ganhará conteúdo e estrutura antes de receber a identidade visual.

Transfira o aprendizado

Sem criar outro site completo, imagine uma barbearia, papelaria ou projeto comunitário. Escreva apenas um h1, um parágrafo de apresentação e um título de aba adequados a esse novo contexto.

Web I • 8 horas

Capítulo 2 — Construindo a página inicial

Conteúdo, hierarquia, listas, links, imagens e informações do documento.

O que a página precisa comunicar?

A primeira versão abre no navegador, mas ainda não responde às perguntas de quem visita o Café Aurora: o que ele oferece, quais são seus diferenciais, em que horário funciona e por que vale a pena conhecê-lo?

Neste capítulo, construiremos uma página inicial bem organizada. Ela ainda será simples visualmente, porque primeiro o conteúdo precisa estar correto e compreensível.

O que você vai aprender

  • Reconhecer elementos, tags, conteúdo e atributos.
  • Organizar títulos e subtítulos em uma hierarquia coerente.
  • Usar parágrafos, ênfase, listas e comentários.
  • Criar links internos e externos e entender caminhos relativos.
  • Inserir imagens com texto alternativo e dimensões.
  • Melhorar o título e a descrição da página.

Aula 1 — Elementos, atributos e hierarquia

Observe a estrutura de um elemento:

<p>Café, encontros e bons momentos.</p>
│                 │                  │
abertura        conteúdo          fechamento

Alguns elementos recebem atributos, informações adicionais escritas na tag de abertura. Em <html lang="pt-BR">, por exemplo, lang informa o idioma do documento.

Títulos formam uma hierarquia

O HTML oferece títulos de <h1> a <h6>. Eles não devem ser escolhidos pelo tamanho visual, mas pelo nível de cada assunto.

  • <h1>: assunto principal da página.
  • <h2>: grandes seções desse assunto.
  • <h3>: subdivisões de uma seção.
<h1>Café Aurora</h1>

<h2>Destaques da casa</h2>
<h3>Café coado</h3>
<p>Grãos selecionados e preparo cuidadoso.</p>

<h3>Pães artesanais</h3>
<p>Produção diária com ingredientes frescos.</p>
Aparência vem depois.

Não pule de <h1> para <h4> apenas porque o tamanho parece melhor. O CSS será usado mais adiante para controlar a aparência.

Aula 2 — Textos, listas e comentários

Use <strong> quando uma informação tiver forte importância e <em> para dar ênfase ao trecho.

<p><strong>Aberto de segunda a sábado.</strong></p>
<p>Um espaço <em>acolhedor</em> para sua pausa.</p>

Listas não ordenadas

Use <ul> quando a ordem dos itens não altera o sentido:

<ul>
  <li>Cafés especiais</li>
  <li>Pães artesanais</li>
  <li>Opções vegetarianas</li>
</ul>

Listas ordenadas

Use <ol> quando a sequência importa:

<ol>
  <li>Escolha seu café</li>
  <li>Selecione um acompanhamento</li>
  <li>Faça seu pedido no balcão</li>
</ol>

Comentários no código

<!-- Esta seção será ampliada quando o cardápio estiver pronto. -->

Comentários ajudam a registrar decisões para quem edita o arquivo e não aparecem na página. Mesmo assim, fazem parte do código enviado ao navegador: nunca coloque senhas, dados pessoais ou informações secretas neles.

Pratique

Transforme o texto abaixo em uma lista HTML adequada: “Wi-Fi, tomadas disponíveis, ambiente climatizado e espaço para bicicletas”. Explique por que escolheu <ul> ou <ol>.

Aula 3 — Links e caminhos

Um link é criado com o elemento <a>. O atributo href informa o destino.

Link para uma parte da própria página

<a href="#diferenciais">Conheça nossos diferenciais</a>

<h2 id="diferenciais">Nossos diferenciais</h2>

O valor após # deve ser igual ao id do elemento de destino. Cada id deve ser único na página.

Endereço absoluto

Um endereço absoluto contém o destino completo e costuma apontar para outro site:

<a href="https://developer.mozilla.org/pt-BR/">Consultar a MDN</a>

Caminho relativo

Um caminho relativo parte da localização do arquivo atual. Quando criarmos uma segunda página na mesma pasta, o link poderá ser:

<a href="cardapio.html">Ver cardápio</a>
Não crie um destino inexistente.

Esse link só deverá entrar no projeto quando o arquivo cardapio.html existir. Assim evitamos uma navegação quebrada.

Link e botão têm funções diferentes.

Use links para navegar até outro local ou documento. Botões serão usados para executar ações, principalmente quando começarmos o JavaScript. Evite href="#" como destino improvisado.

Pratique

Crie um link próximo ao início da página que leve até uma seção com id="destaques". Teste o link e confirme se o endereço do navegador passou a terminar em #destaques.

Aula 4 — Imagens e informações da página

Dentro da pasta cafe-aurora, crie uma pasta chamada img. Para realizar a atividade sem precisar procurar uma foto, você pode baixar a imagem preparada para o Café Aurora.

Imagem pronta para a atividade

Salão do Café Aurora com mesas de madeira, plantas e balcão ao fundo

Imagem didática criada especificamente para o projeto fictício Café Aurora.

↓ Baixar ambiente-cafe.webp

Salve o arquivo baixado dentro da pasta img. A organização ficará assim:

Em um projeto real, você também poderá usar uma foto produzida por você, fornecida pelo responsável do negócio ou obtida com autorização de uso. Renomeie o arquivo de modo descritivo antes de colocá-lo na pasta.

<img
  src="img/ambiente-cafe.webp"
  alt="Salão do Café Aurora com mesas de madeira e plantas"
  width="1200"
  height="800"
>
  • src indica onde o arquivo está.
  • alt comunica o conteúdo ou a função da imagem a quem não consegue vê-la.
  • width e height informam suas proporções e ajudam o navegador a reservar o espaço correto.
Um bom texto alternativo depende do contexto.

Descreva o que é importante para compreender a página, sem começar por “imagem de”. Se a imagem for apenas decorativa e não comunicar nada, use alt="".

Imagem com legenda

<figure>
  <img
    src="img/ambiente-cafe.webp"
    alt="Salão do Café Aurora com mesas de madeira e plantas"
    width="1200"
    height="800"
  >
  <figcaption>Um espaço tranquilo para conversar, estudar ou trabalhar.</figcaption>
</figure>

Título e descrição do documento

Dentro de <head>, atualize:

<title>Café Aurora — Café e produtos artesanais no bairro</title>
<meta
  name="description"
  content="Conheça o Café Aurora, seus cafés especiais, produtos artesanais, ambiente e horário de atendimento."
>

O título identifica a página na aba e em outros contextos. A descrição resume o conteúdo para mecanismos de busca e compartilhamentos, embora sua exibição não seja garantida.

Versão consolidada da página

Compare seu arquivo com esta versão. Não copie apenas por copiar: identifique a função de cada elemento.

Código completo para conferência
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta
    name="description"
    content="Conheça o Café Aurora, seus cafés especiais, produtos artesanais, ambiente e horário de atendimento."
  >
  <title>Café Aurora — Café e produtos artesanais no bairro</title>
</head>
<body>
  <h1>Café Aurora</h1>
  <p>Café, encontros e bons momentos no coração do bairro.</p>

  <a href="#destaques">Conheça nossos destaques</a>

  <h2 id="destaques">Destaques da casa</h2>

  <h3>Café coado</h3>
  <p>Grãos selecionados e preparo cuidadoso.</p>

  <h3>Pães artesanais</h3>
  <p>Produção diária com ingredientes frescos.</p>

  <h2>Nossos diferenciais</h2>
  <ul>
    <li>Cafés especiais</li>
    <li>Pães artesanais</li>
    <li>Opções vegetarianas</li>
    <li>Wi-Fi e tomadas disponíveis</li>
  </ul>

  <figure>
    <img
      src="img/ambiente-cafe.webp"
      alt="Salão do Café Aurora com mesas de madeira e plantas"
      width="1200"
      height="800"
    >
    <figcaption>Um espaço tranquilo para conversar, estudar ou trabalhar.</figcaption>
  </figure>

  <h2>Horário de atendimento</h2>
  <p><strong>De segunda a sábado, das 8h às 19h.</strong></p>
</body>
</html>

Valide o HTML antes de avançar

O validador ajuda a encontrar erros de marcação que o navegador pode tentar corrigir silenciosamente. Salve seu arquivo e abra o HTML Checker do W3C (abre em nova aba).

  1. Escolha a opção de enviar o arquivo ou copie o conteúdo de index.html para o campo de texto.
  2. Inicie a verificação e leia a primeira mensagem de erro, observando a linha indicada.
  3. Corrija primeiro problemas simples, como uma tag sem fechamento ou um id repetido.
  4. Salve e valide novamente até não restarem erros de marcação.
Uma mensagem pode provocar outras.

Comece pelo primeiro erro: ao fechar uma tag esquecida, várias mensagens seguintes podem desaparecer. A validação complementa — e não substitui — a leitura do conteúdo, os testes no navegador e a verificação de acessibilidade.

Erros comuns para revisar

  • Escolher títulos pelo tamanho visual e quebrar a hierarquia.
  • Usar listas apenas para produzir recuos.
  • Repetir o mesmo id em vários elementos.
  • Criar um link para um arquivo que ainda não existe.
  • Usar caminho local do computador, como C:\Fotos\..., no atributo src.
  • Esquecer o alt ou escrever uma descrição que não ajuda.
  • Usar imagens muito grandes sem necessidade ou sem autorização.

Projeto aplicado — entrega do capítulo

Complete a página inicial do Café Aurora com:

  • um título principal e uma frase de apresentação;
  • pelo menos duas seções com hierarquia correta;
  • uma lista adequada ao conteúdo;
  • um link interno funcionando;
  • uma imagem autorizada, com nome organizado, texto alternativo e dimensões;
  • título de aba e descrição coerentes com a página.

Depois, peça a um colega que navegue pela página sem explicar o código. Registre uma informação que ele encontrou facilmente e uma que precisou procurar.

Verifique sua aprendizagem

  • Consigo explicar o que são elemento, tag, conteúdo e atributo.
  • Meus títulos representam a organização do conteúdo.
  • Sei decidir entre lista ordenada e não ordenada.
  • Meus links têm destinos reais e funcionam.
  • Sei construir um caminho relativo para uma imagem.
  • A imagem possui texto alternativo útil e dimensões informadas.
  • A página comunica o que o Café Aurora oferece.
Até aqui

A página já tem conteúdo significativo e navegação interna, mas sua aparência ainda é simples. HTML organiza o conteúdo; CSS cuidará do visual; JavaScript acrescentará comportamentos. No próximo capítulo, criaremos outras páginas e uma navegação estruturada.

Transfira o aprendizado

Escolha outro negócio local e escreva somente a hierarquia de títulos de sua página inicial. Inclua um h1 e dois h2, explicando quais perguntas reais cada seção responde.

Web I • 8 horas

Capítulo 3 — Organizando o site em páginas

Da página única a um site com páginas conectadas, estrutura semântica e navegação consistente.

Quando uma página já não é suficiente

No capítulo anterior, a página inicial do Café Aurora ficou útil: ela apresenta o negócio, mostra destaques, informa horários e já possui uma imagem. Mas, à medida que o conteúdo cresce, encontrar uma informação específica começa a exigir mais procura.

Imagine três visitantes: um quer apenas consultar o cardápio, outro deseja conhecer a história do café e um terceiro procura endereço e horário. Em vez de empilhar tudo na mesma página, vamos separar essas necessidades em páginas próprias e conectá-las de forma clara.

O que você vai aprender

  • Planejar as páginas de acordo com o que cada visitante precisa encontrar.
  • Criar e nomear documentos dentro de uma estrutura de arquivos previsível.
  • Conectar as páginas com um menu e caminhos relativos.
  • Organizar as grandes regiões com header, nav, main, section e footer.
  • Usar article e div quando a necessidade do conteúdo justificar cada um.
  • Manter títulos, descrições, imagens e navegação coerentes entre as páginas.
  • Testar o site e localizar links ou caminhos quebrados.

Aula 1 — Planejando antes de programar

Antes de abrir novos arquivos no VS Code, pense no que cada pessoa procura. Quem deseja apenas o horário não deveria precisar percorrer a história e todos os produtos; quem abriu o cardápio espera encontrar os itens sem procurar no restante da página. Para o Café Aurora, quatro páginas atendem bem essas necessidades sem fragmentar demais o site:

PáginaNecessidade atendidaArquivo
InícioEntender rapidamente o que é o Café Aurora.index.html
CardápioConhecer produtos e destaques da casa.cardapio.html
SobreConhecer a história e a proposta do negócio.sobre.html
ContatoEncontrar endereço, horários e formas de contato.contato.html

Mapa do site

Esse desenho simples é um mapa do site. Antes de escrever código, ele permite enxergar quais páginas existirão e qual pergunta cada uma deverá responder. No Café Aurora, todas serão alcançadas pelo mesmo menu principal.

Estrutura de arquivos

Agora transforme o plano em arquivos. No explorador do VS Code, crie cardapio.html, sobre.html e contato.html na mesma pasta de index.html.

Cada página é um documento completo.

Todos os arquivos precisam de DOCTYPE, html, head e body. Copiar a estrutura correta economiza tempo, mas cada página precisa assumir sua própria identidade: título, descrição e conteúdo devem dizer claramente onde o visitante está.

Título e descrição de cada página

ArquivotitleResumo da descrição
index.htmlCafé Aurora — Café e produtos artesanais no bairroApresentação geral do café.
cardapio.htmlCardápio — Café AuroraCafés e produtos artesanais.
sobre.htmlSobre — Café AuroraHistória e proposta do negócio.
contato.htmlContato — Café AuroraEndereço, horários e contato.

Pratique

Para cada página, escreva em uma frase o que o visitante espera encontrar. Se uma informação não se encaixar claramente em nenhuma delas, decida se está faltando uma página ou se o conteúdo não é necessário.

Aula 2 — Conectando as páginas e dando significado à estrutura

Agora existem quatro arquivos, mas arquivos isolados ainda não formam uma boa experiência de navegação. Se alguém estiver no Cardápio, precisa conseguir voltar ao Início, conhecer a história ou encontrar o Contato sem procurar outro caminho. Essa necessidade é o motivo de criarmos um menu comum às páginas.

Ao mesmo tempo, o documento passa a ter regiões com papéis diferentes: uma área de identificação, a navegação, o conteúdo principal, assuntos internos e informações finais. O HTML possui elementos semânticos para deixar esses papéis explícitos.

ElementoQuando usar
<header>Conteúdo introdutório, como identificação do site e navegação inicial.
<nav>Conjunto importante de links de navegação.
<main>Conteúdo principal e exclusivo daquela página. Normalmente há apenas um por documento.
<section>Grupo temático de conteúdo, geralmente identificado por um título.
<footer>Informações finais, como autoria, direitos ou contato complementar.
Semântica não significa aparência.

Usar header, nav ou main não deixa a página automaticamente mais bonita. Esses elementos explicam o papel do conteúdo; a aparência será construída com CSS.

O menu conecta o site; a semântica explica cada região

Como os quatro documentos HTML estão na mesma pasta, caminhos como href="cardapio.html" partem da página atual e encontram o arquivo vizinho. Agora aplique essa navegação e reorganize o body de index.html:

Estrutura completa do body para conferência
<body>
  <header>
    <p><a href="index.html">Café Aurora</a></p>

    <nav aria-label="Navegação principal">
      <ul>
        <li><a href="index.html" aria-current="page">Início</a></li>
        <li><a href="cardapio.html">Cardápio</a></li>
        <li><a href="sobre.html">Sobre</a></li>
        <li><a href="contato.html">Contato</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <h1>Café Aurora</h1>
    <p>Café, encontros e bons momentos no coração do bairro.</p>

    <section aria-labelledby="destaques">
      <h2 id="destaques">Destaques da casa</h2>
      <h3>Café coado</h3>
      <p>Grãos selecionados e preparo cuidadoso.</p>
      <h3>Pães artesanais</h3>
      <p>Produção diária com ingredientes frescos.</p>
    </section>

    <section aria-labelledby="ambiente">
      <h2 id="ambiente">Um ambiente acolhedor</h2>
      <figure>
        <img
          src="img/ambiente-cafe.webp"
          alt="Salão do Café Aurora com mesas de madeira e plantas"
          width="1536"
          height="1024"
        >
        <figcaption>Um espaço para conversar, estudar ou trabalhar.</figcaption>
      </figure>
    </section>
  </main>

  <footer>
    <p>&copy; 2026 Café Aurora. Projeto educacional fictício.</p>
  </footer>
</body>
  • aria-label dá um nome claro à região de navegação.
  • aria-labelledby liga uma região ao título indicado pelo respectivo id.
  • aria-current="page" informa qual link representa a página atual.
  • O caractere escrito como &copy; é exibido como © no navegador.

Pratique

Abra a página e use o menu para ir ao Cardápio e voltar ao Início. Depois, observe o HTML e explique por que os links estão em nav, o conteúdo exclusivo está em main e os dois assuntos principais estão em section.

Aula 3 — Construindo a página do cardápio

Com as páginas conectadas, surge outra necessidade de organização. O Cardápio reunirá vários produtos dentro do mesmo assunto, mas cada produto possui nome, imagem, descrição e preço e pode ser entendido como uma unidade. É nesse contexto que article passa a fazer sentido.

Baixe as três imagens e coloque-as na pasta img do seu projeto.

Depois de baixar as imagens, construa o arquivo cardapio.html:

Código completo de cardapio.html para conferência
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta
    name="description"
    content="Conheça os cafés e produtos artesanais do Café Aurora."
  >
  <title>Cardápio — Café Aurora</title>
</head>
<body>
  <header>
    <p><a href="index.html">Café Aurora</a></p>

    <nav aria-label="Navegação principal">
      <ul>
        <li><a href="index.html">Início</a></li>
        <li><a href="cardapio.html" aria-current="page">Cardápio</a></li>
        <li><a href="sobre.html">Sobre</a></li>
        <li><a href="contato.html">Contato</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <h1>Cardápio</h1>
    <p>Conheça alguns dos destaques preparados no Café Aurora.</p>

    <section aria-labelledby="destaques-cardapio">
      <h2 id="destaques-cardapio">Destaques da casa</h2>

      <article>
        <h3>Café coado</h3>
        <figure>
          <img
            src="img/cafe-coado.webp"
            alt="Xícara de café coado ao lado de uma pequena jarra"
            width="1536"
            height="1024"
            loading="lazy"
          >
          <figcaption>Café preparado na hora com grãos selecionados.</figcaption>
        </figure>
        <p><strong>R$ 8,00</strong></p>
      </article>

      <article>
        <h3>Pão artesanal</h3>
        <figure>
          <img
            src="img/pao-artesanal.webp"
            alt="Pão artesanal cortado sobre uma mesa de madeira"
            width="1536"
            height="1024"
            loading="lazy"
          >
          <figcaption>Pão de fermentação lenta produzido diariamente.</figcaption>
        </figure>
        <p><strong>R$ 12,00</strong></p>
      </article>

      <article>
        <h3>Bolo caseiro de laranja</h3>
        <figure>
          <img
            src="img/bolo-caseiro.webp"
            alt="Bolo caseiro de laranja com uma fatia servida"
            width="1536"
            height="1024"
            loading="lazy"
          >
          <figcaption>Receita caseira preparada com laranjas frescas.</figcaption>
        </figure>
        <p><strong>R$ 9,00 a fatia</strong></p>
      </article>
    </section>
  </main>

  <footer>
    <p>&copy; 2026 Café Aurora. Projeto educacional fictício.</p>
  </footer>
</body>
</html>
O que a estrutura do cardápio nos ajuda a distinguir
  • section reúne o assunto “Destaques da casa”.
  • Cada produto está em article porque forma uma unidade que pode ser compreendida separadamente.
  • Quando for necessário agrupar elementos apenas para organização ou layout, sem um significado próprio para o conteúdo, div é o agrupamento genérico adequado.
  • O atributo loading="lazy" permite que o navegador adie o carregamento de imagens que ainda estão longe da área visível. Use-o principalmente abaixo da área inicial; a imagem principal que aparece logo ao abrir a página normalmente não deve ser carregada dessa forma.
  • Os valores são fictícios e servem apenas para contextualizar o exercício.

Experimente

Abra index.html, acesse o Cardápio pelo menu e retorne ao Início. Depois, acesse diretamente cardapio.html pelo explorador do VS Code e confirme se as três imagens aparecem.

Aula 4 — Completando o site sem perder a orientação

O Cardápio já mostra a vantagem de separar o conteúdo em páginas. Agora precisamos garantir que, ao entrar em Sobre ou Contato, o visitante continue sentindo que está no mesmo site. Por isso, as páginas restantes reutilizarão cabeçalho, menu e rodapé, mudando apenas o conteúdo principal e as informações próprias de cada documento.

Conteúdo principal de sobre.html

<main>
  <h1>Sobre o Café Aurora</h1>

  <section aria-labelledby="nossa-historia">
    <h2 id="nossa-historia">Nossa história</h2>
    <p>O Café Aurora nasceu do desejo de criar um ponto de encontro
      acolhedor para as pessoas do bairro.</p>
    <p>Valorizamos o preparo cuidadoso, os ingredientes frescos e o
      atendimento próximo.</p>
  </section>

  <section aria-labelledby="nosso-espaco">
    <h2 id="nosso-espaco">Nosso espaço</h2>
    <figure>
      <img
        src="img/ambiente-cafe.webp"
        alt="Salão do Café Aurora com mesas de madeira e plantas"
        width="1536"
        height="1024"
        loading="lazy"
      >
      <figcaption>Um ambiente tranquilo no coração do bairro.</figcaption>
    </figure>
  </section>
</main>

No menu de sobre.html, retire aria-current do link anterior e coloque-o no link Sobre:

<li><a href="sobre.html" aria-current="page">Sobre</a></li>

Conteúdo principal de contato.html

<main>
  <h1>Contato e localização</h1>

  <section aria-labelledby="onde-estamos">
    <h2 id="onde-estamos">Onde estamos</h2>
    <address>
      <p>Rua das Flores, 123 — Centro</p>
      <p>Telefone: <a href="tel:+551100000000">(11) 0000-0000</a></p>
      <p>E-mail: <a href="mailto:contato@cafeaurora.example">contato@cafeaurora.example</a></p>
    </address>
  </section>

  <section aria-labelledby="horarios">
    <h2 id="horarios">Horário de atendimento</h2>
    <ul>
      <li>Segunda a sexta: das 8h às 19h</li>
      <li>Sábado: das 8h às 18h</li>
      <li>Domingo: fechado</li>
    </ul>
  </section>
</main>
Os dados são fictícios.

O domínio .example é reservado para exemplos. Em um projeto real, substitua endereço, telefone e e-mail somente por informações confirmadas pelo responsável do negócio.

O elemento address representa informações de contato relacionadas à página ou ao conteúdo mais próximo. Ele não deve ser usado apenas para criar texto em itálico.

Links que iniciam uma ação

tel: pode abrir o aplicativo de chamadas e mailto: pode abrir o aplicativo de e-mail configurado. Eles não fazem a ligação nem enviam a mensagem automaticamente, e o texto visível deve continuar informando o destino.

O menu precisa ser igual em todas as páginas

Imagine clicar em Contato e encontrar os itens do menu em outra ordem ou com nomes diferentes. Mesmo que os links funcionem, a navegação passa a parecer instável. Como este é um site estático, o menu está escrito em cada arquivo: sempre que acrescentar ou renomear uma página, revise todas as cópias. A única diferença será a posição de aria-current="page".

Teste como visitante

  1. Comece em index.html e abra cada item do menu.
  2. Em cada página, clique novamente nos quatro links.
  3. Confirme o título da aba, o título principal e a indicação da página atual.
  4. Verifique se nenhuma imagem está quebrada.
  5. Use o botão Voltar do navegador e observe o histórico de páginas.

Erros comuns para revisar

  • Criar uma nova página fora da pasta cafe-aurora.
  • Usar espaços, acentos ou letras maiúsculas de forma inconsistente nos nomes dos arquivos.
  • Copiar uma página e esquecer de alterar title, descrição ou h1.
  • Colocar mais de um main no mesmo documento.
  • Usar section apenas como uma caixa sem assunto ou título.
  • Deixar aria-current="page" em dois links do mesmo menu.
  • Corrigir o menu em uma página e esquecer as demais.
  • Escrever caminhos como img\cafe-coado.webp; na Web, use a barra /.

Projeto aplicado — entrega do capítulo

Entregue o site do Café Aurora com:

  • quatro páginas completas e corretamente nomeadas;
  • título e descrição específicos em cada documento;
  • estrutura com header, nav, main e footer;
  • menu igual e funcional em todas as páginas;
  • página atual corretamente identificada;
  • cardápio com três produtos, imagens e textos alternativos;
  • página Sobre com história e imagem do ambiente;
  • página Contato com dados e horários claramente organizados.

Troque de projeto com um colega. Ele deverá encontrar o cardápio, a história e o horário de sábado sem receber instruções. Registre qualquer dificuldade observada.

Verifique sua aprendizagem

  • Consigo explicar por que o conteúdo foi separado em quatro páginas.
  • Sei criar e nomear arquivos dentro da pasta correta.
  • Sei escolher entre section, article e div.
  • Minha página possui apenas um conteúdo principal em main.
  • O menu funciona a partir de qualquer página.
  • A página atual está indicada corretamente.
  • As imagens aparecem e possuem textos alternativos coerentes.
  • Consigo localizar e corrigir um link ou caminho quebrado.
Até aqui

O Café Aurora já possui um site organizado e navegável, mas ainda não recebe dados do visitante. No próximo capítulo, criaremos o formulário de contato e aprofundaremos acessibilidade, rótulos, foco e validação dos campos.

Transfira o aprendizado

Para uma oficina de bicicletas ou biblioteca comunitária, esboce apenas um mapa com três páginas e indique qual delas teria aria-current="page" em cada visita. Não desenvolva o segundo site.

Web I • 8 horas

Capítulo 4 — Formulários e acessibilidade

Coleta de dados, validação nativa, rótulos, instruções e testes por teclado.

O visitante precisa conseguir entrar em contato

O site do Café Aurora já informa produtos, história, endereço e horários. Entretanto, quem deseja tirar uma dúvida, consultar uma encomenda ou enviar uma sugestão ainda precisa sair do site.

Vamos preparar um formulário de contato compreensível para diferentes pessoas e dispositivos. Ele terá campos bem identificados e validação feita pelo navegador. O envio definitivo dependerá de um servidor, assunto que será desenvolvido no Web II.

O que você vai aprender

  • Entender o caminho dos dados de um formulário.
  • Reconhecer a função de form, label, input, select, textarea e button.
  • Relacionar rótulos e campos com for e id.
  • Organizar campos relacionados com fieldset e legend.
  • Usar tipos de campo, name, autocomplete, required, limites e instruções.
  • Testar o formulário com teclado, ampliação e validação nativa.
  • Distinguir validação no navegador de validação no servidor.

Aula 1 — O que acontece com os dados

Um formulário é uma região com controles interativos usados para coletar informações. Quando o visitante envia os dados, o navegador prepara uma solicitação para um endereço capaz de recebê-los.

O limite deste capítulo

No Web I, construiremos a parte que aparece no navegador. A rota do servidor que recebe, protege, armazena ou encaminha os dados será criada no Web II. Nenhum formulário apenas em HTML consegue, sozinho, enviar uma mensagem para a empresa.

A estrutura inicial

<form action="/enviar-contato" method="post">
  <!-- Os campos serão colocados aqui. -->
</form>
RecursoFunção
actionIndica a rota que receberá os dados. /enviar-contato é uma rota futura e ainda não funciona no site estático.
method="post"Envia os dados no corpo da solicitação. É adequado para mensagens e alterações, mas não substitui HTTPS nem segurança no servidor.
method="get"Acrescenta os dados ao endereço. É comum em buscas e filtros compartilháveis, não em mensagens com dados pessoais.
Use somente dados fictícios nos testes.

Não envie nomes, telefones, e-mails ou mensagens reais para serviços de demonstração. Um projeto deve solicitar apenas os dados realmente necessários e informar claramente como serão utilizados.

Pratique

Classifique estas situações: busca de produtos, envio de mensagem, filtro por categoria e alteração de cadastro. Indique quais combinam com GET e quais combinam com POST, justificando cada escolha.

Aula 2 — Campos identificados corretamente

Todo campo precisa de um rótulo visível que explique o que deve ser informado. A associação explícita usa o atributo for no label e um id igual no controle.

<label for="nome">Nome completo (obrigatório)</label>
<input
  type="text"
  id="nome"
  name="nome"
  autocomplete="name"
  minlength="2"
  required
>
AtributoO que informa
typeDefine o tipo de controle e pode oferecer teclado e validação adequados.
idIdentifica o elemento na página e permite ligá-lo ao rótulo e às instruções.
nameÉ o nome usado para representar o dado quando o formulário é enviado.
autocompleteExplica ao navegador qual informação é esperada e permite preenchimento assistido.
minlength e maxlengthDefinem limites de quantidade de caracteres.
requiredInforma ao navegador e às tecnologias assistivas que o campo é obrigatório.

E-mail e telefone

<div>
  <label for="email">E-mail (obrigatório)</label>
  <input
    type="email"
    id="email"
    name="email"
    autocomplete="email"
    required
  >
</div>

<div>
  <label for="telefone">Telefone (opcional)</label>
  <p id="ajuda-telefone">Inclua o código de área.</p>
  <input
    type="tel"
    id="telefone"
    name="telefone"
    autocomplete="tel"
    aria-describedby="ajuda-telefone"
  >
</div>
  • type="email" permite ao navegador verificar o formato básico e oferecer teclado apropriado.
  • type="tel" favorece o teclado numérico em dispositivos móveis, mas não impõe sozinho um formato universal.
  • aria-describedby conecta o campo a uma instrução complementar já visível.
Placeholder não substitui label.

O texto de placeholder desaparece durante a digitação e pode ter baixo contraste. Use um rótulo permanente e coloque formatos ou orientações em texto visível.

Experimente

Crie os campos Nome, E-mail e Telefone. Clique em cada rótulo e confirme se o cursor vai para o campo correspondente. Depois, altere um for de propósito, observe a falha e corrija-o.

Aula 3 — Montando o formulário completo

No arquivo contato.html, mantenha as informações de localização e acrescente esta seção dentro de main:

Código completo do formulário para conferência
<section aria-labelledby="fale-conosco">
  <h2 id="fale-conosco">Fale conosco</h2>
  <p id="orientacao-formulario">
    Os campos identificados como obrigatórios devem ser preenchidos.
  </p>

  <form
    action="/enviar-contato"
    method="post"
    aria-describedby="orientacao-formulario"
  >
    <fieldset>
      <legend>Seus dados</legend>

      <div>
        <label for="nome">Nome completo (obrigatório)</label>
        <input
          type="text"
          id="nome"
          name="nome"
          autocomplete="name"
          minlength="2"
          required
        >
      </div>

      <div>
        <label for="email">E-mail (obrigatório)</label>
        <input
          type="email"
          id="email"
          name="email"
          autocomplete="email"
          required
        >
      </div>

      <div>
        <label for="telefone">Telefone (opcional)</label>
        <p id="ajuda-telefone">Inclua o código de área.</p>
        <input
          type="tel"
          id="telefone"
          name="telefone"
          autocomplete="tel"
          aria-describedby="ajuda-telefone"
        >
      </div>
    </fieldset>

    <fieldset>
      <legend>Sua mensagem</legend>

      <div>
        <label for="assunto">Assunto (obrigatório)</label>
        <select id="assunto" name="assunto" required>
          <option value="" selected disabled>Selecione uma opção</option>
          <option value="duvida">Dúvida</option>
          <option value="encomenda">Encomenda</option>
          <option value="sugestao">Sugestão</option>
        </select>
      </div>

      <div>
        <label for="mensagem">Mensagem (obrigatória)</label>
        <p id="ajuda-mensagem">Escreva entre 10 e 500 caracteres.</p>
        <textarea
          id="mensagem"
          name="mensagem"
          rows="6"
          minlength="10"
          maxlength="500"
          aria-describedby="ajuda-mensagem"
          required
        ></textarea>
      </div>
    </fieldset>

    <button type="submit">Enviar mensagem</button>
  </form>
</section>
Por que agrupar?
  • fieldset reúne controles relacionados.
  • legend dá um título ao grupo.
  • select oferece opções predefinidas; cada option representa uma escolha.
  • textarea recebe textos com várias linhas; rows sugere sua altura inicial.
  • button type="submit" solicita a validação e o envio do formulário.

Teste o formulário no próprio módulo

Use somente dados inventados. A demonstração verifica os campos no navegador e não transmite nenhuma informação.

Seus dados

Inclua o código de área.

Sua mensagem

Escreva entre 10 e 500 caracteres.

Aula 4 — Acessibilidade e testes reais

Acessibilidade não é um recurso acrescentado no final. Ela começa na escolha de elementos nativos, na ordem lógica do documento e em instruções que não dependem apenas de cor, posição ou memória.

Imagem para reconhecer o local

Uma foto da fachada pode ajudar quem visitará o Café Aurora pela primeira vez. Baixe-a e salve na pasta img.

Fachada do Café Aurora

Fachada clara do Café Aurora com porta verde, janelas de madeira, plantas e mesas externas

Imagem didática criada para a página de contato do projeto fictício.

↓ Baixar fachada-cafe.webp

Acrescente a imagem à seção “Onde estamos”:

<figure>
  <img
    src="img/fachada-cafe.webp"
    alt="Fachada clara do Café Aurora com porta verde, janelas de madeira,
      plantas e mesas externas"
    width="1536"
    height="1024"
    loading="lazy"
  >
  <figcaption>Reconheça nossa entrada pela porta verde.</figcaption>
</figure>

Teste usando somente o teclado

  1. Pressione Tab para avançar pelos links, campos e botão.
  2. Use Shift + Tab para voltar.
  3. Confirme se a ordem acompanha a leitura da página.
  4. Observe se o elemento ativo possui uma indicação visual de foco.
  5. Use as setas para escolher uma opção em select.
  6. Pressione Enter no botão de envio.

Teste a validação nativa

  1. Tente enviar sem preencher campos obrigatórios.
  2. Digite um e-mail sem formato válido.
  3. Escreva uma mensagem com menos de 10 caracteres.
  4. Corrija cada problema seguindo a orientação do navegador.
Validação no navegador não basta.

As regras HTML ajudam o visitante e reduzem erros, mas podem ser contornadas. Quando o servidor for construído, ele deverá validar novamente todos os dados antes de processá-los.

Revisão de acessibilidade

  • Mantenha rótulos visíveis e associados aos controles.
  • Identifique campos obrigatórios em texto, além do atributo required.
  • Não remova a indicação visual de foco.
  • Não dependa somente de cor para comunicar erro ou obrigação.
  • Use HTML nativo antes de acrescentar atributos ARIA.
  • Amplie a página para 200% e confirme que ainda é possível ler e preencher tudo.

Erros comuns para revisar

  • Usar placeholder como único rótulo.
  • Criar label e campo com valores diferentes em for e id.
  • Esquecer o atributo name, fazendo o dado chegar sem identificação ao servidor.
  • Marcar um campo como obrigatório sem avisar isso no texto do rótulo.
  • Usar type="text" para tudo e perder recursos nativos de e-mail e telefone.
  • Criar regras rígidas para nomes ou telefones que rejeitam dados legítimos.
  • Remover o contorno de foco sem oferecer substituição visível.
  • Acreditar que POST ou validação HTML tornam os dados seguros.
  • Testar o formulário com informações pessoais verdadeiras.

Projeto aplicado — entrega do capítulo

Atualize contato.html com:

  • foto da fachada com texto alternativo e legenda;
  • formulário dividido em “Seus dados” e “Sua mensagem”;
  • rótulo visível e associação correta em todos os campos;
  • tipos, nomes, preenchimento assistido e obrigatoriedade adequados;
  • instruções visíveis para telefone e mensagem;
  • seleção de assunto e mensagem com limites;
  • teste completo por teclado e validação nativa.

Peça a um colega que preencha o formulário usando somente o teclado e dados inventados. Observe sem orientá-lo e registre qualquer campo ou instrução que não tenha ficado claro.

Verifique sua aprendizagem

  • Consigo explicar por que o formulário ainda não envia mensagens de verdade.
  • Sei diferenciar GET e POST em situações básicas.
  • Todos os campos possuem rótulos corretamente associados.
  • Sei para que servem id, name e autocomplete.
  • Consigo agrupar campos com fieldset e legend.
  • O formulário pode ser percorrido em ordem lógica usando Tab.
  • As orientações permanecem visíveis durante o preenchimento.
  • Entendo que o servidor deverá validar os dados novamente.

Referências para consulta

Até aqui

O site já oferece conteúdo organizado, navegação e um formulário acessível no lado do navegador. A aparência ainda é básica de propósito. No próximo capítulo, começaremos a identidade visual com CSS, preservando toda a estrutura construída.

Transfira o aprendizado

Imagine um formulário curto para solicitar orçamento de um conserto. Liste três campos, escolha o type de cada um e indique quais seriam obrigatórios. Não programe outro formulário completo.

Web I • 8 horas

Capítulo 5 — Identidade visual com CSS

Arquivo de estilos, seletores, cascata, cores, tipografia, modelo de caixa e componentes visuais.

O site funciona, mas ainda não comunica a personalidade do negócio

As páginas do Café Aurora já possuem conteúdo, navegação, imagens e formulário. Sem uma identidade visual coerente, porém, o site parece provisório e o visitante encontra mais dificuldade para reconhecer títulos, ações e informações importantes.

Vamos criar um único arquivo CSS para todas as páginas. A aparência mudará sem retirar o significado do HTML: tons acolhedores representarão o café, a tipografia continuará legível e os elementos interativos manterão foco visível.

O que você vai aprender

  • Explicar a função do CSS e conectá-lo corretamente ao HTML.
  • Reconhecer seletor, propriedade, valor, declaração e regra.
  • Usar seletores de elemento, classe e estado sem depender de estilos em linha.
  • Entender os fundamentos de cascata, herança e especificidade.
  • Definir cores, tipografia e espaçamento com legibilidade e contraste.
  • Compreender conteúdo, preenchimento, borda e margem no modelo de caixa.
  • Criar variáveis CSS e componentes visuais reutilizáveis.
  • Aplicar a mesma identidade às quatro páginas do Café Aurora.

Aula 1 — Do HTML sem estilo ao primeiro arquivo CSS

HTML descreve a estrutura e o significado do conteúdo. CSS descreve como esse conteúdo será apresentado. Separar essas responsabilidades permite alterar o visual de várias páginas sem repetir estilos em cada documento.

Organize o projeto

Crie a pasta css e, dentro dela, o arquivo styles.css. Em seguida, acrescente esta linha dentro de head em cada uma das quatro páginas:

<link rel="stylesheet" href="css/styles.css">
Como o navegador encontra o arquivo?

rel="stylesheet" informa a relação com uma folha de estilos. href="css/styles.css" indica o caminho partindo do documento HTML. Como as quatro páginas estão na raiz do projeto, o mesmo caminho funciona em todas elas.

A primeira regra

body {
  background-color: #fff8ed;
  color: #20241f;
}
ParteFunção no exemplo
bodySeletor: escolhe o elemento que receberá os estilos.
background-colorPropriedade: indica o aspecto que será alterado.
#fff8edValor: define a nova cor de fundo.
propriedade: valor;Declaração: uma instrução dentro das chaves.
{ ... }Bloco: reúne as declarações aplicadas pelo seletor.

Experimente

  1. Teste a regra no Playground da MDN (abre em nova aba).
  2. Troque apenas a cor de fundo e observe o resultado.
  3. Remova um ponto e vírgula e depois uma chave. Compare os efeitos e restaure o código correto.
  4. Copie a regra para styles.css e confirme a mudança nas quatro páginas.

Aula 2 — Seletores, classes e conflitos

Um seletor precisa alcançar o elemento certo sem tornar o código difícil de manter. Elementos são adequados para estilos gerais; classes representam funções visuais que podem ser reutilizadas.

/* Seletores de elemento: base geral */
body {
  font-family: Arial, sans-serif;
}

h1,
h2,
h3 {
  line-height: 1.2;
}

/* Seletor de classe: componente reutilizável */
.botao {
  display: inline-block;
  padding: 12px 18px;
  border-radius: 8px;
  text-decoration: none;
}

Para usar a classe, acrescente o atributo no HTML:

<a class="botao" href="cardapio.html">Conheça o cardápio</a>
p

Seleciona todos os parágrafos. É útil para uma base comum.

.botao

Seleciona qualquer elemento com a classe botao.

nav a

Seleciona links que estão dentro de nav.

a:focus-visible

Seleciona o link quando o foco do teclado precisa aparecer.

Quando duas regras disputam a mesma propriedade

A cascata decide qual declaração será usada. Nesta etapa, observe três ideias: uma regra mais específica pode vencer outra mais geral; quando a força é igual, a regra escrita depois vence; algumas propriedades, como color e font-family, costumam ser herdadas pelos elementos filhos.

a {
  color: #244d3f;
}

.botao {
  color: #ffffff;
  background-color: #244d3f;
}

O link com a classe botao ficará branco porque o seletor de classe é mais específico do que o seletor de elemento.

Comece simples.

Evite usar !important para esconder conflitos e não escolha elementos pelo id apenas para estilizá-los. Classes pequenas e nomes baseados na função do componente deixam a folha mais previsível.

Confira a cascata no DevTools

  1. Inspecione um link com a classe botao.
  2. No painel Styles, marque e desmarque temporariamente a propriedade color para ver qual regra vence.
  3. Abra Computed e procure color para localizar o valor final realmente aplicado.

Esses testes também desaparecem ao recarregar a página. Depois de compreender o resultado, faça a correção permanente em styles.css.

Pratique

Crie as classes botao, produto e destaque. Use cada classe em pelo menos duas páginas e explique por que ela representa uma função reutilizável.

Aula 3 — Cores, tipografia e identidade visual

Uma identidade visual não é apenas decoração. Ela cria hierarquia, reforça a personalidade do negócio e ajuda o visitante a reconhecer títulos, conteúdos e ações. Para o Café Aurora, usaremos verde escuro, terracota, creme e tons neutros.

Defina valores reutilizáveis

:root {
  --cor-principal: #244d3f;
  --cor-destaque: #8a4524;
  --cor-fundo: #fff8ed;
  --cor-superficie: #ffffff;
  --cor-texto: #20241f;
  --cor-borda: #d9cdbd;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  color: var(--cor-texto);
  background-color: var(--cor-fundo);
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.6;
}

h1,
h2,
h3 {
  color: var(--cor-principal);
  line-height: 1.2;
}

img {
  max-width: 100%;
  height: auto;
}
Por que usar variáveis CSS?

Propriedades personalizadas começam com -- e são recuperadas com var(). Se a cor principal mudar, basta alterar sua definição, sem procurar o mesmo código hexadecimal em toda a folha.

Visualize a direção escolhida

Café de bairro • desde 2012

Café Aurora

Sabores artesanais e encontros tranquilos perto de você.

Conheça o cardápio
Xícara de café coado ao lado de uma pequena jarra
Café coadoPreparado na hora.
Bolo caseiro de laranja com uma fatia servida
Bolo caseiroReceita da casa.

Contraste e legibilidade

  • Use texto escuro sobre fundo claro ou texto claro sobre fundo escuro.
  • Não use cor como única forma de indicar estado, erro ou página atual.
  • Evite parágrafos inteiros em letras maiúsculas, itálico ou fonte decorativa.
  • Mantenha altura de linha confortável e permita ampliação sem perda de conteúdo.
  • Prefira texto real no HTML em vez de imagens contendo palavras.

Experimente

Altere uma variável por vez e observe todas as regiões afetadas. Depois, aplique ampliação de 200% no navegador e confirme que textos, botões e imagens continuam utilizáveis.

Aula 4 — Modelo de caixa e componentes do Café Aurora

O navegador representa cada elemento como uma caixa. O conteúdo fica no centro; padding cria espaço interno; border desenha o limite; margin separa a caixa das demais.

.produto {
  margin-bottom: 24px;
  padding: 20px;
  background-color: var(--cor-superficie);
  border: 1px solid var(--cor-borda);
  border-radius: 12px;
}

.botao {
  display: inline-block;
  padding: 12px 18px;
  color: #ffffff;
  background-color: var(--cor-principal);
  border-radius: 8px;
  font-weight: 700;
  text-decoration: none;
}

.botao:hover {
  background-color: #18382e;
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 3px;
  box-shadow: 0 0 0 6px #1b1b1b;
}
O papel de box-sizing: border-box

Com essa regra aplicada a todos os elementos, a largura declarada já considera preenchimento e borda. Isso torna o tamanho das caixas mais previsível.

Uma abertura com imagem

A imagem usada como fundo é decorativa; por isso o conteúdo importante continua escrito no HTML. Como styles.css está dentro da pasta css, o caminho precisa voltar uma pasta antes de entrar em img.

.apresentacao {
  padding: 72px 24px;
  color: #ffffff;
  background:
    linear-gradient(rgba(20, 45, 37, .82), rgba(20, 45, 37, .82)),
    url("../img/ambiente-cafe.webp") center / cover;
}

.apresentacao h1 {
  color: #ffffff;
}
Imagem de conteúdo não deve virar fundo.

Fotos de produtos, pessoas ou locais que ajudam a compreender a página permanecem em img, com texto alternativo adequado. background-image é indicado quando a imagem é apenas visual e sua ausência não elimina informação.

Teste antes de considerar pronto

  1. Abra as quatro páginas e confirme que todas carregam a mesma folha.
  2. Percorra menu, links, campos e botões usando Tab.
  3. Confirme que o foco permanece claramente visível.
  4. Aumente o zoom para 200% e verifique se nada fica inacessível.
  5. Desative temporariamente o CSS nas ferramentas do navegador: o conteúdo ainda deve manter ordem e significado.

Erros comuns para revisar

  • Esquecer o ponto antes do nome de uma classe.
  • Ligar apenas uma página ao arquivo styles.css.
  • Usar um caminho incorreto para CSS ou imagens.
  • Repetir estilos no atributo style de vários elementos.
  • Criar seletores excessivamente específicos e corrigir tudo com !important.
  • Remover o contorno de foco ou deixar texto com contraste insuficiente.
  • Definir alturas fixas que cortam o conteúdo quando o texto aumenta.
  • Escolher cores somente pelo gosto pessoal, sem observar função e legibilidade.
  • Transformar texto importante em imagem para obter determinada aparência.

Projeto aplicado — entrega do capítulo

Crie a primeira identidade visual completa do Café Aurora:

  • pasta css e arquivo styles.css conectados às quatro páginas;
  • variáveis para cores recorrentes e uma paleta com contraste adequado;
  • tipografia e altura de linha legíveis;
  • imagem fluida com max-width: 100%;
  • cabeçalho, menu, apresentação, produtos, formulário e rodapé visualmente coerentes;
  • classes reutilizáveis para botões e cards;
  • estados de passagem do ponteiro e foco do teclado claramente diferentes;
  • verificação das quatro páginas em 100% e 200% de zoom.

Peça a uma pessoa que não acompanhou o desenvolvimento para identificar, sem ajuda, o nome do negócio, a ação principal e três produtos. Se ela hesitar, revise hierarquia, contraste e espaçamento.

Verifique sua aprendizagem

  • Sei conectar uma folha CSS externa a todas as páginas.
  • Reconheço seletor, propriedade, valor, declaração e bloco.
  • Sei quando usar seletor de elemento ou classe.
  • Consigo explicar um conflito simples de cascata e especificidade.
  • Entendo conteúdo, preenchimento, borda e margem.
  • Uso variáveis para manter cores coerentes.
  • Consigo diferenciar imagem de conteúdo de imagem decorativa.
  • Meu site mantém contraste, foco visível e leitura com ampliação.

Referências para consulta

Até aqui

O Café Aurora agora possui uma identidade visual consistente, reutilizável e acessível. No próximo capítulo, usaremos Flexbox, Grid e media queries para organizar o conteúdo em diferentes larguras de tela.

Transfira o aprendizado

Para uma livraria de bairro, proponha apenas três variáveis de cor e uma classe reutilizável. Explique como manteria contraste e foco visível sem reconstruir o projeto.

Web I • 8 horas

Capítulo 6 — Layout responsivo

Medidas flexíveis, abordagem mobile first, Flexbox, Grid, media queries e testes de refluxo.

O mesmo site precisa funcionar em espaços muito diferentes

A identidade visual do Café Aurora já está definida, mas a proprietária percebeu um problema: o cardápio fica agradável no computador e apertado no celular. Em algumas larguras, o menu ocupa espaço demais e os campos do formulário perdem clareza.

Não criaremos um segundo site para celulares. Faremos o mesmo conteúdo se reorganizar de acordo com o espaço disponível, sem esconder informações importantes e sem obrigar o visitante a ampliar ou arrastar a página para os lados.

O que você vai aprender

  • Explicar o que torna um layout responsivo.
  • Construir primeiro uma organização simples para telas estreitas.
  • Usar medidas flexíveis, limites de largura e imagens fluidas.
  • Organizar itens em uma direção com Flexbox.
  • Organizar linhas e colunas com CSS Grid.
  • Criar media queries a partir da necessidade do conteúdo.
  • Preservar a ordem lógica e o acesso por teclado.
  • Testar o site em diferentes larguras e com ampliação.

Aula 1 — Pensar do menor espaço para o maior

Um layout responsivo combina conteúdo flexível, imagens adaptáveis e regras que reorganizam a página quando o espaço deixa de ser suficiente. A abordagem mobile first começa com uma coluna simples e acrescenta possibilidades conforme a largura aumenta.

O ponto de mudança nasce do conteúdo.

Não escolha uma media query porque determinado aparelho possui certa largura. Redimensione a página e observe quando menu, textos, campos ou cards deixam de funcionar bem. Esse é o momento de criar um breakpoint.

Confirme a configuração de viewport

O projeto já possui esta linha dentro de head. Ela faz a largura do documento acompanhar a largura disponível no dispositivo:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
Não bloqueie a ampliação.

Não acrescente user-scalable=no nem limite máximo de zoom. Pessoas que precisam ampliar o conteúdo devem continuar no controle.

Crie uma área central flexível

.container {
  width: min(70rem, calc(100% - 2rem));
  margin-inline: auto;
}

img {
  max-width: 100%;
  height: auto;
}
Medida ou funçãoUso neste capítulo
%Representa uma parte do espaço disponível no elemento pai.
remÉ uma medida relativa ao tamanho-base de fonte configurado no navegador.
frDistribui frações do espaço disponível dentro de um Grid.
min()Escolhe o menor valor entre as opções e impede que o conteúdo fique largo demais.
calc()Combina medidas; no exemplo, reserva uma margem lateral total de 2rem.

max-width: 100% impede a imagem de ultrapassar seu contêiner. Isso ajusta sua apresentação, mas não reduz os bytes transferidos; a otimização do arquivo WebP continua necessária.

Entregue uma imagem adequada ao espaço

srcset oferece arquivos de larguras diferentes. sizes informa quanto espaço a imagem tende a ocupar; com esses dados, o navegador escolhe uma candidata antes de baixar o arquivo.

<img
  src="img/ambiente-cafe-1024.webp"
  srcset="
    img/ambiente-cafe-640.webp 640w,
    img/ambiente-cafe-1024.webp 1024w,
    img/ambiente-cafe.webp 1536w
  "
  sizes="(max-width: 48rem) calc(100vw - 2rem), 70rem"
  alt="Salão do Café Aurora com mesas de madeira e plantas"
  width="1536"
  height="1024"
>
  • Os valores 640w, 1024w e 1536w descrevem a largura real de cada arquivo.
  • O navegador considera o espaço previsto e a densidade da tela; não é preciso escolher a imagem com JavaScript.
  • Use picture quando a versão estreita precisar de outro recorte ou enquadramento, e não apenas de menos pixels. O Café Aurora mantém o mesmo enquadramento, por isso srcset é suficiente aqui.

Experimente

  1. Aplique a classe container ao conteúdo principal de cada página.
  2. Reduza lentamente a largura do navegador.
  3. Procure textos cortados, imagens maiores que a tela e rolagem horizontal.
  4. Retire temporariamente max-width: 100% das imagens, observe o problema e restaure a regra.

Aula 2 — Flexbox para organizar uma direção

Flexbox organiza os filhos diretos de um contêiner em uma direção principal: linha ou coluna. Ele é adequado para menu, grupos de botões, cabeçalhos e regiões em que o conteúdo determina o espaço ocupado.

Transforme a lista do menu em um contêiner flexível

.menu-lista {
  display: flex;
  flex-direction: column;
  gap: .75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 48rem) {
  .menu-lista {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }
}
display: flex

Ativa o modelo flexível nos filhos diretos.

flex-direction

Define se o eixo principal será linha ou coluna.

gap

Cria espaço consistente entre os itens.

flex-wrap

Permite que itens passem para outra linha quando necessário.

justify-content

Distribui itens ao longo do eixo principal.

align-items

Alinha itens no eixo transversal.

Um cabeçalho que aproveita o espaço

.cabecalho-conteudo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
A ordem visual não deve contradizer a leitura.

Evite usar order para criar uma sequência visual diferente do HTML. Leitores de tela e navegação por teclado seguem a ordem do documento, por isso o conteúdo precisa fazer sentido antes mesmo do CSS.

Pratique

Teste o menu com nomes de links curtos e longos. Confirme que eles mudam de linha sem se sobrepor, permanecem clicáveis e mantêm a mesma ordem no HTML e na tela.

Aula 3 — Grid para produtos em linhas e colunas

CSS Grid controla linhas e colunas ao mesmo tempo. Ele é a escolha natural para o cardápio, pois os produtos precisam formar uma grade alinhada quando houver espaço e voltar para uma coluna em telas estreitas.

NecessidadeEscolha inicialExemplo
Organizar em uma direçãoFlexboxMenu ou grupo de botões.
Controlar linhas e colunasGridCards de produtos ou galeria.
Alinhar conteúdo interno de um cardFlexboxEmpurrar preço e botão para o final.
Distribuir vários cardsGridUma, duas ou três colunas.
.grade-produtos {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

@media (min-width: 40rem) {
  .grade-produtos {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 64rem) {
  .grade-produtos {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

1fr cria uma coluna que ocupa o espaço disponível. repeat() evita repetição, enquanto minmax(0, 1fr) permite que as colunas encolham sem provocar estouro causado pelo conteúdo.

Veja a grade responder ao espaço

Cardápio do dia Feito com cuidado, servido perto de você
Xícara de café coado ao lado de uma pequena jarra
Café coadoPreparado na hora.R$ 8,00
Pão artesanal cortado sobre uma mesa de madeira
Pão artesanalFermentação natural.R$ 12,00
Bolo caseiro de laranja com uma fatia servida
Bolo caseiroReceita da casa.R$ 10,00

Uma alternativa fluida

Quando todos os cards podem ter uma largura mínima semelhante, o próprio Grid consegue criar e remover colunas sem media queries:

.grade-produtos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 1.5rem;
}
As duas soluções são válidas.

A versão com media queries ensina onde a mudança acontece e oferece controle explícito. A versão com auto-fit responde ao espaço do próprio contêiner. Escolha pela necessidade do componente, não pela quantidade de código.

Aula 4 — Media queries, formulário e testes de refluxo

Uma media query aplica regras somente quando determinada condição é atendida. Na abordagem mobile first, o CSS fora da consulta atende primeiro as telas estreitas; regras com min-width melhoram a organização quando há espaço.

.formulario-grade {
  display: grid;
  gap: 1rem;
}

@media (min-width: 48rem) {
  .formulario-grade {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .campo-largo {
    grid-column: 1 / -1;
  }
}

No celular, todos os campos ficam em uma coluna. A partir de 48rem, campos curtos podem ocupar duas colunas; a classe campo-largo faz mensagem e botão atravessarem a grade inteira.

Tipografia que cresce dentro de limites

.apresentacao h1 {
  font-size: clamp(2rem, 7vw, 4.5rem);
}

clamp() recebe um valor mínimo, um valor flexível e um máximo. O título cresce com a largura, mas nunca fica menor que 2rem nem maior que 4.5rem.

Procedimento de teste

Chrome DevTools com a barra de dispositivos e uma página exibida em largura móvel
Ative a barra de dispositivos e arraste as alças para testar larguras intermediárias. Fonte: documentação oficial do Chrome DevTools.
  1. Abra as ferramentas de desenvolvimento do navegador e ative o modo responsivo.
  2. Teste larguras próximas de 320, 768 e 1280 CSS pixels, sem tratá-las como modelos específicos.
  3. Arraste a largura lentamente entre esses pontos; problemas costumam aparecer nos intervalos.
  4. Teste orientação vertical e horizontal.
  5. Em uma janela de 1280 CSS pixels, amplie para 400% e confirme que o conteúdo reflui sem rolagem horizontal da página.
  6. Percorra menu, links, campos e botões usando somente Tab e Shift + Tab.
  7. Confirme que nenhuma informação desaparece quando a largura diminui.
overflow-x: hidden não conserta o layout.

Essa regra pode apenas esconder o conteúdo que ultrapassou a tela. Localize a causa: largura fixa, imagem sem limite, palavra longa, coluna rígida ou espaçamento excessivo.

Auditoria em dupla

Um aluno controla a largura e a ampliação; o outro registra o primeiro ponto em que cada região deixa de funcionar. Corrijam a causa e repitam o teste sem orientar o navegador para um aparelho específico.

Erros comuns para revisar

  • Criar larguras fixas maiores que o espaço disponível.
  • Escolher breakpoints copiando nomes de aparelhos.
  • Usar Flexbox para forçar alinhamento de várias linhas e colunas.
  • Usar Grid quando uma simples linha flexível resolveria.
  • Esquecer flex-wrap em um menu com muitos itens.
  • Reordenar visualmente o conteúdo e prejudicar teclado e leitores de tela.
  • Ocultar conteúdo importante na versão estreita.
  • Bloquear zoom ou reduzir demais a fonte para fazer tudo caber.
  • Resolver estouro apenas com overflow-x: hidden.
  • Testar somente em duas larguras prontas e ignorar os intervalos.

Projeto aplicado — entrega do capítulo

Torne responsivas as quatro páginas do Café Aurora:

  • área central com largura máxima e margens flexíveis;
  • imagens que não ultrapassam seus contêineres;
  • menu e cabeçalho organizados com Flexbox;
  • cardápio com Grid de uma, duas e três colunas;
  • formulário de uma coluna no espaço estreito e duas quando houver espaço;
  • breakpoints criados quando o conteúdo realmente precisar;
  • ordem lógica preservada no HTML e no teclado;
  • teste contínuo entre 320 e 1280 CSS pixels e com ampliação de 400%.

Entregue três capturas da mesma página — estreita, intermediária e ampla — e uma lista curta das decisões tomadas. Não basta mostrar que “coube”: explique por que Flexbox, Grid ou media query foi usado em cada região.

Verifique sua aprendizagem

  • Consigo explicar por que responsividade não significa criar outro site.
  • Começo pelo fluxo simples e acrescento colunas quando há espaço.
  • Sei escolher entre Flexbox e Grid em situações comuns.
  • Entendo eixo principal, eixo transversal, linha, coluna e espaçamento.
  • Crio breakpoints de acordo com o conteúdo.
  • Sei usar minmax(), repeat() e fr.
  • Não escondo conteúdo nem bloqueio ampliação para fazê-lo caber.
  • Consigo testar refluxo, teclado e larguras intermediárias.

Referências para consulta

Até aqui

O site já possui estrutura semântica, conteúdo, formulário, identidade visual e layout responsivo. No próximo capítulo, JavaScript acrescentará comportamento e interação sem retirar o funcionamento básico do HTML.

Transfira o aprendizado

Imagine três cards de oficinas em um evento comunitário. Indique se usaria Flexbox ou Grid, em qual situação criaria um breakpoint e quais duas larguras de imagem ofereceria. Registre a decisão sem iniciar outro projeto.

Web I • 8 horas

Capítulo 7 — JavaScript: comportamento e interação

Arquivo externo, valores, condições, funções, DOM, eventos, filtros e melhoria progressiva.

O site funciona; agora ele pode responder às pessoas

O site responsivo do Café Aurora já apresenta todo o conteúdo sem depender de programação. Agora a proprietária quer facilitar duas tarefas: filtrar os produtos do cardápio e acompanhar o tamanho da mensagem de contato.

JavaScript entrará como uma camada de comportamento. Se ele não carregar, os produtos e o formulário continuarão disponíveis. Essa ideia se chama melhoria progressiva.

O que você vai aprender

  • Explicar o papel do JavaScript em relação ao HTML e ao CSS.
  • Conectar um arquivo script.js usando defer.
  • Trabalhar com valores, variáveis, condições e funções simples.
  • Localizar elementos no DOM e responder aos eventos click e input.
  • Construir um filtro de cardápio acessível e um contador de caracteres.
  • Usar o Console para observar mensagens e investigar erros.
  • Preservar o funcionamento básico da página sem JavaScript.

Aula 1 — O primeiro arquivo JavaScript

Crie a pasta js e, dentro dela, o arquivo script.js. Antes do fechamento de body em cada página, conecte o arquivo:

<script src="js/script.js" defer></script>

src informa o caminho. defer permite que o navegador continue lendo o HTML e execute o script somente depois que o documento estiver analisado. Assim, o JavaScript encontra os elementos que precisa usar.

Confirme a conexão no Console

console.log("JavaScript do Café Aurora conectado.");
Painel Console do Chrome DevTools exibindo uma mensagem e a linha para executar JavaScript
Abra o Console com Ctrl + Shift + J no Windows/Linux ou Command + Option + J no macOS. A aparência pode variar conforme o navegador. Fonte: documentação oficial do Chrome DevTools.

Salve o arquivo, recarregue a página e procure a mensagem. Um erro vermelho geralmente informa o arquivo e a linha onde o navegador encontrou o problema; comece a leitura pela primeira mensagem causada pelo seu código.

Valores e variáveis

const nomeDoCafe = "Café Aurora"; // texto
const quantidadeDeProdutos = 3;  // número
const estaAberto = true;         // verdadeiro ou falso
let filtroAtual = "todos";       // valor que poderá mudar

Use const quando a variável não será reatribuída e let quando seu valor precisará mudar. Nomes claros tornam o código mais fácil de explicar e revisar.

Condição e função

function informarAtendimento(estaAberto) {
  if (estaAberto) {
    return "Estamos abertos.";
  }

  return "Estamos fechados no momento.";
}

console.log(informarAtendimento(estaAberto));

Experimente

Troque true por false, recarregue a página e explique por que a função devolveu outra mensagem. Depois restaure o valor.

Aula 2 — DOM e eventos

O navegador transforma o HTML em uma estrutura de objetos chamada DOM. JavaScript pode localizar um elemento nessa estrutura, ler ou alterar propriedades e reagir a ações da pessoa.

querySelector()

Localiza o primeiro elemento que corresponde a um seletor CSS.

textContent

Lê ou substitui o texto de um elemento sem interpretar HTML.

addEventListener()

Executa uma função quando um evento acontece.

event

Representa o acontecimento e fornece informações sobre ele.

Acrescente um parágrafo de retorno ao HTML:

<p id="aviso-cardapio">Todos os produtos estão visíveis.</p>

Depois localize-o e altere seu texto quando um botão for acionado:

const aviso = document.querySelector("#aviso-cardapio");
const botao = document.querySelector("#botao-destaques");

if (aviso && botao) {
  botao.addEventListener("click", function () {
    aviso.textContent = "Mostrando os destaques da casa.";
  });
}
O teste evita interromper o restante do arquivo.

Se um seletor não encontrar o elemento, o valor será null. A condição confirma que os dois elementos existem antes de tentar usá-los.

Prefira eventos no arquivo JavaScript.

addEventListener() mantém estrutura e comportamento separados. Evite colocar onclick diretamente no HTML neste projeto.

Investigue no Console

  1. Execute document.querySelector("h1") e observe o elemento localizado.
  2. Troque o seletor por um ID inexistente e observe null.
  3. Introduza temporariamente um erro de digitação no script, recarregue e use o nome do arquivo e a linha indicados no Console para corrigi-lo.

Aula 3 — Filtro acessível para o cardápio

Os filtros serão botões nativos. O rótulo de cada botão permanece estável e aria-pressed informa qual opção está ativa. Os controles começam ocultos e aparecem somente quando o JavaScript consegue prepará-los; sem script, todos os produtos continuam visíveis.

Experimente o resultado

Cardápio do Café Aurora

3 produtos encontrados.

Café coado

Bebida

Pão artesanal

Comida

Bolo de laranja

Comida

Prepare o HTML do projeto

<div class="filtros" role="group" aria-label="Filtrar produtos" data-filtros hidden>
  <span>Filtrar por:</span>
  <button type="button" data-filtro="todos" aria-pressed="true"
    aria-controls="lista-produtos">Todos</button>
  <button type="button" data-filtro="bebida" aria-pressed="false"
    aria-controls="lista-produtos">Bebidas</button>
  <button type="button" data-filtro="comida" aria-pressed="false"
    aria-controls="lista-produtos">Comidas</button>
</div>

<p data-status-filtro role="status"></p>

<div class="grade-produtos" id="lista-produtos">
  <article data-categoria="bebida">...</article>
  <article data-categoria="comida">...</article>
  <article data-categoria="comida">...</article>
</div>

Construa o comportamento por partes

const filtros = document.querySelector("[data-filtros]");
const botoesFiltro = document.querySelectorAll("[data-filtro]");
const produtos = document.querySelectorAll("[data-categoria]");
const statusFiltro = document.querySelector("[data-status-filtro]");
function filtrarProdutos(categoria) {
  let quantidadeVisivel = 0;

  produtos.forEach(function (produto) {
    const deveMostrar = categoria === "todos" ||
      produto.dataset.categoria === categoria;

    produto.hidden = !deveMostrar;
    if (deveMostrar) quantidadeVisivel += 1;
  });

  const complemento = quantidadeVisivel === 1 ?
    "produto encontrado" : "produtos encontrados";
  statusFiltro.textContent = `${quantidadeVisivel} ${complemento}.`;
}
botoesFiltro.forEach(function (botao) {
  botao.addEventListener("click", function () {
    botoesFiltro.forEach(function (item) {
      item.setAttribute("aria-pressed", "false");
    });

    botao.setAttribute("aria-pressed", "true");
    filtrarProdutos(botao.dataset.filtro);
  });
});
Código completo para conferência — filtro
const filtros = document.querySelector("[data-filtros]");
const botoesFiltro = document.querySelectorAll("[data-filtro]");
const produtos = document.querySelectorAll("[data-categoria]");
const statusFiltro = document.querySelector("[data-status-filtro]");

if (filtros && botoesFiltro.length && produtos.length && statusFiltro) {
  function filtrarProdutos(categoria) {
    let quantidadeVisivel = 0;

    produtos.forEach(function (produto) {
      const deveMostrar = categoria === "todos" ||
        produto.dataset.categoria === categoria;

      produto.hidden = !deveMostrar;
      if (deveMostrar) quantidadeVisivel += 1;
    });

    const complemento = quantidadeVisivel === 1 ?
      "produto encontrado" : "produtos encontrados";
    statusFiltro.textContent = `${quantidadeVisivel} ${complemento}.`;
  }

  botoesFiltro.forEach(function (botao) {
    botao.addEventListener("click", function () {
      botoesFiltro.forEach(function (item) {
        item.setAttribute("aria-pressed", "false");
      });

      botao.setAttribute("aria-pressed", "true");
      filtrarProdutos(botao.dataset.filtro);
    });
  });

  filtros.hidden = false;
  filtrarProdutos("todos");
}

Teste a melhoria progressiva

Desative JavaScript no DevTools ou remova temporariamente a linha do script. Recarregue a página: os controles não devem aparecer, mas os três produtos precisam continuar visíveis. Depois restaure o arquivo.

Aula 4 — Contador de caracteres e revisão

O evento input acontece quando o valor de um campo muda. Usaremos value.length para contar caracteres e textContent para atualizar uma orientação já associada ao campo.

Escreva até 120 caracteres. Nada será enviado.

0 de 120 caracteres

Atualize o HTML do formulário

<label for="mensagem">Mensagem (obrigatória)</label>
<p id="ajuda-mensagem">Escreva entre 10 e 500 caracteres.</p>
<textarea id="mensagem" name="mensagem" minlength="10" maxlength="500"
  aria-describedby="ajuda-mensagem contador-mensagem" required></textarea>
<p id="contador-mensagem">0 de 500 caracteres</p>

Escute cada alteração

const mensagem = document.querySelector("#mensagem");
const contador = document.querySelector("#contador-mensagem");

if (mensagem && contador) {
  mensagem.addEventListener("input", function () {
    const quantidade = mensagem.value.length;
    contador.textContent = `${quantidade} de 500 caracteres`;
  });
}
Evite anúncios a cada tecla.

O contador está relacionado ao campo por aria-describedby, mas não usa uma região viva. Atualizá-la a cada caractere poderia gerar interrupções excessivas para quem usa leitor de tela.

Procedimento final de teste

  1. Recarregue a página com o Console aberto e corrija o primeiro erro causado pelo seu script.
  2. Use Tab, Espaço e Enter para operar todos os filtros sem mouse.
  3. Confirme que somente os cards esperados ficam visíveis e que o status apresenta a quantidade.
  4. Digite, apague e cole texto no campo; o contador deve acompanhar as três ações.
  5. Teste o site sem JavaScript e confirme que conteúdo, links e formulário básico permanecem utilizáveis.

Erros comuns para revisar

  • Escrever um caminho incorreto para script.js ou esquecer de salvar o arquivo.
  • Confundir =, usado para atribuição, com ===, usado para comparação estrita.
  • Usar um seletor que não corresponde ao HTML e tentar acessar uma propriedade de null.
  • Chamar a função no lugar de entregá-la ao evento: addEventListener("click", filtrar()).
  • Alterar o rótulo de um botão pressionado em vez de atualizar seu estado.
  • Esconder todos os produtos no HTML e tornar o conteúdo dependente de JavaScript.
  • Usar innerHTML quando apenas textContent é necessário.
  • Testar somente com o mouse e ignorar Console, teclado e funcionamento sem script.

Projeto aplicado — entrega do capítulo

Conclua a primeira camada de comportamento do Café Aurora:

  • arquivo js/script.js conectado com defer;
  • filtros do cardápio preparados somente quando JavaScript estiver disponível;
  • botões nativos com estado pressionado atualizado corretamente;
  • cards mostrados ou ocultados pela categoria;
  • mensagem de status com a quantidade encontrada;
  • contador relacionado ao campo de mensagem;
  • nenhum erro do projeto no Console;
  • conteúdo e formulário básico disponíveis sem JavaScript.

Entregue os arquivos do site e uma breve explicação: qual necessidade cada interação resolve e o que permanece funcionando quando o script não carrega?

Verifique sua aprendizagem

  • Sei diferenciar estrutura, apresentação e comportamento.
  • Conecto um arquivo JavaScript externo e explico o uso de defer.
  • Reconheço textos, números, booleanos, variáveis, condições e funções.
  • Localizo elementos e verifico se eles existem antes de usá-los.
  • Respondo aos eventos click e input.
  • Atualizo texto, estado e visibilidade sem retirar a semântica do HTML.
  • Uso o Console para confirmar mensagens e localizar erros.
  • Consigo explicar e testar a melhoria progressiva.

Referências para consulta

Até aqui

O Café Aurora agora reúne HTML semântico, formulário acessível, identidade visual, layout responsivo e uma primeira camada de comportamento. No próximo capítulo, os dados do cardápio serão organizados em coleções para evitar repetição e preparar futuras integrações com JSON.

Transfira o aprendizado

Escolha apenas um contexto simples — biblioteca, feira comunitária ou oficina — e descreva uma interação útil com um botão, um evento e uma alteração no DOM. Indique também o que permaneceria disponível sem JavaScript. Não desenvolva outro site completo.

Web I • 8 horas

Capítulo 8 — Arrays, objetos e repetições

Coleções, dados estruturados, laços e uma simulação de pedido do Café Aurora.

Quando três produtos se tornam muitos

O cardápio do Café Aurora começou pequeno, mas poderá receber novos cafés, pães e bolos. Criar uma variável e repetir o mesmo código para cada item tornaria a manutenção lenta e sujeita a erros.

Vamos guardar produtos relacionados em um array, descrever cada produto com um objeto e usar repetições para trabalhar com a coleção. Ao final, esses dados alimentarão uma simulação de pedido sem substituir o cardápio disponível no HTML.

O que você vai aprender

  • Criar arrays e acessar itens por posição.
  • Consultar length e acrescentar um item com push().
  • Representar um produto com propriedades de um objeto.
  • Percorrer uma coleção com for...of e forEach().
  • Usar filter() e find() em necessidades simples.
  • Calcular a quantidade e o total de uma seleção.
  • Inspecionar coleções com console.table().

Aula 1 — Arrays: vários valores em uma coleção

Um array guarda uma sequência de valores em uma única variável. Os colchetes delimitam a coleção e as vírgulas separam seus itens:

const nomesDosProdutos = [
  "Café coado",
  "Pão artesanal",
  "Bolo de laranja"
];

As posições começam em zero. Portanto, nomesDosProdutos[0] contém o primeiro nome e nomesDosProdutos[2] contém o terceiro.

console.log(nomesDosProdutos[0]); // Café coado
console.log(nomesDosProdutos.length); // 3

nomesDosProdutos.push("Capuccino");
console.log(nomesDosProdutos.length); // 4
const protege a referência, não congela o array.

Não podemos atribuir outro array à mesma variável, mas métodos como push() ainda podem modificar seu conteúdo. Use essa possibilidade de forma consciente.

Experimente

Acrescente uma bebida, mostre o último item usando nomesDosProdutos[nomesDosProdutos.length - 1] e explique por que é necessário subtrair um.

Aula 2 — Objetos: dados que pertencem ao mesmo produto

Um nome isolado não informa categoria nem preço. Um objeto reúne dados relacionados em pares formados por propriedade e valor:

const cafeCoado = {
  id: "cafe-coado",
  nome: "Café coado",
  categoria: "bebida",
  preco: 8,
  disponivel: true
};

A notação de ponto acessa uma propriedade conhecida. A notação de colchetes é útil quando o nome da propriedade vem de uma variável.

console.log(cafeCoado.nome); // Café coado
console.log(cafeCoado.preco); // 8

const propriedade = "categoria";
console.log(cafeCoado[propriedade]); // bebida

Combine array e objetos

const produtos = [
  { id: "cafe-coado", nome: "Café coado", categoria: "bebida", preco: 8 },
  { id: "pao-artesanal", nome: "Pão artesanal", categoria: "comida", preco: 12 },
  { id: "bolo-laranja", nome: "Bolo de laranja", categoria: "comida", preco: 9 }
];

console.table(produtos);

console.table() apresenta o array de objetos em linhas e colunas no Console. É uma ferramenta de inspeção; não altera a página.

Pratique

Acrescente o objeto do capuccino com ID único, categoria bebida e preço numérico. Confira no Console se todas as propriedades aparecem na mesma coluna.

Aula 3 — Repetir, filtrar e localizar

Quando a mesma ação deve alcançar todos os itens, uma repetição evita copiar e colar comandos. for...of deixa explícito que cada produto da coleção será visitado:

for (const produto of produtos) {
  console.log(`${produto.nome}: R$ ${produto.preco}`);
}

O método forEach() também executa uma função para cada item. Ele já apareceu no filtro do capítulo anterior:

produtos.forEach(function (produto) {
  console.log(produto.nome);
});
RecursoResultadoUso no Café Aurora
for...ofVisita os itens em sequência.Somar preços selecionados.
forEach()Executa uma função para cada item.Associar eventos aos controles.
filter()Cria um novo array com os itens aprovados pelo teste.Separar somente as bebidas.
find()Devolve o primeiro item encontrado ou undefined.Localizar um produto pelo ID.
const bebidas = produtos.filter(function (produto) {
  return produto.categoria === "bebida";
});

const produtoEscolhido = produtos.find(function (produto) {
  return produto.id === "pao-artesanal";
});

console.log(bebidas);
console.log(produtoEscolhido);
Não confunda resultado com alteração.

filter() cria outro array; ele não remove itens de produtos. Já find() devolve um único item e deve ser verificado antes do uso.

Aula 4 — Aplicação: simulação de pedido

Agora a coleção será usada para localizar cada item marcado e somar os preços. Esta é uma simulação local: ela não envia nem reserva um pedido.

Monte um pedido de teste

Marque produtos para calcular o total. Nenhum pedido será enviado.

Prepare a estrutura

<fieldset data-pedido hidden>
  <legend>Simulação de pedido</legend>
  <label><input type="checkbox" value="cafe-coado" data-item-pedido>
    Café coado — R$ 8,00</label>
  <label><input type="checkbox" value="pao-artesanal" data-item-pedido>
    Pão artesanal — R$ 12,00</label>
  <label><input type="checkbox" value="bolo-laranja" data-item-pedido>
    Bolo de laranja — R$ 9,00</label>
</fieldset>
<p data-resumo-pedido role="status"></p>
<button type="button" data-limpar-pedido hidden>Limpar seleção</button>

Calcule por etapas

function calcularPedido() {
  let quantidade = 0;
  let total = 0;

  for (const item of itensPedido) {
    if (!item.checked) continue;

    const produto = produtos.find(function (produto) {
      return produto.id === item.value;
    });

    if (!produto) continue;
    quantidade += 1;
    total += produto.preco;
  }

  const rotulo = quantidade === 1 ? "item" : "itens";
  resumoPedido.textContent = quantidade === 0
    ? "Nenhum produto selecionado."
    : `${quantidade} ${rotulo} — ${formatarPreco(total)}`;
}
function formatarPreco(valor) {
  return valor.toLocaleString("pt-BR", {
    style: "currency",
    currency: "BRL"
  });
}
Código completo para conferência — simulação de pedido
const produtos = [
  { id: "cafe-coado", nome: "Café coado", categoria: "bebida", preco: 8 },
  { id: "pao-artesanal", nome: "Pão artesanal", categoria: "comida", preco: 12 },
  { id: "bolo-laranja", nome: "Bolo de laranja", categoria: "comida", preco: 9 }
];

const pedido = document.querySelector("[data-pedido]");
const itensPedido = document.querySelectorAll("[data-item-pedido]");
const resumoPedido = document.querySelector("[data-resumo-pedido]");
const limparPedido = document.querySelector("[data-limpar-pedido]");

if (pedido && itensPedido.length && resumoPedido && limparPedido) {
  function formatarPreco(valor) {
    return valor.toLocaleString("pt-BR", {
      style: "currency",
      currency: "BRL"
    });
  }

  function calcularPedido() {
    let quantidade = 0;
    let total = 0;

    for (const item of itensPedido) {
      if (!item.checked) continue;

      const produto = produtos.find(function (produto) {
        return produto.id === item.value;
      });

      if (!produto) continue;
      quantidade += 1;
      total += produto.preco;
    }

    const rotulo = quantidade === 1 ? "item" : "itens";
    resumoPedido.textContent = quantidade === 0
      ? "Nenhum produto selecionado."
      : `${quantidade} ${rotulo} — ${formatarPreco(total)}`;
  }

  itensPedido.forEach(function (item) {
    item.addEventListener("change", calcularPedido);
  });

  limparPedido.addEventListener("click", function () {
    itensPedido.forEach(function (item) {
      item.checked = false;
    });
    calcularPedido();
  });

  pedido.hidden = false;
  limparPedido.hidden = false;
  calcularPedido();
}

Teste e explique

  1. Marque um, dois e três produtos e confira os totais manualmente.
  2. Use somente teclado para marcar itens e limpar a seleção.
  3. Altere temporariamente o valor de um checkbox para um ID inexistente e confirme que o código não quebra.
  4. Desative JavaScript e confirme que o cardápio original continua disponível.

Erros comuns para revisar

  • Esperar que a primeira posição de um array seja 1.
  • Acessar uma propriedade inexistente e não investigar o valor undefined.
  • Repetir manualmente comandos que deveriam percorrer a coleção.
  • Modificar o array enquanto ele está sendo percorrido sem compreender o efeito.
  • Usar filter() quando precisa de somente um item.
  • Usar o resultado de find() sem verificar se algo foi encontrado.
  • Guardar preços como textos contendo R$ em vez de números.
  • Apresentar a simulação como se fosse um pedido realmente enviado.

Projeto aplicado — entrega do capítulo

Acrescente ao Café Aurora:

  • array de objetos com ID, nome, categoria e preço dos produtos;
  • inspeção dos dados com console.table() durante o desenvolvimento;
  • simulação de pedido preparada somente quando JavaScript estiver disponível;
  • cálculo seguro de quantidade e total;
  • valor monetário formatado para reais;
  • botão para limpar a seleção;
  • operação completa por teclado e nenhum erro do projeto no Console.

Entregue o site e descreva por que array e objetos facilitam a inclusão de novos produtos.

Verifique sua aprendizagem

  • Sei criar, consultar e ampliar um array.
  • Entendo por que a primeira posição tem índice zero.
  • Represento dados relacionados com propriedades de um objeto.
  • Percorro uma coleção sem copiar o mesmo comando.
  • Distingo forEach(), filter() e find().
  • Verifico resultados que podem ser undefined.
  • Calculo um total mantendo preços como números.
  • Preservo o conteúdo essencial quando JavaScript não está disponível.

Referências para consulta

Até aqui

Os produtos já podem ser representados, percorridos e consultados como dados. No próximo capítulo, essa coleção sairá do arquivo JavaScript e será carregada de um arquivo JSON com Fetch.

Transfira o aprendizado

Modele três livros de uma pequena biblioteca em um array de objetos com ID, título, categoria e disponibilidade. Explique qual repetição usaria para listar os títulos e como encontraria um livro pelo ID. Não crie outro projeto completo.

Web I • 8 horas

Capítulo 9 — Fetch e JSON

Dados externos, requisições HTTP, código assíncrono, estados da interface e atualização segura do cardápio.

O cardápio muda sem exigir alterações no script

Os produtos do Café Aurora já estão organizados em um array de objetos. Porém, nomes, preços e descrições continuam dentro de script.js. Cada mudança mistura conteúdo com comportamento.

Vamos transferir esses dados para dados/produtos.json e carregá-los com fetch(). O JavaScript passa a buscar, verificar e apresentar informações; se a busca falhar, o cardápio básico que já existe no HTML permanece disponível.

O que você vai aprender

  • Explicar o que é JSON e reconhecer suas regras básicas.
  • Criar e validar o arquivo produtos.json.
  • Entender requisição, resposta, método GET e códigos 200 e 404.
  • Usar fetch(), async e await.
  • Verificar response.ok antes de ler os dados.
  • Tratar falhas com try...catch.
  • Representar carregamento, sucesso e erro de forma compreensível.
  • Inspecionar a requisição no painel Network.

Aula 1 — Separando os dados com JSON

JSON é um formato textual usado para representar dados estruturados. Ele se parece com objetos e arrays de JavaScript, mas possui regras próprias e não executa comandos.

Chaves entre aspas

Propriedades e textos usam aspas duplas.

Valores simples

Texto, número, booleano, null, objeto ou array.

Sem comentários

JSON não aceita comentários com // ou /* */.

Sem vírgula final

O último item não recebe vírgula depois dele.

Crie a pasta dados e o arquivo produtos.json:

[
  {
    "id": "cafe-coado",
    "nome": "Café coado",
    "categoria": "bebida",
    "preco": 8,
    "imagem": "cafe-coado.webp",
    "alt": "Xícara de café coado ao lado de uma pequena jarra",
    "descricao": "Preparado na hora com grãos selecionados."
  },
  {
    "id": "pao-artesanal",
    "nome": "Pão artesanal",
    "categoria": "comida",
    "preco": 12,
    "imagem": "pao-artesanal.webp",
    "alt": "Pão artesanal cortado sobre uma mesa de madeira",
    "descricao": "Fermentação lenta e produção diária."
  },
  {
    "id": "bolo-laranja",
    "nome": "Bolo de laranja",
    "categoria": "comida",
    "preco": 9,
    "imagem": "bolo-caseiro.webp",
    "alt": "Bolo caseiro de laranja com uma fatia servida",
    "descricao": "Receita da casa com laranjas frescas."
  }
]

Experimente

  1. Abra o arquivo no VS Code e confirme que cada produto possui as mesmas propriedades.
  2. Acrescente uma vírgula após o último objeto e observe o aviso do editor.
  3. Retire a vírgula, salve e use a formatação do documento para conferir a estrutura.

Aula 2 — Requisição, resposta e código assíncrono

fetch() faz uma requisição HTTP e devolve uma Promise: um objeto que representa um resultado que chegará depois. Uma função marcada com async pode usar await para aguardar cada etapa sem bloquear toda a página.

ParteNo projeto
RequisiçãoO navegador pede dados/produtos.json.
GETMétodo usado para obter um recurso sem modificá-lo.
RespostaO servidor devolve status, cabeçalhos e conteúdo.
200O arquivo foi encontrado e a resposta ocorreu normalmente.
404O caminho solicitado não foi encontrado.

Faça a primeira busca

async function carregarProdutos() {
  const resposta = await fetch("dados/produtos.json");
  const produtos = await resposta.json();
  console.table(produtos);
}

carregarProdutos();
Use um servidor local.

Não abra apenas o arquivo com endereço iniciado por file://. Execute o projeto com Live Preview ou outro servidor local; assim, a requisição acontece por HTTP como ocorrerá no site publicado.

Confira no painel Network

Chrome DevTools com o painel Network aberto e uma lista de requisições com status, tipo, tamanho e tempo
Abra Network, recarregue a página e filtre por Fetch/XHR. Procure produtos.json e confira Status, Type, Preview e Response. A aparência pode variar. Fonte: documentação oficial do Chrome DevTools.

Investigue

Troque temporariamente o caminho para dados/produto.json, recarregue e localize o status 404 no painel Network. Depois restaure o nome correto.

Aula 3 — Verificar e tratar respostas

fetch() pode receber uma resposta 404 sem entrar automaticamente no tratamento de erro. Por isso, verifique response.ok, que será verdadeiro para respostas na faixa de sucesso.

async function carregarProdutos() {
  try {
    const resposta = await fetch("dados/produtos.json");

    if (!resposta.ok) {
      throw new Error(`Falha HTTP: ${resposta.status}`);
    }

    const produtos = await resposta.json();
    console.table(produtos);
  } catch (erro) {
    console.error("Não foi possível carregar os produtos:", erro);
  }
}

try delimita as etapas que podem falhar. throw cria uma falha quando a resposta HTTP não é satisfatória. catch recebe o erro para registro e recuperação da interface.

Represente cada estado

EstadoMensagem útilO que preservar
Carregando“Atualizando o cardápio...”Não apague a versão existente antes da resposta.
Sucesso“Cardápio atualizado: 3 produtos.”Mostre os dados recebidos.
Erro“Não foi possível atualizar. Exibindo a versão disponível.”Mantenha os cards do HTML.
const statusCarregamento = document.querySelector("[data-status-carregamento]");

statusCarregamento.textContent = "Atualizando o cardápio...";

// Depois do sucesso:
statusCarregamento.textContent = `Cardápio atualizado: ${produtos.length} produtos.`;

// Dentro de catch:
statusCarregamento.textContent =
  "Não foi possível atualizar. Exibindo a versão disponível.";
Uma mensagem amigável não substitui o diagnóstico.

A pessoa vê uma orientação simples na página; o desenvolvedor consulta o erro técnico no Console e a requisição no Network. Cada público recebe a informação adequada.

Aula 4 — Atualizando o cardápio com segurança

Criaremos os elementos com métodos do DOM e preencheremos conteúdo com textContent. Assim, os dados são tratados como texto e não como marcação HTML executável.

Experimente uma busca real

Produtos recebidos do JSON

O botão busca o arquivo contextualizado usado nesta aula.

Pronto para carregar.

Prepare o HTML

<p data-status-carregamento role="status">
  Cardápio disponível.
</p>

<div class="grade-produtos" id="lista-produtos">
  <!-- Preserve aqui os cards básicos construídos em HTML. -->
</div>

Crie um card sem usar innerHTML

function criarCard(produto) {
  const card = document.createElement("article");
  const titulo = document.createElement("h3");
  const imagem = document.createElement("img");
  const descricao = document.createElement("p");
  const preco = document.createElement("strong");

  card.dataset.categoria = produto.categoria;
  titulo.textContent = produto.nome;
  imagem.src = `img/${produto.imagem}`;
  imagem.alt = produto.alt;
  descricao.textContent = `${produto.descricao} `;
  preco.textContent = produto.preco.toLocaleString("pt-BR", {
    style: "currency",
    currency: "BRL"
  });

  descricao.append(preco);
  card.append(titulo, imagem, descricao);
  return card;
}
function apresentarProdutos(produtos) {
  const fragmento = document.createDocumentFragment();

  produtos.forEach(function (produto) {
    fragmento.append(criarCard(produto));
  });

  listaProdutos.replaceChildren(fragmento);
}
Código completo para conferência — carregamento do cardápio
let produtos = [];
const listaProdutos = document.querySelector("#lista-produtos");
const statusCarregamento = document.querySelector("[data-status-carregamento]");

function criarCard(produto) {
  const card = document.createElement("article");
  const titulo = document.createElement("h3");
  const imagem = document.createElement("img");
  const descricao = document.createElement("p");
  const preco = document.createElement("strong");

  card.dataset.categoria = produto.categoria;
  titulo.textContent = produto.nome;
  imagem.src = `img/${produto.imagem}`;
  imagem.alt = produto.alt;
  imagem.width = 960;
  imagem.height = 640;
  imagem.loading = "lazy";
  descricao.textContent = `${produto.descricao} `;
  preco.textContent = produto.preco.toLocaleString("pt-BR", {
    style: "currency",
    currency: "BRL"
  });

  descricao.append(preco);
  card.append(titulo, imagem, descricao);
  return card;
}

function apresentarProdutos(produtos) {
  const fragmento = document.createDocumentFragment();
  produtos.forEach(function (produto) {
    fragmento.append(criarCard(produto));
  });
  listaProdutos.replaceChildren(fragmento);
}

async function carregarProdutos() {
  if (!listaProdutos || !statusCarregamento) return;
  statusCarregamento.textContent = "Atualizando o cardápio...";

  try {
    const resposta = await fetch("dados/produtos.json");
    if (!resposta.ok) {
      throw new Error(`Falha HTTP: ${resposta.status}`);
    }

    const dados = await resposta.json();
    if (!Array.isArray(dados) || dados.length === 0) {
      throw new TypeError("A resposta não contém uma lista válida de produtos.");
    }

    produtos = dados;
    apresentarProdutos(produtos);
    statusCarregamento.textContent =
      `Cardápio atualizado: ${produtos.length} produtos.`;
  } catch (erro) {
    console.error("Não foi possível carregar os produtos:", erro);
    statusCarregamento.textContent =
      "Não foi possível atualizar. Exibindo a versão disponível.";
  }
}

carregarProdutos();

Reutilize os dados na simulação de pedido

Substitua o array fixo do capítulo anterior por let produtos = []. Mostre a simulação somente depois do sucesso da busca, quando o cálculo já puder localizar os produtos recebidos:

produtos = dados;
apresentarProdutos(produtos);

pedido.hidden = false;
limparPedido.hidden = false;
calcularPedido();
Mova, não duplique.

Retire do bloco antigo as linhas que exibiam a simulação imediatamente. Se a busca falhar, o cardápio HTML continua visível e a simulação que depende dos novos dados permanece oculta.

Teste os três estados

  1. Use uma limitação de rede no painel Network e observe “Atualizando o cardápio...”.
  2. Restaure a conexão e confirme a mensagem de sucesso e os três cards.
  3. Bloqueie produtos.json ou erre o caminho e confirme que os cards do HTML permanecem.
  4. Verifique Console, Network, teclado e diferentes larguras antes de concluir.

Erros comuns para revisar

  • Usar aspas simples, comentários ou vírgula final dentro do JSON.
  • Abrir a página com file:// e esperar que a busca funcione como em um servidor.
  • Esquecer que fetch() e response.json() são assíncronos.
  • Não verificar response.ok antes de ler o corpo.
  • Ocultar o cardápio básico antes de saber se a busca funcionou.
  • Mostrar detalhes técnicos do erro para o visitante.
  • Usar no-cors como tentativa de corrigir uma API que não permite acesso.
  • Inserir dados recebidos diretamente com innerHTML.

Projeto aplicado — entrega do capítulo

Atualize o Café Aurora com:

  • pasta dados e arquivo JSON válido;
  • busca por HTTP usando fetch() e await;
  • verificação de response.ok;
  • tratamento de falha com try...catch;
  • mensagens de carregamento, sucesso e erro;
  • cards criados com DOM e textContent;
  • cardápio HTML preservado quando o arquivo não estiver disponível;
  • teste documentado no Console e no painel Network.

Entregue o projeto e três registros: requisição 200, erro 404 provocado durante o teste e página mantendo o conteúdo alternativo.

Verifique sua aprendizagem

  • Reconheço a diferença entre objeto JavaScript e texto JSON.
  • Escrevo JSON válido com aspas duplas e sem vírgula final.
  • Entendo requisição, resposta, GET, 200 e 404.
  • Uso async e await em uma busca simples.
  • Verifico a resposta antes de chamar json().
  • Trato carregamento, sucesso e erro sem apagar o conteúdo disponível.
  • Atualizo o DOM tratando os dados como texto.
  • Localizo e interpreto a requisição no painel Network.

Referências para consulta

Até aqui

O Café Aurora já carrega dados separados do comportamento e se recupera quando a requisição falha. No próximo capítulo, o navegador poderá guardar preferências simples do visitante com Web Storage.

Transfira o aprendizado

Imagine uma agenda cultural com três eventos. Defina quais propriedades iriam para eventos.json, qual mensagem apareceria durante a busca e o que a página preservaria em caso de erro. Não desenvolva outro projeto completo.

Web I • 8 horas

Capítulo 10 — Web Storage

Persistência no navegador, chave e valor, conversão com JSON, recuperação segura e inspeção pelo DevTools.

O pedido desaparece quando a página é recarregada

Na simulação do Café Aurora, a pessoa seleciona produtos e vê o total. Porém, ao recarregar ou fechar a página, a seleção é perdida porque existe apenas na memória durante aquela execução do JavaScript.

Vamos guardar somente os identificadores escolhidos no próprio navegador. Quando a página abrir novamente, o site buscará os produtos atuais no JSON e restaurará as escolhas válidas, mantendo preços e descrições em uma única fonte.

O que você vai aprender

  • Explicar quando um dado precisa continuar disponível após o recarregamento.
  • Distinguir localStorage de sessionStorage.
  • Usar setItem(), getItem() e removeItem().
  • Converter arrays com JSON.stringify() e JSON.parse().
  • Salvar e restaurar a seleção do pedido sem duplicar os dados dos produtos.
  • Tratar armazenamento indisponível ou conteúdo inválido sem interromper a página.
  • Inspecionar chaves e valores no painel Application do DevTools.

Aula 1 — Dados que permanecem no navegador

A Web Storage API oferece espaços de armazenamento formados por pares de chave e valor. Tanto a chave quanto o valor são gravados como texto e ficam separados por origem — combinação de protocolo, domínio e porta.

RecursoDuraçãoExemplo adequado
localStorageContinua disponível depois que o navegador é fechado e aberto.Preferência ou rascunho simples naquele dispositivo.
sessionStorageDura enquanto a sessão daquela aba estiver aberta.Etapa temporária de uma tarefa naquela aba.

Comece com um valor textual no Console:

localStorage.setItem("cafeAurora:filtro", "comida");

const filtroSalvo = localStorage.getItem("cafeAurora:filtro");
console.log(filtroSalvo);

localStorage.removeItem("cafeAurora:filtro");
setItem()

Cria uma chave ou substitui seu valor.

getItem()

Obtém o texto salvo ou devolve null.

removeItem()

Remove somente a chave indicada.

Nome da chave

O prefixo cafeAurora: evita nomes genéricos e facilita a identificação.

Evite clear() neste projeto.

Ele apaga todas as chaves do armazenamento daquela origem, inclusive as criadas por outras partes do site. Para limpar apenas o pedido, use removeItem() com a chave correspondente.

Esse dado pertence àquele navegador, perfil, dispositivo e origem. Ele não vira cadastro, não acompanha automaticamente a pessoa em outro aparelho e pode ser apagado nas configurações do navegador.

Experimente

  1. Execute as três instruções separadamente no Console.
  2. Recarregue depois de setItem() e confirme que o valor permanece.
  3. Execute removeItem() e confirme que getItem() devolve null.

Aula 2 — Convertendo a seleção com JSON

O pedido contém vários identificadores, mas o armazenamento aceita texto. JSON.stringify() transforma o array em texto; JSON.parse() reconstrói o array quando o dado é lido.

const selecionados = ["cafe-coado", "bolo-laranja"];
const texto = JSON.stringify(selecionados);

localStorage.setItem("cafeAurora:pedido", texto);

const registro = localStorage.getItem("cafeAurora:pedido");
const pedidoSalvo = JSON.parse(registro);

console.log(pedidoSalvo);
Guarde escolhas, não cópias do cardápio.

Salvamos apenas ["cafe-coado", "bolo-laranja"]. Nomes e preços continuam em produtos.json; assim, uma atualização de preço não deixa uma cópia antiga armazenada no pedido.

Experimente no próprio módulo

Pedido que pode ser recuperado

Selecione produtos, salve e recarregue esta página. O exemplo usa uma chave exclusiva deste exercício.

Produtos

Nenhum produto selecionado.

Nenhum pedido salvo neste navegador.

Observe a transformação

Depois de salvar o exemplo, execute localStorage.getItem("mbb:web1:pedido-demo-v1") no Console. O resultado terá colchetes e aspas porque é um array representado como texto JSON.

Aula 3 — Salvando o pedido do Café Aurora

No arquivo cardapio.html, acrescente uma mensagem específica depois do resumo do pedido:

<p class="status-persistencia"
   data-status-persistencia
   role="status">
  Nenhum pedido salvo neste navegador.
</p>

No início da parte do pedido em script.js, selecione essa mensagem e defina uma chave clara:

const CHAVE_PEDIDO = "cafeAurora:pedido";
const statusPersistencia = document.querySelector(
  "[data-status-persistencia]"
);

Obtenha somente os identificadores marcados

function obterIdsSelecionados() {
  return Array.from(itensPedido)
    .filter(function (item) {
      return item.checked;
    })
    .map(function (item) {
      return item.value;
    });
}

Grave uma seleção pequena

function salvarPedido() {
  const ids = obterIdsSelecionados();

  try {
    if (ids.length === 0) {
      localStorage.removeItem(CHAVE_PEDIDO);
      statusPersistencia.textContent =
        "Nenhum pedido salvo neste navegador.";
      return;
    }

    localStorage.setItem(CHAVE_PEDIDO, JSON.stringify(ids));
    statusPersistencia.textContent =
      "Pedido salvo neste navegador.";
  } catch (erro) {
    console.error("Não foi possível salvar o pedido:", erro);
    statusPersistencia.textContent =
      "A seleção funciona, mas não pôde ser salva.";
  }
}

O try...catch é necessário porque o navegador ou suas configurações podem impedir o armazenamento. Mesmo nesse caso, a seleção e o cálculo continuam funcionando durante a visita atual.

Aula 4 — Restaurando, validando e inspecionando

Um valor salvo pode estar ausente, antigo ou inválido. Antes de marcar os campos, confirme que o conteúdo reconstruído é um array de textos. Identificadores que não existem mais no cardápio serão simplesmente ignorados.

function restaurarPedido() {
  try {
    const registro = localStorage.getItem(CHAVE_PEDIDO);
    if (!registro) return;

    const ids = JSON.parse(registro);
    const listaValida = Array.isArray(ids) &&
      ids.every(function (id) {
        return typeof id === "string";
      });

    if (!listaValida) {
      throw new TypeError("O pedido salvo não possui o formato esperado.");
    }

    let quantidadeRestaurada = 0;
    itensPedido.forEach(function (item) {
      item.checked = ids.includes(item.value);
      if (item.checked) quantidadeRestaurada += 1;
    });

    statusPersistencia.textContent = quantidadeRestaurada > 0
      ? "Pedido anterior restaurado."
      : "O pedido salvo não contém produtos atuais.";
  } catch (erro) {
    console.error("Não foi possível restaurar o pedido:", erro);
    statusPersistencia.textContent =
      "O pedido salvo não pôde ser lido. Faça uma nova seleção.";
  }
}

Conecte a persistência aos eventos existentes

Substitua o bloco antigo que registrava os eventos de mudança e limpeza por esta versão:

itensPedido.forEach(function (item) {
  item.addEventListener("change", function () {
    calcularPedido();
    salvarPedido();
  });
});

limparPedido.addEventListener("click", function () {
  itensPedido.forEach(function (item) {
    item.checked = false;
  });
  calcularPedido();
  salvarPedido();
});

Como o pedido depende dos produtos carregados no capítulo anterior, restaure a seleção somente depois do sucesso do fetch():

pedido.hidden = false;
limparPedido.hidden = false;
restaurarPedido();
calcularPedido();
Código completo para conferência — persistência do pedido
const CHAVE_PEDIDO = "cafeAurora:pedido";
const statusPersistencia = document.querySelector(
  "[data-status-persistencia]"
);

function obterIdsSelecionados() {
  return Array.from(itensPedido)
    .filter(function (item) {
      return item.checked;
    })
    .map(function (item) {
      return item.value;
    });
}

function salvarPedido() {
  const ids = obterIdsSelecionados();

  try {
    if (ids.length === 0) {
      localStorage.removeItem(CHAVE_PEDIDO);
      statusPersistencia.textContent =
        "Nenhum pedido salvo neste navegador.";
      return;
    }

    localStorage.setItem(CHAVE_PEDIDO, JSON.stringify(ids));
    statusPersistencia.textContent =
      "Pedido salvo neste navegador.";
  } catch (erro) {
    console.error("Não foi possível salvar o pedido:", erro);
    statusPersistencia.textContent =
      "A seleção funciona, mas não pôde ser salva.";
  }
}

function restaurarPedido() {
  try {
    const registro = localStorage.getItem(CHAVE_PEDIDO);
    if (!registro) return;

    const ids = JSON.parse(registro);
    const listaValida = Array.isArray(ids) &&
      ids.every(function (id) {
        return typeof id === "string";
      });

    if (!listaValida) {
      throw new TypeError("O pedido salvo não possui o formato esperado.");
    }

    let quantidadeRestaurada = 0;
    itensPedido.forEach(function (item) {
      item.checked = ids.includes(item.value);
      if (item.checked) quantidadeRestaurada += 1;
    });
    statusPersistencia.textContent = quantidadeRestaurada > 0
      ? "Pedido anterior restaurado."
      : "O pedido salvo não contém produtos atuais.";
  } catch (erro) {
    console.error("Não foi possível restaurar o pedido:", erro);
    statusPersistencia.textContent =
      "O pedido salvo não pôde ser lido. Faça uma nova seleção.";
  }
}

if (pedido && itensPedido.length && resumoPedido &&
    limparPedido && statusPersistencia) {
  itensPedido.forEach(function (item) {
    item.addEventListener("change", function () {
      calcularPedido();
      salvarPedido();
    });
  });

  limparPedido.addEventListener("click", function () {
    itensPedido.forEach(function (item) {
      item.checked = false;
    });
    calcularPedido();
    salvarPedido();
  });
}

Confira no painel Application

Chrome DevTools no painel Application com Local Storage aberto e uma tabela de chaves e valores
Abra Application, expanda Local Storage e escolha a origem do site. Localize cafeAurora:pedido, confira seu valor e teste a remoção da chave. A aparência pode variar. Fonte: documentação oficial do Chrome DevTools.

O projeto em um servidor local e o site publicado possuem origens diferentes. Portanto, um pedido salvo durante o desenvolvimento não aparecerá automaticamente no endereço público.

Armazenamento local não é cofre.

Não grave senha, cartão, documento, token de acesso ou informação pessoal sensível. Scripts executados naquela origem podem acessar esses dados. Use Web Storage apenas para informações pequenas e não sensíveis.

Use para dados pequenos.

Web Storage é síncrono: suas operações acontecem na linha principal. Ele serve bem para preferências e estados simples, mas não para imagens, grandes catálogos ou uma base de dados completa.

Erros comuns para revisar

  • Tentar salvar um array diretamente e obter um texto inadequado.
  • Chamar JSON.parse() quando getItem() devolveu null.
  • Salvar preços e descrições que já pertencem ao arquivo JSON.
  • Usar uma chave genérica como dados ou pedido.
  • Usar clear() e apagar dados de outras partes do site.
  • Restaurar a seleção antes de os produtos necessários terem sido carregados.
  • Confiar no armazenamento como lugar seguro para dados sensíveis.

Projeto aplicado — entrega do capítulo

Atualize o Café Aurora com:

  • chave cafeAurora:pedido;
  • armazenamento somente dos identificadores selecionados;
  • conversão segura com JSON.stringify() e JSON.parse();
  • restauração depois do carregamento dos produtos;
  • mensagens de pedido salvo, restaurado ou indisponível;
  • limpeza apenas da chave do pedido;
  • tratamento de falha sem interromper o cálculo atual.

Entregue o projeto e dois registros: a chave no painel Application e o pedido restaurado após recarregar a página.

Verifique sua aprendizagem

  • Escolho entre localStorage e sessionStorage conforme a duração necessária.
  • Uso os métodos da Web Storage API em vez de propriedades diretas.
  • Sei que chaves e valores são armazenados como texto.
  • Converto arrays para JSON e valido o valor ao recuperá-lo.
  • Salvo apenas os identificadores, preservando o JSON como fonte dos produtos.
  • Restauro o pedido no momento correto do carregamento.
  • Removo apenas a chave necessária e não armazeno informação sensível.
  • Localizo e interpreto o dado no painel Application.

Referências para consulta

Até aqui

O Café Aurora carrega o cardápio de um arquivo JSON e recupera uma seleção simples naquele navegador. No próximo capítulo, o projeto será revisado como produto completo: qualidade, testes e publicação.

Transfira o aprendizado

Imagine o site de uma pequena livraria. Decida se a categoria escolhida pelo visitante deveria usar localStorage ou sessionStorage, proponha uma chave clara e justifique em duas frases. Não desenvolva outro projeto completo.

Web I • 8 horas

Capítulo 11 — Testes, qualidade e publicação

Critérios de conclusão, testes manuais e automáticos, correções finais e publicação responsável do Café Aurora.

Funcionar no computador do autor ainda não encerra o projeto

O site do Café Aurora possui conteúdo, identidade visual, páginas, formulário, responsividade, interações, dados externos e persistência local. Agora ele precisa ser verificado como um produto que outras pessoas realmente usarão.

Neste capítulo, cada teste terá uma ação e um resultado esperado. O objetivo não é perseguir uma nota perfeita em uma ferramenta, mas encontrar problemas reais, corrigi-los e confirmar novamente antes e depois da publicação.

O que você vai aprender

  • Definir critérios objetivos para considerar o site pronto.
  • Testar navegação, teclado, ampliação, responsividade e jornadas completas.
  • Validar todas as páginas HTML e interpretar erros simples.
  • Usar Console, Network, Application e Lighthouse com finalidade clara.
  • Distinguir verificação automática de teste manual.
  • Publicar um site estático pelo GitHub Pages.
  • Repetir os testes essenciais no endereço público.

Aula 1 — Definindo quando o site está pronto

Um projeto está pronto para publicação quando atende a critérios verificáveis. “Parece bom” é uma percepção útil, mas não substitui a confirmação de que os caminhos principais funcionam.

JornadaAção de testeResultado esperado
NavegaçãoAbrir Início, Cardápio, Sobre e Contato.Todos os links chegam à página correta e identificam a página atual.
CardápioFiltrar bebidas e comidas.Os cards e a quantidade visível correspondem ao filtro.
DadosRecarregar, alterar temporariamente o caminho do JSON e depois restaurá-lo.O sucesso atualiza os cards; a falha preserva o conteúdo HTML.
PedidoSelecionar produtos, recarregar e limpar.Total correto, seleção restaurada e chave específica removida.
ContatoEnviar vazio, corrigir os campos e enviar novamente.A validação orienta o preenchimento e nenhum dado real é enviado.
ResponsividadeRedimensionar continuamente e testar teclado e ampliação.Não há conteúdo perdido, sobreposto ou inacessível.

Complete a acessibilidade de todas as páginas

O módulo já possui um link “Pular para o conteúdo”. Aplique a mesma solução ao Café Aurora: coloque o link como primeiro elemento dentro de body e identifique o conteúdo principal.

<body>
  <a class="skip-link" href="#conteudo">
    Pular para o conteúdo
  </a>

  <header>...</header>
  <main id="conteudo" tabindex="-1">
    ...
  </main>
</body>
.skip-link {
  position: absolute;
  top: -5rem;
  left: 1rem;
  z-index: 10;
  padding: .65rem .9rem;
  color: #fff;
  background: #1b1b1b;
  border-radius: .4rem;
}

.skip-link:focus {
  top: 1rem;
}

tabindex="-1" permite que o destino receba foco quando o link é acionado, sem acrescentá-lo à sequência normal da tecla Tab.

Teste sem mouse

  1. Recarregue a página e pressione Tab uma vez.
  2. Confirme que “Pular para o conteúdo” aparece e possui foco visível.
  3. Pressione Enter e confirme que o foco chega ao conteúdo principal.
  4. Continue com Tab por menus, filtros, pedido, formulário e links.

Aula 2 — Validando código e investigando falhas

Use o validador de HTML do W3C. Enquanto o projeto estiver local, escolha o envio de arquivo e valide separadamente index.html, cardapio.html, sobre.html e contato.html.

Erro

Indica uma estrutura que deve ser investigada e corrigida.

Aviso

Pede análise; nem sempre representa um defeito no contexto.

Linha

Ajuda a localizar o problema, mas a causa pode estar algumas linhas antes.

Nova validação

Depois da correção, valide novamente para detectar efeitos colaterais.

Corrija a causa, não somente a linha indicada.

Uma tag não fechada pode gerar vários erros posteriores. Comece pelo primeiro erro, revise a estrutura ao redor, corrija e valide novamente antes de alterar outros pontos.

Use cada painel para uma pergunta

PainelPergunta de teste
Elements e StylesO elemento correto existe e quais regras estão realmente aplicadas?
ConsoleHá erro de JavaScript durante alguma jornada?
NetworkTodos os arquivos carregaram sem 404? O JSON respondeu corretamente?
ApplicationA chave do pedido contém apenas os identificadores esperados?
Modo de dispositivosO layout continua utilizável em larguras diferentes?

Painel de liberação do projeto

Marque um item somente depois de executar o teste.

Verificações finais

0 de 8 verificações concluídas.

Aula 3 — Usando Lighthouse sem depender da nota

O Lighthouse executa verificações automáticas de desempenho, acessibilidade, boas práticas e SEO. Ele ajuda a encontrar indícios, mas não consegue decidir sozinho se o texto é claro, se a jornada faz sentido ou se todo o site funciona apenas com teclado.

Chrome DevTools aberto na guia Lighthouse com opções de dispositivo e categorias de auditoria
Abra o Café Aurora, acesse a guia Lighthouse, escolha Mobile e execute “Analyze page load”. Depois repita em Desktop. A aparência pode variar. Fonte: documentação oficial do Chrome DevTools.

Como interpretar o relatório

  1. Leia a descrição da verificação, não apenas a pontuação.
  2. Comece pelos problemas que afetam acesso, carregamento ou compreensão.
  3. Faça uma alteração por vez e confirme que ela não prejudicou outra parte.
  4. Execute novamente e registre o que realmente melhorou.
Uma nota alta não prova que o site está perfeito.

Ferramentas automáticas cobrem apenas parte da qualidade e podem produzir resultados diferentes conforme computador, rede e momento do teste. A liberação depende do conjunto: validação, testes manuais, jornadas e auditoria automática.

Registre uma melhoria real

Escolha um apontamento compreendido por você, confirme o problema na página, aplique uma correção pequena e repita o teste. Anote: problema, evidência, mudança e resultado. Não altere o projeto apenas para aumentar um número.

Aula 4 — Publicando com GitHub Pages

O Café Aurora é um site estático: o navegador recebe HTML, CSS, JavaScript, JSON e imagens. O GitHub Pages consegue publicar esses arquivos sem servidor próprio. O formulário permanece demonstrativo porque ainda não existe um serviço para receber mensagens.

Faça a revisão antes do envio

  • mantenha index.html na raiz do projeto;
  • preserve as pastas css, js, dados e img;
  • confira letras maiúsculas e minúsculas nos nomes dos arquivos;
  • remova senhas, documentos e qualquer dado pessoal real;
  • não envie arquivos temporários ou imagens que o site não utiliza.

Publique a partir da branch principal

  1. Crie no GitHub um repositório público chamado cafe-aurora.
  2. Envie o conteúdo da pasta do projeto e registre a alteração na branch main. O arquivo index.html deve aparecer na raiz do repositório, não dentro de outra pasta.
  3. Abra Settings → Pages.
  4. Em Build and deployment, escolha Deploy from a branch.
  5. Selecione main, a pasta /(root) e salve.
  6. Aguarde a publicação e use o botão Visit site quando aparecer.
Configuração do GitHub Pages com o seletor da branch de publicação e o botão Save
Na configuração da fonte, escolha a branch main. A interface pode variar. Fonte: documentação oficial do GitHub Pages.
A publicação pode levar alguns minutos.

O GitHub informa que alterações podem demorar até dez minutos para aparecer. Se houver falha, confira o histórico de execução do Pages e corrija a causa antes de reenviar arquivos aleatoriamente.

Repita os testes no endereço público

Abra todas as páginas pelo endereço publicado, confira Console e Network, teste o cardápio, o formulário e o pedido. O localStorage do servidor local não é o mesmo do domínio público; faça uma nova seleção para testar a persistência na nova origem.

Publicado significa público.

Não coloque no repositório senhas, chaves privadas, dados pessoais ou informações que não possam ser expostas. Apagar depois não garante que um segredo deixe de existir no histórico.

Erros comuns para revisar

  • Testar somente a página inicial e esquecer as demais jornadas.
  • Corrigir vários pontos ao mesmo tempo e não saber qual mudança causou regressão.
  • Considerar a pontuação do Lighthouse como garantia de acessibilidade.
  • Publicar uma pasta sem index.html na raiz escolhida.
  • Usar caminhos que funcionam no computador, mas não respeitam maiúsculas e minúsculas no servidor.
  • Esquecer de testar novamente o Fetch e o Web Storage no domínio público.
  • Enviar credenciais ou dados reais para um repositório público.

Projeto final — entrega do Web I

Entregue:

  • pasta final organizada e sem arquivos desnecessários;
  • quatro páginas com link “Pular para o conteúdo”;
  • HTML validado e jornadas testadas;
  • registro de uma melhoria orientada pelo Lighthouse;
  • repositório público sem dados sensíveis;
  • endereço do Café Aurora publicado e funcionando;
  • evidências do teste local e da confirmação no endereço público.

A entrega não precisa obter pontuação perfeita. Ela precisa demonstrar decisões compreendidas, funcionamento verificável, recuperação de falhas e ausência de problemas conhecidos que impeçam o uso.

Verifique sua aprendizagem

  • Transformo requisitos em ações de teste e resultados esperados.
  • Uso teclado, ampliação e redimensionamento nos testes manuais.
  • Valido cada documento HTML e começo pelo primeiro erro encontrado.
  • Escolho o painel do DevTools conforme a pergunta que preciso responder.
  • Interpreto o Lighthouse como apoio, não como aprovação automática.
  • Publico a estrutura correta sem expor dados sensíveis.
  • Repito as jornadas essenciais no domínio público.
  • Consigo explicar por que o Café Aurora está pronto para entrega.

Referências para consulta

Web I concluído

Você partiu de uma necessidade real e construiu um site completo, responsivo, acessível, interativo, capaz de carregar e preservar dados, testado e publicável. No Web II, o mesmo Café Aurora evolui de site para sistema executado no servidor; o Web III permanece reservado para uma etapa futura.

Transfira o aprendizado

Imagine que o site de uma barbearia local será publicado amanhã. Escreva cinco testes indispensáveis, cada um com ação e resultado esperado, e indique qual deles não poderia ser comprovado apenas pelo Lighthouse. Não construa outro site.