Files
ai-for-dummies/app.js
T
2026-09-02 01:49:35 +00:00

149 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const phases = {
plan: { model: { en: 'OPUS / REASONING', pt: 'OPUS / RACIOCÍNIO' }, title: { en: 'Turn ambiguity into work', pt: 'Transforme ambiguidade em trabalho' }, copy: { en: 'Inspect the repository, choose the architecture, split the request, and write acceptance criteria.', pt: 'Inspecione o repositório, escolha a arquitetura, divida o pedido e escreva critérios de aceitação.' }, code: { en: 'plan → decompose → define acceptance', pt: 'planejar → decompor → definir aceitação' } },
build: { model: { en: 'SONNET, HAIKU, OR EQUIVALENT', pt: 'SONNET, HAIKU OU EQUIVALENTE' }, title: { en: 'Execute one bounded slice', pt: 'Execute uma fatia delimitada' }, copy: { en: 'Give each worker enough context, one responsibility, and its own worktree. Less context; less collision.', pt: 'Dê a cada worker contexto suficiente, uma responsabilidade e seu próprio worktree. Menos contexto; menos colisões.' }, code: { en: 'brief + worktree → implement → test', pt: 'brief + worktree → implementar → testar' } },
review: { model: { en: 'STRONG MODEL OR HUMAN', pt: 'MODELO FORTE OU HUMANO' }, title: { en: 'Reconnect result to intent', pt: 'Reconecte o resultado à intenção' }, copy: { en: 'Check the diff against the original brief, run the checks, then merge, request changes, or discard.', pt: 'Compare o diff com o brief original, execute as verificações e então faça merge, peça mudanças ou descarte.' }, code: { en: 'diff + checks → review → merge / iterate', pt: 'diff + verificações → revisar → merge / iterar' } }
};
const interactiveCopy = {
workers: {
ui: { en: ['Interface worker', 'Receives: component contract + visual states', 'Returns: focused diff + viewport evidence'], pt: ['Worker de interface', 'Recebe: contrato do componente + estados visuais', 'Devolve: diff focado + evidência dos viewports'] },
tests: { en: ['Verification worker', 'Receives: acceptance criteria + changed surface', 'Returns: failing case, passing checks, risk notes'], pt: ['Worker de verificação', 'Recebe: critérios de aceitação + superfície alterada', 'Devolve: caso de falha, verificações passando e riscos'] },
docs: { en: ['Documentation worker', 'Receives: reviewed behavior + audience', 'Returns: guide, examples, and migration notes'], pt: ['Worker de documentação', 'Recebe: comportamento revisado + público', 'Devolve: guia, exemplos e notas de migração'] }
},
trees: {
main: { status: 'clean', owner: { en: 'Orchestrator', pt: 'Orquestrador' }, path: './project', command: 'git worktree list', note: { en: 'Shared history and integration point. Workers never edit here.', pt: 'Histórico compartilhado e ponto de integração. Workers nunca editam aqui.' } },
ui: { status: 'working', owner: { en: 'UI worker', pt: 'Worker de UI' }, path: '../task-ui', command: 'git worktree add ../task-ui -b agent/ui', note: { en: 'Own checkout and index. Safe to change presentation files in parallel.', pt: 'Checkout e índice próprios. Seguro para alterar a apresentação em paralelo.' } },
tests: { status: 'ready', owner: { en: 'Test worker', pt: 'Worker de testes' }, path: '../task-tests', command: 'git diff main...agent/tests', note: { en: 'Checks are green. Review the diff before merging into main.', pt: 'Verificações passaram. Revise o diff antes do merge em main.' } },
docs: { status: 'review', owner: { en: 'Docs worker', pt: 'Worker de docs' }, path: '../task-docs', command: 'git merge --no-ff agent/docs', note: { en: 'Review requested. Merge, request changes, or discard without touching another checkout.', pt: 'Revisão solicitada. Faça merge, peça mudanças ou descarte sem tocar em outro checkout.' } }
},
routes: {
plan: { score: 92, label: { en: 'High ambiguity', pt: 'Alta ambiguidade' }, why: { en: 'Architecture and decomposition have a wide error surface. Spend reasoning here.', pt: 'Arquitetura e decomposição têm grande superfície de erro. Invista raciocínio aqui.' } },
build: { score: 38, label: { en: 'Bounded execution', pt: 'Execução delimitada' }, why: { en: 'The brief already removed ambiguity. Optimize for speed and tight feedback.', pt: 'O brief já removeu a ambiguidade. Otimize para velocidade e feedback curto.' } },
explore: { score: 22, label: { en: 'Read-only discovery', pt: 'Descoberta somente leitura' }, why: { en: 'Search, map, and report. A lightweight model can return facts without editing.', pt: 'Busque, mapeie e reporte. Um modelo leve devolve fatos sem editar.' } },
review: { score: 74, label: { en: 'Independent judgment', pt: 'Julgamento independente' }, why: { en: 'Reconnect the diff to intent with fresh context and adversarial attention.', pt: 'Reconecte o diff à intenção com contexto novo e atenção crítica.' } }
},
skillFiles: {
skill: { icon: '◇', title: 'SKILL.md', en: 'Trigger, procedure, constraints, and the exact evidence the agent must return.', pt: 'Gatilho, procedimento, restrições e a evidência exata que o agente deve devolver.' },
references: { icon: '≡', title: 'references/', en: 'Stable facts loaded only when the procedure needs them. Keep the main instruction lean.', pt: 'Fatos estáveis carregados apenas quando o procedimento precisa. Mantenha a instrução principal enxuta.' },
scripts: { icon: '_', title: 'scripts/', en: 'Deterministic checks and repeated operations. Prefer executable proof over prose.', pt: 'Verificações determinísticas e operações repetidas. Prefira prova executável a prosa.' },
assets: { icon: '▧', title: 'assets/', en: 'Templates and examples the agent can copy without reinventing the expected shape.', pt: 'Templates e exemplos que o agente pode copiar sem reinventar o formato esperado.' }
},
commonSkills: {
ponytail: { number: '01', kind: { en: 'SIMPLIFICATION INSTINCT', pt: 'INSTINTO DE SIMPLIFICAÇÃO' }, title: 'ponytail-lite', rule: { en: 'Stop at the first rung that holds.', pt: 'Pare no primeiro degrau que sustenta.' }, use: { en: 'Use when a request invites frameworks, dependencies, abstractions, or speculative scaffolding. It checks reuse, standard library, and native platform features before adding code.', pt: 'Use quando um pedido convida frameworks, dependências, abstrações ou scaffolding especulativo. Verifica reúso, biblioteca padrão e recursos nativos antes de adicionar código.' }, example: { en: 'Date picker? Start with <input type="date">.', pt: 'Seletor de data? Comece com <input type="date">.' }, caution: { en: 'Never simplify away security, accessibility, validation, or real edge cases.', pt: 'Nunca simplifique segurança, acessibilidade, validação ou casos extremos reais.' } },
caveman: { number: '02', kind: { en: 'COMMUNICATION STYLE', pt: 'ESTILO DE COMUNICAÇÃO' }, title: 'caveman', rule: { en: 'Signal first. Drop filler.', pt: 'Sinal primeiro. Corte o excesso.' }, use: { en: 'Use for routine status, handoffs, and technical summaries where speed matters. Short fragments make actions and evidence easy to scan.', pt: 'Use em status, handoffs e resumos técnicos rotineiros onde velocidade importa. Fragmentos curtos facilitam localizar ações e evidências.' }, example: { en: 'Built. Tests pass. Published.', pt: 'Feito. Testes passaram. Publicado.' }, caution: { en: 'Drop the style for security warnings, irreversible actions, and sequences where terse wording can be misread.', pt: 'Abandone o estilo em alertas de segurança, ações irreversíveis e sequências onde concisão pode causar erro.' } },
unlazy: { number: '03', kind: { en: 'COMPLETION DISCIPLINE', pt: 'DISCIPLINA DE CONCLUSÃO' }, title: 'unlazy', rule: { en: 'Define observable gates. Finish against evidence.', pt: 'Defina gates observáveis. Termine com evidências.' }, use: { en: 'Use for substantial autonomous builds, audits, and parallel work where quiet omissions are expensive. It turns “done” into runnable acceptance checks.', pt: 'Use em builds autônomos grandes, auditorias e trabalho paralelo onde omissões custam caro. Transforma “pronto” em verificações executáveis.' }, example: { en: 'Gate: language toggle persists. Check: browser reload. Expect: pt-BR.', pt: 'Gate: idioma persiste. Check: recarregar navegador. Esperado: pt-BR.' }, caution: { en: 'Too heavy for trivial edits or factual answers.', pt: 'Pesado demais para edições triviais ou respostas factuais.' } },
research: { number: '04', kind: { en: 'SOURCE DISCIPLINE', pt: 'DISCIPLINA DE FONTES' }, title: 'research', rule: { en: 'Trace claims to owners.', pt: 'Leve afirmações até suas fontes.' }, use: { en: 'Use when APIs, standards, architecture facts, or current behavior must be verified. Capture findings in a cited note, prioritizing primary sources.', pt: 'Use quando APIs, padrões, fatos de arquitetura ou comportamento atual precisam ser verificados. Registre achados citados, priorizando fontes primárias.' }, example: { en: 'Git behavior → git-scm.com docs, not a remembered blog summary.', pt: 'Comportamento do Git → documentação git-scm.com, não memória de um blog.' }, caution: { en: 'Practitioner articles add context; they do not override official behavior.', pt: 'Artigos de praticantes dão contexto; não substituem comportamento oficial.' } },
debug: { number: '05', kind: { en: 'DIAGNOSTIC LOOP', pt: 'CICLO DE DIAGNÓSTICO' }, title: 'diagnosing-bugs', rule: { en: 'No red-capable loop, no theory.', pt: 'Sem ciclo capaz de falhar, sem teoria.' }, use: { en: 'Use for hard bugs, flakes, and regressions. First build a fast deterministic reproduction, then minimize, rank hypotheses, instrument, and fix the root cause.', pt: 'Use para bugs difíceis, flakes e regressões. Primeiro crie reprodução rápida e determinística; depois minimize, ranqueie hipóteses, instrumente e corrija a causa raiz.' }, example: { en: 'One command reproduces the exact symptom before any fix.', pt: 'Um comando reproduz o sintoma exato antes de qualquer correção.' }, caution: { en: 'Do not jump from error message straight to a patch.', pt: 'Não pule da mensagem de erro direto para um patch.' } },
review: { number: '06', kind: { en: 'INDEPENDENT REVIEW', pt: 'REVISÃO INDEPENDENTE' }, title: 'code-review', rule: { en: 'Check standards and intent separately.', pt: 'Verifique padrões e intenção separadamente.' }, use: { en: 'Use on a branch or PR. One axis checks repository standards; another checks whether the change actually satisfies its originating specification.', pt: 'Use em branch ou PR. Um eixo verifica padrões do repositório; outro verifica se a mudança realmente satisfaz a especificação original.' }, example: { en: 'Clean code can still solve the wrong problem.', pt: 'Código limpo ainda pode resolver o problema errado.' }, caution: { en: 'Pin the comparison point and source specification before reviewing.', pt: 'Fixe o ponto de comparação e a especificação antes de revisar.' } },
tokens: { number: '07', kind: { en: 'CONTEXT ECONOMY', pt: 'ECONOMIA DE CONTEXTO' }, title: 'token-saver', rule: { en: 'Keep signal. Strip command noise.', pt: 'Mantenha sinal. Corte ruído de comandos.' }, use: { en: 'Use around verbose tests, builds, Git output, and logs. Filtering preserves context for reasoning while retaining full failure output for recovery.', pt: 'Use em testes, builds, saídas Git e logs verbosos. Filtragem preserva contexto para raciocínio e mantém falhas completas para recuperação.' }, example: { en: '200 passing-test lines → one result; failures keep their trace.', pt: '200 linhas de testes passando → um resultado; falhas mantêm o trace.' }, caution: { en: 'Read raw output when exact wording or full diffs matter.', pt: 'Leia saída bruta quando texto exato ou diffs completos importarem.' } }
}
};
const translations = {
pt: {
'.chapter-links a:nth-child(1)': '01 frota', '.chapter-links a:nth-child(2)': '02 worktrees', '.chapter-links a:nth-child(3)': '03 skills', '.chapter-links a:nth-child(4)': '04 kit de campo', '.edition': 'ENGENHARIA DE IA <i></i> 01 / 2026',
'.hero .eyebrow': 'Uma apresentação para quem entrega software', '.lede': 'Você não precisa de um exército de modelos. Precisa de um sistema: uma mente para enquadrar o trabalho, várias mãos para executá-lo e uma fronteira clara entre cada tarefa.', '.hero-index span': 'NOTA DE CAMPO / 001', '.hero-index strong': 'Entregue o<br /><em>sistema.</em>', '.hero-index small': 'Skills · agentes · worktrees · evidências',
'.hero-stats div:nth-child(1) span': 'modelo forte<br />para ambiguidade', '.hero-stats div:nth-child(2) span': 'workers delimitados<br />em paralelo', '.hero-stats div:nth-child(3) span': 'iterações<br />com evidências', '.hero-stats p': 'Leia isto como um mapa de rota, não como uma receita de prompt.',
'.thesis span': 'REGRA ZERO', '.thesis strong': 'Modelo forte para ambiguidade.<br />Modelo leve para trabalho delimitado.', '.fleet .section-label span:nth-child(1)': 'Uma pequena frota', '.fleet .section-label span:nth-child(2)': 'coordenação antes do paralelismo', '.captain span': 'ORQUESTRADOR', '.captain h2': 'Decide o que<br />precisa acontecer.', '.worker-card[data-worker="ui"] strong': 'Componentes e estados visuais', '.worker-card[data-worker="tests"] strong': 'Casos de aceitação', '.worker-card[data-worker="docs"] strong': 'Guia e exemplos', '.caption': 'O orquestrador preserva a intenção, escreve pequenos contratos e reúne resultados verificáveis. Ele não precisa digitar cada linha.',
'.failure-map .section-label span:nth-child(1)': 'Por que a fronteira importa', '.failure-map .section-label span:nth-child(2)': 'uma tarefa vaga / três falhas previsíveis', '.failure-grid article:nth-child(1) strong': 'Sopa de contexto', '.failure-grid article:nth-child(1) p': 'Cada worker lê tudo. Ninguém sabe quais fatos são essenciais.', '.failure-grid article:nth-child(2) strong': 'Colisão de branches', '.failure-grid article:nth-child(2) p': 'Dois agentes usam o mesmo checkout. O caminho mais rápido vira resolução de conflitos.', '.failure-grid article:nth-child(3) strong': 'Desvio confiante', '.failure-grid article:nth-child(3) p': 'O diff parece ótimo, mas ninguém verifica se resolveu o problema original.',
'.workflow .eyebrow': 'O ciclo de subagentes', '#workflow-title': 'Clique em uma fase.<br /><em>Veja a passagem.</em>', '.workflow .copy > p:last-child': 'Delegar é mover uma tarefa delimitada para um contexto menor — não abrir mão da responsabilidade.', '.handoff .section-label span:nth-child(1)': 'O que atravessa contextos', '.handoff .section-label span:nth-child(2)': 'brief → diff → evidência', '.handoff thead th:nth-child(1)': 'Pacote', '.handoff thead th:nth-child(2)': 'Contém', '.handoff thead th:nth-child(3)': 'Por que importa',
'.worktrees .eyebrow': 'Git worktrees', '.worktrees h2': 'Uma branch<br />por <em>mão.</em>', '.worktree-intro > p:nth-of-type(2)': 'Um worktree é outro diretório ligado ao mesmo repositório. Cada agente recebe seu próprio checkout e índice; o histórico continua compartilhado.', '.worktree-intro .interaction-hint': 'Selecione um nó para inspecionar checkout, responsável e próxima ação.', '.tree-toolbar > span:first-child': 'topologia do repositório', '.tree-live': '<i></i> 4 checkouts', '.tree-node.root span': 'RAIZ', '.tree-node.ui span': 'AGENTE DE UI', '.tree-node.tests span': 'AGENTE DE TESTES', '.tree-node.docs span': 'AGENTE DE DOCS', '.tree-node.root small': '● limpo', '.tree-node.ui small': '3 arquivos · trabalhando', '.tree-node.tests small': '8 verificações · pronto', '.tree-node.docs small': '2 páginas · revisão', '.routing .eyebrow': 'Roteamento de modelos', '.routing h2': 'Não pague por<br />raciocínio onde precisa<br />de <em>ritmo.</em>', '.routing .interaction-hint': 'Escolha um trabalho para entender por que o perfil do modelo muda.', '.route-table .head span:nth-child(1)': 'Trabalho', '.route-table .head span:nth-child(2)': 'Perfil', '.route-table .head span:nth-child(3)': 'Formato do prompt', '.route-table [data-route="plan"] strong': 'Planejar', '.route-table [data-route="build"] strong': 'Construir', '.route-table [data-route="explore"] strong': 'Explorar', '.route-table [data-route="review"] strong': 'Revisar', '.skills .eyebrow': 'Skills', '.skills h2': 'Escreva do jeito certo<br /><em>uma vez.</em>', '.skills > div:first-child > p': 'Uma skill é um procedimento reutilizável. Ela pode carregar instruções, referências, scripts e assets. Não é memória mágica e não substitui critérios de aceitação.',
'.skill-principles span:nth-child(1)': '01 / defina o gatilho', '.skill-principles span:nth-child(2)': '02 / carregue detalhes sob demanda', '.skill-principles span:nth-child(3)': '03 / devolva evidências', '.skill-package > span': 'PACOTE DE SKILL', '.skill-catalog .section-label span:nth-child(1)': 'Skills comuns', '.skill-catalog .section-label span:nth-child(2)': 'escolha o comportamento antes do modelo', '.catalog-intro .eyebrow': 'O kit de campo', '.catalog-intro h2': 'Trabalhos diferentes.<br />Instintos <em>diferentes.</em>', '.catalog-intro > p': 'Uma skill muda como o agente aborda o trabalho. Algumas moldam a comunicação. Outras impõem pesquisa, diagnóstico, revisão ou disciplina de conclusão. Selecione uma para inspecionar sua regra operacional.', '[data-common-skill="ponytail"] span': 'SIMPLIFICAR', '[data-common-skill="ponytail"] small': 'código mínimo que funciona', '[data-common-skill="caveman"] span': 'COMUNICAR', '[data-common-skill="caveman"] small': 'sinal sem excesso', '[data-common-skill="unlazy"] span': 'CONCLUIR', '[data-common-skill="unlazy"] small': 'gates e evidências', '[data-common-skill="research"] span': 'INVESTIGAR', '[data-common-skill="research"] small': 'fontes primárias primeiro', '[data-common-skill="debug"] span': 'DIAGNOSTICAR', '[data-common-skill="debug"] small': 'ciclo curto de feedback', '[data-common-skill="review"] span': 'REVISAR', '[data-common-skill="review"] small': 'padrões × especificação', '[data-common-skill="tokens"] span': 'ECONOMIZAR', '[data-common-skill="tokens"] small': 'comprima saídas ruidosas', '.skill-loadout > span': 'UM LOADOUT PRÁTICO', '.skill-loadout > div': '<b>PLANEJAR</b> unlazy <i>→</i> <b>CONSTRUIR</b> ponytail-lite <i>→</i> <b>DIAGNOSTICAR</b> diagnosing-bugs <i>→</i> <b>REPORTAR</b> caveman', '.rule span': 'O PAPEL HUMANO', '.rule strong': 'O agente pode ser autônomo na execução. Intenção, limites e evidências continuam sendo seus.', '.callout span': 'COMECE AQUI', '.callout strong': 'Comece com um agente e uma skill. Adicione paralelismo apenas quando as tarefas forem realmente independentes.', '.sources .section-label span:nth-child(1)': 'Continue aprendendo', '.sources .section-label span:nth-child(2)': '12 novas leituras + documentação primária', '.sources p': 'Aprofunde com documentação oficial, casos de produção, Medium e fluxos de praticantes. <a href="docs/references/README.md">Referências primárias →</a> <a href="docs/references/additional-reading.md">Trilha com 12 leituras →</a>'
}
};
const panel = document.querySelector('#phase-panel');
const buttons = document.querySelectorAll('[data-phase]');
const originals = new Map();
let currentLanguage = 'en';
function setText(selector, value) {
const nodes = document.querySelectorAll(selector);
if (!nodes.length) return;
if (!originals.has(selector)) originals.set(selector, [...nodes].map((node) => node.innerHTML));
nodes.forEach((node) => { node.innerHTML = value; });
}
function render(id) {
const phase = phases[id];
panel.innerHTML = `<div class="phase-meta"><span>${phase.model[currentLanguage]}</span><small>${currentLanguage === 'pt' ? 'contexto: isolado' : 'context: isolated'}</small></div><h3>${phase.title[currentLanguage]}</h3><p>${phase.copy[currentLanguage]}</p><code>${phase.code[currentLanguage]}</code>`;
buttons.forEach((button) => { const active = button.dataset.phase === id; button.classList.toggle('active', active); button.setAttribute('aria-selected', String(active)); });
}
function selectButtons(selector, activeValue, key) {
document.querySelectorAll(selector).forEach((button) => {
const active = button.dataset[key] === activeValue;
button.classList.toggle('active', active);
button.setAttribute(button.hasAttribute('aria-selected') ? 'aria-selected' : 'aria-pressed', String(active));
});
}
function renderWorker(id) {
const item = interactiveCopy.workers[id][currentLanguage];
document.querySelector('#worker-detail').innerHTML = `<span>${item[0]}</span><strong>${item[1]}</strong><small>${item[2]}</small>`;
selectButtons('[data-worker]', id, 'worker');
}
function renderTree(id) {
const item = interactiveCopy.trees[id];
const language = currentLanguage;
document.querySelector('#tree-detail').innerHTML = `<div><span>${language === 'pt' ? 'RESPONSÁVEL' : 'OWNER'}</span><strong>${item.owner[language]}</strong></div><div><span>CHECKOUT</span><strong>${item.path}</strong></div><p>${item.note[language]}</p><code>${item.command}</code>`;
selectButtons('[data-tree]', id, 'tree');
}
function renderRoute(id) {
const item = interactiveCopy.routes[id];
const language = currentLanguage;
document.querySelector('#route-detail').innerHTML = `<div class="route-meter"><span style="--score:${item.score}%"></span></div><div><small>${language === 'pt' ? 'CARGA DE RACIOCÍNIO' : 'REASONING LOAD'} · ${item.score}</small><strong>${item.label[language]}</strong><p>${item.why[language]}</p></div>`;
selectButtons('[data-route]', id, 'route');
}
function renderSkillFile(id) {
const item = interactiveCopy.skillFiles[id];
document.querySelector('#skill-detail').innerHTML = `<span>${item.icon}</span><div><strong>${item.title}</strong><p>${item[currentLanguage]}</p><small>${currentLanguage === 'pt' ? 'clique em outro arquivo para explorar' : 'select another file to explore'}</small></div>`;
selectButtons('[data-skill-file]', id, 'skillFile');
}
function renderCommonSkill(id) {
const item = interactiveCopy.commonSkills[id];
const language = currentLanguage;
const labels = language === 'pt'
? ['QUANDO USAR', 'EXEMPLO', 'CUIDADO']
: ['WHEN TO USE', 'EXAMPLE', 'WATCH OUT'];
document.querySelector('#common-skill-detail').innerHTML = `<header><span>${item.number}</span><small>${item.kind[language]}</small></header><h3>${item.title}</h3><blockquote>${item.rule[language]}</blockquote><div class="common-skill-notes"><div><span>${labels[0]}</span><p>${item.use[language]}</p></div><div><span>${labels[1]}</span><p>${item.example[language]}</p></div><div><span>${labels[2]}</span><p>${item.caution[language]}</p></div></div>`;
selectButtons('[data-common-skill]', id, 'commonSkill');
}
function renderInteractive() {
renderWorker(document.querySelector('[data-worker].active')?.dataset.worker || 'ui');
renderTree(document.querySelector('[data-tree].active')?.dataset.tree || 'main');
renderRoute(document.querySelector('[data-route].active')?.dataset.route || 'plan');
renderSkillFile(document.querySelector('[data-skill-file].active')?.dataset.skillFile || 'skill');
renderCommonSkill(document.querySelector('[data-common-skill].active')?.dataset.commonSkill || 'ponytail');
}
function applyLanguage(language) {
currentLanguage = language === 'pt' ? 'pt' : 'en';
document.documentElement.lang = currentLanguage === 'pt' ? 'pt-BR' : 'en';
if (currentLanguage === 'pt') Object.entries(translations.pt).forEach(([selector, value]) => setText(selector, value));
else originals.forEach((values, selector) => document.querySelectorAll(selector).forEach((node, index) => { node.innerHTML = values[index]; }));
document.querySelectorAll('[data-lang]').forEach((button) => { const active = button.dataset.lang === currentLanguage; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); });
render(document.querySelector('[data-phase].active')?.dataset.phase || 'plan');
renderInteractive();
try { localStorage.setItem('ai-for-dummies-language', currentLanguage); } catch (error) { /* previews may disable storage */ }
}
buttons.forEach((button) => button.addEventListener('click', () => render(button.dataset.phase)));
document.querySelectorAll('[data-lang]').forEach((button) => button.addEventListener('click', () => applyLanguage(button.dataset.lang)));
document.querySelectorAll('[data-worker]').forEach((button) => button.addEventListener('click', () => renderWorker(button.dataset.worker)));
document.querySelectorAll('[data-tree]').forEach((button) => button.addEventListener('click', () => renderTree(button.dataset.tree)));
document.querySelectorAll('[data-route]').forEach((button) => button.addEventListener('click', () => renderRoute(button.dataset.route)));
document.querySelectorAll('[data-skill-file]').forEach((button) => button.addEventListener('click', () => renderSkillFile(button.dataset.skillFile)));
document.querySelectorAll('[data-common-skill]').forEach((button) => button.addEventListener('click', () => renderCommonSkill(button.dataset.commonSkill)));
window.addEventListener('scroll', () => { const height = document.documentElement.scrollHeight - window.innerHeight; document.querySelector('.reading-progress span').style.width = `${height > 0 ? (window.scrollY / height) * 100 : 0}%`; }, { passive: true });
let savedLanguage = 'en';
try { savedLanguage = localStorage.getItem('ai-for-dummies-language') || 'en'; } catch (error) { /* previews may disable storage */ }
render('plan');
applyLanguage(savedLanguage);