diff --git a/src/views.rs b/src/views.rs index ebf29e1..fb0d9bc 100644 --- a/src/views.rs +++ b/src/views.rs @@ -1028,8 +1028,13 @@ fn bucket_rows(items: &[Bucket]) -> String { .collect() } -pub fn result(ctx: &Ctx, user: &str, _attempt_id: &str, a: &Analysis, rows: &[RowView]) -> String { +pub fn result(ctx: &Ctx, user: &str, attempt_id: &str, a: &Analysis, rows: &[RowView]) -> String { let base = ctx.base.clone(); + let mistakes_pending = rows + .iter() + .filter(|r| !r.correct && r.tags.is_empty()) + .count(); + let title = format!("practice paper · {attempt_id}"); let detail: String = rows .iter() .enumerate() @@ -1075,36 +1080,50 @@ pub fn result(ctx: &Ctx, user: &str, _attempt_id: &str, a: &Analysis, rows: &[Ro }) .collect(); let topic_buckets: Vec = a.by_topic.iter().map(|(b, _)| b.to_owned()).collect(); + let neg = rows.iter().filter(|r| !r.correct && !r.selected.trim().is_empty()).count() as i32; let body = format!( - r#"

Result

-
-
score{score} / {max}
-
percentage{pct}%
-
accuracy{acc}%
-
time used{dur}
-
-
-

by subject

{subj}
subjectrightwrongblank%
-

by difficulty

{diff}
difficultyrightwrongblank%
-
-

chapters that cost you marks

-
{topic}
subtopicrightwrongblank%
-

every question

-{detail} -

+ r##"

+ +
+
+
score{score} / {max}
+
accuracy{acc}%
+
negative marks−{neg}
+
time used{dur}
+
+
+

by subject

{subj}
subjectrightwrongblank%
+

by difficulty

{diff}
difficultyrightwrongblank%
+
+

chapters that cost you marks

+
{topic}
subtopicrightwrongblank%
+

every question

+{detail} +

+ +

+
+
"##, score = a.score, max = a.max_score, - pct = fmt_pct(a.score, a.max_score), + neg = neg, acc = accuracy(a.right, a.wrong), dur = fmt_dur(a.used_secs), subj = bucket_rows(&a.by_subject), diff = bucket_rows(&a.by_difficulty), topic = bucket_rows(&topic_buckets), + mtag = if mistakes_pending > 0 { format!(r#" {}"#, mistakes_pending) } else { String::new() }, + title_ = esc(&title), detail = detail, b = ctx.base ); diff --git a/static/style.css b/static/style.css index 25419cb..7b92ce4 100644 --- a/static/style.css +++ b/static/style.css @@ -1315,6 +1315,80 @@ footer { background: #ef4444; } +/* Result page: sticky left nav + main column (ZenithPrep style) */ +.result-layout { + display: grid; + grid-template-columns: 230px 1fr; + gap: 20px; + align-items: start; +} + +.result-nav { + position: sticky; + top: 76px; + display: flex; + flex-direction: column; + gap: 6px; + padding: 14px; + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius-xl); +} + +.result-nav b { + font-size: 0.8rem; + color: var(--text-dim); + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.result-nav a { + font-size: 0.875rem; + color: var(--text-muted); + text-decoration: none; + padding: 6px 8px; + border-radius: var(--radius-sm); +} + +.result-nav a:hover { + background: var(--card-hover); + color: #ffffff; +} + +.result-nav .btn { + margin-top: 6px; + text-align: center; +} + +.result-nav .badge { + display: inline-block; + min-width: 18px; + padding: 1px 5px; + border-radius: 999px; + background: #7c3aed; + color: #ffffff; + font-size: 0.7rem; + font-weight: 700; + text-align: center; +} + +@media (max-width: 900px) { + .result-layout { + grid-template-columns: 1fr; + } + .result-nav { + position: static; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + } + .result-nav .btn { + margin-top: 0; + } +} + /* Palette Side Column */ .cbt-side { background: var(--card);