/* ═══════════════════════════════════════════════════════
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';
let html = '';
// Back button
html += `◄ BACK TO INDEX
`;
// Detail header
html += ``;
// 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 += `
`;
} 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 += ``;
}
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 += `
`;
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', () => {
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', () => {
loadIndex();
});
// List card clicks — show all subcategories and entries for that source list
document.querySelectorAll('.crt-sub-card').forEach(card => {
card.addEventListener('click', () => {
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
let eh = '';
for (const sub of lst.subcategories) {
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}
`;
for (const entry of sub.entries) eh += renderEntry(entry);
eh += `
`;
}
entriesEl.innerHTML = eh;
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' }, '', '/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 }, '', `/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();
}
})();