offerpk3 commited on
Commit
386e2bb
·
verified ·
1 Parent(s): 3162e89

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +636 -19
index.html CHANGED
@@ -1,19 +1,636 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Live Crypto Tracker</title>
7
+ <!-- Chart.js is not used in this version, but kept for potential future use -->
8
+ <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script> -->
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ min-height: 100vh;
20
+ color: white;
21
+ overflow-x: hidden;
22
+ }
23
+
24
+ .container {
25
+ max-width: 1400px;
26
+ margin: 0 auto;
27
+ padding: 20px;
28
+ }
29
+
30
+ .header {
31
+ text-align: center;
32
+ margin-bottom: 40px;
33
+ position: relative;
34
+ }
35
+
36
+ .header h1 {
37
+ font-size: 3.5rem;
38
+ font-weight: 800;
39
+ background: linear-gradient(45deg, #f093fb 0%, #f5576c 50%, #4facfe 100%);
40
+ -webkit-background-clip: text;
41
+ -webkit-text-fill-color: transparent;
42
+ background-clip: text;
43
+ margin-bottom: 10px;
44
+ animation: glow 2s ease-in-out infinite alternate;
45
+ }
46
+
47
+ @keyframes glow {
48
+ from { filter: drop-shadow(0 0 10px rgba(240, 147, 251, 0.3)); }
49
+ to { filter: drop-shadow(0 0 20px rgba(240, 147, 251, 0.6)); }
50
+ }
51
+
52
+ .subtitle {
53
+ font-size: 1.2rem;
54
+ opacity: 0.9;
55
+ font-weight: 300;
56
+ }
57
+
58
+ .status {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ gap: 8px;
62
+ background: rgba(255, 255, 255, 0.1);
63
+ padding: 8px 16px;
64
+ border-radius: 50px;
65
+ margin-top: 20px;
66
+ backdrop-filter: blur(10px);
67
+ border: 1px solid rgba(255, 255, 255, 0.2);
68
+ font-size: 0.9rem;
69
+ }
70
+
71
+ .status-dot {
72
+ width: 8px;
73
+ height: 8px;
74
+ border-radius: 50%;
75
+ background: #4ade80;
76
+ animation: pulse 2s infinite;
77
+ }
78
+
79
+ .status-dot.error {
80
+ background: #f87171;
81
+ }
82
+
83
+ @keyframes pulse {
84
+ 0%, 100% { opacity: 1; }
85
+ 50% { opacity: 0.5; }
86
+ }
87
+
88
+ .controls {
89
+ display: flex;
90
+ justify-content: center;
91
+ gap: 20px;
92
+ margin-bottom: 30px;
93
+ flex-wrap: wrap;
94
+ }
95
+
96
+ .control-btn {
97
+ background: rgba(255, 255, 255, 0.15);
98
+ border: none;
99
+ color: white;
100
+ padding: 12px 24px;
101
+ border-radius: 25px;
102
+ cursor: pointer;
103
+ font-weight: 600;
104
+ transition: all 0.3s ease;
105
+ backdrop-filter: blur(10px);
106
+ border: 1px solid rgba(255, 255, 255, 0.2);
107
+ }
108
+
109
+ .control-btn:hover {
110
+ background: rgba(255, 255, 255, 0.25);
111
+ transform: translateY(-2px);
112
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
113
+ }
114
+
115
+ .control-btn.active {
116
+ background: linear-gradient(45deg, #f093fb, #f5576c);
117
+ box-shadow: 0 5px 15px rgba(240, 147, 251, 0.4);
118
+ }
119
+
120
+ #toggleAutoUpdateBtn.off {
121
+ background: rgba(255, 255, 255, 0.15);
122
+ box-shadow: none;
123
+ }
124
+
125
+ .crypto-grid {
126
+ display: grid;
127
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
128
+ gap: 25px;
129
+ margin-bottom: 40px;
130
+ }
131
+
132
+ .crypto-card {
133
+ background: rgba(255, 255, 255, 0.1);
134
+ border-radius: 20px;
135
+ padding: 25px;
136
+ backdrop-filter: blur(15px);
137
+ border: 1px solid rgba(255, 255, 255, 0.2);
138
+ transition: all 0.4s ease;
139
+ position: relative;
140
+ overflow: hidden;
141
+ }
142
+
143
+ .crypto-card::before {
144
+ content: '';
145
+ position: absolute;
146
+ top: 0;
147
+ left: -100%;
148
+ width: 100%;
149
+ height: 100%;
150
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
151
+ transition: left 0.5s;
152
+ }
153
+
154
+ .crypto-card:hover::before {
155
+ left: 100%;
156
+ }
157
+
158
+ .crypto-card:hover {
159
+ transform: translateY(-5px);
160
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
161
+ border-color: rgba(255, 255, 255, 0.3);
162
+ }
163
+
164
+ .crypto-header {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 15px;
168
+ margin-bottom: 20px;
169
+ }
170
+
171
+ .crypto-icon {
172
+ width: 50px;
173
+ height: 50px;
174
+ }
175
+
176
+ .crypto-info h3 {
177
+ font-size: 1.3rem;
178
+ font-weight: 700;
179
+ margin-bottom: 5px;
180
+ }
181
+
182
+ .crypto-symbol {
183
+ color: rgba(255, 255, 255, 0.7);
184
+ font-size: 0.9rem;
185
+ font-weight: 500;
186
+ text-transform: uppercase;
187
+ }
188
+
189
+ .price-display {
190
+ text-align: center;
191
+ margin-bottom: 20px;
192
+ }
193
+
194
+ .current-price {
195
+ font-size: 2.2rem;
196
+ font-weight: 800;
197
+ margin-bottom: 10px;
198
+ background: linear-gradient(45deg, #4facfe, #00f2fe);
199
+ -webkit-background-clip: text;
200
+ -webkit-text-fill-color: transparent;
201
+ background-clip: text;
202
+ }
203
+
204
+ .price-change {
205
+ display: flex;
206
+ align-items: center;
207
+ justify-content: center;
208
+ gap: 8px;
209
+ font-weight: 600;
210
+ padding: 8px 16px;
211
+ border-radius: 15px;
212
+ font-size: 0.95rem;
213
+ }
214
+
215
+ .price-change.positive {
216
+ background: rgba(74, 222, 128, 0.2);
217
+ color: #4ade80;
218
+ border: 1px solid rgba(74, 222, 128, 0.3);
219
+ }
220
+
221
+ .price-change.negative {
222
+ background: rgba(248, 113, 113, 0.2);
223
+ color: #f87171;
224
+ border: 1px solid rgba(248, 113, 113, 0.3);
225
+ }
226
+
227
+ .crypto-stats {
228
+ display: grid;
229
+ grid-template-columns: 1fr 1fr;
230
+ gap: 15px;
231
+ margin-top: 20px;
232
+ }
233
+
234
+ .stat-item {
235
+ text-align: center;
236
+ padding: 12px;
237
+ background: rgba(255, 255, 255, 0.05);
238
+ border-radius: 12px;
239
+ border: 1px solid rgba(255, 255, 255, 0.1);
240
+ }
241
+
242
+ .stat-label {
243
+ font-size: 0.8rem;
244
+ opacity: 0.7;
245
+ margin-bottom: 5px;
246
+ text-transform: uppercase;
247
+ letter-spacing: 0.5px;
248
+ }
249
+
250
+ .stat-value {
251
+ font-weight: 700;
252
+ font-size: 1rem;
253
+ }
254
+
255
+ .loading {
256
+ text-align: center;
257
+ padding: 50px;
258
+ font-size: 1.2rem;
259
+ }
260
+
261
+ .spinner {
262
+ display: inline-block;
263
+ width: 40px;
264
+ height: 40px;
265
+ border: 4px solid rgba(255, 255, 255, 0.3);
266
+ border-radius: 50%;
267
+ border-top-color: #f093fb;
268
+ animation: spin 1s ease-in-out infinite;
269
+ margin-bottom: 20px;
270
+ }
271
+
272
+ @keyframes spin {
273
+ to { transform: rotate(360deg); }
274
+ }
275
+
276
+ .error {
277
+ text-align: center;
278
+ padding: 30px;
279
+ background: rgba(248, 113, 113, 0.1);
280
+ border-radius: 15px;
281
+ border: 1px solid rgba(248, 113, 113, 0.3);
282
+ margin: 20px 0;
283
+ }
284
+
285
+ .refresh-btn {
286
+ background: linear-gradient(45deg, #4facfe, #00f2fe);
287
+ border: none;
288
+ color: white;
289
+ padding: 12px 30px;
290
+ border-radius: 25px;
291
+ cursor: pointer;
292
+ font-weight: 600;
293
+ margin-top: 15px;
294
+ transition: all 0.3s ease;
295
+ box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
296
+ }
297
+
298
+ .refresh-btn:hover {
299
+ transform: translateY(-2px);
300
+ box-shadow: 0 8px 25px rgba(79, 172, 254, 0.6);
301
+ }
302
+
303
+ @media (max-width: 768px) {
304
+ .header h1 {
305
+ font-size: 2.5rem;
306
+ }
307
+ .crypto-grid {
308
+ grid-template-columns: 1fr;
309
+ gap: 20px;
310
+ }
311
+ }
312
+ </style>
313
+ </head>
314
+ <body>
315
+ <div class="container">
316
+ <div class="header">
317
+ <h1>CryptoTracker Pro</h1>
318
+ <p class="subtitle">Real-time cryptocurrency price monitoring</p>
319
+ <div class="status">
320
+ <div id="statusDot" class="status-dot"></div>
321
+ <span id="statusText">Connecting...</span>
322
+ </div>
323
+ </div>
324
+
325
+ <div class="controls">
326
+ <button class="control-btn active" onclick="setUpdateInterval(5000, this)">5s</button>
327
+ <button class="control-btn" onclick="setUpdateInterval(10000, this)">10s</button>
328
+ <button class="control-btn" onclick="setUpdateInterval(30000, this)">30s</button>
329
+ <button id="toggleAutoUpdateBtn" class="control-btn active" onclick="toggleAutoUpdate(this)">Auto: ON</button>
330
+ </div>
331
+
332
+ <div id="cryptoContainer">
333
+ <div class="loading">
334
+ <div class="spinner"></div>
335
+ <p>Loading cryptocurrency data...</p>
336
+ </div>
337
+ </div>
338
+ </div>
339
+
340
+ <script>
341
+ // --- CONFIGURATION ---
342
+
343
+ // Primary API: CoinGecko (Free, No Key Required, Generous Limits)
344
+ const COINGECKO_BASE_URL = 'https://api.coingecko.com/api/v3';
345
+ const COINGECKO_IDS = ['bitcoin', 'ethereum', 'binancecoin', 'ripple', 'cardano', 'solana', 'dogecoin', 'polkadot'];
346
+
347
+ // Fallback API: CoinMarketCap (Requires free API key, limited credits)
348
+ const CMC_BASE_URL = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest';
349
+ const CMC_SYMBOLS = ['BTC', 'ETH', 'BNB', 'XRP', 'ADA', 'SOL', 'DOGE', 'DOT'];
350
+ const CMC_API_KEYS = [
351
+ '51a08ddd-bb80-4f8e-8acf-2d7d7e54ce1d', // Sandbox Key (use your own keys)
352
+ 'YOUR_SECOND_API_KEY_HERE',
353
+ 'YOUR_THIRD_API_KEY_HERE',
354
+ ];
355
+ const PROXY_URL = 'https://api.allorigins.win/get?url='; // CORS Proxy
356
+
357
+ // --- APPLICATION STATE ---
358
+ let updateInterval = 5000;
359
+ let autoUpdate = true;
360
+ let updateTimer;
361
+ let currentCmcKeyIndex = 0;
362
+
363
+ // --- DOM ELEMENTS ---
364
+ const statusText = document.getElementById('statusText');
365
+ const statusDot = document.getElementById('statusDot');
366
+ const cryptoContainer = document.getElementById('cryptoContainer');
367
+
368
+ // --- CORE LOGIC ---
369
+
370
+ async function fetchCryptoData() {
371
+ statusText.textContent = 'Fetching data...';
372
+ statusDot.classList.remove('error');
373
+
374
+ try {
375
+ // 1. Try fetching from CoinGecko first
376
+ const data = await fetchFromCoinGecko();
377
+ displayCryptoData(data);
378
+ statusText.textContent = `Live (Updated: ${new Date().toLocaleTimeString()}) - CoinGecko`;
379
+ return;
380
+ } catch (error) {
381
+ console.warn('CoinGecko API failed:', error.message, 'Falling back to CoinMarketCap.');
382
+
383
+ // 2. If CoinGecko fails, try CoinMarketCap
384
+ try {
385
+ const data = await fetchFromCoinMarketCap();
386
+ if (data) {
387
+ displayCryptoData(data);
388
+ // Status text is updated within the CMC function itself
389
+ } else {
390
+ throw new Error("All CMC keys failed.");
391
+ }
392
+ } catch (cmcError) {
393
+ console.error('All API sources have failed:', cmcError.message);
394
+ displayError("Could not fetch data from any source. The APIs might be down or you may have exceeded rate limits.");
395
+ statusText.textContent = 'Connection Error';
396
+ statusDot.classList.add('error');
397
+ }
398
+ }
399
+ }
400
+
401
+ async function fetchFromCoinGecko() {
402
+ const ids = COINGECKO_IDS.join(',');
403
+ const url = `${COINGECKO_BASE_URL}/coins/markets?vs_currency=usd&ids=${ids}`;
404
+
405
+ const response = await fetch(url);
406
+ if (!response.ok) {
407
+ throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
408
+ }
409
+ const data = await response.json();
410
+
411
+ // Transform CoinGecko data to our standard format
412
+ return transformGeckoData(data);
413
+ }
414
+
415
+ async function fetchFromCoinMarketCap() {
416
+ const symbols = CMC_SYMBOLS.join(',');
417
+ const url = `${CMC_BASE_URL}?symbol=${symbols}&convert=USD`;
418
+
419
+ for (let i = 0; i < CMC_API_KEYS.length; i++) {
420
+ const keyIndex = (currentCmcKeyIndex + i) % CMC_API_KEYS.length;
421
+ const apiKey = CMC_API_KEYS[keyIndex];
422
+
423
+ if (apiKey.startsWith('YOUR_')) continue; // Skip placeholder keys
424
+
425
+ try {
426
+ console.log(`Trying CMC API Key #${keyIndex + 1}`);
427
+ const proxyUrl = `${PROXY_URL}${encodeURIComponent(url)}`;
428
+ const response = await fetch(proxyUrl, {
429
+ headers: { 'X-CMC_PRO_API_KEY': apiKey }
430
+ });
431
+
432
+ if (!response.ok) throw new Error(`Proxy error with status: ${response.status}`);
433
+
434
+ const proxyData = await response.json();
435
+ if (!proxyData.contents) throw new Error('Proxy returned empty contents.');
436
+
437
+ const data = JSON.parse(proxyData.contents);
438
+ if (data.status.error_code !== 0) {
439
+ throw new Error(`CMC API Error: ${data.status.error_message}`);
440
+ }
441
+
442
+ console.log(`Success with CMC API Key #${keyIndex + 1}`);
443
+ currentCmcKeyIndex = keyIndex; // Remember the working key for next time
444
+ statusText.textContent = `Live (Updated: ${new Date().toLocaleTimeString()}) - CMC Key ${keyIndex + 1}`;
445
+ return data.data; // Success!
446
+
447
+ } catch (error) {
448
+ console.warn(`CMC API Key #${keyIndex + 1} failed:`, error.message);
449
+ // Continue to the next key
450
+ }
451
+ }
452
+ return null; // All keys failed
453
+ }
454
+
455
+ // --- DATA TRANSFORMATION ---
456
+
457
+ function transformGeckoData(geckoData) {
458
+ const transformed = {};
459
+ geckoData.forEach(coin => {
460
+ const symbol = coin.symbol.toUpperCase();
461
+ transformed[symbol] = {
462
+ name: coin.name,
463
+ symbol: symbol,
464
+ image: coin.image, // Added image from Gecko
465
+ quote: {
466
+ USD: {
467
+ price: coin.current_price,
468
+ percent_change_24h: coin.price_change_percentage_24h,
469
+ market_cap: coin.market_cap,
470
+ volume_24h: coin.total_volume,
471
+ high_24h: coin.high_24h, // Real data
472
+ low_24h: coin.low_24h, // Real data
473
+ }
474
+ }
475
+ };
476
+ });
477
+ return transformed;
478
+ }
479
+
480
+ // --- UI RENDERING ---
481
+
482
+ function displayCryptoData(data) {
483
+ const cryptoGrid = document.createElement('div');
484
+ cryptoGrid.className = 'crypto-grid';
485
+
486
+ const symbolsToDisplay = Object.keys(data);
487
+
488
+ symbolsToDisplay.forEach(symbol => {
489
+ if (data[symbol]) {
490
+ const crypto = data[symbol];
491
+ const quote = crypto.quote.USD;
492
+ const card = createCryptoCard(crypto, quote);
493
+ cryptoGrid.appendChild(card);
494
+ }
495
+ });
496
+
497
+ cryptoContainer.innerHTML = '';
498
+ cryptoContainer.appendChild(cryptoGrid);
499
+ }
500
+
501
+ function createCryptoCard(crypto, quote) {
502
+ const card = document.createElement('div');
503
+ card.className = 'crypto-card';
504
+
505
+ const priceChange = quote.percent_change_24h || 0;
506
+ const isPositive = priceChange >= 0;
507
+ const changeClass = isPositive ? 'positive' : 'negative';
508
+ const changeSymbol = isPositive ? '↗' : '↘';
509
+
510
+ // Use image from API if available, otherwise default to symbol initial
511
+ const iconHtml = crypto.image
512
+ ? `<img src="${crypto.image}" alt="${crypto.symbol}" class="crypto-icon">`
513
+ : `<div class="crypto-icon" style="display:flex;align-items:center;justify-content:center;font-size:1.5rem;background:linear-gradient(45deg, #667eea, #764ba2);border-radius:50%;">${crypto.symbol.charAt(0)}</div>`;
514
+
515
+ card.innerHTML = `
516
+ <div class="crypto-header">
517
+ ${iconHtml}
518
+ <div class="crypto-info">
519
+ <h3>${crypto.name}</h3>
520
+ <div class="crypto-symbol">${crypto.symbol}</div>
521
+ </div>
522
+ </div>
523
+ <div class="price-display">
524
+ <div class="current-price">$${formatPrice(quote.price)}</div>
525
+ <div class="price-change ${changeClass}">
526
+ <span>${changeSymbol}</span>
527
+ <span>${Math.abs(priceChange).toFixed(2)}% (24h)</span>
528
+ </div>
529
+ </div>
530
+ <div class="crypto-stats">
531
+ <div class="stat-item">
532
+ <div class="stat-label">24h High</div>
533
+ <div class="stat-value">$${formatPrice(quote.high_24h)}</div>
534
+ </div>
535
+ <div class="stat-item">
536
+ <div class="stat-label">24h Low</div>
537
+ <div class="stat-value">$${formatPrice(quote.low_24h)}</div>
538
+ </div>
539
+ <div class="stat-item">
540
+ <div class="stat-label">Market Cap</div>
541
+ <div class="stat-value">$${formatMarketCap(quote.market_cap)}</div>
542
+ </div>
543
+ <div class="stat-item">
544
+ <div class="stat-label">Volume (24h)</div>
545
+ <div class="stat-value">$${formatMarketCap(quote.volume_24h)}</div>
546
+ </div>
547
+ </div>
548
+ `;
549
+ return card;
550
+ }
551
+
552
+ function displayError(message) {
553
+ cryptoContainer.innerHTML = `
554
+ <div class="error">
555
+ <h3>Oops! Something went wrong.</h3>
556
+ <p>${message}</p>
557
+ <button class="refresh-btn" onclick="location.reload()">Refresh Page</button>
558
+ </div>
559
+ `;
560
+ }
561
+
562
+ // --- UTILITY & CONTROL FUNCTIONS ---
563
+
564
+ function formatPrice(price) {
565
+ if (price == null) return 'N/A';
566
+ if (price >= 1) {
567
+ return price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
568
+ }
569
+ return price.toPrecision(4);
570
+ }
571
+
572
+ function formatMarketCap(value) {
573
+ if (value == null) return 'N/A';
574
+ if (value >= 1e12) return (value / 1e12).toFixed(2) + 'T';
575
+ if (value >= 1e9) return (value / 1e9).toFixed(2) + 'B';
576
+ if (value >= 1e6) return (value / 1e6).toFixed(2) + 'M';
577
+ if (value >= 1e3) return (value / 1e3).toFixed(2) + 'K';
578
+ return value.toFixed(2);
579
+ }
580
+
581
+ function setUpdateInterval(interval, clickedButton) {
582
+ updateInterval = interval;
583
+ document.querySelectorAll('.controls .control-btn').forEach(btn => {
584
+ if (btn.id !== 'toggleAutoUpdateBtn') btn.classList.remove('active');
585
+ });
586
+ clickedButton.classList.add('active');
587
+
588
+ if (autoUpdate) {
589
+ clearInterval(updateTimer);
590
+ startAutoUpdate();
591
+ }
592
+ }
593
+
594
+ function toggleAutoUpdate(button) {
595
+ autoUpdate = !autoUpdate;
596
+ if (autoUpdate) {
597
+ button.textContent = 'Auto: ON';
598
+ button.classList.add('active');
599
+ button.classList.remove('off');
600
+ fetchCryptoData(); // Fetch immediately
601
+ startAutoUpdate();
602
+ } else {
603
+ button.textContent = 'Auto: OFF';
604
+ button.classList.remove('active');
605
+ button.classList.add('off');
606
+ clearInterval(updateTimer);
607
+ statusText.textContent = "Auto-update paused";
608
+ }
609
+ }
610
+
611
+ function startAutoUpdate() {
612
+ if (updateTimer) clearInterval(updateTimer);
613
+ updateTimer = setInterval(fetchCryptoData, updateInterval);
614
+ }
615
+
616
+ // --- INITIALIZATION ---
617
+
618
+ // Initial fetch
619
+ fetchCryptoData();
620
+ if (autoUpdate) {
621
+ startAutoUpdate();
622
+ }
623
+
624
+ // Fallback to show demo data if all APIs fail on initial load
625
+ setTimeout(() => {
626
+ if (cryptoContainer.innerHTML.includes('Loading')) {
627
+ console.warn("APIs failed to load in time. Displaying demo data as a last resort.");
628
+ displayError("Live data could not be loaded. This might be a temporary network issue or CORS problem. Please try refreshing.");
629
+ statusText.textContent = 'Demo Mode';
630
+ statusDot.classList.add('error');
631
+ }
632
+ }, 8000); // Increased timeout to give APIs more time
633
+
634
+ </script>
635
+ </body>
636
+ </html>