Search buenodc

Nesta página
  1. Importante
  2. Contextualização
  3. Como começar
  4. Como funciona
  5. Cronograma
  6. Missão 1 — Menu Hamburguer
  7. Missão 2 — Abas de Navegação
  8. Missão 3 — Modal de Detalhes
  9. Missão 4 — UX Avançada
  10. Entrega individual

Atividade Eventos — VibeStore

Disciplina: Programação Web 2
Assunto: Eventos e Interatividade
Professor: Me. Danilo Bueno
Grupos: duplas
Duração: 4 aulas de 50 minutos (em sala)


Importante

Antes de começar a atividade leia o material de apoio sobre JavaScript!! É imprenscindível que você leia antes de começar a codar, pois a atividade é justamente para praticar os conceitos apresentados no material.


Contextualização

A VibeStore é uma loja online de produtos para desenvolvedores: camisetas, moletons e acessórios com estampas do universo dev. O time de design entregou o HTML e o CSS da vitrine, mas a página está completamente estática — nenhum botão funciona.

A dupla foi contratada como time de frontend para dar vida à interface: o menu hamburguer precisa abrir e fechar, as abas devem carregar os produtos de cada categoria e o modal tem que exibir os detalhes do item selecionado.


Como começar

Crie três arquivos na mesma pasta:

index.html — copie o código abaixo e não modifique este arquivo:

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <title>VibeStore</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>

    <!-- Cabeçalho com menu hamburguer -->
    <header>
      <span class="logo">VibeStore</span>
      <button id="btn-menu" aria-label="Abrir menu">☰</button>
      <nav id="menu">
        <a href="#" class="link-menu">Início</a>
        <a href="#" class="link-menu">Sobre</a>
        <a href="#" class="link-menu">Contato</a>
      </nav>
    </header>

    <!-- Abas de categorias -->
    <main>
      <div id="abas">
        <button class="btn-aba ativo" data-aba="camisetas">Camisetas</button>
        <button class="btn-aba" data-aba="moletons">Moletons</button>
        <button class="btn-aba" data-aba="acessorios">Acessórios</button>
      </div>

      <div id="painel-camisetas" class="painel"></div>
      <div id="painel-moletons" class="painel escondido"></div>
      <div id="painel-acessorios" class="painel escondido"></div>
    </main>

    <!-- Modal de detalhes -->
    <div id="modal" class="escondido">
      <div class="modal-overlay"></div>
      <div class="modal-conteudo">
        <button id="btn-fechar-modal">✕</button>
        <h2 id="modal-nome"></h2>
        <p id="modal-descricao"></p>
        <p id="modal-preco"></p>
      </div>
    </div>

    <script src="vibestore.js"></script>
  </body>
</html>

styles.css — copie o código abaixo e não modifique este arquivo:

* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: sans-serif; }

/* Header */
header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  background: #1a1a2e;
  color: white;
}
.logo { font-weight: bold; font-size: 1.2rem; }
#btn-menu { background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; }

/* Menu — oculto por padrão, visível quando .ativo */
#menu {
  display: none;
  flex-direction: column;
  position: absolute;
  top: 56px;
  right: 0;
  background: #16213e;
  min-width: 160px;
  padding: 0.5rem 0;
  z-index: 100;
}
#menu.ativo { display: flex; }
#menu a { color: white; text-decoration: none; padding: 0.75rem 1.5rem; }
#menu a:hover { background: #0f3460; }

