Really-amin commited on
Commit
250b128
·
verified ·
1 Parent(s): dd7ffbd

Upload 196 files

Browse files
Files changed (5) hide show
  1. api_server_extended.py +129 -2
  2. app.py +4 -14
  3. index.html +407 -443
  4. static/css/main.css +802 -568
  5. static/js/app.js +0 -0
api_server_extended.py CHANGED
@@ -2082,8 +2082,9 @@ async def get_defi():
2082
  # ===== News Endpoint (compatible with UI) =====
2083
  @app.get("/api/news")
2084
  async def get_news_api(limit: int = 20):
2085
- """Get news (compatible with UI)"""
2086
  try:
 
2087
  conn = sqlite3.connect(str(DB_PATH))
2088
  cursor = conn.cursor()
2089
  cursor.execute("""
@@ -2105,12 +2106,50 @@ async def get_news_api(limit: int = 20):
2105
  pass
2106
  results.append(record)
2107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2108
  return {
2109
  "success": True,
2110
  "news": results,
2111
- "count": len(results)
 
2112
  }
2113
  except Exception as e:
 
2114
  return {
2115
  "success": False,
2116
  "news": [],
@@ -2667,6 +2706,94 @@ async def get_sentiment_history(
2667
  raise HTTPException(status_code=500, detail=f"Failed to fetch sentiment history: {str(e)}")
2668
 
2669
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2670
  @app.get("/api/news/latest")
2671
  async def get_latest_news(
2672
  limit: int = 20,
 
2082
  # ===== News Endpoint (compatible with UI) =====
2083
  @app.get("/api/news")
2084
  async def get_news_api(limit: int = 20):
2085
+ """Get news (compatible with UI) - with external API fallback"""
2086
  try:
2087
+ # Try to get news from database first
2088
  conn = sqlite3.connect(str(DB_PATH))
2089
  cursor = conn.cursor()
2090
  cursor.execute("""
 
2106
  pass
2107
  results.append(record)
2108
 
2109
+ # If database is empty, fetch from external API
2110
+ if len(results) == 0:
2111
+ logger.info("No news in database, fetching from external API...")
2112
+ try:
2113
+ # Get API key from environment
2114
+ cryptocompare_api_key = os.getenv("CRYPTOCOMPARE_API_KEY", "968a5e25552b4cb5ba3280361d8444ab")
2115
+
2116
+ async with httpx.AsyncClient(timeout=10.0) as client:
2117
+ # Try CryptoCompare News API with API key
2118
+ response = await client.get(
2119
+ "https://min-api.cryptocompare.com/data/v2/news/?lang=EN",
2120
+ headers={
2121
+ "User-Agent": "Mozilla/5.0",
2122
+ "authorization": f"Apikey {cryptocompare_api_key}"
2123
+ }
2124
+ )
2125
+ if response.status_code == 200:
2126
+ data = response.json()
2127
+ if data.get("Data"):
2128
+ for article in data["Data"][:limit]:
2129
+ results.append({
2130
+ "id": article.get("id"),
2131
+ "title": article.get("title", ""),
2132
+ "content": article.get("body", "")[:500],
2133
+ "url": article.get("url", ""),
2134
+ "source": article.get("source", "CryptoCompare"),
2135
+ "sentiment_label": None,
2136
+ "sentiment_confidence": None,
2137
+ "related_symbols": article.get("categories", "").split("|") if article.get("categories") else [],
2138
+ "published_date": datetime.fromtimestamp(article.get("published_on", 0)).isoformat() if article.get("published_on") else None,
2139
+ "analyzed_at": datetime.now().isoformat()
2140
+ })
2141
+ logger.info(f"Fetched {len(results)} news articles from CryptoCompare")
2142
+ except Exception as api_error:
2143
+ logger.warning(f"External news API failed: {api_error}")
2144
+
2145
  return {
2146
  "success": True,
2147
  "news": results,
2148
+ "count": len(results),
2149
+ "source": "database" if len(results) > 0 and rows else "external_api"
2150
  }
2151
  except Exception as e:
2152
+ logger.error(f"Error in get_news_api: {e}")
2153
  return {
2154
  "success": False,
2155
  "news": [],
 
2706
  raise HTTPException(status_code=500, detail=f"Failed to fetch sentiment history: {str(e)}")
2707
 
2708
 
2709
+ @app.post("/api/news/fetch")
2710
+ async def fetch_and_save_news(limit: int = 50):
2711
+ """Fetch news from CryptoCompare API and save to database"""
2712
+ try:
2713
+ cryptocompare_api_key = os.getenv("CRYPTOCOMPARE_API_KEY", "968a5e25552b4cb5ba3280361d8444ab")
2714
+
2715
+ async with httpx.AsyncClient(timeout=15.0) as client:
2716
+ response = await client.get(
2717
+ "https://min-api.cryptocompare.com/data/v2/news/?lang=EN",
2718
+ headers={
2719
+ "User-Agent": "Mozilla/5.0",
2720
+ "authorization": f"Apikey {cryptocompare_api_key}"
2721
+ }
2722
+ )
2723
+
2724
+ if response.status_code != 200:
2725
+ return {
2726
+ "success": False,
2727
+ "error": f"CryptoCompare API returned {response.status_code}",
2728
+ "saved": 0
2729
+ }
2730
+
2731
+ data = response.json()
2732
+
2733
+ if not data.get("Data"):
2734
+ return {
2735
+ "success": False,
2736
+ "error": "No news data returned from API",
2737
+ "saved": 0
2738
+ }
2739
+
2740
+ # Save to database
2741
+ conn = sqlite3.connect(str(DB_PATH))
2742
+ cursor = conn.cursor()
2743
+ saved_count = 0
2744
+
2745
+ for article in data["Data"][:limit]:
2746
+ try:
2747
+ # Check if article already exists
2748
+ cursor.execute("SELECT id FROM news_articles WHERE url = ?", (article.get("url", ""),))
2749
+ if cursor.fetchone():
2750
+ continue # Skip duplicates
2751
+
2752
+ # Extract related symbols from categories
2753
+ categories = article.get("categories", "").split("|") if article.get("categories") else []
2754
+ related_symbols_json = json.dumps(categories)
2755
+
2756
+ # Insert news article
2757
+ cursor.execute("""
2758
+ INSERT INTO news_articles (
2759
+ title, content, url, source,
2760
+ related_symbols, published_date, analyzed_at
2761
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
2762
+ """, (
2763
+ article.get("title", ""),
2764
+ article.get("body", "")[:1000], # Limit content length
2765
+ article.get("url", ""),
2766
+ article.get("source", "CryptoCompare"),
2767
+ related_symbols_json,
2768
+ datetime.fromtimestamp(article.get("published_on", 0)).isoformat() if article.get("published_on") else None,
2769
+ datetime.now().isoformat()
2770
+ ))
2771
+ saved_count += 1
2772
+ except Exception as e:
2773
+ logger.warning(f"Error saving article: {e}")
2774
+ continue
2775
+
2776
+ conn.commit()
2777
+ conn.close()
2778
+
2779
+ logger.info(f"✅ Saved {saved_count} news articles to database")
2780
+
2781
+ return {
2782
+ "success": True,
2783
+ "saved": saved_count,
2784
+ "total_fetched": len(data["Data"][:limit]),
2785
+ "message": f"Successfully saved {saved_count} news articles"
2786
+ }
2787
+
2788
+ except Exception as e:
2789
+ logger.error(f"Error fetching news: {e}")
2790
+ return {
2791
+ "success": False,
2792
+ "error": str(e),
2793
+ "saved": 0
2794
+ }
2795
+
2796
+
2797
  @app.get("/api/news/latest")
2798
  async def get_latest_news(
2799
  limit: int = 20,
app.py CHANGED
@@ -768,9 +768,10 @@ if __name__ == "__main__":
768
  logger.info(f"🤖 Models available: {len(hub.get_available_models())}")
769
  logger.info(f"🔌 FastAPI available: {FASTAPI_AVAILABLE}")
770
 
 
771
  # Choose mode based on environment variables
772
- if USE_FASTAPI_HTML and FASTAPI_AVAILABLE:
773
- # Run FastAPI with HTML interface
774
  logger.info("🌐 Starting FastAPI server with HTML interface...")
775
  import uvicorn
776
  port = int(os.getenv("PORT", "7860"))
@@ -781,7 +782,7 @@ if __name__ == "__main__":
781
  log_level="info"
782
  )
783
  elif USE_GRADIO:
784
- # Run Gradio interface (default)
785
  logger.info("🎨 Starting Gradio interface...")
786
  app = create_gradio_interface()
787
  app.launch(
@@ -790,17 +791,6 @@ if __name__ == "__main__":
790
  share=False,
791
  show_error=True
792
  )
793
- elif FASTAPI_AVAILABLE:
794
- # Fallback to FastAPI if Gradio is disabled but FastAPI is available
795
- logger.info("🌐 Starting FastAPI server (fallback)...")
796
- import uvicorn
797
- port = int(os.getenv("PORT", "7860"))
798
- uvicorn.run(
799
- fastapi_app,
800
- host="0.0.0.0",
801
- port=port,
802
- log_level="info"
803
- )
804
  else:
805
  # No UI mode available
806
  logger.error("❌ No UI mode available (FastAPI unavailable and Gradio disabled). Exiting.")
 
768
  logger.info(f"🤖 Models available: {len(hub.get_available_models())}")
769
  logger.info(f"🔌 FastAPI available: {FASTAPI_AVAILABLE}")
770
 
771
+ # Force FastAPI+HTML mode for HuggingFace Spaces
772
  # Choose mode based on environment variables
773
+ if FASTAPI_AVAILABLE:
774
+ # Run FastAPI with HTML interface (preferred for HF Spaces)
775
  logger.info("🌐 Starting FastAPI server with HTML interface...")
776
  import uvicorn
777
  port = int(os.getenv("PORT", "7860"))
 
782
  log_level="info"
783
  )
784
  elif USE_GRADIO:
785
+ # Run Gradio interface (fallback)
786
  logger.info("🎨 Starting Gradio interface...")
787
  app = create_gradio_interface()
788
  app.launch(
 
791
  share=False,
792
  show_error=True
793
  )
 
 
 
 
 
 
 
 
 
 
 
794
  else:
795
  # No UI mode available
796
  logger.error("❌ No UI mode available (FastAPI unavailable and Gradio disabled). Exiting.")
index.html CHANGED
@@ -9,16 +9,12 @@
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
10
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
11
 
12
- <!-- Icons -->
13
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
14
-
15
  <!-- Charts -->
16
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
17
 
18
  <!-- Styles -->
19
  <link rel="stylesheet" href="/static/css/main.css">
20
  <link rel="stylesheet" href="/static/css/toast.css">
21
- <link rel="stylesheet" href="/static/css/enhancements.css">
22
  <script src="/static/js/trading-pairs-loader.js" defer></script>
23
  <script src="/static/js/app.js" defer></script>
24
 
@@ -29,488 +25,456 @@
29
  <!-- Toast Container -->
30
  <div class="toast-container" id="toast-container"></div>
31
 
32
- <div class="app-container">
33
- <!-- Header -->
34
- <header class="app-header">
35
- <div class="header-content">
36
- <div class="logo">
37
  <div class="logo-icon">
38
- <i class="fas fa-rocket"></i>
 
 
 
 
39
  </div>
40
  <div class="logo-text">
41
- <h1>Crypto Intelligence Hub</h1>
42
- <p><i class="fas fa-brain"></i> AI-Powered Crypto Data Collection Center</p>
43
  </div>
44
  </div>
45
- <div class="header-actions">
46
- <div class="header-stats">
47
- <div class="mini-stat">
48
- <i class="fas fa-database"></i>
49
- <span id="header-resources">-</span>
50
- <small>Resources</small>
51
- </div>
52
- <div class="mini-stat">
53
- <i class="fas fa-robot"></i>
54
- <span id="header-models">-</span>
55
- <small>Models</small>
56
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
- <div class="status-badge" id="api-status">
59
- <span class="status-dot"></span>
60
- <span>Checking...</span>
 
 
 
 
 
 
61
  </div>
62
- <button class="theme-toggle" onclick="toggleTheme()" title="Toggle Theme">
63
- <i class="fas fa-moon"></i>
64
- </button>
65
  </div>
66
  </div>
67
- </header>
68
 
69
- <!-- Navigation Tabs -->
70
- <nav class="tabs-nav">
71
- <button class="tab-btn active" data-tab="dashboard">
72
- <i class="fas fa-chart-line"></i>
73
- <span>Dashboard</span>
74
- </button>
75
- <button class="tab-btn" data-tab="market">
76
- <i class="fas fa-coins"></i>
77
- <span>Market</span>
78
- </button>
79
- <button class="tab-btn" data-tab="models">
80
- <i class="fas fa-robot"></i>
81
- <span>AI Models</span>
82
- </button>
83
- <button class="tab-btn" data-tab="sentiment">
84
- <i class="fas fa-brain"></i>
85
- <span>Sentiment</span>
86
- </button>
87
- <button class="tab-btn" data-tab="ai-analyst">
88
- <i class="fas fa-magic"></i>
89
- <span>AI Analyst</span>
90
- </button>
91
- <button class="tab-btn" data-tab="trading-assistant">
92
- <i class="fas fa-chart-bar"></i>
93
- <span>Trading Signals</span>
94
- </button>
95
- <button class="tab-btn" data-tab="news">
96
- <i class="fas fa-newspaper"></i>
97
- <span>News</span>
98
- </button>
99
- <button class="tab-btn" data-tab="providers">
100
- <i class="fas fa-server"></i>
101
- <span>Providers</span>
102
- </button>
103
- <button class="tab-btn" data-tab="diagnostics">
104
- <i class="fas fa-stethoscope"></i>
105
- <span>Diagnostics</span>
106
- </button>
107
- <button class="tab-btn" data-tab="api-explorer">
108
- <i class="fas fa-code"></i>
109
- <span>API</span>
110
- </button>
111
- </nav>
112
 
113
- <!-- Main Content -->
114
- <main class="main-content">
115
- <!-- Dashboard Tab -->
116
- <section id="tab-dashboard" class="tab-content active">
117
- <div class="section-header">
118
- <h2>System Overview</h2>
119
- <button class="btn-refresh" onclick="loadDashboard()">🔄 Refresh</button>
 
 
 
 
 
 
 
120
  </div>
121
-
122
- <div class="stats-grid" id="dashboard-stats">
123
- <div class="stat-card gradient-purple">
124
- <div class="stat-icon"><i class="fas fa-database"></i></div>
125
- <div class="stat-content">
126
- <div class="stat-value" id="stat-total-resources">-</div>
127
- <div class="stat-label">Total Resources</div>
128
- <div class="stat-trend"><i class="fas fa-arrow-up"></i> Active</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  </div>
130
- </div>
131
- <div class="stat-card gradient-green">
132
- <div class="stat-icon"><i class="fas fa-gift"></i></div>
133
- <div class="stat-content">
134
- <div class="stat-value" id="stat-free-resources">-</div>
135
- <div class="stat-label">Free Resources</div>
136
- <div class="stat-trend"><i class="fas fa-check-circle"></i> Available</div>
 
 
 
 
 
 
 
 
 
 
 
137
  </div>
138
- </div>
139
- <div class="stat-card gradient-blue">
140
- <div class="stat-icon"><i class="fas fa-robot"></i></div>
141
- <div class="stat-content">
142
- <div class="stat-value" id="stat-models">-</div>
143
- <div class="stat-label">AI Models</div>
144
- <div class="stat-trend"><i class="fas fa-brain"></i> Ready</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  </div>
146
- </div>
147
- <div class="stat-card gradient-orange">
148
- <div class="stat-icon"><i class="fas fa-server"></i></div>
149
- <div class="stat-content">
150
- <div class="stat-value" id="stat-providers">-</div>
151
- <div class="stat-label">Providers</div>
152
- <div class="stat-trend"><i class="fas fa-signal"></i> Online</div>
 
 
 
 
 
 
 
 
 
 
 
153
  </div>
154
  </div>
155
- </div>
156
-
157
- <div class="grid-2">
158
- <div class="card">
159
- <h3>System Status</h3>
160
- <div id="system-status"></div>
161
- </div>
162
- <div class="card">
163
- <h3>Category Statistics</h3>
164
- <canvas id="categories-chart"></canvas>
165
- </div>
166
- </div>
167
- </section>
168
-
169
- <!-- Market Tab -->
170
- <section id="tab-market" class="tab-content">
171
- <div class="section-header">
172
- <h2>Market Data</h2>
173
- <button class="btn-refresh" onclick="loadMarketData()">🔄 Refresh</button>
174
- </div>
175
-
176
- <div class="card">
177
- <h3>Top Cryptocurrencies</h3>
178
- <div id="market-data"></div>
179
- </div>
180
-
181
- <div class="card">
182
- <h3>Trending Coins</h3>
183
- <div id="trending-coins"></div>
184
- </div>
185
-
186
- <div class="card">
187
- <h3>Fear & Greed Index</h3>
188
- <div id="fear-greed"></div>
189
- </div>
190
- </section>
191
 
192
- <!-- Models Tab -->
193
- <section id="tab-models" class="tab-content">
194
- <div class="section-header">
195
- <h2>Hugging Face Models</h2>
196
- <button class="btn-refresh" onclick="loadModels()">🔄 Refresh</button>
197
- <button class="btn-primary" onclick="initializeModels()">🚀 Load Models</button>
198
- </div>
199
-
200
- <div class="card">
201
- <h3>Models Status</h3>
202
- <div id="models-status"></div>
203
- </div>
204
-
205
- <div class="card">
206
- <h3>Models List</h3>
207
- <div id="models-list"></div>
208
- </div>
209
-
210
- <div class="card">
211
- <h3>Usage Statistics</h3>
212
- <div id="models-stats"></div>
213
- </div>
214
- </section>
215
-
216
- <!-- Sentiment Analysis Tab -->
217
- <section id="tab-sentiment" class="tab-content">
218
- <div class="section-header">
219
- <h2>Sentiment Analysis</h2>
220
- </div>
221
-
222
- <!-- Global Market Sentiment -->
223
- <div class="card">
224
- <h3>Global Market Sentiment</h3>
225
- <p style="color: var(--text-secondary); margin-bottom: 15px;">Analyze overall crypto market sentiment using AI models</p>
226
- <button class="btn-primary" onclick="analyzeGlobalSentiment()">📊 Analyze Market Sentiment</button>
227
- <div id="global-sentiment-result" style="margin-top: 20px;"></div>
228
- </div>
229
-
230
- <!-- Per-Asset Sentiment -->
231
- <div class="card">
232
- <h3>Per-Asset Sentiment Analysis</h3>
233
- <div class="form-group">
234
- <label>Trading Pair:</label>
235
- <div id="asset-symbol-container">
236
- <input type="text" id="asset-symbol" placeholder="Loading pairs..." readonly>
237
  </div>
238
  </div>
239
- <div class="form-group">
240
- <label>Related Text or News (Optional):</label>
241
- <textarea id="asset-sentiment-text" rows="3" placeholder="Example: Bitcoin breaks resistance at $50,000"></textarea>
242
- </div>
243
- <button class="btn-primary" onclick="analyzeAssetSentiment()">🔍 Analyze Asset Sentiment</button>
244
- <div id="asset-sentiment-result" style="margin-top: 20px;"></div>
245
- </div>
246
-
247
- <!-- Text Analysis -->
248
- <div class="card">
249
- <h3>Text Analysis</h3>
250
- <div class="form-group">
251
- <label>Text to Analyze:</label>
252
- <textarea id="sentiment-text" rows="5" placeholder="Example: Bitcoin price is rising rapidly!"></textarea>
253
- </div>
254
- <div class="form-group">
255
- <label>Analysis Type:</label>
256
- <select id="sentiment-mode">
257
- <option value="auto">Auto (Crypto)</option>
258
- <option value="crypto">Crypto</option>
259
- <option value="financial">Financial</option>
260
- <option value="social">Social</option>
261
- </select>
262
- </div>
263
- <div class="form-group">
264
- <label>Select Model (Optional):</label>
265
- <select id="sentiment-model"></select>
266
- </div>
267
- <button class="btn-primary" onclick="analyzeSentiment()">🔍 Analyze</button>
268
- <div id="sentiment-result"></div>
269
- </div>
270
-
271
- <!-- News/Financial Sentiment -->
272
- <div class="card">
273
- <h3>News & Financial Sentiment Analysis</h3>
274
- <div class="form-group">
275
- <label>News Title:</label>
276
- <input type="text" id="news-title" placeholder="Example: Bitcoin ETF Approval Expected">
277
- </div>
278
- <div class="form-group">
279
- <label>Content or Description:</label>
280
- <textarea id="news-content" rows="4" placeholder="Full news text or description..."></textarea>
281
  </div>
282
- <button class="btn-primary" onclick="analyzeNewsSentiment()">📰 Analyze News</button>
283
- <div id="news-sentiment-result" style="margin-top: 20px;"></div>
284
- </div>
285
-
286
- <!-- News Summarization -->
287
- <div class="card">
288
- <h3>📝 News Summarization</h3>
289
- <p style="color: var(--text-secondary); margin-bottom: 15px;">
290
- Summarize crypto/financial news using AI-powered Hugging Face model
291
- </p>
292
- <div class="form-group">
293
- <label>News Title:</label>
294
- <input type="text" id="summary-news-title" placeholder="Example: Bitcoin Reaches New All-Time High">
295
  </div>
296
- <div class="form-group">
297
- <label>News Content:</label>
298
- <textarea id="summary-news-content" rows="6" placeholder="Enter the full article text here. The AI will generate a concise summary highlighting the key points..."></textarea>
 
299
  </div>
300
- <button class="btn-primary" onclick="summarizeNews()">✨ Summarize News</button>
301
- <div id="news-summary-result" style="margin-top: 20px;"></div>
302
- </div>
303
-
304
- <div class="card">
305
- <h3>Analysis History</h3>
306
- <div id="sentiment-history"></div>
307
- </div>
308
- </section>
309
 
310
- <!-- AI Analyst Tab -->
311
- <section id="tab-ai-analyst" class="tab-content">
312
- <div class="section-header">
313
- <h2>🪄 AI Crypto Analyst</h2>
314
- <p style="color: var(--text-secondary); margin-top: 10px;">Powered by OpenC/crypto-gpt-o3-mini</p>
315
- </div>
316
-
317
- <div class="card">
318
- <h3>Text Generation & Analysis</h3>
319
- <p style="color: var(--text-secondary); margin-bottom: 20px;">
320
- Use AI to generate crypto market analysis, insights, or creative content related to crypto and DeFi.
321
- </p>
322
-
323
- <div class="form-group">
324
- <label>Prompt / Question:</label>
325
- <textarea id="ai-analyst-prompt" rows="6" placeholder="Example: Analyze the current state of Bitcoin and explain potential price movements based on market sentiment..."></textarea>
326
  </div>
327
 
328
- <div class="form-group">
329
- <label>Mode:</label>
330
- <select id="ai-analyst-mode">
331
- <option value="analysis">Analysis</option>
332
- <option value="generation">Generation</option>
333
- </select>
334
  </div>
335
-
336
- <div class="form-group">
337
- <label>Max Length (tokens):</label>
338
- <select id="ai-analyst-max-length">
339
- <option value="100">100</option>
340
- <option value="200" selected>200</option>
341
- <option value="300">300</option>
342
- <option value="500">500</option>
343
- </select>
 
 
 
 
 
 
 
 
344
  </div>
345
 
346
- <button class="btn-primary" onclick="runAIAnalyst()">✨ Generate Analysis</button>
347
-
348
- <div id="ai-analyst-result" style="margin-top: 20px;"></div>
349
- </div>
350
-
351
- <div class="card">
352
- <h3>💡 Example Prompts</h3>
353
- <div style="display: grid; gap: 10px;">
354
- <div style="padding: 10px; background: rgba(31, 41, 55, 0.6); border-radius: 8px; cursor: pointer;" onclick="setAIAnalystPrompt('Analyze the current DeFi market trends and identify key opportunities.')">
355
- <strong>DeFi Market Analysis</strong>
356
- <p style="font-size: 12px; color: var(--text-secondary); margin-top: 5px;">Analyze the current DeFi market trends and identify key opportunities.</p>
357
- </div>
358
- <div style="padding: 10px; background: rgba(31, 41, 55, 0.6); border-radius: 8px; cursor: pointer;" onclick="setAIAnalystPrompt('Explain how Layer 2 solutions are impacting Ethereum scalability.')">
359
- <strong>Layer 2 Solutions</strong>
360
- <p style="font-size: 12px; color: var(--text-secondary); margin-top: 5px;">Explain how Layer 2 solutions are impacting Ethereum scalability.</p>
361
  </div>
362
- <div style="padding: 10px; background: rgba(31, 41, 55, 0.6); border-radius: 8px; cursor: pointer;" onclick="setAIAnalystPrompt('What are the key factors affecting Bitcoin price movements?')">
363
- <strong>Bitcoin Price Factors</strong>
364
- <p style="font-size: 12px; color: var(--text-secondary); margin-top: 5px;">What are the key factors affecting Bitcoin price movements?</p>
365
  </div>
 
 
 
 
 
 
 
 
366
  </div>
367
- </div>
368
- </section>
369
-
370
- <!-- Trading Assistant Tab -->
371
- <section id="tab-trading-assistant" class="tab-content">
372
- <div class="section-header">
373
- <h2>📊 Trading Signal Assistant</h2>
374
- <p style="color: var(--text-secondary); margin-top: 10px;">Powered by agarkovv/CryptoTrader-LM</p>
375
- </div>
376
-
377
- <div class="card">
378
- <h3>Get Trading Decision</h3>
379
- <p style="color: var(--text-secondary); margin-bottom: 20px;">
380
- Get AI-powered trading signals (BUY/SELL/HOLD) based on market analysis.
381
- </p>
382
 
383
- <div class="form-group">
384
- <label>Trading Symbol:</label>
385
- <div id="trading-symbol-container">
386
- <input type="text" id="trading-symbol" placeholder="Loading pairs..." readonly>
 
 
387
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  </div>
389
-
390
- <div class="form-group">
391
- <label>Market Context (Optional):</label>
392
- <textarea id="trading-context" rows="4" placeholder="Example: Recent breakout above resistance, high volume, positive news flow..."></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  </div>
394
 
395
- <button class="btn-primary" onclick="runTradingAssistant()">🎯 Get Trading Signal</button>
396
-
397
- <div id="trading-assistant-result" style="margin-top: 20px;"></div>
398
- </div>
399
-
400
- <div class="card">
401
- <h3>⚠️ Disclaimer</h3>
402
- <div class="alert alert-warning">
403
- <strong>Important:</strong> This trading assistant provides AI-generated signals for informational purposes only.
404
- It should NOT be used as the sole basis for trading decisions. Always conduct your own research,
405
- consider multiple factors, and consult with financial professionals before making investment decisions.
406
- Past performance does not guarantee future results.
407
  </div>
408
- </div>
409
- </section>
410
-
411
- <!-- News Tab -->
412
- <section id="tab-news" class="tab-content">
413
- <div class="section-header">
414
- <h2>Crypto News</h2>
415
- <button class="btn-refresh" onclick="loadNews()">🔄 Refresh</button>
416
- </div>
417
-
418
- <div id="news-list"></div>
419
- </section>
420
 
421
- <!-- Providers Tab -->
422
- <section id="tab-providers" class="tab-content">
423
- <div class="section-header">
424
- <h2>Resources & Providers</h2>
425
- <button class="btn-refresh" onclick="loadProviders()">🔄 Refresh</button>
426
- </div>
427
-
428
- <div class="card">
429
- <h3>Providers List</h3>
430
- <div id="providers-list"></div>
431
- </div>
432
-
433
- <div class="card">
434
- <h3>Search Resources</h3>
435
- <div class="form-group">
436
- <input type="text" id="search-resources" placeholder="Search..." onkeypress="if(event.key === 'Enter') searchResources()">
437
- <button class="btn-primary" onclick="searchResources()">🔍 Search</button>
 
 
 
 
 
 
 
 
438
  </div>
439
- <div id="search-results"></div>
440
- </div>
441
- </section>
442
 
443
- <!-- Diagnostics Tab -->
444
- <section id="tab-diagnostics" class="tab-content">
445
- <div class="section-header">
446
- <h2>Diagnostics & Reports</h2>
447
- <button class="btn-primary" onclick="runDiagnostics()">▶️ Run Diagnostics</button>
448
- </div>
449
-
450
- <div class="card">
451
- <h3>System Health</h3>
452
- <button class="btn-refresh" onclick="loadHealthDiagnostics()">🔄 Refresh Health</button>
453
- <div id="health-diagnostics-result" style="margin-top: 15px;"></div>
454
- </div>
455
-
456
- <div class="card">
457
- <h3>System Status</h3>
458
- <div id="diagnostics-status"></div>
459
- </div>
460
-
461
- <div class="card">
462
- <h3>Error Reports</h3>
463
- <div id="error-logs"></div>
464
- </div>
465
-
466
- <div class="card">
467
- <h3>Recent Logs</h3>
468
- <div id="recent-logs"></div>
469
- </div>
470
- </section>
471
-
472
- <!-- API Explorer Tab -->
473
- <section id="tab-api-explorer" class="tab-content">
474
- <div class="section-header">
475
- <h2>API Explorer</h2>
476
- </div>
477
-
478
- <div class="card">
479
- <h3>Test API Endpoints</h3>
480
- <div class="form-group">
481
- <label>Select Endpoint:</label>
482
- <select id="api-endpoint">
483
- <option value="/api/health">GET /api/health</option>
484
- <option value="/api/status">GET /api/status</option>
485
- <option value="/api/market">GET /api/market</option>
486
- <option value="/api/sentiment">GET /api/sentiment</option>
487
- <option value="/api/resources">GET /api/resources</option>
488
- <option value="/api/providers">GET /api/providers</option>
489
- <option value="/api/models/list">GET /api/models/list</option>
490
- </select>
491
- </div>
492
- <div class="form-group">
493
- <label>Method:</label>
494
- <select id="api-method">
495
- <option value="GET">GET</option>
496
- <option value="POST">POST</option>
497
- </select>
498
  </div>
499
- <div class="form-group">
500
- <label>Body (JSON - for POST):</label>
501
- <textarea id="api-body" rows="5"></textarea>
 
502
  </div>
503
- <button class="btn-primary" onclick="testAPI()">▶️ Execute</button>
504
- <div id="api-result"></div>
505
- </div>
506
- </section>
507
- </main>
508
-
509
- <!-- Footer -->
510
- <footer class="app-footer">
511
- <p>Crypto Intelligence Hub - Powered by FastAPI & Hugging Face</p>
512
- <p><a href="/docs" target="_blank">📖 API Documentation</a> | <a href="/health" target="_blank">💚 System Health</a></p>
513
- </footer>
514
  </div>
515
  </body>
516
  </html>
 
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
10
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
11
 
 
 
 
12
  <!-- Charts -->
13
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
14
 
15
  <!-- Styles -->
16
  <link rel="stylesheet" href="/static/css/main.css">
17
  <link rel="stylesheet" href="/static/css/toast.css">
 
18
  <script src="/static/js/trading-pairs-loader.js" defer></script>
19
  <script src="/static/js/app.js" defer></script>
20
 
 
25
  <!-- Toast Container -->
26
  <div class="toast-container" id="toast-container"></div>
27
 
28
+ <div class="app-layout">
29
+ <!-- Sidebar Navigation -->
30
+ <aside class="sidebar" id="sidebar">
31
+ <div class="sidebar-header">
32
+ <div class="sidebar-logo">
33
  <div class="logo-icon">
34
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
35
+ <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
36
+ <path d="M2 17l10 5 10-5"></path>
37
+ <path d="M2 12l10 5 10-5"></path>
38
+ </svg>
39
  </div>
40
  <div class="logo-text">
41
+ <h1>Crypto Hub</h1>
42
+ <p>AI-Powered</p>
43
  </div>
44
  </div>
45
+ <button class="sidebar-toggle-btn" id="sidebar-close" onclick="toggleSidebar()">
46
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
47
+ <line x1="18" y1="6" x2="6" y2="18"></line>
48
+ <line x1="6" y1="6" x2="18" y2="18"></line>
49
+ </svg>
50
+ </button>
51
+ </div>
52
+
53
+ <nav class="sidebar-nav">
54
+ <button class="nav-item active" data-tab="dashboard" onclick="switchTab('dashboard')">
55
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
56
+ <line x1="18" y1="20" x2="18" y2="10"></line>
57
+ <line x1="12" y1="20" x2="12" y2="4"></line>
58
+ <line x1="6" y1="20" x2="6" y2="14"></line>
59
+ </svg>
60
+ <span>Dashboard</span>
61
+ </button>
62
+ <button class="nav-item" data-tab="market" onclick="switchTab('market')">
63
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
64
+ <circle cx="12" cy="12" r="10"></circle>
65
+ <path d="M12 6v6l4 2"></path>
66
+ </svg>
67
+ <span>Market</span>
68
+ </button>
69
+ <button class="nav-item" data-tab="models" onclick="switchTab('models')">
70
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
71
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
72
+ <rect x="7" y="7" width="3" height="3"></rect>
73
+ <rect x="14" y="7" width="3" height="3"></rect>
74
+ <rect x="7" y="14" width="3" height="3"></rect>
75
+ <rect x="14" y="14" width="3" height="3"></rect>
76
+ </svg>
77
+ <span>AI Models</span>
78
+ </button>
79
+ <button class="nav-item" data-tab="sentiment" onclick="switchTab('sentiment')">
80
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
81
+ <path d="M9 11a3 3 0 1 0 6 0 3 3 0 0 0-6 0z"></path>
82
+ <path d="M17.5 11a2.5 2.5 0 1 0 5 0 2.5 2.5 0 0 0-5 0z"></path>
83
+ <path d="M6.5 11a2.5 2.5 0 1 0 5 0 2.5 2.5 0 0 0-5 0z"></path>
84
+ </svg>
85
+ <span>Sentiment</span>
86
+ </button>
87
+ <button class="nav-item" data-tab="trading-assistant" onclick="switchTab('trading-assistant')">
88
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
89
+ <line x1="12" y1="1" x2="12" y2="23"></line>
90
+ <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
91
+ </svg>
92
+ <span>Trading Signals</span>
93
+ </button>
94
+ <button class="nav-item" data-tab="news" onclick="switchTab('news')">
95
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
96
+ <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
97
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
98
+ <line x1="9" y1="8" x2="15" y2="8"></line>
99
+ <line x1="9" y1="12" x2="15" y2="12"></line>
100
+ </svg>
101
+ <span>News</span>
102
+ </button>
103
+ <button class="nav-item" data-tab="settings" onclick="switchTab('settings')">
104
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
105
+ <circle cx="12" cy="12" r="3"></circle>
106
+ <path d="M12 1v6m0 6v6m9-9h-6m-6 0H3m15.364 6.364l-4.243-4.243m-4.242 0L5.636 17.364m12.728 0l-4.243-4.243m-4.242 0L5.636 6.636"></path>
107
+ </svg>
108
+ <span>Settings</span>
109
+ </button>
110
+ </nav>
111
+
112
+ <div class="sidebar-footer">
113
+ <div class="status-indicator">
114
+ <span class="status-dot"></span>
115
+ <span id="sidebar-status">System Active</span>
116
+ </div>
117
+ <div class="sidebar-stats">
118
+ <div class="stat-mini">
119
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
120
+ <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
121
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
122
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
123
+ </svg>
124
+ <span id="sidebar-resources">-</span>
125
  </div>
126
+ <div class="stat-mini">
127
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
128
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
129
+ <rect x="7" y="7" width="3" height="3"></rect>
130
+ <rect x="14" y="7" width="3" height="3"></rect>
131
+ <rect x="7" y="14" width="3" height="3"></rect>
132
+ <rect x="14" y="14" width="3" height="3"></rect>
133
+ </svg>
134
+ <span id="sidebar-models">-</span>
135
  </div>
 
 
 
136
  </div>
137
  </div>
138
+ </aside>
139
 
140
+ <!-- Mobile Overlay -->
141
+ <div class="sidebar-overlay" id="sidebar-overlay" onclick="toggleSidebar()"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
+ <!-- Main Content Area -->
144
+ <div class="main-wrapper">
145
+ <!-- Top Header -->
146
+ <header class="top-header">
147
+ <button class="hamburger-btn" id="hamburger-btn" onclick="toggleSidebar()">
148
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
149
+ <line x1="3" y1="12" x2="21" y2="12"></line>
150
+ <line x1="3" y1="6" x2="21" y2="6"></line>
151
+ <line x1="3" y1="18" x2="21" y2="18"></line>
152
+ </svg>
153
+ </button>
154
+ <div class="header-title">
155
+ <h2 id="page-title">Dashboard</h2>
156
+ <p id="page-subtitle">System Overview</p>
157
  </div>
158
+ <div class="header-actions">
159
+ <button class="icon-btn" onclick="toggleTheme()" title="Toggle Theme">
160
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
161
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
162
+ </svg>
163
+ </button>
164
+ <button class="icon-btn" onclick="refreshCurrentTab()" title="Refresh">
165
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
166
+ <polyline points="23 4 23 10 17 10"></polyline>
167
+ <polyline points="1 20 1 14 7 14"></polyline>
168
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
169
+ </svg>
170
+ </button>
171
+ </div>
172
+ </header>
173
+
174
+ <!-- Content Area -->
175
+ <main class="content-area">
176
+ <!-- Dashboard Tab -->
177
+ <section id="tab-dashboard" class="tab-panel active">
178
+ <div class="stats-grid" id="dashboard-stats">
179
+ <div class="stat-card gradient-purple">
180
+ <div class="stat-icon">
181
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
182
+ <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
183
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
184
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
185
+ </svg>
186
+ </div>
187
+ <div class="stat-content">
188
+ <div class="stat-value" id="stat-total-resources">-</div>
189
+ <div class="stat-label">Total Resources</div>
190
+ <div class="stat-trend">
191
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
192
+ <polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
193
+ <polyline points="17 6 23 6 23 12"></polyline>
194
+ </svg>
195
+ Active
196
+ </div>
197
+ </div>
198
  </div>
199
+ <div class="stat-card gradient-green">
200
+ <div class="stat-icon">
201
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
202
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
203
+ <polyline points="22 4 12 14.01 9 11.01"></polyline>
204
+ </svg>
205
+ </div>
206
+ <div class="stat-content">
207
+ <div class="stat-value" id="stat-free-resources">-</div>
208
+ <div class="stat-label">Free Resources</div>
209
+ <div class="stat-trend">
210
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
211
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
212
+ <polyline points="22 4 12 14.01 9 11.01"></polyline>
213
+ </svg>
214
+ Available
215
+ </div>
216
+ </div>
217
  </div>
218
+ <div class="stat-card gradient-blue">
219
+ <div class="stat-icon">
220
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
221
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
222
+ <rect x="7" y="7" width="3" height="3"></rect>
223
+ <rect x="14" y="7" width="3" height="3"></rect>
224
+ <rect x="7" y="14" width="3" height="3"></rect>
225
+ <rect x="14" y="14" width="3" height="3"></rect>
226
+ </svg>
227
+ </div>
228
+ <div class="stat-content">
229
+ <div class="stat-value" id="stat-models">-</div>
230
+ <div class="stat-label">AI Models</div>
231
+ <div class="stat-trend">
232
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
233
+ <path d="M9 11a3 3 0 1 0 6 0 3 3 0 0 0-6 0z"></path>
234
+ <path d="M17.5 11a2.5 2.5 0 1 0 5 0 2.5 2.5 0 0 0-5 0z"></path>
235
+ <path d="M6.5 11a2.5 2.5 0 1 0 5 0 2.5 2.5 0 0 0-5 0z"></path>
236
+ </svg>
237
+ Ready
238
+ </div>
239
+ </div>
240
  </div>
241
+ <div class="stat-card gradient-orange">
242
+ <div class="stat-icon">
243
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
244
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
245
+ <line x1="8" y1="21" x2="16" y2="21"></line>
246
+ <line x1="12" y1="17" x2="12" y2="21"></line>
247
+ </svg>
248
+ </div>
249
+ <div class="stat-content">
250
+ <div class="stat-value" id="stat-providers">-</div>
251
+ <div class="stat-label">Providers</div>
252
+ <div class="stat-trend">
253
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
254
+ <path d="M2 12h20M12 2v20"></path>
255
+ </svg>
256
+ Online
257
+ </div>
258
+ </div>
259
  </div>
260
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
+ <div class="grid-2">
263
+ <div class="glass-card">
264
+ <h3>System Status</h3>
265
+ <div id="system-status"></div>
266
+ </div>
267
+ <div class="glass-card">
268
+ <h3>Category Statistics</h3>
269
+ <canvas id="categories-chart"></canvas>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  </div>
271
  </div>
272
+ </section>
273
+
274
+ <!-- Market Tab -->
275
+ <section id="tab-market" class="tab-panel">
276
+ <div class="glass-card">
277
+ <h3>Top Cryptocurrencies</h3>
278
+ <div id="market-data"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  </div>
280
+
281
+ <div class="glass-card">
282
+ <h3>Trending Coins</h3>
283
+ <div id="trending-coins"></div>
 
 
 
 
 
 
 
 
 
284
  </div>
285
+
286
+ <div class="glass-card">
287
+ <h3>Fear & Greed Index</h3>
288
+ <div id="fear-greed"></div>
289
  </div>
290
+ </section>
 
 
 
 
 
 
 
 
291
 
292
+ <!-- Models Tab -->
293
+ <section id="tab-models" class="tab-panel">
294
+ <div class="glass-card">
295
+ <div class="card-header">
296
+ <h3>Hugging Face Models</h3>
297
+ <button class="btn-primary" onclick="initializeModels()">
298
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
299
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
300
+ </svg>
301
+ Load Models
302
+ </button>
303
+ </div>
304
+ <div id="models-status"></div>
 
 
 
305
  </div>
306
 
307
+ <div class="glass-card">
308
+ <h3>Models List</h3>
309
+ <div id="models-list"></div>
 
 
 
310
  </div>
311
+ </section>
312
+
313
+ <!-- Sentiment Analysis Tab -->
314
+ <section id="tab-sentiment" class="tab-panel">
315
+ <!-- Global Market Sentiment -->
316
+ <div class="glass-card">
317
+ <h3>Global Market Sentiment</h3>
318
+ <p class="text-secondary">Analyze overall crypto market sentiment using AI models</p>
319
+ <button class="btn-primary" onclick="analyzeGlobalSentiment()">
320
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
321
+ <line x1="18" y1="20" x2="18" y2="10"></line>
322
+ <line x1="12" y1="20" x2="12" y2="4"></line>
323
+ <line x1="6" y1="20" x2="6" y2="14"></line>
324
+ </svg>
325
+ Analyze Market Sentiment
326
+ </button>
327
+ <div id="global-sentiment-result" style="margin-top: 20px;"></div>
328
  </div>
329
 
330
+ <!-- Per-Asset Sentiment -->
331
+ <div class="glass-card">
332
+ <h3>Per-Asset Sentiment Analysis</h3>
333
+ <div class="form-group">
334
+ <label>Trading Pair:</label>
335
+ <div id="asset-symbol-container">
336
+ <input type="text" id="asset-symbol" placeholder="Loading pairs..." readonly>
337
+ </div>
 
 
 
 
 
 
 
338
  </div>
339
+ <div class="form-group">
340
+ <label>Related Text or News (Optional):</label>
341
+ <textarea id="asset-sentiment-text" rows="3" placeholder="Example: Bitcoin breaks resistance at $50,000"></textarea>
342
  </div>
343
+ <button class="btn-primary" onclick="analyzeAssetSentiment()">
344
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
345
+ <circle cx="11" cy="11" r="8"></circle>
346
+ <path d="M21 21l-4.35-4.35"></path>
347
+ </svg>
348
+ Analyze Asset Sentiment
349
+ </button>
350
+ <div id="asset-sentiment-result" style="margin-top: 20px;"></div>
351
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
+ <!-- Text Analysis -->
354
+ <div class="glass-card">
355
+ <h3>Text Analysis</h3>
356
+ <div class="form-group">
357
+ <label>Text to Analyze:</label>
358
+ <textarea id="sentiment-text" rows="5" placeholder="Example: Bitcoin price is rising rapidly!"></textarea>
359
  </div>
360
+ <div class="form-group">
361
+ <label>Analysis Type:</label>
362
+ <select id="sentiment-mode">
363
+ <option value="auto">Auto (Crypto)</option>
364
+ <option value="crypto">Crypto</option>
365
+ <option value="financial">Financial</option>
366
+ <option value="social">Social</option>
367
+ </select>
368
+ </div>
369
+ <button class="btn-primary" onclick="analyzeSentiment()">
370
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
371
+ <circle cx="11" cy="11" r="8"></circle>
372
+ <path d="M21 21l-4.35-4.35"></path>
373
+ </svg>
374
+ Analyze
375
+ </button>
376
+ <div id="sentiment-result"></div>
377
  </div>
378
+ </section>
379
+
380
+ <!-- Trading Assistant Tab -->
381
+ <section id="tab-trading-assistant" class="tab-panel">
382
+ <div class="glass-card">
383
+ <h3>Trading Signal Assistant</h3>
384
+ <p class="text-secondary">Get AI-powered trading signals (BUY/SELL/HOLD) based on market analysis.</p>
385
+
386
+ <div class="form-group">
387
+ <label>Trading Symbol:</label>
388
+ <div id="trading-symbol-container">
389
+ <input type="text" id="trading-symbol" placeholder="Loading pairs..." readonly>
390
+ </div>
391
+ </div>
392
+
393
+ <div class="form-group">
394
+ <label>Market Context (Optional):</label>
395
+ <textarea id="trading-context" rows="4" placeholder="Example: Recent breakout above resistance, high volume, positive news flow..."></textarea>
396
+ </div>
397
+
398
+ <button class="btn-primary" onclick="runTradingAssistant()">
399
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
400
+ <circle cx="12" cy="12" r="10"></circle>
401
+ <polygon points="12 6 16 14 8 14"></polygon>
402
+ </svg>
403
+ Get Trading Signal
404
+ </button>
405
+
406
+ <div id="trading-assistant-result" style="margin-top: 20px;"></div>
407
  </div>
408
 
409
+ <div class="glass-card">
410
+ <h3>⚠️ Disclaimer</h3>
411
+ <div class="alert alert-warning">
412
+ <strong>Important:</strong> This trading assistant provides AI-generated signals for informational purposes only.
413
+ It should NOT be used as the sole basis for trading decisions. Always conduct your own research.
414
+ </div>
 
 
 
 
 
 
415
  </div>
416
+ </section>
 
 
 
 
 
 
 
 
 
 
 
417
 
418
+ <!-- News Tab -->
419
+ <section id="tab-news" class="tab-panel">
420
+ <div class="glass-card">
421
+ <div class="card-header">
422
+ <h3>Crypto News</h3>
423
+ <div style="display: flex; gap: 10px;">
424
+ <button class="btn-primary" onclick="fetchNewsFromAPI()">
425
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
426
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
427
+ <polyline points="7 10 12 15 17 10"></polyline>
428
+ <line x1="12" y1="15" x2="12" y2="3"></line>
429
+ </svg>
430
+ Fetch Latest News
431
+ </button>
432
+ <button class="btn-refresh" onclick="loadNews()">
433
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
434
+ <polyline points="23 4 23 10 17 10"></polyline>
435
+ <polyline points="1 20 1 14 7 14"></polyline>
436
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
437
+ </svg>
438
+ Refresh
439
+ </button>
440
+ </div>
441
+ </div>
442
+ <div id="news-list"></div>
443
  </div>
444
+ </section>
 
 
445
 
446
+ <!-- Settings Tab -->
447
+ <section id="tab-settings" class="tab-panel">
448
+ <div class="glass-card">
449
+ <h3>System Settings</h3>
450
+ <div class="form-group">
451
+ <label>Theme:</label>
452
+ <select id="theme-select" onchange="changeTheme(this.value)">
453
+ <option value="dark">Dark Mode</option>
454
+ <option value="light">Light Mode</option>
455
+ </select>
456
+ </div>
457
+ <div class="form-group">
458
+ <label>Auto Refresh Interval (seconds):</label>
459
+ <input type="number" id="refresh-interval" value="30" min="10" max="300">
460
+ </div>
461
+ <button class="btn-primary" onclick="saveSettings()">
462
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
463
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
464
+ <polyline points="17 21 17 13 7 13 7 21"></polyline>
465
+ <polyline points="7 3 7 8 15 8"></polyline>
466
+ </svg>
467
+ Save Settings
468
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  </div>
470
+
471
+ <div class="glass-card">
472
+ <h3>API Information</h3>
473
+ <div id="api-info"></div>
474
  </div>
475
+ </section>
476
+ </main>
477
+ </div>
 
 
 
 
 
 
 
 
478
  </div>
479
  </body>
480
  </html>
static/css/main.css CHANGED
@@ -1,4 +1,4 @@
1
- /* Crypto Intelligence Hub - Enhanced Stylesheet */
2
 
3
  :root {
4
  /* Primary Colors */
@@ -32,6 +32,10 @@
32
  --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.4);
33
  --glow: 0 0 20px rgba(102, 126, 234, 0.3);
34
 
 
 
 
 
35
  /* Gradients */
36
  --gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
37
  --gradient-blue: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
@@ -77,48 +81,60 @@ body::before {
77
  z-index: 0;
78
  }
79
 
80
- .app-container {
81
- max-width: 1920px;
82
- margin: 0 auto;
83
- min-height: 100vh;
 
84
  display: flex;
85
- flex-direction: column;
86
  position: relative;
87
  z-index: 1;
88
  }
89
 
90
- /* Header */
91
- .app-header {
92
- background: linear-gradient(135deg, rgba(17, 24, 39, 0.9) 0%, rgba(31, 41, 55, 0.7) 100%);
 
 
 
 
93
  backdrop-filter: blur(20px);
94
- border-bottom: 1px solid var(--border);
95
- padding: 20px 30px;
 
 
 
 
 
 
 
96
  box-shadow: var(--shadow);
97
  }
98
 
99
- .header-content {
 
 
100
  display: flex;
101
  justify-content: space-between;
102
  align-items: center;
103
- flex-wrap: wrap;
104
- gap: 20px;
105
  }
106
 
107
- .logo {
108
  display: flex;
109
  align-items: center;
110
- gap: 15px;
111
  }
112
 
113
- .logo-icon {
114
- width: 60px;
115
- height: 60px;
116
  background: var(--gradient-purple);
117
- border-radius: 16px;
118
  display: flex;
119
  align-items: center;
120
  justify-content: center;
121
- font-size: 28px;
122
  color: white;
123
  box-shadow: var(--glow);
124
  animation: float 3s ease-in-out infinite;
@@ -129,30 +145,102 @@ body::before {
129
  50% { transform: translateY(-5px); }
130
  }
131
 
132
- .logo-text h1 {
133
- font-size: 28px;
134
  font-weight: 800;
135
  background: linear-gradient(135deg, var(--primary), var(--secondary));
136
  -webkit-background-clip: text;
137
  -webkit-text-fill-color: transparent;
138
  background-clip: text;
 
139
  }
140
 
141
- .logo-text p {
142
- font-size: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  color: var(--text-secondary);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  }
145
 
146
- .status-badge {
147
  display: flex;
148
  align-items: center;
149
- gap: 8px;
150
- padding: 10px 20px;
151
  background: rgba(16, 185, 129, 0.15);
152
  border: 1px solid rgba(16, 185, 129, 0.3);
153
- border-radius: 12px;
154
- font-size: 14px;
155
  font-weight: 600;
 
156
  }
157
 
158
  .status-dot {
@@ -168,61 +256,162 @@ body::before {
168
  50% { opacity: 0.5; transform: scale(1.2); }
169
  }
170
 
171
- .status-badge.error .status-dot {
172
- background: var(--danger);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  }
174
 
175
- .status-badge.warning .status-dot {
176
- background: var(--warning);
 
177
  }
178
 
179
- /* Navigation Tabs */
180
- .tabs-nav {
 
 
 
 
 
181
  display: flex;
182
- gap: 10px;
183
- padding: 20px 30px;
184
- background: rgba(17, 24, 39, 0.5);
 
 
 
 
 
 
185
  border-bottom: 1px solid var(--border);
186
- overflow-x: auto;
 
 
 
 
 
 
 
187
  }
188
 
189
- .tab-btn {
190
- padding: 12px 24px;
191
- background: transparent;
192
- border: 1px solid var(--border);
 
 
193
  border-radius: 10px;
194
- color: var(--text-secondary);
195
  cursor: pointer;
196
- font-size: 14px;
197
- font-weight: 600;
198
- transition: all 0.3s;
199
- white-space: nowrap;
200
  }
201
 
202
- .tab-btn:hover {
203
- background: rgba(102, 126, 234, 0.1);
204
- border-color: var(--primary);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  color: var(--text-primary);
 
 
 
 
 
 
206
  }
207
 
208
- .tab-btn.active {
209
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
210
  border-color: var(--primary);
211
- color: white;
212
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
213
  }
214
 
215
- /* Main Content */
216
- .main-content {
 
 
 
217
  flex: 1;
218
  padding: 30px;
 
219
  }
220
 
221
- .tab-content {
222
  display: none;
223
  }
224
 
225
- .tab-content.active {
226
  display: block;
227
  animation: fadeIn 0.3s;
228
  }
@@ -232,22 +421,44 @@ body::before {
232
  to { opacity: 1; transform: translateY(0); }
233
  }
234
 
235
- .section-header {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  display: flex;
237
  justify-content: space-between;
238
  align-items: center;
239
- margin-bottom: 30px;
240
- flex-wrap: wrap;
241
- gap: 15px;
242
  }
243
 
244
- .section-header h2 {
245
- font-size: 28px;
246
- font-weight: 700;
247
- background: linear-gradient(135deg, var(--primary), var(--secondary));
248
- -webkit-background-clip: text;
249
- -webkit-text-fill-color: transparent;
250
- background-clip: text;
251
  }
252
 
253
  /* Stats Grid */
@@ -263,83 +474,128 @@ body::before {
263
  border: 1px solid var(--border);
264
  border-radius: 16px;
265
  padding: 25px;
266
- text-align: center;
267
- transition: all 0.3s;
 
 
268
  backdrop-filter: blur(10px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  }
270
 
271
  .stat-card:hover {
272
  transform: translateY(-5px);
273
  box-shadow: var(--shadow);
274
- border-color: var(--primary);
275
- }
276
-
277
- .stat-icon {
278
- font-size: 40px;
279
- margin-bottom: 10px;
280
  }
281
 
282
- .stat-value {
283
- font-size: 36px;
284
- font-weight: 800;
285
- color: var(--primary);
286
- margin-bottom: 5px;
287
  }
288
 
289
- .stat-label {
290
- font-size: 14px;
291
- color: var(--text-secondary);
292
- font-weight: 600;
293
  }
294
 
295
- /* Cards */
296
- .card {
297
- background: rgba(17, 24, 39, 0.6);
298
- border: 1px solid var(--border);
299
- border-radius: 16px;
300
- padding: 25px;
301
- margin-bottom: 20px;
302
- backdrop-filter: blur(10px);
303
  }
304
 
305
- .card h3 {
306
- font-size: 20px;
307
- margin-bottom: 20px;
308
- color: var(--text-primary);
309
- border-bottom: 2px solid var(--border);
310
- padding-bottom: 10px;
311
  }
312
 
313
- .grid-2 {
314
- display: grid;
315
- grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
316
- gap: 20px;
317
  }
318
 
319
- /* Buttons */
320
- .btn-primary, .btn-refresh {
321
- padding: 12px 24px;
322
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
323
- border: none;
324
- border-radius: 10px;
 
 
 
 
 
 
 
325
  color: white;
326
- font-weight: 600;
327
- cursor: pointer;
328
- transition: all 0.3s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  font-size: 14px;
 
 
330
  }
331
 
332
- .btn-primary:hover, .btn-refresh:hover {
333
- transform: translateY(-2px);
334
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
 
 
 
 
335
  }
336
 
337
- .btn-refresh {
338
- background: rgba(102, 126, 234, 0.2);
339
- border: 1px solid var(--primary);
 
 
 
 
 
 
340
  }
341
 
342
- /* Forms */
 
 
 
343
  .form-group {
344
  margin-bottom: 20px;
345
  }
@@ -362,6 +618,7 @@ body::before {
362
  color: var(--text-primary);
363
  font-family: inherit;
364
  font-size: 14px;
 
365
  }
366
 
367
  .form-group input:focus,
@@ -372,7 +629,61 @@ body::before {
372
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
373
  }
374
 
375
- /* Tables */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  table {
377
  width: 100%;
378
  border-collapse: collapse;
@@ -381,7 +692,7 @@ table {
381
  table th,
382
  table td {
383
  padding: 12px;
384
- text-align: right;
385
  border-bottom: 1px solid var(--border);
386
  }
387
 
@@ -395,29 +706,10 @@ table tr:hover {
395
  background: rgba(102, 126, 234, 0.05);
396
  }
397
 
398
- /* Loading */
399
- .loading {
400
- text-align: center;
401
- padding: 40px;
402
- color: var(--text-secondary);
403
- }
404
-
405
- .spinner {
406
- border: 3px solid var(--border);
407
- border-top: 3px solid var(--primary);
408
- border-radius: 50%;
409
- width: 40px;
410
- height: 40px;
411
- animation: spin 1s linear infinite;
412
- margin: 0 auto;
413
- }
414
-
415
- @keyframes spin {
416
- 0% { transform: rotate(0deg); }
417
- 100% { transform: rotate(360deg); }
418
- }
419
 
420
- /* Alerts */
421
  .alert {
422
  padding: 15px;
423
  border-radius: 10px;
@@ -442,583 +734,525 @@ table tr:hover {
442
  color: var(--warning);
443
  }
444
 
445
- /* Footer */
446
- .app-footer {
447
- background: rgba(17, 24, 39, 0.8);
448
- border-top: 1px solid var(--border);
449
- padding: 20px 30px;
450
- text-align: center;
451
- color: var(--text-secondary);
452
- }
453
-
454
- .app-footer a {
455
- color: var(--primary);
456
- text-decoration: none;
457
- margin: 0 10px;
458
- }
459
-
460
- .app-footer a:hover {
461
- text-decoration: underline;
462
- }
463
-
464
- /* Sentiment Badges */
465
- .sentiment-badge {
466
- display: inline-block;
467
- padding: 6px 12px;
468
- border-radius: 8px;
469
- font-size: 13px;
470
- font-weight: 600;
471
- margin: 5px 5px 5px 0;
472
- }
473
-
474
- .sentiment-badge.bullish {
475
- background: rgba(16, 185, 129, 0.2);
476
- color: var(--success);
477
- border: 1px solid rgba(16, 185, 129, 0.3);
478
- }
479
-
480
- .sentiment-badge.bearish {
481
- background: rgba(239, 68, 68, 0.2);
482
- color: var(--danger);
483
- border: 1px solid rgba(239, 68, 68, 0.3);
484
- }
485
-
486
- .sentiment-badge.neutral {
487
- background: rgba(156, 163, 175, 0.2);
488
- color: var(--text-secondary);
489
- border: 1px solid rgba(156, 163, 175, 0.3);
490
- }
491
-
492
- /* AI Result Cards */
493
- .ai-result-card {
494
- background: rgba(17, 24, 39, 0.6);
495
- border: 1px solid var(--border);
496
- border-radius: 12px;
497
- padding: 20px;
498
- margin-top: 15px;
499
- transition: all 0.3s;
500
- }
501
-
502
- .ai-result-card:hover {
503
- border-color: var(--primary);
504
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.2);
505
- }
506
 
507
- .ai-result-header {
508
  display: flex;
509
- justify-content: space-between;
510
  align-items: center;
511
- margin-bottom: 15px;
512
- padding-bottom: 10px;
513
- border-bottom: 1px solid var(--border);
514
  }
515
 
516
- .ai-result-metric {
517
- display: flex;
518
- flex-direction: column;
519
- align-items: center;
520
- padding: 15px;
521
- background: rgba(31, 41, 55, 0.6);
522
- border-radius: 10px;
523
- min-width: 120px;
524
  }
525
 
526
- .ai-result-metric-value {
527
- font-size: 28px;
528
- font-weight: 800;
529
- margin-bottom: 5px;
530
  }
531
 
532
- .ai-result-metric-label {
533
- font-size: 12px;
 
 
 
534
  color: var(--text-secondary);
535
- text-transform: uppercase;
536
  }
537
 
538
- /* Model Status Indicators */
539
- .model-status {
540
- display: inline-flex;
541
- align-items: center;
542
- gap: 6px;
543
- padding: 4px 10px;
544
- border-radius: 6px;
545
- font-size: 12px;
546
- font-weight: 600;
547
- }
548
 
549
- .model-status.available {
550
- background: rgba(16, 185, 129, 0.15);
551
- color: var(--success);
552
  }
553
 
554
- .model-status.unavailable {
555
- background: rgba(239, 68, 68, 0.15);
556
- color: var(--danger);
557
  }
558
 
559
- .model-status.partial {
560
- background: rgba(245, 158, 11, 0.15);
561
- color: var(--warning);
562
  }
563
 
564
- /* Form Improvements for AI Sections */
565
- .form-group input[type="text"] {
566
- text-transform: uppercase;
567
  }
568
 
569
- .form-group textarea {
570
- resize: vertical;
571
- min-height: 80px;
572
- }
573
 
574
- /* Loading States */
575
- .loading {
576
- display: flex;
577
- flex-direction: column;
578
- align-items: center;
579
- justify-content: center;
580
- padding: 40px;
581
- color: var(--text-secondary);
582
- }
583
-
584
- .loading .spinner {
585
- margin-bottom: 15px;
586
- }
587
-
588
- /* Confidence Bar */
589
- .confidence-bar {
590
- width: 100%;
591
- height: 8px;
592
- background: rgba(31, 41, 55, 0.6);
593
- border-radius: 4px;
594
- overflow: hidden;
595
- margin-top: 5px;
596
- }
597
-
598
- .confidence-fill {
599
- height: 100%;
600
- background: linear-gradient(90deg, var(--primary), var(--primary-dark));
601
- transition: width 0.3s ease;
602
- }
603
-
604
- .confidence-fill.high {
605
- background: linear-gradient(90deg, var(--success), #059669);
606
- }
607
-
608
- .confidence-fill.low {
609
- background: linear-gradient(90deg, var(--danger), #dc2626);
610
- }
611
-
612
- /* Responsive */
613
  @media (max-width: 768px) {
614
- .header-content {
615
- flex-direction: column;
616
- align-items: flex-start;
617
  }
618
 
619
- .tabs-nav {
620
- padding: 15px;
 
 
 
 
 
 
 
 
 
 
 
621
  }
622
 
623
- .main-content {
 
 
 
 
 
 
624
  padding: 15px;
625
  }
626
 
627
- .grid-2 {
628
- grid-template-columns: 1fr;
629
  }
630
 
 
631
  .stats-grid {
632
  grid-template-columns: 1fr;
633
  }
634
 
635
- .ai-result-metric {
636
- min-width: 100px;
637
- padding: 10px;
638
  }
639
 
640
- .ai-result-metric-value {
641
- font-size: 20px;
 
 
642
  }
643
 
644
- .card {
645
- padding: 15px;
 
 
 
 
 
 
 
 
 
 
 
646
  }
647
  }
648
 
 
 
 
 
 
 
649
 
 
 
 
650
 
651
- /* Enhanced Header Actions */
652
- .header-actions {
653
- display: flex;
654
- align-items: center;
655
- gap: 20px;
656
- flex-wrap: wrap;
 
 
 
 
 
 
 
657
  }
658
 
659
- .header-stats {
660
- display: flex;
661
- gap: 15px;
 
 
662
  }
663
 
664
- .mini-stat {
665
- display: flex;
666
- flex-direction: column;
667
- align-items: center;
668
- padding: 10px 15px;
669
- background: rgba(31, 41, 55, 0.6);
670
- border-radius: 10px;
671
- border: 1px solid var(--border);
672
- min-width: 80px;
673
- transition: var(--transition-normal);
674
  }
675
 
676
- .mini-stat:hover {
677
- background: rgba(31, 41, 55, 0.8);
678
- border-color: var(--primary);
679
- transform: translateY(-2px);
680
  }
681
 
682
- .mini-stat i {
683
- font-size: 18px;
684
- color: var(--primary);
685
- margin-bottom: 5px;
686
  }
687
 
688
- .mini-stat span {
689
- font-size: 20px;
690
- font-weight: 700;
691
- color: var(--text-primary);
692
  }
693
 
694
- .mini-stat small {
695
- font-size: 10px;
696
- color: var(--text-secondary);
697
- text-transform: uppercase;
698
- letter-spacing: 0.5px;
699
  }
700
 
701
- .theme-toggle {
702
- width: 40px;
703
- height: 40px;
704
- border-radius: 10px;
705
- background: rgba(31, 41, 55, 0.6);
 
 
 
 
 
 
706
  border: 1px solid var(--border);
707
- color: var(--text-primary);
708
- cursor: pointer;
709
- transition: var(--transition-normal);
710
  display: flex;
711
- align-items: center;
712
- justify-content: center;
 
713
  }
714
 
715
- .theme-toggle:hover {
716
- background: var(--gradient-purple);
717
- border-color: var(--primary);
718
- transform: rotate(15deg);
719
  }
720
 
721
- /* Enhanced Stat Cards */
722
- .stat-card {
 
 
 
 
 
723
  display: flex;
724
  align-items: center;
725
- gap: 20px;
726
- position: relative;
727
- overflow: hidden;
728
  }
729
 
730
- .stat-card::before {
731
- content: '';
732
- position: absolute;
733
- top: 0;
734
- left: 0;
735
- width: 100%;
736
- height: 100%;
737
- background: linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
738
- opacity: 0;
739
- transition: var(--transition-normal);
740
  }
741
 
742
- .stat-card:hover::before {
743
- opacity: 1;
 
 
 
 
744
  }
745
 
746
- .stat-card.gradient-purple {
747
- border-left: 4px solid #667eea;
 
 
748
  }
749
 
750
- .stat-card.gradient-green {
751
- border-left: 4px solid #10b981;
752
  }
753
 
754
- .stat-card.gradient-blue {
755
- border-left: 4px solid #3b82f6;
 
 
 
 
 
 
 
756
  }
757
 
758
- .stat-card.gradient-orange {
759
- border-left: 4px solid #f59e0b;
 
 
 
 
 
760
  }
761
 
762
- .stat-card .stat-icon {
763
- width: 70px;
764
- height: 70px;
765
- border-radius: 16px;
766
  display: flex;
767
  align-items: center;
768
- justify-content: center;
769
- font-size: 32px;
770
- flex-shrink: 0;
 
 
771
  }
772
 
773
- .stat-card.gradient-purple .stat-icon {
774
- background: var(--gradient-purple);
775
- color: white;
776
- box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
777
  }
778
 
779
- .stat-card.gradient-green .stat-icon {
780
- background: var(--gradient-green);
781
- color: white;
782
- box-shadow: 0 10px 30px rgba(16, 185, 129, 0.3);
783
  }
784
 
785
- .stat-card.gradient-blue .stat-icon {
786
- background: var(--gradient-blue);
787
- color: white;
788
- box-shadow: 0 10px 30px rgba(59, 130, 246, 0.3);
789
  }
790
 
791
- .stat-card.gradient-orange .stat-icon {
792
- background: var(--gradient-orange);
793
- color: white;
794
- box-shadow: 0 10px 30px rgba(245, 158, 11, 0.3);
 
 
 
 
795
  }
796
 
797
- .stat-content {
798
- flex: 1;
799
- }
800
 
801
- .stat-trend {
802
- font-size: 12px;
803
- color: var(--text-secondary);
804
- margin-top: 5px;
805
- display: flex;
806
- align-items: center;
807
- gap: 5px;
808
  }
809
 
810
- .stat-trend i {
811
- color: var(--success);
 
812
  }
813
 
814
- /* Enhanced Tab Buttons */
815
- .tab-btn {
816
- display: flex;
817
- align-items: center;
818
- gap: 8px;
 
 
819
  }
820
 
821
- .tab-btn i {
822
- font-size: 16px;
823
  }
824
 
825
- .tab-btn span {
826
- font-size: 14px;
 
827
  }
828
 
829
- /* Smooth Scrollbar */
830
- ::-webkit-scrollbar {
831
- width: 10px;
832
- height: 10px;
833
  }
834
 
835
- ::-webkit-scrollbar-track {
836
- background: var(--dark-card);
 
837
  }
838
 
839
- ::-webkit-scrollbar-thumb {
840
- background: var(--gradient-purple);
841
- border-radius: 5px;
842
  }
843
 
844
- ::-webkit-scrollbar-thumb:hover {
845
- background: var(--primary-light);
 
 
 
 
846
  }
847
 
848
- /* Loading Animation Enhancement */
849
- .spinner {
850
- border: 3px solid var(--border);
851
- border-top: 3px solid var(--primary);
852
- border-radius: 50%;
853
- width: 40px;
854
- height: 40px;
855
- animation: spin 1s linear infinite;
856
- margin: 0 auto;
857
  position: relative;
858
  }
859
 
860
- .spinner::after {
861
- content: '';
862
- position: absolute;
863
- top: 50%;
864
- left: 50%;
865
- transform: translate(-50%, -50%);
866
- width: 20px;
867
- height: 20px;
868
- border: 2px solid var(--secondary);
869
- border-radius: 50%;
870
- animation: spin 0.5s linear infinite reverse;
871
- }
872
-
873
- /* Card Enhancements */
874
- .card {
875
  position: relative;
876
  overflow: hidden;
877
  }
878
 
879
- .card::before {
880
  content: '';
881
  position: absolute;
882
  top: 0;
883
- left: -100%;
884
- width: 100%;
885
- height: 100%;
886
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.05), transparent);
887
- transition: var(--transition-slow);
888
  }
889
 
890
- .card:hover::before {
891
- left: 100%;
 
892
  }
893
 
894
- /* Button Enhancements */
895
- .btn-primary, .btn-refresh {
896
- position: relative;
897
- overflow: hidden;
 
 
 
 
898
  }
899
 
900
- .btn-primary::before, .btn-refresh::before {
901
- content: '';
902
- position: absolute;
903
- top: 50%;
904
- left: 50%;
905
- width: 0;
906
- height: 0;
907
- border-radius: 50%;
908
- background: rgba(255, 255, 255, 0.2);
909
- transform: translate(-50%, -50%);
910
- transition: width 0.6s, height 0.6s;
911
  }
912
 
913
- .btn-primary:hover::before, .btn-refresh:hover::before {
914
- width: 300px;
915
- height: 300px;
 
 
916
  }
917
 
918
- /* Tooltip */
919
- [title] {
920
- position: relative;
 
921
  }
922
 
923
- /* Focus States */
924
- *:focus-visible {
925
- outline: 2px solid var(--primary);
926
- outline-offset: 2px;
 
 
 
927
  }
928
 
929
- /* Selection */
930
- ::selection {
931
- background: var(--primary);
932
- color: white;
 
 
 
933
  }
934
 
935
- /* Responsive Enhancements */
936
- @media (max-width: 768px) {
937
- .header-stats {
938
- display: none;
939
- }
940
-
941
- .mini-stat {
942
- min-width: 60px;
943
- padding: 8px 10px;
944
- }
945
-
946
- .stat-card {
947
- flex-direction: column;
948
- text-align: center;
949
- }
950
-
951
- .stat-card .stat-icon {
952
- width: 60px;
953
- height: 60px;
954
- font-size: 28px;
955
- }
956
-
957
- .tab-btn span {
958
- display: none;
959
- }
960
-
961
- .tab-btn {
962
- padding: 12px 16px;
963
- }
964
  }
965
 
966
-
967
- /* Light Theme */
968
- body.light-theme {
969
- --dark: #f3f4f6;
970
- --dark-card: #ffffff;
971
- --dark-hover: #f9fafb;
972
- --dark-elevated: #e5e7eb;
973
- --text-primary: #111827;
974
- --text-secondary: #6b7280;
975
- --text-muted: #9ca3af;
976
- --border: rgba(0, 0, 0, 0.1);
977
- --border-light: rgba(0, 0, 0, 0.05);
978
- --shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
979
- --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15);
980
- background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 50%, #d1d5db 100%);
981
  }
982
 
983
- body.light-theme::before {
984
- background-image:
985
- radial-gradient(circle at 20% 50%, rgba(102, 126, 234, 0.08) 0%, transparent 50%),
986
- radial-gradient(circle at 80% 80%, rgba(240, 147, 251, 0.08) 0%, transparent 50%),
987
- radial-gradient(circle at 40% 20%, rgba(59, 130, 246, 0.08) 0%, transparent 50%);
988
  }
989
 
990
- body.light-theme .app-header {
991
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.7) 100%);
 
 
 
992
  }
993
 
994
- body.light-theme .tabs-nav {
995
- background: rgba(255, 255, 255, 0.5);
996
- }
997
 
998
- body.light-theme .stat-card,
999
- body.light-theme .card {
1000
- background: rgba(255, 255, 255, 0.8);
1001
- backdrop-filter: blur(10px);
 
 
1002
  }
1003
 
1004
- body.light-theme .mini-stat {
1005
- background: rgba(249, 250, 251, 0.8);
 
 
1006
  }
1007
 
1008
- body.light-theme .theme-toggle {
1009
- background: rgba(249, 250, 251, 0.8);
 
 
1010
  }
1011
 
1012
- body.light-theme .form-group input,
1013
- body.light-theme .form-group textarea,
1014
- body.light-theme .form-group select {
1015
- background: rgba(249, 250, 251, 0.8);
1016
  }
1017
 
1018
- body.light-theme table th {
1019
- background: rgba(249, 250, 251, 0.8);
 
 
 
 
1020
  }
1021
 
1022
- body.light-theme ::-webkit-scrollbar-track {
1023
- background: #e5e7eb;
 
 
 
 
 
 
 
 
 
 
 
 
 
1024
  }
 
1
+ /* Crypto Intelligence Hub - Enhanced Stylesheet with Sidebar Navigation */
2
 
3
  :root {
4
  /* Primary Colors */
 
32
  --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.4);
33
  --glow: 0 0 20px rgba(102, 126, 234, 0.3);
34
 
35
+ /* Sidebar */
36
+ --sidebar-width: 280px;
37
+ --sidebar-collapsed-width: 80px;
38
+
39
  /* Gradients */
40
  --gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
41
  --gradient-blue: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
 
81
  z-index: 0;
82
  }
83
 
84
+ /* =============================================================================
85
+ Layout Structure
86
+ ============================================================================= */
87
+
88
+ .app-layout {
89
  display: flex;
90
+ min-height: 100vh;
91
  position: relative;
92
  z-index: 1;
93
  }
94
 
95
+ /* =============================================================================
96
+ Sidebar Navigation
97
+ ============================================================================= */
98
+
99
+ .sidebar {
100
+ width: var(--sidebar-width);
101
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.95) 0%, rgba(31, 41, 55, 0.9) 100%);
102
  backdrop-filter: blur(20px);
103
+ border-right: 1px solid var(--border);
104
+ display: flex;
105
+ flex-direction: column;
106
+ position: fixed;
107
+ top: 0;
108
+ left: 0;
109
+ height: 100vh;
110
+ z-index: 1000;
111
+ transition: transform var(--transition-normal);
112
  box-shadow: var(--shadow);
113
  }
