<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tokmd Analysis Report</title>
<style>
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-card: #0f3460;
--text-primary: #e6e6e6;
--text-secondary: #a0a0a0;
--accent: #4c9aff;
--accent-hover: #357abd;
--success: #4caf50;
--warning: #ff9800;
--danger: #f44336;
--border: #2a2a4a;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
}
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
header {
text-align: center;
padding: 40px 20px;
background: linear-gradient(135deg, var(--bg-secondary), var(--bg-card));
border-bottom: 1px solid var(--border);
margin-bottom: 30px;
}
header h1 { font-size: 2.5rem; margin-bottom: 10px; }
header .timestamp { color: var(--text-secondary); font-size: 0.9rem; }
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.metric-card {
background: var(--bg-card);
border-radius: 12px;
padding: 20px;
text-align: center;
border: 1px solid var(--border);
transition: transform 0.2s, box-shadow 0.2s;
}
.metric-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(76, 154, 255, 0.2);
}
.metric-card .value {
font-size: 2rem;
font-weight: bold;
color: var(--accent);
display: block;
}
.metric-card .label {
color: var(--text-secondary);
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.section {
background: var(--bg-secondary);
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
border: 1px solid var(--border);
}
.section h2 {
font-size: 1.3rem;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--accent);
display: inline-block;
}
#treemap {
width: 100%;
height: 400px;
background: var(--bg-primary);
border-radius: 8px;
overflow: hidden;
}
.treemap-cell {
position: absolute;
overflow: hidden;
border: 1px solid var(--bg-primary);
transition: opacity 0.2s;
cursor: pointer;
}
.treemap-cell:hover { opacity: 0.85; }
.treemap-label {
padding: 4px 6px;
font-size: 11px;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search-box {
width: 100%;
padding: 12px 16px;
font-size: 1rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
margin-bottom: 16px;
}
.search-box:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(76, 154, 255, 0.2);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid var(--border);
}
th {
background: var(--bg-card);
color: var(--accent);
font-weight: 600;
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 0.5px;
cursor: pointer;
}
th:hover { background: var(--accent); color: white; }
tr:hover { background: rgba(76, 154, 255, 0.1); }
.num { text-align: right; font-family: 'SF Mono', Monaco, monospace; }
.path { font-family: 'SF Mono', Monaco, monospace; font-size: 0.85rem; }
.lang-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
}
.hidden { display: none; }
footer {
text-align: center;
padding: 30px;
color: var(--text-secondary);
font-size: 0.85rem;
}
footer a { color: var(--accent); text-decoration: none; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<header>
<h1>tokmd Analysis Report</h1>
<div class="timestamp">Generated: {{TIMESTAMP}}</div>
</header>
<div class="container">
<div class="metrics-grid">
{{METRICS_CARDS}}
</div>
<div class="section">
<h2>Code Distribution</h2>
<div id="treemap"></div>
</div>
<div class="section">
<h2>Files</h2>
<input type="text" class="search-box" id="search" placeholder="Filter by path, module, or language...">
<table id="files-table">
<thead>
<tr>
<th data-sort="path">Path</th>
<th data-sort="module">Module</th>
<th data-sort="lang">Lang</th>
<th data-sort="lines" class="num">Lines</th>
<th data-sort="code" class="num">Code</th>
<th data-sort="tokens" class="num">Tokens</th>
<th data-sort="bytes" class="num">Bytes</th>
</tr>
</thead>
<tbody>
{{TABLE_ROWS}}
</tbody>
</table>
</div>
</div>
<footer>
Generated by <a href="https://github.com/EffortlessMetrics/tokmd">tokmd</a>
</footer>
<script>
const REPORT_DATA = {{REPORT_JSON}};
const LANG_COLORS = {
'Rust': '#dea584',
'JavaScript': '#f1e05a',
'TypeScript': '#3178c6',
'Python': '#3572A5',
'Go': '#00ADD8',
'Java': '#b07219',
'C': '#555555',
'C++': '#f34b7d',
'C#': '#178600',
'Ruby': '#701516',
'PHP': '#4F5D95',
'Swift': '#F05138',
'Kotlin': '#A97BFF',
'Scala': '#c22d40',
'HTML': '#e34c26',
'CSS': '#563d7c',
'SCSS': '#c6538c',
'JSON': '#292929',
'YAML': '#cb171e',
'TOML': '#9c4221',
'Markdown': '#083fa1',
'Shell': '#89e051',
'SQL': '#e38c00',
};
function getLangColor(lang) {
return LANG_COLORS[lang] || '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
}
function squarify(data, x, y, width, height) {
if (data.length === 0 || width <= 0 || height <= 0) return [];
const total = data.reduce((sum, d) => sum + d.value, 0);
if (total === 0) return [];
const rects = [];
let remaining = [...data];
let cx = x, cy = y, cw = width, ch = height;
while (remaining.length > 0) {
const vertical = ch > cw;
const side = vertical ? ch : cw;
const scale = (cw * ch) / total;
let row = [];
let rowArea = 0;
let worst = Infinity;
for (const item of remaining) {
const testRow = [...row, item];
const testArea = rowArea + item.value * scale;
const testWorst = getWorst(testRow, testArea, side, scale);
if (testWorst <= worst) {
row = testRow;
rowArea = testArea;
worst = testWorst;
} else {
break;
}
}
const rowSide = rowArea / side;
let offset = 0;
for (const item of row) {
const itemSize = (item.value * scale) / rowSide;
if (vertical) {
rects.push({ ...item, x: cx, y: cy + offset, w: rowSide, h: itemSize });
} else {
rects.push({ ...item, x: cx + offset, y: cy, w: itemSize, h: rowSide });
}
offset += itemSize;
}
if (vertical) {
cx += rowSide;
cw -= rowSide;
} else {
cy += rowSide;
ch -= rowSide;
}
remaining = remaining.slice(row.length);
}
return rects;
}
function getWorst(row, area, side, scale) {
if (row.length === 0) return Infinity;
const s2 = side * side;
let min = Infinity, max = 0;
for (const item of row) {
const v = item.value * scale;
min = Math.min(min, v);
max = Math.max(max, v);
}
return Math.max((s2 * max) / (area * area), (area * area) / (s2 * min));
}
function renderTreemap() {
const container = document.getElementById('treemap');
const width = container.offsetWidth;
const height = container.offsetHeight;
container.innerHTML = '';
container.style.position = 'relative';
const moduleData = {};
for (const file of REPORT_DATA.files || []) {
const mod = file.module || '(root)';
if (!moduleData[mod]) moduleData[mod] = { name: mod, value: 0, lang: file.lang };
moduleData[mod].value += file.code || file.lines || 1;
}
const data = Object.values(moduleData).sort((a, b) => b.value - a.value).slice(0, 50);
const rects = squarify(data, 0, 0, width, height);
for (const rect of rects) {
const div = document.createElement('div');
div.className = 'treemap-cell';
div.style.left = rect.x + 'px';
div.style.top = rect.y + 'px';
div.style.width = rect.w + 'px';
div.style.height = rect.h + 'px';
div.style.background = getLangColor(rect.lang);
const label = document.createElement('div');
label.className = 'treemap-label';
label.textContent = rect.name + ' (' + rect.value.toLocaleString() + ')';
div.appendChild(label);
div.title = rect.name + ': ' + rect.value.toLocaleString() + ' lines';
container.appendChild(div);
}
}
document.getElementById('search').addEventListener('input', function(e) {
const filter = e.target.value.toLowerCase();
const rows = document.querySelectorAll('#files-table tbody tr');
rows.forEach(row => {
const text = row.textContent.toLowerCase();
row.classList.toggle('hidden', filter && !text.includes(filter));
});
});
let sortCol = null, sortAsc = true;
document.querySelectorAll('#files-table th').forEach(th => {
th.addEventListener('click', function() {
const col = this.dataset.sort;
if (sortCol === col) sortAsc = !sortAsc;
else { sortCol = col; sortAsc = true; }
const tbody = document.querySelector('#files-table tbody');
const rows = Array.from(tbody.querySelectorAll('tr'));
rows.sort((a, b) => {
const aVal = a.querySelector(`[data-${col}]`)?.dataset[col] || a.cells[getColIndex(col)].textContent;
const bVal = b.querySelector(`[data-${col}]`)?.dataset[col] || b.cells[getColIndex(col)].textContent;
const aNum = parseFloat(aVal.replace(/,/g, ''));
const bNum = parseFloat(bVal.replace(/,/g, ''));
if (!isNaN(aNum) && !isNaN(bNum)) {
return sortAsc ? aNum - bNum : bNum - aNum;
}
return sortAsc ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
});
rows.forEach(row => tbody.appendChild(row));
});
});
function getColIndex(col) {
const map = { path: 0, module: 1, lang: 2, lines: 3, code: 4, tokens: 5, bytes: 6 };
return map[col] || 0;
}
window.addEventListener('load', renderTreemap);
window.addEventListener('resize', renderTreemap);
</script>
</body>
</html>