From 374e2e20563cc508a7c74045cd78b8a489914460 Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 16 Oct 2026 14:56:27 +0530 Subject: [PATCH] Paper preview in ZenithPrep style: stat cards, subject sections shared() now renders: stat cards (questions, duration, max marks, +4/-1), per-subject sections with q-ranges and chapter lists, a 'take this paper' card with the save-and-next explainer and privacy note, then the full question list. New .kv/.grid2/.subj-sec/.ch-list CSS with mobile single-column fallback. --- src/views.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++--- static/style.css | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/views.rs b/src/views.rs index 3d86784..a49760a 100644 --- a/src/views.rs +++ b/src/views.rs @@ -1216,6 +1216,39 @@ pub fn dashboard(ctx: &Ctx, user: &str, tests: &[TestRow], weak: &[(Bucket, Stri } pub fn shared(ctx: &Ctx, title: &str, duration: i64, questions: &[Question], paper_id: &str, token: &str, signed: bool) -> String { + use std::collections::BTreeMap; + // Group questions by subject, preserving first-seen order. + let mut order: Vec = Vec::new(); + let mut by_subj: BTreeMap> = BTreeMap::new(); + for (i, q) in questions.iter().enumerate() { + if !by_subj.contains_key(&q.subject) { + order.push(q.subject.clone()); + } + by_subj.entry(q.subject.clone()).or_default().push((i, q)); + } + let n = questions.len(); + let max_marks = n as i64 * 4; + // Subject sections with chapter lists. + let mut sections = String::new(); + for subj in &order { + let qs = &by_subj[subj]; + let start = qs.first().map(|(i, _)| i + 1).unwrap_or(1); + let end = qs.last().map(|(i, _)| i + 1).unwrap_or(n); + let mut chapters: Vec = Vec::new(); + for (_, q) in qs { + if !chapters.iter().any(|c| c == &q.chapter) { + chapters.push(q.chapter.clone()); + } + } + let ch_list: String = chapters.iter().map(|c| format!("
  • {}
  • ", esc(c))).collect(); + sections.push_str(&format!( + r#"

    {subj} q{start}–q{end}

      {chs}
    "#, + subj = esc(subj), + start = start, + end = end, + chs = ch_list + )); + } let qs: String = questions .iter() .enumerate() @@ -1253,12 +1286,23 @@ pub fn shared(ctx: &Ctx, title: &str, duration: i64, questions: &[Question], pap ) }; let body = format!( - r#"

    {title}

    {count} questions · {dur} minutes · no answers or scores on this page.

    {cta}

    {qs}
    "#, + r#"

    Practice paper

    {title} · JEE Main pattern · no answers or scores on this page.

    +
    +
    questions{count}
    +
    duration{dur} min
    +
    maximum marks{max}
    +
    marking+4 / −1
    +
    +
    {sections}

    take this paper

    The online test works like the real exam: answers count only after save & next.

    {cta}

    Only the question paper is shared. Your answers and score stay private.

    +

    every question

    +
    {qs}
    "#, title = esc(title), count = questions.len(), dur = duration, - qs = qs, - cta = cta + max = max_marks, + sections = sections, + cta = cta, + qs = qs ); shell(ctx, None, title, body) } diff --git a/static/style.css b/static/style.css index 4b226b5..55380b8 100644 --- a/static/style.css +++ b/static/style.css @@ -2229,3 +2229,44 @@ img.brand-logo { vertical-align: middle; filter: drop-shadow(0 0 6px rgba(59, 130, 246, 0.3)); } + +/* ===== paper preview: stat cards + subject sections ===== */ +.kv { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; + margin: 16px 0; +} +.kv .card { + text-align: center; + padding: 16px 12px; +} +.kv .card b { + display: block; + font-size: 1.5rem; + margin-top: 4px; +} +.grid2 { + display: grid; + grid-template-columns: 1fr 320px; + gap: 16px; + margin: 16px 0; +} +@media (max-width: 760px) { + .grid2 { grid-template-columns: 1fr; } +} +.subj-sec { margin-bottom: 12px; } +.subj-sec h3 { margin-bottom: 8px; } +.ch-list { + list-style: none; + margin: 0; + padding: 0; + columns: 2; + font-size: 0.85rem; + color: var(--text-main); +} +.ch-list li { margin-bottom: 4px; } +.ch-list li::before { content: "• "; color: var(--text-dim); } +@media (max-width: 560px) { + .ch-list { columns: 1; } +}