114
 
115
+ .sidebar-header {
116
+ padding: 25px 20px;
117
+ border-bottom: 1px solid var(--border);
118
  display: flex;
119
  justify-content: space-between;
120
  align-items: center;
 
 
121
  }
122
 
123
+ .sidebar-logo {
124
  display: flex;
125
  align-items: center;
126
+ gap: 12px;
127
  }
128
 
129
+ .sidebar-logo .logo-icon {
130
+ width: 45px;
131
+ height: 45px;
132
  background: var(--gradient-purple);
133
+ border-radius: 12px;
134
  display: flex;
135
  align-items: center;
136
  justify-content: center;
137
+ font-size: 22px;
138
  color: white;
139
  box-shadow: var(--glow);
140
  animation: float 3s ease-in-out infinite;
 
145
  50% { transform: translateY(-5px); }
146
  }
147
 
148
+ .sidebar-logo .logo-text h1 {
149
+ font-size: 20px;
150
  font-weight: 800;
151
  background: linear-gradient(135deg, var(--primary), var(--secondary));
152
  -webkit-background-clip: text;
153
  -webkit-text-fill-color: transparent;
154
  background-clip: text;
155
+ margin-bottom: 2px;
156
  }
157
 
158
+ .sidebar-logo .logo-text p {
159
+ font-size: 11px;
160
+ color: var(--text-secondary);
161
+ font-weight: 500;
162
+ }
163
+
164
+ .sidebar-toggle-btn {
165
+ display: none;
166
+ width: 32px;
167
+ height: 32px;
168
+ background: rgba(255, 255, 255, 0.05);
169
+ border: 1px solid var(--border);
170
+ border-radius: 8px;
171
+ color: var(--text-primary);
172
+ cursor: pointer;
173
+ transition: var(--transition-fast);
174
+ }
175
+
176
+ .sidebar-toggle-btn:hover {
177
+ background: rgba(255, 255, 255, 0.1);
178
+ transform: rotate(90deg);
179
+ }
180
+
181
+ /* Sidebar Navigation */
182
+ .sidebar-nav {
183
+ flex: 1;
184
+ padding: 20px 15px;
185
+ overflow-y: auto;
186
+ }
187
+
188
+ .nav-item {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 12px;
192
+ padding: 14px 18px;
193
+ background: transparent;
194
+ border: 1px solid transparent;
195
+ border-radius: 12px;
196
  color: var(--text-secondary);
197
+ cursor: pointer;
198
+ font-size: 15px;
199
+ font-weight: 600;
200
+ transition: all var(--transition-fast);
201
+ width: 100%;
202
+ text-align: left;
203
+ margin-bottom: 8px;
204
+ }
205
+
206
+ .nav-item svg {
207
+ width: 20px;
208
+ height: 20px;
209
+ flex-shrink: 0;
210
+ stroke-width: 2;
211
+ }
212
+
213
+ .nav-item:hover {
214
+ background: rgba(102, 126, 234, 0.1);
215
+ border-color: var(--primary);
216
+ color: var(--text-primary);
217
+ transform: translateX(5px);
218
+ }
219
+
220
+ .nav-item.active {
221
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
222
+ border-color: var(--primary);
223
+ color: white;
224
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
225
+ }
226
+
227
+ /* Sidebar Footer */
228
+ .sidebar-footer {
229
+ padding: 20px;
230
+ border-top: 1px solid var(--border);
231
  }