/* Abas */
main { padding: 1.5rem; }
#abas { display: flex; gap: 0; border-bottom: 2px solid #1a1a2e; margin-bottom: 1.5rem; }
.btn-aba {
  padding: 0.6rem 1.5rem;
  border: 2px solid transparent;
  border-radius: 6px 6px 0 0;
  background: #f0f0f0;
  cursor: pointer;
  font-size: 0.9rem;
  color: #666;
  position: relative;
  bottom: -2px;
  transition: background 0.15s;
}
.btn-aba:hover { background: #e0e0e0; }
.btn-aba.ativo {
  background: white;
  border-color: #1a1a2e;
  border-bottom-color: white;
  color: #1a1a2e;
  font-weight: 600;
}

/* Painéis */
.painel { display: flex; flex-wrap: wrap; gap: 1rem; }
.escondido { display: none !important; }

/* Cards */
.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 1rem;
  width: 200px;
}
.card h3 { font-size: 1rem; margin-bottom: 0.5rem; }
.card p  { font-size: 0.875rem; color: #555; margin-bottom: 0.75rem; }
.btn-detalhes {
  width: 100%;
  padding: 0.4rem;
  background: #1a1a2e;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

/* Modal */
#modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.5);
}
.modal-conteudo {
  position: relative;
  background: white;
  border-radius: 8px;
  padding: 2rem;
  max-width: 400px;
  width: 90%;
  z-index: 1;
}
#btn-fechar-modal {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  background: none;
  border: none;
  font-size: 1.25rem;
  cursor: pointer;
}

vibestore.js — copie o código abaixo. Todo o código da atividade vai abaixo do objeto produtos:

const produtos = {
  camisetas: [
    { id: 1, nome: "Camiseta Dev Clássica",  preco: 59.90,  descricao: "100% algodão. Estampa minimalista para o dia a dia." },
    { id: 2, nome: "Camiseta Open Source",   preco: 64.90,  descricao: "Para quem compartilha código, ideias e café." },
    { id: 3, nome: "Camiseta Semicolon",     preco: 54.90,  descricao: "Um tributo ao símbolo mais incompreendido da programação." },
  ],
  moletons: [
    { id: 4, nome: "Moletom Dark Mode",      preco: 129.90, descricao: "Fundo escuro, igual ao seu tema favorito." },
    { id: 5, nome: "Moletom Pull Request",   preco: 119.90, descricao: "Porque todo bom merge merece uma celebração." },
  ],
  acessorios: [
    { id: 6, nome: "Caneca Terminal",        preco: 39.90,  descricao: "Começa o dia com café e um bom comando." },
    { id: 7, nome: "Mousepad Grid",          preco: 49.90,  descricao: "Layout em grid, até na área de trabalho." },
  ],
};

// ─── seu código começa aqui ───────────────────────────────────

Como funciona

  • Trabalhem em dupla num único arquivo vibestore.js
  • Testem cada tarefa antes de avançar — abram o index.html no navegador com o DevTools aberto (F12)
  • Não é necessário modificar os arquivos index.html e styles.css

Cronograma

AulaFocoMissões
1ªMenu hamburguerMissão 1
2ªAbas de navegaçãoMissão 2
3ªModal de detalhesMissão 3
4ªUX avançada + entregaMissão 4 + envio

Missão 1 — Menu Hamburguer

Objetivo: fazer o botão ☰ abrir e fechar o menu de navegação.

Tarefa 1.1: selecione o botão #btn-menu e o elemento #menu. Adicione um listener de "click" no botão. No callback, use classList.toggle("ativo") no menu.

Abra o index.html no navegador e clique no botão — o menu deve aparecer e desaparecer a cada clique.

Pergunta 1.1: qual é a diferença entre classList.add("ativo") e classList.toggle("ativo")? O que aconteceria com add se você clicasse duas vezes?


Tarefa 1.2: selecione todos os links do menu com querySelectorAll(".link-menu"). Percorra o resultado com forEach e, para cada link, adicione um listener de "click" que remove a classe "ativo" do menu.

Teste clicando em um link — o menu deve fechar.


Tarefa 1.3: adicione um listener de "keydown" no document. No callback, verifique se event.key === "Escape". Se sim, remova a classe "ativo" do menu.

Teste: abra o menu e pressione Esc — deve fechar.

Pergunta 1.3: por que o listener de keydown foi adicionado no document e não no botão #btn-menu?


Missão 2 — Abas de Navegação

Objetivo: exibir os produtos da primeira categoria e depois fazer as abas alternar entre elas.

Tarefa 2.1: antes de fazer as abas funcionar, garanta que o conteúdo apareça na tela. Selecione o painel #painel-camisetas. Para cada produto em produtos.camisetas, crie um <div class="card"> com:

  • Um <h3> com o nome do produto
  • Um <p> com o preço formatado (ex: R$ 59,90)
  • Um <button class="btn-detalhes"> com data-id igual ao id do produto e texto "Ver detalhes"

