Use isto quando um diagrama estático não consegue mostrar movimento — quando a lição é sobre o que acontece, em qual ordem. Aqui: uma requisição web viaja por um balanceador de carga até um de três servidores de aplicação, com controles Reproduzir / Pausar / Passo.
Este é um exemplar copiável, não uma lição finalizada. Leve o bloco <section id="player"> (e sua fatia de CSS + o <script>) para uma lição real construída a partir de assets/lesson-template.html — coloque-o onde fica a demo id="try-it" do template.
Quando muita gente acessa um site movimentado ao mesmo tempo, nenhum computador sozinho responde a todos. Um balanceador de carga fica na porta de entrada e entrega a requisição de cada visitante para o computador de back-end que estiver menos ocupado. O visitante nunca percebe — ele apenas recebe uma resposta.
Uma figura de caixas e setas pode mostrar que isso acontece, mas não a ordem em que acontece. Esta demo permite que você aperte Reproduzir e veja de fato uma requisição viajar: entra pela porta de entrada, sai para um servidor escolhido, o trabalho é feito e uma resposta volta. Aperte Passo para avançar um tempo de cada vez.
Pense nisso como… um recepcionista em um restaurante lotado. As pessoas chegam à porta (o balanceador de carga), e o recepcionista olha ao redor e acomoda cada grupo na mesa (servidor de aplicação) que tiver um garçom livre — assim nenhum garçom sozinho fica sobrecarregado enquanto outros ficam parados.
O balanceador de carga é um proxy reverso (ex.: Nginx, HAProxy ou um ALB L7 gerenciado) que encerra a conexão do cliente e abre sua própria conexão com um upstream escolhido. A seleção aqui é por menor número de conexões (least-connections); variantes round-robin e ponderadas são comuns. Um health check periódico remove do pool um upstream não saudável, então o servidor escolhido é sempre um que passou recentemente em uma verificação.
O tempo do "a resposta volta" é a resposta fluindo de volta pela mesma conexão intermediada pelo proxy. Na prática, o servidor de aplicação pode também acessar um banco de dados ou cache compartilhado antes de responder — omitido aqui para manter o exemplo em uma requisição, uma viagem de ida e volta.
Aperte Reproduzir para rodar de ponta a ponta, Pausar para congelar ou Passo para avançar um tempo. O servidor escolhido se acende; Reiniciar sorteia de novo qual servidor é escolhido.
Pronto. O balanceador de carga vai escolher um servidor quando você apertar Reproduzir ou Passo.
O movimento é uma pequena máquina de estados finitos em JS puro — quatro fases: arrive, route, process, respond. Cada Passo avança uma fase e interpola (tween) o cx/cy do token com requestAnimationFrame e uma curva ease-in-out. Reproduzir apenas dispara o próximo passo automaticamente num timer; Pausar limpa o timer. Não há <video>, nem GIF, nem biblioteca — então ele avança, retrocede e reinicia de forma determinística.
Um SVG estático (o padrão de arquitetura em svg-patterns.md) responde "o que está conectado". A animação responde "em qual ordem e qual caminho desta vez". Recorra a este tipo de demo apenas quando a sequência ou o timing for a lição; caso contrário, uma imagem estática rotulada custa menos e é lida mais rápido.
A sequência inteira é narrada na legenda aria-live="polite", então uma pessoa usando leitor de tela ouve cada tempo sem ver o movimento. Os controles são <button>s reais, e prefers-reduced-motion reduz as interpolações a saltos instantâneos.
O motor é pequeno: uma lista de fases, uma função que move um token de A para B e três botões chamando step(), play(), reset(). Aqui está o coração disso (a versão completa é o <script> no fim deste arquivo).
// quatro fases ordenadas — todo o "processo ao longo do tempo" const PHASES = ['arrive', 'route', 'process', 'respond']; let phase = -1, chosen = pickServer(); // qual servidor de app nesta execução function step() { if (phase >= PHASES.length - 1) return done(); phase++; render(phase, chosen); // interpola o token + acende os nós } function play() { // dispara o próximo passo automaticamente num timer timer = setInterval(() => phase < PHASES.length - 1 ? step() : pause(), 1100); }