Menu flutuante no canto superior direito com links âncora pra cada seção (<h2>) da página. Tem botão hamburger ↔ X pra colapsar, animações em cascata na entrada e gera automaticamente os itens Topo e Rodapé.
Vê o componente em ação em páginas com muitos <h2> — o menu fica no canto superior direito quando a tela é grande.
Usar em:
<h2>Não usar em:
home-grid) — não faz sentidoinfo-accordion dominante — já existe o #sidenav do layout pra esse caso (é o que esta página usa, aliás)Em qualquer página da documentação, adicione uma única linha no topo do <body>:
<nav class="page-toc"></nav>
Pronto. O layout.js faz o resto: escaneia os <h2>, gera os links, insere "Topo" e "Rodapé", injeta o botão toggle, aplica scroll-margin-top.
Pra customizar o texto no TOC sem alterar o <h2> visível:
<h2 id="plain" data-toc-title="Plain" data-sub="Texto SQL puro">
Plain — texto SQL puro
</h2>
| Atributo | Efeito |
|---|---|
data-toc-title |
Texto que aparece no TOC (se diferente do <h2>) |
data-sub |
Subtítulo menor em cinza claro embaixo do título |
id |
Âncora do link. Se omitido, o JS gera a partir do texto |
Se a página quiser controle total (listar seções que não são <h2>, por exemplo), coloca o conteúdo dentro do <nav>:
<nav class="page-toc">
<div class="page-toc-title">Minhas seções</div>
<ul>
<li><a href="#secao-1">Seção customizada</a></li>
</ul>
</nav>
O JS detecta que já tem filhos e preserva.
| Largura | Comportamento |
|---|---|
| > 1440px | Menu aberto por default. Clicar num link mantém aberto (convive com o conteúdo lado a lado). |
| 641–1440px | Menu fechado por default. Hamburger visível; abrir sobrepõe conteúdo. Clicar num link fecha o menu sozinho. |
| ≤ 640px | Hamburger e menu somem totalmente (smartphone). |
O componente ficou dividido em duas partes reutilizáveis:
| Parte | Arquivo |
|---|---|
| CSS — estilos, transições, animações, breakpoints | apoio/documentacao/css/docs.css → bloco "PAGE TOC" |
| JS — detecção, auto-gen a partir dos h2, listeners | apoio/documentacao/js/layout.js → função initPageToc(inner) |
Nenhum HTML inline da página precisa ter <style> ou <script> próprio pro TOC. Tudo mora central.
initPageToc(inner)Quando o layout.js termina de renderizar a página, ele chama a função junto dos outros inits. O fluxo passo a passo:
document.querySelector('.page-toc') — se não tem o <nav>, aborta<nav> já tem filhos, aborta (preserva override manual)inner.querySelectorAll('h2') — se tem menos de 2, aborta<li> (<a href="#" data-toc-top="1">)id (se não tiver) e aplica scroll-margin-top: 80pxdata-toc-title (fallback = texto do h2) e data-sub (opcional)<li> (<a href="#" data-toc-bottom="1">)animationDelay de cada <li> (0.25s base + 0.05s por item)<button class="page-toc-toggle active"> com 2 SVGs (menu + close)data-toc-top → scrollTo({ top: 0 })data-toc-bottom → scrollTo({ top: scrollHeight })≤ 1440px → fecha o menu automaticamentewindow.innerWidth ≤ 1440px, inicia com .collapsed (menu escondido por default em tela menor)O que dá personalidade ao componente são 4 animações que tocam em sequência:
1. Entrada da barra
translateX(calc(100% + 40px)) → 0 + opacity: 0 → 1, 0.3s. A barra desliza de fora da tela pra dentro.
2. Links em cascata
Cada <li> entra com translateX(16px → 0) + fade, 0.3s. Os delays são aplicados via JS: 0.25s no primeiro (pra a barra terminar primeiro) e 0.05s de intervalo entre cada item seguinte.
3. Cross-fade X ↔ hamburger
Os dois SVGs ficam sobrepostos (position: absolute). Quando o estado muda:
opacity 1 → 0 + rotate 0 → 90° (gira pra fora)opacity 0 → 1 + rotate -90° → 0 (gira pra dentro)4. Linhas do hamburger crescendo
As 3 <line> do SVG do hamburger têm transform-origin: left center. Quando aparecem, crescem com scaleX(0 → 1) em cascata — delays 0s, 0.1s, 0.2s. Visualmente: linhas sendo "desenhadas" da esquerda pra direita, uma por vez.
Por que position: fixed em vez de sticky?
Sticky depende de um container pai. Como o layout injeta o conteúdo dentro de #content-inner com max-width, sticky criaria alinhamento inconsistente em páginas largas. Fixed garante posicionamento igual em qualquer página.
Por que largura fixa de 220px?
Texto em 2 linhas (título + subtítulo) precisa de ~200px pra respirar sem quebra estranha. Mais que isso rouba espaço do conteúdo principal em telas 1280–1440px.
Por que breakpoint 1440px?
Em tela menor, a janela não sobra largura suficiente pro menu de 220px conviver lado a lado com o conteúdo sem sobrepor. Acima de 1440px cabe permanentemente — menu fica aberto. Abaixo, menu começa fechado mas hamburger continua visível pra abrir sob demanda.
Por que menu fecha ao clicar num link em tela ≤1440?
UX de mobile menu. Se o menu sobrepõe conteúdo, depois de escolher a seção ele precisa sumir pra revelar o que o usuário foi ver.
Por que 0.25s de delay no primeiro link?
A barra tem transition: transform 0.3s ease. Se os links começassem em 0s, apareceriam no meio da entrada da barra — visualmente bagunçado. 0.25s dá tempo da barra chegar perto do destino antes.
Por que os links entram da direita pra esquerda?
A barra entra do lado direito. Os links seguindo a mesma direção reforçam o fluxo visual do canto da tela. Se entrassem de cima pra baixo, pareceria que vieram de lugar diferente.
Por que "Topo" e "Rodapé" com href="#" + preventDefault?
Se fossem href="#topo" / href="#rodape", sujariam a URL com âncoras falsas. Atributos data-toc-top / data-toc-bottom marcam os elementos pro handler reconhecer e chamar scrollTo() sem alterar o endereço.
Por que z-index 60 e 61?
O audio-player é sticky com z-index: 50. Pro TOC não ficar atrás dele quando abre sobreposto, precisa ser maior. Botão (61) sempre acima do próprio menu (60) pra permanecer clicável.
Por que data-sub em vez de parsing do "—" no texto?
Parsing heurístico é frágil — qualquer <h2> com travessão por outro motivo viraria subtítulo por engano. data-sub é explícito e deixa o autor decidir.
Construído em 2026-04-23 em um único dia. Começou como CSS inline em uma página e evoluiu por iteração até virar componente centralizado:
docs.css + layout.js com auto-gen dos h2