/* ═══════════════════════════════════════════════════════ RECON // Curated Lists Controller (grouped by source) ═══════════════════════════════════════════════════════ */ (function () { 'use strict'; const API = '/api/awesomelist'; const root = document.getElementById('awesomelistRoot'); if (!root) return; let state = { view: 'index', sectors: [], searchTimeout: null }; // ─── Utilities ─────────────────────────────────────── function esc(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; } function md(s) { if (!s) return ''; let h = esc(s); h = h.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); h = h.replace(/\*\*([^*]+)\*\*/g, '$1'); h = h.replace(/\*([^*]+)\*/g, '$1'); h = h.replace(/`([^`]+)`/g, '$1'); return h; } function fmt(n) { return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } // ─── API Fetch ─────────────────────────────────────── async function api(endpoint) { const res = await fetch(API + endpoint); if (!res.ok) throw new Error(`API ${res.status}`); return res.json(); } // ─── Render: Index View ────────────────────────────── function renderIndex(data) { state.view = 'index'; state.sectors = data.sectors || []; let html = ''; // Stats bar html += `
DATABASE● ONLINE
TOTAL ASSETS${fmt(data.total_entries)}
SECTORS${data.total_sectors}
STATUS● DECLASSIFIED
`; // Search html += `
`; // Search results container html += `
`; // Sector grid html += `
`; for (const sec of state.sectors) { if (sec.entry_count === 0) continue; html += `
${sec.icon}
${esc(sec.code)}
${esc(sec.name)}
${fmt(sec.entry_count)} assets ${sec.subcategory_count} lists
● ACCESSIBLE
`; } html += `
`; root.innerHTML = html; bindIndexEvents(); } // ─── Render: Sector Detail ─────────────────────────── function renderSector(sec) { state.view = 'detail'; state.currentSector = sec; let html = ''; // Back button html += `
◄ BACK TO INDEX
`; // Detail header html += `
${esc(sec.code)}
${esc(sec.icon)} ${esc(sec.name)}
${fmt(sec.total_entries)} ASSETS ${sec.list_count} LISTS
`; // Search within sector html += `
`; // List cards (one per source list) html += `
`; for (let i = 0; i < sec.lists.length; i++) { const lst = sec.lists[i]; if (lst.total_entries === 0) continue; html += `
${esc(lst.name)}
${lst.total_entries} items ${lst.subcategories.length} sections
`; } html += `
`; // Shared detail panel html += ``; root.innerHTML = html; bindDetailEvents(sec); } // ─── Render: Single Entry ──────────────────────────── function renderEntry(entry) { const starClass = entry.starred ? ' starred' : ''; const starIcon = entry.starred ? '⭐' : '·'; let html = `
`; html += `${starIcon}`; html += `
`; if (entry.url) { html += `
${esc(entry.name || entry.url)}
`; } else if (entry.name) { html += `
${md(entry.name)}
`; } if (entry.description) { html += `
${md(entry.description)}
`; } if (entry.extra_links && entry.extra_links.length > 0) { html += `
`; for (const link of entry.extra_links) { html += `${esc(link.name)}`; } html += `
`; } html += `
`; return html; } // ─── Render: Search Results ────────────────────────── function renderSearchResults(data) { const container = document.getElementById('crtSearchResults'); const countEl = document.getElementById('crtSearchCount'); const grid = document.getElementById('crtGrid'); if (!data.results || data.results.length === 0) { container.innerHTML = data.query ? '
NO MATCHING ASSETS FOUND
' : ''; countEl.textContent = data.query ? '0 RESULTS' : ''; if (grid) grid.style.display = data.query ? 'none' : ''; return; } countEl.textContent = `${data.results.length} RESULTS`; if (grid) grid.style.display = 'none'; let html = ''; for (const r of data.results) { const starIcon = r.starred ? '⭐ ' : ''; html += `
`; html += `
${esc(r.sector_code)} // ${esc(r.sector_name)} / ${esc(r.subcategory)}
`; html += `
${starIcon}`; if (r.url) { html += `${esc(r.name || r.url)}`; } else { html += md(r.name); } html += `
`; if (r.description) { html += `
${md(r.description)}
`; } html += `
`; } container.innerHTML = html; } // ─── Event Bindings: Index ─────────────────────────── function bindIndexEvents() { document.querySelectorAll('.crt-card').forEach(card => { card.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); loadSector(card.dataset.slug); }); }); const searchInput = document.getElementById('crtSearch'); if (searchInput) { searchInput.addEventListener('input', () => { clearTimeout(state.searchTimeout); const q = searchInput.value.trim(); if (q.length < 2) { renderSearchResults({ query: '', results: [] }); return; } state.searchTimeout = setTimeout(async () => { try { const data = await api(`/search?q=${encodeURIComponent(q)}&limit=100`); renderSearchResults(data); } catch (e) { console.warn('Search failed:', e); } }, 300); }); } } // ─── Event Bindings: Detail ────────────────────────── function bindDetailEvents(sec) { // Back button document.getElementById('crtBack').addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); loadIndex(); }); // List card clicks — show all subcategories and entries for that source list document.querySelectorAll('.crt-sub-card').forEach(card => { card.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const idx = parseInt(card.dataset.listIdx); const lst = sec.lists[idx]; const panel = document.getElementById('crtSubDetail'); const headerEl = document.getElementById('crtSubDetailHeader'); const notesEl = document.getElementById('crtSubDetailNotes'); const entriesEl = document.getElementById('crtSubDetailEntries'); const wasActive = card.classList.contains('active'); document.querySelectorAll('.crt-sub-card').forEach(c => c.classList.remove('active')); if (wasActive) { panel.style.display = 'none'; return; } card.classList.add('active'); // Header headerEl.innerHTML = ` ${esc(lst.name)} ${lst.total_entries} items across ${lst.subcategories.length} sections`; notesEl.innerHTML = ''; notesEl.style.display = 'none'; // Build entries grouped by subcategory with collapsible headings let eh = ''; for (let si = 0; si < lst.subcategories.length; si++) { const sub = lst.subcategories[si]; if (sub.entries.length === 0) continue; // Strip the source prefix from subcategory name if it starts with list name let subName = sub.name; const prefix = lst.name + ' — '; if (subName.startsWith(prefix)) { subName = subName.slice(prefix.length); } if (subName === lst.name) subName = 'General'; eh += `
`; eh += `
${esc(subName)} ${sub.entries.length}
`; eh += ``; eh += `
`; } entriesEl.innerHTML = eh; // Bind subcategory toggle clicks entriesEl.querySelectorAll('.crt-subcat-heading').forEach(heading => { heading.addEventListener('click', (ev) => { ev.preventDefault(); ev.stopPropagation(); const toggleIdx = heading.dataset.subcatToggle; const body = entriesEl.querySelector(`[data-subcat-body="${toggleIdx}"]`); const arrow = heading.querySelector('.crt-subcat-arrow'); if (body.style.display === 'none') { body.style.display = ''; if (arrow) arrow.textContent = '▾'; heading.classList.add('expanded'); } else { body.style.display = 'none'; if (arrow) arrow.textContent = '▸'; heading.classList.remove('expanded'); } }); }); panel.style.display = ''; panel.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }); // Local filter on list cards const catSearch = document.getElementById('crtCatSearch'); if (catSearch) { catSearch.addEventListener('input', () => { const q = catSearch.value.trim().toLowerCase(); document.querySelectorAll('.crt-sub-card').forEach(card => { if (!q) { card.style.display = ''; return; } const text = card.textContent.toLowerCase(); card.style.display = text.includes(q) ? '' : 'none'; }); }); } } // ─── Load Functions ────────────────────────────────── async function loadIndex() { root.innerHTML = '
ACCESSING RECON DATABASE...
'; try { const data = await api(''); renderIndex(data); history.pushState({ view: 'index' }, '', '/depot/recon'); } catch (e) { root.innerHTML = `
DATABASE ACCESS DENIED // ${esc(e.message)}
`; } } async function loadSector(slug) { root.innerHTML = '
DECRYPTING SECTOR DATA...
'; try { const data = await api(`/${slug}`); renderSector(data); history.pushState({ view: 'detail', slug: slug }, '', `/depot/recon?sector=${slug}`); window.scrollTo({ top: 0, behavior: 'smooth' }); } catch (e) { root.innerHTML = `
SECTOR NOT FOUND // ${esc(e.message)}
`; } } // ─── Handle Back/Forward ───────────────────────────── window.addEventListener('popstate', (e) => { if (e.state && e.state.view === 'detail' && e.state.slug) { loadSector(e.state.slug); } else { loadIndex(); } }); // ─── Init ──────────────────────────────────────────── function init() { const params = new URLSearchParams(window.location.search); const sector = params.get('sector'); if (sector) { loadSector(sector); } else { loadIndex(); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();