232
 
233
+ .status-indicator {
234
  display: flex;
235
  align-items: center;
236
+ gap: 10px;
237
+ padding: 12px;
238
  background: rgba(16, 185, 129, 0.15);
239
  border: 1px solid rgba(16, 185, 129, 0.3);
240
+ border-radius: 10px;
241
+ font-size: 13px;
242
  font-weight: 600;
243
+ margin-bottom: 12px;
244
  }
245
 
246
  .status-dot {
 
256
  50% { opacity: 0.5; transform: scale(1.2); }
257
  }
258
 
259
+ .sidebar-stats {
260
+ display: flex;
261
+ gap: 10px;
262
+ }
263
+
264
+ .stat-mini {
265
+ flex: 1;
266
+ display: flex;
267
+ flex-direction: column;
268
+ align-items: center;
269
+ padding: 12px;
270
+ background: rgba(31, 41, 55, 0.6);
271
+ border-radius: 10px;
272
+ border: 1px solid var(--border);
273
+ }
274
+
275
+ .stat-mini svg {
276
+ width: 16px;
277
+ height: 16px;
278
+ color: var(--primary);
279
+ margin-bottom: 5px;
280
+ stroke-width: 2;
281
+ }
282
+
283
+ .stat-mini span {
284
+ font-size: 18px;
285
+ font-weight: 700;
286
+ color: var(--text-primary);
287
+ }
288
+
289
+ /* Sidebar Overlay for Mobile */
290
+ .sidebar-overlay {
291
+ display: none;
292
+ position: fixed;
293
+ top: 0;
294
+ left: 0;
295
+ width: 100%;
296
+ height: 100%;
297
+ background: rgba(0, 0, 0, 0.5);
298
+ backdrop-filter: blur(5px);
299
+ z-index: 999;
300
+ opacity: 0;
301
+ transition: opacity var(--transition-normal);
302
  }
