Initial commit

This commit is contained in:
2026-04-25 12:37:38 +03:00
committed by GitHub
commit df7302b8b0
85 changed files with 10204 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="es">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LSC44NDPLQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LSC44NDPLQ');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VS Code GitHub Copilot Agent Lab | Edición TypeScript</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="alternate" hreflang="en" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="alternate" hreflang="pt-BR" href="https://copilot-dev-days.github.io/agent-lab-typescript/pt_BR/">
<link rel="alternate" hreflang="es" href="https://copilot-dev-days.github.io/agent-lab-typescript/es/">
<link rel="alternate" hreflang="x-default" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="stylesheet" href="../styles.css">
<link rel="stylesheet" href="../light-theme.css">
<script src="../theme-toggle.js"></script>
</head>
<body>
<div style="position:fixed;top:1rem;right:1rem;z-index:1000;display:flex;gap:0.5rem;align-items:center;">
<div class="locale-picker" id="localePicker">
<button type="button" class="locale-toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="Seleccionar idioma">🌐</button>
<ul role="listbox" aria-label="Idioma">
<li role="option" data-locale="en" aria-selected="false">🇬🇧 English</li>
<li role="option" data-locale="es" aria-selected="true" class="active">🇪🇸 Español</li>
<li role="option" data-locale="pt-BR" aria-selected="false">🇧🇷 Português (Brasil)</li>
</ul>
</div>
<button class="theme-toggle" onclick="toggleTheme()">☀️ Claro</button>
</div>
<div class="container">
<section class="hero">
<span class="hero-badge">🚀 TypeScript + Vite + React + Tailwind</span>
<h1>VS Code GitHub Copilot Agent Lab</h1>
<p class="hero-subtitle">
Construye un juego de Bingo Social mientras dominas el Agent Mode de VS Code con GitHub Copilot.
Aprende ingeniería de contexto, agentes personalizados y flujos de desarrollo con IA.
</p>
<div class="hero-cta">
<a href="step.html?step=00-overview" class="btn btn-primary">🚀 Iniciar Taller</a>
<a href="../game/" class="btn btn-secondary">🎮 Jugar</a>
<a href="https://github.com/copilot-dev-days/agent-lab-typescript" class="btn btn-secondary">📦 GitHub Repo</a>
</div>
<p class="hero-also">También disponible en <a href="https://copilot-dev-days.github.io/agent-lab-dotnet/">.NET</a>, <a href="https://copilot-dev-days.github.io/agent-lab-java/">Java</a> y <a href="https://copilot-dev-days.github.io/agent-lab-python/">Python</a></p>
<div class="stats">
<div class="stat">
<div class="stat-value">~1hr</div>
<div class="stat-label">Duración</div>
</div>
<div class="stat">
<div class="stat-value">4</div>
<div class="stat-label">Partes</div>
</div>
<div class="stat">
<div class="stat-value">9</div>
<div class="stat-label">Etapas</div>
</div>
</div>
</section>
<section class="features">
<h2 class="section-title">Lo que Aprenderás</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">🧠</div>
<h3>Ingeniería de Contexto</h3>
<p>Enseña a la IA sobre tu código con instrucciones, haciendo las sugerencias de Copilot más precisas y relevantes.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤖</div>
<h3>Primitivas Agénticas</h3>
<p>Usa agentes en segundo plano, agentes en la nube y flujos personalizados para escalar tu desarrollo.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h3>Desarrollo Design-First</h3>
<p>Deja que la IA itere en la UI mientras tú guías la visión creativa con agentes de diseño especializados.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🧪</div>
<h3>Desarrollo Guiado por Pruebas</h3>
<p>Construye funcionalidades confiables usando agentes TDD con el flujo Red-Green-Refactor.</p>
</div>
</div>
</section>
<section class="workshop">
<h2 class="section-title">Partes del Taller</h2>
<div class="parts-list">
<a href="step.html?step=01-setup" class="part-card">
<div class="part-number">01</div>
<div class="part-content">
<h3>Setup & Ingeniería de Contexto</h3>
<p>Clona, configura y enseña a la IA sobre tu proyecto</p>
</div>
<span class="part-time">15 min</span>
</a>
<a href="step.html?step=02-design" class="part-card">
<div class="part-number">02</div>
<div class="part-content">
<h3>Frontend Design-First</h3>
<p>Rediseña la UI con temas creativos usando Plan Mode</p>
</div>
<span class="part-time">15 min</span>
</a>
<a href="step.html?step=03-quiz-master" class="part-card">
<div class="part-number">03</div>
<div class="part-content">
<h3>Quiz Master Personalizado</h3>
<p>Crea preguntas temáticas con agentes personalizados</p>
</div>
<span class="part-time">10 min</span>
</a>
<a href="step.html?step=04-multi-agent" class="part-card">
<div class="part-number">04</div>
<div class="part-content">
<h3>Desarrollo Multi-Agent</h3>
<p>Construye nuevas funcionalidades con agentes de TDD y diseño</p>
</div>
<span class="part-time">20 min</span>
</a>
</div>
</section>
</div>
<section class="prereqs">
<div class="container">
<h2 class="section-title">Requisitos Previos</h2>
<div class="prereqs-grid">
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>VS Code v1.107+</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>GitHub Copilot</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>Node.js 22+</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>Git instalado</span>
</div>
</div>
</div>
</section>
<div class="container">
<footer>
<div class="footer-links">
<a href="https://github.com/copilot-dev-days/agent-lab-typescript">GitHub Repository</a>
<a href="https://www.youtube.com/code">VS Code YouTube</a>
<a href="https://code.visualstudio.com/docs/copilot/overview">Copilot Docs</a>
<a href="https://github.com/github/awesome-copilot">Awesome Copilot</a>
</div>
<p class="footer-credit">Hecho con 💜 por el equipo de VS Code</p>
</footer>
</div>
<script>
(function(){
var match=location.pathname.match(/\/(pt_BR|es)\//);
var locale=match?match[1]:'en';
var picker=document.getElementById('localePicker');
if(!picker)return;
var btn=picker.querySelector('.locale-toggle'),list=picker.querySelector('ul');
list.querySelectorAll('li').forEach(function(li){
var l=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(l===locale){li.classList.add('active');li.setAttribute('aria-selected','true');}
else{li.classList.remove('active');li.setAttribute('aria-selected','false');}
});
function open(){list.style.display='block';btn.setAttribute('aria-expanded','true');}
function close(){list.style.display='none';btn.setAttribute('aria-expanded','false');}
btn.addEventListener('click',function(e){e.stopPropagation();list.style.display==='block'?close():open();});
document.addEventListener('click',close);
document.addEventListener('keydown',function(e){if(e.key==='Escape')close();});
list.querySelectorAll('li').forEach(function(li){
li.addEventListener('click',function(e){
e.stopPropagation();
var t=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(t===locale){close();return;}
var p=location.pathname;
if(locale!=='en')p=p.replace(new RegExp('/'+locale+'/'),'/');;
if(t!=='en')p=p.replace(/\/docs\//,'/docs/'+t+'/');
localStorage.setItem('preferred-locale',t);
window.location.href=p+location.search;
});
});
})();
</script>
</body>
</html>
+373
View File
@@ -0,0 +1,373 @@
<!DOCTYPE html>
<html lang="es">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LSC44NDPLQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LSC44NDPLQ');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VS Code GitHub Copilot Agent Lab</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="alternate" hreflang="en" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="alternate" hreflang="pt-BR" href="https://copilot-dev-days.github.io/agent-lab-typescript/pt_BR/">
<link rel="alternate" hreflang="es" href="https://copilot-dev-days.github.io/agent-lab-typescript/es/">
<link rel="alternate" hreflang="x-default" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="stylesheet" href="../step.css">
<link rel="stylesheet" href="../light-theme.css">
<script src="../theme-toggle.js"></script>
</head>
<body>
<header class="header">
<div class="header-content">
<a href="index.html" class="header-brand">
<span>🎮</span>
<span class="header-title">VS Code Copilot Agent Lab</span>
</a>
<nav class="header-nav">
<div class="locale-picker" id="localePicker">
<button type="button" class="locale-toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="Seleccionar idioma">🌐</button>
<ul role="listbox" aria-label="Idioma">
<li role="option" data-locale="en" aria-selected="false">🇬🇧 English</li>
<li role="option" data-locale="es" aria-selected="true" class="active">🇪🇸 Español</li>
<li role="option" data-locale="pt-BR" aria-selected="false">🇧🇷 Português (Brasil)</li>
</ul>
</div>
<button class="theme-toggle" onclick="toggleTheme()">☀️ Claro</button>
<button class="nav-btn" id="prevBtn" disabled>← Anterior</button>
<button class="nav-btn" id="nextBtn">Siguiente →</button>
</nav>
</div>
</header>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-section">
<div class="sidebar-title">Partes del Lab</div>
<nav id="stepNav"></nav>
</div>
</aside>
<main class="main">
<article class="content">
<div id="markdown-content" class="markdown">
<div class="loading">Cargando contenido</div>
</div>
<nav class="nav-footer">
<a href="#" class="nav-footer-btn disabled" id="prevLink">← Anterior</a>
<a href="#" class="nav-footer-btn" id="nextLink">Siguiente →</a>
</nav>
</article>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
<script>
const steps = [
{ id: 'guide', file: 'GUIDE.md', title: 'Referencia Rápida', number: '📚' },
{ id: '00-overview', file: '00-overview.md', title: 'Visión General & Checklist', number: '00' },
{ id: '01-setup', file: '01-setup.md', title: 'Setup & Ingeniería de Contexto', number: '01' },
{ id: '02-design', file: '02-design.md', title: 'Frontend Design-First', number: '02' },
{ id: '03-quiz-master', file: '03-quiz-master.md', title: 'Quiz Master Personalizado', number: '03' },
{ id: '04-multi-agent', file: '04-multi-agent.md', title: 'Desarrollo Multi-Agent', number: '04' },
{ id: '05-complete', file: '05-complete.md', title: '🎉 ¡Completo!', number: '🎉' },
];
function celebrate() {
const duration = 3000;
const end = Date.now() + duration;
const colors = ['#00f5ff', '#ff00ff', '#b366ff', '#00ff88', '#ffff00'];
(function frame() {
confetti({ particleCount: 5, angle: 60, spread: 55, origin: { x: 0 }, colors });
confetti({ particleCount: 5, angle: 120, spread: 55, origin: { x: 1 }, colors });
if (Date.now() < end) requestAnimationFrame(frame);
}());
}
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/copilot-dev-days/agent-lab-typescript/main/workshop/es/';
const CHECKBOX_STATE_KEY_PREFIX = 'workshop-checkboxes:es:';
const COPY_STATUS_RESET_DELAY = 2000;
const pendingCopyResetTimers = new Set();
function getCurrentStepId() {
const params = new URLSearchParams(window.location.search);
return params.get('step') || '00-overview';
}
function getCurrentStepIndex() {
return steps.findIndex(s => s.id === getCurrentStepId());
}
function buildSidebar() {
const nav = document.getElementById('stepNav');
const currentId = getCurrentStepId();
nav.innerHTML = steps.map(step => `
<a href="?step=${step.id}" class="step-link ${step.id === currentId ? 'active' : ''}">
<span class="step-number">${step.number}</span>
<span>${step.title}</span>
</a>
`).join('');
}
function updateNavigation() {
const idx = getCurrentStepIndex();
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const prevLink = document.getElementById('prevLink');
const nextLink = document.getElementById('nextLink');
prevBtn.disabled = idx <= 0;
nextBtn.disabled = idx >= steps.length - 1;
prevBtn.onclick = () => idx > 0 && (window.location.search = `?step=${steps[idx - 1].id}`);
nextBtn.onclick = () => idx < steps.length - 1 && (window.location.search = `?step=${steps[idx + 1].id}`);
if (idx > 0) {
prevLink.href = `?step=${steps[idx - 1].id}`;
prevLink.textContent = `← ${steps[idx - 1].title}`;
prevLink.classList.remove('disabled');
} else {
prevLink.classList.add('disabled');
prevLink.textContent = '← Anterior';
}
if (idx < steps.length - 1) {
nextLink.href = `?step=${steps[idx + 1].id}`;
nextLink.textContent = `${steps[idx + 1].title} →`;
nextLink.classList.remove('disabled');
} else {
nextLink.classList.add('disabled');
nextLink.textContent = 'Siguiente →';
}
}
function processMarkdown(md) {
md = md.replace(/\[📚 Lab Guide\][^\n]+\n+---/g, '');
md = md.replace(/\[📚 Lab Guide\][^\n]+/g, '');
md = md.replace(/\[🎮 Live Demo\][^\n]+\n+---/g, '');
md = md.replace(/\[🎮 Live Demo\][^\n]+/g, '');
md = md.replace(/\[← README\][^\n]+/g, '');
md = md.replace(/\[← Overview\][^\n]+/g, '');
md = md.replace(/\[← Part \d+\][^\n]+/g, '');
md = md.replace(/👉 \*\*\[Continue to[^\n]+/g, '');
md = md.replace(/👉 \*\*\[Start with[^\n]+/g, '');
md = md.replace(/\]\(\.\.\/game\/\)/g, '](../game/)');
md = md.replace(/\]\((\d+)-([^)]+)\.md\)/g, '](step.html?step=$1-$2)');
md = md.replace(/\]\(GUIDE\.md\)/g, '](step.html?step=guide)');
md = md.replace(/>\s*💡\s*\*\*Tip:\*\*/gi, '> **💡 Tip**');
md = md.replace(/>\s*💡\s*\*\*(Optional)[,:]\*\*/gi, '> **💡 $1**');
md = md.replace(/>\s*💡\s*\*\*(Think about)[,:]\*\*/gi, '> **💡 $1**');
md = md.replace(/---\s*\n\s*---/g, '---');
return md;
}
function getCheckboxStorageKey(stepId) {
return `${CHECKBOX_STATE_KEY_PREFIX}${stepId}`;
}
function setupInteractiveCheckboxes(stepId) {
const container = document.getElementById('markdown-content');
if (!container) return;
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
if (!checkboxes.length) return;
const storageKey = getCheckboxStorageKey(stepId);
let checkedIndexes = [];
try {
checkedIndexes = JSON.parse(localStorage.getItem(storageKey) || '[]');
if (!Array.isArray(checkedIndexes)) checkedIndexes = [];
} catch {
checkedIndexes = [];
}
checkboxes.forEach((checkbox, index) => {
checkbox.disabled = false;
checkbox.checked = checkedIndexes.includes(index) || checkbox.checked;
checkbox.setAttribute('aria-label', `Elemento de la lista ${index + 1}`);
checkbox.addEventListener('change', () => {
const updated = Array.from(checkboxes)
.reduce((acc, cb, i) => {
if (cb.checked) acc.push(i);
return acc;
}, []);
localStorage.setItem(storageKey, JSON.stringify(updated));
});
});
}
async function copyCodeToClipboard(text) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
const copied = document.execCommand('copy');
document.body.removeChild(textarea);
if (!copied) throw new Error('Fallback copy failed');
}
function setupCodeBlockCopyButtons() {
const container = document.getElementById('markdown-content');
if (!container) return;
const codeBlocks = container.querySelectorAll('pre');
codeBlocks.forEach((pre) => {
const code = pre.querySelector('code');
const codeText = code?.textContent?.trim();
if (!codeText) return;
const copyButton = document.createElement('button');
copyButton.type = 'button';
copyButton.className = 'code-copy-btn';
copyButton.textContent = 'Copiar';
copyButton.setAttribute('aria-label', 'Copiar código');
const liveRegion = document.createElement('span');
liveRegion.className = 'sr-only';
liveRegion.setAttribute('aria-live', 'polite');
let resetTimer = null;
const resetButtonState = () => {
copyButton.textContent = 'Copiar';
copyButton.dataset.state = 'idle';
liveRegion.textContent = '';
};
copyButton.addEventListener('click', async () => {
if (resetTimer) {
clearTimeout(resetTimer);
pendingCopyResetTimers.delete(resetTimer);
}
try {
await copyCodeToClipboard(codeText);
copyButton.textContent = '¡Copiado!';
copyButton.dataset.state = 'copied';
liveRegion.textContent = 'Código copiado al portapapeles';
} catch {
copyButton.textContent = 'Falló';
copyButton.dataset.state = 'error';
liveRegion.textContent = 'No se pudo copiar el código';
}
resetTimer = setTimeout(() => {
pendingCopyResetTimers.delete(resetTimer);
resetTimer = null;
resetButtonState();
}, COPY_STATUS_RESET_DELAY);
pendingCopyResetTimers.add(resetTimer);
});
pre.append(copyButton, liveRegion);
});
}
async function loadContent() {
const idx = getCurrentStepIndex();
if (idx === -1) {
document.getElementById('markdown-content').innerHTML = '<p>Paso no encontrado. <a href="?step=00-overview">Ir a Visión General</a></p>';
return;
}
const step = steps[idx];
document.title = `${step.title} | VS Code Copilot Agent Lab`;
try {
pendingCopyResetTimers.forEach(timer => clearTimeout(timer));
pendingCopyResetTimers.clear();
const response = await fetch(`${GITHUB_RAW_BASE}${step.file}`);
if (!response.ok) throw new Error('Failed to load');
let md = await response.text();
md = processMarkdown(md);
const stepDir = step.file.substring(0, step.file.lastIndexOf('/') + 1);
md = md.replace(/!\[([^\]]*)\]\((?!https?:\/\/|\/\/)([^)]+)\)/g, (match, alt, path) => {
const cleanPath = path.replace(/^\.\//, '');
return `![${alt}](${GITHUB_RAW_BASE}${stepDir}${cleanPath})`;
});
marked.setOptions({ breaks: true, gfm: true });
document.getElementById('markdown-content').innerHTML = marked.parse(md);
setupInteractiveCheckboxes(step.id);
setupCodeBlockCopyButtons();
if (step.id === '05-complete') {
setTimeout(celebrate, 500);
}
} catch (error) {
document.getElementById('markdown-content').innerHTML = `
<h1>Error al Cargar Contenido</h1>
<p>No se pudo cargar ${step.file}. Verifica tu conexión a internet.</p>
<p><a href="index.html">Volver a la página principal</a></p>
`;
}
}
buildSidebar();
updateNavigation();
loadContent();
document.addEventListener('keydown', (e) => {
const idx = getCurrentStepIndex();
if (e.key === 'ArrowLeft' && idx > 0) {
window.location.search = `?step=${steps[idx - 1].id}`;
} else if (e.key === 'ArrowRight' && idx < steps.length - 1) {
window.location.search = `?step=${steps[idx + 1].id}`;
}
});
</script>
<script>
(function(){
var match=location.pathname.match(/\/(pt_BR|es)\//);
var locale=match?match[1]:'en';
var picker=document.getElementById('localePicker');
if(!picker)return;
var btn=picker.querySelector('.locale-toggle'),list=picker.querySelector('ul');
list.querySelectorAll('li').forEach(function(li){
var l=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(l===locale){li.classList.add('active');li.setAttribute('aria-selected','true');}
else{li.classList.remove('active');li.setAttribute('aria-selected','false');}
});
function open(){list.style.display='block';btn.setAttribute('aria-expanded','true');}
function close(){list.style.display='none';btn.setAttribute('aria-expanded','false');}
btn.addEventListener('click',function(e){e.stopPropagation();list.style.display==='block'?close():open();});
document.addEventListener('click',close);
document.addEventListener('keydown',function(e){if(e.key==='Escape')close();});
list.querySelectorAll('li').forEach(function(li){
li.addEventListener('click',function(e){
e.stopPropagation();
var t=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(t===locale){close();return;}
var p=location.pathname;
if(locale!=='en')p=p.replace(new RegExp('/'+locale+'/'),'/');;
if(t!=='en')p=p.replace(/\/docs\//,'/docs/'+t+'/');
localStorage.setItem('preferred-locale',t);
window.location.href=p+location.search;
});
});
})();
</script>
</body>
</html>
+200
View File
@@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LSC44NDPLQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LSC44NDPLQ');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VS Code GitHub Copilot Agent Lab | TypeScript Edition</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="alternate" hreflang="en" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="alternate" hreflang="pt-BR" href="https://copilot-dev-days.github.io/agent-lab-typescript/pt_BR/">
<link rel="alternate" hreflang="es" href="https://copilot-dev-days.github.io/agent-lab-typescript/es/">
<link rel="alternate" hreflang="x-default" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="light-theme.css">
<script src="theme-toggle.js"></script>
</head>
<body>
<div style="position:fixed;top:1rem;right:1rem;z-index:1000;display:flex;gap:0.5rem;align-items:center;">
<div class="locale-picker" id="localePicker">
<button type="button" class="locale-toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="Select language">🌐</button>
<ul role="listbox" aria-label="Language">
<li role="option" data-locale="en" aria-selected="true" class="active">🇬🇧 English</li>
<li role="option" data-locale="es" aria-selected="false">🇪🇸 Español</li>
<li role="option" data-locale="pt-BR" aria-selected="false">🇧🇷 Português (Brasil)</li>
</ul>
</div>
<button class="theme-toggle" onclick="toggleTheme()">☀️ Light</button>
</div>
<div class="container">
<section class="hero">
<span class="hero-badge">🚀 TypeScript + Vite + React + Tailwind</span>
<h1>VS Code GitHub Copilot Agent Lab</h1>
<p class="hero-subtitle">
Build a Social Bingo game while mastering VS Code's Agent Mode with GitHub Copilot.
Learn context engineering, custom agents, and AI-first development workflows.
</p>
<div class="hero-cta">
<a href="step.html?step=00-overview" class="btn btn-primary">🚀 Start Workshop</a>
<a href="game/" class="btn btn-secondary">🎮 Play Game</a>
<a href="https://github.com/copilot-dev-days/agent-lab-typescript" class="btn btn-secondary">📦 GitHub Repo</a>
</div>
<p class="hero-also">Also available in <a href="https://copilot-dev-days.github.io/agent-lab-dotnet/">.NET</a>, <a href="https://copilot-dev-days.github.io/agent-lab-java/">Java</a>, and <a href="https://copilot-dev-days.github.io/agent-lab-python/">Python</a></p>
<div class="stats">
<div class="stat">
<div class="stat-value">~1hr</div>
<div class="stat-label">Duration</div>
</div>
<div class="stat">
<div class="stat-value">4</div>
<div class="stat-label">Parts</div>
</div>
<div class="stat">
<div class="stat-value">9</div>
<div class="stat-label">Checkpoints</div>
</div>
</div>
</section>
<section class="features">
<h2 class="section-title">What You'll Learn</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">🧠</div>
<h3>Context Engineering</h3>
<p>Teach AI about your codebase with instructions, making Copilot suggestions more accurate and relevant.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤖</div>
<h3>Agentic Primitives</h3>
<p>Use background agents, cloud agents, and custom workflows to scale your development.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h3>Design-First Development</h3>
<p>Let AI iterate on UI while you guide the creative vision with specialized design agents.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🧪</div>
<h3>Test-Driven Development</h3>
<p>Build reliable features using TDD agents with Red-Green-Refactor workflow.</p>
</div>
</div>
</section>
<section class="workshop">
<h2 class="section-title">Workshop Parts</h2>
<div class="parts-list">
<a href="step.html?step=01-setup" class="part-card">
<div class="part-number">01</div>
<div class="part-content">
<h3>Setup & Context Engineering</h3>
<p>Clone, configure, and teach the AI about your project</p>
</div>
<span class="part-time">15 min</span>
</a>
<a href="step.html?step=02-design" class="part-card">
<div class="part-number">02</div>
<div class="part-content">
<h3>Design-First Frontend</h3>
<p>Redesign the UI with creative themes using Plan Mode</p>
</div>
<span class="part-time">15 min</span>
</a>
<a href="step.html?step=03-quiz-master" class="part-card">
<div class="part-number">03</div>
<div class="part-content">
<h3>Custom Quiz Master</h3>
<p>Create themed quiz questions with custom agents</p>
</div>
<span class="part-time">10 min</span>
</a>
<a href="step.html?step=04-multi-agent" class="part-card">
<div class="part-number">04</div>
<div class="part-content">
<h3>Multi-Agent Development</h3>
<p>Build new features with TDD and design agents</p>
</div>
<span class="part-time">20 min</span>
</a>
</div>
</section>
</div>
<section class="prereqs">
<div class="container">
<h2 class="section-title">Prerequisites</h2>
<div class="prereqs-grid">
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>VS Code v1.107+</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>GitHub Copilot</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>Node.js 22+</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>Git installed</span>
</div>
</div>
</div>
</section>
<div class="container">
<footer>
<div class="footer-links">
<a href="https://github.com/copilot-dev-days/agent-lab-typescript">GitHub Repository</a>
<a href="https://www.youtube.com/code">VS Code YouTube</a>
<a href="https://code.visualstudio.com/docs/copilot/overview">Copilot Docs</a>
<a href="https://github.com/github/awesome-copilot">Awesome Copilot</a>
</div>
<p class="footer-credit">Built with 💜 by the VS Code Team</p>
</footer>
</div>
<script>
(function(){
var match=location.pathname.match(/\/(pt_BR|es)\//);
var locale=match?match[1]:'en';
var picker=document.getElementById('localePicker');
if(!picker)return;
var btn=picker.querySelector('.locale-toggle'),list=picker.querySelector('ul');
list.querySelectorAll('li').forEach(function(li){
var l=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(l===locale){li.classList.add('active');li.setAttribute('aria-selected','true');}
else{li.classList.remove('active');li.setAttribute('aria-selected','false');}
});
function open(){list.style.display='block';btn.setAttribute('aria-expanded','true');}
function close(){list.style.display='none';btn.setAttribute('aria-expanded','false');}
btn.addEventListener('click',function(e){e.stopPropagation();list.style.display==='block'?close():open();});
document.addEventListener('click',close);
document.addEventListener('keydown',function(e){if(e.key==='Escape')close();});
list.querySelectorAll('li').forEach(function(li){
li.addEventListener('click',function(e){
e.stopPropagation();
var t=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(t===locale){close();return;}
var p=location.pathname;
if(locale!=='en')p=p.replace(new RegExp('/'+locale+'/'),'/');
if(t!=='en'){var base=p.replace(/\/index\.html$/,'/');if(!base.endsWith('/'))base+='/';p=base+t+'/';}
localStorage.setItem('preferred-locale',t);
window.location.href=p+location.search;
});
});
})();
</script>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
html[data-theme="light"] {
--bg-dark: #f5f5f7;
--bg-card: #ffffff;
--bg-code: #f0f0f5;
--neon-cyan: #0077b6;
--neon-magenta: #c2185b;
--neon-purple: #7b1fa2;
--text-primary: #1a1a2e;
--text-secondary: #555570;
--border-color: #d0d0dd;
--success-green: #2e7d32;
--warning-yellow: #f9a825;
}
html[data-theme="light"] body::before {
background: linear-gradient(90deg, rgba(0,119,182,0.05) 1px, transparent 1px),
linear-gradient(rgba(0,119,182,0.05) 1px, transparent 1px);
}
html[data-theme="light"] .hero::before {
background: radial-gradient(ellipse at 50% 0%, rgba(0,119,182,0.15), transparent 70%);
}
html[data-theme="light"] .header {
background: rgba(245,245,247,0.95);
}
html[data-theme="light"] code,
html[data-theme="light"] pre {
background: var(--bg-code);
color: #1a1a2e;
}
html[data-theme="light"] .btn-secondary {
border-color: var(--border-color);
color: var(--text-primary);
}
html[data-theme="light"] .btn-secondary:hover {
background: rgba(0,119,182,0.1);
border-color: var(--neon-cyan);
}
html[data-theme="light"] .card {
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.theme-toggle {
background: none;
border: 1px solid var(--border-color);
color: var(--text-primary);
cursor: pointer;
padding: 0.4rem 0.8rem;
border-radius: 6px;
font-size: 1rem;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.theme-toggle:hover {
border-color: var(--neon-cyan);
color: var(--neon-cyan);
}
+200
View File
@@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LSC44NDPLQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LSC44NDPLQ');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VS Code GitHub Copilot Agent Lab | Edição TypeScript</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="alternate" hreflang="en" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="alternate" hreflang="pt-BR" href="https://copilot-dev-days.github.io/agent-lab-typescript/pt_BR/">
<link rel="alternate" hreflang="es" href="https://copilot-dev-days.github.io/agent-lab-typescript/es/">
<link rel="alternate" hreflang="x-default" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="stylesheet" href="../styles.css">
<link rel="stylesheet" href="../light-theme.css">
<script src="../theme-toggle.js"></script>
</head>
<body>
<div style="position:fixed;top:1rem;right:1rem;z-index:1000;display:flex;gap:0.5rem;align-items:center;">
<div class="locale-picker" id="localePicker">
<button type="button" class="locale-toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="Selecionar idioma">🌐</button>
<ul role="listbox" aria-label="Idioma">
<li role="option" data-locale="en" aria-selected="false">🇬🇧 English</li>
<li role="option" data-locale="es" aria-selected="false">🇪🇸 Español</li>
<li role="option" data-locale="pt-BR" aria-selected="true" class="active">🇧🇷 Português (Brasil)</li>
</ul>
</div>
<button class="theme-toggle" onclick="toggleTheme()">☀️ Claro</button>
</div>
<div class="container">
<section class="hero">
<span class="hero-badge">🚀 TypeScript + Vite + React + Tailwind</span>
<h1>VS Code GitHub Copilot Agent Lab</h1>
<p class="hero-subtitle">
Construa um jogo de Bingo Social enquanto domina o Agent Mode do VS Code com GitHub Copilot.
Aprenda engenharia de contexto, agentes personalizados e fluxos de desenvolvimento com IA.
</p>
<div class="hero-cta">
<a href="step.html?step=00-overview" class="btn btn-primary">🚀 Iniciar Workshop</a>
<a href="../game/" class="btn btn-secondary">🎮 Jogar</a>
<a href="https://github.com/copilot-dev-days/agent-lab-typescript" class="btn btn-secondary">📦 GitHub Repo</a>
</div>
<p class="hero-also">Também disponível em <a href="https://copilot-dev-days.github.io/agent-lab-dotnet/">.NET</a>, <a href="https://copilot-dev-days.github.io/agent-lab-java/">Java</a> e <a href="https://copilot-dev-days.github.io/agent-lab-python/">Python</a></p>
<div class="stats">
<div class="stat">
<div class="stat-value">~1hr</div>
<div class="stat-label">Duração</div>
</div>
<div class="stat">
<div class="stat-value">4</div>
<div class="stat-label">Partes</div>
</div>
<div class="stat">
<div class="stat-value">9</div>
<div class="stat-label">Etapas</div>
</div>
</div>
</section>
<section class="features">
<h2 class="section-title">O que Você Vai Aprender</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">🧠</div>
<h3>Engenharia de Contexto</h3>
<p>Ensine a IA sobre seu código com instruções, tornando as sugestões do Copilot mais precisas e relevantes.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤖</div>
<h3>Primitivas Agênticas</h3>
<p>Use agentes em segundo plano, agentes na nuvem e fluxos personalizados para escalar seu desenvolvimento.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h3>Desenvolvimento Design-First</h3>
<p>Deixe a IA iterar na UI enquanto você guia a visão criativa com agentes de design especializados.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🧪</div>
<h3>Desenvolvimento Orientado a Testes</h3>
<p>Construa funcionalidades confiáveis usando agentes TDD com o fluxo Red-Green-Refactor.</p>
</div>
</div>
</section>
<section class="workshop">
<h2 class="section-title">Partes do Workshop</h2>
<div class="parts-list">
<a href="step.html?step=01-setup" class="part-card">
<div class="part-number">01</div>
<div class="part-content">
<h3>Setup & Engenharia de Contexto</h3>
<p>Clone, configure e ensine a IA sobre seu projeto</p>
</div>
<span class="part-time">15 min</span>
</a>
<a href="step.html?step=02-design" class="part-card">
<div class="part-number">02</div>
<div class="part-content">
<h3>Frontend Design-First</h3>
<p>Redesenhe a UI com temas criativos usando Plan Mode</p>
</div>
<span class="part-time">15 min</span>
</a>
<a href="step.html?step=03-quiz-master" class="part-card">
<div class="part-number">03</div>
<div class="part-content">
<h3>Quiz Master Personalizado</h3>
<p>Crie perguntas temáticas com agentes personalizados</p>
</div>
<span class="part-time">10 min</span>
</a>
<a href="step.html?step=04-multi-agent" class="part-card">
<div class="part-number">04</div>
<div class="part-content">
<h3>Desenvolvimento Multi-Agent</h3>
<p>Construa novas funcionalidades com agentes de TDD e design</p>
</div>
<span class="part-time">20 min</span>
</a>
</div>
</section>
</div>
<section class="prereqs">
<div class="container">
<h2 class="section-title">Pré-requisitos</h2>
<div class="prereqs-grid">
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>VS Code v1.107+</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>GitHub Copilot</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>Node.js 22+</span>
</div>
<div class="prereq-item">
<span class="prereq-check">✓</span>
<span>Git instalado</span>
</div>
</div>
</div>
</section>
<div class="container">
<footer>
<div class="footer-links">
<a href="https://github.com/copilot-dev-days/agent-lab-typescript">GitHub Repository</a>
<a href="https://www.youtube.com/code">VS Code YouTube</a>
<a href="https://code.visualstudio.com/docs/copilot/overview">Copilot Docs</a>
<a href="https://github.com/github/awesome-copilot">Awesome Copilot</a>
</div>
<p class="footer-credit">Feito com 💜 pelo time do VS Code</p>
</footer>
</div>
<script>
(function(){
var match=location.pathname.match(/\/(pt_BR|es)\//);
var locale=match?match[1]:'en';
var picker=document.getElementById('localePicker');
if(!picker)return;
var btn=picker.querySelector('.locale-toggle'),list=picker.querySelector('ul');
list.querySelectorAll('li').forEach(function(li){
var l=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(l===locale){li.classList.add('active');li.setAttribute('aria-selected','true');}
else{li.classList.remove('active');li.setAttribute('aria-selected','false');}
});
function open(){list.style.display='block';btn.setAttribute('aria-expanded','true');}
function close(){list.style.display='none';btn.setAttribute('aria-expanded','false');}
btn.addEventListener('click',function(e){e.stopPropagation();list.style.display==='block'?close():open();});
document.addEventListener('click',close);
document.addEventListener('keydown',function(e){if(e.key==='Escape')close();});
list.querySelectorAll('li').forEach(function(li){
li.addEventListener('click',function(e){
e.stopPropagation();
var t=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(t===locale){close();return;}
var p=location.pathname;
if(locale!=='en')p=p.replace(new RegExp('/'+locale+'/'),'/');;
if(t!=='en')p=p.replace(/\/docs\//,'/docs/'+t+'/');
localStorage.setItem('preferred-locale',t);
window.location.href=p+location.search;
});
});
})();
</script>
</body>
</html>
+373
View File
@@ -0,0 +1,373 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LSC44NDPLQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LSC44NDPLQ');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VS Code GitHub Copilot Agent Lab</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="alternate" hreflang="en" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="alternate" hreflang="pt-BR" href="https://copilot-dev-days.github.io/agent-lab-typescript/pt_BR/">
<link rel="alternate" hreflang="es" href="https://copilot-dev-days.github.io/agent-lab-typescript/es/">
<link rel="alternate" hreflang="x-default" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="stylesheet" href="../step.css">
<link rel="stylesheet" href="../light-theme.css">
<script src="../theme-toggle.js"></script>
</head>
<body>
<header class="header">
<div class="header-content">
<a href="index.html" class="header-brand">
<span>🎮</span>
<span class="header-title">VS Code Copilot Agent Lab</span>
</a>
<nav class="header-nav">
<div class="locale-picker" id="localePicker">
<button type="button" class="locale-toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="Selecionar idioma">🌐</button>
<ul role="listbox" aria-label="Idioma">
<li role="option" data-locale="en" aria-selected="false">🇬🇧 English</li>
<li role="option" data-locale="es" aria-selected="false">🇪🇸 Español</li>
<li role="option" data-locale="pt-BR" aria-selected="true" class="active">🇧🇷 Português (Brasil)</li>
</ul>
</div>
<button class="theme-toggle" onclick="toggleTheme()">☀️ Claro</button>
<button class="nav-btn" id="prevBtn" disabled>← Anterior</button>
<button class="nav-btn" id="nextBtn">Próximo →</button>
</nav>
</div>
</header>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-section">
<div class="sidebar-title">Partes do Lab</div>
<nav id="stepNav"></nav>
</div>
</aside>
<main class="main">
<article class="content">
<div id="markdown-content" class="markdown">
<div class="loading">Carregando conteúdo</div>
</div>
<nav class="nav-footer">
<a href="#" class="nav-footer-btn disabled" id="prevLink">← Anterior</a>
<a href="#" class="nav-footer-btn" id="nextLink">Próximo →</a>
</nav>
</article>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
<script>
const steps = [
{ id: 'guide', file: 'GUIDE.md', title: 'Referência Rápida', number: '📚' },
{ id: '00-overview', file: '00-overview.md', title: 'Visão Geral & Checklist', number: '00' },
{ id: '01-setup', file: '01-setup.md', title: 'Setup & Engenharia de Contexto', number: '01' },
{ id: '02-design', file: '02-design.md', title: 'Frontend Design-First', number: '02' },
{ id: '03-quiz-master', file: '03-quiz-master.md', title: 'Quiz Master Personalizado', number: '03' },
{ id: '04-multi-agent', file: '04-multi-agent.md', title: 'Desenvolvimento Multi-Agent', number: '04' },
{ id: '05-complete', file: '05-complete.md', title: '🎉 Completo!', number: '🎉' },
];
function celebrate() {
const duration = 3000;
const end = Date.now() + duration;
const colors = ['#00f5ff', '#ff00ff', '#b366ff', '#00ff88', '#ffff00'];
(function frame() {
confetti({ particleCount: 5, angle: 60, spread: 55, origin: { x: 0 }, colors });
confetti({ particleCount: 5, angle: 120, spread: 55, origin: { x: 1 }, colors });
if (Date.now() < end) requestAnimationFrame(frame);
}());
}
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/copilot-dev-days/agent-lab-typescript/main/workshop/pt_BR/';
const CHECKBOX_STATE_KEY_PREFIX = 'workshop-checkboxes:pt_BR:';
const COPY_STATUS_RESET_DELAY = 2000;
const pendingCopyResetTimers = new Set();
function getCurrentStepId() {
const params = new URLSearchParams(window.location.search);
return params.get('step') || '00-overview';
}
function getCurrentStepIndex() {
return steps.findIndex(s => s.id === getCurrentStepId());
}
function buildSidebar() {
const nav = document.getElementById('stepNav');
const currentId = getCurrentStepId();
nav.innerHTML = steps.map(step => `
<a href="?step=${step.id}" class="step-link ${step.id === currentId ? 'active' : ''}">
<span class="step-number">${step.number}</span>
<span>${step.title}</span>
</a>
`).join('');
}
function updateNavigation() {
const idx = getCurrentStepIndex();
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const prevLink = document.getElementById('prevLink');
const nextLink = document.getElementById('nextLink');
prevBtn.disabled = idx <= 0;
nextBtn.disabled = idx >= steps.length - 1;
prevBtn.onclick = () => idx > 0 && (window.location.search = `?step=${steps[idx - 1].id}`);
nextBtn.onclick = () => idx < steps.length - 1 && (window.location.search = `?step=${steps[idx + 1].id}`);
if (idx > 0) {
prevLink.href = `?step=${steps[idx - 1].id}`;
prevLink.textContent = `← ${steps[idx - 1].title}`;
prevLink.classList.remove('disabled');
} else {
prevLink.classList.add('disabled');
prevLink.textContent = '← Anterior';
}
if (idx < steps.length - 1) {
nextLink.href = `?step=${steps[idx + 1].id}`;
nextLink.textContent = `${steps[idx + 1].title} →`;
nextLink.classList.remove('disabled');
} else {
nextLink.classList.add('disabled');
nextLink.textContent = 'Próximo →';
}
}
function processMarkdown(md) {
md = md.replace(/\[📚 Lab Guide\][^\n]+\n+---/g, '');
md = md.replace(/\[📚 Lab Guide\][^\n]+/g, '');
md = md.replace(/\[🎮 Live Demo\][^\n]+\n+---/g, '');
md = md.replace(/\[🎮 Live Demo\][^\n]+/g, '');
md = md.replace(/\[← README\][^\n]+/g, '');
md = md.replace(/\[← Overview\][^\n]+/g, '');
md = md.replace(/\[← Part \d+\][^\n]+/g, '');
md = md.replace(/👉 \*\*\[Continue to[^\n]+/g, '');
md = md.replace(/👉 \*\*\[Start with[^\n]+/g, '');
md = md.replace(/\]\(\.\.\/game\/\)/g, '](../game/)');
md = md.replace(/\]\((\d+)-([^)]+)\.md\)/g, '](step.html?step=$1-$2)');
md = md.replace(/\]\(GUIDE\.md\)/g, '](step.html?step=guide)');
md = md.replace(/>\s*💡\s*\*\*Tip:\*\*/gi, '> **💡 Tip**');
md = md.replace(/>\s*💡\s*\*\*(Optional)[,:]\*\*/gi, '> **💡 $1**');
md = md.replace(/>\s*💡\s*\*\*(Think about)[,:]\*\*/gi, '> **💡 $1**');
md = md.replace(/---\s*\n\s*---/g, '---');
return md;
}
function getCheckboxStorageKey(stepId) {
return `${CHECKBOX_STATE_KEY_PREFIX}${stepId}`;
}
function setupInteractiveCheckboxes(stepId) {
const container = document.getElementById('markdown-content');
if (!container) return;
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
if (!checkboxes.length) return;
const storageKey = getCheckboxStorageKey(stepId);
let checkedIndexes = [];
try {
checkedIndexes = JSON.parse(localStorage.getItem(storageKey) || '[]');
if (!Array.isArray(checkedIndexes)) checkedIndexes = [];
} catch {
checkedIndexes = [];
}
checkboxes.forEach((checkbox, index) => {
checkbox.disabled = false;
checkbox.checked = checkedIndexes.includes(index) || checkbox.checked;
checkbox.setAttribute('aria-label', `Item da checklist ${index + 1}`);
checkbox.addEventListener('change', () => {
const updated = Array.from(checkboxes)
.reduce((acc, cb, i) => {
if (cb.checked) acc.push(i);
return acc;
}, []);
localStorage.setItem(storageKey, JSON.stringify(updated));
});
});
}
async function copyCodeToClipboard(text) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
const copied = document.execCommand('copy');
document.body.removeChild(textarea);
if (!copied) throw new Error('Fallback copy failed');
}
function setupCodeBlockCopyButtons() {
const container = document.getElementById('markdown-content');
if (!container) return;
const codeBlocks = container.querySelectorAll('pre');
codeBlocks.forEach((pre) => {
const code = pre.querySelector('code');
const codeText = code?.textContent?.trim();
if (!codeText) return;
const copyButton = document.createElement('button');
copyButton.type = 'button';
copyButton.className = 'code-copy-btn';
copyButton.textContent = 'Copiar';
copyButton.setAttribute('aria-label', 'Copiar código');
const liveRegion = document.createElement('span');
liveRegion.className = 'sr-only';
liveRegion.setAttribute('aria-live', 'polite');
let resetTimer = null;
const resetButtonState = () => {
copyButton.textContent = 'Copiar';
copyButton.dataset.state = 'idle';
liveRegion.textContent = '';
};
copyButton.addEventListener('click', async () => {
if (resetTimer) {
clearTimeout(resetTimer);
pendingCopyResetTimers.delete(resetTimer);
}
try {
await copyCodeToClipboard(codeText);
copyButton.textContent = 'Copiado!';
copyButton.dataset.state = 'copied';
liveRegion.textContent = 'Código copiado para a área de transferência';
} catch {
copyButton.textContent = 'Falhou';
copyButton.dataset.state = 'error';
liveRegion.textContent = 'Não foi possível copiar o código';
}
resetTimer = setTimeout(() => {
pendingCopyResetTimers.delete(resetTimer);
resetTimer = null;
resetButtonState();
}, COPY_STATUS_RESET_DELAY);
pendingCopyResetTimers.add(resetTimer);
});
pre.append(copyButton, liveRegion);
});
}
async function loadContent() {
const idx = getCurrentStepIndex();
if (idx === -1) {
document.getElementById('markdown-content').innerHTML = '<p>Passo não encontrado. <a href="?step=00-overview">Ir para Visão Geral</a></p>';
return;
}
const step = steps[idx];
document.title = `${step.title} | VS Code Copilot Agent Lab`;
try {
pendingCopyResetTimers.forEach(timer => clearTimeout(timer));
pendingCopyResetTimers.clear();
const response = await fetch(`${GITHUB_RAW_BASE}${step.file}`);
if (!response.ok) throw new Error('Failed to load');
let md = await response.text();
md = processMarkdown(md);
const stepDir = step.file.substring(0, step.file.lastIndexOf('/') + 1);
md = md.replace(/!\[([^\]]*)\]\((?!https?:\/\/|\/\/)([^)]+)\)/g, (match, alt, path) => {
const cleanPath = path.replace(/^\.\//, '');
return `![${alt}](${GITHUB_RAW_BASE}${stepDir}${cleanPath})`;
});
marked.setOptions({ breaks: true, gfm: true });
document.getElementById('markdown-content').innerHTML = marked.parse(md);
setupInteractiveCheckboxes(step.id);
setupCodeBlockCopyButtons();
if (step.id === '05-complete') {
setTimeout(celebrate, 500);
}
} catch (error) {
document.getElementById('markdown-content').innerHTML = `
<h1>Erro ao Carregar Conteúdo</h1>
<p>Não foi possível carregar ${step.file}. Verifique sua conexão com a internet.</p>
<p><a href="index.html">Voltar para a página inicial</a></p>
`;
}
}
buildSidebar();
updateNavigation();
loadContent();
document.addEventListener('keydown', (e) => {
const idx = getCurrentStepIndex();
if (e.key === 'ArrowLeft' && idx > 0) {
window.location.search = `?step=${steps[idx - 1].id}`;
} else if (e.key === 'ArrowRight' && idx < steps.length - 1) {
window.location.search = `?step=${steps[idx + 1].id}`;
}
});
</script>
<script>
(function(){
var match=location.pathname.match(/\/(pt_BR|es)\//);
var locale=match?match[1]:'en';
var picker=document.getElementById('localePicker');
if(!picker)return;
var btn=picker.querySelector('.locale-toggle'),list=picker.querySelector('ul');
list.querySelectorAll('li').forEach(function(li){
var l=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(l===locale){li.classList.add('active');li.setAttribute('aria-selected','true');}
else{li.classList.remove('active');li.setAttribute('aria-selected','false');}
});
function open(){list.style.display='block';btn.setAttribute('aria-expanded','true');}
function close(){list.style.display='none';btn.setAttribute('aria-expanded','false');}
btn.addEventListener('click',function(e){e.stopPropagation();list.style.display==='block'?close():open();});
document.addEventListener('click',close);
document.addEventListener('keydown',function(e){if(e.key==='Escape')close();});
list.querySelectorAll('li').forEach(function(li){
li.addEventListener('click',function(e){
e.stopPropagation();
var t=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(t===locale){close();return;}
var p=location.pathname;
if(locale!=='en')p=p.replace(new RegExp('/'+locale+'/'),'/');;
if(t!=='en')p=p.replace(/\/docs\//,'/docs/'+t+'/');
localStorage.setItem('preferred-locale',t);
window.location.href=p+location.search;
});
});
})();
</script>
</body>
</html>
+249
View File
@@ -0,0 +1,249 @@
:root {
--bg-dark: #0a0a0f;
--bg-card: #12121a;
--bg-code: #1a1a2e;
--bg-overlay: rgba(18, 18, 26, 0.92);
--neon-cyan: #00f5ff;
--neon-magenta: #ff00ff;
--neon-purple: #b366ff;
--text-primary: #ffffff;
--text-secondary: #a0a0b0;
--border-color: #2a2a3a;
--success-green: #4ec9b0;
--warning-yellow: #dcdcaa;
--code-block-top-padding: 3.5rem;
--code-block-horizontal-padding: 1.25rem;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Space Grotesk', sans-serif;
background: var(--bg-dark);
color: var(--text-primary);
line-height: 1.7;
min-height: 100vh;
}
body::before {
content: '';
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background:
linear-gradient(90deg, rgba(0,245,255,0.03) 1px, transparent 1px),
linear-gradient(rgba(0,245,255,0.03) 1px, transparent 1px);
background-size: 50px 50px;
pointer-events: none;
z-index: -1;
}
.header {
background: var(--bg-card);
border-bottom: 1px solid var(--border-color);
padding: 1rem 2rem;
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
max-width: 1200px; margin: 0 auto;
display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.header-brand {
display: flex; align-items: center; gap: 1rem;
text-decoration: none; color: var(--text-primary);
}
.header-brand:hover { color: var(--neon-cyan); }
.header-title {
font-size: 1.25rem; font-weight: 600;
background: linear-gradient(135deg, var(--neon-cyan), var(--neon-magenta));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.header-nav { display: flex; gap: 0.5rem; }
.nav-btn {
display: inline-flex; align-items: center; gap: 0.5rem;
padding: 0.5rem 1rem; background: transparent;
border: 1px solid var(--border-color); border-radius: 6px;
color: var(--text-secondary); text-decoration: none;
font-size: 0.875rem; transition: all 0.2s ease;
cursor: pointer; font-family: inherit;
}
.nav-btn:hover:not(:disabled) { border-color: var(--neon-cyan); color: var(--neon-cyan); }
.nav-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.layout { display: flex; max-width: 1400px; margin: 0 auto; }
.sidebar {
width: 280px; background: var(--bg-card);
border-right: 1px solid var(--border-color);
height: calc(100vh - 65px); position: sticky; top: 65px;
overflow-y: auto; flex-shrink: 0;
}
.sidebar-section { padding: 1rem; }
.sidebar-title {
font-size: 0.75rem; text-transform: uppercase;
letter-spacing: 0.1em; color: var(--text-secondary);
margin-bottom: 0.75rem; padding: 0 0.5rem;
}
.step-link {
display: flex; align-items: center; gap: 0.75rem;
padding: 0.625rem 0.75rem; border-radius: 6px;
text-decoration: none; color: var(--text-secondary);
font-size: 0.875rem; transition: all 0.2s ease;
margin-bottom: 0.25rem;
}
.step-link:hover { background: rgba(0,245,255,0.1); color: var(--text-primary); }
.step-link.active {
background: linear-gradient(135deg, rgba(0,245,255,0.2), rgba(255,0,255,0.2));
color: var(--text-primary); border-left: 3px solid var(--neon-cyan);
}
.step-number {
width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;
background: var(--border-color); border-radius: 6px;
font-size: 0.75rem; font-weight: 600;
font-family: 'JetBrains Mono', monospace; flex-shrink: 0;
}
.step-link.active .step-number {
background: linear-gradient(135deg, var(--neon-cyan), var(--neon-magenta));
color: var(--bg-dark);
}
.main { flex: 1; min-width: 0; padding: 2rem 3rem; }
.content { max-width: 800px; margin: 0 auto; }
.loading {
display: flex; align-items: center; justify-content: center;
height: 300px; color: var(--text-secondary);
}
.loading::after {
content: ''; width: 32px; height: 32px;
border: 3px solid var(--border-color);
border-top-color: var(--neon-cyan); border-radius: 50%;
animation: spin 0.8s linear infinite; margin-left: 1rem;
}
@keyframes spin { to { transform: rotate(360deg); } }
.markdown h1 {
font-size: 2.25rem; font-weight: 700; margin-bottom: 1.5rem;
background: linear-gradient(135deg, var(--neon-cyan), var(--neon-magenta));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text; line-height: 1.3;
}
.markdown h2 {
font-size: 1.5rem; font-weight: 600; margin: 2.5rem 0 1rem;
color: var(--neon-cyan); border-bottom: 1px solid var(--border-color);
padding-bottom: 0.5rem;
}
.markdown h3 { font-size: 1.25rem; font-weight: 600; margin: 2rem 0 0.75rem; color: var(--neon-magenta); }
.markdown h4 { font-size: 1.1rem; font-weight: 600; margin: 1.5rem 0 0.5rem; color: var(--text-primary); }
.markdown p { margin-bottom: 1rem; color: var(--text-secondary); }
.markdown a { color: var(--neon-cyan); text-decoration: none; }
.markdown a:hover { text-decoration: underline; }
.markdown strong { color: var(--text-primary); font-weight: 600; }
.markdown ul, .markdown ol { margin: 1rem 0 1.5rem 1.5rem; color: var(--text-secondary); }
.markdown li { margin-bottom: 0.5rem; }
.markdown li::marker { color: var(--neon-cyan); }
.markdown input[type="checkbox"] {
appearance: none; width: 18px; height: 18px;
border: 2px solid var(--border-color); border-radius: 4px;
margin-right: 0.5rem; vertical-align: middle;
position: relative; cursor: pointer;
}
.markdown input[type="checkbox"]:checked { background: var(--neon-cyan); border-color: var(--neon-cyan); }
.markdown input[type="checkbox"]:checked::after {
content: '✓'; position: absolute; color: var(--bg-dark);
font-size: 12px; top: 50%; left: 50%; transform: translate(-50%, -50%);
}
.markdown code { font-family: 'JetBrains Mono', monospace; font-size: 0.875em; }
.markdown :not(pre) > code {
background: var(--bg-code); padding: 0.2em 0.4em;
border-radius: 4px; color: var(--warning-yellow);
}
.markdown pre {
position: relative;
background: var(--bg-code); border: 1px solid var(--border-color);
border-radius: 8px;
padding: var(--code-block-top-padding) var(--code-block-horizontal-padding) var(--code-block-horizontal-padding) var(--code-block-horizontal-padding);
overflow-x: auto; margin: 1.5rem 0;
}
.markdown pre code { color: var(--text-primary); background: none; padding: 0; }
.markdown .code-copy-btn {
position: absolute; top: 0.75rem; right: 0.75rem;
display: inline-flex; align-items: center; justify-content: center;
min-width: max-content; padding: 0.4rem 0.75rem;
border: 1px solid var(--border-color); border-radius: 999px;
background: var(--bg-overlay); color: var(--text-secondary);
font: inherit; font-size: 0.8rem; cursor: pointer; transition: all 0.2s ease;
}
.markdown .code-copy-btn:hover,
.markdown .code-copy-btn:focus-visible {
border-color: var(--neon-cyan); color: var(--neon-cyan);
outline: none;
}
.markdown .code-copy-btn[data-state="copied"] {
border-color: var(--success-green); color: var(--success-green);
}
.markdown .code-copy-btn[data-state="error"] {
border-color: var(--neon-magenta); color: var(--neon-magenta);
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.markdown blockquote {
background: var(--bg-card); border-left: 4px solid var(--neon-cyan);
border-radius: 0 8px 8px 0; padding: 1rem 1.25rem; margin: 1.5rem 0;
}
.markdown blockquote p { margin: 0; color: var(--text-secondary); }
.markdown blockquote p:first-child { font-weight: 600; color: var(--neon-cyan); }
.markdown img { max-width: 100%; border-radius: 8px; border: 1px solid var(--border-color); margin: 1.5rem 0; }
.markdown table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; }
.markdown th, .markdown td { padding: 0.75rem 1rem; border: 1px solid var(--border-color); text-align: left; }
.markdown th { background: var(--bg-card); color: var(--text-primary); font-weight: 600; }
.markdown td { color: var(--text-secondary); }
.markdown tr:hover td { background: rgba(0,245,255,0.05); }
.markdown hr { border: none; border-top: 1px solid var(--border-color); margin: 2.5rem 0; }
.nav-footer {
display: flex; justify-content: space-between;
margin-top: 3rem; padding-top: 2rem;
border-top: 1px solid var(--border-color);
}
.nav-footer-btn {
display: inline-flex; align-items: center; gap: 0.5rem;
padding: 0.75rem 1.5rem; background: var(--bg-card);
border: 1px solid var(--border-color); border-radius: 8px;
color: var(--text-primary); text-decoration: none;
font-weight: 500; transition: all 0.2s ease;
}
.nav-footer-btn:hover { border-color: var(--neon-cyan); transform: translateY(-2px); }
.nav-footer-btn.disabled { opacity: 0.4; pointer-events: none; }
@media (max-width: 900px) {
.sidebar { display: none; }
.main { padding: 1.5rem; }
.header-title { display: none; }
}
/* --- Locale Picker --- */
.locale-picker { position: relative; display: inline-flex; }
.locale-toggle { display: flex; align-items: center; justify-content: center; padding: 8px; border-radius: 10px; border: 1px solid var(--border-color); background: var(--bg-card); color: var(--text-secondary); font-size: 1rem; line-height: 1; transition: all 0.2s; cursor: pointer; }
.locale-toggle:hover { background: var(--neon-cyan); color: #fff; border-color: var(--neon-cyan); }
.locale-picker ul { display: none; position: absolute; top: 100%; right: 0; margin: 4px 0 0; padding: 4px 0; list-style: none; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 100; min-width: 180px; }
.locale-picker li { padding: 8px 16px; cursor: pointer; font-size: 0.9rem; color: var(--text-primary); white-space: nowrap; transition: background 0.15s; }
.locale-picker li:hover { background: var(--neon-cyan); color: #fff; }
.locale-picker li.active { font-weight: 600; color: var(--neon-cyan); }
.locale-picker li.active:hover { color: #fff; }
+380
View File
@@ -0,0 +1,380 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LSC44NDPLQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LSC44NDPLQ');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VS Code GitHub Copilot Agent Lab</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="alternate" hreflang="en" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="alternate" hreflang="pt-BR" href="https://copilot-dev-days.github.io/agent-lab-typescript/pt_BR/">
<link rel="alternate" hreflang="es" href="https://copilot-dev-days.github.io/agent-lab-typescript/es/">
<link rel="alternate" hreflang="x-default" href="https://copilot-dev-days.github.io/agent-lab-typescript/">
<link rel="stylesheet" href="step.css">
<link rel="stylesheet" href="light-theme.css">
<script src="theme-toggle.js"></script>
</head>
<body>
<header class="header">
<div class="header-content">
<a href="index.html" class="header-brand">
<span>🎮</span>
<span class="header-title">VS Code Copilot Agent Lab</span>
</a>
<nav class="header-nav">
<div class="locale-picker" id="localePicker">
<button type="button" class="locale-toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="Select language">🌐</button>
<ul role="listbox" aria-label="Language">
<li role="option" data-locale="en" aria-selected="true" class="active">🇬🇧 English</li>
<li role="option" data-locale="es" aria-selected="false">🇪🇸 Español</li>
<li role="option" data-locale="pt-BR" aria-selected="false">🇧🇷 Português (Brasil)</li>
</ul>
</div>
<button class="theme-toggle" onclick="toggleTheme()">☀️ Light</button>
<button class="nav-btn" id="prevBtn" disabled>← Previous</button>
<button class="nav-btn" id="nextBtn">Next →</button>
</nav>
</div>
</header>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-section">
<div class="sidebar-title">Lab Parts</div>
<nav id="stepNav"></nav>
</div>
</aside>
<main class="main">
<article class="content">
<div id="markdown-content" class="markdown">
<div class="loading">Loading content</div>
</div>
<nav class="nav-footer">
<a href="#" class="nav-footer-btn disabled" id="prevLink">← Previous</a>
<a href="#" class="nav-footer-btn" id="nextLink">Next →</a>
</nav>
</article>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
<script>
const steps = [
{ id: 'guide', file: 'GUIDE.md', title: 'Quick Reference', number: '📚' },
{ id: '00-overview', file: '00-overview.md', title: 'Overview & Checklist', number: '00' },
{ id: '01-setup', file: '01-setup.md', title: 'Setup & Context Engineering', number: '01' },
{ id: '02-design', file: '02-design.md', title: 'Design-First Frontend', number: '02' },
{ id: '03-quiz-master', file: '03-quiz-master.md', title: 'Custom Quiz Master', number: '03' },
{ id: '04-multi-agent', file: '04-multi-agent.md', title: 'Multi-Agent Development', number: '04' },
{ id: '05-complete', file: '05-complete.md', title: '🎉 Complete!', number: '🎉' },
];
function celebrate() {
const duration = 3000;
const end = Date.now() + duration;
const colors = ['#00f5ff', '#ff00ff', '#b366ff', '#00ff88', '#ffff00'];
(function frame() {
confetti({ particleCount: 5, angle: 60, spread: 55, origin: { x: 0 }, colors });
confetti({ particleCount: 5, angle: 120, spread: 55, origin: { x: 1 }, colors });
if (Date.now() < end) requestAnimationFrame(frame);
}());
}
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/copilot-dev-days/agent-lab-typescript/main/workshop/';
const CHECKBOX_STATE_KEY_PREFIX = 'workshop-checkboxes:';
const COPY_STATUS_RESET_DELAY = 2000;
const pendingCopyResetTimers = new Set();
function getCurrentStepId() {
const params = new URLSearchParams(window.location.search);
return params.get('step') || '00-overview';
}
function getCurrentStepIndex() {
return steps.findIndex(s => s.id === getCurrentStepId());
}
function buildSidebar() {
const nav = document.getElementById('stepNav');
const currentId = getCurrentStepId();
nav.innerHTML = steps.map(step => `
<a href="?step=${step.id}" class="step-link ${step.id === currentId ? 'active' : ''}">
<span class="step-number">${step.number}</span>
<span>${step.title}</span>
</a>
`).join('');
}
function updateNavigation() {
const idx = getCurrentStepIndex();
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const prevLink = document.getElementById('prevLink');
const nextLink = document.getElementById('nextLink');
prevBtn.disabled = idx <= 0;
nextBtn.disabled = idx >= steps.length - 1;
prevBtn.onclick = () => idx > 0 && (window.location.search = `?step=${steps[idx - 1].id}`);
nextBtn.onclick = () => idx < steps.length - 1 && (window.location.search = `?step=${steps[idx + 1].id}`);
if (idx > 0) {
prevLink.href = `?step=${steps[idx - 1].id}`;
prevLink.textContent = `← ${steps[idx - 1].title}`;
prevLink.classList.remove('disabled');
} else {
prevLink.classList.add('disabled');
prevLink.textContent = '← Previous';
}
if (idx < steps.length - 1) {
nextLink.href = `?step=${steps[idx + 1].id}`;
nextLink.textContent = `${steps[idx + 1].title} →`;
nextLink.classList.remove('disabled');
} else {
nextLink.classList.add('disabled');
nextLink.textContent = 'Next →';
}
}
function processMarkdown(md) {
// Remove header/footer nav lines
md = md.replace(/\[📚 Lab Guide\][^\n]+\n+---/g, '');
md = md.replace(/\[📚 Lab Guide\][^\n]+/g, '');
md = md.replace(/\[🎮 Live Demo\][^\n]+\n+---/g, '');
md = md.replace(/\[🎮 Live Demo\][^\n]+/g, '');
md = md.replace(/\[← README\][^\n]+/g, '');
md = md.replace(/\[← Overview\][^\n]+/g, '');
md = md.replace(/\[← Part \d+\][^\n]+/g, '');
md = md.replace(/👉 \*\*\[Continue to[^\n]+/g, '');
md = md.replace(/👉 \*\*\[Start with[^\n]+/g, '');
// Convert relative game link to absolute
md = md.replace(/\]\(\.\.\/game\/\)/g, '](game/)');
// Convert internal markdown links to step viewer links
md = md.replace(/\]\((\d+)-([^)]+)\.md\)/g, '](step.html?step=$1-$2)');
md = md.replace(/\]\(GUIDE\.md\)/g, '](step.html?step=guide)');
// Handle GitHub-style alerts
md = md.replace(/>\s*💡\s*\*\*Tip:\*\*/gi, '> **💡 Tip**');
md = md.replace(/>\s*💡\s*\*\*(Optional)[,:]\*\*/gi, '> **💡 $1**');
md = md.replace(/>\s*💡\s*\*\*(Think about)[,:]\*\*/gi, '> **💡 $1**');
// Clean up multiple consecutive horizontal rules
md = md.replace(/---\s*\n\s*---/g, '---');
return md;
}
function getCheckboxStorageKey(stepId) {
return `${CHECKBOX_STATE_KEY_PREFIX}${stepId}`;
}
function setupInteractiveCheckboxes(stepId) {
const container = document.getElementById('markdown-content');
if (!container) return;
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
if (!checkboxes.length) return;
const storageKey = getCheckboxStorageKey(stepId);
let checkedIndexes = [];
try {
checkedIndexes = JSON.parse(localStorage.getItem(storageKey) || '[]');
if (!Array.isArray(checkedIndexes)) checkedIndexes = [];
} catch {
checkedIndexes = [];
}
checkboxes.forEach((checkbox, index) => {
checkbox.disabled = false;
checkbox.checked = checkedIndexes.includes(index) || checkbox.checked;
checkbox.setAttribute('aria-label', `Checklist item ${index + 1}`);
checkbox.addEventListener('change', () => {
const updated = Array.from(checkboxes)
.reduce((acc, cb, i) => {
if (cb.checked) acc.push(i);
return acc;
}, []);
localStorage.setItem(storageKey, JSON.stringify(updated));
});
});
}
async function copyCodeToClipboard(text) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
// Keep the deprecated fallback for older browsers that lack navigator.clipboard.
const copied = document.execCommand('copy');
document.body.removeChild(textarea);
if (!copied) throw new Error('Fallback copy failed');
}
function setupCodeBlockCopyButtons() {
const container = document.getElementById('markdown-content');
if (!container) return;
const codeBlocks = container.querySelectorAll('pre');
codeBlocks.forEach((pre) => {
const code = pre.querySelector('code');
const codeText = code?.textContent?.trim();
if (!codeText) return;
const copyButton = document.createElement('button');
copyButton.type = 'button';
copyButton.className = 'code-copy-btn';
copyButton.textContent = 'Copy';
copyButton.setAttribute('aria-label', 'Copy code');
const liveRegion = document.createElement('span');
liveRegion.className = 'sr-only';
liveRegion.setAttribute('aria-live', 'polite');
let resetTimer = null;
const resetButtonState = () => {
copyButton.textContent = 'Copy';
copyButton.dataset.state = 'idle';
liveRegion.textContent = '';
};
copyButton.addEventListener('click', async () => {
if (resetTimer) {
clearTimeout(resetTimer);
pendingCopyResetTimers.delete(resetTimer);
}
try {
await copyCodeToClipboard(codeText);
copyButton.textContent = 'Copied!';
copyButton.dataset.state = 'copied';
liveRegion.textContent = 'Code copied to clipboard';
} catch {
copyButton.textContent = 'Failed';
copyButton.dataset.state = 'error';
liveRegion.textContent = 'Unable to copy code';
}
resetTimer = setTimeout(() => {
pendingCopyResetTimers.delete(resetTimer);
resetTimer = null;
resetButtonState();
}, COPY_STATUS_RESET_DELAY);
pendingCopyResetTimers.add(resetTimer);
});
pre.append(copyButton, liveRegion);
});
}
async function loadContent() {
const idx = getCurrentStepIndex();
if (idx === -1) {
document.getElementById('markdown-content').innerHTML = '<p>Step not found. <a href="?step=00-overview">Go to Overview</a></p>';
return;
}
const step = steps[idx];
document.title = `${step.title} | VS Code Copilot Agent Lab`;
try {
pendingCopyResetTimers.forEach(timer => clearTimeout(timer));
pendingCopyResetTimers.clear();
const response = await fetch(`${GITHUB_RAW_BASE}${step.file}`);
if (!response.ok) throw new Error('Failed to load');
let md = await response.text();
md = processMarkdown(md);
// Fix relative image paths to resolve against the markdown file's directory
const stepDir = step.file.substring(0, step.file.lastIndexOf('/') + 1);
md = md.replace(/!\[([^\]]*)\]\((?!https?:\/\/|\/\/)([^)]+)\)/g, (match, alt, path) => {
const cleanPath = path.replace(/^\.\//, '');
return `![${alt}](${GITHUB_RAW_BASE}${stepDir}${cleanPath})`;
});
marked.setOptions({ breaks: true, gfm: true });
document.getElementById('markdown-content').innerHTML = marked.parse(md);
setupInteractiveCheckboxes(step.id);
setupCodeBlockCopyButtons();
if (step.id === '05-complete') {
setTimeout(celebrate, 500);
}
} catch (error) {
document.getElementById('markdown-content').innerHTML = `
<h1>Error Loading Content</h1>
<p>Could not load ${step.file}. Please check your internet connection.</p>
<p><a href="index.html">Return to home</a></p>
`;
}
}
buildSidebar();
updateNavigation();
loadContent();
document.addEventListener('keydown', (e) => {
const idx = getCurrentStepIndex();
if (e.key === 'ArrowLeft' && idx > 0) {
window.location.search = `?step=${steps[idx - 1].id}`;
} else if (e.key === 'ArrowRight' && idx < steps.length - 1) {
window.location.search = `?step=${steps[idx + 1].id}`;
}
});
</script>
<script>
(function(){
var match=location.pathname.match(/\/(pt_BR|es)\//);
var locale=match?match[1]:'en';
var picker=document.getElementById('localePicker');
if(!picker)return;
var btn=picker.querySelector('.locale-toggle'),list=picker.querySelector('ul');
list.querySelectorAll('li').forEach(function(li){
var l=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(l===locale){li.classList.add('active');li.setAttribute('aria-selected','true');}
else{li.classList.remove('active');li.setAttribute('aria-selected','false');}
});
function open(){list.style.display='block';btn.setAttribute('aria-expanded','true');}
function close(){list.style.display='none';btn.setAttribute('aria-expanded','false');}
btn.addEventListener('click',function(e){e.stopPropagation();list.style.display==='block'?close():open();});
document.addEventListener('click',close);
document.addEventListener('keydown',function(e){if(e.key==='Escape')close();});
list.querySelectorAll('li').forEach(function(li){
li.addEventListener('click',function(e){
e.stopPropagation();
var t=li.dataset.locale==='pt-BR'?'pt_BR':li.dataset.locale;
if(t===locale){close();return;}
var p=location.pathname;
if(locale!=='en')p=p.replace(new RegExp('/'+locale+'/'),'/');
if(t!=='en'){var ls=p.lastIndexOf('/');p=p.substring(0,ls+1)+t+p.substring(ls);}
localStorage.setItem('preferred-locale',t);
window.location.href=p+location.search;
});
});
})();
</script>
</body>
</html>
+151
View File
@@ -0,0 +1,151 @@
:root {
--bg-dark: #0a0a0f;
--bg-card: #12121a;
--neon-cyan: #00f5ff;
--neon-magenta: #ff00ff;
--neon-purple: #b366ff;
--text-primary: #ffffff;
--text-secondary: #a0a0b0;
--border-color: #2a2a3a;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Space Grotesk', sans-serif;
background: var(--bg-dark);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
}
body::before {
content: '';
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background:
linear-gradient(90deg, rgba(0,245,255,0.03) 1px, transparent 1px),
linear-gradient(rgba(0,245,255,0.03) 1px, transparent 1px);
background-size: 50px 50px;
pointer-events: none;
z-index: -1;
}
.container { max-width: 1200px; margin: 0 auto; padding: 0 2rem; }
.hero { padding: 6rem 0 4rem; text-align: center; }
.hero-badge {
display: inline-block; padding: 0.5rem 1rem;
background: rgba(0,245,255,0.1); border: 1px solid var(--neon-cyan);
border-radius: 50px; font-size: 0.875rem; color: var(--neon-cyan);
margin-bottom: 2rem; font-family: 'JetBrains Mono', monospace;
}
.hero h1 {
font-size: clamp(2.5rem, 6vw, 4rem); font-weight: 700; margin-bottom: 1rem;
background: linear-gradient(135deg, var(--neon-cyan), var(--neon-magenta));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.hero-subtitle { font-size: 1.25rem; color: var(--text-secondary); max-width: 600px; margin: 0 auto 2rem; }
.hero-cta { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
.btn {
display: inline-flex; align-items: center; gap: 0.5rem;
padding: 1rem 2rem; border-radius: 8px; font-weight: 600;
text-decoration: none; transition: all 0.3s ease; font-size: 1rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--neon-cyan), var(--neon-purple));
color: var(--bg-dark);
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 40px rgba(0,245,255,0.3); }
.btn-secondary { background: transparent; border: 2px solid var(--border-color); color: var(--text-primary); }
.btn-secondary:hover { border-color: var(--neon-cyan); color: var(--neon-cyan); }
.stats { display: flex; justify-content: center; gap: 3rem; margin-top: 4rem; flex-wrap: wrap; }
.stat { text-align: center; }
.stat-value { font-size: 2rem; font-weight: 700; color: var(--neon-cyan); font-family: 'JetBrains Mono', monospace; }
.stat-label { font-size: 0.875rem; color: var(--text-secondary); margin-top: 0.25rem; }
.features { padding: 4rem 0; }
.section-title { font-size: 2rem; text-align: center; margin-bottom: 3rem; }
.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; }
.feature-card {
background: var(--bg-card); border: 1px solid var(--border-color);
border-radius: 12px; padding: 2rem; transition: all 0.3s ease;
}
.feature-card:hover { border-color: var(--neon-cyan); transform: translateY(-4px); }
.feature-icon { font-size: 2.5rem; margin-bottom: 1rem; }
.feature-card h3 { font-size: 1.25rem; margin-bottom: 0.5rem; }
.feature-card p { color: var(--text-secondary); font-size: 0.95rem; }
.workshop { padding: 4rem 0; }
.parts-list { display: flex; flex-direction: column; gap: 1rem; max-width: 800px; margin: 0 auto; }
.part-card {
display: flex; align-items: center; gap: 1.5rem;
background: var(--bg-card); border: 1px solid var(--border-color);
border-radius: 12px; padding: 1.5rem; text-decoration: none;
color: inherit; transition: all 0.3s ease;
}
.part-card:hover { border-color: var(--neon-magenta); background: rgba(255,0,255,0.05); }
.part-number {
width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, var(--neon-cyan), var(--neon-magenta));
border-radius: 12px; font-weight: 700; font-size: 1.25rem;
color: var(--bg-dark); flex-shrink: 0;
}
.part-content { flex: 1; }
.part-content h3 { font-size: 1.1rem; margin-bottom: 0.25rem; }
.part-content p { color: var(--text-secondary); font-size: 0.9rem; }
.part-time {
font-family: 'JetBrains Mono', monospace; font-size: 0.875rem;
color: var(--neon-cyan); padding: 0.25rem 0.75rem;
background: rgba(0,245,255,0.1); border-radius: 50px;
}
.prereqs { padding: 4rem 0; background: var(--bg-card); }
.prereqs-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem; max-width: 800px; margin: 0 auto;
}
.prereq-item { display: flex; align-items: center; gap: 0.75rem; padding: 1rem; background: var(--bg-dark); border-radius: 8px; }
.prereq-check { color: var(--neon-cyan); font-size: 1.25rem; }
footer { padding: 3rem 0; text-align: center; border-top: 1px solid var(--border-color); }
.footer-links { display: flex; justify-content: center; gap: 2rem; margin-bottom: 1rem; flex-wrap: wrap; }
.footer-links a { color: var(--text-secondary); text-decoration: none; transition: color 0.3s ease; }
.footer-links a:hover { color: var(--neon-cyan); }
.footer-credit { color: var(--text-secondary); font-size: 0.875rem; }
@media (max-width: 768px) {
.hero { padding: 4rem 0 3rem; }
.stats { gap: 2rem; }
.part-card { flex-direction: column; text-align: center; }
.part-time { margin-top: 0.5rem; }
}
.hero-also {
margin-top: 1rem;
font-size: 0.95rem;
color: var(--text-secondary);
}
.hero-also a {
color: var(--neon-cyan);
text-decoration: none;
transition: color 0.3s ease;
}
.hero-also a:hover {
color: var(--neon-magenta);
text-decoration: underline;
}
/* --- Locale Picker --- */
.locale-picker { position: relative; display: inline-flex; }
.locale-toggle { display: flex; align-items: center; justify-content: center; padding: 8px; border-radius: 10px; border: 1px solid var(--border-color); background: var(--bg-card); color: var(--text-secondary); font-size: 1rem; line-height: 1; transition: all 0.2s; cursor: pointer; }
.locale-toggle:hover { background: var(--neon-cyan); color: #fff; border-color: var(--neon-cyan); }
.locale-picker ul { display: none; position: absolute; top: 100%; right: 0; margin: 4px 0 0; padding: 4px 0; list-style: none; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 100; min-width: 180px; }
.locale-picker li { padding: 8px 16px; cursor: pointer; font-size: 0.9rem; color: var(--text-primary); white-space: nowrap; transition: background 0.15s; }
.locale-picker li:hover { background: var(--neon-cyan); color: #fff; }
.locale-picker li.active { font-weight: 600; color: var(--neon-cyan); }
.locale-picker li.active:hover { color: #fff; }
+24
View File
@@ -0,0 +1,24 @@
(function() {
const saved = localStorage.getItem('theme');
if (saved) {
document.documentElement.setAttribute('data-theme', saved);
}
})();
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
const next = current === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
updateToggleIcon();
}
function updateToggleIcon() {
const btn = document.querySelector('.theme-toggle');
if (!btn) return;
const isLight = document.documentElement.getAttribute('data-theme') === 'light';
btn.innerHTML = isLight ? '🌙 Dark' : '☀️ Light';
}
document.addEventListener('DOMContentLoaded', updateToggleIcon);