mirror of
https://github.com/andwati/copilot-dev-days.git
synced 2026-10-18 02:02:52 +00:00
Initial commit
This commit is contained in:
@@ -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>
|
||||
@@ -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 ``;
|
||||
});
|
||||
|
||||
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
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 ``;
|
||||
});
|
||||
|
||||
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
@@ -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
@@ -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 ``;
|
||||
});
|
||||
|
||||
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
@@ -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; }
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user