Spaces:
Running
Running
| class CryptoCard extends HTMLElement { | |
| connectedCallback() { | |
| const cryptoData = JSON.parse(this.getAttribute('data-crypto')); | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| .crypto-card { | |
| background: linear-gradient(145deg, #1f2937, #111827); | |
| border-radius: 20px; | |
| padding: 1.5rem; | |
| transition: all 0.3s ease; | |
| cursor: pointer; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .crypto-card:hover { | |
| transform: translateY(-8px); | |
| box-shadow: 15px 15px 30px #0a0e14, -15px -15px 30px #243142; | |
| } | |
| .crypto-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| .crypto-icon { | |
| width: 48px; | |
| height: 48px; | |
| border-radius: 12px; | |
| object-fit: cover; | |
| } | |
| .crypto-name { | |
| font-size: 1.125rem; | |
| font-weight: 700; | |
| color: white; | |
| } | |
| .crypto-symbol { | |
| color: #9ca3af; | |
| font-size: 0.875rem; | |
| } | |
| .crypto-price { | |
| font-size: 1.5rem; | |
| font-weight: 800; | |
| margin-bottom: 0.5rem; | |
| } | |
| .price-change { | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| } | |
| .price-up { | |
| color: #10b981; | |
| } | |
| .price-down { | |
| color: #ef4444; | |
| } | |
| .crypto-stats { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 0.5rem; | |
| margin-top: 1rem; | |
| } | |
| .stat-label { | |
| color: #9ca3af; | |
| font-size: 0.75rem; | |
| } | |
| .stat-value { | |
| font-weight: 600; | |
| color: white; | |
| } | |
| .sparkline { | |
| height: 40px; | |
| margin-top: 1rem; | |
| } | |
| .watchlist-btn { | |
| background: none; | |
| border: none; | |
| color: #9ca3af; | |
| cursor: pointer; | |
| transition: color 0.3s ease; | |
| } | |
| .watchlist-btn:hover { | |
| color: #3b82f6; | |
| } | |
| .premium-badge { | |
| position: absolute; | |
| top: 1rem; | |
| right: 1rem; | |
| background: linear-gradient(135deg, #fbbf24, #f59e0b); | |
| color: #1f2937; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .quick-actions { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-top: 1rem; | |
| } | |
| </style> | |
| <div class="crypto-card"> | |
| ${cryptoData.market_cap_rank <= 10 ? '<div class="premium-badge">Top 10</div>' : ''} | |
| <div class="crypto-header"> | |
| <img | |
| src="${cryptoData.image}" | |
| alt="${cryptoData.name}" | |
| class="crypto-icon" | |
| > | |
| <div> | |
| <div class="crypto-name">${cryptoData.name}</div> | |
| <div class="crypto-symbol">${cryptoData.symbol.toUpperCase()}</div> | |
| </div> | |
| </div> | |
| <div class="crypto-price">$${cryptoData.current_price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</div> | |
| <div class="price-change ${cryptoData.price_change_percentage_24h >= 0 ? 'price-up' : 'price-down'}"> | |
| ${cryptoData.price_change_percentage_24h >= 0 ? '+' : ''}${cryptoData.price_change_percentage_24h?.toFixed(2) || '0.00'}% | |
| </div> | |
| <div class="crypto-stats"> | |
| <div class="stat-label">Market Cap</div> | |
| <div class="stat-value">$${(cryptoData.market_cap / 1000000000).toFixed(2)}B</div> | |
| <div class="stat-label">24h Volume</div> | |
| <div class="stat-value">$${(cryptoData.total_volume / 1000000).toFixed(2)}M</div> | |
| </div> | |
| <div class="sparkline" id="sparkline-${cryptoData.id}"></div> | |
| <div class="quick-actions"> | |
| <button class="watchlist-btn"> | |
| <i data-feather="star"></i> | |
| </button> | |
| <button class="watchlist-btn"> | |
| <i data-feather="bell"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| this.setupInteractions(); | |
| this.renderSparkline(cryptoData); | |
| } | |
| setupInteractions() { | |
| const card = this.shadowRoot.querySelector('.crypto-card'); | |
| const watchlistBtn = this.shadowRoot.querySelector('.watchlist-btn'); | |
| watchlistBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| this.toggleWatchlist(); | |
| }); | |
| card.addEventListener('click', () => { | |
| this.viewCryptoDetails(); | |
| }); | |
| } | |
| renderSparkline(cryptoData) { | |
| const sparklineContainer = this.shadowRoot.querySelector('.sparkline'); | |
| if (cryptoData.sparkline_in_7d?.price) { | |
| // In a real implementation, you would use a charting library | |
| // For demo, we'll create a simple SVG sparkline | |
| const sparklineData = cryptoData.sparkline_in_7d.price; | |
| const width = 200; | |
| const height = 40; | |
| sparklineContainer.innerHTML = ` | |
| <svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"> | |
| <polyline | |
| fill="none" | |
| stroke="${cryptoData.price_change_percentage_24h >= 0 ? '#10b981' : '#ef4444'}" | |
| stroke-width="2" | |
| points="${sparklineData.map((price, index) => { | |
| const x = (index / (sparklineData.length - 1)) * width},${height - ((price - Math.min(...sparklineData)) / (Math.max(...sparklineData) - Math.min(...sparklineData)) * height}" | |
| /> | |
| </svg> | |
| `; | |
| } | |
| } | |
| toggleWatchlist() { | |
| const starIcon = this.shadowRoot.querySelector('.watchlist-btn i'); | |
| if (starIcon.getAttribute('data-feather') === 'star') { | |
| starIcon.setAttribute('data-feather', 'star'); | |
| } else { | |
| starIcon.setAttribute('data-feather', 'star'); | |
| } | |
| feather.replace(); | |
| } | |
| viewCryptoDetails() { | |
| const cryptoData = JSON.parse(this.getAttribute('data-crypto'))); | |
| // Navigate to crypto details page | |
| window.location.href = `/crypto-details.html?id=${cryptoData.id}`; | |
| } | |
| } | |
| customElements.define('crypto-card', CryptoCard); |