Skip to content

Commit 0495719

Browse files
committed
Add roast/templates/report.html
1 parent e730fd5 commit 0495719

1 file changed

Lines changed: 385 additions & 0 deletions

File tree

roast/templates/report.html

Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Roast My Code Report</title>
7+
<style>
8+
:root {
9+
--bg: #0d1117;
10+
--panel: #161b22;
11+
--panel-2: #1f2937;
12+
--text: #c9d1d9;
13+
--muted: #8b949e;
14+
--border: #30363d;
15+
--green: #2ea043;
16+
--yellow: #d29922;
17+
--red: #f85149;
18+
--blue: #58a6ff;
19+
}
20+
21+
* { box-sizing: border-box; }
22+
body {
23+
margin: 0;
24+
padding: 32px 18px 56px;
25+
background: radial-gradient(circle at top right, #111827 0%, var(--bg) 55%);
26+
color: var(--text);
27+
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
28+
}
29+
30+
.container {
31+
max-width: 1120px;
32+
margin: 0 auto;
33+
}
34+
35+
.card {
36+
background: linear-gradient(180deg, #161b22 0%, #121820 100%);
37+
border: 1px solid var(--border);
38+
border-radius: 14px;
39+
padding: 20px;
40+
margin-bottom: 18px;
41+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.02) inset;
42+
}
43+
44+
h1, h2, h3 { margin: 0 0 12px; }
45+
h1 { font-size: 2rem; letter-spacing: 0.02em; }
46+
h2 { font-size: 1.35rem; }
47+
p { margin: 0; color: var(--muted); }
48+
49+
.hero {
50+
display: grid;
51+
gap: 24px;
52+
grid-template-columns: 280px 1fr;
53+
align-items: center;
54+
}
55+
56+
.score-circle-wrap {
57+
position: relative;
58+
width: 220px;
59+
height: 220px;
60+
margin: 0 auto;
61+
display: grid;
62+
place-items: center;
63+
}
64+
65+
.score-circle {
66+
transform: rotate(-90deg);
67+
width: 220px;
68+
height: 220px;
69+
}
70+
71+
.score-circle .track {
72+
fill: none;
73+
stroke: #2b3340;
74+
stroke-width: 14;
75+
}
76+
77+
.score-circle .value {
78+
fill: none;
79+
stroke: {{ overall_color }};
80+
stroke-width: 14;
81+
stroke-linecap: round;
82+
stroke-dasharray: {{ score_ring_circumference }};
83+
stroke-dashoffset: {{ score_ring_offset }};
84+
transition: stroke-dashoffset 0.8s ease-out;
85+
}
86+
87+
.score-number {
88+
position: absolute;
89+
text-align: center;
90+
}
91+
92+
.score-number .big {
93+
font-size: 2.9rem;
94+
font-weight: 800;
95+
line-height: 1;
96+
color: {{ overall_color }};
97+
}
98+
99+
.score-number .label {
100+
color: var(--muted);
101+
font-size: 0.85rem;
102+
margin-top: 6px;
103+
letter-spacing: 0.06em;
104+
text-transform: uppercase;
105+
}
106+
107+
.headline {
108+
font-size: 1.25rem;
109+
font-weight: 700;
110+
color: #facc15;
111+
margin-bottom: 12px;
112+
}
113+
114+
.meta-grid {
115+
display: grid;
116+
grid-template-columns: repeat(3, minmax(0, 1fr));
117+
gap: 12px;
118+
margin-top: 10px;
119+
}
120+
121+
.meta-item {
122+
background: var(--panel-2);
123+
border: 1px solid var(--border);
124+
border-radius: 10px;
125+
padding: 10px 12px;
126+
}
127+
128+
.meta-item strong { display: block; color: var(--text); font-size: 1rem; }
129+
.meta-item span { color: var(--muted); font-size: 0.85rem; }
130+
131+
.badges {
132+
display: flex;
133+
gap: 10px;
134+
flex-wrap: wrap;
135+
margin-top: 12px;
136+
}
137+
138+
.badge {
139+
border-radius: 999px;
140+
padding: 6px 12px;
141+
font-weight: 700;
142+
font-size: 0.85rem;
143+
color: #0d1117;
144+
}
145+
146+
.roast-list {
147+
margin: 14px 0 0;
148+
padding: 0;
149+
list-style: none;
150+
display: grid;
151+
gap: 10px;
152+
}
153+
154+
.roast-line {
155+
background: #1a1f27;
156+
border-left: 3px solid #f97316;
157+
border-radius: 8px;
158+
padding: 10px 12px;
159+
color: #f5f5f5;
160+
font-size: 0.95rem;
161+
}
162+
163+
.verdict {
164+
display: inline-block;
165+
margin-top: 14px;
166+
font-size: 1.12rem;
167+
font-weight: 800;
168+
padding: 8px 12px;
169+
border-radius: 10px;
170+
border: 1px solid var(--border);
171+
background: #0f1720;
172+
}
173+
174+
.table-wrap { overflow-x: auto; }
175+
table {
176+
width: 100%;
177+
border-collapse: collapse;
178+
min-width: 680px;
179+
}
180+
181+
th, td {
182+
border-bottom: 1px solid var(--border);
183+
text-align: left;
184+
padding: 10px 10px;
185+
font-size: 0.9rem;
186+
vertical-align: top;
187+
}
188+
189+
th {
190+
color: var(--muted);
191+
font-size: 0.82rem;
192+
letter-spacing: 0.04em;
193+
text-transform: uppercase;
194+
cursor: pointer;
195+
user-select: none;
196+
white-space: nowrap;
197+
}
198+
199+
tr:hover td { background: #18202b; }
200+
201+
.sev-high { color: var(--red); font-weight: 700; }
202+
.sev-medium { color: var(--yellow); font-weight: 700; }
203+
.sev-low { color: var(--green); font-weight: 700; }
204+
205+
.share-box {
206+
margin-top: 8px;
207+
display: grid;
208+
gap: 10px;
209+
}
210+
211+
.share-box textarea {
212+
width: 100%;
213+
min-height: 56px;
214+
padding: 10px;
215+
border-radius: 8px;
216+
border: 1px solid var(--border);
217+
background: #0b1320;
218+
color: var(--text);
219+
resize: vertical;
220+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
221+
font-size: 0.85rem;
222+
}
223+
224+
.copy-btn {
225+
justify-self: start;
226+
border: 1px solid var(--border);
227+
border-radius: 8px;
228+
background: #111827;
229+
color: var(--text);
230+
padding: 8px 12px;
231+
font-weight: 700;
232+
cursor: pointer;
233+
}
234+
235+
.copy-btn:hover { border-color: #4b5563; }
236+
.small-note { color: var(--muted); font-size: 0.82rem; margin-top: 8px; }
237+
238+
@media (max-width: 860px) {
239+
.hero { grid-template-columns: 1fr; }
240+
.meta-grid { grid-template-columns: 1fr; }
241+
}
242+
</style>
243+
</head>
244+
<body>
245+
<div class="container">
246+
<section class="card hero">
247+
<div class="score-circle-wrap">
248+
<svg class="score-circle" viewBox="0 0 220 220" aria-label="Overall score">
249+
<circle class="track" cx="110" cy="110" r="90"></circle>
250+
<circle class="value" cx="110" cy="110" r="90"></circle>
251+
</svg>
252+
<div class="score-number">
253+
<div class="big">{{ overall_score }}</div>
254+
<div class="label">Overall Score</div>
255+
</div>
256+
</div>
257+
<div>
258+
<h1>🔥 Roast My Code</h1>
259+
<p>The AI that roasts your codebase so your teammates don't have to.</p>
260+
<div class="headline">{{ roast.headline }}</div>
261+
<div class="meta-grid">
262+
<div class="meta-item"><strong>{{ report.total_files }}</strong><span>Scanned Files</span></div>
263+
<div class="meta-item"><strong>{{ report.total_lines }}</strong><span>Total Lines</span></div>
264+
<div class="meta-item"><strong>{{ report.issues|length }}</strong><span>Total Issues</span></div>
265+
</div>
266+
<div class="badges">
267+
{% for item in score_items %}
268+
<span class="badge" style="background: {{ item.color }};">{{ item.name }}: {{ item.value }}</span>
269+
{% endfor %}
270+
</div>
271+
</div>
272+
</section>
273+
274+
<section class="card">
275+
<h2>LLM Roast</h2>
276+
<ul class="roast-list">
277+
{% for line in roast.roast_lines %}
278+
<li class="roast-line">🔥 {{ line }}</li>
279+
{% endfor %}
280+
</ul>
281+
<div class="verdict">{{ roast.verdict_emoji }} {{ roast.verdict }}</div>
282+
</section>
283+
284+
<section class="card">
285+
<h2>Issues</h2>
286+
<p style="margin-bottom: 12px;">Click table headers to sort by file, line, severity, or category.</p>
287+
<div class="table-wrap">
288+
<table id="issues-table">
289+
<thead>
290+
<tr>
291+
<th data-col="0">File</th>
292+
<th data-col="1">Line</th>
293+
<th data-col="2">Severity</th>
294+
<th data-col="3">Category</th>
295+
<th data-col="4">Description</th>
296+
</tr>
297+
</thead>
298+
<tbody>
299+
{% for issue in issues %}
300+
<tr>
301+
<td>{{ issue.file }}</td>
302+
<td>{{ issue.line if issue.line is not none else "-" }}</td>
303+
<td class="sev-{{ issue.severity }}">{{ issue.severity|upper }}</td>
304+
<td>{{ issue.category }}</td>
305+
<td>{{ issue.description }}</td>
306+
</tr>
307+
{% endfor %}
308+
</tbody>
309+
</table>
310+
</div>
311+
<div class="small-note">Rows: {{ issues|length }}</div>
312+
</section>
313+
314+
<section class="card">
315+
<h2>Share your score</h2>
316+
<p>Copy the markdown below to flex or beg for help in your PR.</p>
317+
<div class="share-box">
318+
<textarea id="badge-markdown" readonly>{{ badge_markdown }}</textarea>
319+
<button class="copy-btn" id="copy-btn" type="button">Copy Badge Markdown</button>
320+
</div>
321+
<div class="small-note" id="copy-status"></div>
322+
</section>
323+
</div>
324+
325+
<script>
326+
(function () {
327+
const table = document.getElementById("issues-table");
328+
const headers = table.querySelectorAll("th");
329+
const tbody = table.querySelector("tbody");
330+
let sortState = { column: null, asc: true };
331+
332+
const severityRank = { HIGH: 0, MEDIUM: 1, LOW: 2 };
333+
334+
function getCellValue(row, index) {
335+
return row.children[index].innerText.trim();
336+
}
337+
338+
function compareValues(a, b, index, asc) {
339+
const av = getCellValue(a, index);
340+
const bv = getCellValue(b, index);
341+
342+
if (index === 1) {
343+
const aNum = av === "-" ? Number.MAX_SAFE_INTEGER : Number(av);
344+
const bNum = bv === "-" ? Number.MAX_SAFE_INTEGER : Number(bv);
345+
return asc ? aNum - bNum : bNum - aNum;
346+
}
347+
348+
if (index === 2) {
349+
const aRank = severityRank[av] ?? 99;
350+
const bRank = severityRank[bv] ?? 99;
351+
return asc ? aRank - bRank : bRank - aRank;
352+
}
353+
354+
return asc ? av.localeCompare(bv) : bv.localeCompare(av);
355+
}
356+
357+
headers.forEach((header) => {
358+
header.addEventListener("click", () => {
359+
const col = Number(header.getAttribute("data-col"));
360+
const asc = sortState.column === col ? !sortState.asc : true;
361+
sortState = { column: col, asc };
362+
363+
const rows = Array.from(tbody.querySelectorAll("tr"));
364+
rows.sort((a, b) => compareValues(a, b, col, asc));
365+
rows.forEach((row) => tbody.appendChild(row));
366+
});
367+
});
368+
369+
const copyBtn = document.getElementById("copy-btn");
370+
const badgeTextarea = document.getElementById("badge-markdown");
371+
const copyStatus = document.getElementById("copy-status");
372+
373+
copyBtn.addEventListener("click", async () => {
374+
try {
375+
badgeTextarea.select();
376+
await navigator.clipboard.writeText(badgeTextarea.value);
377+
copyStatus.textContent = "Copied badge markdown.";
378+
} catch (err) {
379+
copyStatus.textContent = "Copy failed. Select and copy manually.";
380+
}
381+
});
382+
})();
383+
</script>
384+
</body>
385+
</html>

0 commit comments

Comments
 (0)