feat: add skeleton loaders to marketplace and integrate global header across docs

Marketplace:
- Add skeleton loading states for stats grid and GPU offer cards
- Show animated skeleton placeholders during data fetch
- Add skeleton CSS with shimmer animation and dark mode support
- Wrap stats section in #stats-grid container for skeleton injection

Trade Exchange:
- Replace inline header with data-global-header component
- Switch GPU offers to production API (/api/miners/list)
- Add fallback to demo
This commit is contained in:
oib
2026-02-15 20:44:04 +01:00
parent 7062b2cc78
commit fdc3012780
29 changed files with 19780 additions and 388 deletions

View File

@@ -5,12 +5,16 @@
'use strict';
function sendToAnalytics(metric) {
const safeEntries = Array.isArray(metric.entries) ? metric.entries : [];
const safeValue = Number.isFinite(metric.value) ? Math.round(metric.value) : 0;
const safeDelta = Number.isFinite(metric.delta) ? Math.round(metric.delta) : 0;
const data = {
name: metric.name,
value: Math.round(metric.value),
id: metric.id,
delta: Math.round(metric.delta),
entries: metric.entries.map(e => ({
value: safeValue,
id: metric.id || 'unknown',
delta: safeDelta,
entries: safeEntries.map(e => ({
name: e.name,
startTime: e.startTime,
duration: e.duration
@@ -19,9 +23,27 @@
timestamp: new Date().toISOString()
};
const payload = JSON.stringify(data);
// Send to analytics endpoint
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/web-vitals', JSON.stringify(data));
const blob = new Blob([payload], { type: 'application/json' });
const ok = navigator.sendBeacon('/api/web-vitals', blob);
if (!ok) {
fetch('/api/web-vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true
}).catch(() => {});
}
} else {
fetch('/api/web-vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true
}).catch(() => {});
}
// Also log to console in development