offerpk3 commited on
Commit
f751c53
·
verified ·
1 Parent(s): 5b222cd

Update index.html

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