perf: minify JS + inline CSS (-24KB total)
- analytics-tracker.min.js: 12.5KB → 7.5KB (-5KB) - Inline CSS in base.html minified: -19.3KB (whitespace, comments, semicolons) - Total page weight reduced by ~24KB Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
380db6de61
commit
847ecd00fa
243
static/js/analytics-tracker.min.js
vendored
Normal file
243
static/js/analytics-tracker.min.js
vendored
Normal file
@ -0,0 +1,243 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
const HEARTBEAT_INTERVAL = 60000;
|
||||
const SCROLL_DEBOUNCE_MS = 500;
|
||||
const TRACK_ENDPOINT = '/api/analytics/track';
|
||||
const HEARTBEAT_ENDPOINT = '/api/analytics/heartbeat';
|
||||
const SCROLL_ENDPOINT = '/api/analytics/scroll';
|
||||
const ERROR_ENDPOINT = '/api/analytics/error';
|
||||
const PERFORMANCE_ENDPOINT = '/api/analytics/performance';
|
||||
let pageStartTime = Date.now();
|
||||
let currentPageViewId = null;
|
||||
let maxScrollDepth = 0;
|
||||
let scrollTimeout = null;
|
||||
function init() {
|
||||
const pageViewMeta = document.querySelector('meta[name="page-view-id"]');
|
||||
if (pageViewMeta && pageViewMeta.content) {
|
||||
currentPageViewId = parseInt(pageViewMeta.content, 10);
|
||||
}
|
||||
setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
|
||||
document.addEventListener('click', handleClick, true);
|
||||
window.addEventListener('beforeunload', handleUnload);
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
window.onerror = handleError;
|
||||
window.addEventListener('unhandledrejection', handlePromiseRejection);
|
||||
if (document.readyState === 'complete') {
|
||||
trackPerformance();
|
||||
} else {
|
||||
window.addEventListener('load', function() {
|
||||
setTimeout(trackPerformance, 100);
|
||||
});
|
||||
}
|
||||
trackContactClicks();
|
||||
}
|
||||
function handleClick(e) {
|
||||
const target = e.target.closest('a, button, [data-track], input[type="submit"], .clickable, .company-card, nav a');
|
||||
if (!target) return;
|
||||
const data = {
|
||||
type: 'click',
|
||||
page_view_id: currentPageViewId,
|
||||
element_type: getElementType(target, e),
|
||||
element_id: target.id || null,
|
||||
element_text: (target.textContent || '').trim().substring(0, 100) || null,
|
||||
element_class: target.className || null,
|
||||
target_url: target.href || target.closest('a')?.href || null,
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
};
|
||||
sendTracking(data);
|
||||
}
|
||||
function getElementType(target, e) {
|
||||
if (target.closest('nav')) return 'nav';
|
||||
if (target.closest('.company-card')) return 'company_card';
|
||||
if (target.closest('form')) return 'form';
|
||||
if (target.closest('.sidebar')) return 'sidebar';
|
||||
if (target.closest('.dropdown')) return 'dropdown';
|
||||
if (target.closest('.modal')) return 'modal';
|
||||
if (target.tagName === 'A') return 'link';
|
||||
if (target.tagName === 'BUTTON') return 'button';
|
||||
if (target.tagName === 'INPUT') return 'input';
|
||||
if (target.hasAttribute('data-track')) return target.getAttribute('data-track');
|
||||
return target.tagName.toLowerCase();
|
||||
}
|
||||
function handleUnload() {
|
||||
if (!currentPageViewId) return;
|
||||
const timeOnPage = Math.round((Date.now() - pageStartTime) / 1000);
|
||||
const data = JSON.stringify({
|
||||
type: 'page_time',
|
||||
page_view_id: currentPageViewId,
|
||||
time_seconds: timeOnPage
|
||||
});
|
||||
navigator.sendBeacon(TRACK_ENDPOINT, data);
|
||||
if (maxScrollDepth > 0) {
|
||||
navigator.sendBeacon(SCROLL_ENDPOINT, JSON.stringify({
|
||||
page_view_id: currentPageViewId,
|
||||
scroll_depth: maxScrollDepth
|
||||
}));
|
||||
}
|
||||
}
|
||||
function handleVisibilityChange() {
|
||||
if (document.visibilityState === 'hidden' && currentPageViewId) {
|
||||
const timeOnPage = Math.round((Date.now() - pageStartTime) / 1000);
|
||||
sendTracking({
|
||||
type: 'page_time',
|
||||
page_view_id: currentPageViewId,
|
||||
time_seconds: timeOnPage
|
||||
});
|
||||
}
|
||||
}
|
||||
function handleScroll() {
|
||||
if (scrollTimeout) {
|
||||
clearTimeout(scrollTimeout);
|
||||
}
|
||||
scrollTimeout = setTimeout(function() {
|
||||
calculateScrollDepth();
|
||||
}, SCROLL_DEBOUNCE_MS);
|
||||
}
|
||||
function calculateScrollDepth() {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const scrollHeight = document.documentElement.scrollHeight;
|
||||
const clientHeight = document.documentElement.clientHeight;
|
||||
const scrollPercent = Math.round((scrollTop + clientHeight) / scrollHeight * 100);
|
||||
if (scrollPercent > maxScrollDepth) {
|
||||
maxScrollDepth = Math.min(scrollPercent, 100);
|
||||
if (currentPageViewId && (maxScrollDepth === 25 || maxScrollDepth === 50 ||
|
||||
maxScrollDepth === 75 || maxScrollDepth >= 95)) {
|
||||
fetch(SCROLL_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
page_view_id: currentPageViewId,
|
||||
scroll_depth: maxScrollDepth
|
||||
}),
|
||||
credentials: 'same-origin'
|
||||
}).catch(function() {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleError(message, source, lineno, colno, error) {
|
||||
const errorData = {
|
||||
message: message ? message.toString() : 'Unknown error',
|
||||
source: source,
|
||||
lineno: lineno,
|
||||
colno: colno,
|
||||
stack: error && error.stack ? error.stack : null,
|
||||
url: window.location.href
|
||||
};
|
||||
fetch(ERROR_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(errorData),
|
||||
credentials: 'same-origin'
|
||||
}).catch(function() {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
function handlePromiseRejection(event) {
|
||||
const reason = event.reason;
|
||||
handleError(
|
||||
'Unhandled Promise Rejection: ' + (reason && reason.message ? reason.message : String(reason)),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
reason instanceof Error ? reason : null
|
||||
);
|
||||
}
|
||||
function trackPerformance() {
|
||||
if (!currentPageViewId) return;
|
||||
if (!window.performance || !performance.timing) return;
|
||||
const timing = performance.timing;
|
||||
const navStart = timing.navigationStart;
|
||||
const metrics = {
|
||||
page_view_id: currentPageViewId,
|
||||
dom_content_loaded_ms: timing.domContentLoadedEventEnd - navStart,
|
||||
load_time_ms: timing.loadEventEnd - navStart
|
||||
};
|
||||
if (performance.getEntriesByType) {
|
||||
const paintEntries = performance.getEntriesByType('paint');
|
||||
paintEntries.forEach(function(entry) {
|
||||
if (entry.name === 'first-paint') {
|
||||
metrics.first_paint_ms = Math.round(entry.startTime);
|
||||
}
|
||||
if (entry.name === 'first-contentful-paint') {
|
||||
metrics.first_contentful_paint_ms = Math.round(entry.startTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (metrics.load_time_ms > 0 && metrics.load_time_ms < 300000) {
|
||||
fetch(PERFORMANCE_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(metrics),
|
||||
credentials: 'same-origin'
|
||||
}).catch(function() {
|
||||
});
|
||||
}
|
||||
}
|
||||
function trackContactClicks() {
|
||||
document.querySelectorAll('a[href^="mailto:"]').forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
trackConversion('contact_click', 'email', link.href.replace('mailto:', ''));
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('a[href^="tel:"]').forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
trackConversion('contact_click', 'phone', link.href.replace('tel:', ''));
|
||||
});
|
||||
});
|
||||
if (window.location.pathname.startsWith('/company/')) {
|
||||
document.querySelectorAll('a[target="_blank"][href^="http"]').forEach(function(link) {
|
||||
const href = link.href.toLowerCase();
|
||||
if (!href.includes('facebook.com') && !href.includes('linkedin.com') &&
|
||||
!href.includes('instagram.com') && !href.includes('twitter.com')) {
|
||||
link.addEventListener('click', function(e) {
|
||||
trackConversion('contact_click', 'website', link.href);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function trackConversion(eventType, targetType, targetValue) {
|
||||
let companyId = null;
|
||||
const companyMeta = document.querySelector('meta[name="company-id"]');
|
||||
if (companyMeta && companyMeta.content) {
|
||||
companyId = parseInt(companyMeta.content, 10);
|
||||
}
|
||||
fetch('/api/analytics/conversion', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
event_type: eventType,
|
||||
target_type: targetType,
|
||||
target_value: targetValue,
|
||||
company_id: companyId
|
||||
}),
|
||||
credentials: 'same-origin'
|
||||
}).catch(function() {
|
||||
});
|
||||
}
|
||||
function sendHeartbeat() {
|
||||
fetch(HEARTBEAT_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin'
|
||||
}).catch(function() {
|
||||
});
|
||||
}
|
||||
function sendTracking(data) {
|
||||
fetch(TRACK_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'same-origin'
|
||||
}).catch(function() {
|
||||
});
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
1470
templates/base.html
1470
templates/base.html
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user