303
 
304
+ .sidebar-overlay.active {
305
+ display: block;
306
+ opacity: 1;
307
  }
308
 
309
+ /* =============================================================================
310
+ Main Wrapper
311
+ ============================================================================= */
312
+
313
+ .main-wrapper {
314
+ flex: 1;
315
+ margin-left: var(--sidebar-width);
316
  display: flex;
317
+ flex-direction: column;
318
+ min-height: 100vh;
319
+ transition: margin-left var(--transition-normal);
320
+ }
321
+
322
+ /* Top Header */
323
+ .top-header {
324
+ background: linear-gradient(135deg, rgba(17, 24, 39, 0.9) 0%, rgba(31, 41, 55, 0.7) 100%);
325
+ backdrop-filter: blur(20px);
326
  border-bottom: 1px solid var(--border);
327
+ padding: 20px 30px;
328
+ display: flex;
329
+ align-items: center;
330
+ gap: 20px;
331
+ box-shadow: var(--shadow);
332
+ position: sticky;
333
+ top: 0;
334
+ z-index: 100;
335
  }
336
 
337
+ .hamburger-btn {
338
+ display: none;
339
+ width: 40px;
340
+ height: 40px;
341
+ background: rgba(102, 126, 234, 0.2);
342
+ border: 1px solid var(--primary);
343
  border-radius: 10px;
344
+ color: var(--text-primary);
345
  cursor: pointer;
346
+ font-size: 18px;
347
+ transition: var(--transition-fast);
 
 
348
  }
