|
|
<!DOCTYPE html> |
|
|
<html lang="en" dir="ltr" data-theme="dark"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<meta name="description" content="Crypto Monitor - Premium Dashboard with Real-time Market Data, AI Analysis & Sentiment"> |
|
|
<meta name="theme-color" content="#8B5CF6"> |
|
|
<title>Dashboard | Crypto Monitor</title> |
|
|
|
|
|
|
|
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%238B5CF6'/%3E%3Cstop offset='100%25' stop-color='%236D28D9'/%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='50' cy='50' r='45' fill='url(%23g)'/%3E%3Ctext x='50' y='65' font-size='50' text-anchor='middle' fill='white' font-weight='bold'%3EC%3C/text%3E%3C/svg%3E"> |
|
|
|
|
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin> |
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
|
|
<link rel="dns-prefetch" href="https://fonts.googleapis.com"> |
|
|
|
|
|
|
|
|
<style> |
|
|
/* Critical above-the-fold styles - Cursor-inspired */ |
|
|
:root{ |
|
|
--bg-primary:#0A0A0A; |
|
|
--surface-primary:#1E1E1E; |
|
|
--text-primary:#EFEFEF; |
|
|
--accent-purple:#8B5CF6; |
|
|
--sidebar-width:240px; |
|
|
} |
|
|
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box} |
|
|
html{font-size:15px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} |
|
|
body{font-family:'Inter',-apple-system,BlinkMacSystemFont,system-ui,sans-serif;font-size:15px;line-height:1.5;color:var(--text-primary);background:var(--bg-primary);min-height:100vh} |
|
|
.app-container{display:flex;min-height:100vh} |
|
|
.sidebar{position:fixed;left:0;top:0;bottom:0;width:var(--sidebar-width);background:#0F0F0F;border-right:1px solid #2A2A2A;z-index:1200;transition:width 300ms cubic-bezier(0.4,0,0.2,1)} |
|
|
.main-content{flex:1;margin-left:var(--sidebar-width);min-height:100vh;display:flex;flex-direction:column;transition:margin-left 300ms cubic-bezier(0.4,0,0.2,1)} |
|
|
.page-content{padding:24px;max-width:1400px;margin:0 auto;width:100%} |
|
|
@media(max-width:1024px){.sidebar{transform:translateX(-100%)}.main-content{margin-left:0}} |
|
|
</style> |
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="/static/shared/css/design-system-cursor.css"> |
|
|
<link rel="stylesheet" href="/static/shared/css/layout-cursor.css"> |
|
|
<link rel="stylesheet" href="/static/shared/css/components-cursor.css"> |
|
|
<link rel="stylesheet" href="/static/shared/css/animations-cursor.css"> |
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="/static/pages/dashboard/dashboard.css?v=4.0"> |
|
|
|
|
|
|
|
|
<script src="/static/shared/js/utils/error-suppressor.js"></script> |
|
|
|
|
|
|
|
|
<script src="/static/assets/icons/crypto-icons.js"></script> |
|
|
|
|
|
|
|
|
<script src="/static/js/api-config.js"></script> |
|
|
<script> |
|
|
|
|
|
window.apiReady = new Promise((resolve) => { |
|
|
if (window.apiClient) { |
|
|
console.log('✅ API Client ready'); |
|
|
resolve(window.apiClient); |
|
|
} else { |
|
|
console.error('❌ API Client not loaded'); |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</head> |
|
|
<body> |
|
|
<div class="app-container"> |
|
|
|
|
|
<aside id="sidebar-container"></aside> |
|
|
|
|
|
|
|
|
<main class="main-content"> |
|
|
|
|
|
<header id="header-container"></header> |
|
|
|
|
|
|
|
|
<div class="page-content stagger-fade-in"> |
|
|
|
|
|
<div class="page-header"> |
|
|
<h1 class="page-title">Dashboard</h1> |
|
|
<p class="page-description">Real-time Market Data & AI Analysis</p> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--space-4); margin-bottom: var(--space-8);"> |
|
|
|
|
|
<div class="stat-card skeleton-wave" style="min-height: 140px;"></div> |
|
|
<div class="stat-card skeleton-wave" style="min-height: 140px;"></div> |
|
|
<div class="stat-card skeleton-wave" style="min-height: 140px;"></div> |
|
|
<div class="stat-card skeleton-wave" style="min-height: 140px;"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div style="display: grid; grid-template-columns: 1fr; gap: var(--space-6); margin-bottom: var(--space-8);"> |
|
|
|
|
|
<div class="card" id="market-chart-container"> |
|
|
<div class="card-header"> |
|
|
<div> |
|
|
<h3 class="card-title">Market Overview</h3> |
|
|
<p class="card-subtitle">Real-time price movements</p> |
|
|
</div> |
|
|
<div style="display: flex; gap: var(--space-2);"> |
|
|
<button class="btn btn-ghost btn-sm" data-timeframe="1h">1H</button> |
|
|
<button class="btn btn-ghost btn-sm" data-timeframe="4h">4H</button> |
|
|
<button class="btn btn-secondary btn-sm" data-timeframe="1d">1D</button> |
|
|
<button class="btn btn-ghost btn-sm" data-timeframe="1w">1W</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<div id="market-chart" style="min-height: 400px; display: flex; align-items: center; justify-content: center;"> |
|
|
<div class="spinner spinner-lg"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: var(--space-6); margin-bottom: var(--space-8);"> |
|
|
|
|
|
<div class="card"> |
|
|
<div class="card-header"> |
|
|
<h3 class="card-title">Top Gainers</h3> |
|
|
<span class="badge badge-success pill">24h</span> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<div id="top-gainers-list"> |
|
|
|
|
|
<div style="display: flex; flex-direction: column; gap: var(--space-3);"> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="card"> |
|
|
<div class="card-header"> |
|
|
<h3 class="card-title">Top Losers</h3> |
|
|
<span class="badge badge-danger pill">24h</span> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<div id="top-losers-list"> |
|
|
|
|
|
<div style="display: flex; flex-direction: column; gap: var(--space-3);"> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="card" id="sentiment-container"> |
|
|
<div class="card-header"> |
|
|
<div> |
|
|
<h3 class="card-title">AI Sentiment Analysis</h3> |
|
|
<p class="card-subtitle">Market sentiment powered by AI models</p> |
|
|
</div> |
|
|
<span class="badge badge-primary">AI</span> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<div id="sentiment-content" style="min-height: 200px; display: flex; align-items: center; justify-content: center;"> |
|
|
<div class="dots-loader"> |
|
|
<span></span> |
|
|
<span></span> |
|
|
<span></span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="card" style="margin-top: var(--space-6);" id="recent-activity"> |
|
|
<div class="card-header"> |
|
|
<h3 class="card-title">Recent Activity</h3> |
|
|
<button class="btn btn-ghost btn-sm">View All</button> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<div id="activity-list"> |
|
|
|
|
|
<div style="display: flex; flex-direction: column; gap: var(--space-3);"> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
<div class="skeleton skeleton-text"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div style="margin-top: var(--space-16); padding-top: var(--space-6); border-top: 1px solid var(--border-default); text-align: center; color: var(--text-tertiary); font-size: var(--text-sm);"> |
|
|
<p>Last updated: <span id="footer-last-update">Just now</span></p> |
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="toast-container" aria-live="polite"></div> |
|
|
|
|
|
<script type="module"> |
|
|
|
|
|
(async function() { |
|
|
const initApp = async () => { |
|
|
try { |
|
|
|
|
|
const { LayoutManager } = await import('/static/shared/js/core/layout-manager.js?v=4.0'); |
|
|
await LayoutManager.init('dashboard'); |
|
|
|
|
|
|
|
|
const { default: dashboardPage } = await import('/static/pages/dashboard/dashboard.js?v=4.0'); |
|
|
window.dashboardPage = dashboardPage; |
|
|
|
|
|
window.addEventListener('beforeunload', () => { |
|
|
if (window.dashboardPage) window.dashboardPage.destroy(); |
|
|
}); |
|
|
|
|
|
console.log('✅ Dashboard initialized with Cursor UI'); |
|
|
} catch (error) { |
|
|
console.error('❌ Failed to initialize dashboard:', error); |
|
|
|
|
|
|
|
|
const pageContent = document.querySelector('.page-content'); |
|
|
if (pageContent) { |
|
|
pageContent.insertAdjacentHTML('afterbegin', ` |
|
|
<div class="alert alert-danger" style="margin-bottom: var(--space-6);"> |
|
|
<svg class="alert-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<circle cx="12" cy="12" r="10"/> |
|
|
<line x1="12" y1="8" x2="12" y2="12"/> |
|
|
<line x1="12" y1="16" x2="12.01" y2="16"/> |
|
|
</svg> |
|
|
<div class="alert-content"> |
|
|
<div class="alert-title">Initialization Error</div> |
|
|
<div class="alert-message">Failed to load dashboard. Please refresh the page.</div> |
|
|
</div> |
|
|
</div> |
|
|
`); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
if (window.requestIdleCallback) { |
|
|
window.requestIdleCallback(initApp, { timeout: 2000 }); |
|
|
} else { |
|
|
setTimeout(initApp, 100); |
|
|
} |
|
|
})(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|