feat: cleanup exam template
This commit is contained in:
@@ -61,7 +61,8 @@
|
||||
#let show-points = extra.at("show-points", default: true)
|
||||
#let page-per-item = extra.at("page-per-item", default: false)
|
||||
#let show-bubbles = extra.at("show-bubbles", default: true)
|
||||
#let bubble-radius = eval(extra.at("bubble-radius", default: "0.42em"))
|
||||
#let bubble-radius = eval(extra.at("bubble-radius", default: "0.5em"))
|
||||
#let scratch-space = eval(extra.at("scratch-space", default: "1.5em"))
|
||||
|
||||
#let form-note = if cb-meta.form.at("count", default: 1) > 1 {
|
||||
" · Form " + cb-meta.form.id
|
||||
@@ -76,7 +77,7 @@
|
||||
#cb-meta.course.code · #cb-meta.assessment.title#form-note
|
||||
],
|
||||
footer: context text(size: 0.85em)[
|
||||
#counter(page).display("Page 1 of 1", both: true)
|
||||
Page #counter(page).display("1 of 1", both: true)
|
||||
],
|
||||
)
|
||||
#set text(font: body-font, size: body-size, lang: "en")
|
||||
@@ -129,6 +130,31 @@
|
||||
// ever generate both.
|
||||
#let bubble() = circle(radius: bubble-radius, stroke: 0.5pt)
|
||||
|
||||
// A small colored badge for a question's cognitive level, in the same visual
|
||||
// style as the tier badges on Exam 4. `level` counts up from 1; swap the order
|
||||
// of `level-colors` if your taxonomy numbers complexity the other way. Nothing
|
||||
// is drawn when a variant withholds `level` (`fields.level: false`), same as
|
||||
// any other optional field.
|
||||
#let level-colors = (
|
||||
(bg: rgb("#D9E8EE"), fg: rgb("#264653")),
|
||||
(bg: rgb("#DCF6F3"), fg: rgb("#2a9d8f")),
|
||||
(bg: rgb("#FAF2DD"), fg: rgb("#D19F1F")),
|
||||
(bg: rgb("#FBE6E0"), fg: rgb("#E24E29")),
|
||||
(bg: rgb("#F0E9F5"), fg: rgb("#9967B6")),
|
||||
)
|
||||
|
||||
#let level-tag(q) = {
|
||||
let level = q.at("level", default: none)
|
||||
if level != none {
|
||||
let idx = calc.max(1, calc.min(level, level-colors.len())) - 1
|
||||
let colors = level-colors.at(idx)
|
||||
let label = q.at("level-name", default: "Level " + str(level))
|
||||
box(fill: colors.bg, radius: 3pt, inset: (x: 6pt, y: 3pt))[
|
||||
#text(weight: "bold", size: 0.75em, fill: colors.fg)[#label]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// The renderer
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -146,38 +172,44 @@
|
||||
|
||||
// The number is the one recorded in the assessment record, not the position on
|
||||
// the page. Keep it that way: it is the join key to every grading export.
|
||||
//
|
||||
// Built in code rather than written as `*#q.number.*` because a field access
|
||||
// followed by a literal period reads as the start of another field access.
|
||||
let number-label = str(q.number) + "."
|
||||
|
||||
block(above: 1.2em, below: 0.5em)[
|
||||
*#number-label* #points-tag(q) #markup(q.stem)
|
||||
]
|
||||
|
||||
if q.at("multi-select", default: false) {
|
||||
block(below: 0.4em)[
|
||||
#text(size: 0.9em, style: "italic")[Select all that apply.]
|
||||
block(breakable: false)[
|
||||
#block(above: 1.2em, below: 0.3em)[
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
align: (left + horizon, right + horizon),
|
||||
[*#number-label* #points-tag(q)], level-tag(q),
|
||||
)
|
||||
]
|
||||
}
|
||||
#block(below: 1.5em)[#markup(q.stem)]
|
||||
|
||||
block(inset: (left: 1.2em))[
|
||||
#for opt in q.at("options", default: ()) {
|
||||
if show-bubbles {
|
||||
grid(
|
||||
columns: (1.6em, 1.4em, 1fr),
|
||||
gutter: 0.2em,
|
||||
align(horizon)[#bubble()], [#(opt.letter + ".")], [#markup(opt.text)],
|
||||
)
|
||||
} else {
|
||||
grid(
|
||||
columns: (1.4em, 1fr),
|
||||
gutter: 0.2em,
|
||||
[#(opt.letter + ".")], [#markup(opt.text)],
|
||||
)
|
||||
}
|
||||
v(0.15em)
|
||||
#if q.at("multi-select", default: false) {
|
||||
block(below: 0.4em)[
|
||||
#text(size: 0.9em, style: "italic")[Select all that apply.]
|
||||
]
|
||||
}
|
||||
|
||||
#block(inset: (left: 1.2em))[
|
||||
#for opt in q.at("options", default: ()) {
|
||||
if show-bubbles {
|
||||
grid(
|
||||
columns: (1.6em, 1.4em, 1fr),
|
||||
gutter: 0.2em,
|
||||
align(top)[#v(-bubble-radius / 2.1) #bubble()], [#(opt.letter + ".")], [#markup(opt.text)],
|
||||
)
|
||||
} else {
|
||||
grid(
|
||||
columns: (1.4em, 1fr),
|
||||
gutter: 0.2em,
|
||||
[#(opt.letter + ".")], [#markup(opt.text)],
|
||||
)
|
||||
}
|
||||
v(0.15em)
|
||||
}
|
||||
]
|
||||
|
||||
#if scratch-space > 0pt { v(scratch-space) }
|
||||
]
|
||||
|
||||
if page-per-item { pagebreak(weak: true) }
|
||||
@@ -187,43 +219,95 @@
|
||||
// The page
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#set page(paper: paper, margin: 2cm)
|
||||
#set text(font: body-font, size: body-size, lang: "en")
|
||||
#set par(justify: false, leading: 0.7em)
|
||||
|
||||
// ── Cover page: no header or footer, so it reads as its own sheet. ──
|
||||
#set page(header: none, footer: none)
|
||||
|
||||
#v(4em)
|
||||
#align(center)[
|
||||
#text(size: 1.4em, weight: "bold", fill: accent)[#cb-meta.assessment.title]\
|
||||
#text(size: 0.95em)[
|
||||
#cb-meta.course.code — #cb-meta.course.title · #cb-meta.assessment.term
|
||||
#text(weight: "bold", size: 18pt, fill: accent)[
|
||||
#cb-meta.course.code — #cb-meta.course.title
|
||||
]\
|
||||
#text(size: 0.9em)[
|
||||
#text(size: 16pt)[#cb-meta.assessment.title#form-note]\
|
||||
#text(size: 14pt)[
|
||||
#cb-meta.assessment.at("date", default: "")
|
||||
#{
|
||||
let m = cb-meta.assessment.at("minutes-allowed", default: none)
|
||||
if m != none { " · " + str(int(calc.round(m))) + " minutes" }
|
||||
}
|
||||
#{
|
||||
let p = cb-meta.totals.at("points", default: none)
|
||||
if p != none { " · " + fmt-points(p) }
|
||||
}
|
||||
]
|
||||
]\
|
||||
#{
|
||||
let p = cb-meta.totals.at("points", default: none)
|
||||
if p != none { text(size: 12pt)[#fmt-points(p)] }
|
||||
}
|
||||
]
|
||||
|
||||
#if show-name-block {
|
||||
block(above: 1em, below: 1.5em)[
|
||||
#grid(
|
||||
columns: (auto, 1fr, auto, 1fr),
|
||||
gutter: 0.6em,
|
||||
[*Name*], box(width: 100%, repeat[.]), [*Student ID*], box(width: 100%, repeat[.]),
|
||||
)
|
||||
]
|
||||
}
|
||||
#v(1.5em)
|
||||
|
||||
#{
|
||||
let instructions = cb-meta.assessment.at("instructions", default: none)
|
||||
if instructions != none {
|
||||
block(fill: luma(245), inset: 8pt, radius: 3pt, width: 100%)[
|
||||
#markup(instructions)
|
||||
]
|
||||
}
|
||||
if instructions != none [
|
||||
Please read the following instructions carefully before beginning your assessment.
|
||||
|
||||
#v(1em)
|
||||
|
||||
#markup(instructions)
|
||||
]
|
||||
}
|
||||
|
||||
#if show-name-block [
|
||||
#v(1.5em)
|
||||
|
||||
I agree to follow the above instructions. I affirm that all work on this
|
||||
assessment will be my own and that I will not give or receive any
|
||||
unauthorized assistance. To have your assessment graded, you must write your
|
||||
name, sign, and provide your student ID below.
|
||||
|
||||
#v(1em)
|
||||
|
||||
#grid(
|
||||
columns: (50%, 50%),
|
||||
rows: 6em,
|
||||
[
|
||||
#v(1em)
|
||||
#line(length: 18em)
|
||||
|
||||
*Name*
|
||||
],
|
||||
[
|
||||
#v(1em)
|
||||
#line(length: 18em)
|
||||
|
||||
*Signature*
|
||||
],
|
||||
)
|
||||
|
||||
#line(length: 8em)
|
||||
|
||||
*Student ID*
|
||||
]
|
||||
|
||||
#pagebreak()
|
||||
// Intentionally blank. Pull this sheet and the cover above as one unit if you
|
||||
// need the cover gone — nothing on the back of either one is a question.
|
||||
#pagebreak()
|
||||
|
||||
// ── The exam itself: header and footer turn on here. ──
|
||||
#set page(
|
||||
header: text(size: 0.85em)[
|
||||
#cb-meta.course.code · #cb-meta.assessment.title#form-note
|
||||
],
|
||||
footer: context text(size: 0.85em)[
|
||||
Page #counter(page).display("1 of 1", both: true)
|
||||
],
|
||||
)
|
||||
// Restart the printed page count here too, so the footer reads "Page 1 of N"
|
||||
// for the exam itself rather than counting the cover and the blank page.
|
||||
#counter(page).update(1)
|
||||
|
||||
// coursebank:begin questions
|
||||
#render-question((
|
||||
number: 1,
|
||||
|
||||
Reference in New Issue
Block a user