349
 
350
+ .hamburger-btn:hover {
351
+ background: var(--gradient-purple);
352
+ transform: scale(1.05);
353
+ }
354
+
355
+ .header-title {
356
+ flex: 1;
357
+ }
358
+
359
+ .header-title h2 {
360
+ font-size: 24px;
361
+ font-weight: 700;
362
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
363
+ -webkit-background-clip: text;
364
+ -webkit-text-fill-color: transparent;
365
+ background-clip: text;
366
+ margin-bottom: 2px;
367
+ }
368
+
369
+ .header-title p {
370
+ font-size: 13px;
371
+ color: var(--text-secondary);
372
+ }
373
+
374
+ .header-actions {
375
+ display: flex;
376
+ gap: 10px;
377
+ }
378
+
379
+ .icon-btn {
380
+ width: 40px;
381
+ height: 40px;
382
+ background: rgba(31, 41, 55, 0.6);
383
+ border: 1px solid var(--border);
384
+ border-radius: 10px;
385
  color: var(--text-primary);
386
+ cursor: pointer;
387
+ transition: var(--transition-fast);
388
+ display: flex;
389
+ align-items: center;
390
+ justify-content: center;
391
+ font-size: 16px;
392
  }
393
 
394
+ .icon-btn:hover {
395
+ background: var(--gradient-purple);
396
  border-color: var(--primary);
397
+ transform: translateY(-2px);
 
398
  }
