| class CustomStatCard extends HTMLElement { |
| constructor() { |
| super(); |
| } |
|
|
| connectedCallback() { |
| this.attachShadow({ mode: 'open' }); |
| |
| const title = this.getAttribute('title') || 'Metric'; |
| const value = this.getAttribute('value') || '0'; |
| const color = this.getAttribute('color') || 'green'; |
| const icon = this.getAttribute('icon') || 'activity'; |
| const trend = this.getAttribute('trend') || ''; |
| |
| const colorMap = { |
| green: '#22c55e', |
| orange: '#f97316', |
| gold: '#ffd700', |
| red: '#ef4444', |
| blue: '#3b82f6', |
| purple: '#a855f7' |
| }; |
| |
| const accentColor = colorMap[color] || colorMap.green; |
| |
| let trendHtml = ''; |
| if (trend) { |
| const isPositive = trend.includes('+') || trend === 'record'; |
| const isNegative = trend.includes('-'); |
| const isNeutral = trend === 'stable'; |
| |
| if (isPositive) { |
| trendHtml = `<div class="text-xs text-neon-green mt-1 flex items-center"> |
| <svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg> |
| ${trend} |
| </div>`; |
| } else if (isNegative) { |
| trendHtml = `<div class="text-xs text-neon-red mt-1 flex items-center"> |
| <svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 18 13.5 8.5 8.5 13.5 1 6"></polyline><polyline points="17 18 23 18 23 12"></polyline></svg> |
| ${trend} |
| </div>`; |
| } else { |
| trendHtml = `<div class="text-xs text-gray-400 mt-1">${trend}</div>`; |
| } |
| } |
| |
| this.shadowRoot.innerHTML = ` |
| <style> |
| :host { |
| display: block; |
| } |
| .card { |
| background-color: #0a0a0a; |
| border: 1px solid #1f2937; |
| border-radius: 0.5rem; |
| padding: 1rem; |
| position: relative; |
| overflow: hidden; |
| transition: all 0.2s; |
| } |
| .card:hover { |
| border-color: ${accentColor}; |
| box-shadow: 0 0 20px rgba(${parseInt(accentColor.slice(1,3),16)}, ${parseInt(accentColor.slice(3,5),16)}, ${parseInt(accentColor.slice(5,7),16)}, 0.15); |
| } |
| .icon-bg { |
| position: absolute; |
| right: 8px; |
| top: 8px; |
| opacity: 0.1; |
| transition: opacity 0.2s; |
| } |
| .card:hover .icon-bg { |
| opacity: 0.2; |
| } |
| .title { |
| color: #9ca3af; |
| font-size: 0.65rem; |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| font-weight: 700; |
| } |
| .value { |
| color: #fff; |
| font-size: 1.25rem; |
| font-weight: 700; |
| margin-top: 0.5rem; |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
| } |
| </style> |
| <div class="card"> |
| <div class="icon-bg"> |
| <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="${accentColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" data-feather="${icon}"></svg> |
| </div> |
| <div class="title">${title}</div> |
| <div class="value">${value}</div> |
| ${trendHtml} |
| </div> |
| `; |
| |
| |
| if (typeof feather !== 'undefined') { |
| feather.replace(); |
| } |
| } |
| } |
|
|
| customElements.define('custom-stat-card', CustomStatCard); |