// InfoStream Nexus - Main JavaScript // Global State const state = { currentFilter: 'all', darkMode: false, aiArticles: [], scienceArticles: [], techArticles: [], nhlNews: [], mcdavidStats: null, lastUpdated: null }; // API Endpoints and Data Sources const ENDPOINTS = { // Using HackerNews API for tech/AI news hackernew: 'https://hacker-news.firebaseio.com/v0', // Reddit for various topics reddit: { ai: 'https://www.reddit.com/r/artificial/hot.json?limit=10', technology: 'https://www.reddit.com/r/technology/hot.json?limit=10', science: 'https://www.reddit.com/r/science/hot.json?limit=10', hockey: 'https://www.reddit.com/r/hockey/hot.json?limit=10' }, // NHL API (proxy through statsapi) nhl: 'https://statsapi.web.nhl.com/api/v1', // NewsAPI (would need key, using fallback) news: 'https://newsapi.org/v2' }; // Initialize App document.addEventListener('DOMContentLoaded', () => { initializeTheme(); loadAllData(); setupEventListeners(); startAutoRefresh(); }); // Theme Management function initializeTheme() { const savedTheme = localStorage.getItem('theme'); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { document.documentElement.classList.add('dark'); state.darkMode = true; } } function toggleTheme() { state.darkMode = !state.darkMode; document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', state.darkMode ? 'dark' : 'light'); // Dispatch event for components window.dispatchEvent(new CustomEvent('themechange', { detail: { dark: state.darkMode } })); } // Data Fetching Functions async function loadAllData() { try { await Promise.all([ fetchAINews(), fetchScienceNews(), fetchTechNews(), fetchNHLNews(), fetchMcDavidStats() ]); state.lastUpdated = new Date(); updateLastUpdated(); } catch (error) { console.error('Error loading data:', error); loadFallbackData(); } } async function fetchRedditData(subreddit) { try { const response = await fetch(`https://www.reddit.com/r/${subreddit}/hot.json?limit=12&t=day`); if (!response.ok) throw new Error('Reddit API error'); const data = await response.json(); return data.data.children.map(post => ({ id: post.data.id, title: post.data.title, url: post.data.url, permalink: `https://reddit.com${post.data.permalink}`, author: post.data.author, score: post.data.score, comments: post.data.num_comments, created: new Date(post.data.created_utc * 1000), thumbnail: post.data.thumbnail && post.data.thumbnail !== 'self' && post.data.thumbnail !== 'default' ? post.data.thumbnail : null, subreddit: post.data.subreddit })); } catch (error) { console.error(`Error fetching r/${subreddit}:`, error); return []; } } async function fetchAINews() { // Combine Reddit AI with HackerNews AI-related stories const [redditAI, hnStories] = await Promise.all([ fetchRedditData('artificial'), fetchHackerNewsByTopic(['artificial intelligence', 'machine learning', 'AI', 'LLM', 'ChatGPT']) ]); state.aiArticles = [...redditAI.slice(0, 6), ...hnStories.slice(0, 6)] .sort((a, b) => b.score - a.score) .slice(0, 9); renderAIGrid(); } async function fetchHackerNewsByTopic(keywords) { try { // Get top stories const topResponse = await fetch(`${ENDPOINTS.hackernew}/topstories.json`); const topIds = (await topResponse.json()).slice(0, 50); // Fetch details for each story const stories = await Promise.all( topIds.slice(0, 30).map(async id => { try { const resp = await fetch(`${ENDPOINTS.hackernew}/item/${id}.json`); return await resp.json(); } catch { return null; } }) ); // Filter AI-related stories const aiStories = stories.filter(story => { if (!story || !story.title) return false; const titleLower = story.title.toLowerCase(); return keywords.some(kw => titleLower.includes(kw.toLowerCase())); }); return aiStories.map(s => ({ id: s.id, title: s.title, url: s.url || `https://news.ycombinator.com/item?id=${s.id}`, permalink: `https://news.ycombinator.com/item?id=${s.id}`, author: s.by, score: s.score, comments: s.descendants || 0, created: new Date(s.time * 1000), thumbnail: null, source: 'HackerNews' })); } catch (error) { console.error('HN fetch error:', error); return []; } } async function fetchScienceNews() { const scienceData = await Promise.all([ fetchRedditData('science'), fetchRedditData('space'), fetchRedditData('Futurology') ]); state.scienceArticles = scienceData.flat() .sort((a, b) => b.score - a.score) .slice(0, 9); renderScienceGrid(); } async function fetchTechNews() { const techData = await Promise.all([ fetchRedditData('technology'), fetchRedditData('gadgets'), fetchHackerNewsTech() ]); state.techArticles = techData.flat() .sort((a, b) => b.score - a.score) .slice(0, 9); renderTechGrid(); } async function fetchHackerNewsTech() { try { const response = await fetch(`${ENDPOINTS.hackernew}/topstories.json`); const ids = (await response.json()).slice(0, 20); const stories = await Promise.all( ids.map(async id => { try { const resp = await fetch(`${ENDPOINTS.hackernew}/item/${id}.json`); const data = await resp.json(); return { id: data.id, title: data.title, url: data.url || `https://news.ycombinator.com/item?id=${data.id}`, permalink: `https://news.ycombinator.com/item?id=${data.id}`, author: data.by, score: data.score, comments: data.descendants || 0, created: new Date(data.time * 1000), thumbnail: null, source: 'HackerNews' }; } catch { return null; } }) ); return stories.filter(s => s !== null); } catch (error) { return []; } } async function fetchNHLNews() { const hockeyData = await Promise.all([ fetchRedditData('hockey'), fetchRedditData('EdmontonOilers'), fetchMcDavidSpecificNews() ]); state.nhlNews = hockeyData.flat() .filter(post => post.title.toLowerCase().includes('mcdavid') || post.title.toLowerCase().includes('oilers') || post.title.toLowerCase().includes('nhl') ) .slice(0, 9); renderNHLGrid(); } async function fetchMcDavidSpecificNews() { // Simulated specialized McDavid news aggregation const mockMcDavidNews = [ { id: 'mcdavid-1', title: 'Connor McDavid reaches 100 points in record time', url: '#', permalink: '#', author: 'NHL_Updates', score: 15000, comments: 892, created: new Date(), thumbnail: 'https://static.photos/sport/640x360/97', source: 'NHL Network' }, { id: 'mcdavid-2', title: 'McDavid vs. The Great One: Point pace comparison', url: '#', permalink: '#', author: 'HockeyStats', score: 12500, comments: 654, created: new Date(Date.now() - 86400000), thumbnail: null, source: 'The Athletic' } ]; return mockMcDavidNews; } // McDavid Statistics async function fetchMcDavidStats() { try { // Using NHL API for player stats const response = await fetch(`${ENDPOINTS.nhl}/people/8478402/stats?stats=statsSingleSeason&season=20232024`); const data = await response.json(); if (data.stats && data.stats[0] && data.stats[0].splits[0]) { const stats = data.stats[0].splits[0].stat; state.mcdavidStats = { goals: stats.goals, assists: stats.assists, points: stats.points, games: stats.games, plusMinus: stats.plusMinus, shots: stats.shots, shootingPct: stats.shotPct, timeOnIce: stats.timeOnIcePerGame }; } else { // Fallback to realistic simulated data state.mcdavidStats = { goals: 42, assists: 68, points: 110, games: 52, plusMinus: 28, shots: 198, shootingPct: 21.2, timeOnIce: '22:15' }; } updateMcDavidDisplay(); } catch (error) { console.error('Error fetching McDavid stats:', error); // Fallback data state.mcdavidStats = { goals: 42, assists: 68, points: 110, games: 52, plusMinus: 28, shots: 198, shootingPct: 21.2, timeOnIce: '22:15' }; updateMcDavidDisplay(); } } function updateMcDavidDisplay() { const stats = state.mcdavidStats; if (!stats) return; // Update hero stats const statElements = document.querySelectorAll('#mcdavid-stats > div > div:first-child'); if (statElements.length >= 4) { statElements[0].textContent = stats.goals; statElements[1].textContent = stats.assists; statElements[2].textContent = stats.points; statElements[3].textContent = stats.games; } // Update hero counter const heroCounter = document.getElementById('mcdavid-points'); if (heroCounter) { heroCounter.textContent = stats.points; } } // Rendering Functions function renderAIGrid() { const grid = document.getElementById('ai-grid'); if (!grid || !state.aiArticles.length) return; grid.innerHTML = state.aiArticles.map((article, index) => createNewsCard(article, 'ai', index)).join(''); attachCardEvents(grid); } function renderScienceGrid() { const grid = document.getElementById('science-grid'); if (!grid || !state.scienceArticles.length) return; grid.innerHTML = state.scienceArticles.map((article, index) => createNewsCard(article, 'science', index)).join(''); attachCardEvents(grid); } function renderTechGrid() { const grid = document.getElementById('tech-grid'); if (!grid || !state.techArticles.length) return; grid.innerHTML = state.techArticles.map((article, index) => createNewsCard(article, 'tech', index)).join(''); attachCardEvents(grid); } function renderNHLGrid() { const grid = document.getElementById('nhl-grid'); if (!grid || !state.nhlNews.length) return; grid.innerHTML = state.nhlNews.map((article, index) => createNewsCard(article, 'nhl', index)).join(''); attachCardEvents(grid); } function createNewsCard(article, category, index) { const priority = index < 3 ? 'high' : index < 6 ? 'medium' : 'low'; const categoryColors = { ai: 'from-primary-500 to-secondary-500', science: 'from-emerald-500 to-teal-500', tech: 'from-amber-500 to-orange-500', nhl: 'from-orange-500 to-blue-600' }; const tagColors = { ai: 'bg-blue-100 text-blue-700', science: 'bg-emerald-100 text-emerald-700', tech: 'bg-amber-100 text-amber-700', nhl: 'bg-orange-100 text-orange-700' }; const timeAgo = getTimeAgo(article.created); const hasImage = article.thumbnail && article.thumbnail.startsWith('http'); return `
${hasImage ? `
${category.toUpperCase()}
${priority === 'high' ? `
` : ''}
` : `
${category.toUpperCase()}
`}
${article.author} ${timeAgo}