Adicione cada card ao painel e recarregue — os cards de camisetas devem aparecer imediatamente.

Dica: use produto.preco.toFixed(2).replace(".", ",") para formatar o preço.


Tarefa 2.2: selecione todos os botões .btn-aba com querySelectorAll. Para cada botão, adicione um listener de "click". No callback:

  • Remova a classe "ativo" de todos os botões .btn-aba
  • Adicione "ativo" apenas no botão clicado (event.target)

Teste clicando nas abas — o estilo ativo deve trocar.

Pergunta 2.2: o que é event.target? É sempre o mesmo elemento que recebeu o addEventListener?


Tarefa 2.3: ainda no mesmo listener, adicione a lógica de troca de painel e carregamento:

  • Leia o nome da categoria com event.target.dataset.aba
  • Selecione todos os .painel e adicione "escondido" em cada um
  • Remova "escondido" do painel "#painel-" + aba
  • Limpe o painel com innerHTML = "" e popule com os produtos de produtos[aba] (mesmo código da Tarefa 2.1, mas usando a variável aba)

Teste: clicar nas abas deve trocar o painel e mostrar os produtos da categoria selecionada.


Missão 3 — Modal de Detalhes

Objetivo: abrir o modal com os detalhes do produto ao clicar em “Ver detalhes”.

Tarefa 3.1: selecione o #btn-fechar-modal e o #modal. Adicione um listener de "click" no botão fechar. No callback, adicione a classe "escondido" ao modal.

Teste: abra o DevTools, remova manualmente escondido do modal, depois clique no X — deve fechar.


Tarefa 3.2: em vez de adicionar um listener em cada botão “Ver detalhes” (que são criados dinamicamente), use event delegation. Selecione o elemento <main> e adicione um listener de "click". No callback:

  • Verifique se event.target.classList.contains("btn-detalhes")
  • Se sim: imprima event.target.dataset.id no console

Pergunta 3.2: por que usar event delegation em vez de adicionar um listener em cada botão de produto?


Tarefa 3.3: dentro do if da tarefa anterior, use o data-id para encontrar o produto no objeto produtos. Como o objeto tem categorias, você precisará procurar em todas. Depois de encontrar o produto:

  • Preencha #modal-nome com o nome
  • Preencha #modal-descricao com a descrição
  • Preencha #modal-preco com o preço formatado
  • Remova a classe "escondido" do modal

Teste: clicar em “Ver detalhes” deve abrir o modal com as informações corretas.

Dica: para buscar o produto em todas as categorias, use Object.values(produtos).flat().find(p => p.id === id).


Missão 4 — UX Avançada

Objetivo: polir a experiência: bloquear scroll, fechar com Escape e refatorar o código.

Tarefa 4.1: quando o modal abrir, adicione document.body.style.overflow = "hidden" para impedir o scroll da página. Quando fechar, restaure com = "".

Teste com uma página longa (adicione produtos suficientes) — ao abrir o modal, a página não deve rolar.


Tarefa 4.2: reutilize o listener de keydown já criado na Tarefa 1.3. Além de fechar o menu, adicione uma condição para fechar o modal quando Escape for pressionado — mas somente se o modal estiver visível (dica: !modal.classList.contains("escondido")).

Pergunta 4.2: como você verificou se o modal estava aberto antes de agir? Por que essa verificação é necessária?


Tarefa 4.3: extraia duas funções reutilizáveis no início do seu código — antes dos listeners:

function abrirModal(produto) { /* ... */ }
function fecharModal() { /* ... */ }

Refatore todos os pontos que abrem ou fecham o modal para usar essas funções.


Tarefa 4.4 (desafio): implemente o fechamento do modal ao clicar no overlay (fundo escuro). O clique no .modal-conteudo (a caixa branca) não deve fechar o modal.

Dica: adicione um listener de "click" no #modal. No callback do .modal-conteudo, use event.stopPropagation() para impedir que o clique suba até o #modal.


Entrega individual

Acesse o Moodle e responda o questionário de reflexão individual — cada membro da dupla entrega o seu próprio.