|
|
<!DOCTYPE html> |
|
|
<html lang="fa" dir="rtl"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>🚀 داشبورد هوشمند کریپتو</title> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> |
|
|
<style> |
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
:root { |
|
|
--primary: #667eea; |
|
|
--secondary: #764ba2; |
|
|
--success: #10b981; |
|
|
--danger: #ef4444; |
|
|
--warning: #f59e0b; |
|
|
--dark: #1e293b; |
|
|
--light: #f8fafc; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: 'Vazirmatn', sans-serif; |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: white; |
|
|
min-height: 100vh; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.container { |
|
|
max-width: 1400px; |
|
|
margin: 0 auto; |
|
|
} |
|
|
|
|
|
|
|
|
.header { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
backdrop-filter: blur(20px); |
|
|
border-radius: 20px; |
|
|
padding: 30px; |
|
|
margin-bottom: 30px; |
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); |
|
|
} |
|
|
|
|
|
.header-title { |
|
|
font-size: 32px; |
|
|
font-weight: 800; |
|
|
margin-bottom: 20px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 15px; |
|
|
} |
|
|
|
|
|
.status-badge { |
|
|
display: inline-flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
padding: 8px 16px; |
|
|
background: rgba(16, 185, 129, 0.2); |
|
|
border-radius: 20px; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
.status-dot { |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
background: var(--success); |
|
|
border-radius: 50%; |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0%, 100% { opacity: 1; } |
|
|
50% { opacity: 0.5; } |
|
|
} |
|
|
|
|
|
|
|
|
.stats-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
|
|
gap: 20px; |
|
|
margin-bottom: 30px; |
|
|
} |
|
|
|
|
|
.stat-card { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
backdrop-filter: blur(20px); |
|
|
border-radius: 15px; |
|
|
padding: 25px; |
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); |
|
|
} |
|
|
|
|
|
.stat-label { |
|
|
font-size: 14px; |
|
|
opacity: 0.8; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
.stat-value { |
|
|
font-size: 32px; |
|
|
font-weight: 800; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
.stat-change { |
|
|
font-size: 14px; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
.stat-change.positive { color: var(--success); } |
|
|
.stat-change.negative { color: var(--danger); } |
|
|
|
|
|
|
|
|
.tabs { |
|
|
display: flex; |
|
|
gap: 10px; |
|
|
margin-bottom: 30px; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.tab-btn { |
|
|
padding: 12px 24px; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
border: none; |
|
|
border-radius: 12px; |
|
|
color: white; |
|
|
font-size: 16px; |
|
|
font-weight: 600; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.tab-btn:hover { |
|
|
background: rgba(255, 255, 255, 0.2); |
|
|
} |
|
|
|
|
|
.tab-btn.active { |
|
|
background: rgba(255, 255, 255, 0.3); |
|
|
} |
|
|
|
|
|
|
|
|
.tab-content { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.tab-content.active { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
|
|
|
.card-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); |
|
|
gap: 20px; |
|
|
} |
|
|
|
|
|
.coin-card { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
backdrop-filter: blur(20px); |
|
|
border-radius: 15px; |
|
|
padding: 20px; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.coin-card:hover { |
|
|
transform: translateY(-5px); |
|
|
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
|
|
|
.coin-header { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
|
|
|
.coin-symbol { |
|
|
font-size: 24px; |
|
|
font-weight: 800; |
|
|
} |
|
|
|
|
|
.coin-rank { |
|
|
background: rgba(255, 255, 255, 0.2); |
|
|
padding: 4px 12px; |
|
|
border-radius: 10px; |
|
|
font-size: 12px; |
|
|
} |
|
|
|
|
|
.coin-price { |
|
|
font-size: 28px; |
|
|
font-weight: 800; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
.coin-change { |
|
|
font-size: 16px; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
|
|
|
.news-card { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
backdrop-filter: blur(20px); |
|
|
border-radius: 15px; |
|
|
padding: 20px; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.news-card:hover { |
|
|
background: rgba(255, 255, 255, 0.15); |
|
|
} |
|
|
|
|
|
.news-title { |
|
|
font-size: 18px; |
|
|
font-weight: 700; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
.news-meta { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
font-size: 14px; |
|
|
opacity: 0.8; |
|
|
} |
|
|
|
|
|
|
|
|
.sentiment-container { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
backdrop-filter: blur(20px); |
|
|
border-radius: 20px; |
|
|
padding: 30px; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.sentiment-value { |
|
|
font-size: 64px; |
|
|
font-weight: 900; |
|
|
margin: 20px 0; |
|
|
} |
|
|
|
|
|
.sentiment-label { |
|
|
font-size: 24px; |
|
|
font-weight: 700; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.gauge { |
|
|
width: 100%; |
|
|
height: 40px; |
|
|
background: linear-gradient(to right, #ef4444, #f59e0b, #10b981); |
|
|
border-radius: 20px; |
|
|
position: relative; |
|
|
margin: 30px 0; |
|
|
} |
|
|
|
|
|
.gauge-pointer { |
|
|
position: absolute; |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
background: white; |
|
|
border-radius: 50%; |
|
|
top: 10px; |
|
|
transform: translateX(-50%); |
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
|
|
|
|
|
|
.chart-container { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
backdrop-filter: blur(20px); |
|
|
border-radius: 20px; |
|
|
padding: 30px; |
|
|
margin-top: 30px; |
|
|
} |
|
|
|
|
|
.chart-title { |
|
|
font-size: 20px; |
|
|
font-weight: 700; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
|
|
|
.loading { |
|
|
text-align: center; |
|
|
padding: 40px; |
|
|
font-size: 18px; |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.stats-grid { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
|
|
|
.card-grid { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
|
|
|
<div class="header"> |
|
|
<div class="header-title"> |
|
|
<span>🚀</span> |
|
|
<span>داشبورد هوشمند کریپتو</span> |
|
|
<span class="status-badge"> |
|
|
<span class="status-dot"></span> |
|
|
آنلاین |
|
|
</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="stats-grid" id="marketStats"> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">کل ارزش بازار</div> |
|
|
<div class="stat-value" id="totalMarketCap">$0</div> |
|
|
<div class="stat-change positive" id="marketCapChange">+0%</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">حجم معاملات 24 ساعت</div> |
|
|
<div class="stat-value" id="totalVolume">$0</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">تسلط بیتکوین</div> |
|
|
<div class="stat-value" id="btcDominance">0%</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">تعداد ارزها</div> |
|
|
<div class="stat-value" id="activeCryptos">0</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="tabs"> |
|
|
<button class="tab-btn active" onclick="switchTab('trending')">🔥 ترندها</button> |
|
|
<button class="tab-btn" onclick="switchTab('top')">💎 برترینها</button> |
|
|
<button class="tab-btn" onclick="switchTab('news')">📰 اخبار</button> |
|
|
<button class="tab-btn" onclick="switchTab('sentiment')">📊 احساسات</button> |
|
|
<button class="tab-btn" onclick="switchTab('blockchain')">⛓️ بلاکچین</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="trending" class="tab-content active"> |
|
|
<h2 style="margin-bottom: 20px;">🔥 ارزهای ترند</h2> |
|
|
<div class="card-grid" id="trendingGrid"> |
|
|
<div class="loading">در حال بارگذاری...</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="top" class="tab-content"> |
|
|
<h2 style="margin-bottom: 20px;">💎 برترین ارزها</h2> |
|
|
<div class="card-grid" id="topGrid"> |
|
|
<div class="loading">در حال بارگذاری...</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="news" class="tab-content"> |
|
|
<h2 style="margin-bottom: 20px;">📰 آخرین اخبار</h2> |
|
|
<div class="card-grid" id="newsGrid"> |
|
|
<div class="loading">در حال بارگذاری...</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="sentiment" class="tab-content"> |
|
|
<h2 style="margin-bottom: 20px;">📊 احساسات بازار</h2> |
|
|
<div class="sentiment-container"> |
|
|
<div class="sentiment-label">شاخص ترس و طمع</div> |
|
|
<div class="sentiment-value" id="sentimentValue">50</div> |
|
|
<div class="sentiment-label" id="sentimentLabel">خنثی</div> |
|
|
<div class="gauge"> |
|
|
<div class="gauge-pointer" id="gaugePointer" style="left: 50%;"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="chart-container"> |
|
|
<div class="chart-title">تاریخچه 7 روز اخیر</div> |
|
|
<canvas id="sentimentChart" height="100"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="blockchain" class="tab-content"> |
|
|
<h2 style="margin-bottom: 20px;">⛓️ آمار بلاکچین</h2> |
|
|
<div class="stats-grid"> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">قیمت گس اتریوم</div> |
|
|
<div class="stat-value" id="ethGas">0 Gwei</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">آخرین بلاک اتریوم</div> |
|
|
<div class="stat-value" id="ethBlock">0</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">قیمت گس BSC</div> |
|
|
<div class="stat-value" id="bscGas">0 Gwei</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-label">آخرین بلاک BSC</div> |
|
|
<div class="stat-value" id="bscBlock">0</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
let currentTab = 'trending'; |
|
|
|
|
|
|
|
|
function switchTab(tabName) { |
|
|
currentTab = tabName; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(btn => { |
|
|
btn.classList.remove('active'); |
|
|
}); |
|
|
event.target.classList.add('active'); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.tab-content').forEach(content => { |
|
|
content.classList.remove('active'); |
|
|
}); |
|
|
document.getElementById(tabName).classList.add('active'); |
|
|
|
|
|
|
|
|
loadTabData(tabName); |
|
|
} |
|
|
|
|
|
|
|
|
async function loadTabData(tabName) { |
|
|
switch(tabName) { |
|
|
case 'trending': |
|
|
await loadTrending(); |
|
|
break; |
|
|
case 'top': |
|
|
await loadTopCoins(); |
|
|
break; |
|
|
case 'news': |
|
|
await loadNews(); |
|
|
break; |
|
|
case 'sentiment': |
|
|
await loadSentiment(); |
|
|
break; |
|
|
case 'blockchain': |
|
|
await loadBlockchain(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function loadMarketOverview() { |
|
|
try { |
|
|
const response = await fetch('/api/crypto/market-overview'); |
|
|
const data = await response.json(); |
|
|
|
|
|
document.getElementById('totalMarketCap').textContent = formatCurrency(data.total_market_cap); |
|
|
document.getElementById('totalVolume').textContent = formatCurrency(data.total_volume_24h); |
|
|
document.getElementById('btcDominance').textContent = data.btc_dominance.toFixed(1) + '%'; |
|
|
document.getElementById('activeCryptos').textContent = data.active_cryptocurrencies.toLocaleString(); |
|
|
|
|
|
const changeElement = document.getElementById('marketCapChange'); |
|
|
const change = data.market_cap_change_24h; |
|
|
changeElement.textContent = (change > 0 ? '+' : '') + change.toFixed(2) + '%'; |
|
|
changeElement.className = 'stat-change ' + (change > 0 ? 'positive' : 'negative'); |
|
|
} catch (error) { |
|
|
console.error('Error loading market overview:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function loadTrending() { |
|
|
try { |
|
|
const response = await fetch('/api/crypto/prices/trending?limit=12'); |
|
|
const coins = await response.json(); |
|
|
|
|
|
const grid = document.getElementById('trendingGrid'); |
|
|
grid.innerHTML = coins.map(coin => createCoinCard(coin)).join(''); |
|
|
} catch (error) { |
|
|
console.error('Error loading trending:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function loadTopCoins() { |
|
|
try { |
|
|
const response = await fetch('/api/crypto/prices/top?limit=20'); |
|
|
const coins = await response.json(); |
|
|
|
|
|
const grid = document.getElementById('topGrid'); |
|
|
grid.innerHTML = coins.map(coin => createCoinCard(coin)).join(''); |
|
|
} catch (error) { |
|
|
console.error('Error loading top coins:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function loadNews() { |
|
|
try { |
|
|
const response = await fetch('/api/crypto/news/latest?limit=12'); |
|
|
const news = await response.json(); |
|
|
|
|
|
const grid = document.getElementById('newsGrid'); |
|
|
grid.innerHTML = news.map(article => ` |
|
|
<div class="news-card" onclick="window.open('${article.url}', '_blank')"> |
|
|
<div class="news-title">${article.title}</div> |
|
|
<div class="news-meta"> |
|
|
<span>${article.source}</span> |
|
|
<span>${formatTime(article.published_at)}</span> |
|
|
</div> |
|
|
</div> |
|
|
`).join(''); |
|
|
} catch (error) { |
|
|
console.error('Error loading news:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function loadSentiment() { |
|
|
try { |
|
|
const [current, history] = await Promise.all([ |
|
|
fetch('/api/crypto/sentiment/current').then(r => r.json()), |
|
|
fetch('/api/crypto/sentiment/history?hours=168').then(r => r.json()) |
|
|
]); |
|
|
|
|
|
|
|
|
document.getElementById('sentimentValue').textContent = current.fear_greed_index; |
|
|
document.getElementById('sentimentLabel').textContent = current.classification; |
|
|
|
|
|
|
|
|
const pointer = document.getElementById('gaugePointer'); |
|
|
pointer.style.left = current.fear_greed_index + '%'; |
|
|
|
|
|
|
|
|
if (history.history && history.history.length > 0) { |
|
|
updateSentimentChart(history.history); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error loading sentiment:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function loadBlockchain() { |
|
|
try { |
|
|
const [gas, stats] = await Promise.all([ |
|
|
fetch('/api/crypto/blockchain/gas').then(r => r.json()), |
|
|
fetch('/api/crypto/blockchain/stats').then(r => r.json()) |
|
|
]); |
|
|
|
|
|
document.getElementById('ethGas').textContent = gas.ethereum.gas_price_gwei + ' Gwei'; |
|
|
document.getElementById('ethBlock').textContent = stats.ethereum.latest_block.toLocaleString(); |
|
|
document.getElementById('bscGas').textContent = gas.bsc.gas_price_gwei + ' Gwei'; |
|
|
document.getElementById('bscBlock').textContent = stats.bsc.latest_block.toLocaleString(); |
|
|
} catch (error) { |
|
|
console.error('Error loading blockchain:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function createCoinCard(coin) { |
|
|
const changeClass = coin.price_change_24h >= 0 ? 'positive' : 'negative'; |
|
|
const changeSymbol = coin.price_change_24h >= 0 ? '+' : ''; |
|
|
|
|
|
return ` |
|
|
<div class="coin-card"> |
|
|
<div class="coin-header"> |
|
|
<div class="coin-symbol">${coin.symbol}</div> |
|
|
<div class="coin-rank">#${coin.rank}</div> |
|
|
</div> |
|
|
<div style="font-size: 14px; opacity: 0.8; margin-bottom: 10px;">${coin.name}</div> |
|
|
<div class="coin-price">$${formatNumber(coin.price)}</div> |
|
|
<div class="coin-change ${changeClass}"> |
|
|
${changeSymbol}${coin.price_change_24h.toFixed(2)}% |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
function updateSentimentChart(data) { |
|
|
const ctx = document.getElementById('sentimentChart'); |
|
|
|
|
|
new Chart(ctx, { |
|
|
type: 'line', |
|
|
data: { |
|
|
labels: data.map(d => new Date(d.timestamp).toLocaleDateString('fa-IR')), |
|
|
datasets: [{ |
|
|
label: 'شاخص ترس و طمع', |
|
|
data: data.map(d => d.value), |
|
|
borderColor: 'rgba(255, 255, 255, 0.8)', |
|
|
backgroundColor: 'rgba(255, 255, 255, 0.1)', |
|
|
tension: 0.4, |
|
|
fill: true, |
|
|
borderWidth: 3 |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
plugins: { |
|
|
legend: { display: false } |
|
|
}, |
|
|
scales: { |
|
|
y: { |
|
|
beginAtZero: true, |
|
|
max: 100, |
|
|
ticks: { color: 'rgba(255, 255, 255, 0.8)' }, |
|
|
grid: { color: 'rgba(255, 255, 255, 0.1)' } |
|
|
}, |
|
|
x: { |
|
|
ticks: { color: 'rgba(255, 255, 255, 0.8)' }, |
|
|
grid: { color: 'rgba(255, 255, 255, 0.1)' } |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function formatNumber(num) { |
|
|
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; |
|
|
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; |
|
|
if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; |
|
|
return num.toFixed(2); |
|
|
} |
|
|
|
|
|
function formatCurrency(num) { |
|
|
return '$' + formatNumber(num); |
|
|
} |
|
|
|
|
|
function formatTime(timestamp) { |
|
|
const date = new Date(timestamp); |
|
|
const now = new Date(); |
|
|
const diff = Math.floor((now - date) / 1000); |
|
|
|
|
|
if (diff < 60) return 'همین الان'; |
|
|
if (diff < 3600) return Math.floor(diff / 60) + ' دقیقه پیش'; |
|
|
if (diff < 86400) return Math.floor(diff / 3600) + ' ساعت پیش'; |
|
|
return Math.floor(diff / 86400) + ' روز پیش'; |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
loadMarketOverview(); |
|
|
loadTrending(); |
|
|
|
|
|
|
|
|
setInterval(() => { |
|
|
loadMarketOverview(); |
|
|
loadTabData(currentTab); |
|
|
}, 30000); |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |