const lessons = [ { id: 'go-json-parse', title: 'Parse JSON into a Go struct', language: 'Go', tags: ['json', 'struct', 'api'], excerpt: 'Use encoding/json with exported struct fields and explicit error handling.', summary: 'Go parses JSON by unmarshalling bytes into a struct. Field names must be exported, and struct tags let you map JSON keys to Go names.', code: `type Payload struct {\n Name string \`json:"name"\`\n}\n\nvar p Payload\nif err := json.Unmarshal(body, &p); err != nil {\n return err\n}` }, { id: 'js-async-await', title: 'Use async/await for readable JavaScript APIs', language: 'JavaScript', tags: ['async', 'fetch', 'promises'], excerpt: 'Wrap fetch calls in async functions, await the response, then handle errors explicitly.', summary: 'async/await keeps asynchronous JavaScript code close to synchronous control flow. Always check response.ok and catch failures near the calling boundary.', code: `async function loadUser(id) {\n const res = await fetch(\`/api/users/\${id}\`);\n if (!res.ok) throw new Error('Request failed');\n return await res.json();\n}` }, { id: 'python-list-comprehension', title: 'Filter and transform Python lists', language: 'Python', tags: ['lists', 'comprehension', 'clean code'], excerpt: 'List comprehensions are concise when the expression and predicate stay simple.', summary: 'Use a list comprehension when you can express mapping and filtering in one readable line. Prefer a normal loop when you need multiple steps or side effects.', code: `names = ['Ada', 'Linus', 'Grace']\nshort_names = [name.lower() for name in names if len(name) <= 5]` }, { id: 'sql-joins', title: 'Choose the right SQL join', language: 'SQL', tags: ['joins', 'select', 'relational'], excerpt: 'INNER JOIN returns matching rows; LEFT JOIN keeps all rows from the left table.', summary: 'Start with INNER JOIN when both records must exist. Use LEFT JOIN for optional related records, such as users without orders yet.', code: `SELECT users.name, orders.total\nFROM users\nLEFT JOIN orders ON orders.user_id = users.id\nWHERE users.active = 1;` }, { id: 'php-pdo-prepared', title: 'Run safe PHP PDO queries', language: 'PHP', tags: ['pdo', 'security', 'mysql'], excerpt: 'Prepared statements prevent SQL injection and keep user input out of raw SQL strings.', summary: 'Create a PDO statement, bind values, execute it, and fetch typed results. Never concatenate untrusted request input into SQL.', code: `$stmt = $pdo->prepare('SELECT * FROM posts WHERE slug = :slug');\n$stmt->execute(['slug' => $slug]);\n$post = $stmt->fetch(PDO::FETCH_ASSOC);` }, { id: 'ts-generics', title: 'Model reusable TypeScript functions with generics', language: 'TypeScript', tags: ['types', 'generics', 'safety'], excerpt: 'Generics preserve specific types while keeping helpers reusable.', summary: 'Use a generic type parameter when the function should work with many input types but return or store the same specific type information.', code: `function first(items: T[]): T | undefined {\n return items[0];\n}\n\nconst value = first(['docs', 'code']);` } ]; const els = {}; let activeResults = []; let selectedId = null; let toastInstance = null; function $(selector) { return document.querySelector(selector); } function escapeHtml(value) { return String(value).replace(/[&<>"']/g, (char) => ({'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[char])); } function scoreLesson(lesson, query, language) { const q = query.trim().toLowerCase(); const haystack = `${lesson.title} ${lesson.language} ${lesson.tags.join(' ')} ${lesson.excerpt} ${lesson.summary}`.toLowerCase(); let score = 0; if (language === 'all' || lesson.language === language) score += 25; if (language !== 'all' && lesson.language !== language) return 0; if (!q) return score; const terms = q.split(/\s+/).filter(Boolean); terms.forEach((term) => { if (lesson.title.toLowerCase().includes(term)) score += 18; if (lesson.tags.join(' ').toLowerCase().includes(term)) score += 12; if (haystack.includes(term)) score += 6; }); return score; } function searchLessons(query, language) { return lessons .map((lesson) => ({ ...lesson, score: scoreLesson(lesson, query, language) })) .filter((lesson) => lesson.score > 0) .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title)); } function renderResults(results) { activeResults = results; els.resultCount.textContent = results.length ? `${results.length} result${results.length === 1 ? '' : 's'} ranked by relevance.` : 'No matches yet. Try another language or broader topic.'; if (!results.length) { const hasQuery = els.query && els.query.value.trim().length >= 2; els.resultsList.innerHTML = hasQuery ? `

No results found

Try “JSON”, “async”, “join”, or choose All languages.

` : `

Start with a query

Search a topic or choose a suggested chip to preview the knowledge workflow.

`; renderEmptyDetail(); return; } els.resultsList.innerHTML = results.map((lesson, index) => ` `).join(''); } function renderEmptyDetail() { selectedId = null; els.detail.innerHTML = `

Choose a result

Open a result to see a concise explanation, tags, and copy-ready code examples.

`; } function renderDetail(id) { const lesson = lessons.find(item => item.id === id); if (!lesson) return renderEmptyDetail(); selectedId = id; els.detail.innerHTML = `
${escapeHtml(lesson.language)} ${lesson.tags.map(tag => `${escapeHtml(tag)}`).join('')}

${escapeHtml(lesson.title)}

${escapeHtml(lesson.summary)}

Code example

${escapeHtml(lesson.code)}
`; renderResults(activeResults); updateUrl({ result: id }); } function updateUrl(extra = {}) { const params = new URLSearchParams(window.location.search); const query = els.query.value.trim(); const language = els.language.value; if (query) params.set('q', query); else params.delete('q'); if (language && language !== 'all') params.set('language', language); else params.delete('language'); if (extra.result) params.set('result', extra.result); const next = `${window.location.pathname}${params.toString() ? `?${params}` : ''}#search`; history.replaceState({}, '', next); } function showToast(message) { els.toastMessage.textContent = message; if (window.bootstrap && toastInstance) toastInstance.show(); } function submitSearch(showNotification = true) { const query = els.query.value.trim(); if (query.length < 2) { els.form.classList.add('was-validated'); els.query.focus(); return; } els.form.classList.remove('was-validated'); selectedId = null; const results = searchLessons(query, els.language.value); renderResults(results); if (results[0]) renderDetail(results[0].id); updateUrl(); if (showNotification) showToast('Search complete. Results ranked locally for the MVP demo.'); } async function copyText(text, message) { try { await navigator.clipboard.writeText(text); showToast(message); } catch (error) { showToast('Copy failed. You can manually copy from the page.'); } } function initFromUrl() { const params = new URLSearchParams(window.location.search); const q = params.get('q'); const language = params.get('language'); const result = params.get('result'); if (q) els.query.value = q; if (language && [...els.language.options].some(option => option.value === language || option.textContent === language)) els.language.value = language; if (q && q.length >= 2) { const results = searchLessons(q, els.language.value); renderResults(results); renderDetail(result && lessons.some(item => item.id === result) ? result : (results[0]?.id || null)); } } function bindEvents() { els.form.addEventListener('submit', (event) => { event.preventDefault(); submitSearch(); }); document.addEventListener('click', (event) => { const resultButton = event.target.closest('[data-id]'); if (resultButton) { renderDetail(resultButton.dataset.id); document.getElementById('detail').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); return; } const chip = event.target.closest('.chip'); if (chip) { els.query.value = chip.dataset.query || ''; els.language.value = chip.dataset.language || 'all'; submitSearch(); return; } const languageCard = event.target.closest('.language-card'); if (languageCard) { els.language.value = languageCard.dataset.language; els.query.value = languageCard.dataset.language; submitSearch(); document.getElementById('search').scrollIntoView({ behavior: 'smooth' }); return; } const copyCode = event.target.closest('[data-copy-code]'); if (copyCode) { const lesson = lessons.find(item => item.id === copyCode.dataset.copyCode); if (lesson) copyText(lesson.code, 'Code example copied.'); return; } const shareResult = event.target.closest('[data-share-result]'); if (shareResult) { updateUrl({ result: shareResult.dataset.shareResult }); copyText(window.location.href, 'Shareable result link copied.'); } }); els.copySearchLink.addEventListener('click', () => { updateUrl(selectedId ? { result: selectedId } : {}); copyText(window.location.href, 'Shareable search link copied.'); }); } document.addEventListener('DOMContentLoaded', () => { els.form = $('#searchForm'); els.query = $('#queryInput'); els.language = $('#languageFilter'); els.resultsList = $('#resultsList'); els.resultCount = $('#resultCount'); els.detail = $('#detail'); els.copySearchLink = $('#copySearchLink'); els.toastMessage = $('#toastMessage'); const toastEl = $('#appToast'); if (window.bootstrap && toastEl) toastInstance = new bootstrap.Toast(toastEl, { delay: 2800 }); renderResults([]); bindEvents(); initFromUrl(); });