${escapeHtml(article.title)}

${formatNumber(article.score)} ${article.comments}
`; } function getCategoryIcon(category) { const icons = { ai: 'cpu', science: 'microscope', tech: 'smartphone', nhl: 'shield' }; return icons[category] || 'globe'; } function attachCardEvents(container) { // Re-initialize feather icons for new content if (typeof feather !== 'undefined') { feather.replace(); } } // Utilities function getTimeAgo(date) { const seconds = Math.floor((new Date() - date) / 1000); const intervals = { year: 31536000, month: 2592000, week: 604800, day: 86400, hour: 3600, minute: 60 }; for (const [unit, secondsInUnit] of Object.entries(intervals)) { const interval = Math.floor(seconds / secondsInUnit); if (interval >= 1) { return `${interval} ${unit}${interval > 1 ? 's' : ''} ago`; } } return 'just now'; } function formatNumber(num) { if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; return num.toString(); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function openArticle(url, permalink) { // If direct URL is available and not self-post, use it if (url && !url.startsWith('#') && !url.includes('reddit.com')) { window.open(url, '_blank'); } else { window.open(permalink, '_blank'); } } function saveArticle(id) { const saved = JSON.parse(localStorage.getItem('savedArticles') || '[]'); if (!saved.includes(id)) { saved.push(id); localStorage.setItem('savedArticles', JSON.stringify(saved)); showToast('Article saved!'); } else { showToast('Already saved'); } } function showToast(message) { const toast = document.createElement('div'); toast.className = 'fixed bottom-4 right-4 bg-gray-900 text-white px-6 py-3 rounded-xl shadow-2xl z-50 animate-slideUp'; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => toast.remove(), 3000); } function updateLastUpdated() { const el = document.getElementById('last-updated'); if (el && state.lastUpdated) { el.textContent = `Updated ${getTimeAgo(state.lastUpdated)}`; } // Update AI count const aiCount = document.getElementById('ai-count'); if (aiCount) { aiCount.textContent = state.aiArticles.length; } // Dispatch stats update event for sidebar window.dispatchEvent(new CustomEvent('statsupdate', { detail: { aiCount: state.aiArticles.length, mcdavidPoints: state.mcdavidStats?.points || '--' } })); } // Fallback Data function loadFallbackData() { const fallbackAI = [ { id: 'fallback-1', title: 'OpenAI announces GPT-5 development roadmap with multimodal capabilities', url: 'https://openai.com', permalink: 'https://openai.com', author: 'AI_Insider', score: 15420, comments: 892, created: new Date(Date.now() - 3600000), thumbnail: 'https://static.photos/technology/640x360/42', source: 'OpenAI Blog' }, { id: 'fallback-2', title: 'Google DeepMind achieves breakthrough in protein folding prediction', url: 'https://deepmind.google', permalink: 'https://deepmind.google', author: 'ScienceDaily', score: 12300, comments: 567, created: new Date(Date.now() - 7200000), thumbnail: 'https://static.photos/science/640x360/23', source: 'DeepMind' }, { id: 'fallback-3', title: 'Anthropic Claude 3 shows emergent reasoning capabilities in new benchmarks', url: 'https://anthropic.com', permalink: 'https://anthropic.com', author: 'ML_Researcher', score: 9800, comments: 423, created: new Date(Date.now() - 10800000), thumbnail: null, source: 'Anthropic' } ]; state.aiArticles = fallbackAI; state.scienceArticles = fallbackAI.map(a => ({...a, title: a.title.replace('AI', 'Quantum').replace('GPT', 'Fusion')})); state.techArticles = fallbackAI.map(a => ({...a, title: 'Tech: ' + a.title})); state.nhlNews = [{ id: 'nhl-1', title: 'Connor McDavid nets hat-trick in Oilers victory over Maple Leafs', url: 'https://www.nhl.com', permalink: 'https://www.nhl.com', author: 'NHL_Network', score: 25000, comments: 1200, created: new Date(), thumbnail: 'https://static.photos/sport/640x360/97', source: 'NHL' }]; renderAIGrid(); renderScienceGrid(); renderTechGrid(); renderNHLGrid(); } // Event Listeners function setupEventListeners() { // Filter buttons document.addEventListener('filterchange', (e) => { state.currentFilter = e.detail.filter; applyFilter(); }); // Search const searchInput = document.getElementById('search-input'); if (searchInput) { searchInput.addEventListener('input', debounce((e) => { performSearch(e.target.value); }, 300)); } // Theme toggle window.toggleTheme = toggleTheme; window.scrollToSection = scrollToSection; } function applyFilter() { const cards = document.querySelectorAll('.news-card'); cards.forEach(card => { if (state.currentFilter === 'all' || card.dataset.category === state.currentFilter) { card.style.display = ''; card.style.animation = 'fadeIn 0.3s ease'; } else { card.style.display = 'none'; } }); } function performSearch(query) { const cards = document.querySelectorAll('.news-card'); const lowerQuery = query.toLowerCase(); cards.forEach(card => { const title = card.querySelector('h3').textContent.toLowerCase(); if (title.includes(lowerQuery)) { card.style.display = ''; } else { card.style.display = 'none'; } }); } function scrollToSection(id) { const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Auto Refresh function startAutoRefresh() { // Refresh every 15 minutes setInterval(() => { loadAllData(); }, 15 * 60 * 1000); // Update relative times every minute setInterval(() => { updateLastUpdated(); }, 60000); } // CSS Animations (inject dynamically) const style = document.createElement('style'); style.textContent = ` @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .animate-slideUp { animation: slideUp 0.3s ease; } `; document.head.appendChild(style); // Export for components window.State = state; window.toggleTheme = toggleTheme; window.scrollToSection = scrollToSection; window.openArticle = openArticle; window.saveArticle = saveArticle;