399
 
400
+ /* =============================================================================
401
+ Content Area
402
+ ============================================================================= */
403
+
404
+ .content-area {
405
  flex: 1;
406
  padding: 30px;
407
+ overflow-y: auto;
408
  }
409
 
410
+ .tab-panel {
411
  display: none;
412
  }
413
 
414
+ .tab-panel.active {
415
  display: block;
416
  animation: fadeIn 0.3s;
417
  }
 
421
  to { opacity: 1; transform: translateY(0); }
422
  }
423
 
424
+ /* =============================================================================
425
+ Cards & Components
426
+ ============================================================================= */
427
+
428
+ .glass-card {
429
+ background: rgba(17, 24, 39, 0.6);
430
+ backdrop-filter: blur(10px);
431
+ border: 1px solid var(--border);
432
+ border-radius: 16px;
433
+ padding: 25px;
434
+ margin-bottom: 20px;
435
+ transition: all var(--transition-normal);
436
+ }
437
+
438
+ .glass-card:hover {
439
+ border-color: rgba(102, 126, 234, 0.3);
440
+ box-shadow: 0 8px 32px rgba(102, 126, 234, 0.15);
441
+ }
442
+
443
+ .glass-card h3 {
444
+ font-size: 20px;
445
+ margin-bottom: 20px;
446
+ color: var(--text-primary);
447
+ border-bottom: 2px solid var(--border);
448
+ padding-bottom: 10px;
449
+ }
450
+
451
+ .card-header {
452
  display: flex;
453
  justify-content: space-between;
454
  align-items: center;
455
+ margin-bottom: 20px;
 
 
456
  }
