| <!DOCTYPE html> |
| <html lang="en" class="dark"> |
| <head> |
| <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>RMI News Network — Live Crypto Intelligence</title> |
| <style> |
| :root{--bg:#06060f;--fg:#e8e8f0;--accent:#7c6ff7;--green:#2ecc71;--red:#e74c3c;--card:#0f0f1d;--border:#1e1e35;--muted:#888;--yellow:#f1c40f;--orange:#e67e22;--surface:#121224;--glow:rgba(124,111,247,.15);--cyan:#06b6d4} |
| *{margin:0;padding:0;box-sizing:border-box} |
| body{background:var(--bg);color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif} |
| header{background:var(--card);border-bottom:1px solid var(--border);padding:10px 24px;display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:100} |
| .logo{font-size:20px;font-weight:800}.logo span:first-child{color:var(--accent)}.logo span:last-child{color:var(--fg)} |
| nav{display:flex;gap:4px} |
| nav a{color:var(--muted);text-decoration:none;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:500} |
| nav a:hover,nav a.active{color:var(--fg);background:rgba(255,255,255,.06)} |
| nav a.active{color:var(--accent)} |
| .market-ticker{display:flex;gap:16px;margin-left:auto;font-size:12px} |
| .ticker-item{display:flex;align-items:center;gap:4px} |
| .ticker-price{font-weight:700;font-family:monospace} |
| .ticker-change{font-size:11px} |
| main{max-width:1500px;margin:0 auto;padding:20px;display:grid;grid-template-columns:240px 1fr 340px;gap:20px} |
| .sidebar{position:sticky;top:70px;height:fit-content;max-height:calc(100vh-90px);overflow-y:auto} |
| .section-title{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:2px;margin:16px 0 8px;font-weight:700} |
| .filter-btn{display:block;width:100%;text-align:left;padding:7px 12px;border-radius:6px;color:var(--muted);font-size:12px;cursor:pointer;border:none;background:none;margin:1px 0} |
| .filter-btn:hover{background:var(--surface);color:var(--fg)} |
| .filter-btn.active{background:var(--accent);color:#fff;font-weight:600} |
| .panel{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px;margin-bottom:12px} |
| .panel h4{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px} |
| .fear-greed-display{text-align:center;padding:12px 0} |
| .fg-value{font-size:42px;font-weight:800;line-height:1} |
| .fg-label{font-size:13px;margin-top:4px} |
| .fg-bar{height:8px;border-radius:4px;margin-top:6px;background:linear-gradient(90deg,#ff0044,#ff8800,#ffd700,#88ff00,#00ff88)} |
| .fg-marker{width:4px;height:14px;border-radius:2px;background:#fff;position:relative;top:-11px} |
| .price-row{display:flex;justify-content:space-between;padding:5px 0;font-size:12px;border-bottom:1px solid rgba(255,255,255,.03)} |
| .price-row:last-child{border:none} |
| .price-name{color:var(--muted)} |
| .price-value{font-weight:600;font-family:monospace} |
| .article-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:16px;margin-bottom:8px;transition:.15s;cursor:pointer} |
| .article-card:hover{border-color:var(--accent);box-shadow:0 4px 20px var(--glow)} |
| .article-source{font-size:10px;color:var(--accent);font-weight:600;margin-bottom:4px} |
| .article-title{font-size:15px;font-weight:700;line-height:1.4;margin-bottom:6px} |
| .article-title a{color:var(--fg);text-decoration:none} |
| .article-meta{font-size:10px;color:var(--muted)} |
| .article-card.high-impact{border-left:3px solid var(--red)} |
| .article-card.medium-impact{border-left:3px solid var(--orange)} |
| .prediction-item{padding:7px 0;border-bottom:1px solid rgba(255,255,255,.03);font-size:11px} |
| .prediction-item:last-child{border:none} |
| .prediction-title{color:var(--fg);margin-bottom:2px} |
| .prediction-volume{color:var(--muted);font-size:10px} |
| .loading{text-align:center;padding:40px;color:var(--muted)} |
| .spinner{display:inline-block;width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite} |
| @keyframes spin{to{transform:rotate(360deg)}} |
| .brief-bar{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px 14px;margin-bottom:16px;font-size:12px;color:var(--muted)} |
| .brief-bar b{color:var(--fg)} |
| .source-badge{display:inline-block;padding:2px 6px;border-radius:3px;font-size:9px;background:rgba(6,182,212,.1);color:var(--cyan);margin-right:4px} |
| </style> |
| </head> |
| <body> |
|
|
| <header> |
| <div class="logo"><span>RMI</span><span> News</span></div> |
| <nav> |
| <a href="/rugcharts">RugCharts</a> |
| <a href="/rugmaps">RugMaps</a> |
| <a href="/news" class="active">News</a> |
| </nav> |
| <div class="market-ticker" id="tickerBar"> |
| <div class="spinner"></div> |
| </div> |
| </header> |
|
|
| <main> |
| |
| <aside class="sidebar"> |
| <div class="panel"> |
| <h4>😨 Fear & Greed</h4> |
| <div id="fearGreedWidget"><div class="spinner"></div></div> |
| </div> |
|
|
| <div class="panel"> |
| <h4>💰 Live Prices</h4> |
| <div id="pricesWidget"><div class="spinner"></div></div> |
| </div> |
|
|
| <div class="panel"> |
| <h4>🔥 Trending</h4> |
| <div id="trendingWidget"><div class="spinner"></div></div> |
| </div> |
|
|
| <div class="section-title">Filters</div> |
| <button class="filter-btn active" onclick="filterNews('all')">All News</button> |
| <button class="filter-btn" onclick="filterNews('bitcoin')">Bitcoin</button> |
| <button class="filter-btn" onclick="filterNews('ethereum')">Ethereum</button> |
| <button class="filter-btn" onclick="filterNews('defi')">DeFi</button> |
| <button class="filter-btn" onclick="filterNews('regulation')">Regulation</button> |
| <button class="filter-btn" onclick="filterNews('security')">Security</button> |
| </aside> |
|
|
| |
| <div> |
| <div class="brief-bar" id="briefBar"><div class="spinner"></div> Loading market data...</div> |
| <div style="display:flex;gap:6px;margin-bottom:12px;flex-wrap:wrap" id="tabBar"> |
| <button class="sort-btn active" onclick="switchTab('all')">📰 All News</button> |
| <button class="sort-btn" onclick="switchTab('ct')">𝕏 CT Rundown</button> |
| <button class="sort-btn" onclick="switchTab('weekly')">⭐ Weekly Best</button> |
| <button class="sort-btn" onclick="switchTab('academic')">📚 Academic</button> |
| <button class="sort-btn" onclick="switchTab('social')">💬 Social</button> |
| </div> |
| <div id="articles"><div class="loading"><div class="spinner"></div> Loading news...</div></div> |
| <div id="ctRundown" style="display:none"></div> |
| <div id="weeklyContent" style="display:none"></div> |
| <div id="academicContent" style="display:none"></div> |
| <div id="socialContent" style="display:none"></div> |
| </div> |
|
|
| |
| <aside class="sidebar"> |
| <div class="panel"> |
| <h4>🎲 Prediction Markets</h4> |
| <div id="polyWidget"><div class="spinner"></div></div> |
| </div> |
|
|
| <div class="panel"> |
| <h4>📡 Data Sources</h4> |
| <div style="font-size:11px;color:var(--muted);line-height:1.6"> |
| <span class="source-badge">CG</span> CoinGecko<br> |
| <span class="source-badge">FG</span> Fear & Greed<br> |
| <span class="source-badge">PM</span> Polymarket<br> |
| <span class="source-badge">RSS</span> 200+ RSS Feeds<br> |
| <span class="source-badge">DB</span> DataBus (56 chains) |
| </div> |
| </div> |
| </aside> |
| </main> |
|
|
| <script> |
| const DB = '/api/v1/databus'; |
| let allArticles = []; |
| let currentFilter = 'all'; |
| |
| async function loadAll() { |
| |
| const [prices, fearGreed, trending, polymarket, brief, newsData] = await Promise.all([ |
| fetch(DB+'/market/prices').then(r=>r.json()).catch(()=>null), |
| fetch(DB+'/market/fear-greed').then(r=>r.json()).catch(()=>null), |
| fetch(DB+'/market/trending').then(r=>r.json()).catch(()=>null), |
| fetch(DB+'/market/prediction-markets').then(r=>r.json()).catch(()=>null), |
| fetch(DB+'/market/brief').then(r=>r.json()).catch(()=>null), |
| fetch(DB+'/news/full?limit=30').then(r=>r.json()).catch(()=>null), |
| ]); |
| |
| |
| if (prices?.prices) { |
| let html = ''; |
| for (const [coin, data] of Object.entries(prices.prices)) { |
| const change = data.usd_24h_change || 0; |
| const color = change < -3 ? 'var(--red)' : change < 0 ? 'var(--orange)' : 'var(--green)'; |
| html += `<div class="ticker-item"> |
| <span style="color:var(--muted)">${coin.toUpperCase().slice(0,4)}</span> |
| <span class="ticker-price">$${(data.usd||0).toLocaleString()}</span> |
| <span class="ticker-change" style="color:${color}">${change>=0?'+':''}${change.toFixed(1)}%</span> |
| </div>`; |
| } |
| document.getElementById('tickerBar').innerHTML = html; |
| } |
| |
| |
| if (fearGreed) { |
| const v = fearGreed.value || 50; |
| document.getElementById('fearGreedWidget').innerHTML = ` |
| <div class="fear-greed-display"> |
| <div class="fg-value" style="color:${fearGreed.color}">${v}</div> |
| <div class="fg-label">${fearGreed.classification}</div> |
| <div class="fg-bar"><div class="fg-marker" style="left:${v}%"></div></div> |
| </div>`; |
| } |
| |
| |
| if (prices?.prices) { |
| let html = ''; |
| for (const [coin, data] of Object.entries(prices.prices)) { |
| const change = data.usd_24h_change || 0; |
| const color = change < 0 ? 'var(--red)' : 'var(--green)'; |
| html += `<div class="price-row"> |
| <span class="price-name">${coin.charAt(0).toUpperCase()+coin.slice(1)}</span> |
| <span class="price-value">$${(data.usd||0).toLocaleString()} <span style="color:${color};font-size:10px">${change>=0?'+':''}${change.toFixed(1)}%</span></span> |
| </div>`; |
| } |
| document.getElementById('pricesWidget').innerHTML = html; |
| } |
| |
| |
| if (trending?.trending) { |
| document.getElementById('trendingWidget').innerHTML = trending.trending.slice(0,8).map(t => |
| `<div class="price-row"> |
| <span class="price-name">${t.name}</span> |
| <span style="font-size:10px;color:var(--muted)">${t.symbol} #${t.market_cap_rank||'?'}</span> |
| </div>` |
| ).join(''); |
| } |
| |
| |
| if (polymarket?.markets?.length) { |
| document.getElementById('polyWidget').innerHTML = polymarket.markets.map(m => |
| `<div class="prediction-item"> |
| <div class="prediction-title">${m.title.slice(0,80)}</div> |
| <div class="prediction-volume">Vol: $${(m.volume||0).toLocaleString()}</div> |
| </div>` |
| ).join(''); |
| } |
| |
| |
| if (brief?.brief || newsData?.market_brief?.brief) { |
| const b = brief?.brief || newsData?.market_brief?.brief || ''; |
| document.getElementById('briefBar').innerHTML = `<b>Market:</b> ${b}`; |
| } |
| |
| |
| const newsContent = newsData?.headlines; |
| if (newsContent?.articles) { |
| allArticles = newsContent.articles; |
| } else { |
| |
| try { |
| const fallback = await fetch('/news/daily-data').then(r=>r.json()); |
| allArticles = fallback?.articles || []; |
| } catch(e) { allArticles = []; } |
| } |
| renderArticles(); |
| } |
| |
| function filterNews(cat) { |
| currentFilter = cat; |
| document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); |
| event.target.classList.add('active'); |
| renderArticles(); |
| } |
| |
| function renderArticles() { |
| let filtered = allArticles; |
| if (currentFilter !== 'all') { |
| filtered = allArticles.filter(a => { |
| const text = (a.title + ' ' + (a.source||'') + ' ' + (a.category||'')).toLowerCase(); |
| return text.includes(currentFilter.toLowerCase()); |
| }); |
| } |
| |
| if (!filtered.length) { |
| document.getElementById('articles').innerHTML = '<div class="loading">No articles found. Try a different filter.</div>'; |
| return; |
| } |
| |
| document.getElementById('articles').innerHTML = filtered.map(a => { |
| const impact = a.impact || (a.category === 'security' ? 'high' : 'medium'); |
| return `<div class="article-card ${impact}-impact"> |
| <div class="article-source">${a.source||'Unknown'} · ${a.category||'General'} · ${a.published||''}</div> |
| <div class="article-title"><a href="${a.url||'#'}" target="_blank">${a.title||'Untitled'}</a></div> |
| <div class="article-meta">${a.summary||a.description||''.slice(0,150)}</div> |
| </div>`; |
| }).join(''); |
| } |
| |
| loadAll(); |
| |
| setInterval(loadAll, 120000); |
| </script> |
| </body> |
| </html> |
|
|