rmi-backend/static/news.html

267 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>
<!-- Left Sidebar -->
<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>
<!-- Main Feed -->
<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>
<!-- Right Sidebar -->
<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() {
// Load market data and news in parallel
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),
]);
// Ticker bar
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;
}
// Fear & Greed
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>`;
}
// Prices
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;
}
// Trending
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('');
}
// Polymarket
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('');
}
// Brief bar
if (brief?.brief || newsData?.market_brief?.brief) {
const b = brief?.brief || newsData?.market_brief?.brief || '';
document.getElementById('briefBar').innerHTML = `<b>Market:</b> ${b}`;
}
// News articles
const newsContent = newsData?.headlines;
if (newsContent?.articles) {
allArticles = newsContent.articles;
} else {
// Fallback to existing news endpoint
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();
// Refresh every 2 minutes
setInterval(loadAll, 120000);
</script>
</body>
</html>