// ---- Configuration ---- // Single data source: the ArXivSignals public daily feed — the latest arXiv // announce day, ranked by signal score, refreshed as the pipeline publishes. const FEED_URL = "https://arxivsignals.io/daily.json"; // ---- Helpers ---- function $(id) { return document.getElementById(id); } async function fetchJSON(url) { try { const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } catch (error) { console.error('Fetch error:', error); console.error('Failed URL:', url); if (error.message.includes('Failed to fetch') || error.name === 'TypeError') { throw new Error(`CORS or network error - ${error.message}`); } throw error; } } function formatAuthors(authors) { if (!Array.isArray(authors) || authors.length === 0) return 'No authors'; if (authors.length === 1) return authors[0]; return `${authors[0]} et al. · ${authors.length} authors`; } // Feed tag slugs are kebab/snake case ("multi-agent-systems", "computer_vision"). function formatTag(tag) { if (!tag) return tag; return tag.replace(/[_-]/g, ' ').replace(/^\w/, c => c.toUpperCase()); } // ---- State Management ---- const state = { feed: null, // the whole /daily.json body papers: [], // feed.papers (already signal-ranked) searchTerm: '', selectedTags: new Set(), selectedCategories: new Set(), currentPage: 1, itemsPerPage: 99, filteredItems: [], theme: 'dark' }; // ---- Data Loading ---- async function loadFeed() { showStatus('Loading latest papers…'); try { const feed = await fetchJSON(FEED_URL); state.feed = feed; state.papers = Array.isArray(feed.papers) ? feed.papers : []; state.currentPage = 1; updateHeader(); filterItems(); renderCards(); updateTagButton(); updateCategoryButton(); const cats = new Set(); state.papers.forEach(p => { if (p.categories && p.categories[0]) cats.add(p.categories[0]); }); showStatus(`${state.filteredItems.length} papers across ${cats.size} categories · ${feed.date}`, 'success'); return feed; } catch (error) { console.error('Failed to load the daily feed:', error); showStatus('Failed to load the daily feed. Check the browser console for details.', 'error'); return null; } } function updateHeader() { const dateLabel = $('currentDate'); const countLabel = $('dayCount'); if (dateLabel) dateLabel.textContent = state.feed?.date || '—'; if (countLabel) { const n = state.papers.length; let updated = ''; if (state.feed?.catalog_updated_at) { const t = new Date(state.feed.catalog_updated_at.replace(' ', 'T')); if (!Number.isNaN(t.getTime())) { updated = ` · updated ${t.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`; } } countLabel.textContent = `${n} papers${updated}`; } } function showStatus(message, type = 'info') { const statusEl = $('status'); if (!statusEl) return; statusEl.textContent = message; statusEl.className = `status ${type}`; statusEl.style.display = 'block'; if (type !== 'error') { setTimeout(() => { statusEl.style.display = 'none'; }, 5000); } } // ---- Rendering ---- function renderCard(paper) { const el = document.createElement('article'); el.className = 'card'; // Thumbnail wrapper (for positioning action buttons) const thumbWrapper = document.createElement('div'); thumbWrapper.className = 'thumb-wrapper'; const thumb = document.createElement('img'); thumb.className = 'thumb'; thumb.src = '/placeholder.svg'; thumb.alt = 'thumbnail'; thumb.title = 'Open arXiv abstract'; if (paper.thumbnail_url) { thumb.dataset.src = paper.thumbnail_url; thumb.onerror = () => { thumb.src = '/placeholder.svg'; }; } thumb.addEventListener('click', () => { if (paper.arxiv_url) window.open(paper.arxiv_url, '_blank', 'noopener'); }); thumbWrapper.appendChild(thumb); // "AI summary" chip on the thumbnail, like the portal's explore cards — // links straight to the summary on the public site (the thumb itself opens arXiv) if (paper.has_summary && paper.portal_url) { const chip = document.createElement('a'); chip.className = 'summary-chip'; chip.href = paper.portal_url; chip.target = '_blank'; chip.rel = 'noopener'; chip.title = 'Read the AI summary on ArXivSignals'; chip.textContent = '+ AI summary'; thumbWrapper.appendChild(chip); } // Action buttons (on thumbnail) const actions = document.createElement('div'); actions.className = 'card-actions'; const icons = { id: '', link: '', copy: '' }; if (paper.id) { const copyIdBtn = createActionButton(icons.id, 'Copy arXiv ID: ' + paper.id, async () => { await copyToClipboard(paper.id, copyIdBtn, icons.id); }); actions.appendChild(copyIdBtn); } if (paper.arxiv_url) { const copyLinkBtn = createActionButton(icons.link, 'Copy arXiv link', async () => { await copyToClipboard(paper.arxiv_url, copyLinkBtn, icons.link); }); actions.appendChild(copyLinkBtn); } const summaryText = [paper.one_liner, paper.abstract_snippet].filter(Boolean).join('\n\n'); if (summaryText) { const copySummaryBtn = createActionButton(icons.copy, 'Copy summary', async () => { await copyToClipboard(summaryText, copySummaryBtn, icons.copy); }); actions.appendChild(copySummaryBtn); } thumbWrapper.appendChild(actions); el.appendChild(thumbWrapper); const body = document.createElement('div'); body.className = 'body'; // Meta header row: mono arXiv id · contribution type · signal score const metadata = document.createElement('div'); metadata.className = 'metadata'; const idEl = document.createElement('span'); idEl.className = 'paper-id'; idEl.textContent = paper.id || ''; metadata.appendChild(idEl); const spacer = document.createElement('span'); spacer.className = 'spacer'; metadata.appendChild(spacer); if (paper.contribution_type) { const contribEl = document.createElement('span'); contribEl.className = 'contrib-pill'; contribEl.textContent = paper.contribution_type; metadata.appendChild(contribEl); } if (typeof paper.signal_score === 'number') { const signalEl = document.createElement('span'); signalEl.className = 'signal-badge'; signalEl.title = 'ArXivSignals signal score (0-100)'; signalEl.textContent = `sig ${paper.signal_score}`; metadata.appendChild(signalEl); } body.appendChild(metadata); // Title with hyperlink to arXiv const titleLink = document.createElement('a'); titleLink.href = paper.arxiv_url; titleLink.target = '_blank'; titleLink.rel = 'noopener'; const title = document.createElement('h3'); title.className = 'title'; title.textContent = paper.title || 'No title'; titleLink.appendChild(title); body.appendChild(titleLink); // Authors (immediately below title) const authors = document.createElement('div'); authors.className = 'authors'; authors.textContent = formatAuthors(paper.authors); body.appendChild(authors); // One-liner takeaway + abstract snippet if (paper.one_liner) { const oneLiner = document.createElement('p'); oneLiner.className = 'summary one-liner'; oneLiner.textContent = paper.one_liner; body.appendChild(oneLiner); } if (paper.abstract_snippet) { const snippet = document.createElement('p'); snippet.className = 'summary snippet'; snippet.textContent = paper.abstract_snippet; body.appendChild(snippet); } // Chips: primary category first (mono, blue-tinted), then topic tags const catStr = (paper.categories && paper.categories[0]) || null; if (catStr || (paper.tags && paper.tags.length > 0)) { const tags = document.createElement('div'); tags.className = 'tags'; if (catStr) { const categoryEl = document.createElement('span'); categoryEl.className = 'category'; categoryEl.textContent = catStr; tags.appendChild(categoryEl); } (paper.tags || []).forEach(tag => { const tagEl = document.createElement('span'); tagEl.className = 'tag'; tagEl.textContent = formatTag(tag); tags.appendChild(tagEl); }); body.appendChild(tags); } // Footer: mono microlinks — arXiv abs + the ArXivSignals paper page const footer = document.createElement('div'); footer.className = 'card-footer'; if (paper.arxiv_url) { const arxivLink = document.createElement('a'); arxivLink.href = paper.arxiv_url; arxivLink.target = '_blank'; arxivLink.rel = 'noopener'; arxivLink.textContent = 'arXiv ↗'; footer.appendChild(arxivLink); } if (paper.portal_url) { const signalsLink = document.createElement('a'); signalsLink.className = 'signals-link'; signalsLink.href = paper.portal_url; signalsLink.target = '_blank'; signalsLink.rel = 'noopener'; signalsLink.textContent = paper.has_summary ? 'AI summary →' : 'Signals →'; footer.appendChild(signalsLink); } body.appendChild(footer); el.appendChild(body); return el; } // Helper function to create action buttons (icon-only) function createActionButton(iconSvg, title, onClick) { const btn = document.createElement('button'); btn.className = 'action-btn'; btn.innerHTML = iconSvg; btn.title = title; btn.addEventListener('click', onClick); return btn; } // SVG icons for feedback const feedbackIcons = { check: '', error: '' }; // Helper function to copy text to clipboard async function copyToClipboard(text, btn, originalIcon) { try { await navigator.clipboard.writeText(text); showCopySuccess(btn, originalIcon); } catch (err) { console.error('Failed to copy:', err); showCopyError(btn, originalIcon); } } function showCopySuccess(btn, originalIcon) { btn.classList.add('success'); btn.innerHTML = feedbackIcons.check; setTimeout(() => { btn.classList.remove('success'); btn.innerHTML = originalIcon; }, 1500); } function showCopyError(btn, originalIcon) { btn.innerHTML = feedbackIcons.error; setTimeout(() => { btn.innerHTML = originalIcon; }, 1500); } function renderCards() { const cardsEl = $('cards'); if (!cardsEl) return; cardsEl.innerHTML = ''; if (!state.filteredItems || state.filteredItems.length === 0) { cardsEl.innerHTML = '
No papers found
'; return; } const startIndex = (state.currentPage - 1) * state.itemsPerPage; const endIndex = startIndex + state.itemsPerPage; const pageItems = state.filteredItems.slice(startIndex, endIndex); pageItems.forEach((paper) => { cardsEl.appendChild(renderCard(paper)); }); setupLazyLoading(); updatePagination(); } function setupLazyLoading() { const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.add('loaded'); observer.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } function updatePagination() { const pageInfoEl = $('pageInfo'); const prevBtn = $('prevPage'); const nextBtn = $('nextPage'); if (!pageInfoEl || !prevBtn || !nextBtn) return; const totalPages = Math.ceil(state.filteredItems.length / state.itemsPerPage); const currentPage = state.currentPage; pageInfoEl.textContent = `Page ${currentPage} of ${totalPages}`; prevBtn.disabled = currentPage <= 1; nextBtn.disabled = currentPage >= totalPages; } function filterItems() { let items = state.papers; // Apply search filter (title, one-liner, snippet, authors) if (state.searchTerm) { const term = state.searchTerm.toLowerCase(); items = items.filter((p) => { const title = (p.title || '').toLowerCase(); const oneLiner = (p.one_liner || '').toLowerCase(); const snippet = (p.abstract_snippet || '').toLowerCase(); const authors = (p.authors || []).join(' ').toLowerCase(); return title.includes(term) || oneLiner.includes(term) || snippet.includes(term) || authors.includes(term); }); } // Apply tag filter if (state.selectedTags.size > 0) { items = items.filter((p) => { if (!p.tags || !Array.isArray(p.tags)) return false; return p.tags.some(tag => state.selectedTags.has(tag)); }); } // Apply category filter (primary category = categories[0]) if (state.selectedCategories.size > 0) { items = items.filter((p) => { const primary = (p.categories && p.categories[0]) || 'Unknown category'; return state.selectedCategories.has(primary); }); } state.filteredItems = items; } // ---- Event Handlers ---- function setupEventListeners() { // Search const searchInput = $('searchInput'); if (searchInput) { searchInput.addEventListener('input', (e) => { state.searchTerm = e.target.value.trim(); state.currentPage = 1; filterItems(); renderCards(); }); } // Keyboard shortcuts (only when not in input fields) document.addEventListener('keydown', (e) => { if (document.activeElement.matches('input, textarea, [contenteditable]')) { return; } const categoryPanel = $('categoryPanel'); const tagPanel = $('tagPanel'); const hasOpenModal = (categoryPanel && !categoryPanel.classList.contains('hidden')) || (tagPanel && !tagPanel.classList.contains('hidden')); if (hasOpenModal && e.key !== 'Escape') { return; } // Focus search with Ctrl+K or Cmd+K if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); if (searchInput) { searchInput.focus(); searchInput.select(); } } // Reload with R key if (e.key === 'r' && !e.ctrlKey && !e.metaKey) { e.preventDefault(); loadFeed(); } // Close modals with Escape if (e.key === 'Escape') { if (categoryPanel && !categoryPanel.classList.contains('hidden')) { categoryPanel.classList.add('hidden'); } else if (tagPanel && !tagPanel.classList.contains('hidden')) { tagPanel.classList.add('hidden'); } } }); // Reload button const reloadBtn = $('reloadBtn'); if (reloadBtn) { reloadBtn.addEventListener('click', () => loadFeed()); } // Pagination const prevPageBtn = $('prevPage'); const nextPageBtn = $('nextPage'); if (prevPageBtn) { prevPageBtn.addEventListener('click', () => { if (state.currentPage > 1) { state.currentPage--; renderCards(); } }); } if (nextPageBtn) { nextPageBtn.addEventListener('click', () => { const totalPages = Math.ceil(state.filteredItems.length / state.itemsPerPage); if (state.currentPage < totalPages) { state.currentPage++; renderCards(); } }); } // Tag filtering const tagFilterBtn = $('tagFilterBtn'); const tagPanel = $('tagPanel'); const tagSearch = $('tagSearch'); const closeTagPanel = $('closeTagPanel'); const clearTags = $('clearTags'); const applyTags = $('applyTags'); if (tagFilterBtn && tagPanel) { tagFilterBtn.addEventListener('click', () => { if (!tagPanel.classList.contains('hidden')) { tagPanel.classList.add('hidden'); } else { populateTagList(); tagPanel.classList.remove('hidden'); } }); } if (closeTagPanel) { closeTagPanel.addEventListener('click', () => { tagPanel.classList.add('hidden'); }); } if (tagSearch) { tagSearch.addEventListener('input', (e) => { filterTagList(e.target.value); }); } if (clearTags) { clearTags.addEventListener('click', () => { state.selectedTags.clear(); updateTagSelectionUI(); }); } if (applyTags) { applyTags.addEventListener('click', () => { tagPanel.classList.add('hidden'); state.currentPage = 1; filterItems(); renderCards(); }); } // Category filtering const categoryFilterBtn = $('categoryFilterBtn'); const categoryPanel = $('categoryPanel'); const categorySearch = $('categorySearch'); const closeCategoryPanel = $('closeCategoryPanel'); const clearCategories = $('clearCategories'); const applyCategories = $('applyCategories'); if (categoryFilterBtn && categoryPanel) { categoryFilterBtn.addEventListener('click', () => { if (!categoryPanel.classList.contains('hidden')) { categoryPanel.classList.add('hidden'); } else { populateCategoryList(); categoryPanel.classList.remove('hidden'); } }); } if (closeCategoryPanel) { closeCategoryPanel.addEventListener('click', () => { categoryPanel.classList.add('hidden'); }); } if (categorySearch) { categorySearch.addEventListener('input', (e) => { filterCategoryList(e.target.value); }); } if (clearCategories) { clearCategories.addEventListener('click', () => { state.selectedCategories.clear(); updateCategorySelectionUI(); }); } if (applyCategories) { applyCategories.addEventListener('click', () => { categoryPanel.classList.add('hidden'); state.currentPage = 1; filterItems(); renderCards(); }); } // Theme toggle — dark by default, matching arxivsignals.io const themeBtn = $('themeBtn'); if (themeBtn) { themeBtn.addEventListener('click', toggleTheme); const savedTheme = localStorage.getItem('arxiv-theme') || 'dark'; setTheme(savedTheme, { silent: true }); } } // ---- Theme Management ---- function toggleTheme() { const newTheme = state.theme === 'light' ? 'dark' : 'light'; setTheme(newTheme); } function setTheme(theme, { silent = false } = {}) { state.theme = theme; document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('arxiv-theme', theme); const themeBtn = $('themeBtn'); if (themeBtn) { themeBtn.classList.toggle('dark', theme === 'dark'); themeBtn.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'; } if (!silent) showStatus(`Switched to ${theme} mode`, 'success'); } // ---- Tag Management ---- function populateTagList() { const tagList = $('tagList'); if (!tagList) return; const tagCounts = new Map(); state.papers.forEach((p) => { if (p.tags && Array.isArray(p.tags)) { p.tags.forEach(tag => { tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1); }); } }); const sortedTags = Array.from(tagCounts.entries()).sort((a, b) => { const tagA = formatTag(a[0]).toLowerCase(); const tagB = formatTag(b[0]).toLowerCase(); return tagA.localeCompare(tagB); }); tagList.innerHTML = ''; sortedTags.forEach(([tag, count]) => { const tagEl = document.createElement('div'); tagEl.className = 'tag-item'; tagEl.innerHTML = ` `; const checkbox = tagEl.querySelector('input'); checkbox.addEventListener('change', (e) => { if (e.target.checked) { state.selectedTags.add(tag); } else { state.selectedTags.delete(tag); } updateTagButton(); }); tagList.appendChild(tagEl); }); } function filterTagList(searchTerm) { const tagItems = document.querySelectorAll('.tag-item'); const term = searchTerm.toLowerCase(); tagItems.forEach(item => { const tagName = item.querySelector('.tag-name').textContent.toLowerCase(); const checkbox = item.querySelector('input'); const originalTag = checkbox ? checkbox.value.toLowerCase() : ''; if (tagName.includes(term) || originalTag.includes(term)) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); } function updateTagSelectionUI() { document.querySelectorAll('.tag-item input').forEach(checkbox => { checkbox.checked = state.selectedTags.has(checkbox.value); }); updateTagButton(); } function updateTagButton() { const tagFilterBtn = $('tagFilterBtn'); if (!tagFilterBtn) return; if (state.selectedTags.size > 0) { tagFilterBtn.textContent = `Tags (${state.selectedTags.size})`; tagFilterBtn.classList.add('active'); } else { tagFilterBtn.textContent = 'Tags'; tagFilterBtn.classList.remove('active'); } } // ---- Category Management ---- function populateCategoryList() { const categoryList = $('categoryList'); if (!categoryList) return; const categoryCounts = new Map(); state.papers.forEach((p) => { const category = (p.categories && p.categories[0]) || 'Unknown category'; categoryCounts.set(category, (categoryCounts.get(category) || 0) + 1); }); const sortedCategories = Array.from(categoryCounts.entries()).sort((a, b) => b[1] - a[1]); categoryList.innerHTML = ''; sortedCategories.forEach(([category, count]) => { const categoryEl = document.createElement('div'); categoryEl.className = 'tag-item'; categoryEl.innerHTML = ` `; const checkbox = categoryEl.querySelector('input'); checkbox.addEventListener('change', (e) => { if (e.target.checked) { state.selectedCategories.add(category); } else { state.selectedCategories.delete(category); } updateCategoryButton(); }); categoryList.appendChild(categoryEl); }); } function filterCategoryList(searchTerm) { const categoryItems = document.querySelectorAll('#categoryList .tag-item'); const term = searchTerm.toLowerCase(); categoryItems.forEach(item => { const categoryName = item.querySelector('.tag-name').textContent.toLowerCase(); if (categoryName.includes(term)) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); } function updateCategorySelectionUI() { document.querySelectorAll('#categoryList .tag-item input').forEach(checkbox => { checkbox.checked = state.selectedCategories.has(checkbox.value); }); updateCategoryButton(); } function updateCategoryButton() { const categoryFilterBtn = $('categoryFilterBtn'); if (!categoryFilterBtn) return; if (state.selectedCategories.size > 0) { categoryFilterBtn.textContent = `Categories (${state.selectedCategories.size})`; categoryFilterBtn.classList.add('active'); } else { categoryFilterBtn.textContent = 'Categories'; categoryFilterBtn.classList.remove('active'); } } // ---- Initialization ---- async function init() { setupEventListeners(); await loadFeed(); } // Start the application document.addEventListener('DOMContentLoaded', init);