457
 
458
+ .card-header h3 {
459
+ margin-bottom: 0;
460
+ border-bottom: none;
461
+ padding-bottom: 0;
 
 
 
462
  }
463
 
464
  /* Stats Grid */
 
474
  border: 1px solid var(--border);
475
  border-radius: 16px;
476
  padding: 25px;
477
+ display: flex;
478
+ align-items: center;
479
+ gap: 20px;
480
+ transition: all var(--transition-normal);
481
  backdrop-filter: blur(10px);
482
+ position: relative;
483
+ overflow: hidden;
484
+ }
485
+
486
+ .stat-card::before {
487
+ content: '';
488
+ position: absolute;
489
+ top: 0;
490
+ left: 0;
491
+ width: 100%;
492
+ height: 100%;
493
+ background: linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
494
+ opacity: 0;
495
+ transition: var(--transition-normal);
496
  }
497
 
498
  .stat-card:hover {
499
  transform: translateY(-5px);
500
  box-shadow: var(--shadow);
 
 
 
 
 
 
501
  }
502
 
503
+ .stat-card:hover::before {
504
+ opacity: 1;
 
 
 
505
  }
506
 
507
+ .stat-card.gradient-purple {
508
+ border-left: 4px solid #667eea;
 
 
509
  }
510
 
511
+ .stat-card.gradient-green {
512
+ border-left: 4px solid #10b981;
 
 
 
 
 
 
513
  }
514
 
515
+ .stat-card.gradient-blue {
516
+ border-left: 4px solid #3b82f6;
 
 
 
 
517
  }
518
 
519
+ .stat-card.gradient-orange {
520
+ border-left: 4px solid #f59e0b;
 
 
521
  }
522
 
523
+ .stat-icon {
524
+ width: 70px;
525
+ height: 70px;
526
+ border-radius: 16px;
527
+ display: flex;
528
+ align-items: center;
529
+ justify-content: center;
530
+ font-size: 32px;
531
+ flex-shrink: 0;
532
+ }
533
+
534
+ .stat-card.gradient-purple .stat-icon {
535
+ background: var(--gradient-purple);
536
  color: white;
537
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
538
+ }
539
+
540
+ .stat-card.gradient-green .stat-icon {
541
+ background: var(--gradient-green);
542
+ color: white;
543
+ box-shadow: 0 10px 30px rgba(16, 185, 129, 0.3);
544
+ }
545
+
546
+ .stat-card.gradient-blue .stat-icon {
547
+ background: var(--gradient-blue);
548
+ color: white;
549
+ box-shadow: 0 10px 30px rgba(59, 130, 246, 0.3);
550
+ }
551
+
552
+ .stat-card.gradient-orange .stat-icon {
553
+ background: var(--gradient-orange);
554
+ color: white;
555
+ box-shadow: 0 10px 30px rgba(245, 158, 11, 0.3);
556
+ }
557
+
558
+ .stat-content {
559
+ flex: 1;
560
+ }
561
+
562
+ .stat-value {
563
+ font-size: 36px;
564
+ font-weight: 800;
565
+ color: var(--primary);
566
+ margin-bottom: 5px;
567
+ }
568
+
569
+ .stat-label {
570
  font-size: 14px;
571
+ color: var(--text-secondary);
572
+ font-weight: 600;
573
  }
574
 
575
+ .stat-trend {
576
+ font-size: 12px;
577
+ color: var(--text-secondary);
578
+ margin-top: 5px;
579
+ display: flex;
580
+ align-items: center;
581
+ gap: 5px;
582
  }
583
 
584
+ .stat-trend i {
585
+ color: var(--success);
586
+ }
587
+
588
+ /* Grid Layouts */
589
+ .grid-2 {
590
+ display: grid;
591
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
592
+ gap: 20px;
593
  }
594
 
595
+ /* =============================================================================
596
+ Forms
597
+ ============================================================================= */
598
+
599
  .form-group {
600
  margin-bottom: 20px;
601
  }
 
618
  color: var(--text-primary);
619
  font-family: inherit;
620
  font-size: 14px;
621
+ transition: var(--transition-fast);
622
  }
623
 
624
  .form-group input:focus,
 
629
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
630
  }
631
 
632
+ .form-group textarea {
633
+ resize: vertical;
634
+ min-height: 80px;
635
+ }
636
+
637
+ /* =============================================================================
638
+ Buttons
639
+ ============================================================================= */
640
+
641
+ .btn-primary, .btn-refresh {
642
+ padding: 12px 24px;
643
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
644
+ border: none;
645
+ border-radius: 10px;
646
+ color: white;
647
+ font-weight: 600;
648
+ cursor: pointer;
649
+ transition: all var(--transition-fast);
650
+ font-size: 14px;
651
+ position: relative;
652
+ overflow: hidden;
653
+ }
654
+
655
+ .btn-primary::before, .btn-refresh::before {
656
+ content: '';
657
+ position: absolute;
658
+ top: 50%;
659
+ left: 50%;
660
+ width: 0;
661
+ height: 0;
662
+ border-radius: 50%;
663
+ background: rgba(255, 255, 255, 0.2);
664
+ transform: translate(-50%, -50%);
665
+ transition: width 0.6s, height 0.6s;
666
+ }
667
+
668
+ .btn-primary:hover, .btn-refresh:hover {
669
+ transform: translateY(-2px);
670
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
671
+ }
672
+
673
+ .btn-primary:hover::before, .btn-refresh:hover::before {
674
+ width: 300px;
675
+ height: 300px;
676
+ }
677
+
678
+ .btn-refresh {
679
+ background: rgba(102, 126, 234, 0.2);
680
+ border: 1px solid var(--primary);
681
+ }
682
+
683
+ /* =============================================================================
684
+ Tables
685
+ ============================================================================= */
686
+
687
  table {
688
  width: 100%;
689
  border-collapse: collapse;
 
692
  table th,
693
  table td {
694
  padding: 12px;
695
+ text-align: left;
696
  border-bottom: 1px solid var(--border);
697
  }
698
 
 
706
  background: rgba(102, 126, 234, 0.05);
707
  }
708
 
709
+ /* =============================================================================
710
+ Alerts
711
+ ============================================================================= */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
 
 
713
  .alert {
714
  padding: 15px;
715
  border-radius: 10px;
 
734
  color: var(--warning);
735
  }
736
 
737
+ /* =============================================================================
738
+ Loading States
739
+ ============================================================================= */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
 
741
+ .loading {
742
  display: flex;
743
+ flex-direction: column;
744
  align-items: center;
745
+ justify-content: center;
746
+ padding: 40px;
747
+ color: var(--text-secondary);
748
  }
749
 
750
+ .spinner {
751
+ border: 3px solid var(--border);
752
+ border-top: 3px solid var(--primary);
753
+ border-radius: 50%;
754
+ width: 40px;
755
+ height: 40px;
756
+ animation: spin 1s linear infinite;
757
+ margin: 0 auto 15px;
758
  }
759
 
760
+ @keyframes spin {
761
+ 0% { transform: rotate(0deg); }
762
+ 100% { transform: rotate(360deg); }
 
763
  }
764
 
765
+ /* =============================================================================
766
+ Utility Classes
767
+ ============================================================================= */
768
+
769
+ .text-secondary {
770
  color: var(--text-secondary);
771
+ margin-bottom: 15px;
772
  }
773
 
774
+ /* =============================================================================
775
+ Scrollbar
776
+ ============================================================================= */
 
 
 
 
 
 
 
777
 
778
+ ::-webkit-scrollbar {
779
+ width: 10px;
780
+ height: 10px;
781
  }
782
 
783
+ ::-webkit-scrollbar-track {
784
+ background: var(--dark-card);
 
785
  }
786
 
787
+ ::-webkit-scrollbar-thumb {
788
+ background: var(--gradient-purple);
789
+ border-radius: 5px;
790
  }
791
 
792
+ ::-webkit-scrollbar-thumb:hover {
793
+ background: var(--primary-light);
 
794
  }
795
 
796
+ /* =============================================================================
797
+ Responsive Design (Mobile First)
798
+ ============================================================================= */
 
799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  @media (max-width: 768px) {
801
+ /* Hide sidebar by default on mobile */
802
+ .sidebar {
803
+ transform: translateX(-100%);
804
  }
805
 
806
+ .sidebar.active {
807
+ transform: translateX(0);
808
+ }
809
+
810
+ .sidebar-toggle-btn {
811
+ display: flex;
812
+ align-items: center;
813
+ justify-content: center;
814
+ }
815
+
816
+ /* Show hamburger button */
817
+ .hamburger-btn {
818
+ display: flex;
819
  }
820
 
821
+ /* Adjust main wrapper */
822
+ .main-wrapper {
823
+ margin-left: 0;
824
+ }
825
+
826
+ /* Adjust content padding */
827
+ .content-area {
828
  padding: 15px;
829
  }
830
 
831
+ .top-header {
832
+ padding: 15px 20px;
833
  }
834
 
835
+ /* Adjust grids */
836
  .stats-grid {
837
  grid-template-columns: 1fr;
838
  }
839
 
840
+ .grid-2 {
841
+ grid-template-columns: 1fr;
 
842
  }
843
 
844
+ /* Adjust stat cards */
845
+ .stat-card {
846
+ flex-direction: column;
847
+ text-align: center;
848
  }
849
 
850
+ .stat-card .stat-icon {
851
+ width: 60px;
852
+ height: 60px;
853
+ font-size: 28px;
854
+ }
855
+
856
+ /* Adjust header title */
857
+ .header-title h2 {
858
+ font-size: 18px;
859
+ }
860
+
861
+ .header-title p {
862
+ font-size: 11px;
863
  }
864
  }
