<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RustDupe Duplicate Report</title>
<style>
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--card-bg: #ffffff;
--border-color: #dee2e6;
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--success-color: #198754;
--info-color: #0dcaf0;
--warning-color: #ffc107;
--danger-color: #dc3545;
--ref-badge-bg: #cfe2ff;
--ref-badge-text: #084298;
--table-hover: #f1f3f5;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #1a1a1a;
--text-color: #e9ecef;
--card-bg: #2d2d2d;
--border-color: #404040;
--primary-color: #4dabf7;
--secondary-color: #adb5bd;
--ref-badge-bg: #003366;
--ref-badge-text: #e7f5ff;
--table-hover: #383838;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: var(--text-color);
background-color: var(--bg-color);
margin: 0;
padding: 0;
line-height: 1.6;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 40px 20px;
}
header {
margin-bottom: 40px;
border-bottom: 2px solid var(--border-color);
padding-bottom: 20px;
}
h1 {
margin: 0;
font-size: 2.5rem;
font-weight: 800;
}
.meta {
color: var(--secondary-color);
margin-top: 10px;
display: flex;
gap: 20px;
font-size: 0.9rem;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
.stat-box {
background: var(--card-bg);
padding: 25px;
border-radius: 12px;
border: 1px solid var(--border-color);
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
transition: transform 0.2s;
}
.stat-box:hover {
transform: translateY(-2px);
}
.stat-label {
color: var(--secondary-color);
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
display: block;
margin-bottom: 5px;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
color: var(--primary-color);
}
.stat-box.highlight .stat-value {
color: var(--success-color);
}
.duplicate-groups {
display: flex;
flex-direction: column;
gap: 20px;
}
.group-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.03);
}
.group-header {
padding: 20px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(0,0,0,0.02);
transition: background 0.2s;
}
.group-header:hover {
background: rgba(0,0,0,0.04);
}
.group-info {
display: flex;
align-items: center;
gap: 15px;
}
.group-size {
font-weight: 700;
font-size: 1.1rem;
}
.group-count {
color: var(--secondary-color);
font-size: 0.9rem;
background: var(--border-color);
padding: 2px 10px;
border-radius: 20px;
}
.group-hash {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.8rem;
color: var(--secondary-color);
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.group-content {
padding: 0;
border-top: 1px solid var(--border-color);
display: none;
}
details[open] .group-content {
display: block;
}
details summary {
list-style: none;
}
details summary::-webkit-details-marker {
display: none;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
padding: 12px 20px;
font-size: 0.8rem;
text-transform: uppercase;
color: var(--secondary-color);
background: rgba(0,0,0,0.01);
border-bottom: 2px solid var(--border-color);
}
td {
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
font-size: 0.95rem;
word-break: break-all;
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--table-hover);
}
.path-cell {
color: var(--text-color);
}
.meta-cell {
color: var(--secondary-color);
font-size: 0.85rem;
white-space: nowrap;
}
.badge {
padding: 4px 10px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 700;
}
.badge-ref {
background: var(--ref-badge-bg);
color: var(--ref-badge-text);
}
@media (max-width: 768px) {
.container { padding: 20px 15px; }
h1 { font-size: 1.75rem; }
.stats { grid-template-columns: 1fr; }
.group-header { flex-direction: column; align-items: flex-start; gap: 10px; }
.group-hash { max-width: 100%; }
th { display: none; }
td { display: block; padding: 10px 20px; }
td:before {
content: attr(data-label);
font-size: 0.7rem;
text-transform: uppercase;
color: var(--secondary-color);
display: block;
margin-bottom: 2px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Duplicate Report</h1>
<div class="meta">
<span>{{ timestamp }}</span>
<span>RustDupe v0.2.0</span>
</div>
</header>
<section class="stats">
<div class="stat-box">
<span class="stat-label">Total Scanned</span>
<span class="stat-value">{{ summary.total_files }} files</span>
</div>
<div class="stat-box">
<span class="stat-label">Total Size</span>
<span class="stat-value">{{ total_size }}</span>
</div>
<div class="stat-box">
<span class="stat-label">Duplicate Groups</span>
<span class="stat-value">{{ summary.duplicate_groups }}</span>
</div>
<div class="stat-box highlight">
<span class="stat-label">Reclaimable</span>
<span class="stat-value">{{ reclaimable_space }}</span>
</div>
</section>
<div class="duplicate-groups">
{% for group in groups %}
<details class="group-card">
<summary class="group-header">
<div class="group-info">
<span class="group-size">{{ group.size_formatted }}</span>
<span class="group-count">{{ group.files.len() }} files</span>
</div>
<div class="group-hash" title="{{ group.hash_hex }}">{{ group.hash_hex }}</div>
</summary>
<div class="group-content">
<table>
<thead>
<tr>
<th>Path</th>
<th>Modified</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for file in group.files %}
<tr>
<td class="path-cell" data-label="Path">{{ file.path_display }}</td>
<td class="meta-cell" data-label="Modified">{{ file.modified_formatted }}</td>
<td data-label="Status">
{% if file.is_reference %}
<span class="badge badge-ref">Reference</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</details>
{% endfor %}
</div>
</div>
</body>
</html>