563 lines
20 KiB
HTML
563 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Download Analytics - Claude Code Templates</title>
|
|
|
|
<!-- Google tag (gtag.js) -->
|
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-YWW6FV2SGN"></script>
|
|
<script>
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag('js', new Date());
|
|
|
|
gtag('config', 'G-YWW6FV2SGN');
|
|
</script>
|
|
|
|
<!-- Favicon -->
|
|
<link rel="icon" type="image/x-icon" href="static/favicon/favicon.ico">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="static/favicon/favicon-16x16.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="static/favicon/favicon-32x32.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="static/favicon/apple-touch-icon.png">
|
|
<link rel="icon" type="image/png" sizes="192x192" href="static/favicon/android-chrome-192x192.png">
|
|
<link rel="icon" type="image/png" sizes="512x512" href="static/favicon/android-chrome-512x512.png">
|
|
|
|
<meta name="description" content="Detailed download analytics and usage statistics for Claude Code Templates.">
|
|
|
|
<!-- Open Graph / Facebook -->
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:url" content="https://aitmpl.com/download-stats.html">
|
|
<meta property="og:title" content="Download Analytics - Claude Code Templates">
|
|
<meta property="og:description" content="Detailed download analytics and usage statistics for Claude Code Templates.">
|
|
<meta property="og:image" content="https://aitmpl.com/images/social-preview.png">
|
|
<meta property="og:image:width" content="1200">
|
|
<meta property="og:image:height" content="630">
|
|
<meta property="og:site_name" content="Claude Code Templates">
|
|
|
|
<!-- Twitter -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="Download Analytics - Claude Code Templates">
|
|
<meta name="twitter:description" content="Detailed download analytics and usage statistics for Claude Code Templates.">
|
|
<meta name="twitter:image" content="https://aitmpl.com/images/social-preview.png">
|
|
|
|
<link rel="stylesheet" href="css/styles.css">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
|
|
<!-- Hotjar Tracking Code for https://aitmpl.com -->
|
|
<script>
|
|
(function(h,o,t,j,a,r){
|
|
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
|
h._hjSettings={hjid:6519181,hjsv:6};
|
|
a=o.getElementsByTagName('head')[0];
|
|
r=o.createElement('script');r.async=1;
|
|
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
|
a.appendChild(r);
|
|
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
|
</script>
|
|
|
|
<style>
|
|
.analytics-page {
|
|
min-height: 100vh;
|
|
padding: 2rem 0;
|
|
}
|
|
|
|
.analytics-header {
|
|
text-align: center;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.analytics-title {
|
|
font-size: 2.5rem;
|
|
color: var(--text-accent);
|
|
margin-bottom: 1rem;
|
|
font-weight: 300;
|
|
}
|
|
|
|
.analytics-subtitle {
|
|
color: var(--text-secondary);
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.back-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: var(--text-info);
|
|
text-decoration: none;
|
|
margin-bottom: 2rem;
|
|
padding: 0.5rem 0;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.back-link:hover {
|
|
color: var(--text-accent);
|
|
}
|
|
|
|
.metrics-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.metric-card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-primary);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.metric-value {
|
|
font-size: 2.5rem;
|
|
font-weight: bold;
|
|
color: var(--text-accent);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.metric-label {
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.metric-change {
|
|
font-size: 0.8rem;
|
|
margin-top: 0.5rem;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.change-positive {
|
|
color: var(--text-success);
|
|
}
|
|
|
|
.change-neutral {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.charts-section {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 2rem;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.chart-container {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-primary);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.chart-title {
|
|
color: var(--text-accent);
|
|
font-size: 1.2rem;
|
|
margin-bottom: 1rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.chart-canvas {
|
|
position: relative;
|
|
height: 300px;
|
|
}
|
|
|
|
.detailed-tables {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 2rem;
|
|
}
|
|
|
|
.table-container {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-primary);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table-header {
|
|
background: var(--bg-tertiary);
|
|
padding: 1rem;
|
|
border-bottom: 1px solid var(--border-primary);
|
|
}
|
|
|
|
.table-title {
|
|
color: var(--text-accent);
|
|
font-size: 1.1rem;
|
|
margin: 0;
|
|
}
|
|
|
|
.table-content {
|
|
padding: 0;
|
|
}
|
|
|
|
.data-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid var(--border-secondary);
|
|
}
|
|
|
|
.data-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.data-label {
|
|
color: var(--text-primary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.data-value {
|
|
color: var(--text-accent);
|
|
font-weight: bold;
|
|
}
|
|
|
|
.progress-bar {
|
|
width: 60px;
|
|
height: 4px;
|
|
background: var(--border-primary);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: var(--text-accent);
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.charts-section,
|
|
.detailed-tables {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.analytics-title {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.metrics-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
|
|
.metric-card {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.metric-value {
|
|
font-size: 2rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="analytics-page">
|
|
<div class="container">
|
|
<a href="/" class="back-link">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"/>
|
|
</svg>
|
|
Back to Templates
|
|
</a>
|
|
|
|
<div class="analytics-header">
|
|
<h1 class="analytics-title">📊 Download Analytics</h1>
|
|
<p class="analytics-subtitle">Comprehensive usage statistics and trends</p>
|
|
</div>
|
|
|
|
<div class="metrics-grid" id="metricsGrid">
|
|
<!-- Metrics will be populated by JavaScript -->
|
|
</div>
|
|
|
|
<div class="charts-section">
|
|
<div class="chart-container">
|
|
<h3 class="chart-title">Downloads by Component Type</h3>
|
|
<div class="chart-canvas">
|
|
<canvas id="typeChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<h3 class="chart-title">Downloads Over Time</h3>
|
|
<div class="chart-canvas">
|
|
<canvas id="timeChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="detailed-tables">
|
|
<div class="table-container">
|
|
<div class="table-header">
|
|
<h3 class="table-title">🏆 Popular Components</h3>
|
|
</div>
|
|
<div class="table-content" id="popularComponentsTable">
|
|
<!-- Table will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-container">
|
|
<div class="table-header">
|
|
<h3 class="table-title">📊 Category Breakdown</h3>
|
|
</div>
|
|
<div class="table-content" id="categoryBreakdownTable">
|
|
<!-- Table will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-updated" style="margin-top: 2rem; text-align: center;">
|
|
<span>Data last updated: <span id="lastUpdatedTime">Loading...</span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let statsData = null;
|
|
|
|
// Load analytics data
|
|
async function loadAnalyticsData() {
|
|
try {
|
|
const response = await fetch('/analytics/download-stats.json');
|
|
statsData = await response.json();
|
|
|
|
populateMetrics();
|
|
createCharts();
|
|
populateTables();
|
|
updateTimestamp();
|
|
|
|
} catch (error) {
|
|
console.error('Failed to load analytics data:', error);
|
|
showErrorState();
|
|
}
|
|
}
|
|
|
|
function populateMetrics() {
|
|
const metricsGrid = document.getElementById('metricsGrid');
|
|
const totalComponents = Object.keys(statsData.downloads_by_component).length;
|
|
const todayDate = new Date().toISOString().split('T')[0];
|
|
const todayDownloads = statsData.downloads_by_date[todayDate] || 0;
|
|
|
|
const metrics = [
|
|
{
|
|
value: statsData.total_downloads,
|
|
label: 'Total Downloads',
|
|
change: `+${statsData.data_points} tracked events`,
|
|
changeType: 'positive'
|
|
},
|
|
{
|
|
value: totalComponents,
|
|
label: 'Unique Components',
|
|
change: 'across all categories',
|
|
changeType: 'neutral'
|
|
},
|
|
{
|
|
value: todayDownloads,
|
|
label: 'Downloads Today',
|
|
change: new Date().toLocaleDateString(),
|
|
changeType: 'positive'
|
|
},
|
|
{
|
|
value: Object.keys(statsData.downloads_by_date).length,
|
|
label: 'Active Days',
|
|
change: 'with recorded activity',
|
|
changeType: 'neutral'
|
|
}
|
|
];
|
|
|
|
metricsGrid.innerHTML = metrics.map(metric => `
|
|
<div class="metric-card">
|
|
<div class="metric-value">${metric.value}</div>
|
|
<div class="metric-label">${metric.label}</div>
|
|
<div class="metric-change change-${metric.changeType}">${metric.change}</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function createCharts() {
|
|
createTypeChart();
|
|
createTimeChart();
|
|
}
|
|
|
|
function createTypeChart() {
|
|
const ctx = document.getElementById('typeChart').getContext('2d');
|
|
const data = statsData.downloads_by_type;
|
|
const labels = Object.keys(data).filter(key => data[key] > 0);
|
|
const values = labels.map(key => data[key]);
|
|
|
|
new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: labels.map(label => label.charAt(0).toUpperCase() + label.slice(1)),
|
|
datasets: [{
|
|
data: values,
|
|
backgroundColor: [
|
|
'#d57455',
|
|
'#f97316',
|
|
'#a5d6ff',
|
|
'#3fb950',
|
|
'#f85149',
|
|
'#7d8590'
|
|
],
|
|
borderWidth: 2,
|
|
borderColor: '#0d1117'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
color: '#c9d1d9',
|
|
padding: 20
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function createTimeChart() {
|
|
const ctx = document.getElementById('timeChart').getContext('2d');
|
|
const dates = Object.keys(statsData.downloads_by_date).sort();
|
|
const values = dates.map(date => statsData.downloads_by_date[date]);
|
|
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: dates.map(date => new Date(date).toLocaleDateString()),
|
|
datasets: [{
|
|
label: 'Downloads',
|
|
data: values,
|
|
borderColor: '#d57455',
|
|
backgroundColor: 'rgba(213, 116, 85, 0.1)',
|
|
borderWidth: 2,
|
|
fill: true,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
color: '#c9d1d9'
|
|
},
|
|
grid: {
|
|
color: '#30363d'
|
|
}
|
|
},
|
|
x: {
|
|
ticks: {
|
|
color: '#c9d1d9'
|
|
},
|
|
grid: {
|
|
color: '#30363d'
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
color: '#c9d1d9'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function populateTables() {
|
|
populatePopularComponents();
|
|
populateCategoryBreakdown();
|
|
}
|
|
|
|
function populatePopularComponents() {
|
|
const table = document.getElementById('popularComponentsTable');
|
|
const components = Object.entries(statsData.downloads_by_component)
|
|
.sort(([,a], [,b]) => b - a)
|
|
.slice(0, 10);
|
|
|
|
const maxDownloads = Math.max(...Object.values(statsData.downloads_by_component));
|
|
|
|
table.innerHTML = components.map(([name, downloads]) => {
|
|
const percentage = (downloads / maxDownloads) * 100;
|
|
return `
|
|
<div class="data-row">
|
|
<div class="data-label">${name}</div>
|
|
<div style="display: flex; align-items: center;">
|
|
<div class="data-value">${downloads}</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${percentage}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function populateCategoryBreakdown() {
|
|
const table = document.getElementById('categoryBreakdownTable');
|
|
const categories = Object.entries(statsData.downloads_by_type)
|
|
.filter(([,count]) => count > 0)
|
|
.sort(([,a], [,b]) => b - a);
|
|
|
|
const maxDownloads = Math.max(...Object.values(statsData.downloads_by_type));
|
|
|
|
table.innerHTML = categories.map(([type, downloads]) => {
|
|
const percentage = (downloads / maxDownloads) * 100;
|
|
const emoji = {
|
|
'agent': '🤖',
|
|
'command': '⚡',
|
|
'mcp': '🔌',
|
|
'template': '📦',
|
|
'health-check': '🔍',
|
|
'analytics': '📊'
|
|
}[type] || '📄';
|
|
|
|
return `
|
|
<div class="data-row">
|
|
<div class="data-label">${emoji} ${type.charAt(0).toUpperCase() + type.slice(1)}</div>
|
|
<div style="display: flex; align-items: center;">
|
|
<div class="data-value">${downloads}</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${percentage}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function updateTimestamp() {
|
|
const timestamp = new Date(statsData.last_updated);
|
|
document.getElementById('lastUpdatedTime').textContent = timestamp.toLocaleString();
|
|
}
|
|
|
|
function showErrorState() {
|
|
document.getElementById('metricsGrid').innerHTML = `
|
|
<div class="metric-card" style="grid-column: 1 / -1; text-align: center; padding: 2rem;">
|
|
<div style="color: var(--text-error); font-size: 1.5rem; margin-bottom: 1rem;">⚠️</div>
|
|
<div class="metric-label">Failed to load analytics data</div>
|
|
<div class="metric-change change-neutral">Please try again later</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Load data when page loads
|
|
loadAnalyticsData();
|
|
</script>
|
|
</body>
|
|
</html> |