865
 
866
+ /* Tablet adjustments */
867
+ @media (min-width: 769px) and (max-width: 1024px) {
868
+ .stats-grid {
869
+ grid-template-columns: repeat(2, 1fr);
870
+ }
871
+ }
872
 
873
+ /* =============================================================================
874
+ Light Theme
875
+ ============================================================================= */
876
 
877
+ body.light-theme {
878
+ --dark: #f3f4f6;
879
+ --dark-card: #ffffff;
880
+ --dark-hover: #f9fafb;
881
+ --dark-elevated: #e5e7eb;
882
+ --text-primary: #111827;
883
+ --text-secondary: #6b7280;
884
+ --text-muted: #9ca3af;
885
+ --border: rgba(0, 0, 0, 0.1);
886
+ --border-light: rgba(0, 0, 0, 0.05);
887
+ --shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
888
+ --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15);
889
+ background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 50%, #d1d5db 100%);
890
  }
891
 
892
+ body.light-theme::before {
893
+ background-image:
894
+ radial-gradient(circle at 20% 50%, rgba(102, 126, 234, 0.08) 0%, transparent 50%),
895
+ radial-gradient(circle at 80% 80%, rgba(240, 147, 251, 0.08) 0%, transparent 50%),
896
+ radial-gradient(circle at 40% 20%, rgba(59, 130, 246, 0.08) 0%, transparent 50%);
897
  }
898
 
899
+ body.light-theme .sidebar {
900
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(249, 250, 251, 0.9) 100%);
 
 
 
 
 
 
 
 
901
  }
902
 
903
+ body.light-theme .top-header {
904
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.7) 100%);
 
 
905
  }
906
 
907
+ body.light-theme .glass-card,
908
+ body.light-theme .stat-card {
909
+ background: rgba(255, 255, 255, 0.8);
910
+ backdrop-filter: blur(10px);
911
  }
912
 
913
+ body.light-theme .form-group input,
914
+ body.light-theme .form-group textarea,
915
+ body.light-theme .form-group select {
916
+ background: rgba(249, 250, 251, 0.8);
917
  }
918
 
919
+ body.light-theme table th {
920
+ background: rgba(249, 250, 251, 0.8);
 
 
 
921
  }
922
 
923
+ body.light-theme ::-webkit-scrollbar-track {
924
+ background: #e5e7eb;
925
+ }
926
+
927
+ /* =============================================================================
928
+ News Cards - Modern Design
929
+ ============================================================================= */
930
+
931
+ .news-card {
932
+ background: rgba(17, 24, 39, 0.6);
933
+ backdrop-filter: blur(10px);
934
  border: 1px solid var(--border);
935
+ border-radius: 16px;
936
+ padding: 20px;
937
+ margin-bottom: 20px;
938
  display: flex;
939
+ gap: 20px;
940
+ transition: all var(--transition-normal);
941
+ cursor: pointer;
942
  }
943
 
944
+ .news-card:hover {
945
+ border-color: rgba(102, 126, 234, 0.5);
946
+ box-shadow: 0 8px 32px rgba(102, 126, 234, 0.2);
947
+ transform: translateY(-2px);
948
  }
949
 
950
+ .news-card-image {
951
+ width: 120px;
952
+ height: 120px;
953
+ border-radius: 12px;
954
+ object-fit: cover;
955
+ flex-shrink: 0;
956
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
957
  display: flex;
958
  align-items: center;
959
+ justify-content: center;
960
+ color: white;
961
+ font-size: 32px;
962
  }
963
 
964
+ .news-card-content {
965
+ flex: 1;
966
+ min-width: 0;
 
 
 
 
 
 
 
967
  }
968
 
969
+ .news-card-title {
970
+ font-size: 18px;
971
+ font-weight: 700;
972
+ color: var(--text-primary);
973
+ margin-bottom: 10px;
974
+ line-height: 1.4;
975
  }
976
 
977
+ .news-card-title a {
978
+ color: var(--text-primary);
979
+ text-decoration: none;
980
+ transition: color var(--transition-fast);
981
  }
982
 
983
+ .news-card-title a:hover {
984
+ color: var(--primary);
985
  }
986
 
987
+ .news-card-excerpt {
988
+ color: var(--text-secondary);
989
+ font-size: 14px;
990
+ line-height: 1.6;
991
+ margin-bottom: 12px;
992
+ display: -webkit-box;
993
+ -webkit-line-clamp: 2;
994
+ -webkit-box-orient: vertical;
995
+ overflow: hidden;
996
  }
997
 
998
+ .news-card-meta {
999
+ display: flex;
1000
+ align-items: center;
1001
+ gap: 15px;
1002
+ flex-wrap: wrap;
1003
+ font-size: 12px;
1004
+ color: var(--text-muted);
1005
  }
1006
 
1007
+ .news-card-source {
 
 
 
1008
  display: flex;
1009
  align-items: center;
1010
+ gap: 6px;
1011
+ padding: 4px 10px;
1012
+ background: rgba(102, 126, 234, 0.1);
1013
+ border-radius: 6px;
1014
+ font-weight: 600;
1015
  }
1016
 
1017
+ .news-card-time {
1018
+ display: flex;
1019
+ align-items: center;
1020
+ gap: 6px;
1021
  }
1022
 
1023
+ .news-card-time svg {
1024
+ width: 14px;
1025
+ height: 14px;
 
1026
  }
1027
 
1028
+ .news-card-symbols {
1029
+ display: flex;
1030
+ gap: 6px;
1031
+ flex-wrap: wrap;
1032
  }
1033
 
1034
+ .symbol-badge {
1035
+ padding: 3px 8px;
1036
+ background: rgba(59, 130, 246, 0.15);
1037
+ border: 1px solid rgba(59, 130, 246, 0.3);
1038
+ border-radius: 4px;
1039
+ font-size: 11px;
1040
+ font-weight: 600;
1041
+ color: var(--info);
1042
  }
1043
 
1044
+ /* =============================================================================
1045
+ Sentiment Visualizations
1046
+ ============================================================================= */
1047
 
1048
+ .sentiment-gauge-container {
1049
+ position: relative;
1050
+ width: 300px;
1051
+ height: 150px;
1052
+ margin: 20px auto;
 
 
1053
  }
1054
 
1055
+ .sentiment-gauge {
1056
+ width: 100%;
1057
+ height: 100%;
1058
  }
1059
 
1060
+ .sentiment-trend-arrow {
1061
+ display: inline-block;
1062
+ width: 24px;
1063
+ height: 24px;
1064
+ margin: 0 8px;
1065
+ vertical-align: middle;
1066
+ animation: pulse-arrow 2s ease-in-out infinite;
1067
  }
1068
 
1069
+ .sentiment-trend-arrow.bullish {
1070
+ color: var(--success);
1071
  }
1072
 
1073
+ .sentiment-trend-arrow.bearish {
1074
+ color: var(--danger);
1075
+ transform: rotate(180deg);
1076
  }
1077
 
1078
+ .sentiment-trend-arrow.neutral {
1079
+ color: var(--warning);
1080
+ transform: rotate(90deg);
 
1081
  }
1082
 
1083
+ @keyframes pulse-arrow {
1084
+ 0%, 100% { transform: translateY(0); opacity: 1; }
1085
+ 50% { transform: translateY(-5px); opacity: 0.7; }
1086
  }
1087
 
1088
+ .confidence-bar-container {
1089
+ margin: 15px 0;
 
1090
  }
1091
 
1092
+ .confidence-bar-label {
1093
+ display: flex;
1094
+ justify-content: space-between;
1095
+ margin-bottom: 8px;
1096
+ font-size: 13px;
1097
+ color: var(--text-secondary);
1098
  }
1099
 
1100
+ .confidence-bar {
1101
+ width: 100%;
1102
+ height: 12px;
1103
+ background: rgba(31, 41, 55, 0.6);
1104
+ border-radius: 6px;
1105
+ overflow: hidden;
 
 
 
1106
  position: relative;
1107
  }
1108
 
1109
+ .confidence-bar-fill {
1110
+ height: 100%;
1111
+ background: linear-gradient(90deg, var(--primary), var(--primary-light));
1112
+ border-radius: 6px;
1113
+ transition: width 1s ease-out;
 
 
 
 
 
 
 
 
 
 
1114
  position: relative;
1115
  overflow: hidden;
1116
  }
1117
 
1118
+ .confidence-bar-fill::after {
1119
  content: '';
1120
  position: absolute;
1121
  top: 0;
1122
+ left: 0;
1123
+ right: 0;
1124
+ bottom: 0;
1125
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
1126
+ animation: shimmer 2s infinite;
1127
  }
1128
 
1129
+ @keyframes shimmer {
1130
+ 0% { transform: translateX(-100%); }
1131
+ 100% { transform: translateX(100%); }
1132
  }
1133
 
1134
+ .sentiment-badge {
1135
+ display: inline-block;
1136
+ padding: 6px 12px;
1137
+ border-radius: 6px;
1138
+ font-size: 12px;
1139
+ font-weight: 700;
1140
+ text-transform: uppercase;
1141
+ letter-spacing: 0.5px;
1142
  }
1143
 
1144
+ .sentiment-badge.bullish,
1145
+ .sentiment-badge.positive {
1146
+ background: rgba(16, 185, 129, 0.2);
1147
+ border: 1px solid rgba(16, 185, 129, 0.4);
1148
+ color: var(--success);
 
 
 
 
 
 
1149
  }
1150
 
1151
+ .sentiment-badge.bearish,
1152
+ .sentiment-badge.negative {
1153
+ background: rgba(239, 68, 68, 0.2);
1154
+ border: 1px solid rgba(239, 68, 68, 0.4);
1155
+ color: var(--danger);
1156
  }
1157
 
1158
+ .sentiment-badge.neutral {
1159
+ background: rgba(245, 158, 11, 0.2);
1160
+ border: 1px solid rgba(245, 158, 11, 0.4);
1161
+ color: var(--warning);
1162
  }
1163
 
1164
+ .ai-result-card {
1165
+ background: rgba(17, 24, 39, 0.6);
1166
+ backdrop-filter: blur(10px);
1167
+ border: 1px solid var(--border);
1168
+ border-radius: 16px;
1169
+ padding: 25px;
1170
+ margin-top: 20px;
1171
  }
1172
 
1173
+ .ai-result-header {
1174
+ display: flex;
1175
+ justify-content: space-between;
1176
+ align-items: center;
1177
+ margin-bottom: 20px;
1178
+ padding-bottom: 15px;
1179
+ border-bottom: 1px solid var(--border);
1180
  }
1181
 
1182
+ .ai-result-header h4 {
1183
+ font-size: 20px;
1184
+ font-weight: 700;
1185
+ color: var(--text-primary);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1186
  }
1187
 
1188
+ .ai-result-metric {
1189
+ text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
1190
  }
1191
 
1192
+ .ai-result-metric-value {
1193
+ font-size: 36px;
1194
+ font-weight: 800;
1195
+ margin-bottom: 5px;
 
1196
  }
1197
 
1198
+ .ai-result-metric-label {
1199
+ font-size: 13px;
1200
+ color: var(--text-secondary);
1201
+ text-transform: uppercase;
1202
+ letter-spacing: 1px;
1203
  }
1204
 
1205
+ /* =============================================================================
1206
+ Utility Classes
1207
+ ============================================================================= */
1208
 
1209
+ .time-ago {
1210
+ color: var(--text-muted);
1211
+ font-size: 12px;
1212
+ display: inline-flex;
1213
+ align-items: center;
1214
+ gap: 4px;
1215
  }
1216
 
1217
+ .icon-btn svg {
1218
+ width: 18px;
1219
+ height: 18px;
1220
+ stroke-width: 2;
1221
  }
1222
 
1223
+ .hamburger-btn svg {
1224
+ width: 24px;
1225
+ height: 24px;
1226
+ stroke-width: 2;
1227
  }
1228
 
1229
+ .sidebar-toggle-btn svg {
1230
+ width: 20px;
1231
+ height: 20px;
1232
+ stroke-width: 2;
1233
  }
1234
 
1235
+ .btn-primary svg,
1236
+ .btn-refresh svg {
1237
+ width: 16px;
1238
+ height: 16px;
1239
+ stroke-width: 2;
1240
+ vertical-align: middle;
1241
  }
1242
 
1243
+ /* Responsive adjustments for news cards */
1244
+ @media (max-width: 768px) {
1245
+ .news-card {
1246
+ flex-direction: column;
1247
+ }
1248
+
1249
+ .news-card-image {
1250
+ width: 100%;
1251
+ height: 200px;
1252
+ }
1253
+
1254
+ .sentiment-gauge-container {
1255
+ width: 100%;
1256
+ max-width: 300px;
1257
+ }
1258
  }
static/js/app.js CHANGED
The diff for this file is too large to render. See raw diff