Files
aitbc/website/assets/js/web-vitals.js
oib fdc3012780 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
2026-02-15 20:44:04 +01:00

181 lines
6.0 KiB
JavaScript

// Web Vitals monitoring for AITBC
// Tracks Core Web Vitals: LCP, FID, CLS, TTFB, FCP
(function() {
'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: safeValue,
id: metric.id || 'unknown',
delta: safeDelta,
entries: safeEntries.map(e => ({
name: e.name,
startTime: e.startTime,
duration: e.duration
})),
url: window.location.href,
timestamp: new Date().toISOString()
};
const payload = JSON.stringify(data);
// Send to analytics endpoint
if (navigator.sendBeacon) {
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
console.log(`[Web Vitals] ${metric.name}: ${metric.value}`, metric);
}
// Largest Contentful Paint (LCP)
function observeLCP() {
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
sendToAnalytics({
name: 'LCP',
value: lastEntry.renderTime || lastEntry.loadTime,
id: lastEntry.id,
delta: 0,
entries: entries
});
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });
} catch (e) {}
}
// First Input Delay (FID)
function observeFID() {
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
sendToAnalytics({
name: 'FID',
value: entry.processingStart - entry.startTime,
id: entry.id,
delta: 0,
entries: [entry]
});
});
});
observer.observe({ entryTypes: ['first-input'] });
} catch (e) {}
}
// Cumulative Layout Shift (CLS)
function observeCLS() {
try {
let clsValue = 0;
const observer = new PerformanceObserver((list) => {
try {
const entries = list.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
sendToAnalytics({
name: 'CLS',
value: clsValue,
id: 'cls',
delta: 0,
entries: entries
});
} catch (e) {
// CLS measurement failed, but don't crash the whole script
console.log('CLS measurement error:', e.message);
}
});
// Try to observe layout-shift, but handle if it's not supported
try {
observer.observe({ entryTypes: ['layout-shift'] });
} catch (e) {
console.log('Layout-shift observation not supported:', e.message);
// CLS will not be measured, but other metrics will still work
}
} catch (e) {
console.log('CLS observer setup failed:', e.message);
}
}
// Time to First Byte (TTFB)
function measureTTFB() {
try {
const nav = performance.getEntriesByType('navigation')[0];
if (nav) {
sendToAnalytics({
name: 'TTFB',
value: nav.responseStart,
id: 'ttfb',
delta: 0,
entries: [nav]
});
}
} catch (e) {}
}
// First Contentful Paint (FCP)
function observeFCP() {
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (entry.name === 'first-contentful-paint') {
sendToAnalytics({
name: 'FCP',
value: entry.startTime,
id: entry.id,
delta: 0,
entries: [entry]
});
}
});
});
observer.observe({ entryTypes: ['paint'] });
} catch (e) {}
}
// Initialize when page loads
if (document.readyState === 'complete') {
observeLCP();
observeFID();
observeCLS();
observeFCP();
measureTTFB();
} else {
window.addEventListener('load', () => {
observeLCP();
observeFID();
observeCLS();
observeFCP();
measureTTFB();
});
}
})();