A base de tudo. Aqui não entra framework nenhum: é HTML para estrutura, CSS para estilo e JavaScript para comportamento, do jeito que o navegador entende sem intermediário. A trilha é longa e foi dividida em duas páginas — esta é a primeira metade, dos fundamentos até o CSS moderno.
Revisão já conversamos sobre issoNovo ainda não vimos6 partes · 41 tópicos · parte 1 de 2
Mostrar
Nenhum tópico com esse filtro.
PARTE 1
Fundamentos
Os conceitos que organizam todo o resto. Sem isso, os nomes de framework viram sopa de letrinha.
Front-end vs Back-end
Revisão
Front-end é tudo que roda no navegador do visitante: o que ele vê, lê e clica. Back-end é o que roda no servidor, invisível para ele: banco de dados, login, envio de e-mail, processamento de pagamento.
O exemplo que fecha a ideia é uma loja online. A vitrine com fotos, o carrinho que você vê na tela e o botão “comprar” são front-end. Quando você clica em comprar, quem confere estoque, calcula frete, cobra o cartão e grava o pedido é o back-end.
Pergunta
Front-end
Back-end
Onde roda?
No navegador do visitante
No servidor
O visitante vê o código?
Sim — é só abrir o “ver fonte”
Não, nunca
Linguagens típicas
HTML, CSS, JavaScript
JavaScript (Node), Python, PHP, Java, Ruby
Guarda dados?
Não de forma confiável
Sim — é a função dele
Seus projetos usam?
Sim, integralmente
Não — a conversão vai pro WhatsApp
Por que isso importa pra você: como sua landing page não tem back-end, nada que exija segredo pode estar nela. Uma chave de API escrita no seu HTML está pública para qualquer visitante. Se um dia precisar de algo assim, esse é exatamente o momento em que entra um back-end.
UI vs UX
Revisão
UI (User Interface) é o desenho: cores, tipografia, espaçamento, ícones, o formato do botão. UX (User Experience) é a experiência de usar: se a pessoa entende onde clicar, se o caminho até o objetivo é curto, se ela não fica perdida.
São independentes. Dá pra ter UI lindo com UX péssimo — um site premiado onde ninguém acha o telefone. E o contrário: uma página feia, sem graça, mas que resolve o problema da pessoa em dois cliques.
Numa landing page, a decisão mais cara costuma ser de UX, não de UI: quantos campos o formulário tem, se o botão de WhatsApp aparece antes ou depois da rolagem, se a pessoa precisa entender a oferta antes de agir. Cada campo a mais no formulário derruba conversão — isso é decisão de UX, e nenhuma escolha de cor conserta.
As três camadas: HTML, CSS e JavaScript
Revisão
Front-end é feito de três linguagens com papéis separados. Elas não competem — se empilham.
Camada
Papel
Na sua landing page
HTML
Estrutura — o esqueleto e o conteúdo
Títulos, textos, imagens, o formulário
CSS
Estilo — como cada coisa aparece
Cores da marca do cliente, layout, espaçamento
JS
Comportamento — o que acontece ao interagir
Menu que abre, botão que monta a mensagem do WhatsApp
O teste que mostra a separação: desligue o CSS de um site e ele vira um documento sem estilo, mas legível — o conteúdo continua lá. Desligue o JavaScript e ele fica estático, mas ainda navegável. É por isso que a regra “a página funciona com JS desligado” é razoável de exigir: o essencial não deveria depender da terceira camada.
html
<!-- HTML: a estrutura, sem estilo nenhum -->
<button class="cta">Falar no WhatsApp</button>
css
/* CSS: como esse botão aparece */
.cta {
background: #7A1C30;
color: #fff;
padding: 14px 28px;
border: 0;
border-radius: 4px;
cursor: pointer;
}
javascript
// JS: o que acontece quando clicam nele
document.querySelector('.cta').addEventListener('click', () => {
window.open('https://wa.me/5567999999999', '_blank', 'noopener');
});
PARTE 2
Frameworks e ecossistema
O mapa dos nomes que aparecem em toda vaga, tutorial e menu de ferramenta — e onde cada um se encaixa.
O que é framework (e o que é biblioteca)
Revisão
Framework é uma estrutura de código pronta que já resolve os problemas comuns de um tipo de projeto, para você não escrever tudo do zero.
A diferença para uma biblioteca está em quem manda no fluxo. Numa biblioteca, você chama ela quando precisa — uma função de formatar data, por exemplo; o controle é seu. Num framework, ele chama você: define a estrutura do projeto, as pastas, o ciclo de vida, e o seu código se encaixa nas regras dele.
Isso tem um nome clássico: inversão de controle. Biblioteca é ferramenta na sua mão. Framework é a oficina — você trabalha dentro dela.
O custo que ninguém menciona: framework economiza tempo no começo e cobra depois, em manutenção. Um projeto React de 2021 parado não “continua funcionando”: as dependências envelhecem, quebram entre si, e atualizar exige trabalho. Um HTML puro de 2015 abre hoje igual. Isso não é argumento contra frameworks — é o motivo de escolher um só quando ele paga o próprio custo.
Frameworks de front-end
RevisãoJS
Todos resolvem o mesmo problema central: manter a tela em sincronia com os dados sem você atualizar cada pedaço na mão.
Nome
O que é
Quando faz sentido
React
Biblioteca de interface da Meta, baseada em componentes. É a mais usada do mercado
Padrão de mercado; base de várias outras ferramentas
Next.js
React + estrutura pronta: rotas por pasta, renderização no servidor, otimizações
Sites com conteúdo dinâmico e SEO importante
Angular
Framework completo do Google, opinativo e estruturado
Sistemas grandes, times grandes, ambiente corporativo
Vue
Mais leve e com curva de aprendizado mais suave
Projetos médios; dá pra adotar aos poucos
Svelte
Compila para JS puro — não envia framework junto para o navegador
Quando performance e peso final são prioridade
A relação React → Next.js é de camada, não de rivalidade: Next.js é construído em cima do React. Trocar um pelo outro não é migração de linguagem, é reorganização de estrutura.
Um detalhe que explica a confusão nas listas de ferramentas: quando um app te deixa escolher entre “React, Next.js, Angular e Android”, os três primeiros são web e o quarto é uma plataforma de celular. Estão na mesma lista porque são destinos possíveis do código gerado, não porque sejam a mesma categoria de coisa.
Node.js — o runtime, não um framework
Revisão
Antes do Node, JavaScript só rodava dentro do navegador. O Node pegou o motor que o Chrome usa para executar JS (o V8) e passou a rodá-lo fora do navegador — no seu computador ou num servidor. Com isso, JavaScript virou uma linguagem que também serve para back-end.
Node não é framework: é o ambiente onde o código roda. A hierarquia fica assim:
hierarquia
JavaScript ← a linguagem
│
├── roda no navegador → front-end (React, Vue, ou JS puro)
│
└── roda no Node.js → back-end
└── Express, NestJS, Fastify (frameworks)
Vale saber mesmo sem fazer back-end: quase toda ferramenta moderna de desenvolvimento roda sobre Node. Se um dia você instalar o Tailwind, um compilador de Sass ou um minificador, é o Node que executa essas ferramentas na sua máquina — mesmo que o site entregue não tenha uma linha de Node.
Frameworks de back-end
Revisão
Em cima do Node
Nome
Característica
Express
O mais usado. Minimalista — dá liberdade e pouca estrutura
NestJS
Estruturado e opinativo, filosofia parecida com a do Angular
Fastify
Semelhante ao Express, com foco em performance
Em outras linguagens
Nome
Linguagem
Característica
Django
Python
“Pilhas incluídas”: já vem com banco, painel admin e autenticação
Laravel
PHP
Muito popular no Brasil para sites e sistemas web
Ruby on Rails
Ruby
Pioneiro do modelo; inspirou boa parte dos outros
Spring
Java
Padrão em bancos e empresas grandes
Para o seu trabalho, o momento em que um deles entra é bem definido: quando o cliente precisar de algo que não cabe no navegador — área de aluno com login, catálogo que ele edita sozinho, cobrança recorrente, envio de e-mail pelo próprio site.
Frameworks e ferramentas de CSS
RevisãoCSS
Tipo
Exemplos
Como funciona
CSS puro
—
Você escreve as regras. Zero dependência. É o seu caso
Utilitário
Tailwind
Classes prontas no HTML: class="flex p-4 text-lg"
Componentes
Bootstrap
Blocos já estilizados: grid, botões, cards, modais
Pré-processador
Sass/SCSS, Less
CSS com variáveis, aninhamento e funções — precisa ser compilado
CSS-in-JS
Styled Components
Estilo escrito dentro do componente JavaScript
Sobre pré-processadores: o Sass existia principalmente porque o CSS não tinha variáveis nem aninhamento. Hoje o CSS tem os dois nativamente (--cor e o aninhamento moderno). Boa parte do motivo de usar Sass em projeto pequeno desapareceu — e com ele desapareceu a necessidade de uma etapa de compilação.
Quando sair do vanilla
Revisão
O gatilho não é o número de páginas. Sete páginas estáticas continuam sendo sete arquivos HTML. O gatilho é a origem do conteúdo e a existência de estado compartilhado.
árvore de decisão
O conteúdo muda sem você editar o código?
├── NÃO → HTML puro resolve. Mesmo com 20 páginas.
└── SIM ↓
Precisa de login, banco de dados ou pagamento?
├── SIM → back-end + framework. Sem alternativa.
└── NÃO ↓
É muito item repetido com o mesmo layout
(catálogo, blog, portfólio grande)?
├── SIM → gerador de site estático ou Next.js
└── NÃO → HTML puro ainda ganha.
Sobre o caminho de migração: vanilla → React é reescrita, não upgrade. O React trabalha com componentes e estado, uma lógica diferente da manipulação direta do DOM; o CSS aproveita bastante, a lógica não. Já React → Next.js é tranquilo, porque um é construído sobre o outro: muda a organização de pastas e o roteamento, não a lógica dos componentes.
npm e package.json
Novo
npm (Node Package Manager) é o instalador de pacotes do mundo JavaScript. Você pede uma biblioteca pelo nome e ele baixa ela e tudo de que ela depende.
O package.json é a ficha do projeto: nome, versão, lista de dependências e atalhos de comando. É o arquivo que permite outra pessoa clonar seu projeto, rodar um comando e ter exatamente o mesmo ambiente.
terminal
npm init -y # cria o package.json
npm install nome-do-pacote # instala e anota na lista
npm run dev # roda um atalho definido em "scripts"
A pasta node_modules que aparece guarda os pacotes baixados. Ela é gigante e nunca vai pro Git nem pro servidor — o package.json é que descreve o que precisa ser reinstalado.
No seu fluxo: entregando HTML único, você não usa npm no projeto do cliente. Mas vai encontrar npm em qualquer tutorial moderno, e é assim que você instalaria uma ferramenta de apoio — um minificador, por exemplo — na sua máquina.
Bundlers: Vite e Webpack
Novo
Um bundler (empacotador) pega dezenas de arquivos de código-fonte e produz poucos arquivos otimizados para o navegador: junta, remove o que não é usado, minifica, converte sintaxe nova para versões que navegadores antigos entendem.
Webpack é o veterano: poderoso, configurável e trabalhoso. Vite é a geração seguinte: praticamente sem configuração, com recarga instantânea durante o desenvolvimento. Hoje, projeto novo começa com Vite.
É aqui que mora a palavra build. “Precisa de build” significa: o código que você escreve não é o código que vai pro ar — existe uma etapa de processamento no meio.
Por que isso é relevante pra você: a maior vantagem da sua entrega é justamente não ter essa etapa. O arquivo que você escreve é o arquivo que roda. Isso elimina uma classe inteira de problema — configuração quebrada, versão incompatível, build que falha no servidor.
TypeScript
NovoJS
TypeScript é JavaScript com tipos declarados. Você diz que uma variável é texto ou número, e o editor avisa antes de rodar quando algo não bate.
comparação
// JavaScript — só descobre o erro rodando
function calcularFrete(peso) {
return peso * 12.5;
}
calcularFrete("dois quilos"); // NaN, silenciosamente
// TypeScript — o editor acusa antes de rodar
function calcularFrete(peso: number): number {
return peso * 12.5;
}
calcularFrete("dois quilos"); // erro apontado na hora
O navegador não entende TypeScript — ele é convertido para JavaScript antes de ir pro ar. Ou seja: exige build.
Compensa em projeto grande, com várias pessoas, onde um erro de tipo custa caro. Em landing page de uma página, o custo da etapa de build supera o benefício.
PARTE 3
HTML na prática
A camada que quase todo mundo acha que já sabe — e que decide sozinha o SEO e a acessibilidade do site.
Anatomia de um documento HTML
NovoHTML
Todo arquivo HTML tem duas partes: o <head>, que ninguém vê mas os robôs leem, e o <body>, que é a página visível.
html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Título que aparece na aba e no Google</title>
<meta name="description" content="Resumo de até 155 caracteres.">
</head>
<body>
<!-- tudo que a pessoa vê -->
</body>
</html>
Linha
Para que serve
Se faltar
<!DOCTYPE html>
Diz que é HTML moderno
Navegador entra em “modo de compatibilidade” e o CSS se comporta diferente
lang="pt-BR"
Idioma da página
Leitor de tela lê português com sotaque inglês; o Chrome oferece traduzir
charset="UTF-8"
Codificação dos caracteres
Acentos viram ção
viewport
Manda o celular usar a largura real da tela
O site abre “dado zoom out” no celular. Erro clássico
title
Aba do navegador e título no Google
Aparece o nome do arquivo na busca
Atenção: essas cinco linhas resolvem sozinhas metade dos bugs de “o site tá estranho no celular” e “os acentos quebraram”. Vale decorar.
HTML semântico
NovoHTML
Semântico significa usar a tag que descreve o que aquilo é, em vez de <div> para tudo. Visualmente dá no mesmo — mas Google, leitores de tela e navegação por teclado leem a diferença.
<header>
<nav>...</nav>
</header>
<main>
<h1>Bar de drinks</h1>
<section>...</section>
</main>
<footer>...</footer>
As tags que resolvem 90% dos casos
Tag
Uso
<header>
Topo da página ou de uma seção
<nav>
Bloco de navegação (menu)
<main>
Conteúdo principal. Um por página
<section>
Seção temática, normalmente com título próprio
<article>
Conteúdo que faz sentido sozinho (post, card de depoimento)
<aside>
Conteúdo lateral, complementar
<footer>
Rodapé da página ou da seção
<figure> / <figcaption>
Imagem com legenda
Hierarquia de títulos
Um <h1> por página — é o título principal, e o Google usa ele com peso. Os demais descem em ordem: h2 para seções, h3 para subseções. Não pule níveis para conseguir um tamanho de fonte: tamanho é trabalho do CSS, hierarquia é trabalho do HTML.
Regra prática: se você está prestes a escrever <div class="alguma-coisa">, pergunte se existe uma tag com esse nome. Muitas vezes existe — e ela vem com comportamento de teclado e significado de graça.
Atributos essenciais
NovoHTML
Atributo
O que faz
class
Nome de grupo — pode repetir. É o que o CSS usa
id
Identificador único na página. Serve para âncoras (#contato) e para o JS
alt
Descrição da imagem. Obrigatório. Decorativa? alt="" vazio
href
Destino do link
target="_blank"
Abre em nova aba. Use junto com rel="noopener"
loading="lazy"
Só carrega a imagem quando ela chega perto da tela
width / height
Reserva o espaço da imagem e evita a página “pular” ao carregar
data-*
Dado personalizado que o JS lê: data-num="300"
imagem bem escrita
<img src="bar-montado.webp"
alt="Bar montado em festa de casamento ao ar livre"
width="1200" height="800"
loading="lazy">
Sobre data-*: é a ponte oficial entre HTML e JS. Você guarda o valor no HTML e lê pelo dataset — sem precisar escrever o número dentro do JavaScript.
Bloqueia o envio vazio e mostra a mensagem do navegador
type="tel" / "email" / "date"
Muda o teclado no celular e o formato aceito
inputmode="numeric"
Abre teclado numérico sem mudar a validação
min / max / step
Faixa permitida em número e data
autocomplete
Deixa o navegador preencher — aumenta conversão de verdade
pattern
Expressão regular para formatos específicos
O <label> não é opcional
O for do label tem que bater com o id do campo. Isso faz o clique no texto focar o campo, permite o leitor de tela anunciar o rótulo, e aumenta a área de toque no celular. Usar só placeholder como rótulo é um erro: ele some quando a pessoa começa a digitar.
javascript
// Deixa o navegador validar; só depois monta a mensagem
form.addEventListener('submit', (e) => {
e.preventDefault();
if (!form.reportValidity()) return; // mostra os erros nativos
const d = new FormData(form);
const msg = `Olá! Vim pelo site.\n\n`
+ `Nome: ${d.get('nome')}\n`
+ `Convidados: ${d.get('pessoas')}`;
window.open(`https://wa.me/5567999999999?text=${encodeURIComponent(msg)}`,
'_blank', 'noopener');
});
encodeURIComponent é obrigatório. Sem ele, acento, quebra de linha e o símbolo & quebram a URL do WhatsApp e a mensagem chega cortada.
Acessibilidade
NovoHTML
Acessibilidade não é recurso extra: é o site funcionar para quem usa leitor de tela, navega por teclado, enxerga pouco contraste ou está no sol com o brilho no mínimo. E o que resolve a maior parte é barato.
Item
O mínimo
Imagens
alt descritivo; alt="" quando for decoração
Contraste
4.5:1 para texto normal, 3:1 para texto grande
Foco
Contorno visível ao navegar por Tab. Nunca outline: none sem substituto
Formulário
Todo campo com <label> ligado pelo for
Botão vs link
Link leva a algum lugar; botão executa ação. Não troque os dois
Ícone sozinho
aria-label dizendo o que ele faz
Menu mobile
aria-expanded mudando de false para true
Movimento
Respeitar prefers-reduced-motion
html
<!-- botão só com ícone precisa de nome acessível -->
<button aria-label="Abrir menu" aria-expanded="false"
aria-controls="painel-menu">☰</button>
<!-- link de WhatsApp: o texto já descreve, não precisa de aria -->
<a href="https://wa.me/5567999999999">Falar no WhatsApp</a>
Teste de 30 segundos: aperte Tab repetidamente na sua página. Você consegue ver onde está o foco em todos os passos? Consegue chegar no botão de WhatsApp e acioná-lo com Enter? Se sim, você já está acima da média do mercado.
PARTE 4
CSS na prática
Onde você passa a maior parte do tempo. Os conceitos aqui explicam quase todo “por que isso não está funcionando”.
Box model
NovoCSS
Todo elemento é uma caixa com quatro camadas, de dentro para fora: conteúdo, padding (espaço interno), border (borda) e margin (espaço externo).
Por padrão, width: 300px define a largura só do conteúdo. Se você somar padding e borda, a caixa fica maior que 300px — e o layout estoura. O border-box muda isso: a largura passa a incluir padding e borda.
css
* { box-sizing: border-box; }
/* Agora isto ocupa exatamente 300px, não 340px: */
.card {
width: 300px;
padding: 20px; /* 20px de cada lado */
}
Colapso de margem
Duas margens verticais que se encostam não somam — vence a maior. Margem de 30px embaixo de um elemento e 20px em cima do próximo resultam em 30px, não 50px. É a causa mais comum de “o espaçamento não está batendo”. Usar gap no flex ou grid evita o problema inteiro.
Seletores e especificidade
NovoCSS
Quando duas regras mandam na mesma propriedade, vence a mais específica — não a que veio depois. Esta é a resposta para “editei a cor e não mudou nada”.
Sobre !important: resolve na hora e cria dívida. Depois de dois ou três, você precisa de !important para sobrescrever !important, e o arquivo vira areia movediça. Antes de usar, veja se não dá para baixar a especificidade da regra concorrente.
Seletores que vale conhecer
css
.card .titulo { } /* descendente: título dentro de card */
.card > .titulo { } /* filho direto apenas */
.card + .card { } /* um card imediatamente após outro */
.card:not(.ativo) { } /* todos menos os ativos */
a[href^="https"] { } /* link cujo href começa com https */
a[href$=".pdf"] { } /* link cujo href termina em .pdf */
Variáveis CSS (custom properties)
NovoCSS
Você declara um valor num lugar só e usa em todo o arquivo. Mudar a paleta do cliente vira editar cinco linhas em vez de caçar hexadecimais.
css
:root {
--marsala: #7A1C30;
--preto: #0B0B0B;
--off: #F9F8F6;
--espaco: 24px;
}
.botao {
background: var(--marsala);
color: var(--off);
padding: calc(var(--espaco) / 2) var(--espaco);
}
/* valor reserva, caso a variável não exista */
.card { border-color: var(--linha, #ddd); }
Diferente das variáveis do Sass, estas são lidas pelo navegador em tempo real — o JavaScript consegue trocar uma e a página inteira muda na hora. É assim que se faz tema claro/escuro sem duplicar CSS.
No seu fluxo: deixe um bloco :root no topo do CSS com todas as cores e medidas do cliente. Quando ele pedir “deixa mais escuro”, é um lugar só para mexer.
Unidades de medida
NovoCSS
Unidade
Relativa a
Use para
px
Nada — é fixo
Bordas, sombras, detalhes que não devem escalar
rem
Fonte da raiz (16px por padrão)
Fontes e espaçamentos. A escolha padrão
em
Fonte do próprio elemento
Padding que deve acompanhar o tamanho do texto
%
Elemento pai
Larguras dentro de um container
vw / vh
Largura / altura da janela
Blocos de tela cheia
ch
Largura do caractere “0”
Limitar linha de texto (60–70ch lê bem)
fr
Fração do espaço livre
Só dentro de Grid
clamp() — tipografia que se ajusta sozinha
Define mínimo, valor ideal e máximo numa linha só. Substitui várias media queries para tamanho de fonte.
css
h1 {
/* nunca menor que 32px, nunca maior que 64px,
entre os dois acompanha a largura da tela */
font-size: clamp(32px, 6vw, 64px);
}
Cuidado com 100vh no celular: a barra de endereço do navegador entra e sai ao rolar, e o vh muda junto — o conteúdo “pula”. A unidade dvh (dynamic viewport height) existe justamente para isso: height: 100dvh.
Flexbox
RevisãoCSS
Organiza elementos numa direção — linha ou coluna — e distribui o espaço entre eles. É a ferramenta para menu, barra de botões, card com ícone ao lado do texto.
css
.menu {
display: flex;
align-items: center; /* alinha no eixo vertical */
justify-content: space-between; /* distribui no horizontal */
gap: 24px; /* espaço entre os filhos */
flex-wrap: wrap; /* quebra linha se faltar espaço */
}
O truque da linha pontilhada de cardápio: rótulo, um elemento vazio com flex: 1 e border-bottom: 1px dotted, e o valor. O elemento do meio estica e preenche o espaço disponível, seja qual for o tamanho do texto dos lados.
Grid
RevisãoCSS
Organiza em duas dimensões — linhas e colunas ao mesmo tempo. É a ferramenta para grade de cards, galeria, layout de página com barra lateral.
css
.galeria {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 colunas iguais */
gap: 24px;
}
/* Grade que se adapta sozinha, sem media query:
cada card tem no mínimo 260px; cabem quantos couberem */
.galeria-auto {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 24px;
}
12345678
auto-fit + minmax: arraste o canto da caixasem media query
css
.grade {
display: grid;
gap: 8px;
/* quantas colunas de 64px couberem; o que sobra, divide igual */
grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
}
Aquele auto-fit com minmax é o padrão mais útil do Grid: resolve responsividade de galeria sem escrever uma media query. Vale decorar.
Flex ou Grid?
Situação
Escolha
Uma fila de itens (menu, botões)
Flex
Grade de cards
Grid
Conteúdo define o tamanho
Flex
Layout define o tamanho
Grid
Página inteira com áreas
Grid
Responsividade e media queries
NovoCSS
Mobile-first significa escrever o CSS base para a tela pequena e ir adicionando o que muda conforme a tela cresce. Fica menos código, porque o layout de celular é quase sempre o mais simples: tudo empilhado numa coluna.
Não persiga modelos de aparelho. Escolha o ponto onde o seu layout começa a ficar feio — esse é o breakpoint certo, mesmo que seja 843px.
Outras media queries úteis
css
@media (prefers-reduced-motion: reduce) { /* desliga animação */ }
@media (prefers-color-scheme: dark) { /* tema escuro do SO */ }
@media (hover: none) { /* telas sem mouse */ }
@media print { /* versão impressa */ }
Pseudo-classes e pseudo-elementos
NovoCSS
Pseudo-classe (um :) estiliza um estado do elemento. Pseudo-elemento (dois ::) cria uma parte visual que não existe no HTML.
css
/* estados */
.botao:hover { background: #5A1827; }
.botao:focus-visible{ outline: 2px solid #C9A66B; outline-offset: 2px; }
.botao:disabled { opacity: .5; cursor: not-allowed; }
.campo:invalid { border-color: #B8461F; }
/* posição na lista */
.card:first-child { }
.card:last-child { }
.card:nth-child(2n) { } /* os pares */
/* partes criadas pelo CSS */
.rotulo::before {
content: "";
display: inline-block;
width: 28px; height: 1px;
background: currentColor;
margin-right: 12px;
}
:focus-visible em vez de :focus: o contorno aparece para quem navega por teclado e não aparece no clique de mouse. Resolve a reclamação de “esse contorno feio ao clicar” sem quebrar a acessibilidade — que é o que outline: none faz.
O ::before e o ::after precisam de content para existir, nem que seja vazio. São a forma de criar detalhes gráficos — filetes, aspas decorativas, marcadores — sem sujar o HTML com <div> vazia.
Position e empilhamento
NovoCSS
Valor
Comportamento
Uso típico
static
Padrão. Segue o fluxo da página
Quase tudo
relative
Desloca a partir da posição original, sem tirar do fluxo
Servir de referência para um filho absoluto
absolute
Sai do fluxo; posiciona-se pelo ancestral posicionado mais próximo
Selo sobre uma imagem, badge no canto do card
fixed
Sai do fluxo; fica preso à janela
Botão flutuante de WhatsApp
sticky
Normal até chegar no limite, depois gruda
Menu que fica no topo ao rolar
css
/* menu que gruda no topo */
.menu {
position: sticky;
top: 0;
z-index: 50;
}
/* botão flutuante do WhatsApp */
.whatsapp {
position: fixed;
right: 24px;
bottom: 24px;
z-index: 40;
}
/* selo posicionado dentro de um card */
.card { position: relative; }
.card__selo{ position: absolute; top: 12px; right: 12px; }
Duas armadilhas do z-index: ele só funciona em elemento posicionado (qualquer coisa diferente de static). E overflow: hidden em um pai corta o filho sticky — se o seu menu grudento parou de funcionar, procure um overflow acima dele.
Transições e animações
RevisãoCSS
Transição suaviza a mudança entre dois estados. Animação (@keyframes) define uma sequência própria, que roda sozinha.
css
/* transição: do estado normal para o hover */
.botao {
background: #7A1C30;
transition: background .2s ease, transform .2s ease;
}
.botao:hover {
background: #5A1827;
transform: translateY(-2px);
}
/* animação própria */
@keyframes surgir {
from { opacity: 0; transform: translateY(18px); }
to { opacity: 1; transform: translateY(0); }
}
.card { animation: surgir .6s cubic-bezier(.22,.61,.36,1) both; }
Cubo 3D em CSS puro — arraste para girartransform-style: preserve-3d
transform e opacity são processadas pela placa de vídeo e não forçam o navegador a recalcular o layout. Animar width, height, top ou margin trava em celular. Precisa mover algo? Use transform: translate(), não top.
Opções de animação
Opção
Peso
Quando usar
CSS puro
Zero
Hover, fade, reveal simples. Resolve quase tudo
AOS
~14KB via CDN
Reveal ao rolar, sem escrever JS
GSAP
~70KB
Sequências complexas, timeline sincronizada
Lottie
~250KB + JSON
Ilustração animada vinda do After Effects
Framer Motion
Só com React
Não se aplica ao seu fluxo
Obrigatório em qualquer entrega: respeitar quem desligou animação no sistema. São três linhas e evita enjoo real em pessoas com sensibilidade vestibular.
O suficiente para fazer uma landing page interativa sem framework — que é exatamente o que os seus projetos pedem.
O DOM
NovoJS
Quando o navegador lê seu HTML, ele monta na memória uma árvore de objetos representando cada elemento. Essa árvore é o DOM (Document Object Model). O JavaScript não mexe no arquivo HTML — ele mexe nessa árvore, e o navegador redesenha a tela.
do arquivo à árvore
<body> document
<header> └── html
<h1>Art Bar</h1> ├── head
</header> └── body
<main> ├── header
<p>Drinks</p> │ └── h1 → "Art Bar"
</main> └── main
</body> └── p → "Drinks"
Duas consequências práticas. Primeira: se o seu <script> roda antes do elemento existir na árvore, ele não acha nada — por isso o script vai no fim do <body>. Segunda: alterações no DOM são temporárias. Recarregou a página, voltou tudo ao HTML original.
Selecionar e manipular elementos
NovoJS
selecionar
// um elemento — aceita qualquer seletor CSS
const menu = document.querySelector('.menu');
const cta = document.querySelector('#orcamento');
// vários — devolve uma lista
const cards = document.querySelectorAll('.card');
cards.forEach(card => console.log(card));
manipular
// texto (seguro — não interpreta HTML)
titulo.textContent = 'Novo título';
// HTML (só com conteúdo que VOCÊ escreveu)
box.innerHTML = '<strong>Atenção</strong>';
// classes — a forma correta de mudar aparência
menu.classList.add('is-solid');
menu.classList.remove('is-solid');
menu.classList.toggle('is-solid');
menu.classList.toggle('is-solid', window.scrollY > 80); // condicional
// atributos
botao.setAttribute('aria-expanded', 'true');
link.href = 'https://wa.me/5567999999999';
// esconder / mostrar
painel.hidden = true;
Regra que economiza muita dor: o JavaScript troca classes, o CSS decide a aparência. Escrever elemento.style.background = 'red' espalha estilo pelo JS e cria estilo inline, que tem especificidade altíssima e é difícil de sobrescrever depois.
Sobre innerHTML: nunca use com texto vindo de fora — de um formulário, de uma URL, de uma API. Isso executa HTML e script arbitrário na sua página. Para texto de terceiro, use sempre textContent.
Eventos
NovoJS
Evento é “algo aconteceu”: um clique, uma tecla, uma rolagem, um formulário enviado. Você registra uma função que roda quando isso acontece.
Em vez de registrar um ouvinte em cada card, registre um só no container e descubra quem foi clicado. Funciona inclusive para elementos criados depois.
O evento scroll dispara dezenas de vezes por segundo. Trabalho pesado direto nele engasga a rolagem — a solução é agrupar dentro de requestAnimationFrame.
Avisa quando um elemento entra ou sai da tela, sem você ficar calculando posição no scroll. É a ferramenta certa para animação de entrada, contador que começa ao aparecer e menu que destaca a seção atual.
javascript
const observador = new IntersectionObserver((entradas) => {
entradas.forEach(entrada => {
if (entrada.isIntersecting) {
entrada.target.classList.add('is-in');
observador.unobserve(entrada.target); // anima uma vez só
}
});
}, { threshold: .15 }); // 15% visível já conta
document.querySelectorAll('[data-reveal]')
.forEach(el => observador.observe(el));
O threshold é a fração do elemento que precisa estar visível para disparar. O unobserve é o que impede a animação de repetir a cada rolagem — e libera memória.
Armadilha de SEO e de primeira impressão: se o CSS deixa o elemento em opacity: 0 esperando o observer, e o JS falha ou está desligado, o conteúdo nunca aparece. Deixe o estado final como padrão e aplique o inicial só quando o JS assumir — ou aceite que a seção pode ficar invisível para alguém.
Fetch, Promises e async/await
NovoJS
Buscar algo na internet leva tempo, e o JavaScript não fica parado esperando. Uma Promise é a promessa de um resultado que chega depois. O async/await é a forma legível de escrever isso.
javascript
async function buscarCep(cep) {
try {
const resposta = await fetch(`https://viacep.com.br/ws/${cep}/json/`);
if (!resposta.ok) throw new Error('CEP não encontrado');
const dados = await resposta.json();
return dados.localidade; // "Campo Grande"
} catch (erro) {
console.error(erro);
return null;
}
}
// usando
const cidade = await buscarCep('79000000');
Peça
Papel
async
Marca a função como assíncrona; ela pode usar await
await
“Espere o resultado antes de seguir” — sem travar a página
try / catch
Captura erro de rede e resposta inválida
resposta.ok
Falso em erro 404 ou 500. O fetch não joga erro sozinho nesses casos
No seu fluxo: raramente necessário — um formulário que abre o WhatsApp não precisa de fetch. Aparece quando você quer buscar CEP para preencher endereço, puxar horários de uma planilha publicada ou consultar uma API pública.
JSON
NovoJS
JSON é um formato de texto para representar dados. É o que quase toda API devolve e o que você usa para separar conteúdo de código.
javascript
const DADOS = {
drinks: [
{ nome: "Negroni de Cerrado", alcool: true,
ingredientes: ["gin", "campari", "vermute"] },
{ nome: "Pantanal Sem Álcool", alcool: false,
ingredientes: ["maracujá", "hortelã", "tônica"] }
]
};
// gerar HTML a partir dos dados
const html = DADOS.drinks.map(d => `
<article class="card">
<h3>${d.nome}</h3>
<p>${d.ingredientes.join(' · ')}</p>
</article>
`).join('');
document.querySelector('.carta').innerHTML = html;
Padrão muito útil na sua entrega: colocar todo o conteúdo variável num const DADOS = {...} no topo do script. Quando o cliente pedir para trocar um drink ou um depoimento, você edita uma lista no começo do arquivo em vez de caçar HTML no meio da página.
localStorage
NovoJS
Guarda pequenos textos no navegador da pessoa, e eles sobrevivem ao fechar a aba. Serve para preferências: tema escolhido, aviso de cookie já dispensado, formulário parcialmente preenchido.
javascript
// sempre dentro de try/catch: pode estar bloqueado
try {
localStorage.setItem('tema', 'escuro');
const tema = localStorage.getItem('tema'); // "escuro" ou null
localStorage.removeItem('tema');
} catch (e) {
// aba anônima ou cookies bloqueados — a página segue funcionando
}
// guardando objeto: precisa virar texto
localStorage.setItem('rascunho', JSON.stringify({ nome: 'Ana' }));
const rascunho = JSON.parse(localStorage.getItem('rascunho') || '{}');
Característica
Detalhe
Capacidade
Cerca de 5MB por site
Alcance
Um navegador, um dispositivo. Não sincroniza
Formato
Só texto — objetos passam por JSON.stringify
Segurança
Visível e editável pelo visitante. Nunca guarde nada sensível
sessionStorage
Igual, mas apaga ao fechar a aba
Não é banco de dados. O visitante limpa o histórico e os dados somem. Abriu no celular, não vê o que salvou no computador. Para qualquer coisa que precise durar de verdade, é back-end.
PARTE 6
CSS moderno
Recursos que chegaram nos últimos dois anos e que apagam JavaScript do seu arquivo. Todos funcionam hoje, sem build e sem biblioteca.
O seletor :has()
NovoCSS
Durante vinte anos o CSS só olhava para baixo: você estilizava um filho, nunca o pai. O :has() inverte isso — estiliza um elemento com base no que existe dentro dele.
css
/* card que TEM imagem ganha layout diferente */
.card:has(img) {
grid-template-columns: 120px 1fr;
}
/* campo preenchido: esconde o rótulo flutuante */
.campo:has(input:not(:placeholder-shown)) .rotulo {
opacity: 0;
}
/* o formulário inteiro reage a um checkbox marcado */
form:has(#outro:checked) .campo-extra {
display: block;
}
/* menu aberto trava a rolagem — sem tocar no JS */
body:has(.menu-painel.is-aberto) {
overflow: hidden;
}
Aquele último exemplo merece atenção: é um caso em que você provavelmente escreve document.body.style.overflow = 'hidden' no JavaScript. Com :has(), o CSS reage sozinho à classe que já existe.
Nome informal: chamam de “seletor pai”, mas ele é mais que isso — funciona com irmãos também. .titulo:has(+ .subtitulo) estiliza um título só quando existe um subtítulo logo depois dele.
Container queries
NovoCSS
Media query pergunta “qual o tamanho da tela?”. Container query pergunta “qual o tamanho do espaço onde eu estou?”. A diferença é grande: o mesmo card pode aparecer largo numa seção e estreito numa barra lateral, e se adaptar sozinho aos dois.
css
/* 1. declara que este elemento é um container mensurável */
.coluna {
container-type: inline-size;
container-name: coluna;
}
/* 2. o filho reage à largura DA COLUNA, não da tela */
@container coluna (min-width: 420px) {
.card {
display: grid;
grid-template-columns: 140px 1fr;
gap: 16px;
}
}
Por que isso muda o jeito de pensar: com media query, um componente precisa saber em que ponto da página ele foi colocado. Com container query, ele se vira sozinho em qualquer lugar. É o que torna um componente de verdade reutilizável.
Onde te ajuda hoje: naquela grade de cards que você monta com auto-fit. Quando a coluna fica estreita, o card pode empilhar foto e texto; quando fica larga, coloca lado a lado — sem você escolher breakpoint de tela nenhum.
Aninhamento nativo e @layer
NovoCSS
O CSS agora aninha regras sem pré-processador. Era o principal motivo de usar Sass — e ele sumiu.
O & representa o próprio seletor pai. Dentro do aninhamento você também pode colocar media queries, o que mantém tudo sobre um componente no mesmo bloco.
@layer — resolvendo brigas de especificidade
Camadas definem a ordem de prioridade independentemente da especificidade. Uma regra numa camada posterior vence uma regra mais específica de uma camada anterior. É a solução estrutural para o problema que hoje se resolve com !important.
css
@layer base, componentes, utilidades;
@layer base {
#conteudo p { color: gray; } /* especificidade alta */
}
@layer utilidades {
.texto-escuro { color: black; } /* especificidade baixa... e vence */
}
Cuidado com aninhamento profundo. Aninhar quatro níveis gera seletores enormes e especificidade alta — o mesmo problema que o Sass criou em tanto projeto. Dois níveis costuma ser o limite saudável.
Cores modernas: oklch() e color-mix()
NovoCSS
Hexadecimal não tem lógica interna: #7A1C30 não te diz nada, e “clarear 10%” vira tentativa e erro. O oklch() descreve a cor em três números que fazem sentido — Luminosidade, Croma (saturação) e Hue (matiz, em graus).
css
:root {
--marca-h: 345; /* o matiz do marsala, em graus */
--marca: oklch(38% 0.13 var(--marca-h));
--marca-clara: oklch(58% 0.13 var(--marca-h)); /* só a luz mudou */
--marca-escura:oklch(26% 0.13 var(--marca-h));
}
A vantagem prática: a paleta inteira do cliente sai de um número. Mudou o matiz, mudou tudo de forma coerente — e a diferença de luminosidade é perceptualmente uniforme, coisa que o HSL não garante.
color-mix() — misturar sem calculadora
css
/* a cor da marca com 15% de transparência */
.aviso { background: color-mix(in srgb, var(--marca) 15%, transparent); }
/* a cor da marca puxada para o branco */
.suave { background: color-mix(in srgb, var(--marca) 20%, white); }
Uso direto no seu trabalho: aquele fundo claro de seção que combina com a cor do cliente. Em vez de pedir o hexadecimal ou chutar no conta-gotas, você deriva do próprio token — e se o cliente trocar a cor, o fundo acompanha.
Modo escuro: color-scheme, light-dark() e o alternador sem flash
NovoCSS
Seguir o tema do sistema é fácil: um @media (prefers-color-scheme: dark) resolve quando o cliente não pede botão nenhum. O problema aparece quando ele pede — alternador manual, escolha lembrada na próxima visita, e a página sem piscar clara antes de escurecer.
Primeiro: declare que a página sabe lidar com os dois temas
html
<meta name="color-scheme" content="light dark">
Sem essa declaração, o navegador trata a página como só-clara: barra de rolagem, campo de formulário e seletor de data continuam com aparência clara mesmo que o seu fundo seja escuro. Com ela, esses elementos nativos acompanham o tema. A propriedade color-scheme no CSS faz o mesmo; a meta tag tem a vantagem de ser lida antes do CSS carregar, o que ajuda a evitar um clarão branco no início.
light-dark() — as duas cores num lugar só
Antes, cada cor que muda de tema pedia duas declarações: uma no :root e outra dentro do @media (prefers-color-scheme: dark), repetidas para cada variável. A função light-dark() junta as duas.
light-dark(): as duas cores num lugar sócolor-scheme
css
.card {
color-scheme: light; /* dark, ou "light dark" para seguir o sistema */
background: light-dark(#FFFCF7, #16181C);
color: light-dark(#3F444D, #E8E6E1);
border: 1px solid light-dark(#E6E2DA, #2E3236);
}
.card .botao {
background: light-dark(#7A1C30, #D9869A);
color: light-dark(#FFFFFF, #16181C);
}
Com color-scheme: light dark no :root, o navegador usa o primeiro valor de cada light-dark() quando o sistema está no claro e o segundo quando está no escuro — sem media query. Aproveite para escolher cada par com oklch(): mesma cor, só a luminosidade muda.
O alternador manual: vencer a escolha do sistema
Quando o cliente quer um botão de sol/lua, ele precisa sobrescrever a preferência do sistema. Um atributo no <html> troca o color-scheme dali, e o light-dark() acompanha sozinho.
css
:root {
color-scheme: light dark; /* segue o sistema por padrão */
}
html[data-tema="claro"] { color-scheme: light; }
html[data-tema="escuro"] { color-scheme: dark; }
app.js
const botao = document.querySelector('.alternador-tema');
if (botao) {
botao.addEventListener('click', () => {
const raiz = document.documentElement;
// sem escolha salva, o "atual" é o que o sistema está mostrando
const escuroAgora = raiz.dataset.tema
? raiz.dataset.tema === 'escuro'
: matchMedia('(prefers-color-scheme: dark)').matches;
const novo = escuroAgora ? 'claro' : 'escuro';
raiz.dataset.tema = novo;
try { localStorage.setItem('tema', novo); } catch (e) {}
});
}
A checagem com matchMedia não é enfeite: sem ela, quem está com o sistema no escuro clicaria no botão e nada mudaria na primeira vez, porque o "próximo" tema calculado seria o que já está na tela.
O flash de tema errado — e como evitar
Se você lê o localStorage só depois que a página carregou, o visitante vê a página abrir no tema padrão e trocar de repente para o salvo. A saída é aplicar o atributo antes do CSS renderizar, com um script inline no <head>, antes de qualquer <link rel="stylesheet">.
Por que inline e não no app.js: um arquivo externo só roda depois de baixado, e nesse intervalo o navegador já pintou a página no tema errado. O script precisa ser pequeno, ficar no começo do <head> e ter try/catch — aba anônima ou localStorage bloqueado não pode quebrar a página.
A meta tag e o light-dark() funcionam sozinhos, seguindo o sistema. O alternador e o script inline só fazem sentido juntos, e só se o cliente pediu o botão — sem esse pedido, pare no light-dark().
Animação por rolagem sem JavaScript
NovoCSS
Aquele reveal ao rolar que hoje exige IntersectionObserver — ou a biblioteca AOS — virou CSS puro. E roda na thread de composição, então não trava nem em celular fraco.
css
@keyframes surgir {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
.secao {
animation: surgir linear both;
animation-timeline: view(); /* atrelada à entrada na tela */
animation-range: entry 10% cover 35%; /* começo e fim do movimento */
}
/* barra de progresso de leitura, no topo da página */
.progresso {
animation: encher linear;
animation-timeline: scroll(); /* atrelada à rolagem da página */
}
@keyframes encher { to { width: 100%; } }
Linha do tempo
Atrelada a
Uso
scroll()
A rolagem da página inteira
Barra de progresso, parallax
view()
A entrada do elemento na tela
Reveal de seção, fade de imagem
Suporte ainda não é universal. Chrome e Edge sim; Safari e Firefox chegaram depois. Como é animação, a degradação é segura: onde não funciona, o elemento aparece estático. Mas nunca esconda conteúdo dependendo disso — vale a mesma regra do IntersectionObserver.
View Transitions
NovoCSS
Transição animada entre estados — ou entre páginas diferentes — com o navegador cuidando do trabalho pesado. É o efeito que antes só existia em aplicativo nativo ou em SPA com framework.
css
/* transição suave entre páginas do mesmo site */
@view-transition { navigation: auto; }
/* elemento que "viaja" de uma página para a outra */
.hero-foto { view-transition-name: foto-principal; }
Com esse nome igual nas duas páginas, o navegador entende que é o mesmo elemento e anima a transformação entre uma posição e a outra — a foto do card cresce e vira a foto do topo da página seguinte.
Onde isso te interessa: se um dia você entregar um site de várias páginas em HTML puro, duas linhas de CSS dão a ele a sensação de aplicativo. É exatamente o tipo de polimento que fazia as pessoas acharem que precisavam de framework.