{"owner":"zarazhangrui","repo":"codebase-to-course","hasSkills":true,"totalSkillsCount":5,"totalTokensCount":15642,"categories":["anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["references/interactive-elements.md","references/module-brief-template.md","references/content-philosophy.md","references/design-system.md","references/gotchas.md"],"skills":{"references/interactive-elements.md":"# Interactive Elements Reference\n\nImplementation patterns for every interactive element type used in courses. Pick the elements that best serve each module's teaching goal.\n\n> **Architecture note:** All CSS and JavaScript for these elements live in `references/styles.css` and `references/main.js`, which are copied verbatim into every course directory. When writing module HTML files, use only the HTML patterns below — do **not** inline `<style>` or `<script>` tags for these elements. The engines in `main.js` auto-initialize on page load by scanning for the relevant class names and `data-*` attributes described here.\n\n## Table of Contents\n1. [Code ↔ English Translation Blocks](#code--english-translation-blocks)\n2. [Multiple-Choice Quizzes](#multiple-choice-quizzes)\n3. [Drag-and-Drop Matching](#drag-and-drop-matching)\n4. [Group Chat Animation](#group-chat-animation)\n5. [Message Flow / Data Flow Animation](#message-flow--data-flow-animation)\n6. [Interactive Architecture Diagram](#interactive-architecture-diagram)\n7. [Layer Toggle Demo](#layer-toggle-demo)\n8. [\"Spot the Bug\" Challenge](#spot-the-bug-challenge)\n9. [Scenario Quiz](#scenario-quiz)\n10. [Callout Boxes](#callout-boxes)\n11. [Pattern/Feature Cards](#patternfeature-cards)\n12. [Flow Diagrams](#flow-diagrams)\n13. [Permission/Config Badges](#permissionconfig-badges)\n14. [Glossary Tooltips](#glossary-tooltips)\n15. [Visual File Tree](#visual-file-tree)\n16. [Icon-Label Rows](#icon-label-rows)\n17. [Numbered Step Cards](#numbered-step-cards)\n\n---\n\n## Code ↔ English Translation Blocks\n\nThe most important teaching element. Shows real code from the project on the left and a plain English translation on the right, line by line.\n\n**HTML:**\n```html\n<div class=\"translation-block animate-in\">\n  <div class=\"translation-code\">\n    <span class=\"translation-label\">CODE</span>\n    <pre><code>\n<span class=\"code-line\"><span class=\"code-keyword\">const</span> response = <span class=\"code-keyword\">await</span> <span class=\"code-function\">fetch</span>(url, {</span>\n<span class=\"code-line\">  <span class=\"code-property\">method</span>: <span class=\"code-string\">'POST'</span>,</span>\n<span class=\"code-line\">  <span class=\"code-property\">headers</span>: { <span class=\"code-string\">'Authorization'</span>: apiKey }</span>\n<span class=\"code-line\">});</span>\n    </code></pre>\n  </div>\n  <div class=\"translation-english\">\n    <span class=\"translation-label\">PLAIN ENGLISH</span>\n    <div class=\"translation-lines\">\n      <p class=\"tl\">Send a request to the URL and wait for a response...</p>\n      <p class=\"tl\">We're sending data (POST), not just asking for it (GET)...</p>\n      <p class=\"tl\">Include our API key so the server knows who we are...</p>\n      <p class=\"tl\">End of the request setup.</p>\n    </div>\n  </div>\n</div>\n```\n\n**CSS:**\n```css\n.translation-block {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 0;\n  border-radius: var(--radius-md);\n  overflow: hidden;\n  box-shadow: var(--shadow-md);\n  margin: var(--space-8) 0;\n}\n.translation-code {\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-6);\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  position: relative;\n  overflow-x: hidden;  /* NO horizontal scrollbar — ever */\n}\n.translation-code pre,\n.translation-code code {\n  white-space: pre-wrap;       /* wrap long lines instead of scrolling */\n  word-break: break-word;      /* break mid-word if needed */\n  overflow-x: hidden;\n}\n.translation-english {\n  background: var(--color-surface-warm);\n  padding: var(--space-6);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  border-left: 3px solid var(--color-accent);\n}\n.translation-label {\n  position: absolute;\n  top: var(--space-2);\n  right: var(--space-3);\n  font-size: var(--text-xs);\n  text-transform: uppercase;\n  letter-spacing: 0.1em;\n  opacity: 0.5;\n}\n.translation-english .translation-label {\n  color: var(--color-text-muted);\n}\n/* Responsive: stack vertically on mobile */\n@media (max-width: 768px) {\n  .translation-block { grid-template-columns: 1fr; }\n  .translation-english { border-left: none; border-top: 3px solid var(--color-accent); }\n}\n```\n\n**Rules:**\n- Each English line should correspond to 1-2 code lines\n- Use conversational language, not technical jargon\n- Highlight the \"why\" not just the \"what\" — e.g., \"Include our API key so the server knows who we are\" not \"Set the Authorization header\"\n\n---\n\n## Multiple-Choice Quizzes\n\nFor testing understanding with instant feedback. Each question has options, one correct answer, and per-question explanations.\n\n**Wiring:** `main.js` exposes `window.selectOption(btn)`, `window.checkQuiz(containerId)`, and `window.resetQuiz(containerId)`. Call them via `onclick`. Per-question explanations go in `data-explanation-right` and `data-explanation-wrong` on the `.quiz-question-block`.\n\n**HTML:**\n```html\n<div class=\"quiz-container\" id=\"quiz-module3\">\n  <div class=\"quiz-question-block\"\n       data-correct=\"option-b\"\n       data-explanation-right=\"Exactly — because X is responsible for Y in this architecture.\"\n       data-explanation-wrong=\"Not quite. Think about where Y lives in the codebase...\">\n    <h3 class=\"quiz-question\">Question text here?</h3>\n    <div class=\"quiz-options\">\n      <button class=\"quiz-option\" data-value=\"option-a\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer A</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-b\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer B (correct)</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-c\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer C</span>\n      </button>\n    </div>\n    <div class=\"quiz-feedback\"></div>\n  </div>\n\n  <button class=\"quiz-check-btn\" onclick=\"checkQuiz('quiz-module3')\">Check Answers</button>\n  <button class=\"quiz-reset-btn\" onclick=\"resetQuiz('quiz-module3')\">Try Again</button>\n</div>\n```\n\n**CSS for quiz states:**\n```css\n.quiz-option {\n  display: flex; align-items: center; gap: var(--space-3);\n  padding: var(--space-3) var(--space-4);\n  border: 2px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  background: var(--color-surface);\n  cursor: pointer; width: 100%;\n  transition: border-color var(--duration-fast), background var(--duration-fast);\n}\n.quiz-option:hover { border-color: var(--color-accent-muted); }\n.quiz-option.selected { border-color: var(--color-accent); background: var(--color-accent-light); }\n.quiz-option.correct { border-color: var(--color-success); background: var(--color-success-light); }\n.quiz-option.incorrect { border-color: var(--color-error); background: var(--color-error-light); }\n.quiz-option-radio {\n  width: 18px; height: 18px; border-radius: 50%;\n  border: 2px solid var(--color-border);\n  transition: all var(--duration-fast);\n}\n.quiz-option.selected .quiz-option-radio {\n  border-color: var(--color-accent);\n  background: var(--color-accent);\n  box-shadow: inset 0 0 0 3px white;\n}\n.quiz-feedback {\n  max-height: 0; overflow: hidden; opacity: 0;\n  transition: max-height var(--duration-normal), opacity var(--duration-normal);\n}\n.quiz-feedback.show { max-height: 200px; opacity: 1; padding: var(--space-3); margin-top: var(--space-2); border-radius: var(--radius-sm); }\n.quiz-feedback.success { background: var(--color-success-light); color: var(--color-success); }\n.quiz-feedback.error { background: var(--color-error-light); color: var(--color-error); }\n```\n\n---\n\n## Drag-and-Drop Matching\n\nFor matching concepts to descriptions. Supports both mouse (HTML5 Drag API) and touch.\n\n**HTML:**\n```html\n<div class=\"dnd-container\">\n  <div class=\"dnd-chips\">\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-a\">Actor A</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-b\">Actor B</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-c\">Actor C</div>\n  </div>\n  <div class=\"dnd-zones\">\n    <div class=\"dnd-zone\" data-correct=\"actor-a\">\n      <p class=\"dnd-zone-label\">Description for Actor A</p>\n      <div class=\"dnd-zone-target\">Drop here</div>\n    </div>\n    <!-- more zones -->\n  </div>\n  <button onclick=\"checkDnD()\">Check Matches</button>\n  <button onclick=\"resetDnD()\">Reset</button>\n</div>\n```\n\n**JS (mouse + touch):**\n```javascript\n// MOUSE: HTML5 Drag API\nchips.forEach(chip => {\n  chip.addEventListener('dragstart', (e) => {\n    e.dataTransfer.setData('text/plain', chip.dataset.answer);\n    chip.classList.add('dragging');\n  });\n  chip.addEventListener('dragend', () => chip.classList.remove('dragging'));\n});\n\nzones.forEach(zone => {\n  const target = zone.querySelector('.dnd-zone-target');\n  target.addEventListener('dragover', (e) => { e.preventDefault(); target.classList.add('drag-over'); });\n  target.addEventListener('dragleave', () => target.classList.remove('drag-over'));\n  target.addEventListener('drop', (e) => {\n    e.preventDefault();\n    target.classList.remove('drag-over');\n    const answer = e.dataTransfer.getData('text/plain');\n    const chip = document.querySelector(`[data-answer=\"${answer}\"]`);\n    target.textContent = chip.textContent;\n    target.dataset.placed = answer;\n    chip.classList.add('placed');\n  });\n});\n\n// TOUCH: Custom implementation (HTML5 drag doesn't work on mobile)\nchips.forEach(chip => {\n  chip.addEventListener('touchstart', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    const clone = chip.cloneNode(true);\n    clone.classList.add('touch-ghost');\n    clone.style.cssText = `position:fixed; z-index:1000; pointer-events:none;\n      left:${touch.clientX - 40}px; top:${touch.clientY - 20}px;`;\n    document.body.appendChild(clone);\n    chip._ghost = clone;\n    chip._answer = chip.dataset.answer;\n  }, { passive: false });\n\n  chip.addEventListener('touchmove', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    if (chip._ghost) {\n      chip._ghost.style.left = (touch.clientX - 40) + 'px';\n      chip._ghost.style.top = (touch.clientY - 20) + 'px';\n    }\n    // Highlight zone under finger\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    zones.forEach(z => z.querySelector('.dnd-zone-target').classList.remove('drag-over'));\n    if (el && el.closest('.dnd-zone-target')) {\n      el.closest('.dnd-zone-target').classList.add('drag-over');\n    }\n  }, { passive: false });\n\n  chip.addEventListener('touchend', (e) => {\n    if (chip._ghost) { chip._ghost.remove(); chip._ghost = null; }\n    const touch = e.changedTouches[0];\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    if (el && el.closest('.dnd-zone-target')) {\n      const target = el.closest('.dnd-zone-target');\n      target.textContent = chip.textContent;\n      target.dataset.placed = chip._answer;\n      chip.classList.add('placed');\n    }\n  });\n});\n```\n\n---\n\n## Group Chat Animation\n\niMessage/WeChat-style chat showing components \"talking\" to each other. Messages appear one by one with typing indicators.\n\n**Wiring:** `main.js` auto-initializes every `.chat-window` on page load. Give each chat window a unique `id`. Control buttons need these classes: `.chat-next-btn`, `.chat-all-btn`, `.chat-reset-btn`. The typing indicator avatar element should have `id=\"{chatWindowId}-typing-avatar\"` or simply be the first `.chat-avatar` inside `.chat-typing`.\n\n**HTML:**\n```html\n<div class=\"chat-window\" id=\"chat-module2\">\n  <div class=\"chat-messages\">\n    <div class=\"chat-message\" data-msg=\"0\" data-sender=\"actor-a\" style=\"display:none\">\n      <div class=\"chat-avatar\" style=\"background: var(--color-actor-1)\">A</div>\n      <div class=\"chat-bubble\">\n        <span class=\"chat-sender\" style=\"color: var(--color-actor-1)\">Actor A</span>\n        <p>Hey Background, I need the data for this item.</p>\n      </div>\n    </div>\n    <!-- more messages... -->\n  </div>\n\n  <div class=\"chat-typing\" id=\"chat-typing\" style=\"display:none\">\n    <div class=\"chat-avatar\" id=\"typing-avatar\">?</div>\n    <div class=\"chat-typing-dots\">\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n    </div>\n  </div>\n\n  <div class=\"chat-controls\">\n    <button class=\"btn chat-next-btn\">Next Message</button>\n    <button class=\"btn chat-all-btn\">Play All</button>\n    <button class=\"btn chat-reset-btn\">Replay</button>\n    <span class=\"chat-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for typing dots:**\n```css\n.typing-dot {\n  width: 8px; height: 8px; border-radius: 50%;\n  background: var(--color-text-muted);\n  animation: typingBounce 1.4s infinite;\n}\n.typing-dot:nth-child(2) { animation-delay: 0.2s; }\n.typing-dot:nth-child(3) { animation-delay: 0.4s; }\n@keyframes typingBounce {\n  0%, 60%, 100% { transform: translateY(0); }\n  30% { transform: translateY(-6px); }\n}\n```\n\n---\n\n## Message Flow / Data Flow Animation\n\nStep-by-step visualization of data moving between components. User clicks \"Next Step\" to advance.\n\n**Wiring:** `main.js` auto-initializes every `.flow-animation` on page load. Pass steps as JSON in `data-steps`. Each step object: `{ highlight: \"flow-actor-id\", label: \"description\", packet: true, from: \"actor-id-suffix\", to: \"actor-id-suffix\" }`. Actor element IDs must be `flow-actor-1`, `flow-actor-2`, etc. Control buttons need classes `.flow-next-btn` and `.flow-reset-btn`.\n\n> **⚠️ Single quotes in step labels will break parsing.** The `data-steps` attribute is delimited by single quotes (`data-steps='[...]'`), so any single quote inside a label (e.g. `\"the user's request\"`) will terminate the attribute early and cause `JSON.parse` to fail silently — the entire animation will stop working. Either avoid apostrophes in labels, replace them with `&apos;`, or rewrite the attribute using double-quote delimiters with escaped inner quotes (`data-steps=\"[{\\\"label\\\":\\\"...\\\"}]\"`).\n\n**HTML:**\n```html\n<div class=\"flow-animation\" data-steps='[\n  {\"highlight\":\"flow-actor-1\",\"label\":\"User clicks the button\"},\n  {\"highlight\":\"flow-actor-1\",\"label\":\"Frontend sends request\",\"packet\":true,\"from\":\"actor-1\",\"to\":\"actor-2\"},\n  {\"highlight\":\"flow-actor-2\",\"label\":\"Backend calls the database\",\"packet\":true,\"from\":\"actor-2\",\"to\":\"actor-3\"}\n]'>\n  <div class=\"flow-actors\">\n    <div class=\"flow-actor\" id=\"flow-actor-1\">\n      <div class=\"flow-actor-icon\">A</div>\n      <span>Actor 1</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-2\">\n      <div class=\"flow-actor-icon\">B</div>\n      <span>Actor 2</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-3\">\n      <div class=\"flow-actor-icon\">C</div>\n      <span>Actor 3</span>\n    </div>\n  </div>\n\n  <div class=\"flow-packet\" id=\"flow-packet\"></div>\n\n  <div class=\"flow-step-label\" id=\"flow-label\">Click \"Next Step\" to begin</div>\n\n  <div class=\"flow-controls\">\n    <button class=\"btn flow-next-btn\">Next Step</button>\n    <button class=\"btn flow-reset-btn\">Restart</button>\n    <span class=\"flow-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for active actor glow:**\n```css\n.flow-actor.active {\n  box-shadow: 0 0 0 3px var(--color-accent), 0 0 20px rgba(217, 79, 48, 0.2);\n  transform: scale(1.05);\n  transition: all var(--duration-normal) var(--ease-out);\n}\n```\n\n---\n\n## Interactive Architecture Diagram\n\nFull-system diagram where hovering/clicking a component shows a description tooltip.\n\n**HTML:**\n```html\n<div class=\"arch-diagram\">\n  <div class=\"arch-zone arch-zone-browser\">\n    <h4 class=\"arch-zone-label\">Browser</h4>\n    <div class=\"arch-component\" data-desc=\"Injects UI into the web page, reads DOM, captures user actions\"\n         onclick=\"showArchDesc(this)\">\n      <div class=\"arch-icon\">📄</div>\n      <span>Component A</span>\n    </div>\n    <!-- more components -->\n  </div>\n  <div class=\"arch-zone arch-zone-external\">\n    <h4 class=\"arch-zone-label\">External Services</h4>\n    <!-- API cards -->\n  </div>\n  <div class=\"arch-description\" id=\"arch-desc\">Click any component to learn what it does</div>\n</div>\n```\n\n---\n\n## Layer Toggle Demo\n\nShows how different layers (e.g., HTML/CSS/JS, or data/logic/UI) build on each other. Three tabs switch between views.\n\n**HTML:**\n```html\n<div class=\"layer-demo\">\n  <div class=\"layer-tabs\">\n    <button class=\"layer-tab active\" onclick=\"showLayer('html')\">HTML</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('css')\">+ CSS</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('js')\">+ JS</button>\n  </div>\n  <div class=\"layer-viewport\">\n    <div class=\"layer\" id=\"layer-html\" style=\"display:block\">\n      <!-- Raw unstyled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-css\" style=\"display:none\">\n      <!-- Styled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-js\" style=\"display:none\">\n      <!-- Interactive version -->\n    </div>\n  </div>\n  <p class=\"layer-description\" id=\"layer-desc\">This is the raw HTML...</p>\n</div>\n```\n\n---\n\n## \"Spot the Bug\" Challenge\n\nShow code with a deliberate bug. User clicks the buggy line. Reveal explains the issue.\n\n**HTML:**\n```html\n<div class=\"bug-challenge\">\n  <h3>Find the bug in this code:</h3>\n  <div class=\"bug-code\">\n    <div class=\"bug-line\" data-line=\"1\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">1</span>\n      <code>chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"2\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">2</span>\n      <code>  if (msg.action === 'fetchData') {</code>\n    </div>\n    <div class=\"bug-line bug-target\" data-line=\"3\" onclick=\"checkBugLine(this, true)\">\n      <span class=\"line-num\">3</span>\n      <code>    fetch(url).then(r => r.json()).then(data => sendResponse(data));</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"4\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">4</span>\n      <code>  }</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"5\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">5</span>\n      <code>});</code>\n    </div>\n  </div>\n  <div class=\"bug-feedback\" id=\"bug-feedback\"></div>\n</div>\n```\n\n**JS:**\n```javascript\nwindow.checkBugLine = function(el, isCorrect) {\n  const feedback = el.closest('.bug-challenge').querySelector('.bug-feedback');\n  if (isCorrect) {\n    el.classList.add('correct');\n    feedback.innerHTML = '<strong>Found it!</strong> The listener uses an async operation (fetch) but doesn\\'t return true. Chrome closes the message channel before the response can be sent. Fix: add <code>return true;</code> at the end.';\n    feedback.className = 'bug-feedback show success';\n  } else {\n    el.classList.add('incorrect');\n    feedback.innerHTML = 'Not this line — look for where the async timing might cause problems...';\n    feedback.className = 'bug-feedback show error';\n    setTimeout(() => { el.classList.remove('incorrect'); feedback.className = 'bug-feedback'; }, 2000);\n  }\n};\n```\n\n---\n\n## Scenario Quiz\n\n\"What would a senior engineer do?\" — situational questions with explanations.\n\nSame HTML/CSS/JS pattern as Multiple-Choice Quizzes, but with longer scenario descriptions and more detailed explanations. Wrap each question in a scenario context block:\n\n```html\n<div class=\"scenario-block\">\n  <div class=\"scenario-context\">\n    <span class=\"scenario-label\">Scenario</span>\n    <p>Your app processes a 3-hour podcast transcript. The API has a 16,000 token limit. What do you do?</p>\n  </div>\n  <!-- quiz-options here -->\n</div>\n```\n\n---\n\n## Callout Boxes\n\n\"Aha!\" moments — universal CS insights. Max 2 per module.\n\n```html\n<div class=\"callout callout-accent\">\n  <div class=\"callout-icon\">💡</div>\n  <div class=\"callout-content\">\n    <strong class=\"callout-title\">Key Insight</strong>\n    <p>This pattern — splitting responsibilities into focused roles — is one of the most important ideas in software engineering. Engineers call it \"separation of concerns.\"</p>\n  </div>\n</div>\n```\n\n**Variants:**\n- `callout-accent`: vermillion left border, light accent background (for CS insights)\n- `callout-info`: teal left border, light info background (for \"good to know\")\n- `callout-warning`: red left border, light error background (for common mistakes)\n\n---\n\n## Pattern/Feature Cards\n\nGrid of cards highlighting engineering patterns, tech stack components, or key concepts.\n\n```html\n<div class=\"pattern-cards\">\n  <div class=\"pattern-card\" style=\"border-top: 3px solid var(--color-actor-1)\">\n    <div class=\"pattern-icon\" style=\"background: var(--color-actor-1)\">🔄</div>\n    <h4 class=\"pattern-title\">Caching</h4>\n    <p class=\"pattern-desc\">Store results to avoid redundant work — like keeping leftovers instead of cooking a new meal every time.</p>\n  </div>\n  <!-- more cards -->\n</div>\n```\n\n```css\n.pattern-cards {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n  gap: var(--space-4);\n}\n.pattern-card {\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  padding: var(--space-6);\n  box-shadow: var(--shadow-sm);\n  transition: transform var(--duration-normal) var(--ease-out), box-shadow var(--duration-normal);\n}\n.pattern-card:hover {\n  transform: translateY(-4px);\n  box-shadow: var(--shadow-md);\n}\n```\n\n---\n\n## Flow Diagrams\n\n**Horizontal flow (desktop):**\n```html\n<div class=\"flow-steps\">\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">1</div>\n    <p>User clicks button</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">2</div>\n    <p>Component A detects click</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <!-- more steps -->\n</div>\n```\n\nArrows rotate to `↓` on mobile via CSS transform.\n\n---\n\n## Permission/Config Badges\n\nFor annotating config files, permissions, or settings:\n\n```html\n<div class=\"badge-list\">\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">storage</code>\n    <span class=\"badge-desc\">Save data between sessions (like browser bookmarks)</span>\n  </div>\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">activeTab</code>\n    <span class=\"badge-desc\">Access the currently open tab (only when the user clicks)</span>\n  </div>\n</div>\n```\n\n```css\n.badge-item {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-3) var(--space-4);\n  border: 1px solid var(--color-border-light);\n  border-radius: var(--radius-sm);\n  transition: border-color var(--duration-fast);\n}\n.badge-item:hover { border-color: var(--color-accent-muted); }\n.badge-code {\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  background: var(--color-bg-code);\n  color: #CBA6F7;\n  padding: var(--space-1) var(--space-3);\n  border-radius: var(--radius-sm);\n  white-space: nowrap;\n}\n```\n\n---\n\n## Glossary Tooltips\n\nThe most important accessibility feature for non-technical learners. Any technical term in the course text should be wrapped in a tooltip that shows a plain-English definition on hover (desktop) or tap (mobile). The learner never has to leave the page or Google anything.\n\n**HTML — mark up terms inline:**\n```html\n<p>The extension uses a\n  <span class=\"term\" data-definition=\"A service worker is a background script that runs independently of the web page — like a behind-the-scenes assistant that's always on, even when you're not looking at the page.\">service worker</span>\n  to handle API calls.\n</p>\n```\n\n**CSS:**\n```css\n.term {\n  border-bottom: 1.5px dashed var(--color-accent-muted);\n  cursor: pointer;    /* NOT cursor: help — pointer feels clickable and inviting */\n  position: relative;\n}\n.term:hover, .term.active {\n  border-bottom-color: var(--color-accent);\n  color: var(--color-accent);\n}\n\n/* The tooltip bubble — uses position: fixed and is appended to document.body\n   via JS so it is NEVER clipped by ancestor overflow: hidden containers\n   (like translation blocks). See JS section below for positioning logic. */\n.term-tooltip {\n  position: fixed;        /* CRITICAL: fixed, not absolute — prevents clipping */\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-3) var(--space-4);\n  border-radius: var(--radius-sm);\n  font-size: var(--text-sm);\n  font-family: var(--font-body);\n  line-height: var(--leading-normal);\n  width: max(200px, min(320px, 80vw));\n  box-shadow: var(--shadow-lg);\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity var(--duration-fast);\n  z-index: 10000;        /* Above everything, including nav */\n}\n/* Arrow pointing down */\n.term-tooltip::after {\n  content: '';\n  position: absolute;\n  top: 100%;\n  left: 50%;\n  transform: translateX(-50%);\n  border: 6px solid transparent;\n  border-top-color: var(--color-bg-code);\n}\n.term-tooltip.visible {\n  opacity: 1;\n}\n\n/* If tooltip goes off-screen top, flip to below */\n.term-tooltip.flip {\n  bottom: auto;\n  top: calc(100% + 8px);\n}\n.term-tooltip.flip::after {\n  top: auto;\n  bottom: 100%;\n  border-top-color: transparent;\n  border-bottom-color: var(--color-bg-code);\n}\n```\n\n**JS — position: fixed tooltips appended to body (never clipped by overflow):**\n```javascript\n// Tooltip container — appended to body so it's never clipped\nlet activeTooltip = null;\n\nfunction positionTooltip(term, tip) {\n  const rect = term.getBoundingClientRect();\n  const tipWidth = 300; // approximate\n  let left = rect.left + rect.width / 2 - tipWidth / 2;\n  // Clamp to viewport\n  left = Math.max(8, Math.min(left, window.innerWidth - tipWidth - 8));\n\n  // Try above first\n  let top = rect.top - 8;\n  tip.style.left = left + 'px';\n\n  // Position above by default, flip below if no room\n  document.body.appendChild(tip);\n  const tipHeight = tip.offsetHeight;\n  if (rect.top - tipHeight - 8 < 0) {\n    // Flip below\n    tip.style.top = (rect.bottom + 8) + 'px';\n    tip.classList.add('flip');\n  } else {\n    tip.style.top = (rect.top - tipHeight - 8) + 'px';\n    tip.classList.remove('flip');\n  }\n}\n\ndocument.querySelectorAll('.term').forEach(term => {\n  const tip = document.createElement('span');\n  tip.className = 'term-tooltip';\n  tip.textContent = term.dataset.definition;\n\n  // Hover for desktop\n  term.addEventListener('mouseenter', () => {\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    positionTooltip(term, tip);\n    requestAnimationFrame(() => tip.classList.add('visible'));\n    activeTooltip = tip;\n  });\n\n  term.addEventListener('mouseleave', () => {\n    tip.classList.remove('visible');\n    setTimeout(() => { if (!tip.classList.contains('visible')) tip.remove(); }, 150);\n    activeTooltip = null;\n  });\n\n  // Tap for mobile\n  term.addEventListener('click', (e) => {\n    e.stopPropagation();\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    if (tip.classList.contains('visible')) {\n      tip.classList.remove('visible');\n      tip.remove();\n      activeTooltip = null;\n    } else {\n      positionTooltip(term, tip);\n      requestAnimationFrame(() => tip.classList.add('visible'));\n      activeTooltip = tip;\n    }\n  });\n});\n\n// Close tooltips when clicking elsewhere\ndocument.addEventListener('click', () => {\n  if (activeTooltip) {\n    activeTooltip.classList.remove('visible');\n    activeTooltip.remove();\n    activeTooltip = null;\n  }\n});\n```\n\n**Rules:**\n- Mark up EVERY technical term on first use in each module (API, DOM, callback, async, endpoint, middleware, etc.)\n- Keep definitions to 1-2 sentences max, in everyday language\n- Use a metaphor in the definition when it helps — e.g., \"A **callback** is like leaving your phone number at a restaurant so they can call you when your table is ready\"\n- Don't mark the same term twice within the same screen — only on first appearance per module\n- The dashed underline should be subtle enough not to distract but visible enough that curious learners discover it\n\n---\n\n## Visual File Tree\n\nUse instead of paragraphs listing \"this folder does X, that folder does Y.\" Much easier to scan.\n\n```html\n<div class=\"file-tree\">\n  <div class=\"ft-folder open\">\n    <span class=\"ft-name\">app/</span>\n    <span class=\"ft-desc\">Pages and API routes</span>\n    <div class=\"ft-children\">\n      <div class=\"ft-folder\">\n        <span class=\"ft-name\">api/</span>\n        <span class=\"ft-desc\">Backend endpoints the frontend calls</span>\n      </div>\n      <div class=\"ft-file\">\n        <span class=\"ft-name\">layout.tsx</span>\n        <span class=\"ft-desc\">The shell that wraps every page</span>\n      </div>\n    </div>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">components/</span>\n    <span class=\"ft-desc\">Reusable UI building blocks</span>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">lib/</span>\n    <span class=\"ft-desc\">Shared logic and utilities</span>\n  </div>\n</div>\n```\n\n```css\n.file-tree { font-family: var(--font-mono); font-size: var(--text-sm); }\n.ft-folder, .ft-file {\n  padding: var(--space-2) var(--space-3);\n  border-left: 2px solid var(--color-border-light);\n  margin-left: var(--space-4);\n}\n.ft-folder > .ft-name { color: var(--color-accent); font-weight: 600; }\n.ft-folder > .ft-name::before { content: '📁 '; }\n.ft-file > .ft-name::before { content: '📄 '; }\n.ft-desc {\n  color: var(--color-text-secondary);\n  font-family: var(--font-body);\n  margin-left: var(--space-2);\n  font-size: var(--text-xs);\n}\n.ft-children { margin-left: var(--space-4); }\n```\n\n---\n\n## Icon-Label Rows\n\nFor listing components, features, or concepts visually. Replaces bullet-point paragraphs.\n\n```html\n<div class=\"icon-rows\">\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-1)\">🖥️</div>\n    <div>\n      <strong>Frontend (Next.js)</strong>\n      <p>What the user sees and interacts with</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-2)\">⚡</div>\n    <div>\n      <strong>API Routes</strong>\n      <p>Backend logic that runs on the server</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-3)\">🗄️</div>\n    <div>\n      <strong>Database (Supabase)</strong>\n      <p>Where all the data is stored permanently</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.icon-rows { display: flex; flex-direction: column; gap: var(--space-4); }\n.icon-row {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-4);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  box-shadow: var(--shadow-sm);\n}\n.icon-row p { margin: 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n.icon-circle {\n  width: 48px; height: 48px; border-radius: 50%;\n  display: flex; align-items: center; justify-content: center;\n  font-size: 1.25rem; flex-shrink: 0;\n}\n```\n\n---\n\n## Numbered Step Cards\n\nFor sequences that would otherwise be a numbered paragraph list. Visual, scannable, and each step stands alone.\n\n```html\n<div class=\"step-cards\">\n  <div class=\"step-card\">\n    <div class=\"step-num\">1</div>\n    <div class=\"step-body\">\n      <strong>User pastes a YouTube URL</strong>\n      <p>The frontend captures the URL and extracts the video ID</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">2</div>\n    <div class=\"step-body\">\n      <strong>API fetches the transcript</strong>\n      <p>A server-side route calls an external service to get the video's text</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">3</div>\n    <div class=\"step-body\">\n      <strong>AI analyzes the content</strong>\n      <p>The transcript is sent to an AI model that extracts key moments</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.step-cards { display: flex; flex-direction: column; gap: var(--space-3); }\n.step-card {\n  display: flex; align-items: flex-start; gap: var(--space-4);\n  padding: var(--space-4) var(--space-5);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  border-left: 3px solid var(--color-accent);\n  box-shadow: var(--shadow-sm);\n}\n.step-num {\n  width: 32px; height: 32px; border-radius: 50%;\n  background: var(--color-accent);\n  color: white; font-weight: 700;\n  display: flex; align-items: center; justify-content: center;\n  font-family: var(--font-display);\n  flex-shrink: 0;\n}\n.step-body p { margin: var(--space-1) 0 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n```\n","references/module-brief-template.md":"# Module Brief Template\n\n> **When to read this:** During Phase 2.5 (planning checkpoint) for complex codebases. Fill in one brief per module, save to `course-name/briefs/0N-slug.md`. Each brief gives a parallel agent everything it needs to write one module without reading the codebase or SKILL.md.\n\n---\n\n## Module N: [Title]\n\n### Teaching Arc\n- **Metaphor:** [A fresh, specific metaphor — never \"restaurant.\" See `references/content-philosophy.md` > Metaphors First]\n- **Opening hook:** [1 sentence that connects to something the learner already knows from using the app]\n- **Key insight:** [The one thing the learner should walk away understanding]\n- **\"Why should I care?\":** [How this helps them steer AI / debug / make decisions]\n\n### Code Snippets (pre-extracted)\n\nInclude the actual code the module will use in code↔English translation blocks. Copy-paste from the codebase with file path and line numbers. The writing agent will use these verbatim — it will NOT re-read the codebase.\n\nFile: src/example/file.ts (lines 12-24)\n[paste actual code here]\n\nFile: src/another/file.ts (lines 45-52)\n[paste actual code here]\n\n### Interactive Elements\n\nCheck which elements this module needs. Include enough detail for the writing agent to build them.\n\n- [ ] **Code↔English translation** — which snippet(s) from above\n- [ ] **Quiz** — [number] questions, style: [scenario / debugging / architecture / tracing]. Brief description of each question's angle.\n- [ ] **Group chat animation** — actors: [list]. Message flow summary: [who says what to whom, in what order]\n- [ ] **Data flow animation** — actors: [list]. Steps: [sequence of highlights and packet movements]\n- [ ] **Drag-and-drop** — items: [list], targets: [list]\n- [ ] **Other** — [architecture diagram, layer toggle, pattern cards, etc.]\n\n### Reference Files to Read\n\nList only the sections the writing agent needs — not the whole file.\n\n- `references/interactive-elements.md` → [section names, e.g., \"Multiple-Choice Quizzes\", \"Group Chat Animation\"]\n- `references/design-system.md` → [only if needed for specific tokens not in the brief]\n- `references/content-philosophy.md` → [always include — agent needs content rules]\n- `references/gotchas.md` → [always include — agent needs the checklist]\n\n### Connections\n\n- **Previous module:** [Title — what it covered, so this module can build on it]\n- **Next module:** [Title — what it will cover, so this module can set it up]\n- **Tone/style notes:** [Any course-wide consistency notes: accent color name, actor naming convention, etc.]\n","references/content-philosophy.md":"# Content Philosophy\n\n> **When to read this:** During Phase 2.5 (writing module briefs) and Phase 3 (writing module HTML). These principles guide every content decision — what to show, how to explain it, and how to test understanding.\n\nThese principles are what separate a great course from a generic tutorial. They should guide every content decision:\n\n### Show, Don't Tell — Aggressively Visual\nPeople's eyes glaze over text blocks. The course should feel closer to an infographic than a textbook. Follow these hard rules:\n\n**Text limits:**\n- Max **2-3 sentences** per text block. If you're writing a fourth sentence, stop and convert it into a visual instead.\n- No text block should ever be wider than the content width AND taller than ~4 lines. If it is, break it up with a visual element.\n- Every screen must be **at least 50% visual** (diagrams, code blocks, cards, animations, badges — anything that isn't a paragraph).\n\n**Convert text to visuals:**\n- A list of 3+ items → **cards with icons** (pattern cards, feature cards)\n- A sequence of steps → **flow diagram with arrows** or **numbered step cards**\n- \"Component A talks to Component B\" → **animated data flow** or **group chat visualization**\n- \"This file does X, that file does Y\" → **visual file tree with annotations** or **icon + one-liner badges**\n- Explaining what code does → **code↔English translation block** (not a paragraph *about* the code)\n- Comparing two approaches → **side-by-side columns** with visual contrast\n\n**Visual breathing room:**\n- Use generous spacing between elements (`--space-8` to `--space-12` between sections)\n- Alternate between full-width visuals and narrow text blocks to create rhythm\n- Every module should have at least one \"hero visual\" — a diagram, animation, or interactive element that dominates the screen and teaches the core concept at a glance\n\n### Code ↔ English Translations\nEvery code snippet gets a side-by-side plain English translation. Left panel: real code from the project with syntax highlighting. Right panel: line-by-line plain English explaining what each line does. This is the single most valuable teaching tool for non-technical learners.\n\n**Critical: No horizontal scrollbars on code.** All code must use `white-space: pre-wrap` so it wraps instead of scrolling. This is a course for non-technical people, not an IDE — readability beats preserving indentation structure.\n\n**Critical: Use original code exactly as-is.** Never modify, simplify, or trim code snippets from the codebase. The learner should be able to open the real file and see the exact same code they learned from — that builds trust. Instead of editing code to make it shorter, *choose* naturally short, punchy snippets (5-10 lines) from the codebase that illustrate the concept well. Every codebase has compact, self-contained moments — find those rather than butchering longer functions.\n\n### One Concept Per Screen\nNo walls of text. Each screen within a module teaches exactly one idea. If you need more space, add another screen — don't cram.\n\n### Metaphors First, Then Reality\nIntroduce every new concept with a metaphor from everyday life. Then immediately ground it: \"In our code, this looks like...\" The metaphor builds intuition; the code grounds it in reality.\n\n**Critical: No recycled metaphors.** Do NOT default to \"restaurant\" for everything — that's the #1 crutch. Each concept deserves its own metaphor that feels natural to *that specific idea*. A database is a library with a card catalog. Auth is a bouncer checking IDs. An event loop is an air traffic controller. Message passing is a postal system. API rate limiting is a nightclub with a capacity limit. Pick the metaphor that makes the concept click, not the one that's easiest to reach for. If you catch yourself using \"restaurant\" or \"kitchen\" more than once in a course, stop and rethink.\n\n### Learn by Tracing\nFollow what actually happens when the learner does something they already do every day in the app — trace the data flow end-to-end. \"You know that button you click? Here's the journey your data takes after you click it...\" This works because the learner has *already experienced the result* — now they're seeing the machinery behind it. It's like watching a behind-the-scenes documentary of a movie you loved.\n\n### Make It Memorable\nUse \"aha!\" callout boxes for universal CS insights. Use humor where natural (not forced). Give components personality — they're \"characters\" in a story, not abstract boxes on a diagram.\n\n### Glossary Tooltips — No Term Left Behind\nEvery technical term (API, DOM, callback, middleware, etc.) gets a dashed-underline tooltip on first use in each module. Hover on desktop or tap on mobile to see a 1-2 sentence plain-English definition. The learner should never have to leave the page to Google a term. This is the difference between a course that *says* it's for non-technical people and one that actually *is*.\n\n**Be extremely aggressive with tooltips.** If there is even a 1% chance a non-technical person doesn't know a word, tooltip it. This includes:\n- Software names they might not know (Blender, GIMP, Audacity, etc.)\n- Everyday developer terms (REPL, JSON, flag, CLI, API, SDK, etc.)\n- Programming concepts (function, variable, dictionary, class, module, etc.)\n- Infrastructure terms (PATH, pip, namespace, entry point, etc.)\n- Acronyms — ALWAYS tooltip acronyms on first use\n\n**The vocabulary IS the learning.** One of the key goals is for learners to acquire the precise technical vocabulary they need to communicate with AI coding agents. Each tooltip should teach the term in a way that helps the learner USE it in their own instructions — e.g., \"A **flag** is an option you add to a command to change its behavior — like adding '--json' to get structured data instead of plain text. When talking to AI, you'd say 'add a flag for verbose output.'\"\n\n**Cursor:** Use `cursor: pointer` on terms (not `cursor: help`). The question-mark cursor feels clinical — a pointer feels clickable and inviting.\n\n**Tooltip overflow fix:** Translation blocks and other containers with `overflow: hidden` will clip tooltips. To fix this, the tooltip JS must use `position: fixed` and calculate coordinates from `getBoundingClientRect()` instead of relying on CSS `position: absolute` within the container. Append tooltips to `document.body` rather than inside the term element. This ensures tooltips are never clipped by any ancestor's overflow.\n\n### Quizzes That Test Application, Not Memory\n\nThe goal of learning is practical application — being able to *do something* with what you learned. Quizzes should test whether the learner can use their knowledge to solve a new problem, not whether they can regurgitate a definition.\n\n**What to quiz (in order of value):**\n1. **\"What would you do?\" scenarios** — Present a new situation the learner hasn't seen and ask them to apply what they learned. e.g., \"You want to add a 'save to favorites' feature. Which files would you need to change?\" This is the gold standard.\n2. **Debugging scenarios** — \"A user reports X is broken. Based on what you learned, where would you look first?\" This tests whether they understood the architecture, not just memorized file names.\n3. **Architecture decisions** — \"You're building a similar app from scratch. Would you put this logic in the frontend or backend? Why?\" Tests whether they understood the *reasoning* behind design choices.\n4. **Tracing exercises** — \"When a user does X, trace the path the data takes.\" Tests whether they can follow the flow.\n\n**What NOT to quiz:**\n- Definitions (\"What does API stand for?\") — that's what the glossary tooltips are for\n- File name recall (\"Which file handles X?\") — nobody memorizes file names\n- Syntax details (\"What's the correct way to write a fetch call?\") — this isn't a coding bootcamp\n- Anything that can be answered by scrolling up and copying — that tests scrolling, not understanding\n\n**Quiz tone:**\n- Wrong answers get encouraging, non-judgmental explanations (\"Not quite — here's why...\")\n- Correct answers get brief reinforcement of the underlying principle (\"Exactly! This works because...\")\n- Never punitive, never score-focused. No \"You got 3/5!\" — the quiz is a thinking exercise, not an exam\n- Wrong answer explanations should teach something new, not just say \"wrong, the answer was B\"\n\n**How many quizzes:** One per module, placed at the end after the learner has seen all the content. 3-5 questions per quiz. Each question should make the learner pause and *think*, not just pick the obvious answer.\n\n**Deciding what concepts are worth quizzing:** Quiz the things that would actually help someone in practice — architecture understanding (\"where does this logic live and why?\"), debugging intuition (\"what would cause this symptom?\"), and decision-making (\"what's the tradeoff here?\"). If a concept won't help someone debug a problem, steer an AI assistant, or make an architectural decision, it's not worth quizzing.\n","references/design-system.md":"# Design System Reference\n\nComplete CSS design tokens for the course. Copy this entire `:root` block into the course HTML and adapt the accent color to suit the project's personality.\n\n## Table of Contents\n1. [Color Palette](#color-palette)\n2. [Typography](#typography)\n3. [Spacing & Layout](#spacing--layout)\n4. [Shadows & Depth](#shadows--depth)\n5. [Animations & Transitions](#animations--transitions)\n6. [Navigation & Progress](#navigation--progress)\n7. [Module Structure](#module-structure)\n8. [Responsive Breakpoints](#responsive-breakpoints)\n9. [Scrollbar & Background](#scrollbar--background)\n\n---\n\n## Color Palette\n\n```css\n:root {\n  /* --- BACKGROUNDS --- */\n  --color-bg:             #FAF7F2;       /* warm off-white, like aged paper */\n  --color-bg-warm:        #F5F0E8;       /* slightly warmer for alternating modules */\n  --color-bg-code:        #1E1E2E;       /* deep indigo-charcoal for code blocks */\n  --color-text:           #2C2A28;       /* dark charcoal, easy on eyes */\n  --color-text-secondary: #6B6560;       /* warm gray for secondary text */\n  --color-text-muted:     #9E9790;       /* muted for timestamps, labels */\n  --color-border:         #E5DFD6;       /* subtle warm border */\n  --color-border-light:   #EEEBE5;       /* even lighter border */\n  --color-surface:        #FFFFFF;       /* card surfaces */\n  --color-surface-warm:   #FDF9F3;       /* warm card surface */\n\n  /* --- ACCENT (adapt per project — pick ONE bold color) ---\n     Default: vermillion. Alternatives: coral (#E06B56), teal (#2A7B9B),\n     amber (#D4A843), forest (#2D8B55). Avoid purple gradients. */\n  --color-accent:         #D94F30;\n  --color-accent-hover:   #C4432A;\n  --color-accent-light:   #FDEEE9;\n  --color-accent-muted:   #E8836C;\n\n  /* --- SEMANTIC --- */\n  --color-success:        #2D8B55;\n  --color-success-light:  #E8F5EE;\n  --color-error:          #C93B3B;\n  --color-error-light:    #FDE8E8;\n  --color-info:           #2A7B9B;\n  --color-info-light:     #E4F2F7;\n\n  /* --- ACTOR COLORS (assign to main components) ---\n     Each major \"character\" in the codebase gets a distinct color\n     for chat bubbles, diagrams, and highlights */\n  --color-actor-1:        #D94F30;       /* vermillion */\n  --color-actor-2:        #2A7B9B;       /* teal */\n  --color-actor-3:        #7B6DAA;       /* muted plum */\n  --color-actor-4:        #D4A843;       /* golden */\n  --color-actor-5:        #2D8B55;       /* forest */\n}\n```\n\n**Rules:**\n- Even-numbered modules use `--color-bg`, odd-numbered use `--color-bg-warm` (alternating backgrounds create visual rhythm)\n- Actor colors should be visually distinct from each other and from the accent\n- Code blocks always use `--color-bg-code` with light text\n\n---\n\n## Typography\n\n```css\n:root {\n  /* --- FONTS ---\n     Display: bold, geometric, personality-driven. NOT Inter/Roboto/Arial.\n     Body: readable with character. NOT system fonts.\n     Mono: developer-friendly with clear character distinction. */\n  --font-display:  'Bricolage Grotesque', Georgia, serif;\n  --font-body:     'DM Sans', -apple-system, sans-serif;\n  --font-mono:     'JetBrains Mono', 'Fira Code', 'Consolas', monospace;\n\n  /* --- TYPE SCALE (1.25 ratio) --- */\n  --text-xs:   0.75rem;    /* 12px — labels, badges */\n  --text-sm:   0.875rem;   /* 14px — secondary text, code */\n  --text-base: 1rem;       /* 16px — body text */\n  --text-lg:   1.125rem;   /* 18px — lead paragraphs */\n  --text-xl:   1.25rem;    /* 20px — screen headings */\n  --text-2xl:  1.5rem;     /* 24px — sub-module titles */\n  --text-3xl:  1.875rem;   /* 30px — module subtitles */\n  --text-4xl:  2.25rem;    /* 36px — module titles */\n  --text-5xl:  3rem;       /* 48px — hero text */\n  --text-6xl:  3.75rem;    /* 60px — module numbers */\n\n  /* --- LINE HEIGHTS --- */\n  --leading-tight:  1.15;  /* headings */\n  --leading-snug:   1.3;   /* subheadings */\n  --leading-normal: 1.6;   /* body text */\n  --leading-loose:  1.8;   /* relaxed reading */\n}\n```\n\n**Google Fonts link (put in `<head>`):**\n```html\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400;1,9..40,500&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n```\n\n**Rules:**\n- Module numbers: `--text-6xl`, font-display, weight 800, `--color-accent` with 15% opacity\n- Module titles: `--text-4xl`, font-display, weight 700\n- Screen headings: `--text-xl` or `--text-2xl`, font-display, weight 600\n- Body text: `--text-base` or `--text-lg`, font-body, `--leading-normal`\n- Code: `--text-sm`, font-mono\n- Labels/badges: `--text-xs`, font-mono, uppercase, letter-spacing 0.05em\n\n---\n\n## Spacing & Layout\n\n```css\n:root {\n  --space-1:  0.25rem;   /* 4px */\n  --space-2:  0.5rem;    /* 8px */\n  --space-3:  0.75rem;   /* 12px */\n  --space-4:  1rem;      /* 16px */\n  --space-5:  1.25rem;   /* 20px */\n  --space-6:  1.5rem;    /* 24px */\n  --space-8:  2rem;      /* 32px */\n  --space-10: 2.5rem;    /* 40px */\n  --space-12: 3rem;      /* 48px */\n  --space-16: 4rem;      /* 64px */\n  --space-20: 5rem;      /* 80px */\n  --space-24: 6rem;      /* 96px */\n\n  --content-width:     800px;   /* standard reading width */\n  --content-width-wide: 1000px; /* for side-by-side layouts */\n  --nav-height:        50px;\n  --radius-sm:  8px;\n  --radius-md:  12px;\n  --radius-lg:  16px;\n  --radius-full: 9999px;\n}\n```\n\n**Module layout:**\n```css\n.module {\n  min-height: 100dvh;       /* fallback: 100vh */\n  scroll-snap-align: start;\n  padding: var(--space-16) var(--space-6);\n  padding-top: calc(var(--nav-height) + var(--space-12));\n}\n.module-content {\n  max-width: var(--content-width);\n  margin: 0 auto;\n}\n```\n\n---\n\n## Shadows & Depth\n\n```css\n:root {\n  --shadow-sm:  0 1px 2px rgba(44, 42, 40, 0.05);\n  --shadow-md:  0 4px 12px rgba(44, 42, 40, 0.08);\n  --shadow-lg:  0 8px 24px rgba(44, 42, 40, 0.1);\n  --shadow-xl:  0 16px 48px rgba(44, 42, 40, 0.12);\n}\n```\n\nUse warm-tinted RGBA (44, 42, 40) — never pure black shadows.\n\n---\n\n## Animations & Transitions\n\n```css\n:root {\n  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);\n  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);\n  --duration-fast:   150ms;\n  --duration-normal: 300ms;\n  --duration-slow:   500ms;\n  --stagger-delay:   120ms;\n}\n```\n\n**Scroll-triggered reveal pattern:**\n```css\n.animate-in {\n  opacity: 0;\n  transform: translateY(20px);\n  transition: opacity var(--duration-slow) var(--ease-out),\n              transform var(--duration-slow) var(--ease-out);\n}\n.animate-in.visible {\n  opacity: 1;\n  transform: translateY(0);\n}\n\n/* Stagger children */\n.stagger-children > .animate-in {\n  transition-delay: calc(var(--stagger-index, 0) * var(--stagger-delay));\n}\n```\n\n**JS setup for stagger:**\n```javascript\ndocument.querySelectorAll('.stagger-children').forEach(parent => {\n  Array.from(parent.children).forEach((child, i) => {\n    child.style.setProperty('--stagger-index', i);\n  });\n});\n```\n\n**Intersection Observer (trigger reveals):**\n```javascript\nconst observer = new IntersectionObserver((entries) => {\n  entries.forEach(entry => {\n    if (entry.isIntersecting) {\n      entry.target.classList.add('visible');\n      observer.unobserve(entry.target); // animate only once\n    }\n  });\n}, { rootMargin: '0px 0px -10% 0px', threshold: 0.1 });\n\ndocument.querySelectorAll('.animate-in').forEach(el => observer.observe(el));\n```\n\n---\n\n## Navigation & Progress\n\n**HTML structure:**\n```html\n<nav class=\"nav\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-valuenow=\"0\"></div>\n  <div class=\"nav-inner\">\n    <span class=\"nav-title\">Course Title</span>\n    <div class=\"nav-dots\">\n      <button class=\"nav-dot\" data-target=\"module-1\" data-tooltip=\"Module 1 Name\"\n              role=\"tab\" aria-label=\"Module 1\"></button>\n      <!-- one per module -->\n    </div>\n  </div>\n</nav>\n```\n\n**Progress bar (CSS-only where possible, JS fallback):**\n```javascript\nfunction updateProgressBar() {\n  const scrollTop = window.scrollY;\n  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;\n  const progress = (scrollTop / scrollHeight) * 100;\n  progressBar.style.width = progress + '%';\n}\nwindow.addEventListener('scroll', () => {\n  requestAnimationFrame(updateProgressBar);\n}, { passive: true });\n```\n\n**Nav dot states:**\n- Default: `border: 2px solid var(--color-text-muted)`, empty center\n- Current: `border-color: var(--color-accent)`, filled center, subtle glow shadow\n- Visited: `background: var(--color-accent)`, filled solid\n\n**Keyboard navigation:**\n```javascript\ndocument.addEventListener('keydown', (e) => {\n  if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) return;\n  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { nextModule(); e.preventDefault(); }\n  if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { prevModule(); e.preventDefault(); }\n});\n```\n\n---\n\n## Module Structure\n\n**HTML template for each module:**\n```html\n<section class=\"module\" id=\"module-N\" style=\"background: var(--color-bg or --color-bg-warm)\">\n  <div class=\"module-content\">\n    <header class=\"module-header animate-in\">\n      <span class=\"module-number\">0N</span>\n      <h1 class=\"module-title\">Module Title</h1>\n      <p class=\"module-subtitle\">One-line description of what this module teaches</p>\n    </header>\n\n    <div class=\"module-body\">\n      <section class=\"screen animate-in\">\n        <h2 class=\"screen-heading\">Screen Title</h2>\n        <p>Content...</p>\n        <!-- Interactive elements, code translations, etc. -->\n      </section>\n\n      <section class=\"screen animate-in\">\n        <!-- Next screen -->\n      </section>\n    </div>\n  </div>\n</section>\n```\n\n---\n\n## Responsive Breakpoints\n\n```css\n/* Tablet */\n@media (max-width: 768px) {\n  :root {\n    --text-4xl: 1.875rem;\n    --text-5xl: 2.25rem;\n    --text-6xl: 3rem;\n  }\n  .translation-block { grid-template-columns: 1fr; } /* stack code/english */\n  .pattern-cards { grid-template-columns: 1fr 1fr; }\n}\n\n/* Mobile */\n@media (max-width: 480px) {\n  :root {\n    --text-4xl: 1.5rem;\n    --text-5xl: 1.875rem;\n    --text-6xl: 2.25rem;\n  }\n  .module { padding: var(--space-8) var(--space-4); }\n  .pattern-cards { grid-template-columns: 1fr; }\n  .flow-steps { flex-direction: column; }\n  .flow-arrow { transform: rotate(90deg); }\n}\n```\n\n---\n\n## Scrollbar & Background\n\n```css\n/* Custom scrollbar */\n::-webkit-scrollbar { width: 6px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: var(--radius-full);\n}\n\n/* Subtle atmospheric background */\nbody {\n  background: var(--color-bg);\n  background-image: radial-gradient(\n    ellipse at 20% 50%,\n    rgba(217, 79, 48, 0.03) 0%,\n    transparent 50%\n  );\n}\n\n/* Page scroll setup */\nhtml {\n  scroll-snap-type: y proximity;\n  scroll-behavior: smooth;\n}\n```\n\n---\n\n## Code Block Globals\n\nAll code blocks in the course — whether inside translation blocks, standalone snippets, or quiz challenges — must wrap text and never show a horizontal scrollbar. This is a teaching tool, not an IDE.\n\n```css\npre, code {\n  white-space: pre-wrap;       /* wrap long lines */\n  word-break: break-word;      /* break mid-word if absolutely needed */\n  overflow-x: hidden;          /* no horizontal scrollbar — ever */\n}\n/* Hide scrollbars on code containers */\n.translation-code::-webkit-scrollbar,\npre::-webkit-scrollbar {\n  display: none;\n}\n```\n\nCode snippets must be **exact copies** from the real codebase — never modified, trimmed, or simplified. Instead, choose naturally short (5-10 line) sections from the code that illustrate the concept well. If a longer block is needed, show it all — the wrapping CSS will handle readability.\n\n---\n\n## Syntax Highlighting (Catppuccin-inspired)\n\nFor code blocks on the dark `--color-bg-code` background:\n\n```css\n.code-keyword  { color: #CBA6F7; }  /* purple — if, else, return, function */\n.code-string   { color: #A6E3A1; }  /* green — \"strings\" */\n.code-function { color: #89B4FA; }  /* blue — function names */\n.code-comment  { color: #6C7086; }  /* muted gray — // comments */\n.code-number   { color: #FAB387; }  /* peach — numbers */\n.code-property { color: #F9E2AF; }  /* yellow — object keys */\n.code-operator { color: #94E2D5; }  /* teal — =, =>, +, etc. */\n.code-tag      { color: #F38BA8; }  /* pink — HTML tags */\n.code-attr     { color: #F9E2AF; }  /* yellow — HTML attributes */\n.code-value    { color: #A6E3A1; }  /* green — attribute values */\n```\n","references/gotchas.md":"# Gotchas — Common Failure Points\n\n> **When to read this:** During Phase 3 (writing module HTML) and Phase 4 (review). Check every one of these before considering a course complete.\n\nThese are real problems encountered when building courses. Check every one before considering a course complete.\n\n### Tooltip Clipping\nTranslation blocks use `overflow: hidden` for code wrapping. If tooltips use `position: absolute` inside the term element, they get clipped by the container. **Fix:** Tooltips must use `position: fixed` and be appended to `document.body`. Calculate position from `getBoundingClientRect()`. This is already handled by `main.js` but is the #1 bug that appears in every build.\n\n### Not Enough Tooltips\nThe most common failure is under-tooltipping. Non-technical learners don't know terms like REPL, JSON, flag, entry point, PATH, pip, namespace, function, class, module, PR, E2E, or even software names like Blender/GIMP. **Rule of thumb:** if a term wouldn't appear in everyday conversation with a non-technical friend, tooltip it. Err heavily on the side of too many. BUT: don't tooltip terms the user already knows well from their domain (e.g., AI/ML concepts for someone in AI).\n\n### Walls of Text\nThe course looks like a textbook instead of an infographic. This happens when you write more than 2-3 sentences in a row without a visual break. Every screen must be at least 50% visual. Convert any list of 3+ items into cards, any sequence into step cards or flow diagrams, any code explanation into a code↔English translation block.\n\n### Recycled Metaphors\nUsing \"restaurant\" or \"kitchen\" for everything. Every module needs its own metaphor that feels inevitable for that specific concept. If you catch yourself reaching for the same metaphor twice, stop and find one that fits the concept organically.\n\n### Code Modifications\nTrimming, simplifying, or \"cleaning up\" code snippets from the codebase. The learner should be able to open the real file and see the exact same code. Instead of editing code to be shorter, *choose* naturally short snippets (5-10 lines) from the codebase that illustrate the point.\n\n### Quiz Questions That Test Memory\nAsking \"What does API stand for?\" or \"Which file handles X?\" — those test recall, not understanding. Every quiz question should present a new scenario the learner hasn't seen and ask them to *apply* what they learned.\n\n### Scroll-Snap Mandatory\nUsing `scroll-snap-type: y mandatory` traps users inside long modules. Always use `proximity`.\n\n### Module Quality Degradation\nTrying to write all modules in one pass causes later modules to be thin and rushed. Build one module at a time and verify each before moving on. For complex codebases, use the parallel path with module briefs.\n\n### Missing Interactive Elements\nA module with only text and code blocks, no interactivity. Every module needs at least one of: quiz, data flow animation, group chat, architecture diagram, drag-and-drop. These aren't decorations — they're how non-technical learners actually process information.\n"},"files":{"references/interactive-elements.md":"# Interactive Elements Reference\n\nImplementation patterns for every interactive element type used in courses. Pick the elements that best serve each module's teaching goal.\n\n> **Architecture note:** All CSS and JavaScript for these elements live in `references/styles.css` and `references/main.js`, which are copied verbatim into every course directory. When writing module HTML files, use only the HTML patterns below — do **not** inline `<style>` or `<script>` tags for these elements. The engines in `main.js` auto-initialize on page load by scanning for the relevant class names and `data-*` attributes described here.\n\n## Table of Contents\n1. [Code ↔ English Translation Blocks](#code--english-translation-blocks)\n2. [Multiple-Choice Quizzes](#multiple-choice-quizzes)\n3. [Drag-and-Drop Matching](#drag-and-drop-matching)\n4. [Group Chat Animation](#group-chat-animation)\n5. [Message Flow / Data Flow Animation](#message-flow--data-flow-animation)\n6. [Interactive Architecture Diagram](#interactive-architecture-diagram)\n7. [Layer Toggle Demo](#layer-toggle-demo)\n8. [\"Spot the Bug\" Challenge](#spot-the-bug-challenge)\n9. [Scenario Quiz](#scenario-quiz)\n10. [Callout Boxes](#callout-boxes)\n11. [Pattern/Feature Cards](#patternfeature-cards)\n12. [Flow Diagrams](#flow-diagrams)\n13. [Permission/Config Badges](#permissionconfig-badges)\n14. [Glossary Tooltips](#glossary-tooltips)\n15. [Visual File Tree](#visual-file-tree)\n16. [Icon-Label Rows](#icon-label-rows)\n17. [Numbered Step Cards](#numbered-step-cards)\n\n---\n\n## Code ↔ English Translation Blocks\n\nThe most important teaching element. Shows real code from the project on the left and a plain English translation on the right, line by line.\n\n**HTML:**\n```html\n<div class=\"translation-block animate-in\">\n  <div class=\"translation-code\">\n    <span class=\"translation-label\">CODE</span>\n    <pre><code>\n<span class=\"code-line\"><span class=\"code-keyword\">const</span> response = <span class=\"code-keyword\">await</span> <span class=\"code-function\">fetch</span>(url, {</span>\n<span class=\"code-line\">  <span class=\"code-property\">method</span>: <span class=\"code-string\">'POST'</span>,</span>\n<span class=\"code-line\">  <span class=\"code-property\">headers</span>: { <span class=\"code-string\">'Authorization'</span>: apiKey }</span>\n<span class=\"code-line\">});</span>\n    </code></pre>\n  </div>\n  <div class=\"translation-english\">\n    <span class=\"translation-label\">PLAIN ENGLISH</span>\n    <div class=\"translation-lines\">\n      <p class=\"tl\">Send a request to the URL and wait for a response...</p>\n      <p class=\"tl\">We're sending data (POST), not just asking for it (GET)...</p>\n      <p class=\"tl\">Include our API key so the server knows who we are...</p>\n      <p class=\"tl\">End of the request setup.</p>\n    </div>\n  </div>\n</div>\n```\n\n**CSS:**\n```css\n.translation-block {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 0;\n  border-radius: var(--radius-md);\n  overflow: hidden;\n  box-shadow: var(--shadow-md);\n  margin: var(--space-8) 0;\n}\n.translation-code {\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-6);\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  position: relative;\n  overflow-x: hidden;  /* NO horizontal scrollbar — ever */\n}\n.translation-code pre,\n.translation-code code {\n  white-space: pre-wrap;       /* wrap long lines instead of scrolling */\n  word-break: break-word;      /* break mid-word if needed */\n  overflow-x: hidden;\n}\n.translation-english {\n  background: var(--color-surface-warm);\n  padding: var(--space-6);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  border-left: 3px solid var(--color-accent);\n}\n.translation-label {\n  position: absolute;\n  top: var(--space-2);\n  right: var(--space-3);\n  font-size: var(--text-xs);\n  text-transform: uppercase;\n  letter-spacing: 0.1em;\n  opacity: 0.5;\n}\n.translation-english .translation-label {\n  color: var(--color-text-muted);\n}\n/* Responsive: stack vertically on mobile */\n@media (max-width: 768px) {\n  .translation-block { grid-template-columns: 1fr; }\n  .translation-english { border-left: none; border-top: 3px solid var(--color-accent); }\n}\n```\n\n**Rules:**\n- Each English line should correspond to 1-2 code lines\n- Use conversational language, not technical jargon\n- Highlight the \"why\" not just the \"what\" — e.g., \"Include our API key so the server knows who we are\" not \"Set the Authorization header\"\n\n---\n\n## Multiple-Choice Quizzes\n\nFor testing understanding with instant feedback. Each question has options, one correct answer, and per-question explanations.\n\n**Wiring:** `main.js` exposes `window.selectOption(btn)`, `window.checkQuiz(containerId)`, and `window.resetQuiz(containerId)`. Call them via `onclick`. Per-question explanations go in `data-explanation-right` and `data-explanation-wrong` on the `.quiz-question-block`.\n\n**HTML:**\n```html\n<div class=\"quiz-container\" id=\"quiz-module3\">\n  <div class=\"quiz-question-block\"\n       data-correct=\"option-b\"\n       data-explanation-right=\"Exactly — because X is responsible for Y in this architecture.\"\n       data-explanation-wrong=\"Not quite. Think about where Y lives in the codebase...\">\n    <h3 class=\"quiz-question\">Question text here?</h3>\n    <div class=\"quiz-options\">\n      <button class=\"quiz-option\" data-value=\"option-a\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer A</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-b\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer B (correct)</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-c\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer C</span>\n      </button>\n    </div>\n    <div class=\"quiz-feedback\"></div>\n  </div>\n\n  <button class=\"quiz-check-btn\" onclick=\"checkQuiz('quiz-module3')\">Check Answers</button>\n  <button class=\"quiz-reset-btn\" onclick=\"resetQuiz('quiz-module3')\">Try Again</button>\n</div>\n```\n\n**CSS for quiz states:**\n```css\n.quiz-option {\n  display: flex; align-items: center; gap: var(--space-3);\n  padding: var(--space-3) var(--space-4);\n  border: 2px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  background: var(--color-surface);\n  cursor: pointer; width: 100%;\n  transition: border-color var(--duration-fast), background var(--duration-fast);\n}\n.quiz-option:hover { border-color: var(--color-accent-muted); }\n.quiz-option.selected { border-color: var(--color-accent); background: var(--color-accent-light); }\n.quiz-option.correct { border-color: var(--color-success); background: var(--color-success-light); }\n.quiz-option.incorrect { border-color: var(--color-error); background: var(--color-error-light); }\n.quiz-option-radio {\n  width: 18px; height: 18px; border-radius: 50%;\n  border: 2px solid var(--color-border);\n  transition: all var(--duration-fast);\n}\n.quiz-option.selected .quiz-option-radio {\n  border-color: var(--color-accent);\n  background: var(--color-accent);\n  box-shadow: inset 0 0 0 3px white;\n}\n.quiz-feedback {\n  max-height: 0; overflow: hidden; opacity: 0;\n  transition: max-height var(--duration-normal), opacity var(--duration-normal);\n}\n.quiz-feedback.show { max-height: 200px; opacity: 1; padding: var(--space-3); margin-top: var(--space-2); border-radius: var(--radius-sm); }\n.quiz-feedback.success { background: var(--color-success-light); color: var(--color-success); }\n.quiz-feedback.error { background: var(--color-error-light); color: var(--color-error); }\n```\n\n---\n\n## Drag-and-Drop Matching\n\nFor matching concepts to descriptions. Supports both mouse (HTML5 Drag API) and touch.\n\n**HTML:**\n```html\n<div class=\"dnd-container\">\n  <div class=\"dnd-chips\">\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-a\">Actor A</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-b\">Actor B</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-c\">Actor C</div>\n  </div>\n  <div class=\"dnd-zones\">\n    <div class=\"dnd-zone\" data-correct=\"actor-a\">\n      <p class=\"dnd-zone-label\">Description for Actor A</p>\n      <div class=\"dnd-zone-target\">Drop here</div>\n    </div>\n    <!-- more zones -->\n  </div>\n  <button onclick=\"checkDnD()\">Check Matches</button>\n  <button onclick=\"resetDnD()\">Reset</button>\n</div>\n```\n\n**JS (mouse + touch):**\n```javascript\n// MOUSE: HTML5 Drag API\nchips.forEach(chip => {\n  chip.addEventListener('dragstart', (e) => {\n    e.dataTransfer.setData('text/plain', chip.dataset.answer);\n    chip.classList.add('dragging');\n  });\n  chip.addEventListener('dragend', () => chip.classList.remove('dragging'));\n});\n\nzones.forEach(zone => {\n  const target = zone.querySelector('.dnd-zone-target');\n  target.addEventListener('dragover', (e) => { e.preventDefault(); target.classList.add('drag-over'); });\n  target.addEventListener('dragleave', () => target.classList.remove('drag-over'));\n  target.addEventListener('drop', (e) => {\n    e.preventDefault();\n    target.classList.remove('drag-over');\n    const answer = e.dataTransfer.getData('text/plain');\n    const chip = document.querySelector(`[data-answer=\"${answer}\"]`);\n    target.textContent = chip.textContent;\n    target.dataset.placed = answer;\n    chip.classList.add('placed');\n  });\n});\n\n// TOUCH: Custom implementation (HTML5 drag doesn't work on mobile)\nchips.forEach(chip => {\n  chip.addEventListener('touchstart', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    const clone = chip.cloneNode(true);\n    clone.classList.add('touch-ghost');\n    clone.style.cssText = `position:fixed; z-index:1000; pointer-events:none;\n      left:${touch.clientX - 40}px; top:${touch.clientY - 20}px;`;\n    document.body.appendChild(clone);\n    chip._ghost = clone;\n    chip._answer = chip.dataset.answer;\n  }, { passive: false });\n\n  chip.addEventListener('touchmove', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    if (chip._ghost) {\n      chip._ghost.style.left = (touch.clientX - 40) + 'px';\n      chip._ghost.style.top = (touch.clientY - 20) + 'px';\n    }\n    // Highlight zone under finger\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    zones.forEach(z => z.querySelector('.dnd-zone-target').classList.remove('drag-over'));\n    if (el && el.closest('.dnd-zone-target')) {\n      el.closest('.dnd-zone-target').classList.add('drag-over');\n    }\n  }, { passive: false });\n\n  chip.addEventListener('touchend', (e) => {\n    if (chip._ghost) { chip._ghost.remove(); chip._ghost = null; }\n    const touch = e.changedTouches[0];\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    if (el && el.closest('.dnd-zone-target')) {\n      const target = el.closest('.dnd-zone-target');\n      target.textContent = chip.textContent;\n      target.dataset.placed = chip._answer;\n      chip.classList.add('placed');\n    }\n  });\n});\n```\n\n---\n\n## Group Chat Animation\n\niMessage/WeChat-style chat showing components \"talking\" to each other. Messages appear one by one with typing indicators.\n\n**Wiring:** `main.js` auto-initializes every `.chat-window` on page load. Give each chat window a unique `id`. Control buttons need these classes: `.chat-next-btn`, `.chat-all-btn`, `.chat-reset-btn`. The typing indicator avatar element should have `id=\"{chatWindowId}-typing-avatar\"` or simply be the first `.chat-avatar` inside `.chat-typing`.\n\n**HTML:**\n```html\n<div class=\"chat-window\" id=\"chat-module2\">\n  <div class=\"chat-messages\">\n    <div class=\"chat-message\" data-msg=\"0\" data-sender=\"actor-a\" style=\"display:none\">\n      <div class=\"chat-avatar\" style=\"background: var(--color-actor-1)\">A</div>\n      <div class=\"chat-bubble\">\n        <span class=\"chat-sender\" style=\"color: var(--color-actor-1)\">Actor A</span>\n        <p>Hey Background, I need the data for this item.</p>\n      </div>\n    </div>\n    <!-- more messages... -->\n  </div>\n\n  <div class=\"chat-typing\" id=\"chat-typing\" style=\"display:none\">\n    <div class=\"chat-avatar\" id=\"typing-avatar\">?</div>\n    <div class=\"chat-typing-dots\">\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n    </div>\n  </div>\n\n  <div class=\"chat-controls\">\n    <button class=\"btn chat-next-btn\">Next Message</button>\n    <button class=\"btn chat-all-btn\">Play All</button>\n    <button class=\"btn chat-reset-btn\">Replay</button>\n    <span class=\"chat-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for typing dots:**\n```css\n.typing-dot {\n  width: 8px; height: 8px; border-radius: 50%;\n  background: var(--color-text-muted);\n  animation: typingBounce 1.4s infinite;\n}\n.typing-dot:nth-child(2) { animation-delay: 0.2s; }\n.typing-dot:nth-child(3) { animation-delay: 0.4s; }\n@keyframes typingBounce {\n  0%, 60%, 100% { transform: translateY(0); }\n  30% { transform: translateY(-6px); }\n}\n```\n\n---\n\n## Message Flow / Data Flow Animation\n\nStep-by-step visualization of data moving between components. User clicks \"Next Step\" to advance.\n\n**Wiring:** `main.js` auto-initializes every `.flow-animation` on page load. Pass steps as JSON in `data-steps`. Each step object: `{ highlight: \"flow-actor-id\", label: \"description\", packet: true, from: \"actor-id-suffix\", to: \"actor-id-suffix\" }`. Actor element IDs must be `flow-actor-1`, `flow-actor-2`, etc. Control buttons need classes `.flow-next-btn` and `.flow-reset-btn`.\n\n> **⚠️ Single quotes in step labels will break parsing.** The `data-steps` attribute is delimited by single quotes (`data-steps='[...]'`), so any single quote inside a label (e.g. `\"the user's request\"`) will terminate the attribute early and cause `JSON.parse` to fail silently — the entire animation will stop working. Either avoid apostrophes in labels, replace them with `&apos;`, or rewrite the attribute using double-quote delimiters with escaped inner quotes (`data-steps=\"[{\\\"label\\\":\\\"...\\\"}]\"`).\n\n**HTML:**\n```html\n<div class=\"flow-animation\" data-steps='[\n  {\"highlight\":\"flow-actor-1\",\"label\":\"User clicks the button\"},\n  {\"highlight\":\"flow-actor-1\",\"label\":\"Frontend sends request\",\"packet\":true,\"from\":\"actor-1\",\"to\":\"actor-2\"},\n  {\"highlight\":\"flow-actor-2\",\"label\":\"Backend calls the database\",\"packet\":true,\"from\":\"actor-2\",\"to\":\"actor-3\"}\n]'>\n  <div class=\"flow-actors\">\n    <div class=\"flow-actor\" id=\"flow-actor-1\">\n      <div class=\"flow-actor-icon\">A</div>\n      <span>Actor 1</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-2\">\n      <div class=\"flow-actor-icon\">B</div>\n      <span>Actor 2</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-3\">\n      <div class=\"flow-actor-icon\">C</div>\n      <span>Actor 3</span>\n    </div>\n  </div>\n\n  <div class=\"flow-packet\" id=\"flow-packet\"></div>\n\n  <div class=\"flow-step-label\" id=\"flow-label\">Click \"Next Step\" to begin</div>\n\n  <div class=\"flow-controls\">\n    <button class=\"btn flow-next-btn\">Next Step</button>\n    <button class=\"btn flow-reset-btn\">Restart</button>\n    <span class=\"flow-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for active actor glow:**\n```css\n.flow-actor.active {\n  box-shadow: 0 0 0 3px var(--color-accent), 0 0 20px rgba(217, 79, 48, 0.2);\n  transform: scale(1.05);\n  transition: all var(--duration-normal) var(--ease-out);\n}\n```\n\n---\n\n## Interactive Architecture Diagram\n\nFull-system diagram where hovering/clicking a component shows a description tooltip.\n\n**HTML:**\n```html\n<div class=\"arch-diagram\">\n  <div class=\"arch-zone arch-zone-browser\">\n    <h4 class=\"arch-zone-label\">Browser</h4>\n    <div class=\"arch-component\" data-desc=\"Injects UI into the web page, reads DOM, captures user actions\"\n         onclick=\"showArchDesc(this)\">\n      <div class=\"arch-icon\">📄</div>\n      <span>Component A</span>\n    </div>\n    <!-- more components -->\n  </div>\n  <div class=\"arch-zone arch-zone-external\">\n    <h4 class=\"arch-zone-label\">External Services</h4>\n    <!-- API cards -->\n  </div>\n  <div class=\"arch-description\" id=\"arch-desc\">Click any component to learn what it does</div>\n</div>\n```\n\n---\n\n## Layer Toggle Demo\n\nShows how different layers (e.g., HTML/CSS/JS, or data/logic/UI) build on each other. Three tabs switch between views.\n\n**HTML:**\n```html\n<div class=\"layer-demo\">\n  <div class=\"layer-tabs\">\n    <button class=\"layer-tab active\" onclick=\"showLayer('html')\">HTML</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('css')\">+ CSS</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('js')\">+ JS</button>\n  </div>\n  <div class=\"layer-viewport\">\n    <div class=\"layer\" id=\"layer-html\" style=\"display:block\">\n      <!-- Raw unstyled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-css\" style=\"display:none\">\n      <!-- Styled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-js\" style=\"display:none\">\n      <!-- Interactive version -->\n    </div>\n  </div>\n  <p class=\"layer-description\" id=\"layer-desc\">This is the raw HTML...</p>\n</div>\n```\n\n---\n\n## \"Spot the Bug\" Challenge\n\nShow code with a deliberate bug. User clicks the buggy line. Reveal explains the issue.\n\n**HTML:**\n```html\n<div class=\"bug-challenge\">\n  <h3>Find the bug in this code:</h3>\n  <div class=\"bug-code\">\n    <div class=\"bug-line\" data-line=\"1\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">1</span>\n      <code>chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"2\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">2</span>\n      <code>  if (msg.action === 'fetchData') {</code>\n    </div>\n    <div class=\"bug-line bug-target\" data-line=\"3\" onclick=\"checkBugLine(this, true)\">\n      <span class=\"line-num\">3</span>\n      <code>    fetch(url).then(r => r.json()).then(data => sendResponse(data));</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"4\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">4</span>\n      <code>  }</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"5\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">5</span>\n      <code>});</code>\n    </div>\n  </div>\n  <div class=\"bug-feedback\" id=\"bug-feedback\"></div>\n</div>\n```\n\n**JS:**\n```javascript\nwindow.checkBugLine = function(el, isCorrect) {\n  const feedback = el.closest('.bug-challenge').querySelector('.bug-feedback');\n  if (isCorrect) {\n    el.classList.add('correct');\n    feedback.innerHTML = '<strong>Found it!</strong> The listener uses an async operation (fetch) but doesn\\'t return true. Chrome closes the message channel before the response can be sent. Fix: add <code>return true;</code> at the end.';\n    feedback.className = 'bug-feedback show success';\n  } else {\n    el.classList.add('incorrect');\n    feedback.innerHTML = 'Not this line — look for where the async timing might cause problems...';\n    feedback.className = 'bug-feedback show error';\n    setTimeout(() => { el.classList.remove('incorrect'); feedback.className = 'bug-feedback'; }, 2000);\n  }\n};\n```\n\n---\n\n## Scenario Quiz\n\n\"What would a senior engineer do?\" — situational questions with explanations.\n\nSame HTML/CSS/JS pattern as Multiple-Choice Quizzes, but with longer scenario descriptions and more detailed explanations. Wrap each question in a scenario context block:\n\n```html\n<div class=\"scenario-block\">\n  <div class=\"scenario-context\">\n    <span class=\"scenario-label\">Scenario</span>\n    <p>Your app processes a 3-hour podcast transcript. The API has a 16,000 token limit. What do you do?</p>\n  </div>\n  <!-- quiz-options here -->\n</div>\n```\n\n---\n\n## Callout Boxes\n\n\"Aha!\" moments — universal CS insights. Max 2 per module.\n\n```html\n<div class=\"callout callout-accent\">\n  <div class=\"callout-icon\">💡</div>\n  <div class=\"callout-content\">\n    <strong class=\"callout-title\">Key Insight</strong>\n    <p>This pattern — splitting responsibilities into focused roles — is one of the most important ideas in software engineering. Engineers call it \"separation of concerns.\"</p>\n  </div>\n</div>\n```\n\n**Variants:**\n- `callout-accent`: vermillion left border, light accent background (for CS insights)\n- `callout-info`: teal left border, light info background (for \"good to know\")\n- `callout-warning`: red left border, light error background (for common mistakes)\n\n---\n\n## Pattern/Feature Cards\n\nGrid of cards highlighting engineering patterns, tech stack components, or key concepts.\n\n```html\n<div class=\"pattern-cards\">\n  <div class=\"pattern-card\" style=\"border-top: 3px solid var(--color-actor-1)\">\n    <div class=\"pattern-icon\" style=\"background: var(--color-actor-1)\">🔄</div>\n    <h4 class=\"pattern-title\">Caching</h4>\n    <p class=\"pattern-desc\">Store results to avoid redundant work — like keeping leftovers instead of cooking a new meal every time.</p>\n  </div>\n  <!-- more cards -->\n</div>\n```\n\n```css\n.pattern-cards {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n  gap: var(--space-4);\n}\n.pattern-card {\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  padding: var(--space-6);\n  box-shadow: var(--shadow-sm);\n  transition: transform var(--duration-normal) var(--ease-out), box-shadow var(--duration-normal);\n}\n.pattern-card:hover {\n  transform: translateY(-4px);\n  box-shadow: var(--shadow-md);\n}\n```\n\n---\n\n## Flow Diagrams\n\n**Horizontal flow (desktop):**\n```html\n<div class=\"flow-steps\">\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">1</div>\n    <p>User clicks button</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">2</div>\n    <p>Component A detects click</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <!-- more steps -->\n</div>\n```\n\nArrows rotate to `↓` on mobile via CSS transform.\n\n---\n\n## Permission/Config Badges\n\nFor annotating config files, permissions, or settings:\n\n```html\n<div class=\"badge-list\">\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">storage</code>\n    <span class=\"badge-desc\">Save data between sessions (like browser bookmarks)</span>\n  </div>\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">activeTab</code>\n    <span class=\"badge-desc\">Access the currently open tab (only when the user clicks)</span>\n  </div>\n</div>\n```\n\n```css\n.badge-item {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-3) var(--space-4);\n  border: 1px solid var(--color-border-light);\n  border-radius: var(--radius-sm);\n  transition: border-color var(--duration-fast);\n}\n.badge-item:hover { border-color: var(--color-accent-muted); }\n.badge-code {\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  background: var(--color-bg-code);\n  color: #CBA6F7;\n  padding: var(--space-1) var(--space-3);\n  border-radius: var(--radius-sm);\n  white-space: nowrap;\n}\n```\n\n---\n\n## Glossary Tooltips\n\nThe most important accessibility feature for non-technical learners. Any technical term in the course text should be wrapped in a tooltip that shows a plain-English definition on hover (desktop) or tap (mobile). The learner never has to leave the page or Google anything.\n\n**HTML — mark up terms inline:**\n```html\n<p>The extension uses a\n  <span class=\"term\" data-definition=\"A service worker is a background script that runs independently of the web page — like a behind-the-scenes assistant that's always on, even when you're not looking at the page.\">service worker</span>\n  to handle API calls.\n</p>\n```\n\n**CSS:**\n```css\n.term {\n  border-bottom: 1.5px dashed var(--color-accent-muted);\n  cursor: pointer;    /* NOT cursor: help — pointer feels clickable and inviting */\n  position: relative;\n}\n.term:hover, .term.active {\n  border-bottom-color: var(--color-accent);\n  color: var(--color-accent);\n}\n\n/* The tooltip bubble — uses position: fixed and is appended to document.body\n   via JS so it is NEVER clipped by ancestor overflow: hidden containers\n   (like translation blocks). See JS section below for positioning logic. */\n.term-tooltip {\n  position: fixed;        /* CRITICAL: fixed, not absolute — prevents clipping */\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-3) var(--space-4);\n  border-radius: var(--radius-sm);\n  font-size: var(--text-sm);\n  font-family: var(--font-body);\n  line-height: var(--leading-normal);\n  width: max(200px, min(320px, 80vw));\n  box-shadow: var(--shadow-lg);\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity var(--duration-fast);\n  z-index: 10000;        /* Above everything, including nav */\n}\n/* Arrow pointing down */\n.term-tooltip::after {\n  content: '';\n  position: absolute;\n  top: 100%;\n  left: 50%;\n  transform: translateX(-50%);\n  border: 6px solid transparent;\n  border-top-color: var(--color-bg-code);\n}\n.term-tooltip.visible {\n  opacity: 1;\n}\n\n/* If tooltip goes off-screen top, flip to below */\n.term-tooltip.flip {\n  bottom: auto;\n  top: calc(100% + 8px);\n}\n.term-tooltip.flip::after {\n  top: auto;\n  bottom: 100%;\n  border-top-color: transparent;\n  border-bottom-color: var(--color-bg-code);\n}\n```\n\n**JS — position: fixed tooltips appended to body (never clipped by overflow):**\n```javascript\n// Tooltip container — appended to body so it's never clipped\nlet activeTooltip = null;\n\nfunction positionTooltip(term, tip) {\n  const rect = term.getBoundingClientRect();\n  const tipWidth = 300; // approximate\n  let left = rect.left + rect.width / 2 - tipWidth / 2;\n  // Clamp to viewport\n  left = Math.max(8, Math.min(left, window.innerWidth - tipWidth - 8));\n\n  // Try above first\n  let top = rect.top - 8;\n  tip.style.left = left + 'px';\n\n  // Position above by default, flip below if no room\n  document.body.appendChild(tip);\n  const tipHeight = tip.offsetHeight;\n  if (rect.top - tipHeight - 8 < 0) {\n    // Flip below\n    tip.style.top = (rect.bottom + 8) + 'px';\n    tip.classList.add('flip');\n  } else {\n    tip.style.top = (rect.top - tipHeight - 8) + 'px';\n    tip.classList.remove('flip');\n  }\n}\n\ndocument.querySelectorAll('.term').forEach(term => {\n  const tip = document.createElement('span');\n  tip.className = 'term-tooltip';\n  tip.textContent = term.dataset.definition;\n\n  // Hover for desktop\n  term.addEventListener('mouseenter', () => {\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    positionTooltip(term, tip);\n    requestAnimationFrame(() => tip.classList.add('visible'));\n    activeTooltip = tip;\n  });\n\n  term.addEventListener('mouseleave', () => {\n    tip.classList.remove('visible');\n    setTimeout(() => { if (!tip.classList.contains('visible')) tip.remove(); }, 150);\n    activeTooltip = null;\n  });\n\n  // Tap for mobile\n  term.addEventListener('click', (e) => {\n    e.stopPropagation();\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    if (tip.classList.contains('visible')) {\n      tip.classList.remove('visible');\n      tip.remove();\n      activeTooltip = null;\n    } else {\n      positionTooltip(term, tip);\n      requestAnimationFrame(() => tip.classList.add('visible'));\n      activeTooltip = tip;\n    }\n  });\n});\n\n// Close tooltips when clicking elsewhere\ndocument.addEventListener('click', () => {\n  if (activeTooltip) {\n    activeTooltip.classList.remove('visible');\n    activeTooltip.remove();\n    activeTooltip = null;\n  }\n});\n```\n\n**Rules:**\n- Mark up EVERY technical term on first use in each module (API, DOM, callback, async, endpoint, middleware, etc.)\n- Keep definitions to 1-2 sentences max, in everyday language\n- Use a metaphor in the definition when it helps — e.g., \"A **callback** is like leaving your phone number at a restaurant so they can call you when your table is ready\"\n- Don't mark the same term twice within the same screen — only on first appearance per module\n- The dashed underline should be subtle enough not to distract but visible enough that curious learners discover it\n\n---\n\n## Visual File Tree\n\nUse instead of paragraphs listing \"this folder does X, that folder does Y.\" Much easier to scan.\n\n```html\n<div class=\"file-tree\">\n  <div class=\"ft-folder open\">\n    <span class=\"ft-name\">app/</span>\n    <span class=\"ft-desc\">Pages and API routes</span>\n    <div class=\"ft-children\">\n      <div class=\"ft-folder\">\n        <span class=\"ft-name\">api/</span>\n        <span class=\"ft-desc\">Backend endpoints the frontend calls</span>\n      </div>\n      <div class=\"ft-file\">\n        <span class=\"ft-name\">layout.tsx</span>\n        <span class=\"ft-desc\">The shell that wraps every page</span>\n      </div>\n    </div>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">components/</span>\n    <span class=\"ft-desc\">Reusable UI building blocks</span>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">lib/</span>\n    <span class=\"ft-desc\">Shared logic and utilities</span>\n  </div>\n</div>\n```\n\n```css\n.file-tree { font-family: var(--font-mono); font-size: var(--text-sm); }\n.ft-folder, .ft-file {\n  padding: var(--space-2) var(--space-3);\n  border-left: 2px solid var(--color-border-light);\n  margin-left: var(--space-4);\n}\n.ft-folder > .ft-name { color: var(--color-accent); font-weight: 600; }\n.ft-folder > .ft-name::before { content: '📁 '; }\n.ft-file > .ft-name::before { content: '📄 '; }\n.ft-desc {\n  color: var(--color-text-secondary);\n  font-family: var(--font-body);\n  margin-left: var(--space-2);\n  font-size: var(--text-xs);\n}\n.ft-children { margin-left: var(--space-4); }\n```\n\n---\n\n## Icon-Label Rows\n\nFor listing components, features, or concepts visually. Replaces bullet-point paragraphs.\n\n```html\n<div class=\"icon-rows\">\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-1)\">🖥️</div>\n    <div>\n      <strong>Frontend (Next.js)</strong>\n      <p>What the user sees and interacts with</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-2)\">⚡</div>\n    <div>\n      <strong>API Routes</strong>\n      <p>Backend logic that runs on the server</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-3)\">🗄️</div>\n    <div>\n      <strong>Database (Supabase)</strong>\n      <p>Where all the data is stored permanently</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.icon-rows { display: flex; flex-direction: column; gap: var(--space-4); }\n.icon-row {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-4);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  box-shadow: var(--shadow-sm);\n}\n.icon-row p { margin: 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n.icon-circle {\n  width: 48px; height: 48px; border-radius: 50%;\n  display: flex; align-items: center; justify-content: center;\n  font-size: 1.25rem; flex-shrink: 0;\n}\n```\n\n---\n\n## Numbered Step Cards\n\nFor sequences that would otherwise be a numbered paragraph list. Visual, scannable, and each step stands alone.\n\n```html\n<div class=\"step-cards\">\n  <div class=\"step-card\">\n    <div class=\"step-num\">1</div>\n    <div class=\"step-body\">\n      <strong>User pastes a YouTube URL</strong>\n      <p>The frontend captures the URL and extracts the video ID</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">2</div>\n    <div class=\"step-body\">\n      <strong>API fetches the transcript</strong>\n      <p>A server-side route calls an external service to get the video's text</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">3</div>\n    <div class=\"step-body\">\n      <strong>AI analyzes the content</strong>\n      <p>The transcript is sent to an AI model that extracts key moments</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.step-cards { display: flex; flex-direction: column; gap: var(--space-3); }\n.step-card {\n  display: flex; align-items: flex-start; gap: var(--space-4);\n  padding: var(--space-4) var(--space-5);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  border-left: 3px solid var(--color-accent);\n  box-shadow: var(--shadow-sm);\n}\n.step-num {\n  width: 32px; height: 32px; border-radius: 50%;\n  background: var(--color-accent);\n  color: white; font-weight: 700;\n  display: flex; align-items: center; justify-content: center;\n  font-family: var(--font-display);\n  flex-shrink: 0;\n}\n.step-body p { margin: var(--space-1) 0 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n```\n","references/module-brief-template.md":"# Module Brief Template\n\n> **When to read this:** During Phase 2.5 (planning checkpoint) for complex codebases. Fill in one brief per module, save to `course-name/briefs/0N-slug.md`. Each brief gives a parallel agent everything it needs to write one module without reading the codebase or SKILL.md.\n\n---\n\n## Module N: [Title]\n\n### Teaching Arc\n- **Metaphor:** [A fresh, specific metaphor — never \"restaurant.\" See `references/content-philosophy.md` > Metaphors First]\n- **Opening hook:** [1 sentence that connects to something the learner already knows from using the app]\n- **Key insight:** [The one thing the learner should walk away understanding]\n- **\"Why should I care?\":** [How this helps them steer AI / debug / make decisions]\n\n### Code Snippets (pre-extracted)\n\nInclude the actual code the module will use in code↔English translation blocks. Copy-paste from the codebase with file path and line numbers. The writing agent will use these verbatim — it will NOT re-read the codebase.\n\nFile: src/example/file.ts (lines 12-24)\n[paste actual code here]\n\nFile: src/another/file.ts (lines 45-52)\n[paste actual code here]\n\n### Interactive Elements\n\nCheck which elements this module needs. Include enough detail for the writing agent to build them.\n\n- [ ] **Code↔English translation** — which snippet(s) from above\n- [ ] **Quiz** — [number] questions, style: [scenario / debugging / architecture / tracing]. Brief description of each question's angle.\n- [ ] **Group chat animation** — actors: [list]. Message flow summary: [who says what to whom, in what order]\n- [ ] **Data flow animation** — actors: [list]. Steps: [sequence of highlights and packet movements]\n- [ ] **Drag-and-drop** — items: [list], targets: [list]\n- [ ] **Other** — [architecture diagram, layer toggle, pattern cards, etc.]\n\n### Reference Files to Read\n\nList only the sections the writing agent needs — not the whole file.\n\n- `references/interactive-elements.md` → [section names, e.g., \"Multiple-Choice Quizzes\", \"Group Chat Animation\"]\n- `references/design-system.md` → [only if needed for specific tokens not in the brief]\n- `references/content-philosophy.md` → [always include — agent needs content rules]\n- `references/gotchas.md` → [always include — agent needs the checklist]\n\n### Connections\n\n- **Previous module:** [Title — what it covered, so this module can build on it]\n- **Next module:** [Title — what it will cover, so this module can set it up]\n- **Tone/style notes:** [Any course-wide consistency notes: accent color name, actor naming convention, etc.]\n","references/content-philosophy.md":"# Content Philosophy\n\n> **When to read this:** During Phase 2.5 (writing module briefs) and Phase 3 (writing module HTML). These principles guide every content decision — what to show, how to explain it, and how to test understanding.\n\nThese principles are what separate a great course from a generic tutorial. They should guide every content decision:\n\n### Show, Don't Tell — Aggressively Visual\nPeople's eyes glaze over text blocks. The course should feel closer to an infographic than a textbook. Follow these hard rules:\n\n**Text limits:**\n- Max **2-3 sentences** per text block. If you're writing a fourth sentence, stop and convert it into a visual instead.\n- No text block should ever be wider than the content width AND taller than ~4 lines. If it is, break it up with a visual element.\n- Every screen must be **at least 50% visual** (diagrams, code blocks, cards, animations, badges — anything that isn't a paragraph).\n\n**Convert text to visuals:**\n- A list of 3+ items → **cards with icons** (pattern cards, feature cards)\n- A sequence of steps → **flow diagram with arrows** or **numbered step cards**\n- \"Component A talks to Component B\" → **animated data flow** or **group chat visualization**\n- \"This file does X, that file does Y\" → **visual file tree with annotations** or **icon + one-liner badges**\n- Explaining what code does → **code↔English translation block** (not a paragraph *about* the code)\n- Comparing two approaches → **side-by-side columns** with visual contrast\n\n**Visual breathing room:**\n- Use generous spacing between elements (`--space-8` to `--space-12` between sections)\n- Alternate between full-width visuals and narrow text blocks to create rhythm\n- Every module should have at least one \"hero visual\" — a diagram, animation, or interactive element that dominates the screen and teaches the core concept at a glance\n\n### Code ↔ English Translations\nEvery code snippet gets a side-by-side plain English translation. Left panel: real code from the project with syntax highlighting. Right panel: line-by-line plain English explaining what each line does. This is the single most valuable teaching tool for non-technical learners.\n\n**Critical: No horizontal scrollbars on code.** All code must use `white-space: pre-wrap` so it wraps instead of scrolling. This is a course for non-technical people, not an IDE — readability beats preserving indentation structure.\n\n**Critical: Use original code exactly as-is.** Never modify, simplify, or trim code snippets from the codebase. The learner should be able to open the real file and see the exact same code they learned from — that builds trust. Instead of editing code to make it shorter, *choose* naturally short, punchy snippets (5-10 lines) from the codebase that illustrate the concept well. Every codebase has compact, self-contained moments — find those rather than butchering longer functions.\n\n### One Concept Per Screen\nNo walls of text. Each screen within a module teaches exactly one idea. If you need more space, add another screen — don't cram.\n\n### Metaphors First, Then Reality\nIntroduce every new concept with a metaphor from everyday life. Then immediately ground it: \"In our code, this looks like...\" The metaphor builds intuition; the code grounds it in reality.\n\n**Critical: No recycled metaphors.** Do NOT default to \"restaurant\" for everything — that's the #1 crutch. Each concept deserves its own metaphor that feels natural to *that specific idea*. A database is a library with a card catalog. Auth is a bouncer checking IDs. An event loop is an air traffic controller. Message passing is a postal system. API rate limiting is a nightclub with a capacity limit. Pick the metaphor that makes the concept click, not the one that's easiest to reach for. If you catch yourself using \"restaurant\" or \"kitchen\" more than once in a course, stop and rethink.\n\n### Learn by Tracing\nFollow what actually happens when the learner does something they already do every day in the app — trace the data flow end-to-end. \"You know that button you click? Here's the journey your data takes after you click it...\" This works because the learner has *already experienced the result* — now they're seeing the machinery behind it. It's like watching a behind-the-scenes documentary of a movie you loved.\n\n### Make It Memorable\nUse \"aha!\" callout boxes for universal CS insights. Use humor where natural (not forced). Give components personality — they're \"characters\" in a story, not abstract boxes on a diagram.\n\n### Glossary Tooltips — No Term Left Behind\nEvery technical term (API, DOM, callback, middleware, etc.) gets a dashed-underline tooltip on first use in each module. Hover on desktop or tap on mobile to see a 1-2 sentence plain-English definition. The learner should never have to leave the page to Google a term. This is the difference between a course that *says* it's for non-technical people and one that actually *is*.\n\n**Be extremely aggressive with tooltips.** If there is even a 1% chance a non-technical person doesn't know a word, tooltip it. This includes:\n- Software names they might not know (Blender, GIMP, Audacity, etc.)\n- Everyday developer terms (REPL, JSON, flag, CLI, API, SDK, etc.)\n- Programming concepts (function, variable, dictionary, class, module, etc.)\n- Infrastructure terms (PATH, pip, namespace, entry point, etc.)\n- Acronyms — ALWAYS tooltip acronyms on first use\n\n**The vocabulary IS the learning.** One of the key goals is for learners to acquire the precise technical vocabulary they need to communicate with AI coding agents. Each tooltip should teach the term in a way that helps the learner USE it in their own instructions — e.g., \"A **flag** is an option you add to a command to change its behavior — like adding '--json' to get structured data instead of plain text. When talking to AI, you'd say 'add a flag for verbose output.'\"\n\n**Cursor:** Use `cursor: pointer` on terms (not `cursor: help`). The question-mark cursor feels clinical — a pointer feels clickable and inviting.\n\n**Tooltip overflow fix:** Translation blocks and other containers with `overflow: hidden` will clip tooltips. To fix this, the tooltip JS must use `position: fixed` and calculate coordinates from `getBoundingClientRect()` instead of relying on CSS `position: absolute` within the container. Append tooltips to `document.body` rather than inside the term element. This ensures tooltips are never clipped by any ancestor's overflow.\n\n### Quizzes That Test Application, Not Memory\n\nThe goal of learning is practical application — being able to *do something* with what you learned. Quizzes should test whether the learner can use their knowledge to solve a new problem, not whether they can regurgitate a definition.\n\n**What to quiz (in order of value):**\n1. **\"What would you do?\" scenarios** — Present a new situation the learner hasn't seen and ask them to apply what they learned. e.g., \"You want to add a 'save to favorites' feature. Which files would you need to change?\" This is the gold standard.\n2. **Debugging scenarios** — \"A user reports X is broken. Based on what you learned, where would you look first?\" This tests whether they understood the architecture, not just memorized file names.\n3. **Architecture decisions** — \"You're building a similar app from scratch. Would you put this logic in the frontend or backend? Why?\" Tests whether they understood the *reasoning* behind design choices.\n4. **Tracing exercises** — \"When a user does X, trace the path the data takes.\" Tests whether they can follow the flow.\n\n**What NOT to quiz:**\n- Definitions (\"What does API stand for?\") — that's what the glossary tooltips are for\n- File name recall (\"Which file handles X?\") — nobody memorizes file names\n- Syntax details (\"What's the correct way to write a fetch call?\") — this isn't a coding bootcamp\n- Anything that can be answered by scrolling up and copying — that tests scrolling, not understanding\n\n**Quiz tone:**\n- Wrong answers get encouraging, non-judgmental explanations (\"Not quite — here's why...\")\n- Correct answers get brief reinforcement of the underlying principle (\"Exactly! This works because...\")\n- Never punitive, never score-focused. No \"You got 3/5!\" — the quiz is a thinking exercise, not an exam\n- Wrong answer explanations should teach something new, not just say \"wrong, the answer was B\"\n\n**How many quizzes:** One per module, placed at the end after the learner has seen all the content. 3-5 questions per quiz. Each question should make the learner pause and *think*, not just pick the obvious answer.\n\n**Deciding what concepts are worth quizzing:** Quiz the things that would actually help someone in practice — architecture understanding (\"where does this logic live and why?\"), debugging intuition (\"what would cause this symptom?\"), and decision-making (\"what's the tradeoff here?\"). If a concept won't help someone debug a problem, steer an AI assistant, or make an architectural decision, it's not worth quizzing.\n","references/design-system.md":"# Design System Reference\n\nComplete CSS design tokens for the course. Copy this entire `:root` block into the course HTML and adapt the accent color to suit the project's personality.\n\n## Table of Contents\n1. [Color Palette](#color-palette)\n2. [Typography](#typography)\n3. [Spacing & Layout](#spacing--layout)\n4. [Shadows & Depth](#shadows--depth)\n5. [Animations & Transitions](#animations--transitions)\n6. [Navigation & Progress](#navigation--progress)\n7. [Module Structure](#module-structure)\n8. [Responsive Breakpoints](#responsive-breakpoints)\n9. [Scrollbar & Background](#scrollbar--background)\n\n---\n\n## Color Palette\n\n```css\n:root {\n  /* --- BACKGROUNDS --- */\n  --color-bg:             #FAF7F2;       /* warm off-white, like aged paper */\n  --color-bg-warm:        #F5F0E8;       /* slightly warmer for alternating modules */\n  --color-bg-code:        #1E1E2E;       /* deep indigo-charcoal for code blocks */\n  --color-text:           #2C2A28;       /* dark charcoal, easy on eyes */\n  --color-text-secondary: #6B6560;       /* warm gray for secondary text */\n  --color-text-muted:     #9E9790;       /* muted for timestamps, labels */\n  --color-border:         #E5DFD6;       /* subtle warm border */\n  --color-border-light:   #EEEBE5;       /* even lighter border */\n  --color-surface:        #FFFFFF;       /* card surfaces */\n  --color-surface-warm:   #FDF9F3;       /* warm card surface */\n\n  /* --- ACCENT (adapt per project — pick ONE bold color) ---\n     Default: vermillion. Alternatives: coral (#E06B56), teal (#2A7B9B),\n     amber (#D4A843), forest (#2D8B55). Avoid purple gradients. */\n  --color-accent:         #D94F30;\n  --color-accent-hover:   #C4432A;\n  --color-accent-light:   #FDEEE9;\n  --color-accent-muted:   #E8836C;\n\n  /* --- SEMANTIC --- */\n  --color-success:        #2D8B55;\n  --color-success-light:  #E8F5EE;\n  --color-error:          #C93B3B;\n  --color-error-light:    #FDE8E8;\n  --color-info:           #2A7B9B;\n  --color-info-light:     #E4F2F7;\n\n  /* --- ACTOR COLORS (assign to main components) ---\n     Each major \"character\" in the codebase gets a distinct color\n     for chat bubbles, diagrams, and highlights */\n  --color-actor-1:        #D94F30;       /* vermillion */\n  --color-actor-2:        #2A7B9B;       /* teal */\n  --color-actor-3:        #7B6DAA;       /* muted plum */\n  --color-actor-4:        #D4A843;       /* golden */\n  --color-actor-5:        #2D8B55;       /* forest */\n}\n```\n\n**Rules:**\n- Even-numbered modules use `--color-bg`, odd-numbered use `--color-bg-warm` (alternating backgrounds create visual rhythm)\n- Actor colors should be visually distinct from each other and from the accent\n- Code blocks always use `--color-bg-code` with light text\n\n---\n\n## Typography\n\n```css\n:root {\n  /* --- FONTS ---\n     Display: bold, geometric, personality-driven. NOT Inter/Roboto/Arial.\n     Body: readable with character. NOT system fonts.\n     Mono: developer-friendly with clear character distinction. */\n  --font-display:  'Bricolage Grotesque', Georgia, serif;\n  --font-body:     'DM Sans', -apple-system, sans-serif;\n  --font-mono:     'JetBrains Mono', 'Fira Code', 'Consolas', monospace;\n\n  /* --- TYPE SCALE (1.25 ratio) --- */\n  --text-xs:   0.75rem;    /* 12px — labels, badges */\n  --text-sm:   0.875rem;   /* 14px — secondary text, code */\n  --text-base: 1rem;       /* 16px — body text */\n  --text-lg:   1.125rem;   /* 18px — lead paragraphs */\n  --text-xl:   1.25rem;    /* 20px — screen headings */\n  --text-2xl:  1.5rem;     /* 24px — sub-module titles */\n  --text-3xl:  1.875rem;   /* 30px — module subtitles */\n  --text-4xl:  2.25rem;    /* 36px — module titles */\n  --text-5xl:  3rem;       /* 48px — hero text */\n  --text-6xl:  3.75rem;    /* 60px — module numbers */\n\n  /* --- LINE HEIGHTS --- */\n  --leading-tight:  1.15;  /* headings */\n  --leading-snug:   1.3;   /* subheadings */\n  --leading-normal: 1.6;   /* body text */\n  --leading-loose:  1.8;   /* relaxed reading */\n}\n```\n\n**Google Fonts link (put in `<head>`):**\n```html\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400;1,9..40,500&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n```\n\n**Rules:**\n- Module numbers: `--text-6xl`, font-display, weight 800, `--color-accent` with 15% opacity\n- Module titles: `--text-4xl`, font-display, weight 700\n- Screen headings: `--text-xl` or `--text-2xl`, font-display, weight 600\n- Body text: `--text-base` or `--text-lg`, font-body, `--leading-normal`\n- Code: `--text-sm`, font-mono\n- Labels/badges: `--text-xs`, font-mono, uppercase, letter-spacing 0.05em\n\n---\n\n## Spacing & Layout\n\n```css\n:root {\n  --space-1:  0.25rem;   /* 4px */\n  --space-2:  0.5rem;    /* 8px */\n  --space-3:  0.75rem;   /* 12px */\n  --space-4:  1rem;      /* 16px */\n  --space-5:  1.25rem;   /* 20px */\n  --space-6:  1.5rem;    /* 24px */\n  --space-8:  2rem;      /* 32px */\n  --space-10: 2.5rem;    /* 40px */\n  --space-12: 3rem;      /* 48px */\n  --space-16: 4rem;      /* 64px */\n  --space-20: 5rem;      /* 80px */\n  --space-24: 6rem;      /* 96px */\n\n  --content-width:     800px;   /* standard reading width */\n  --content-width-wide: 1000px; /* for side-by-side layouts */\n  --nav-height:        50px;\n  --radius-sm:  8px;\n  --radius-md:  12px;\n  --radius-lg:  16px;\n  --radius-full: 9999px;\n}\n```\n\n**Module layout:**\n```css\n.module {\n  min-height: 100dvh;       /* fallback: 100vh */\n  scroll-snap-align: start;\n  padding: var(--space-16) var(--space-6);\n  padding-top: calc(var(--nav-height) + var(--space-12));\n}\n.module-content {\n  max-width: var(--content-width);\n  margin: 0 auto;\n}\n```\n\n---\n\n## Shadows & Depth\n\n```css\n:root {\n  --shadow-sm:  0 1px 2px rgba(44, 42, 40, 0.05);\n  --shadow-md:  0 4px 12px rgba(44, 42, 40, 0.08);\n  --shadow-lg:  0 8px 24px rgba(44, 42, 40, 0.1);\n  --shadow-xl:  0 16px 48px rgba(44, 42, 40, 0.12);\n}\n```\n\nUse warm-tinted RGBA (44, 42, 40) — never pure black shadows.\n\n---\n\n## Animations & Transitions\n\n```css\n:root {\n  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);\n  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);\n  --duration-fast:   150ms;\n  --duration-normal: 300ms;\n  --duration-slow:   500ms;\n  --stagger-delay:   120ms;\n}\n```\n\n**Scroll-triggered reveal pattern:**\n```css\n.animate-in {\n  opacity: 0;\n  transform: translateY(20px);\n  transition: opacity var(--duration-slow) var(--ease-out),\n              transform var(--duration-slow) var(--ease-out);\n}\n.animate-in.visible {\n  opacity: 1;\n  transform: translateY(0);\n}\n\n/* Stagger children */\n.stagger-children > .animate-in {\n  transition-delay: calc(var(--stagger-index, 0) * var(--stagger-delay));\n}\n```\n\n**JS setup for stagger:**\n```javascript\ndocument.querySelectorAll('.stagger-children').forEach(parent => {\n  Array.from(parent.children).forEach((child, i) => {\n    child.style.setProperty('--stagger-index', i);\n  });\n});\n```\n\n**Intersection Observer (trigger reveals):**\n```javascript\nconst observer = new IntersectionObserver((entries) => {\n  entries.forEach(entry => {\n    if (entry.isIntersecting) {\n      entry.target.classList.add('visible');\n      observer.unobserve(entry.target); // animate only once\n    }\n  });\n}, { rootMargin: '0px 0px -10% 0px', threshold: 0.1 });\n\ndocument.querySelectorAll('.animate-in').forEach(el => observer.observe(el));\n```\n\n---\n\n## Navigation & Progress\n\n**HTML structure:**\n```html\n<nav class=\"nav\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-valuenow=\"0\"></div>\n  <div class=\"nav-inner\">\n    <span class=\"nav-title\">Course Title</span>\n    <div class=\"nav-dots\">\n      <button class=\"nav-dot\" data-target=\"module-1\" data-tooltip=\"Module 1 Name\"\n              role=\"tab\" aria-label=\"Module 1\"></button>\n      <!-- one per module -->\n    </div>\n  </div>\n</nav>\n```\n\n**Progress bar (CSS-only where possible, JS fallback):**\n```javascript\nfunction updateProgressBar() {\n  const scrollTop = window.scrollY;\n  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;\n  const progress = (scrollTop / scrollHeight) * 100;\n  progressBar.style.width = progress + '%';\n}\nwindow.addEventListener('scroll', () => {\n  requestAnimationFrame(updateProgressBar);\n}, { passive: true });\n```\n\n**Nav dot states:**\n- Default: `border: 2px solid var(--color-text-muted)`, empty center\n- Current: `border-color: var(--color-accent)`, filled center, subtle glow shadow\n- Visited: `background: var(--color-accent)`, filled solid\n\n**Keyboard navigation:**\n```javascript\ndocument.addEventListener('keydown', (e) => {\n  if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) return;\n  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { nextModule(); e.preventDefault(); }\n  if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { prevModule(); e.preventDefault(); }\n});\n```\n\n---\n\n## Module Structure\n\n**HTML template for each module:**\n```html\n<section class=\"module\" id=\"module-N\" style=\"background: var(--color-bg or --color-bg-warm)\">\n  <div class=\"module-content\">\n    <header class=\"module-header animate-in\">\n      <span class=\"module-number\">0N</span>\n      <h1 class=\"module-title\">Module Title</h1>\n      <p class=\"module-subtitle\">One-line description of what this module teaches</p>\n    </header>\n\n    <div class=\"module-body\">\n      <section class=\"screen animate-in\">\n        <h2 class=\"screen-heading\">Screen Title</h2>\n        <p>Content...</p>\n        <!-- Interactive elements, code translations, etc. -->\n      </section>\n\n      <section class=\"screen animate-in\">\n        <!-- Next screen -->\n      </section>\n    </div>\n  </div>\n</section>\n```\n\n---\n\n## Responsive Breakpoints\n\n```css\n/* Tablet */\n@media (max-width: 768px) {\n  :root {\n    --text-4xl: 1.875rem;\n    --text-5xl: 2.25rem;\n    --text-6xl: 3rem;\n  }\n  .translation-block { grid-template-columns: 1fr; } /* stack code/english */\n  .pattern-cards { grid-template-columns: 1fr 1fr; }\n}\n\n/* Mobile */\n@media (max-width: 480px) {\n  :root {\n    --text-4xl: 1.5rem;\n    --text-5xl: 1.875rem;\n    --text-6xl: 2.25rem;\n  }\n  .module { padding: var(--space-8) var(--space-4); }\n  .pattern-cards { grid-template-columns: 1fr; }\n  .flow-steps { flex-direction: column; }\n  .flow-arrow { transform: rotate(90deg); }\n}\n```\n\n---\n\n## Scrollbar & Background\n\n```css\n/* Custom scrollbar */\n::-webkit-scrollbar { width: 6px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: var(--radius-full);\n}\n\n/* Subtle atmospheric background */\nbody {\n  background: var(--color-bg);\n  background-image: radial-gradient(\n    ellipse at 20% 50%,\n    rgba(217, 79, 48, 0.03) 0%,\n    transparent 50%\n  );\n}\n\n/* Page scroll setup */\nhtml {\n  scroll-snap-type: y proximity;\n  scroll-behavior: smooth;\n}\n```\n\n---\n\n## Code Block Globals\n\nAll code blocks in the course — whether inside translation blocks, standalone snippets, or quiz challenges — must wrap text and never show a horizontal scrollbar. This is a teaching tool, not an IDE.\n\n```css\npre, code {\n  white-space: pre-wrap;       /* wrap long lines */\n  word-break: break-word;      /* break mid-word if absolutely needed */\n  overflow-x: hidden;          /* no horizontal scrollbar — ever */\n}\n/* Hide scrollbars on code containers */\n.translation-code::-webkit-scrollbar,\npre::-webkit-scrollbar {\n  display: none;\n}\n```\n\nCode snippets must be **exact copies** from the real codebase — never modified, trimmed, or simplified. Instead, choose naturally short (5-10 line) sections from the code that illustrate the concept well. If a longer block is needed, show it all — the wrapping CSS will handle readability.\n\n---\n\n## Syntax Highlighting (Catppuccin-inspired)\n\nFor code blocks on the dark `--color-bg-code` background:\n\n```css\n.code-keyword  { color: #CBA6F7; }  /* purple — if, else, return, function */\n.code-string   { color: #A6E3A1; }  /* green — \"strings\" */\n.code-function { color: #89B4FA; }  /* blue — function names */\n.code-comment  { color: #6C7086; }  /* muted gray — // comments */\n.code-number   { color: #FAB387; }  /* peach — numbers */\n.code-property { color: #F9E2AF; }  /* yellow — object keys */\n.code-operator { color: #94E2D5; }  /* teal — =, =>, +, etc. */\n.code-tag      { color: #F38BA8; }  /* pink — HTML tags */\n.code-attr     { color: #F9E2AF; }  /* yellow — HTML attributes */\n.code-value    { color: #A6E3A1; }  /* green — attribute values */\n```\n","references/gotchas.md":"# Gotchas — Common Failure Points\n\n> **When to read this:** During Phase 3 (writing module HTML) and Phase 4 (review). Check every one of these before considering a course complete.\n\nThese are real problems encountered when building courses. Check every one before considering a course complete.\n\n### Tooltip Clipping\nTranslation blocks use `overflow: hidden` for code wrapping. If tooltips use `position: absolute` inside the term element, they get clipped by the container. **Fix:** Tooltips must use `position: fixed` and be appended to `document.body`. Calculate position from `getBoundingClientRect()`. This is already handled by `main.js` but is the #1 bug that appears in every build.\n\n### Not Enough Tooltips\nThe most common failure is under-tooltipping. Non-technical learners don't know terms like REPL, JSON, flag, entry point, PATH, pip, namespace, function, class, module, PR, E2E, or even software names like Blender/GIMP. **Rule of thumb:** if a term wouldn't appear in everyday conversation with a non-technical friend, tooltip it. Err heavily on the side of too many. BUT: don't tooltip terms the user already knows well from their domain (e.g., AI/ML concepts for someone in AI).\n\n### Walls of Text\nThe course looks like a textbook instead of an infographic. This happens when you write more than 2-3 sentences in a row without a visual break. Every screen must be at least 50% visual. Convert any list of 3+ items into cards, any sequence into step cards or flow diagrams, any code explanation into a code↔English translation block.\n\n### Recycled Metaphors\nUsing \"restaurant\" or \"kitchen\" for everything. Every module needs its own metaphor that feels inevitable for that specific concept. If you catch yourself reaching for the same metaphor twice, stop and find one that fits the concept organically.\n\n### Code Modifications\nTrimming, simplifying, or \"cleaning up\" code snippets from the codebase. The learner should be able to open the real file and see the exact same code. Instead of editing code to be shorter, *choose* naturally short snippets (5-10 lines) from the codebase that illustrate the point.\n\n### Quiz Questions That Test Memory\nAsking \"What does API stand for?\" or \"Which file handles X?\" — those test recall, not understanding. Every quiz question should present a new scenario the learner hasn't seen and ask them to *apply* what they learned.\n\n### Scroll-Snap Mandatory\nUsing `scroll-snap-type: y mandatory` traps users inside long modules. Always use `proximity`.\n\n### Module Quality Degradation\nTrying to write all modules in one pass causes later modules to be thin and rushed. Build one module at a time and verify each before moving on. For complex codebases, use the parallel path with module briefs.\n\n### Missing Interactive Elements\nA module with only text and code blocks, no interactivity. Every module needs at least one of: quiz, data flow animation, group chat, architecture diagram, drag-and-drop. These aren't decorations — they're how non-technical learners actually process information.\n"},"items":[{"name":"content-philosophy.md","path":"references/content-philosophy.md","rawUrl":"https://raw.githubusercontent.com/zarazhangrui/codebase-to-course/HEAD/references/content-philosophy.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Content Philosophy\n\n> **When to read this:** During Phase 2.5 (writing module briefs) and Phase 3 (writing module HTML). These principles guide every content decision — what to show, how to explain it, and how to test understanding.\n\nThese principles are what separate a great course from a generic tutorial. They should guide every content decision:\n\n### Show, Don't Tell — Aggressively Visual\nPeople's eyes glaze over text blocks. The course should feel closer to an infographic than a textbook. Follow these hard rules:\n\n**Text limits:**\n- Max **2-3 sentences** per text block. If you're writing a fourth sentence, stop and convert it into a visual instead.\n- No text block should ever be wider than the content width AND taller than ~4 lines. If it is, break it up with a visual element.\n- Every screen must be **at least 50% visual** (diagrams, code blocks, cards, animations, badges — anything that isn't a paragraph).\n\n**Convert text to visuals:**\n- A list of 3+ items → **cards with icons** (pattern cards, feature cards)\n- A sequence of steps → **flow diagram with arrows** or **numbered step cards**\n- \"Component A talks to Component B\" → **animated data flow** or **group chat visualization**\n- \"This file does X, that file does Y\" → **visual file tree with annotations** or **icon + one-liner badges**\n- Explaining what code does → **code↔English translation block** (not a paragraph *about* the code)\n- Comparing two approaches → **side-by-side columns** with visual contrast\n\n**Visual breathing room:**\n- Use generous spacing between elements (`--space-8` to `--space-12` between sections)\n- Alternate between full-width visuals and narrow text blocks to create rhythm\n- Every module should have at least one \"hero visual\" — a diagram, animation, or interactive element that dominates the screen and teaches the core concept at a glance\n\n### Code ↔ English Translations\nEvery code snippet gets a side-by-side plain English translation. Left panel: real code from the project with syntax highlighting. Right panel: line-by-line plain English explaining what each line does. This is the single most valuable teaching tool for non-technical learners.\n\n**Critical: No horizontal scrollbars on code.** All code must use `white-space: pre-wrap` so it wraps instead of scrolling. This is a course for non-technical people, not an IDE — readability beats preserving indentation structure.\n\n**Critical: Use original code exactly as-is.** Never modify, simplify, or trim code snippets from the codebase. The learner should be able to open the real file and see the exact same code they learned from — that builds trust. Instead of editing code to make it shorter, *choose* naturally short, punchy snippets (5-10 lines) from the codebase that illustrate the concept well. Every codebase has compact, self-contained moments — find those rather than butchering longer functions.\n\n### One Concept Per Screen\nNo walls of text. Each screen within a module teaches exactly one idea. If you need more space, add another screen — don't cram.\n\n### Metaphors First, Then Reality\nIntroduce every new concept with a metaphor from everyday life. Then immediately ground it: \"In our code, this looks like...\" The metaphor builds intuition; the code grounds it in reality.\n\n**Critical: No recycled metaphors.** Do NOT default to \"restaurant\" for everything — that's the #1 crutch. Each concept deserves its own metaphor that feels natural to *that specific idea*. A database is a library with a card catalog. Auth is a bouncer checking IDs. An event loop is an air traffic controller. Message passing is a postal system. API rate limiting is a nightclub with a capacity limit. Pick the metaphor that makes the concept click, not the one that's easiest to reach for. If you catch yourself using \"restaurant\" or \"kitchen\" more than once in a course, stop and rethink.\n\n### Learn by Tracing\nFollow what actually happens when the learner does something they already do every day in the app — trace the data flow end-to-end. \"You know that button you click? Here's the journey your data takes after you click it...\" This works because the learner has *already experienced the result* — now they're seeing the machinery behind it. It's like watching a behind-the-scenes documentary of a movie you loved.\n\n### Make It Memorable\nUse \"aha!\" callout boxes for universal CS insights. Use humor where natural (not forced). Give components personality — they're \"characters\" in a story, not abstract boxes on a diagram.\n\n### Glossary Tooltips — No Term Left Behind\nEvery technical term (API, DOM, callback, middleware, etc.) gets a dashed-underline tooltip on first use in each module. Hover on desktop or tap on mobile to see a 1-2 sentence plain-English definition. The learner should never have to leave the page to Google a term. This is the difference between a course that *says* it's for non-technical people and one that actually *is*.\n\n**Be extremely aggressive with tooltips.** If there is even a 1% chance a non-technical person doesn't know a word, tooltip it. This includes:\n- Software names they might not know (Blender, GIMP, Audacity, etc.)\n- Everyday developer terms (REPL, JSON, flag, CLI, API, SDK, etc.)\n- Programming concepts (function, variable, dictionary, class, module, etc.)\n- Infrastructure terms (PATH, pip, namespace, entry point, etc.)\n- Acronyms — ALWAYS tooltip acronyms on first use\n\n**The vocabulary IS the learning.** One of the key goals is for learners to acquire the precise technical vocabulary they need to communicate with AI coding agents. Each tooltip should teach the term in a way that helps the learner USE it in their own instructions — e.g., \"A **flag** is an option you add to a command to change its behavior — like adding '--json' to get structured data instead of plain text. When talking to AI, you'd say 'add a flag for verbose output.'\"\n\n**Cursor:** Use `cursor: pointer` on terms (not `cursor: help`). The question-mark cursor feels clinical — a pointer feels clickable and inviting.\n\n**Tooltip overflow fix:** Translation blocks and other containers with `overflow: hidden` will clip tooltips. To fix this, the tooltip JS must use `position: fixed` and calculate coordinates from `getBoundingClientRect()` instead of relying on CSS `position: absolute` within the container. Append tooltips to `document.body` rather than inside the term element. This ensures tooltips are never clipped by any ancestor's overflow.\n\n### Quizzes That Test Application, Not Memory\n\nThe goal of learning is practical application — being able to *do something* with what you learned. Quizzes should test whether the learner can use their knowledge to solve a new problem, not whether they can regurgitate a definition.\n\n**What to quiz (in order of value):**\n1. **\"What would you do?\" scenarios** — Present a new situation the learner hasn't seen and ask them to apply what they learned. e.g., \"You want to add a 'save to favorites' feature. Which files would you need to change?\" This is the gold standard.\n2. **Debugging scenarios** — \"A user reports X is broken. Based on what you learned, where would you look first?\" This tests whether they understood the architecture, not just memorized file names.\n3. **Architecture decisions** — \"You're building a similar app from scratch. Would you put this logic in the frontend or backend? Why?\" Tests whether they understood the *reasoning* behind design choices.\n4. **Tracing exercises** — \"When a user does X, trace the path the data takes.\" Tests whether they can follow the flow.\n\n**What NOT to quiz:**\n- Definitions (\"What does API stand for?\") — that's what the glossary tooltips are for\n- File name recall (\"Which file handles X?\") — nobody memorizes file names\n- Syntax details (\"What's the correct way to write a fetch call?\") — this isn't a coding bootcamp\n- Anything that can be answered by scrolling up and copying — that tests scrolling, not understanding\n\n**Quiz tone:**\n- Wrong answers get encouraging, non-judgmental explanations (\"Not quite — here's why...\")\n- Correct answers get brief reinforcement of the underlying principle (\"Exactly! This works because...\")\n- Never punitive, never score-focused. No \"You got 3/5!\" — the quiz is a thinking exercise, not an exam\n- Wrong answer explanations should teach something new, not just say \"wrong, the answer was B\"\n\n**How many quizzes:** One per module, placed at the end after the learner has seen all the content. 3-5 questions per quiz. Each question should make the learner pause and *think*, not just pick the obvious answer.\n\n**Deciding what concepts are worth quizzing:** Quiz the things that would actually help someone in practice — architecture understanding (\"where does this logic live and why?\"), debugging intuition (\"what would cause this symptom?\"), and decision-making (\"what's the tradeoff here?\"). If a concept won't help someone debug a problem, steer an AI assistant, or make an architectural decision, it's not worth quizzing.\n","isInternal":false,"tokens":1973,"sizeBytes":9047},{"name":"design-system.md","path":"references/design-system.md","rawUrl":"https://raw.githubusercontent.com/zarazhangrui/codebase-to-course/HEAD/references/design-system.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Design System Reference\n\nComplete CSS design tokens for the course. Copy this entire `:root` block into the course HTML and adapt the accent color to suit the project's personality.\n\n## Table of Contents\n1. [Color Palette](#color-palette)\n2. [Typography](#typography)\n3. [Spacing & Layout](#spacing--layout)\n4. [Shadows & Depth](#shadows--depth)\n5. [Animations & Transitions](#animations--transitions)\n6. [Navigation & Progress](#navigation--progress)\n7. [Module Structure](#module-structure)\n8. [Responsive Breakpoints](#responsive-breakpoints)\n9. [Scrollbar & Background](#scrollbar--background)\n\n---\n\n## Color Palette\n\n```css\n:root {\n  /* --- BACKGROUNDS --- */\n  --color-bg:             #FAF7F2;       /* warm off-white, like aged paper */\n  --color-bg-warm:        #F5F0E8;       /* slightly warmer for alternating modules */\n  --color-bg-code:        #1E1E2E;       /* deep indigo-charcoal for code blocks */\n  --color-text:           #2C2A28;       /* dark charcoal, easy on eyes */\n  --color-text-secondary: #6B6560;       /* warm gray for secondary text */\n  --color-text-muted:     #9E9790;       /* muted for timestamps, labels */\n  --color-border:         #E5DFD6;       /* subtle warm border */\n  --color-border-light:   #EEEBE5;       /* even lighter border */\n  --color-surface:        #FFFFFF;       /* card surfaces */\n  --color-surface-warm:   #FDF9F3;       /* warm card surface */\n\n  /* --- ACCENT (adapt per project — pick ONE bold color) ---\n     Default: vermillion. Alternatives: coral (#E06B56), teal (#2A7B9B),\n     amber (#D4A843), forest (#2D8B55). Avoid purple gradients. */\n  --color-accent:         #D94F30;\n  --color-accent-hover:   #C4432A;\n  --color-accent-light:   #FDEEE9;\n  --color-accent-muted:   #E8836C;\n\n  /* --- SEMANTIC --- */\n  --color-success:        #2D8B55;\n  --color-success-light:  #E8F5EE;\n  --color-error:          #C93B3B;\n  --color-error-light:    #FDE8E8;\n  --color-info:           #2A7B9B;\n  --color-info-light:     #E4F2F7;\n\n  /* --- ACTOR COLORS (assign to main components) ---\n     Each major \"character\" in the codebase gets a distinct color\n     for chat bubbles, diagrams, and highlights */\n  --color-actor-1:        #D94F30;       /* vermillion */\n  --color-actor-2:        #2A7B9B;       /* teal */\n  --color-actor-3:        #7B6DAA;       /* muted plum */\n  --color-actor-4:        #D4A843;       /* golden */\n  --color-actor-5:        #2D8B55;       /* forest */\n}\n```\n\n**Rules:**\n- Even-numbered modules use `--color-bg`, odd-numbered use `--color-bg-warm` (alternating backgrounds create visual rhythm)\n- Actor colors should be visually distinct from each other and from the accent\n- Code blocks always use `--color-bg-code` with light text\n\n---\n\n## Typography\n\n```css\n:root {\n  /* --- FONTS ---\n     Display: bold, geometric, personality-driven. NOT Inter/Roboto/Arial.\n     Body: readable with character. NOT system fonts.\n     Mono: developer-friendly with clear character distinction. */\n  --font-display:  'Bricolage Grotesque', Georgia, serif;\n  --font-body:     'DM Sans', -apple-system, sans-serif;\n  --font-mono:     'JetBrains Mono', 'Fira Code', 'Consolas', monospace;\n\n  /* --- TYPE SCALE (1.25 ratio) --- */\n  --text-xs:   0.75rem;    /* 12px — labels, badges */\n  --text-sm:   0.875rem;   /* 14px — secondary text, code */\n  --text-base: 1rem;       /* 16px — body text */\n  --text-lg:   1.125rem;   /* 18px — lead paragraphs */\n  --text-xl:   1.25rem;    /* 20px — screen headings */\n  --text-2xl:  1.5rem;     /* 24px — sub-module titles */\n  --text-3xl:  1.875rem;   /* 30px — module subtitles */\n  --text-4xl:  2.25rem;    /* 36px — module titles */\n  --text-5xl:  3rem;       /* 48px — hero text */\n  --text-6xl:  3.75rem;    /* 60px — module numbers */\n\n  /* --- LINE HEIGHTS --- */\n  --leading-tight:  1.15;  /* headings */\n  --leading-snug:   1.3;   /* subheadings */\n  --leading-normal: 1.6;   /* body text */\n  --leading-loose:  1.8;   /* relaxed reading */\n}\n```\n\n**Google Fonts link (put in `<head>`):**\n```html\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400;1,9..40,500&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n```\n\n**Rules:**\n- Module numbers: `--text-6xl`, font-display, weight 800, `--color-accent` with 15% opacity\n- Module titles: `--text-4xl`, font-display, weight 700\n- Screen headings: `--text-xl` or `--text-2xl`, font-display, weight 600\n- Body text: `--text-base` or `--text-lg`, font-body, `--leading-normal`\n- Code: `--text-sm`, font-mono\n- Labels/badges: `--text-xs`, font-mono, uppercase, letter-spacing 0.05em\n\n---\n\n## Spacing & Layout\n\n```css\n:root {\n  --space-1:  0.25rem;   /* 4px */\n  --space-2:  0.5rem;    /* 8px */\n  --space-3:  0.75rem;   /* 12px */\n  --space-4:  1rem;      /* 16px */\n  --space-5:  1.25rem;   /* 20px */\n  --space-6:  1.5rem;    /* 24px */\n  --space-8:  2rem;      /* 32px */\n  --space-10: 2.5rem;    /* 40px */\n  --space-12: 3rem;      /* 48px */\n  --space-16: 4rem;      /* 64px */\n  --space-20: 5rem;      /* 80px */\n  --space-24: 6rem;      /* 96px */\n\n  --content-width:     800px;   /* standard reading width */\n  --content-width-wide: 1000px; /* for side-by-side layouts */\n  --nav-height:        50px;\n  --radius-sm:  8px;\n  --radius-md:  12px;\n  --radius-lg:  16px;\n  --radius-full: 9999px;\n}\n```\n\n**Module layout:**\n```css\n.module {\n  min-height: 100dvh;       /* fallback: 100vh */\n  scroll-snap-align: start;\n  padding: var(--space-16) var(--space-6);\n  padding-top: calc(var(--nav-height) + var(--space-12));\n}\n.module-content {\n  max-width: var(--content-width);\n  margin: 0 auto;\n}\n```\n\n---\n\n## Shadows & Depth\n\n```css\n:root {\n  --shadow-sm:  0 1px 2px rgba(44, 42, 40, 0.05);\n  --shadow-md:  0 4px 12px rgba(44, 42, 40, 0.08);\n  --shadow-lg:  0 8px 24px rgba(44, 42, 40, 0.1);\n  --shadow-xl:  0 16px 48px rgba(44, 42, 40, 0.12);\n}\n```\n\nUse warm-tinted RGBA (44, 42, 40) — never pure black shadows.\n\n---\n\n## Animations & Transitions\n\n```css\n:root {\n  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);\n  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);\n  --duration-fast:   150ms;\n  --duration-normal: 300ms;\n  --duration-slow:   500ms;\n  --stagger-delay:   120ms;\n}\n```\n\n**Scroll-triggered reveal pattern:**\n```css\n.animate-in {\n  opacity: 0;\n  transform: translateY(20px);\n  transition: opacity var(--duration-slow) var(--ease-out),\n              transform var(--duration-slow) var(--ease-out);\n}\n.animate-in.visible {\n  opacity: 1;\n  transform: translateY(0);\n}\n\n/* Stagger children */\n.stagger-children > .animate-in {\n  transition-delay: calc(var(--stagger-index, 0) * var(--stagger-delay));\n}\n```\n\n**JS setup for stagger:**\n```javascript\ndocument.querySelectorAll('.stagger-children').forEach(parent => {\n  Array.from(parent.children).forEach((child, i) => {\n    child.style.setProperty('--stagger-index', i);\n  });\n});\n```\n\n**Intersection Observer (trigger reveals):**\n```javascript\nconst observer = new IntersectionObserver((entries) => {\n  entries.forEach(entry => {\n    if (entry.isIntersecting) {\n      entry.target.classList.add('visible');\n      observer.unobserve(entry.target); // animate only once\n    }\n  });\n}, { rootMargin: '0px 0px -10% 0px', threshold: 0.1 });\n\ndocument.querySelectorAll('.animate-in').forEach(el => observer.observe(el));\n```\n\n---\n\n## Navigation & Progress\n\n**HTML structure:**\n```html\n<nav class=\"nav\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-valuenow=\"0\"></div>\n  <div class=\"nav-inner\">\n    <span class=\"nav-title\">Course Title</span>\n    <div class=\"nav-dots\">\n      <button class=\"nav-dot\" data-target=\"module-1\" data-tooltip=\"Module 1 Name\"\n              role=\"tab\" aria-label=\"Module 1\"></button>\n      <!-- one per module -->\n    </div>\n  </div>\n</nav>\n```\n\n**Progress bar (CSS-only where possible, JS fallback):**\n```javascript\nfunction updateProgressBar() {\n  const scrollTop = window.scrollY;\n  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;\n  const progress = (scrollTop / scrollHeight) * 100;\n  progressBar.style.width = progress + '%';\n}\nwindow.addEventListener('scroll', () => {\n  requestAnimationFrame(updateProgressBar);\n}, { passive: true });\n```\n\n**Nav dot states:**\n- Default: `border: 2px solid var(--color-text-muted)`, empty center\n- Current: `border-color: var(--color-accent)`, filled center, subtle glow shadow\n- Visited: `background: var(--color-accent)`, filled solid\n\n**Keyboard navigation:**\n```javascript\ndocument.addEventListener('keydown', (e) => {\n  if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) return;\n  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { nextModule(); e.preventDefault(); }\n  if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { prevModule(); e.preventDefault(); }\n});\n```\n\n---\n\n## Module Structure\n\n**HTML template for each module:**\n```html\n<section class=\"module\" id=\"module-N\" style=\"background: var(--color-bg or --color-bg-warm)\">\n  <div class=\"module-content\">\n    <header class=\"module-header animate-in\">\n      <span class=\"module-number\">0N</span>\n      <h1 class=\"module-title\">Module Title</h1>\n      <p class=\"module-subtitle\">One-line description of what this module teaches</p>\n    </header>\n\n    <div class=\"module-body\">\n      <section class=\"screen animate-in\">\n        <h2 class=\"screen-heading\">Screen Title</h2>\n        <p>Content...</p>\n        <!-- Interactive elements, code translations, etc. -->\n      </section>\n\n      <section class=\"screen animate-in\">\n        <!-- Next screen -->\n      </section>\n    </div>\n  </div>\n</section>\n```\n\n---\n\n## Responsive Breakpoints\n\n```css\n/* Tablet */\n@media (max-width: 768px) {\n  :root {\n    --text-4xl: 1.875rem;\n    --text-5xl: 2.25rem;\n    --text-6xl: 3rem;\n  }\n  .translation-block { grid-template-columns: 1fr; } /* stack code/english */\n  .pattern-cards { grid-template-columns: 1fr 1fr; }\n}\n\n/* Mobile */\n@media (max-width: 480px) {\n  :root {\n    --text-4xl: 1.5rem;\n    --text-5xl: 1.875rem;\n    --text-6xl: 2.25rem;\n  }\n  .module { padding: var(--space-8) var(--space-4); }\n  .pattern-cards { grid-template-columns: 1fr; }\n  .flow-steps { flex-direction: column; }\n  .flow-arrow { transform: rotate(90deg); }\n}\n```\n\n---\n\n## Scrollbar & Background\n\n```css\n/* Custom scrollbar */\n::-webkit-scrollbar { width: 6px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: var(--radius-full);\n}\n\n/* Subtle atmospheric background */\nbody {\n  background: var(--color-bg);\n  background-image: radial-gradient(\n    ellipse at 20% 50%,\n    rgba(217, 79, 48, 0.03) 0%,\n    transparent 50%\n  );\n}\n\n/* Page scroll setup */\nhtml {\n  scroll-snap-type: y proximity;\n  scroll-behavior: smooth;\n}\n```\n\n---\n\n## Code Block Globals\n\nAll code blocks in the course — whether inside translation blocks, standalone snippets, or quiz challenges — must wrap text and never show a horizontal scrollbar. This is a teaching tool, not an IDE.\n\n```css\npre, code {\n  white-space: pre-wrap;       /* wrap long lines */\n  word-break: break-word;      /* break mid-word if absolutely needed */\n  overflow-x: hidden;          /* no horizontal scrollbar — ever */\n}\n/* Hide scrollbars on code containers */\n.translation-code::-webkit-scrollbar,\npre::-webkit-scrollbar {\n  display: none;\n}\n```\n\nCode snippets must be **exact copies** from the real codebase — never modified, trimmed, or simplified. Instead, choose naturally short (5-10 line) sections from the code that illustrate the concept well. If a longer block is needed, show it all — the wrapping CSS will handle readability.\n\n---\n\n## Syntax Highlighting (Catppuccin-inspired)\n\nFor code blocks on the dark `--color-bg-code` background:\n\n```css\n.code-keyword  { color: #CBA6F7; }  /* purple — if, else, return, function */\n.code-string   { color: #A6E3A1; }  /* green — \"strings\" */\n.code-function { color: #89B4FA; }  /* blue — function names */\n.code-comment  { color: #6C7086; }  /* muted gray — // comments */\n.code-number   { color: #FAB387; }  /* peach — numbers */\n.code-property { color: #F9E2AF; }  /* yellow — object keys */\n.code-operator { color: #94E2D5; }  /* teal — =, =>, +, etc. */\n.code-tag      { color: #F38BA8; }  /* pink — HTML tags */\n.code-attr     { color: #F9E2AF; }  /* yellow — HTML attributes */\n.code-value    { color: #A6E3A1; }  /* green — attribute values */\n```\n","isInternal":false,"tokens":3837,"sizeBytes":12661},{"name":"gotchas.md","path":"references/gotchas.md","rawUrl":"https://raw.githubusercontent.com/zarazhangrui/codebase-to-course/HEAD/references/gotchas.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Gotchas — Common Failure Points\n\n> **When to read this:** During Phase 3 (writing module HTML) and Phase 4 (review). Check every one of these before considering a course complete.\n\nThese are real problems encountered when building courses. Check every one before considering a course complete.\n\n### Tooltip Clipping\nTranslation blocks use `overflow: hidden` for code wrapping. If tooltips use `position: absolute` inside the term element, they get clipped by the container. **Fix:** Tooltips must use `position: fixed` and be appended to `document.body`. Calculate position from `getBoundingClientRect()`. This is already handled by `main.js` but is the #1 bug that appears in every build.\n\n### Not Enough Tooltips\nThe most common failure is under-tooltipping. Non-technical learners don't know terms like REPL, JSON, flag, entry point, PATH, pip, namespace, function, class, module, PR, E2E, or even software names like Blender/GIMP. **Rule of thumb:** if a term wouldn't appear in everyday conversation with a non-technical friend, tooltip it. Err heavily on the side of too many. BUT: don't tooltip terms the user already knows well from their domain (e.g., AI/ML concepts for someone in AI).\n\n### Walls of Text\nThe course looks like a textbook instead of an infographic. This happens when you write more than 2-3 sentences in a row without a visual break. Every screen must be at least 50% visual. Convert any list of 3+ items into cards, any sequence into step cards or flow diagrams, any code explanation into a code↔English translation block.\n\n### Recycled Metaphors\nUsing \"restaurant\" or \"kitchen\" for everything. Every module needs its own metaphor that feels inevitable for that specific concept. If you catch yourself reaching for the same metaphor twice, stop and find one that fits the concept organically.\n\n### Code Modifications\nTrimming, simplifying, or \"cleaning up\" code snippets from the codebase. The learner should be able to open the real file and see the exact same code. Instead of editing code to be shorter, *choose* naturally short snippets (5-10 lines) from the codebase that illustrate the point.\n\n### Quiz Questions That Test Memory\nAsking \"What does API stand for?\" or \"Which file handles X?\" — those test recall, not understanding. Every quiz question should present a new scenario the learner hasn't seen and ask them to *apply* what they learned.\n\n### Scroll-Snap Mandatory\nUsing `scroll-snap-type: y mandatory` traps users inside long modules. Always use `proximity`.\n\n### Module Quality Degradation\nTrying to write all modules in one pass causes later modules to be thin and rushed. Build one module at a time and verify each before moving on. For complex codebases, use the parallel path with module briefs.\n\n### Missing Interactive Elements\nA module with only text and code blocks, no interactivity. Every module needs at least one of: quiz, data flow animation, group chat, architecture diagram, drag-and-drop. These aren't decorations — they're how non-technical learners actually process information.\n","isInternal":false,"tokens":662,"sizeBytes":3052},{"name":"interactive-elements.md","path":"references/interactive-elements.md","rawUrl":"https://raw.githubusercontent.com/zarazhangrui/codebase-to-course/HEAD/references/interactive-elements.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Interactive Elements Reference\n\nImplementation patterns for every interactive element type used in courses. Pick the elements that best serve each module's teaching goal.\n\n> **Architecture note:** All CSS and JavaScript for these elements live in `references/styles.css` and `references/main.js`, which are copied verbatim into every course directory. When writing module HTML files, use only the HTML patterns below — do **not** inline `<style>` or `<script>` tags for these elements. The engines in `main.js` auto-initialize on page load by scanning for the relevant class names and `data-*` attributes described here.\n\n## Table of Contents\n1. [Code ↔ English Translation Blocks](#code--english-translation-blocks)\n2. [Multiple-Choice Quizzes](#multiple-choice-quizzes)\n3. [Drag-and-Drop Matching](#drag-and-drop-matching)\n4. [Group Chat Animation](#group-chat-animation)\n5. [Message Flow / Data Flow Animation](#message-flow--data-flow-animation)\n6. [Interactive Architecture Diagram](#interactive-architecture-diagram)\n7. [Layer Toggle Demo](#layer-toggle-demo)\n8. [\"Spot the Bug\" Challenge](#spot-the-bug-challenge)\n9. [Scenario Quiz](#scenario-quiz)\n10. [Callout Boxes](#callout-boxes)\n11. [Pattern/Feature Cards](#patternfeature-cards)\n12. [Flow Diagrams](#flow-diagrams)\n13. [Permission/Config Badges](#permissionconfig-badges)\n14. [Glossary Tooltips](#glossary-tooltips)\n15. [Visual File Tree](#visual-file-tree)\n16. [Icon-Label Rows](#icon-label-rows)\n17. [Numbered Step Cards](#numbered-step-cards)\n\n---\n\n## Code ↔ English Translation Blocks\n\nThe most important teaching element. Shows real code from the project on the left and a plain English translation on the right, line by line.\n\n**HTML:**\n```html\n<div class=\"translation-block animate-in\">\n  <div class=\"translation-code\">\n    <span class=\"translation-label\">CODE</span>\n    <pre><code>\n<span class=\"code-line\"><span class=\"code-keyword\">const</span> response = <span class=\"code-keyword\">await</span> <span class=\"code-function\">fetch</span>(url, {</span>\n<span class=\"code-line\">  <span class=\"code-property\">method</span>: <span class=\"code-string\">'POST'</span>,</span>\n<span class=\"code-line\">  <span class=\"code-property\">headers</span>: { <span class=\"code-string\">'Authorization'</span>: apiKey }</span>\n<span class=\"code-line\">});</span>\n    </code></pre>\n  </div>\n  <div class=\"translation-english\">\n    <span class=\"translation-label\">PLAIN ENGLISH</span>\n    <div class=\"translation-lines\">\n      <p class=\"tl\">Send a request to the URL and wait for a response...</p>\n      <p class=\"tl\">We're sending data (POST), not just asking for it (GET)...</p>\n      <p class=\"tl\">Include our API key so the server knows who we are...</p>\n      <p class=\"tl\">End of the request setup.</p>\n    </div>\n  </div>\n</div>\n```\n\n**CSS:**\n```css\n.translation-block {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 0;\n  border-radius: var(--radius-md);\n  overflow: hidden;\n  box-shadow: var(--shadow-md);\n  margin: var(--space-8) 0;\n}\n.translation-code {\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-6);\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  position: relative;\n  overflow-x: hidden;  /* NO horizontal scrollbar — ever */\n}\n.translation-code pre,\n.translation-code code {\n  white-space: pre-wrap;       /* wrap long lines instead of scrolling */\n  word-break: break-word;      /* break mid-word if needed */\n  overflow-x: hidden;\n}\n.translation-english {\n  background: var(--color-surface-warm);\n  padding: var(--space-6);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  border-left: 3px solid var(--color-accent);\n}\n.translation-label {\n  position: absolute;\n  top: var(--space-2);\n  right: var(--space-3);\n  font-size: var(--text-xs);\n  text-transform: uppercase;\n  letter-spacing: 0.1em;\n  opacity: 0.5;\n}\n.translation-english .translation-label {\n  color: var(--color-text-muted);\n}\n/* Responsive: stack vertically on mobile */\n@media (max-width: 768px) {\n  .translation-block { grid-template-columns: 1fr; }\n  .translation-english { border-left: none; border-top: 3px solid var(--color-accent); }\n}\n```\n\n**Rules:**\n- Each English line should correspond to 1-2 code lines\n- Use conversational language, not technical jargon\n- Highlight the \"why\" not just the \"what\" — e.g., \"Include our API key so the server knows who we are\" not \"Set the Authorization header\"\n\n---\n\n## Multiple-Choice Quizzes\n\nFor testing understanding with instant feedback. Each question has options, one correct answer, and per-question explanations.\n\n**Wiring:** `main.js` exposes `window.selectOption(btn)`, `window.checkQuiz(containerId)`, and `window.resetQuiz(containerId)`. Call them via `onclick`. Per-question explanations go in `data-explanation-right` and `data-explanation-wrong` on the `.quiz-question-block`.\n\n**HTML:**\n```html\n<div class=\"quiz-container\" id=\"quiz-module3\">\n  <div class=\"quiz-question-block\"\n       data-correct=\"option-b\"\n       data-explanation-right=\"Exactly — because X is responsible for Y in this architecture.\"\n       data-explanation-wrong=\"Not quite. Think about where Y lives in the codebase...\">\n    <h3 class=\"quiz-question\">Question text here?</h3>\n    <div class=\"quiz-options\">\n      <button class=\"quiz-option\" data-value=\"option-a\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer A</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-b\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer B (correct)</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-c\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer C</span>\n      </button>\n    </div>\n    <div class=\"quiz-feedback\"></div>\n  </div>\n\n  <button class=\"quiz-check-btn\" onclick=\"checkQuiz('quiz-module3')\">Check Answers</button>\n  <button class=\"quiz-reset-btn\" onclick=\"resetQuiz('quiz-module3')\">Try Again</button>\n</div>\n```\n\n**CSS for quiz states:**\n```css\n.quiz-option {\n  display: flex; align-items: center; gap: var(--space-3);\n  padding: var(--space-3) var(--space-4);\n  border: 2px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  background: var(--color-surface);\n  cursor: pointer; width: 100%;\n  transition: border-color var(--duration-fast), background var(--duration-fast);\n}\n.quiz-option:hover { border-color: var(--color-accent-muted); }\n.quiz-option.selected { border-color: var(--color-accent); background: var(--color-accent-light); }\n.quiz-option.correct { border-color: var(--color-success); background: var(--color-success-light); }\n.quiz-option.incorrect { border-color: var(--color-error); background: var(--color-error-light); }\n.quiz-option-radio {\n  width: 18px; height: 18px; border-radius: 50%;\n  border: 2px solid var(--color-border);\n  transition: all var(--duration-fast);\n}\n.quiz-option.selected .quiz-option-radio {\n  border-color: var(--color-accent);\n  background: var(--color-accent);\n  box-shadow: inset 0 0 0 3px white;\n}\n.quiz-feedback {\n  max-height: 0; overflow: hidden; opacity: 0;\n  transition: max-height var(--duration-normal), opacity var(--duration-normal);\n}\n.quiz-feedback.show { max-height: 200px; opacity: 1; padding: var(--space-3); margin-top: var(--space-2); border-radius: var(--radius-sm); }\n.quiz-feedback.success { background: var(--color-success-light); color: var(--color-success); }\n.quiz-feedback.error { background: var(--color-error-light); color: var(--color-error); }\n```\n\n---\n\n## Drag-and-Drop Matching\n\nFor matching concepts to descriptions. Supports both mouse (HTML5 Drag API) and touch.\n\n**HTML:**\n```html\n<div class=\"dnd-container\">\n  <div class=\"dnd-chips\">\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-a\">Actor A</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-b\">Actor B</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-c\">Actor C</div>\n  </div>\n  <div class=\"dnd-zones\">\n    <div class=\"dnd-zone\" data-correct=\"actor-a\">\n      <p class=\"dnd-zone-label\">Description for Actor A</p>\n      <div class=\"dnd-zone-target\">Drop here</div>\n    </div>\n    <!-- more zones -->\n  </div>\n  <button onclick=\"checkDnD()\">Check Matches</button>\n  <button onclick=\"resetDnD()\">Reset</button>\n</div>\n```\n\n**JS (mouse + touch):**\n```javascript\n// MOUSE: HTML5 Drag API\nchips.forEach(chip => {\n  chip.addEventListener('dragstart', (e) => {\n    e.dataTransfer.setData('text/plain', chip.dataset.answer);\n    chip.classList.add('dragging');\n  });\n  chip.addEventListener('dragend', () => chip.classList.remove('dragging'));\n});\n\nzones.forEach(zone => {\n  const target = zone.querySelector('.dnd-zone-target');\n  target.addEventListener('dragover', (e) => { e.preventDefault(); target.classList.add('drag-over'); });\n  target.addEventListener('dragleave', () => target.classList.remove('drag-over'));\n  target.addEventListener('drop', (e) => {\n    e.preventDefault();\n    target.classList.remove('drag-over');\n    const answer = e.dataTransfer.getData('text/plain');\n    const chip = document.querySelector(`[data-answer=\"${answer}\"]`);\n    target.textContent = chip.textContent;\n    target.dataset.placed = answer;\n    chip.classList.add('placed');\n  });\n});\n\n// TOUCH: Custom implementation (HTML5 drag doesn't work on mobile)\nchips.forEach(chip => {\n  chip.addEventListener('touchstart', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    const clone = chip.cloneNode(true);\n    clone.classList.add('touch-ghost');\n    clone.style.cssText = `position:fixed; z-index:1000; pointer-events:none;\n      left:${touch.clientX - 40}px; top:${touch.clientY - 20}px;`;\n    document.body.appendChild(clone);\n    chip._ghost = clone;\n    chip._answer = chip.dataset.answer;\n  }, { passive: false });\n\n  chip.addEventListener('touchmove', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    if (chip._ghost) {\n      chip._ghost.style.left = (touch.clientX - 40) + 'px';\n      chip._ghost.style.top = (touch.clientY - 20) + 'px';\n    }\n    // Highlight zone under finger\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    zones.forEach(z => z.querySelector('.dnd-zone-target').classList.remove('drag-over'));\n    if (el && el.closest('.dnd-zone-target')) {\n      el.closest('.dnd-zone-target').classList.add('drag-over');\n    }\n  }, { passive: false });\n\n  chip.addEventListener('touchend', (e) => {\n    if (chip._ghost) { chip._ghost.remove(); chip._ghost = null; }\n    const touch = e.changedTouches[0];\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    if (el && el.closest('.dnd-zone-target')) {\n      const target = el.closest('.dnd-zone-target');\n      target.textContent = chip.textContent;\n      target.dataset.placed = chip._answer;\n      chip.classList.add('placed');\n    }\n  });\n});\n```\n\n---\n\n## Group Chat Animation\n\niMessage/WeChat-style chat showing components \"talking\" to each other. Messages appear one by one with typing indicators.\n\n**Wiring:** `main.js` auto-initializes every `.chat-window` on page load. Give each chat window a unique `id`. Control buttons need these classes: `.chat-next-btn`, `.chat-all-btn`, `.chat-reset-btn`. The typing indicator avatar element should have `id=\"{chatWindowId}-typing-avatar\"` or simply be the first `.chat-avatar` inside `.chat-typing`.\n\n**HTML:**\n```html\n<div class=\"chat-window\" id=\"chat-module2\">\n  <div class=\"chat-messages\">\n    <div class=\"chat-message\" data-msg=\"0\" data-sender=\"actor-a\" style=\"display:none\">\n      <div class=\"chat-avatar\" style=\"background: var(--color-actor-1)\">A</div>\n      <div class=\"chat-bubble\">\n        <span class=\"chat-sender\" style=\"color: var(--color-actor-1)\">Actor A</span>\n        <p>Hey Background, I need the data for this item.</p>\n      </div>\n    </div>\n    <!-- more messages... -->\n  </div>\n\n  <div class=\"chat-typing\" id=\"chat-typing\" style=\"display:none\">\n    <div class=\"chat-avatar\" id=\"typing-avatar\">?</div>\n    <div class=\"chat-typing-dots\">\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n    </div>\n  </div>\n\n  <div class=\"chat-controls\">\n    <button class=\"btn chat-next-btn\">Next Message</button>\n    <button class=\"btn chat-all-btn\">Play All</button>\n    <button class=\"btn chat-reset-btn\">Replay</button>\n    <span class=\"chat-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for typing dots:**\n```css\n.typing-dot {\n  width: 8px; height: 8px; border-radius: 50%;\n  background: var(--color-text-muted);\n  animation: typingBounce 1.4s infinite;\n}\n.typing-dot:nth-child(2) { animation-delay: 0.2s; }\n.typing-dot:nth-child(3) { animation-delay: 0.4s; }\n@keyframes typingBounce {\n  0%, 60%, 100% { transform: translateY(0); }\n  30% { transform: translateY(-6px); }\n}\n```\n\n---\n\n## Message Flow / Data Flow Animation\n\nStep-by-step visualization of data moving between components. User clicks \"Next Step\" to advance.\n\n**Wiring:** `main.js` auto-initializes every `.flow-animation` on page load. Pass steps as JSON in `data-steps`. Each step object: `{ highlight: \"flow-actor-id\", label: \"description\", packet: true, from: \"actor-id-suffix\", to: \"actor-id-suffix\" }`. Actor element IDs must be `flow-actor-1`, `flow-actor-2`, etc. Control buttons need classes `.flow-next-btn` and `.flow-reset-btn`.\n\n> **⚠️ Single quotes in step labels will break parsing.** The `data-steps` attribute is delimited by single quotes (`data-steps='[...]'`), so any single quote inside a label (e.g. `\"the user's request\"`) will terminate the attribute early and cause `JSON.parse` to fail silently — the entire animation will stop working. Either avoid apostrophes in labels, replace them with `&apos;`, or rewrite the attribute using double-quote delimiters with escaped inner quotes (`data-steps=\"[{\\\"label\\\":\\\"...\\\"}]\"`).\n\n**HTML:**\n```html\n<div class=\"flow-animation\" data-steps='[\n  {\"highlight\":\"flow-actor-1\",\"label\":\"User clicks the button\"},\n  {\"highlight\":\"flow-actor-1\",\"label\":\"Frontend sends request\",\"packet\":true,\"from\":\"actor-1\",\"to\":\"actor-2\"},\n  {\"highlight\":\"flow-actor-2\",\"label\":\"Backend calls the database\",\"packet\":true,\"from\":\"actor-2\",\"to\":\"actor-3\"}\n]'>\n  <div class=\"flow-actors\">\n    <div class=\"flow-actor\" id=\"flow-actor-1\">\n      <div class=\"flow-actor-icon\">A</div>\n      <span>Actor 1</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-2\">\n      <div class=\"flow-actor-icon\">B</div>\n      <span>Actor 2</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-3\">\n      <div class=\"flow-actor-icon\">C</div>\n      <span>Actor 3</span>\n    </div>\n  </div>\n\n  <div class=\"flow-packet\" id=\"flow-packet\"></div>\n\n  <div class=\"flow-step-label\" id=\"flow-label\">Click \"Next Step\" to begin</div>\n\n  <div class=\"flow-controls\">\n    <button class=\"btn flow-next-btn\">Next Step</button>\n    <button class=\"btn flow-reset-btn\">Restart</button>\n    <span class=\"flow-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for active actor glow:**\n```css\n.flow-actor.active {\n  box-shadow: 0 0 0 3px var(--color-accent), 0 0 20px rgba(217, 79, 48, 0.2);\n  transform: scale(1.05);\n  transition: all var(--duration-normal) var(--ease-out);\n}\n```\n\n---\n\n## Interactive Architecture Diagram\n\nFull-system diagram where hovering/clicking a component shows a description tooltip.\n\n**HTML:**\n```html\n<div class=\"arch-diagram\">\n  <div class=\"arch-zone arch-zone-browser\">\n    <h4 class=\"arch-zone-label\">Browser</h4>\n    <div class=\"arch-component\" data-desc=\"Injects UI into the web page, reads DOM, captures user actions\"\n         onclick=\"showArchDesc(this)\">\n      <div class=\"arch-icon\">📄</div>\n      <span>Component A</span>\n    </div>\n    <!-- more components -->\n  </div>\n  <div class=\"arch-zone arch-zone-external\">\n    <h4 class=\"arch-zone-label\">External Services</h4>\n    <!-- API cards -->\n  </div>\n  <div class=\"arch-description\" id=\"arch-desc\">Click any component to learn what it does</div>\n</div>\n```\n\n---\n\n## Layer Toggle Demo\n\nShows how different layers (e.g., HTML/CSS/JS, or data/logic/UI) build on each other. Three tabs switch between views.\n\n**HTML:**\n```html\n<div class=\"layer-demo\">\n  <div class=\"layer-tabs\">\n    <button class=\"layer-tab active\" onclick=\"showLayer('html')\">HTML</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('css')\">+ CSS</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('js')\">+ JS</button>\n  </div>\n  <div class=\"layer-viewport\">\n    <div class=\"layer\" id=\"layer-html\" style=\"display:block\">\n      <!-- Raw unstyled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-css\" style=\"display:none\">\n      <!-- Styled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-js\" style=\"display:none\">\n      <!-- Interactive version -->\n    </div>\n  </div>\n  <p class=\"layer-description\" id=\"layer-desc\">This is the raw HTML...</p>\n</div>\n```\n\n---\n\n## \"Spot the Bug\" Challenge\n\nShow code with a deliberate bug. User clicks the buggy line. Reveal explains the issue.\n\n**HTML:**\n```html\n<div class=\"bug-challenge\">\n  <h3>Find the bug in this code:</h3>\n  <div class=\"bug-code\">\n    <div class=\"bug-line\" data-line=\"1\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">1</span>\n      <code>chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"2\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">2</span>\n      <code>  if (msg.action === 'fetchData') {</code>\n    </div>\n    <div class=\"bug-line bug-target\" data-line=\"3\" onclick=\"checkBugLine(this, true)\">\n      <span class=\"line-num\">3</span>\n      <code>    fetch(url).then(r => r.json()).then(data => sendResponse(data));</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"4\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">4</span>\n      <code>  }</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"5\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">5</span>\n      <code>});</code>\n    </div>\n  </div>\n  <div class=\"bug-feedback\" id=\"bug-feedback\"></div>\n</div>\n```\n\n**JS:**\n```javascript\nwindow.checkBugLine = function(el, isCorrect) {\n  const feedback = el.closest('.bug-challenge').querySelector('.bug-feedback');\n  if (isCorrect) {\n    el.classList.add('correct');\n    feedback.innerHTML = '<strong>Found it!</strong> The listener uses an async operation (fetch) but doesn\\'t return true. Chrome closes the message channel before the response can be sent. Fix: add <code>return true;</code> at the end.';\n    feedback.className = 'bug-feedback show success';\n  } else {\n    el.classList.add('incorrect');\n    feedback.innerHTML = 'Not this line — look for where the async timing might cause problems...';\n    feedback.className = 'bug-feedback show error';\n    setTimeout(() => { el.classList.remove('incorrect'); feedback.className = 'bug-feedback'; }, 2000);\n  }\n};\n```\n\n---\n\n## Scenario Quiz\n\n\"What would a senior engineer do?\" — situational questions with explanations.\n\nSame HTML/CSS/JS pattern as Multiple-Choice Quizzes, but with longer scenario descriptions and more detailed explanations. Wrap each question in a scenario context block:\n\n```html\n<div class=\"scenario-block\">\n  <div class=\"scenario-context\">\n    <span class=\"scenario-label\">Scenario</span>\n    <p>Your app processes a 3-hour podcast transcript. The API has a 16,000 token limit. What do you do?</p>\n  </div>\n  <!-- quiz-options here -->\n</div>\n```\n\n---\n\n## Callout Boxes\n\n\"Aha!\" moments — universal CS insights. Max 2 per module.\n\n```html\n<div class=\"callout callout-accent\">\n  <div class=\"callout-icon\">💡</div>\n  <div class=\"callout-content\">\n    <strong class=\"callout-title\">Key Insight</strong>\n    <p>This pattern — splitting responsibilities into focused roles — is one of the most important ideas in software engineering. Engineers call it \"separation of concerns.\"</p>\n  </div>\n</div>\n```\n\n**Variants:**\n- `callout-accent`: vermillion left border, light accent background (for CS insights)\n- `callout-info`: teal left border, light info background (for \"good to know\")\n- `callout-warning`: red left border, light error background (for common mistakes)\n\n---\n\n## Pattern/Feature Cards\n\nGrid of cards highlighting engineering patterns, tech stack components, or key concepts.\n\n```html\n<div class=\"pattern-cards\">\n  <div class=\"pattern-card\" style=\"border-top: 3px solid var(--color-actor-1)\">\n    <div class=\"pattern-icon\" style=\"background: var(--color-actor-1)\">🔄</div>\n    <h4 class=\"pattern-title\">Caching</h4>\n    <p class=\"pattern-desc\">Store results to avoid redundant work — like keeping leftovers instead of cooking a new meal every time.</p>\n  </div>\n  <!-- more cards -->\n</div>\n```\n\n```css\n.pattern-cards {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n  gap: var(--space-4);\n}\n.pattern-card {\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  padding: var(--space-6);\n  box-shadow: var(--shadow-sm);\n  transition: transform var(--duration-normal) var(--ease-out), box-shadow var(--duration-normal);\n}\n.pattern-card:hover {\n  transform: translateY(-4px);\n  box-shadow: var(--shadow-md);\n}\n```\n\n---\n\n## Flow Diagrams\n\n**Horizontal flow (desktop):**\n```html\n<div class=\"flow-steps\">\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">1</div>\n    <p>User clicks button</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">2</div>\n    <p>Component A detects click</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <!-- more steps -->\n</div>\n```\n\nArrows rotate to `↓` on mobile via CSS transform.\n\n---\n\n## Permission/Config Badges\n\nFor annotating config files, permissions, or settings:\n\n```html\n<div class=\"badge-list\">\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">storage</code>\n    <span class=\"badge-desc\">Save data between sessions (like browser bookmarks)</span>\n  </div>\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">activeTab</code>\n    <span class=\"badge-desc\">Access the currently open tab (only when the user clicks)</span>\n  </div>\n</div>\n```\n\n```css\n.badge-item {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-3) var(--space-4);\n  border: 1px solid var(--color-border-light);\n  border-radius: var(--radius-sm);\n  transition: border-color var(--duration-fast);\n}\n.badge-item:hover { border-color: var(--color-accent-muted); }\n.badge-code {\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  background: var(--color-bg-code);\n  color: #CBA6F7;\n  padding: var(--space-1) var(--space-3);\n  border-radius: var(--radius-sm);\n  white-space: nowrap;\n}\n```\n\n---\n\n## Glossary Tooltips\n\nThe most important accessibility feature for non-technical learners. Any technical term in the course text should be wrapped in a tooltip that shows a plain-English definition on hover (desktop) or tap (mobile). The learner never has to leave the page or Google anything.\n\n**HTML — mark up terms inline:**\n```html\n<p>The extension uses a\n  <span class=\"term\" data-definition=\"A service worker is a background script that runs independently of the web page — like a behind-the-scenes assistant that's always on, even when you're not looking at the page.\">service worker</span>\n  to handle API calls.\n</p>\n```\n\n**CSS:**\n```css\n.term {\n  border-bottom: 1.5px dashed var(--color-accent-muted);\n  cursor: pointer;    /* NOT cursor: help — pointer feels clickable and inviting */\n  position: relative;\n}\n.term:hover, .term.active {\n  border-bottom-color: var(--color-accent);\n  color: var(--color-accent);\n}\n\n/* The tooltip bubble — uses position: fixed and is appended to document.body\n   via JS so it is NEVER clipped by ancestor overflow: hidden containers\n   (like translation blocks). See JS section below for positioning logic. */\n.term-tooltip {\n  position: fixed;        /* CRITICAL: fixed, not absolute — prevents clipping */\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-3) var(--space-4);\n  border-radius: var(--radius-sm);\n  font-size: var(--text-sm);\n  font-family: var(--font-body);\n  line-height: var(--leading-normal);\n  width: max(200px, min(320px, 80vw));\n  box-shadow: var(--shadow-lg);\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity var(--duration-fast);\n  z-index: 10000;        /* Above everything, including nav */\n}\n/* Arrow pointing down */\n.term-tooltip::after {\n  content: '';\n  position: absolute;\n  top: 100%;\n  left: 50%;\n  transform: translateX(-50%);\n  border: 6px solid transparent;\n  border-top-color: var(--color-bg-code);\n}\n.term-tooltip.visible {\n  opacity: 1;\n}\n\n/* If tooltip goes off-screen top, flip to below */\n.term-tooltip.flip {\n  bottom: auto;\n  top: calc(100% + 8px);\n}\n.term-tooltip.flip::after {\n  top: auto;\n  bottom: 100%;\n  border-top-color: transparent;\n  border-bottom-color: var(--color-bg-code);\n}\n```\n\n**JS — position: fixed tooltips appended to body (never clipped by overflow):**\n```javascript\n// Tooltip container — appended to body so it's never clipped\nlet activeTooltip = null;\n\nfunction positionTooltip(term, tip) {\n  const rect = term.getBoundingClientRect();\n  const tipWidth = 300; // approximate\n  let left = rect.left + rect.width / 2 - tipWidth / 2;\n  // Clamp to viewport\n  left = Math.max(8, Math.min(left, window.innerWidth - tipWidth - 8));\n\n  // Try above first\n  let top = rect.top - 8;\n  tip.style.left = left + 'px';\n\n  // Position above by default, flip below if no room\n  document.body.appendChild(tip);\n  const tipHeight = tip.offsetHeight;\n  if (rect.top - tipHeight - 8 < 0) {\n    // Flip below\n    tip.style.top = (rect.bottom + 8) + 'px';\n    tip.classList.add('flip');\n  } else {\n    tip.style.top = (rect.top - tipHeight - 8) + 'px';\n    tip.classList.remove('flip');\n  }\n}\n\ndocument.querySelectorAll('.term').forEach(term => {\n  const tip = document.createElement('span');\n  tip.className = 'term-tooltip';\n  tip.textContent = term.dataset.definition;\n\n  // Hover for desktop\n  term.addEventListener('mouseenter', () => {\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    positionTooltip(term, tip);\n    requestAnimationFrame(() => tip.classList.add('visible'));\n    activeTooltip = tip;\n  });\n\n  term.addEventListener('mouseleave', () => {\n    tip.classList.remove('visible');\n    setTimeout(() => { if (!tip.classList.contains('visible')) tip.remove(); }, 150);\n    activeTooltip = null;\n  });\n\n  // Tap for mobile\n  term.addEventListener('click', (e) => {\n    e.stopPropagation();\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    if (tip.classList.contains('visible')) {\n      tip.classList.remove('visible');\n      tip.remove();\n      activeTooltip = null;\n    } else {\n      positionTooltip(term, tip);\n      requestAnimationFrame(() => tip.classList.add('visible'));\n      activeTooltip = tip;\n    }\n  });\n});\n\n// Close tooltips when clicking elsewhere\ndocument.addEventListener('click', () => {\n  if (activeTooltip) {\n    activeTooltip.classList.remove('visible');\n    activeTooltip.remove();\n    activeTooltip = null;\n  }\n});\n```\n\n**Rules:**\n- Mark up EVERY technical term on first use in each module (API, DOM, callback, async, endpoint, middleware, etc.)\n- Keep definitions to 1-2 sentences max, in everyday language\n- Use a metaphor in the definition when it helps — e.g., \"A **callback** is like leaving your phone number at a restaurant so they can call you when your table is ready\"\n- Don't mark the same term twice within the same screen — only on first appearance per module\n- The dashed underline should be subtle enough not to distract but visible enough that curious learners discover it\n\n---\n\n## Visual File Tree\n\nUse instead of paragraphs listing \"this folder does X, that folder does Y.\" Much easier to scan.\n\n```html\n<div class=\"file-tree\">\n  <div class=\"ft-folder open\">\n    <span class=\"ft-name\">app/</span>\n    <span class=\"ft-desc\">Pages and API routes</span>\n    <div class=\"ft-children\">\n      <div class=\"ft-folder\">\n        <span class=\"ft-name\">api/</span>\n        <span class=\"ft-desc\">Backend endpoints the frontend calls</span>\n      </div>\n      <div class=\"ft-file\">\n        <span class=\"ft-name\">layout.tsx</span>\n        <span class=\"ft-desc\">The shell that wraps every page</span>\n      </div>\n    </div>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">components/</span>\n    <span class=\"ft-desc\">Reusable UI building blocks</span>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">lib/</span>\n    <span class=\"ft-desc\">Shared logic and utilities</span>\n  </div>\n</div>\n```\n\n```css\n.file-tree { font-family: var(--font-mono); font-size: var(--text-sm); }\n.ft-folder, .ft-file {\n  padding: var(--space-2) var(--space-3);\n  border-left: 2px solid var(--color-border-light);\n  margin-left: var(--space-4);\n}\n.ft-folder > .ft-name { color: var(--color-accent); font-weight: 600; }\n.ft-folder > .ft-name::before { content: '📁 '; }\n.ft-file > .ft-name::before { content: '📄 '; }\n.ft-desc {\n  color: var(--color-text-secondary);\n  font-family: var(--font-body);\n  margin-left: var(--space-2);\n  font-size: var(--text-xs);\n}\n.ft-children { margin-left: var(--space-4); }\n```\n\n---\n\n## Icon-Label Rows\n\nFor listing components, features, or concepts visually. Replaces bullet-point paragraphs.\n\n```html\n<div class=\"icon-rows\">\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-1)\">🖥️</div>\n    <div>\n      <strong>Frontend (Next.js)</strong>\n      <p>What the user sees and interacts with</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-2)\">⚡</div>\n    <div>\n      <strong>API Routes</strong>\n      <p>Backend logic that runs on the server</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-3)\">🗄️</div>\n    <div>\n      <strong>Database (Supabase)</strong>\n      <p>Where all the data is stored permanently</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.icon-rows { display: flex; flex-direction: column; gap: var(--space-4); }\n.icon-row {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-4);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  box-shadow: var(--shadow-sm);\n}\n.icon-row p { margin: 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n.icon-circle {\n  width: 48px; height: 48px; border-radius: 50%;\n  display: flex; align-items: center; justify-content: center;\n  font-size: 1.25rem; flex-shrink: 0;\n}\n```\n\n---\n\n## Numbered Step Cards\n\nFor sequences that would otherwise be a numbered paragraph list. Visual, scannable, and each step stands alone.\n\n```html\n<div class=\"step-cards\">\n  <div class=\"step-card\">\n    <div class=\"step-num\">1</div>\n    <div class=\"step-body\">\n      <strong>User pastes a YouTube URL</strong>\n      <p>The frontend captures the URL and extracts the video ID</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">2</div>\n    <div class=\"step-body\">\n      <strong>API fetches the transcript</strong>\n      <p>A server-side route calls an external service to get the video's text</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">3</div>\n    <div class=\"step-body\">\n      <strong>AI analyzes the content</strong>\n      <p>The transcript is sent to an AI model that extracts key moments</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.step-cards { display: flex; flex-direction: column; gap: var(--space-3); }\n.step-card {\n  display: flex; align-items: flex-start; gap: var(--space-4);\n  padding: var(--space-4) var(--space-5);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  border-left: 3px solid var(--color-accent);\n  box-shadow: var(--shadow-sm);\n}\n.step-num {\n  width: 32px; height: 32px; border-radius: 50%;\n  background: var(--color-accent);\n  color: white; font-weight: 700;\n  display: flex; align-items: center; justify-content: center;\n  font-family: var(--font-display);\n  flex-shrink: 0;\n}\n.step-body p { margin: var(--space-1) 0 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n```\n","isInternal":false,"tokens":8559,"sizeBytes":32424},{"name":"module-brief-template.md","path":"references/module-brief-template.md","rawUrl":"https://raw.githubusercontent.com/zarazhangrui/codebase-to-course/HEAD/references/module-brief-template.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Module Brief Template\n\n> **When to read this:** During Phase 2.5 (planning checkpoint) for complex codebases. Fill in one brief per module, save to `course-name/briefs/0N-slug.md`. Each brief gives a parallel agent everything it needs to write one module without reading the codebase or SKILL.md.\n\n---\n\n## Module N: [Title]\n\n### Teaching Arc\n- **Metaphor:** [A fresh, specific metaphor — never \"restaurant.\" See `references/content-philosophy.md` > Metaphors First]\n- **Opening hook:** [1 sentence that connects to something the learner already knows from using the app]\n- **Key insight:** [The one thing the learner should walk away understanding]\n- **\"Why should I care?\":** [How this helps them steer AI / debug / make decisions]\n\n### Code Snippets (pre-extracted)\n\nInclude the actual code the module will use in code↔English translation blocks. Copy-paste from the codebase with file path and line numbers. The writing agent will use these verbatim — it will NOT re-read the codebase.\n\nFile: src/example/file.ts (lines 12-24)\n[paste actual code here]\n\nFile: src/another/file.ts (lines 45-52)\n[paste actual code here]\n\n### Interactive Elements\n\nCheck which elements this module needs. Include enough detail for the writing agent to build them.\n\n- [ ] **Code↔English translation** — which snippet(s) from above\n- [ ] **Quiz** — [number] questions, style: [scenario / debugging / architecture / tracing]. Brief description of each question's angle.\n- [ ] **Group chat animation** — actors: [list]. Message flow summary: [who says what to whom, in what order]\n- [ ] **Data flow animation** — actors: [list]. Steps: [sequence of highlights and packet movements]\n- [ ] **Drag-and-drop** — items: [list], targets: [list]\n- [ ] **Other** — [architecture diagram, layer toggle, pattern cards, etc.]\n\n### Reference Files to Read\n\nList only the sections the writing agent needs — not the whole file.\n\n- `references/interactive-elements.md` → [section names, e.g., \"Multiple-Choice Quizzes\", \"Group Chat Animation\"]\n- `references/design-system.md` → [only if needed for specific tokens not in the brief]\n- `references/content-philosophy.md` → [always include — agent needs content rules]\n- `references/gotchas.md` → [always include — agent needs the checklist]\n\n### Connections\n\n- **Previous module:** [Title — what it covered, so this module can build on it]\n- **Next module:** [Title — what it will cover, so this module can set it up]\n- **Tone/style notes:** [Any course-wide consistency notes: accent color name, actor naming convention, etc.]\n","isInternal":false,"tokens":611,"sizeBytes":2580}],"systemPromptSnippet":"<yakaai_skills repo=\"zarazhangrui/codebase-to-course\">\n<!-- File: references/content-philosophy.md (Tokens: ~1973 | Category: anthropic-skill) -->\n# Content Philosophy\n\n> **When to read this:** During Phase 2.5 (writing module briefs) and Phase 3 (writing module HTML). These principles guide every content decision — what to show, how to explain it, and how to test understanding.\n\nThese principles are what separate a great course from a generic tutorial. They should guide every content decision:\n\n### Show, Don't Tell — Aggressively Visual\nPeople's eyes glaze over text blocks. The course should feel closer to an infographic than a textbook. Follow these hard rules:\n\n**Text limits:**\n- Max **2-3 sentences** per text block. If you're writing a fourth sentence, stop and convert it into a visual instead.\n- No text block should ever be wider than the content width AND taller than ~4 lines. If it is, break it up with a visual element.\n- Every screen must be **at least 50% visual** (diagrams, code blocks, cards, animations, badges — anything that isn't a paragraph).\n\n**Convert text to visuals:**\n- A list of 3+ items → **cards with icons** (pattern cards, feature cards)\n- A sequence of steps → **flow diagram with arrows** or **numbered step cards**\n- \"Component A talks to Component B\" → **animated data flow** or **group chat visualization**\n- \"This file does X, that file does Y\" → **visual file tree with annotations** or **icon + one-liner badges**\n- Explaining what code does → **code↔English translation block** (not a paragraph *about* the code)\n- Comparing two approaches → **side-by-side columns** with visual contrast\n\n**Visual breathing room:**\n- Use generous spacing between elements (`--space-8` to `--space-12` between sections)\n- Alternate between full-width visuals and narrow text blocks to create rhythm\n- Every module should have at least one \"hero visual\" — a diagram, animation, or interactive element that dominates the screen and teaches the core concept at a glance\n\n### Code ↔ English Translations\nEvery code snippet gets a side-by-side plain English translation. Left panel: real code from the project with syntax highlighting. Right panel: line-by-line plain English explaining what each line does. This is the single most valuable teaching tool for non-technical learners.\n\n**Critical: No horizontal scrollbars on code.** All code must use `white-space: pre-wrap` so it wraps instead of scrolling. This is a course for non-technical people, not an IDE — readability beats preserving indentation structure.\n\n**Critical: Use original code exactly as-is.** Never modify, simplify, or trim code snippets from the codebase. The learner should be able to open the real file and see the exact same code they learned from — that builds trust. Instead of editing code to make it shorter, *choose* naturally short, punchy snippets (5-10 lines) from the codebase that illustrate the concept well. Every codebase has compact, self-contained moments — find those rather than butchering longer functions.\n\n### One Concept Per Screen\nNo walls of text. Each screen within a module teaches exactly one idea. If you need more space, add another screen — don't cram.\n\n### Metaphors First, Then Reality\nIntroduce every new concept with a metaphor from everyday life. Then immediately ground it: \"In our code, this looks like...\" The metaphor builds intuition; the code grounds it in reality.\n\n**Critical: No recycled metaphors.** Do NOT default to \"restaurant\" for everything — that's the #1 crutch. Each concept deserves its own metaphor that feels natural to *that specific idea*. A database is a library with a card catalog. Auth is a bouncer checking IDs. An event loop is an air traffic controller. Message passing is a postal system. API rate limiting is a nightclub with a capacity limit. Pick the metaphor that makes the concept click, not the one that's easiest to reach for. If you catch yourself using \"restaurant\" or \"kitchen\" more than once in a course, stop and rethink.\n\n### Learn by Tracing\nFollow what actually happens when the learner does something they already do every day in the app — trace the data flow end-to-end. \"You know that button you click? Here's the journey your data takes after you click it...\" This works because the learner has *already experienced the result* — now they're seeing the machinery behind it. It's like watching a behind-the-scenes documentary of a movie you loved.\n\n### Make It Memorable\nUse \"aha!\" callout boxes for universal CS insights. Use humor where natural (not forced). Give components personality — they're \"characters\" in a story, not abstract boxes on a diagram.\n\n### Glossary Tooltips — No Term Left Behind\nEvery technical term (API, DOM, callback, middleware, etc.) gets a dashed-underline tooltip on first use in each module. Hover on desktop or tap on mobile to see a 1-2 sentence plain-English definition. The learner should never have to leave the page to Google a term. This is the difference between a course that *says* it's for non-technical people and one that actually *is*.\n\n**Be extremely aggressive with tooltips.** If there is even a 1% chance a non-technical person doesn't know a word, tooltip it. This includes:\n- Software names they might not know (Blender, GIMP, Audacity, etc.)\n- Everyday developer terms (REPL, JSON, flag, CLI, API, SDK, etc.)\n- Programming concepts (function, variable, dictionary, class, module, etc.)\n- Infrastructure terms (PATH, pip, namespace, entry point, etc.)\n- Acronyms — ALWAYS tooltip acronyms on first use\n\n**The vocabulary IS the learning.** One of the key goals is for learners to acquire the precise technical vocabulary they need to communicate with AI coding agents. Each tooltip should teach the term in a way that helps the learner USE it in their own instructions — e.g., \"A **flag** is an option you add to a command to change its behavior — like adding '--json' to get structured data instead of plain text. When talking to AI, you'd say 'add a flag for verbose output.'\"\n\n**Cursor:** Use `cursor: pointer` on terms (not `cursor: help`). The question-mark cursor feels clinical — a pointer feels clickable and inviting.\n\n**Tooltip overflow fix:** Translation blocks and other containers with `overflow: hidden` will clip tooltips. To fix this, the tooltip JS must use `position: fixed` and calculate coordinates from `getBoundingClientRect()` instead of relying on CSS `position: absolute` within the container. Append tooltips to `document.body` rather than inside the term element. This ensures tooltips are never clipped by any ancestor's overflow.\n\n### Quizzes That Test Application, Not Memory\n\nThe goal of learning is practical application — being able to *do something* with what you learned. Quizzes should test whether the learner can use their knowledge to solve a new problem, not whether they can regurgitate a definition.\n\n**What to quiz (in order of value):**\n1. **\"What would you do?\" scenarios** — Present a new situation the learner hasn't seen and ask them to apply what they learned. e.g., \"You want to add a 'save to favorites' feature. Which files would you need to change?\" This is the gold standard.\n2. **Debugging scenarios** — \"A user reports X is broken. Based on what you learned, where would you look first?\" This tests whether they understood the architecture, not just memorized file names.\n3. **Architecture decisions** — \"You're building a similar app from scratch. Would you put this logic in the frontend or backend? Why?\" Tests whether they understood the *reasoning* behind design choices.\n4. **Tracing exercises** — \"When a user does X, trace the path the data takes.\" Tests whether they can follow the flow.\n\n**What NOT to quiz:**\n- Definitions (\"What does API stand for?\") — that's what the glossary tooltips are for\n- File name recall (\"Which file handles X?\") — nobody memorizes file names\n- Syntax details (\"What's the correct way to write a fetch call?\") — this isn't a coding bootcamp\n- Anything that can be answered by scrolling up and copying — that tests scrolling, not understanding\n\n**Quiz tone:**\n- Wrong answers get encouraging, non-judgmental explanations (\"Not quite — here's why...\")\n- Correct answers get brief reinforcement of the underlying principle (\"Exactly! This works because...\")\n- Never punitive, never score-focused. No \"You got 3/5!\" — the quiz is a thinking exercise, not an exam\n- Wrong answer explanations should teach something new, not just say \"wrong, the answer was B\"\n\n**How many quizzes:** One per module, placed at the end after the learner has seen all the content. 3-5 questions per quiz. Each question should make the learner pause and *think*, not just pick the obvious answer.\n\n**Deciding what concepts are worth quizzing:** Quiz the things that would actually help someone in practice — architecture understanding (\"where does this logic live and why?\"), debugging intuition (\"what would cause this symptom?\"), and decision-making (\"what's the tradeoff here?\"). If a concept won't help someone debug a problem, steer an AI assistant, or make an architectural decision, it's not worth quizzing.\n\n<!-- File: references/design-system.md (Tokens: ~3837 | Category: anthropic-skill) -->\n# Design System Reference\n\nComplete CSS design tokens for the course. Copy this entire `:root` block into the course HTML and adapt the accent color to suit the project's personality.\n\n## Table of Contents\n1. [Color Palette](#color-palette)\n2. [Typography](#typography)\n3. [Spacing & Layout](#spacing--layout)\n4. [Shadows & Depth](#shadows--depth)\n5. [Animations & Transitions](#animations--transitions)\n6. [Navigation & Progress](#navigation--progress)\n7. [Module Structure](#module-structure)\n8. [Responsive Breakpoints](#responsive-breakpoints)\n9. [Scrollbar & Background](#scrollbar--background)\n\n---\n\n## Color Palette\n\n```css\n:root {\n  /* --- BACKGROUNDS --- */\n  --color-bg:             #FAF7F2;       /* warm off-white, like aged paper */\n  --color-bg-warm:        #F5F0E8;       /* slightly warmer for alternating modules */\n  --color-bg-code:        #1E1E2E;       /* deep indigo-charcoal for code blocks */\n  --color-text:           #2C2A28;       /* dark charcoal, easy on eyes */\n  --color-text-secondary: #6B6560;       /* warm gray for secondary text */\n  --color-text-muted:     #9E9790;       /* muted for timestamps, labels */\n  --color-border:         #E5DFD6;       /* subtle warm border */\n  --color-border-light:   #EEEBE5;       /* even lighter border */\n  --color-surface:        #FFFFFF;       /* card surfaces */\n  --color-surface-warm:   #FDF9F3;       /* warm card surface */\n\n  /* --- ACCENT (adapt per project — pick ONE bold color) ---\n     Default: vermillion. Alternatives: coral (#E06B56), teal (#2A7B9B),\n     amber (#D4A843), forest (#2D8B55). Avoid purple gradients. */\n  --color-accent:         #D94F30;\n  --color-accent-hover:   #C4432A;\n  --color-accent-light:   #FDEEE9;\n  --color-accent-muted:   #E8836C;\n\n  /* --- SEMANTIC --- */\n  --color-success:        #2D8B55;\n  --color-success-light:  #E8F5EE;\n  --color-error:          #C93B3B;\n  --color-error-light:    #FDE8E8;\n  --color-info:           #2A7B9B;\n  --color-info-light:     #E4F2F7;\n\n  /* --- ACTOR COLORS (assign to main components) ---\n     Each major \"character\" in the codebase gets a distinct color\n     for chat bubbles, diagrams, and highlights */\n  --color-actor-1:        #D94F30;       /* vermillion */\n  --color-actor-2:        #2A7B9B;       /* teal */\n  --color-actor-3:        #7B6DAA;       /* muted plum */\n  --color-actor-4:        #D4A843;       /* golden */\n  --color-actor-5:        #2D8B55;       /* forest */\n}\n```\n\n**Rules:**\n- Even-numbered modules use `--color-bg`, odd-numbered use `--color-bg-warm` (alternating backgrounds create visual rhythm)\n- Actor colors should be visually distinct from each other and from the accent\n- Code blocks always use `--color-bg-code` with light text\n\n---\n\n## Typography\n\n```css\n:root {\n  /* --- FONTS ---\n     Display: bold, geometric, personality-driven. NOT Inter/Roboto/Arial.\n     Body: readable with character. NOT system fonts.\n     Mono: developer-friendly with clear character distinction. */\n  --font-display:  'Bricolage Grotesque', Georgia, serif;\n  --font-body:     'DM Sans', -apple-system, sans-serif;\n  --font-mono:     'JetBrains Mono', 'Fira Code', 'Consolas', monospace;\n\n  /* --- TYPE SCALE (1.25 ratio) --- */\n  --text-xs:   0.75rem;    /* 12px — labels, badges */\n  --text-sm:   0.875rem;   /* 14px — secondary text, code */\n  --text-base: 1rem;       /* 16px — body text */\n  --text-lg:   1.125rem;   /* 18px — lead paragraphs */\n  --text-xl:   1.25rem;    /* 20px — screen headings */\n  --text-2xl:  1.5rem;     /* 24px — sub-module titles */\n  --text-3xl:  1.875rem;   /* 30px — module subtitles */\n  --text-4xl:  2.25rem;    /* 36px — module titles */\n  --text-5xl:  3rem;       /* 48px — hero text */\n  --text-6xl:  3.75rem;    /* 60px — module numbers */\n\n  /* --- LINE HEIGHTS --- */\n  --leading-tight:  1.15;  /* headings */\n  --leading-snug:   1.3;   /* subheadings */\n  --leading-normal: 1.6;   /* body text */\n  --leading-loose:  1.8;   /* relaxed reading */\n}\n```\n\n**Google Fonts link (put in `<head>`):**\n```html\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400;1,9..40,500&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n```\n\n**Rules:**\n- Module numbers: `--text-6xl`, font-display, weight 800, `--color-accent` with 15% opacity\n- Module titles: `--text-4xl`, font-display, weight 700\n- Screen headings: `--text-xl` or `--text-2xl`, font-display, weight 600\n- Body text: `--text-base` or `--text-lg`, font-body, `--leading-normal`\n- Code: `--text-sm`, font-mono\n- Labels/badges: `--text-xs`, font-mono, uppercase, letter-spacing 0.05em\n\n---\n\n## Spacing & Layout\n\n```css\n:root {\n  --space-1:  0.25rem;   /* 4px */\n  --space-2:  0.5rem;    /* 8px */\n  --space-3:  0.75rem;   /* 12px */\n  --space-4:  1rem;      /* 16px */\n  --space-5:  1.25rem;   /* 20px */\n  --space-6:  1.5rem;    /* 24px */\n  --space-8:  2rem;      /* 32px */\n  --space-10: 2.5rem;    /* 40px */\n  --space-12: 3rem;      /* 48px */\n  --space-16: 4rem;      /* 64px */\n  --space-20: 5rem;      /* 80px */\n  --space-24: 6rem;      /* 96px */\n\n  --content-width:     800px;   /* standard reading width */\n  --content-width-wide: 1000px; /* for side-by-side layouts */\n  --nav-height:        50px;\n  --radius-sm:  8px;\n  --radius-md:  12px;\n  --radius-lg:  16px;\n  --radius-full: 9999px;\n}\n```\n\n**Module layout:**\n```css\n.module {\n  min-height: 100dvh;       /* fallback: 100vh */\n  scroll-snap-align: start;\n  padding: var(--space-16) var(--space-6);\n  padding-top: calc(var(--nav-height) + var(--space-12));\n}\n.module-content {\n  max-width: var(--content-width);\n  margin: 0 auto;\n}\n```\n\n---\n\n## Shadows & Depth\n\n```css\n:root {\n  --shadow-sm:  0 1px 2px rgba(44, 42, 40, 0.05);\n  --shadow-md:  0 4px 12px rgba(44, 42, 40, 0.08);\n  --shadow-lg:  0 8px 24px rgba(44, 42, 40, 0.1);\n  --shadow-xl:  0 16px 48px rgba(44, 42, 40, 0.12);\n}\n```\n\nUse warm-tinted RGBA (44, 42, 40) — never pure black shadows.\n\n---\n\n## Animations & Transitions\n\n```css\n:root {\n  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);\n  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);\n  --duration-fast:   150ms;\n  --duration-normal: 300ms;\n  --duration-slow:   500ms;\n  --stagger-delay:   120ms;\n}\n```\n\n**Scroll-triggered reveal pattern:**\n```css\n.animate-in {\n  opacity: 0;\n  transform: translateY(20px);\n  transition: opacity var(--duration-slow) var(--ease-out),\n              transform var(--duration-slow) var(--ease-out);\n}\n.animate-in.visible {\n  opacity: 1;\n  transform: translateY(0);\n}\n\n/* Stagger children */\n.stagger-children > .animate-in {\n  transition-delay: calc(var(--stagger-index, 0) * var(--stagger-delay));\n}\n```\n\n**JS setup for stagger:**\n```javascript\ndocument.querySelectorAll('.stagger-children').forEach(parent => {\n  Array.from(parent.children).forEach((child, i) => {\n    child.style.setProperty('--stagger-index', i);\n  });\n});\n```\n\n**Intersection Observer (trigger reveals):**\n```javascript\nconst observer = new IntersectionObserver((entries) => {\n  entries.forEach(entry => {\n    if (entry.isIntersecting) {\n      entry.target.classList.add('visible');\n      observer.unobserve(entry.target); // animate only once\n    }\n  });\n}, { rootMargin: '0px 0px -10% 0px', threshold: 0.1 });\n\ndocument.querySelectorAll('.animate-in').forEach(el => observer.observe(el));\n```\n\n---\n\n## Navigation & Progress\n\n**HTML structure:**\n```html\n<nav class=\"nav\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-valuenow=\"0\"></div>\n  <div class=\"nav-inner\">\n    <span class=\"nav-title\">Course Title</span>\n    <div class=\"nav-dots\">\n      <button class=\"nav-dot\" data-target=\"module-1\" data-tooltip=\"Module 1 Name\"\n              role=\"tab\" aria-label=\"Module 1\"></button>\n      <!-- one per module -->\n    </div>\n  </div>\n</nav>\n```\n\n**Progress bar (CSS-only where possible, JS fallback):**\n```javascript\nfunction updateProgressBar() {\n  const scrollTop = window.scrollY;\n  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;\n  const progress = (scrollTop / scrollHeight) * 100;\n  progressBar.style.width = progress + '%';\n}\nwindow.addEventListener('scroll', () => {\n  requestAnimationFrame(updateProgressBar);\n}, { passive: true });\n```\n\n**Nav dot states:**\n- Default: `border: 2px solid var(--color-text-muted)`, empty center\n- Current: `border-color: var(--color-accent)`, filled center, subtle glow shadow\n- Visited: `background: var(--color-accent)`, filled solid\n\n**Keyboard navigation:**\n```javascript\ndocument.addEventListener('keydown', (e) => {\n  if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) return;\n  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { nextModule(); e.preventDefault(); }\n  if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { prevModule(); e.preventDefault(); }\n});\n```\n\n---\n\n## Module Structure\n\n**HTML template for each module:**\n```html\n<section class=\"module\" id=\"module-N\" style=\"background: var(--color-bg or --color-bg-warm)\">\n  <div class=\"module-content\">\n    <header class=\"module-header animate-in\">\n      <span class=\"module-number\">0N</span>\n      <h1 class=\"module-title\">Module Title</h1>\n      <p class=\"module-subtitle\">One-line description of what this module teaches</p>\n    </header>\n\n    <div class=\"module-body\">\n      <section class=\"screen animate-in\">\n        <h2 class=\"screen-heading\">Screen Title</h2>\n        <p>Content...</p>\n        <!-- Interactive elements, code translations, etc. -->\n      </section>\n\n      <section class=\"screen animate-in\">\n        <!-- Next screen -->\n      </section>\n    </div>\n  </div>\n</section>\n```\n\n---\n\n## Responsive Breakpoints\n\n```css\n/* Tablet */\n@media (max-width: 768px) {\n  :root {\n    --text-4xl: 1.875rem;\n    --text-5xl: 2.25rem;\n    --text-6xl: 3rem;\n  }\n  .translation-block { grid-template-columns: 1fr; } /* stack code/english */\n  .pattern-cards { grid-template-columns: 1fr 1fr; }\n}\n\n/* Mobile */\n@media (max-width: 480px) {\n  :root {\n    --text-4xl: 1.5rem;\n    --text-5xl: 1.875rem;\n    --text-6xl: 2.25rem;\n  }\n  .module { padding: var(--space-8) var(--space-4); }\n  .pattern-cards { grid-template-columns: 1fr; }\n  .flow-steps { flex-direction: column; }\n  .flow-arrow { transform: rotate(90deg); }\n}\n```\n\n---\n\n## Scrollbar & Background\n\n```css\n/* Custom scrollbar */\n::-webkit-scrollbar { width: 6px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: var(--radius-full);\n}\n\n/* Subtle atmospheric background */\nbody {\n  background: var(--color-bg);\n  background-image: radial-gradient(\n    ellipse at 20% 50%,\n    rgba(217, 79, 48, 0.03) 0%,\n    transparent 50%\n  );\n}\n\n/* Page scroll setup */\nhtml {\n  scroll-snap-type: y proximity;\n  scroll-behavior: smooth;\n}\n```\n\n---\n\n## Code Block Globals\n\nAll code blocks in the course — whether inside translation blocks, standalone snippets, or quiz challenges — must wrap text and never show a horizontal scrollbar. This is a teaching tool, not an IDE.\n\n```css\npre, code {\n  white-space: pre-wrap;       /* wrap long lines */\n  word-break: break-word;      /* break mid-word if absolutely needed */\n  overflow-x: hidden;          /* no horizontal scrollbar — ever */\n}\n/* Hide scrollbars on code containers */\n.translation-code::-webkit-scrollbar,\npre::-webkit-scrollbar {\n  display: none;\n}\n```\n\nCode snippets must be **exact copies** from the real codebase — never modified, trimmed, or simplified. Instead, choose naturally short (5-10 line) sections from the code that illustrate the concept well. If a longer block is needed, show it all — the wrapping CSS will handle readability.\n\n---\n\n## Syntax Highlighting (Catppuccin-inspired)\n\nFor code blocks on the dark `--color-bg-code` background:\n\n```css\n.code-keyword  { color: #CBA6F7; }  /* purple — if, else, return, function */\n.code-string   { color: #A6E3A1; }  /* green — \"strings\" */\n.code-function { color: #89B4FA; }  /* blue — function names */\n.code-comment  { color: #6C7086; }  /* muted gray — // comments */\n.code-number   { color: #FAB387; }  /* peach — numbers */\n.code-property { color: #F9E2AF; }  /* yellow — object keys */\n.code-operator { color: #94E2D5; }  /* teal — =, =>, +, etc. */\n.code-tag      { color: #F38BA8; }  /* pink — HTML tags */\n.code-attr     { color: #F9E2AF; }  /* yellow — HTML attributes */\n.code-value    { color: #A6E3A1; }  /* green — attribute values */\n```\n\n<!-- File: references/gotchas.md (Tokens: ~662 | Category: anthropic-skill) -->\n# Gotchas — Common Failure Points\n\n> **When to read this:** During Phase 3 (writing module HTML) and Phase 4 (review). Check every one of these before considering a course complete.\n\nThese are real problems encountered when building courses. Check every one before considering a course complete.\n\n### Tooltip Clipping\nTranslation blocks use `overflow: hidden` for code wrapping. If tooltips use `position: absolute` inside the term element, they get clipped by the container. **Fix:** Tooltips must use `position: fixed` and be appended to `document.body`. Calculate position from `getBoundingClientRect()`. This is already handled by `main.js` but is the #1 bug that appears in every build.\n\n### Not Enough Tooltips\nThe most common failure is under-tooltipping. Non-technical learners don't know terms like REPL, JSON, flag, entry point, PATH, pip, namespace, function, class, module, PR, E2E, or even software names like Blender/GIMP. **Rule of thumb:** if a term wouldn't appear in everyday conversation with a non-technical friend, tooltip it. Err heavily on the side of too many. BUT: don't tooltip terms the user already knows well from their domain (e.g., AI/ML concepts for someone in AI).\n\n### Walls of Text\nThe course looks like a textbook instead of an infographic. This happens when you write more than 2-3 sentences in a row without a visual break. Every screen must be at least 50% visual. Convert any list of 3+ items into cards, any sequence into step cards or flow diagrams, any code explanation into a code↔English translation block.\n\n### Recycled Metaphors\nUsing \"restaurant\" or \"kitchen\" for everything. Every module needs its own metaphor that feels inevitable for that specific concept. If you catch yourself reaching for the same metaphor twice, stop and find one that fits the concept organically.\n\n### Code Modifications\nTrimming, simplifying, or \"cleaning up\" code snippets from the codebase. The learner should be able to open the real file and see the exact same code. Instead of editing code to be shorter, *choose* naturally short snippets (5-10 lines) from the codebase that illustrate the point.\n\n### Quiz Questions That Test Memory\nAsking \"What does API stand for?\" or \"Which file handles X?\" — those test recall, not understanding. Every quiz question should present a new scenario the learner hasn't seen and ask them to *apply* what they learned.\n\n### Scroll-Snap Mandatory\nUsing `scroll-snap-type: y mandatory` traps users inside long modules. Always use `proximity`.\n\n### Module Quality Degradation\nTrying to write all modules in one pass causes later modules to be thin and rushed. Build one module at a time and verify each before moving on. For complex codebases, use the parallel path with module briefs.\n\n### Missing Interactive Elements\nA module with only text and code blocks, no interactivity. Every module needs at least one of: quiz, data flow animation, group chat, architecture diagram, drag-and-drop. These aren't decorations — they're how non-technical learners actually process information.\n\n<!-- File: references/interactive-elements.md (Tokens: ~8559 | Category: anthropic-skill) -->\n# Interactive Elements Reference\n\nImplementation patterns for every interactive element type used in courses. Pick the elements that best serve each module's teaching goal.\n\n> **Architecture note:** All CSS and JavaScript for these elements live in `references/styles.css` and `references/main.js`, which are copied verbatim into every course directory. When writing module HTML files, use only the HTML patterns below — do **not** inline `<style>` or `<script>` tags for these elements. The engines in `main.js` auto-initialize on page load by scanning for the relevant class names and `data-*` attributes described here.\n\n## Table of Contents\n1. [Code ↔ English Translation Blocks](#code--english-translation-blocks)\n2. [Multiple-Choice Quizzes](#multiple-choice-quizzes)\n3. [Drag-and-Drop Matching](#drag-and-drop-matching)\n4. [Group Chat Animation](#group-chat-animation)\n5. [Message Flow / Data Flow Animation](#message-flow--data-flow-animation)\n6. [Interactive Architecture Diagram](#interactive-architecture-diagram)\n7. [Layer Toggle Demo](#layer-toggle-demo)\n8. [\"Spot the Bug\" Challenge](#spot-the-bug-challenge)\n9. [Scenario Quiz](#scenario-quiz)\n10. [Callout Boxes](#callout-boxes)\n11. [Pattern/Feature Cards](#patternfeature-cards)\n12. [Flow Diagrams](#flow-diagrams)\n13. [Permission/Config Badges](#permissionconfig-badges)\n14. [Glossary Tooltips](#glossary-tooltips)\n15. [Visual File Tree](#visual-file-tree)\n16. [Icon-Label Rows](#icon-label-rows)\n17. [Numbered Step Cards](#numbered-step-cards)\n\n---\n\n## Code ↔ English Translation Blocks\n\nThe most important teaching element. Shows real code from the project on the left and a plain English translation on the right, line by line.\n\n**HTML:**\n```html\n<div class=\"translation-block animate-in\">\n  <div class=\"translation-code\">\n    <span class=\"translation-label\">CODE</span>\n    <pre><code>\n<span class=\"code-line\"><span class=\"code-keyword\">const</span> response = <span class=\"code-keyword\">await</span> <span class=\"code-function\">fetch</span>(url, {</span>\n<span class=\"code-line\">  <span class=\"code-property\">method</span>: <span class=\"code-string\">'POST'</span>,</span>\n<span class=\"code-line\">  <span class=\"code-property\">headers</span>: { <span class=\"code-string\">'Authorization'</span>: apiKey }</span>\n<span class=\"code-line\">});</span>\n    </code></pre>\n  </div>\n  <div class=\"translation-english\">\n    <span class=\"translation-label\">PLAIN ENGLISH</span>\n    <div class=\"translation-lines\">\n      <p class=\"tl\">Send a request to the URL and wait for a response...</p>\n      <p class=\"tl\">We're sending data (POST), not just asking for it (GET)...</p>\n      <p class=\"tl\">Include our API key so the server knows who we are...</p>\n      <p class=\"tl\">End of the request setup.</p>\n    </div>\n  </div>\n</div>\n```\n\n**CSS:**\n```css\n.translation-block {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 0;\n  border-radius: var(--radius-md);\n  overflow: hidden;\n  box-shadow: var(--shadow-md);\n  margin: var(--space-8) 0;\n}\n.translation-code {\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-6);\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  position: relative;\n  overflow-x: hidden;  /* NO horizontal scrollbar — ever */\n}\n.translation-code pre,\n.translation-code code {\n  white-space: pre-wrap;       /* wrap long lines instead of scrolling */\n  word-break: break-word;      /* break mid-word if needed */\n  overflow-x: hidden;\n}\n.translation-english {\n  background: var(--color-surface-warm);\n  padding: var(--space-6);\n  font-size: var(--text-sm);\n  line-height: 1.7;\n  border-left: 3px solid var(--color-accent);\n}\n.translation-label {\n  position: absolute;\n  top: var(--space-2);\n  right: var(--space-3);\n  font-size: var(--text-xs);\n  text-transform: uppercase;\n  letter-spacing: 0.1em;\n  opacity: 0.5;\n}\n.translation-english .translation-label {\n  color: var(--color-text-muted);\n}\n/* Responsive: stack vertically on mobile */\n@media (max-width: 768px) {\n  .translation-block { grid-template-columns: 1fr; }\n  .translation-english { border-left: none; border-top: 3px solid var(--color-accent); }\n}\n```\n\n**Rules:**\n- Each English line should correspond to 1-2 code lines\n- Use conversational language, not technical jargon\n- Highlight the \"why\" not just the \"what\" — e.g., \"Include our API key so the server knows who we are\" not \"Set the Authorization header\"\n\n---\n\n## Multiple-Choice Quizzes\n\nFor testing understanding with instant feedback. Each question has options, one correct answer, and per-question explanations.\n\n**Wiring:** `main.js` exposes `window.selectOption(btn)`, `window.checkQuiz(containerId)`, and `window.resetQuiz(containerId)`. Call them via `onclick`. Per-question explanations go in `data-explanation-right` and `data-explanation-wrong` on the `.quiz-question-block`.\n\n**HTML:**\n```html\n<div class=\"quiz-container\" id=\"quiz-module3\">\n  <div class=\"quiz-question-block\"\n       data-correct=\"option-b\"\n       data-explanation-right=\"Exactly — because X is responsible for Y in this architecture.\"\n       data-explanation-wrong=\"Not quite. Think about where Y lives in the codebase...\">\n    <h3 class=\"quiz-question\">Question text here?</h3>\n    <div class=\"quiz-options\">\n      <button class=\"quiz-option\" data-value=\"option-a\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer A</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-b\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer B (correct)</span>\n      </button>\n      <button class=\"quiz-option\" data-value=\"option-c\" onclick=\"selectOption(this)\">\n        <div class=\"quiz-option-radio\"></div>\n        <span>Answer C</span>\n      </button>\n    </div>\n    <div class=\"quiz-feedback\"></div>\n  </div>\n\n  <button class=\"quiz-check-btn\" onclick=\"checkQuiz('quiz-module3')\">Check Answers</button>\n  <button class=\"quiz-reset-btn\" onclick=\"resetQuiz('quiz-module3')\">Try Again</button>\n</div>\n```\n\n**CSS for quiz states:**\n```css\n.quiz-option {\n  display: flex; align-items: center; gap: var(--space-3);\n  padding: var(--space-3) var(--space-4);\n  border: 2px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  background: var(--color-surface);\n  cursor: pointer; width: 100%;\n  transition: border-color var(--duration-fast), background var(--duration-fast);\n}\n.quiz-option:hover { border-color: var(--color-accent-muted); }\n.quiz-option.selected { border-color: var(--color-accent); background: var(--color-accent-light); }\n.quiz-option.correct { border-color: var(--color-success); background: var(--color-success-light); }\n.quiz-option.incorrect { border-color: var(--color-error); background: var(--color-error-light); }\n.quiz-option-radio {\n  width: 18px; height: 18px; border-radius: 50%;\n  border: 2px solid var(--color-border);\n  transition: all var(--duration-fast);\n}\n.quiz-option.selected .quiz-option-radio {\n  border-color: var(--color-accent);\n  background: var(--color-accent);\n  box-shadow: inset 0 0 0 3px white;\n}\n.quiz-feedback {\n  max-height: 0; overflow: hidden; opacity: 0;\n  transition: max-height var(--duration-normal), opacity var(--duration-normal);\n}\n.quiz-feedback.show { max-height: 200px; opacity: 1; padding: var(--space-3); margin-top: var(--space-2); border-radius: var(--radius-sm); }\n.quiz-feedback.success { background: var(--color-success-light); color: var(--color-success); }\n.quiz-feedback.error { background: var(--color-error-light); color: var(--color-error); }\n```\n\n---\n\n## Drag-and-Drop Matching\n\nFor matching concepts to descriptions. Supports both mouse (HTML5 Drag API) and touch.\n\n**HTML:**\n```html\n<div class=\"dnd-container\">\n  <div class=\"dnd-chips\">\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-a\">Actor A</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-b\">Actor B</div>\n    <div class=\"dnd-chip\" draggable=\"true\" data-answer=\"actor-c\">Actor C</div>\n  </div>\n  <div class=\"dnd-zones\">\n    <div class=\"dnd-zone\" data-correct=\"actor-a\">\n      <p class=\"dnd-zone-label\">Description for Actor A</p>\n      <div class=\"dnd-zone-target\">Drop here</div>\n    </div>\n    <!-- more zones -->\n  </div>\n  <button onclick=\"checkDnD()\">Check Matches</button>\n  <button onclick=\"resetDnD()\">Reset</button>\n</div>\n```\n\n**JS (mouse + touch):**\n```javascript\n// MOUSE: HTML5 Drag API\nchips.forEach(chip => {\n  chip.addEventListener('dragstart', (e) => {\n    e.dataTransfer.setData('text/plain', chip.dataset.answer);\n    chip.classList.add('dragging');\n  });\n  chip.addEventListener('dragend', () => chip.classList.remove('dragging'));\n});\n\nzones.forEach(zone => {\n  const target = zone.querySelector('.dnd-zone-target');\n  target.addEventListener('dragover', (e) => { e.preventDefault(); target.classList.add('drag-over'); });\n  target.addEventListener('dragleave', () => target.classList.remove('drag-over'));\n  target.addEventListener('drop', (e) => {\n    e.preventDefault();\n    target.classList.remove('drag-over');\n    const answer = e.dataTransfer.getData('text/plain');\n    const chip = document.querySelector(`[data-answer=\"${answer}\"]`);\n    target.textContent = chip.textContent;\n    target.dataset.placed = answer;\n    chip.classList.add('placed');\n  });\n});\n\n// TOUCH: Custom implementation (HTML5 drag doesn't work on mobile)\nchips.forEach(chip => {\n  chip.addEventListener('touchstart', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    const clone = chip.cloneNode(true);\n    clone.classList.add('touch-ghost');\n    clone.style.cssText = `position:fixed; z-index:1000; pointer-events:none;\n      left:${touch.clientX - 40}px; top:${touch.clientY - 20}px;`;\n    document.body.appendChild(clone);\n    chip._ghost = clone;\n    chip._answer = chip.dataset.answer;\n  }, { passive: false });\n\n  chip.addEventListener('touchmove', (e) => {\n    e.preventDefault();\n    const touch = e.touches[0];\n    if (chip._ghost) {\n      chip._ghost.style.left = (touch.clientX - 40) + 'px';\n      chip._ghost.style.top = (touch.clientY - 20) + 'px';\n    }\n    // Highlight zone under finger\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    zones.forEach(z => z.querySelector('.dnd-zone-target').classList.remove('drag-over'));\n    if (el && el.closest('.dnd-zone-target')) {\n      el.closest('.dnd-zone-target').classList.add('drag-over');\n    }\n  }, { passive: false });\n\n  chip.addEventListener('touchend', (e) => {\n    if (chip._ghost) { chip._ghost.remove(); chip._ghost = null; }\n    const touch = e.changedTouches[0];\n    const el = document.elementFromPoint(touch.clientX, touch.clientY);\n    if (el && el.closest('.dnd-zone-target')) {\n      const target = el.closest('.dnd-zone-target');\n      target.textContent = chip.textContent;\n      target.dataset.placed = chip._answer;\n      chip.classList.add('placed');\n    }\n  });\n});\n```\n\n---\n\n## Group Chat Animation\n\niMessage/WeChat-style chat showing components \"talking\" to each other. Messages appear one by one with typing indicators.\n\n**Wiring:** `main.js` auto-initializes every `.chat-window` on page load. Give each chat window a unique `id`. Control buttons need these classes: `.chat-next-btn`, `.chat-all-btn`, `.chat-reset-btn`. The typing indicator avatar element should have `id=\"{chatWindowId}-typing-avatar\"` or simply be the first `.chat-avatar` inside `.chat-typing`.\n\n**HTML:**\n```html\n<div class=\"chat-window\" id=\"chat-module2\">\n  <div class=\"chat-messages\">\n    <div class=\"chat-message\" data-msg=\"0\" data-sender=\"actor-a\" style=\"display:none\">\n      <div class=\"chat-avatar\" style=\"background: var(--color-actor-1)\">A</div>\n      <div class=\"chat-bubble\">\n        <span class=\"chat-sender\" style=\"color: var(--color-actor-1)\">Actor A</span>\n        <p>Hey Background, I need the data for this item.</p>\n      </div>\n    </div>\n    <!-- more messages... -->\n  </div>\n\n  <div class=\"chat-typing\" id=\"chat-typing\" style=\"display:none\">\n    <div class=\"chat-avatar\" id=\"typing-avatar\">?</div>\n    <div class=\"chat-typing-dots\">\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n      <span class=\"typing-dot\"></span>\n    </div>\n  </div>\n\n  <div class=\"chat-controls\">\n    <button class=\"btn chat-next-btn\">Next Message</button>\n    <button class=\"btn chat-all-btn\">Play All</button>\n    <button class=\"btn chat-reset-btn\">Replay</button>\n    <span class=\"chat-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for typing dots:**\n```css\n.typing-dot {\n  width: 8px; height: 8px; border-radius: 50%;\n  background: var(--color-text-muted);\n  animation: typingBounce 1.4s infinite;\n}\n.typing-dot:nth-child(2) { animation-delay: 0.2s; }\n.typing-dot:nth-child(3) { animation-delay: 0.4s; }\n@keyframes typingBounce {\n  0%, 60%, 100% { transform: translateY(0); }\n  30% { transform: translateY(-6px); }\n}\n```\n\n---\n\n## Message Flow / Data Flow Animation\n\nStep-by-step visualization of data moving between components. User clicks \"Next Step\" to advance.\n\n**Wiring:** `main.js` auto-initializes every `.flow-animation` on page load. Pass steps as JSON in `data-steps`. Each step object: `{ highlight: \"flow-actor-id\", label: \"description\", packet: true, from: \"actor-id-suffix\", to: \"actor-id-suffix\" }`. Actor element IDs must be `flow-actor-1`, `flow-actor-2`, etc. Control buttons need classes `.flow-next-btn` and `.flow-reset-btn`.\n\n> **⚠️ Single quotes in step labels will break parsing.** The `data-steps` attribute is delimited by single quotes (`data-steps='[...]'`), so any single quote inside a label (e.g. `\"the user's request\"`) will terminate the attribute early and cause `JSON.parse` to fail silently — the entire animation will stop working. Either avoid apostrophes in labels, replace them with `&apos;`, or rewrite the attribute using double-quote delimiters with escaped inner quotes (`data-steps=\"[{\\\"label\\\":\\\"...\\\"}]\"`).\n\n**HTML:**\n```html\n<div class=\"flow-animation\" data-steps='[\n  {\"highlight\":\"flow-actor-1\",\"label\":\"User clicks the button\"},\n  {\"highlight\":\"flow-actor-1\",\"label\":\"Frontend sends request\",\"packet\":true,\"from\":\"actor-1\",\"to\":\"actor-2\"},\n  {\"highlight\":\"flow-actor-2\",\"label\":\"Backend calls the database\",\"packet\":true,\"from\":\"actor-2\",\"to\":\"actor-3\"}\n]'>\n  <div class=\"flow-actors\">\n    <div class=\"flow-actor\" id=\"flow-actor-1\">\n      <div class=\"flow-actor-icon\">A</div>\n      <span>Actor 1</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-2\">\n      <div class=\"flow-actor-icon\">B</div>\n      <span>Actor 2</span>\n    </div>\n    <div class=\"flow-actor\" id=\"flow-actor-3\">\n      <div class=\"flow-actor-icon\">C</div>\n      <span>Actor 3</span>\n    </div>\n  </div>\n\n  <div class=\"flow-packet\" id=\"flow-packet\"></div>\n\n  <div class=\"flow-step-label\" id=\"flow-label\">Click \"Next Step\" to begin</div>\n\n  <div class=\"flow-controls\">\n    <button class=\"btn flow-next-btn\">Next Step</button>\n    <button class=\"btn flow-reset-btn\">Restart</button>\n    <span class=\"flow-progress\"></span>\n  </div>\n</div>\n```\n\n**CSS for active actor glow:**\n```css\n.flow-actor.active {\n  box-shadow: 0 0 0 3px var(--color-accent), 0 0 20px rgba(217, 79, 48, 0.2);\n  transform: scale(1.05);\n  transition: all var(--duration-normal) var(--ease-out);\n}\n```\n\n---\n\n## Interactive Architecture Diagram\n\nFull-system diagram where hovering/clicking a component shows a description tooltip.\n\n**HTML:**\n```html\n<div class=\"arch-diagram\">\n  <div class=\"arch-zone arch-zone-browser\">\n    <h4 class=\"arch-zone-label\">Browser</h4>\n    <div class=\"arch-component\" data-desc=\"Injects UI into the web page, reads DOM, captures user actions\"\n         onclick=\"showArchDesc(this)\">\n      <div class=\"arch-icon\">📄</div>\n      <span>Component A</span>\n    </div>\n    <!-- more components -->\n  </div>\n  <div class=\"arch-zone arch-zone-external\">\n    <h4 class=\"arch-zone-label\">External Services</h4>\n    <!-- API cards -->\n  </div>\n  <div class=\"arch-description\" id=\"arch-desc\">Click any component to learn what it does</div>\n</div>\n```\n\n---\n\n## Layer Toggle Demo\n\nShows how different layers (e.g., HTML/CSS/JS, or data/logic/UI) build on each other. Three tabs switch between views.\n\n**HTML:**\n```html\n<div class=\"layer-demo\">\n  <div class=\"layer-tabs\">\n    <button class=\"layer-tab active\" onclick=\"showLayer('html')\">HTML</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('css')\">+ CSS</button>\n    <button class=\"layer-tab\" onclick=\"showLayer('js')\">+ JS</button>\n  </div>\n  <div class=\"layer-viewport\">\n    <div class=\"layer\" id=\"layer-html\" style=\"display:block\">\n      <!-- Raw unstyled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-css\" style=\"display:none\">\n      <!-- Styled version -->\n    </div>\n    <div class=\"layer\" id=\"layer-js\" style=\"display:none\">\n      <!-- Interactive version -->\n    </div>\n  </div>\n  <p class=\"layer-description\" id=\"layer-desc\">This is the raw HTML...</p>\n</div>\n```\n\n---\n\n## \"Spot the Bug\" Challenge\n\nShow code with a deliberate bug. User clicks the buggy line. Reveal explains the issue.\n\n**HTML:**\n```html\n<div class=\"bug-challenge\">\n  <h3>Find the bug in this code:</h3>\n  <div class=\"bug-code\">\n    <div class=\"bug-line\" data-line=\"1\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">1</span>\n      <code>chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"2\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">2</span>\n      <code>  if (msg.action === 'fetchData') {</code>\n    </div>\n    <div class=\"bug-line bug-target\" data-line=\"3\" onclick=\"checkBugLine(this, true)\">\n      <span class=\"line-num\">3</span>\n      <code>    fetch(url).then(r => r.json()).then(data => sendResponse(data));</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"4\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">4</span>\n      <code>  }</code>\n    </div>\n    <div class=\"bug-line\" data-line=\"5\" onclick=\"checkBugLine(this, false)\">\n      <span class=\"line-num\">5</span>\n      <code>});</code>\n    </div>\n  </div>\n  <div class=\"bug-feedback\" id=\"bug-feedback\"></div>\n</div>\n```\n\n**JS:**\n```javascript\nwindow.checkBugLine = function(el, isCorrect) {\n  const feedback = el.closest('.bug-challenge').querySelector('.bug-feedback');\n  if (isCorrect) {\n    el.classList.add('correct');\n    feedback.innerHTML = '<strong>Found it!</strong> The listener uses an async operation (fetch) but doesn\\'t return true. Chrome closes the message channel before the response can be sent. Fix: add <code>return true;</code> at the end.';\n    feedback.className = 'bug-feedback show success';\n  } else {\n    el.classList.add('incorrect');\n    feedback.innerHTML = 'Not this line — look for where the async timing might cause problems...';\n    feedback.className = 'bug-feedback show error';\n    setTimeout(() => { el.classList.remove('incorrect'); feedback.className = 'bug-feedback'; }, 2000);\n  }\n};\n```\n\n---\n\n## Scenario Quiz\n\n\"What would a senior engineer do?\" — situational questions with explanations.\n\nSame HTML/CSS/JS pattern as Multiple-Choice Quizzes, but with longer scenario descriptions and more detailed explanations. Wrap each question in a scenario context block:\n\n```html\n<div class=\"scenario-block\">\n  <div class=\"scenario-context\">\n    <span class=\"scenario-label\">Scenario</span>\n    <p>Your app processes a 3-hour podcast transcript. The API has a 16,000 token limit. What do you do?</p>\n  </div>\n  <!-- quiz-options here -->\n</div>\n```\n\n---\n\n## Callout Boxes\n\n\"Aha!\" moments — universal CS insights. Max 2 per module.\n\n```html\n<div class=\"callout callout-accent\">\n  <div class=\"callout-icon\">💡</div>\n  <div class=\"callout-content\">\n    <strong class=\"callout-title\">Key Insight</strong>\n    <p>This pattern — splitting responsibilities into focused roles — is one of the most important ideas in software engineering. Engineers call it \"separation of concerns.\"</p>\n  </div>\n</div>\n```\n\n**Variants:**\n- `callout-accent`: vermillion left border, light accent background (for CS insights)\n- `callout-info`: teal left border, light info background (for \"good to know\")\n- `callout-warning`: red left border, light error background (for common mistakes)\n\n---\n\n## Pattern/Feature Cards\n\nGrid of cards highlighting engineering patterns, tech stack components, or key concepts.\n\n```html\n<div class=\"pattern-cards\">\n  <div class=\"pattern-card\" style=\"border-top: 3px solid var(--color-actor-1)\">\n    <div class=\"pattern-icon\" style=\"background: var(--color-actor-1)\">🔄</div>\n    <h4 class=\"pattern-title\">Caching</h4>\n    <p class=\"pattern-desc\">Store results to avoid redundant work — like keeping leftovers instead of cooking a new meal every time.</p>\n  </div>\n  <!-- more cards -->\n</div>\n```\n\n```css\n.pattern-cards {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n  gap: var(--space-4);\n}\n.pattern-card {\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  padding: var(--space-6);\n  box-shadow: var(--shadow-sm);\n  transition: transform var(--duration-normal) var(--ease-out), box-shadow var(--duration-normal);\n}\n.pattern-card:hover {\n  transform: translateY(-4px);\n  box-shadow: var(--shadow-md);\n}\n```\n\n---\n\n## Flow Diagrams\n\n**Horizontal flow (desktop):**\n```html\n<div class=\"flow-steps\">\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">1</div>\n    <p>User clicks button</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <div class=\"flow-step\">\n    <div class=\"flow-step-num\">2</div>\n    <p>Component A detects click</p>\n  </div>\n  <div class=\"flow-arrow\">→</div>\n  <!-- more steps -->\n</div>\n```\n\nArrows rotate to `↓` on mobile via CSS transform.\n\n---\n\n## Permission/Config Badges\n\nFor annotating config files, permissions, or settings:\n\n```html\n<div class=\"badge-list\">\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">storage</code>\n    <span class=\"badge-desc\">Save data between sessions (like browser bookmarks)</span>\n  </div>\n  <div class=\"badge-item\">\n    <code class=\"badge-code\">activeTab</code>\n    <span class=\"badge-desc\">Access the currently open tab (only when the user clicks)</span>\n  </div>\n</div>\n```\n\n```css\n.badge-item {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-3) var(--space-4);\n  border: 1px solid var(--color-border-light);\n  border-radius: var(--radius-sm);\n  transition: border-color var(--duration-fast);\n}\n.badge-item:hover { border-color: var(--color-accent-muted); }\n.badge-code {\n  font-family: var(--font-mono);\n  font-size: var(--text-sm);\n  background: var(--color-bg-code);\n  color: #CBA6F7;\n  padding: var(--space-1) var(--space-3);\n  border-radius: var(--radius-sm);\n  white-space: nowrap;\n}\n```\n\n---\n\n## Glossary Tooltips\n\nThe most important accessibility feature for non-technical learners. Any technical term in the course text should be wrapped in a tooltip that shows a plain-English definition on hover (desktop) or tap (mobile). The learner never has to leave the page or Google anything.\n\n**HTML — mark up terms inline:**\n```html\n<p>The extension uses a\n  <span class=\"term\" data-definition=\"A service worker is a background script that runs independently of the web page — like a behind-the-scenes assistant that's always on, even when you're not looking at the page.\">service worker</span>\n  to handle API calls.\n</p>\n```\n\n**CSS:**\n```css\n.term {\n  border-bottom: 1.5px dashed var(--color-accent-muted);\n  cursor: pointer;    /* NOT cursor: help — pointer feels clickable and inviting */\n  position: relative;\n}\n.term:hover, .term.active {\n  border-bottom-color: var(--color-accent);\n  color: var(--color-accent);\n}\n\n/* The tooltip bubble — uses position: fixed and is appended to document.body\n   via JS so it is NEVER clipped by ancestor overflow: hidden containers\n   (like translation blocks). See JS section below for positioning logic. */\n.term-tooltip {\n  position: fixed;        /* CRITICAL: fixed, not absolute — prevents clipping */\n  background: var(--color-bg-code);\n  color: #CDD6F4;\n  padding: var(--space-3) var(--space-4);\n  border-radius: var(--radius-sm);\n  font-size: var(--text-sm);\n  font-family: var(--font-body);\n  line-height: var(--leading-normal);\n  width: max(200px, min(320px, 80vw));\n  box-shadow: var(--shadow-lg);\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity var(--duration-fast);\n  z-index: 10000;        /* Above everything, including nav */\n}\n/* Arrow pointing down */\n.term-tooltip::after {\n  content: '';\n  position: absolute;\n  top: 100%;\n  left: 50%;\n  transform: translateX(-50%);\n  border: 6px solid transparent;\n  border-top-color: var(--color-bg-code);\n}\n.term-tooltip.visible {\n  opacity: 1;\n}\n\n/* If tooltip goes off-screen top, flip to below */\n.term-tooltip.flip {\n  bottom: auto;\n  top: calc(100% + 8px);\n}\n.term-tooltip.flip::after {\n  top: auto;\n  bottom: 100%;\n  border-top-color: transparent;\n  border-bottom-color: var(--color-bg-code);\n}\n```\n\n**JS — position: fixed tooltips appended to body (never clipped by overflow):**\n```javascript\n// Tooltip container — appended to body so it's never clipped\nlet activeTooltip = null;\n\nfunction positionTooltip(term, tip) {\n  const rect = term.getBoundingClientRect();\n  const tipWidth = 300; // approximate\n  let left = rect.left + rect.width / 2 - tipWidth / 2;\n  // Clamp to viewport\n  left = Math.max(8, Math.min(left, window.innerWidth - tipWidth - 8));\n\n  // Try above first\n  let top = rect.top - 8;\n  tip.style.left = left + 'px';\n\n  // Position above by default, flip below if no room\n  document.body.appendChild(tip);\n  const tipHeight = tip.offsetHeight;\n  if (rect.top - tipHeight - 8 < 0) {\n    // Flip below\n    tip.style.top = (rect.bottom + 8) + 'px';\n    tip.classList.add('flip');\n  } else {\n    tip.style.top = (rect.top - tipHeight - 8) + 'px';\n    tip.classList.remove('flip');\n  }\n}\n\ndocument.querySelectorAll('.term').forEach(term => {\n  const tip = document.createElement('span');\n  tip.className = 'term-tooltip';\n  tip.textContent = term.dataset.definition;\n\n  // Hover for desktop\n  term.addEventListener('mouseenter', () => {\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    positionTooltip(term, tip);\n    requestAnimationFrame(() => tip.classList.add('visible'));\n    activeTooltip = tip;\n  });\n\n  term.addEventListener('mouseleave', () => {\n    tip.classList.remove('visible');\n    setTimeout(() => { if (!tip.classList.contains('visible')) tip.remove(); }, 150);\n    activeTooltip = null;\n  });\n\n  // Tap for mobile\n  term.addEventListener('click', (e) => {\n    e.stopPropagation();\n    if (activeTooltip && activeTooltip !== tip) {\n      activeTooltip.classList.remove('visible');\n      activeTooltip.remove();\n    }\n    if (tip.classList.contains('visible')) {\n      tip.classList.remove('visible');\n      tip.remove();\n      activeTooltip = null;\n    } else {\n      positionTooltip(term, tip);\n      requestAnimationFrame(() => tip.classList.add('visible'));\n      activeTooltip = tip;\n    }\n  });\n});\n\n// Close tooltips when clicking elsewhere\ndocument.addEventListener('click', () => {\n  if (activeTooltip) {\n    activeTooltip.classList.remove('visible');\n    activeTooltip.remove();\n    activeTooltip = null;\n  }\n});\n```\n\n**Rules:**\n- Mark up EVERY technical term on first use in each module (API, DOM, callback, async, endpoint, middleware, etc.)\n- Keep definitions to 1-2 sentences max, in everyday language\n- Use a metaphor in the definition when it helps — e.g., \"A **callback** is like leaving your phone number at a restaurant so they can call you when your table is ready\"\n- Don't mark the same term twice within the same screen — only on first appearance per module\n- The dashed underline should be subtle enough not to distract but visible enough that curious learners discover it\n\n---\n\n## Visual File Tree\n\nUse instead of paragraphs listing \"this folder does X, that folder does Y.\" Much easier to scan.\n\n```html\n<div class=\"file-tree\">\n  <div class=\"ft-folder open\">\n    <span class=\"ft-name\">app/</span>\n    <span class=\"ft-desc\">Pages and API routes</span>\n    <div class=\"ft-children\">\n      <div class=\"ft-folder\">\n        <span class=\"ft-name\">api/</span>\n        <span class=\"ft-desc\">Backend endpoints the frontend calls</span>\n      </div>\n      <div class=\"ft-file\">\n        <span class=\"ft-name\">layout.tsx</span>\n        <span class=\"ft-desc\">The shell that wraps every page</span>\n      </div>\n    </div>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">components/</span>\n    <span class=\"ft-desc\">Reusable UI building blocks</span>\n  </div>\n  <div class=\"ft-folder\">\n    <span class=\"ft-name\">lib/</span>\n    <span class=\"ft-desc\">Shared logic and utilities</span>\n  </div>\n</div>\n```\n\n```css\n.file-tree { font-family: var(--font-mono); font-size: var(--text-sm); }\n.ft-folder, .ft-file {\n  padding: var(--space-2) var(--space-3);\n  border-left: 2px solid var(--color-border-light);\n  margin-left: var(--space-4);\n}\n.ft-folder > .ft-name { color: var(--color-accent); font-weight: 600; }\n.ft-folder > .ft-name::before { content: '📁 '; }\n.ft-file > .ft-name::before { content: '📄 '; }\n.ft-desc {\n  color: var(--color-text-secondary);\n  font-family: var(--font-body);\n  margin-left: var(--space-2);\n  font-size: var(--text-xs);\n}\n.ft-children { margin-left: var(--space-4); }\n```\n\n---\n\n## Icon-Label Rows\n\nFor listing components, features, or concepts visually. Replaces bullet-point paragraphs.\n\n```html\n<div class=\"icon-rows\">\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-1)\">🖥️</div>\n    <div>\n      <strong>Frontend (Next.js)</strong>\n      <p>What the user sees and interacts with</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-2)\">⚡</div>\n    <div>\n      <strong>API Routes</strong>\n      <p>Backend logic that runs on the server</p>\n    </div>\n  </div>\n  <div class=\"icon-row\">\n    <div class=\"icon-circle\" style=\"background: var(--color-actor-3)\">🗄️</div>\n    <div>\n      <strong>Database (Supabase)</strong>\n      <p>Where all the data is stored permanently</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.icon-rows { display: flex; flex-direction: column; gap: var(--space-4); }\n.icon-row {\n  display: flex; align-items: center; gap: var(--space-4);\n  padding: var(--space-4);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  box-shadow: var(--shadow-sm);\n}\n.icon-row p { margin: 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n.icon-circle {\n  width: 48px; height: 48px; border-radius: 50%;\n  display: flex; align-items: center; justify-content: center;\n  font-size: 1.25rem; flex-shrink: 0;\n}\n```\n\n---\n\n## Numbered Step Cards\n\nFor sequences that would otherwise be a numbered paragraph list. Visual, scannable, and each step stands alone.\n\n```html\n<div class=\"step-cards\">\n  <div class=\"step-card\">\n    <div class=\"step-num\">1</div>\n    <div class=\"step-body\">\n      <strong>User pastes a YouTube URL</strong>\n      <p>The frontend captures the URL and extracts the video ID</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">2</div>\n    <div class=\"step-body\">\n      <strong>API fetches the transcript</strong>\n      <p>A server-side route calls an external service to get the video's text</p>\n    </div>\n  </div>\n  <div class=\"step-card\">\n    <div class=\"step-num\">3</div>\n    <div class=\"step-body\">\n      <strong>AI analyzes the content</strong>\n      <p>The transcript is sent to an AI model that extracts key moments</p>\n    </div>\n  </div>\n</div>\n```\n\n```css\n.step-cards { display: flex; flex-direction: column; gap: var(--space-3); }\n.step-card {\n  display: flex; align-items: flex-start; gap: var(--space-4);\n  padding: var(--space-4) var(--space-5);\n  background: var(--color-surface);\n  border-radius: var(--radius-md);\n  border-left: 3px solid var(--color-accent);\n  box-shadow: var(--shadow-sm);\n}\n.step-num {\n  width: 32px; height: 32px; border-radius: 50%;\n  background: var(--color-accent);\n  color: white; font-weight: 700;\n  display: flex; align-items: center; justify-content: center;\n  font-family: var(--font-display);\n  flex-shrink: 0;\n}\n.step-body p { margin: var(--space-1) 0 0; color: var(--color-text-secondary); font-size: var(--text-sm); }\n```\n\n<!-- File: references/module-brief-template.md (Tokens: ~611 | Category: anthropic-skill) -->\n# Module Brief Template\n\n> **When to read this:** During Phase 2.5 (planning checkpoint) for complex codebases. Fill in one brief per module, save to `course-name/briefs/0N-slug.md`. Each brief gives a parallel agent everything it needs to write one module without reading the codebase or SKILL.md.\n\n---\n\n## Module N: [Title]\n\n### Teaching Arc\n- **Metaphor:** [A fresh, specific metaphor — never \"restaurant.\" See `references/content-philosophy.md` > Metaphors First]\n- **Opening hook:** [1 sentence that connects to something the learner already knows from using the app]\n- **Key insight:** [The one thing the learner should walk away understanding]\n- **\"Why should I care?\":** [How this helps them steer AI / debug / make decisions]\n\n### Code Snippets (pre-extracted)\n\nInclude the actual code the module will use in code↔English translation blocks. Copy-paste from the codebase with file path and line numbers. The writing agent will use these verbatim — it will NOT re-read the codebase.\n\nFile: src/example/file.ts (lines 12-24)\n[paste actual code here]\n\nFile: src/another/file.ts (lines 45-52)\n[paste actual code here]\n\n### Interactive Elements\n\nCheck which elements this module needs. Include enough detail for the writing agent to build them.\n\n- [ ] **Code↔English translation** — which snippet(s) from above\n- [ ] **Quiz** — [number] questions, style: [scenario / debugging / architecture / tracing]. Brief description of each question's angle.\n- [ ] **Group chat animation** — actors: [list]. Message flow summary: [who says what to whom, in what order]\n- [ ] **Data flow animation** — actors: [list]. Steps: [sequence of highlights and packet movements]\n- [ ] **Drag-and-drop** — items: [list], targets: [list]\n- [ ] **Other** — [architecture diagram, layer toggle, pattern cards, etc.]\n\n### Reference Files to Read\n\nList only the sections the writing agent needs — not the whole file.\n\n- `references/interactive-elements.md` → [section names, e.g., \"Multiple-Choice Quizzes\", \"Group Chat Animation\"]\n- `references/design-system.md` → [only if needed for specific tokens not in the brief]\n- `references/content-philosophy.md` → [always include — agent needs content rules]\n- `references/gotchas.md` → [always include — agent needs the checklist]\n\n### Connections\n\n- **Previous module:** [Title — what it covered, so this module can build on it]\n- **Next module:** [Title — what it will cover, so this module can set it up]\n- **Tone/style notes:** [Any course-wide consistency notes: accent color name, actor naming convention, etc.]\n</yakaai_skills>"}