// lehrbuch.typ — book components for the Famulus Notebook teaching book. // Called from raw Typst emitted by filters/lehrbuch.lua. #import "lettreai.typ": colors, tint, shade, mono-font #let bk-blue = colors.oxford-blue #let bk-gold = colors.gold #let bk-rule = 0.6pt + tint(colors.oxford-blue, 0.7) // --------------------------------------------------------------------------- // Parts: an unnumbered divider inside the flow, no page break. // --------------------------------------------------------------------------- #let bk-part(kicker: "", title: "") = { v(1.8em, weak: true) block(width: 100%, breakable: false, above: 1.8em, below: 1.4em, { place(hide(heading(level: 1, numbering: none, outlined: true, if kicker != "" [#kicker · #title] else [#title]))) line(length: 100%, stroke: 2.2pt + bk-gold) v(0.55em) if kicker != "" { text(size: 0.95em, weight: 700, fill: shade(bk-gold, 0.35), tracking: 0.12em, upper(kicker)) v(0.1em) } text(size: 1.9em, weight: 800, fill: bk-blue, title) v(0.35em) line(length: 100%, stroke: 0.6pt + bk-gold) }) } // --------------------------------------------------------------------------- // Example: the source of a block beside (or above) its result. // --------------------------------------------------------------------------- #let bk-pane-label(t) = text(size: 7pt, weight: 700, tracking: 0.1em, fill: tint(bk-blue, 0.35), upper(t)) #let bk-source(text-src) = { set par(justify: false, leading: 0.42em, spacing: 0.42em) set text(font: mono-font, size: 7.1pt, fill: shade(bk-blue, 0.1), hyphenate: false) for line in text-src.split("\n") { let lead = line.len() - line.trim(" ", at: start).len() block(above: 0pt, below: 0.42em, breakable: false, [#h(lead * 0.6em)#line.trim(" ", at: start)]) } } #let bk-pane-src(body) = block(width: 100%, inset: (x: 7pt, y: 6pt), radius: 2pt, fill: tint(bk-blue, 0.955), stroke: (left: 2.5pt + bk-gold), { bk-pane-label("Source") v(0.3em) body }) #let bk-pane-out(body, breakable: true) = block(width: 100%, inset: (x: 8pt, y: 6pt), radius: 2pt, breakable: breakable, fill: white, stroke: 0.6pt + tint(bk-blue, 0.6), { bk-pane-label("Result") v(0.25em) set text(size: 8.6pt) set par(justify: false) set table(inset: 3.5pt) show table.cell.where(y: 0): set text(size: 7.8pt) body }) #let bk-example(layout: "side", caption: [], src, out) = block(width: 100%, above: 1.1em, below: 1.2em, breakable: layout != "side", { if layout == "side" { grid(columns: (1.08fr, 1fr), column-gutter: 7pt, bk-pane-src(src), bk-pane-out(out, breakable: false)) } else { bk-pane-src(src) v(4pt, weak: true) bk-pane-out(out) } v(3pt, weak: true) block(width: 100%, inset: (left: 2pt), text(size: 8.4pt, fill: shade(colors.ink, 0.1), caption)) }) #let bk-figure(value, label, expected: none) = block(above: 0.5em, below: 0.6em, { text(size: 2.1em, weight: 800, fill: bk-blue, value) h(0.5em) text(size: 0.95em, fill: tint(colors.ink, 0.25), label) if expected != none { h(0.6em) box(inset: (x: 4pt, y: 2pt), radius: 2pt, fill: tint(bk-gold, 0.8), text(size: 0.8em, [expected #expected])) } }) #let bk-card(where, body) = block(width: 100%, above: 0.45em, below: 0.45em, inset: (x: 6pt, y: 5pt), radius: 2pt, fill: tint(bk-gold, 0.93), stroke: (left: 2pt + bk-gold), { if where != "" { text(size: 7pt, font: mono-font, fill: tint(bk-blue, 0.2), where) linebreak() text(size: 8.2pt, style: "italic", body) } else { set text(size: 8.4pt) body } }) // --------------------------------------------------------------------------- // Boxes. // --------------------------------------------------------------------------- #let bk-box(kind: "famuli", title: [], body) = { let accent = if kind == "exercise" or kind == "inshort" { bk-gold } else { bk-blue } let fill = if kind == "famuli" { tint(bk-blue, 0.95) } else if kind == "inshort" { tint(bk-gold, 0.9) } else if kind == "exercise" { tint(bk-gold, 0.96) } else { white } let edge = if kind == "specimen" { (rest: 0.6pt + tint(bk-blue, 0.55)) } else if kind == "inshort" { (rest: 0.6pt + tint(bk-gold, 0.4), left: 3pt + bk-gold) } else { (left: 3pt + accent) } let mark = if kind == "famuli" { "⟨/⟩ " } else if kind == "exercise" { "✎ " } else { "" } block(width: 100%, above: 1em, below: 1em, inset: (x: 9pt, y: 7pt), radius: 2pt, breakable: false, fill: fill, stroke: edge, { text(size: 0.86em, weight: 700, fill: shade(accent, if accent == bk-gold { 0.45 } else { 0.0 }), [#mark#title]) v(0.25em) set text(size: 0.95em) body }) } // --------------------------------------------------------------------------- // Index: #bk-idx("Main!Sub") marks a term; #bk-index() prints the index. // --------------------------------------------------------------------------- #let bk-idx(key, see: none) = [#metadata((key: key, see: see))] #let _bk-sort(s) = lower(s).trim(regex("^[^\p{L}\p{N}]+"), at: start) #let bk-index() = context { let marks = query() let entries = (:) for m in marks { let key = m.value.key let parts = key.split("!") let main = parts.at(0) let sub = if parts.len() > 1 { parts.slice(1).join("!") } else { none } let slot = entries.at(main, default: (pages: (), see: none, subs: (:))) let page = counter(page).at(m.location()).first() let hit = (page: page, loc: m.location()) if sub == none { if m.value.see != none { slot.see = m.value.see } else if slot.pages.all(p => p.page != page) { slot.pages.push(hit) } } else { let s = slot.subs.at(sub, default: (pages: (), see: none)) if m.value.see != none { s.see = m.value.see } else if s.pages.all(p => p.page != page) { s.pages.push(hit) } slot.subs.insert(sub, s) } entries.insert(main, slot) } let refs(slot) = { let items = slot.pages.map(p => link(p.loc, str(p.page))) let out = items.join(", ") if slot.see != none { out = if items.len() > 0 [#out; _see_ #slot.see] else [_see_ #slot.see] } out } let names = entries.keys().sorted(key: _bk-sort) set text(size: 8.6pt) set par(justify: false, hanging-indent: 1.2em, spacing: 0.35em) columns(2, gutter: 14pt, { let letter = none for name in names { let first = upper(_bk-sort(name).clusters().at(0, default: "?")) if first != letter { letter = first v(0.6em, weak: true) block(below: 0.3em, sticky: true, text(weight: 800, fill: bk-blue, size: 1.1em, first)) } let slot = entries.at(name) let r = refs(slot) par[#name#if slot.pages.len() > 0 or slot.see != none [, #r]] for sub in slot.subs.keys().sorted(key: _bk-sort) { par[#h(1em)#sub, #refs(slot.subs.at(sub))] } } }) }