From bc1d68f6014a3c394021b9b8752d130ff8a9941e Mon Sep 17 00:00:00 2001 From: mntsx Date: Sat, 23 May 2026 06:28:30 +0200 Subject: [PATCH] black-angular-frame:0.1.0 --- .../preview/black-angular-frame/0.1.0/LICENSE | 21 + .../black-angular-frame/0.1.0/README.md | 308 +++ .../0.1.0/black-angular-frame.typ | 1275 ++++++++++++ .../0.1.0/template/assets/curves.csv | 12 + .../0.1.0/template/assets/github-logo.png | Bin 0 -> 6362 bytes .../0.1.0/template/assets/typst-logo.png | Bin 0 -> 12710 bytes .../0.1.0/template/main.typ | 1796 +++++++++++++++++ .../black-angular-frame/0.1.0/thumbnail.png | Bin 0 -> 138305 bytes .../black-angular-frame/0.1.0/typst.toml | 23 + 9 files changed, 3435 insertions(+) create mode 100644 packages/preview/black-angular-frame/0.1.0/LICENSE create mode 100644 packages/preview/black-angular-frame/0.1.0/README.md create mode 100644 packages/preview/black-angular-frame/0.1.0/black-angular-frame.typ create mode 100644 packages/preview/black-angular-frame/0.1.0/template/assets/curves.csv create mode 100644 packages/preview/black-angular-frame/0.1.0/template/assets/github-logo.png create mode 100644 packages/preview/black-angular-frame/0.1.0/template/assets/typst-logo.png create mode 100644 packages/preview/black-angular-frame/0.1.0/template/main.typ create mode 100644 packages/preview/black-angular-frame/0.1.0/thumbnail.png create mode 100644 packages/preview/black-angular-frame/0.1.0/typst.toml diff --git a/packages/preview/black-angular-frame/0.1.0/LICENSE b/packages/preview/black-angular-frame/0.1.0/LICENSE new file mode 100644 index 0000000000..82eca67b4d --- /dev/null +++ b/packages/preview/black-angular-frame/0.1.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Miguel Montes (@miguelm7654) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/preview/black-angular-frame/0.1.0/README.md b/packages/preview/black-angular-frame/0.1.0/README.md new file mode 100644 index 0000000000..9342cb71ec --- /dev/null +++ b/packages/preview/black-angular-frame/0.1.0/README.md @@ -0,0 +1,308 @@ +# black-angular-frame + +A Typst presentation template with a square, minimal, academic design language: a solid navigation bar on top, a two-level footer with page number, a tinted title strip for regular content slides, and compact box environments without wasted whitespace. + +![black-angular-frame thumbnail](thumbnail.png) + +--- + +## Quick start + +```typst +#import "@preview/black-angular-frame:0.1.0": * + +#let presentation-config = ( + title: "My Presentation", + subtitle: "An optional subtitle", + authors: "Alice, Bob", + institution: "Institution Name", + date: "May 2026", + final-message: "Thank you for your attention", + TOC: true, +) + +#show: black-angular-frame.with(config: presentation-config) + +#new-section("Introduction") + +#slide(title: "First slide")[ + - Bullet one + - Bullet two +] + +#thank-you-slide +``` + +Initialize from the template after publication with: + +```bash +typst init @preview/black-angular-frame:0.1.0 +``` + +For local development in this repository, compile the demo with: + +```bash +typst compile example.typ example.pdf --font-path assets/fonts +``` + +The source file is `example.typ` and the output path should be exactly `example.pdf`; this repository keeps only that generated PDF. + +The local demo imports `black-angular-frame.typ` directly and uses the fonts in `assets/fonts/`. If you use VS Code + Tinymist, configure the font path manually if you want IBM Plex to be discovered from `assets/fonts/`. + +--- + +## Template configuration + +Pass configuration through `#show: black-angular-frame.with(config: presentation-config)`. + +| Name | Expected value | Default | Description | +|------|----------------|---------|-------------| +| `title` | String | `""` | Presentation title. | +| `subtitle` | String | `""` | Presentation subtitle. | +| `authors` | String | `""` | Author line shown on the cover and footer. | +| `institution` | String | `""` | Institution shown on the cover and footer. | +| `date` | String | `""` | Date shown on the cover. | +| `final-message` | String | `""` | Message shown on the last slide. | +| `primary-color` | Color | `rgb("#1C1C1C")` | Main bars, highlights, numbering, and accents. | +| `secondary-color` | Color | `rgb("#D9D9D9")` | Secondary header and footer bands. | +| `background-color` | Color | `rgb("#FFFFFF")` | Slide background color. | +| `font-color` | Color | `luma(20)` | Default body text color. | +| `header-font-color-1` | Color | `_muted-nav(primary-color)` | Inactive text in the primary header band and text in the lower footer band. | +| `header-font-color-2` | Color | `primary-color` | Text in the secondary header and footer bands. | +| `header-font-color-1-highlight` | Color | `rgb("#FFFFFF")` | Active text in the primary header band. | +| `content-center` | Float 0-1 | `0.3` | Vertical position used to center content; `0` starts at the top, `1` at the bottom. | +| `content-upper-padding` | Float 0-1 | `0.05` | Top proportion of the available content area kept empty. | +| `content-lower-padding` | Float 0-1 | `0.05` | Bottom proportion of the available content area kept empty. | +| `logos` | `array[content]` | `()` | Logo images or custom content shown on the cover. | +| `TOC` | Bool | `true` | Whether to add the table of contents slide with links to each section's divider or first slide. | + +The template still accepts the previous parameter names (`title-color`, `bg-color`, `cover-images`, `toc`, and related aliases) for existing documents, but new presentations should use the `config` object. + +For cover logos, pass Typst content rather than path strings, for example `logos: (image("assets/logo.png", height: 45pt),)`. This keeps image paths resolved from the user document instead of from the package internals. + +--- + +## Fonts + +The template uses IBM Plex when available, with Liberation and DejaVu fallbacks. For local compilation with IBM Plex, install the fonts on your system or pass them with `--font-path`. + +This repository keeps IBM Plex files in `assets/fonts/` so `example.typ` can compile locally with the intended typography. The package manifest excludes `assets/fonts/**` from the Typst Universe bundle, because Universe packages must not ship font files. + +| Role | Font | +|------|------| +| Body text | IBM Plex Serif | +| Titles and UI elements | IBM Plex Sans | +| Code and verbatim | IBM Plex Mono | + +Fallback chains include Liberation and DejaVu, so the template works even when IBM Plex is unavailable. + +--- + +## Slide functions + +### `#slide(title: "...", body)` + +Standard content slide. Content is vertically centred in the available area. + +```typst +#slide(title: "My Slide")[ + Content goes here. +] +``` + +### `#new-section("Name", slide-title: auto)` + +Registers a section, increments the section counter, resets figure and theorem counters, updates the navigation bar label, and automatically renders the section intro slide. Use `slide-title:` when the intro slide should display a different title from the navigation label. + +### `#section-slide("Name")` + +Renders an extra full-page section divider manually. This is no longer needed for the normal `#new-section(...)` workflow. + +### `#new-subsection("Name")` + +Registers a subsection in the TOC without rendering a divider slide. + +### `#thank-you-slide` + +A centered italic final-message slide. Configure the displayed text with `final-message`. No parentheses — this is a content value, not a function call. + +--- + +## Layout helpers + +### `#two-col(left, right, left-width: 48%, gutter: 4%)` + +Splits the slide into two columns. + +```typst +#two-col( + [Left column content], + [Right column content], + left-width: 55%, +) +``` + +--- + +## Figures + +### `#fs-figure(caption: [...], body)` + +Numbered figure. The counter resets at each `#new-section`. Reference by number in surrounding text. + +```typst +#fs-figure(caption: [A diagram showing the architecture.])[ + #image("diagram.svg", width: 80%) +] +``` + +--- + +## Tables + +### `#fs-table-cell(body, fill: ..., stroke: ..., pos: ...)` + +Cell helper for tables built with `grid`. It applies the template table font, IBM Plex Sans by default. + +```typst +#fs-table-cell(fill: luma(248), stroke: luma(200) + 0.6pt, pos: center)[88.9] +``` + +--- + +## Theorem-style boxes + +All boxes display a colored header with `Kind N.M` (section.number) and an optional name panel. + +| Function | Default color | +|----------|---------------| +| `#theorem(name: "...", body)` | `blue.darken(50%)` | +| `#lemma(name: "...", body)` | `blue.darken(30%)` | +| `#corollary(name: "...", body)` | `blue.darken(40%)` | +| `#proposition(name: "...", body)` | `teal.darken(30%)` | +| `#definition(name: "...", body)` | `purple.darken(20%)` | +| `#example(name: "...", body)` | `green.darken(30%)` | +| `#exercise(name: "...", body)` | `orange.darken(20%)` | +| `#remark(name: "...", body)` | `luma(90)` | +| `#proof(body)` | left-border style | +| `#fs-box("kind", name: "...", color: ..., body)` | any color | + +The `color:` parameter can be overridden on any box. `#fs-box` accepts any string as the kind label. + +```typst +#theorem(name: "Banach Fixed-Point Theorem")[ + Let $(M, d)$ be a complete metric space and $f$ a contraction. Then $f$ has a unique fixed point. +] + +#fs-box("warning", name: "Careful!", color: red.darken(20%))[ + Do not confuse contractions with nonexpansive maps. +] +``` + +## Code and pseudo-code boxes + +Code fragments and pseudo-code can use the same framed header style as theorem boxes, with a `type` label and optional `title`. + +| Function | Purpose | +|----------|---------| +| `#code-box("...", type: "...", title: "...", lang: "...")` | Source code with optional syntax highlighting | +| `#pseudo-code("...", type: "...", title: "...")` | Pseudo-code with the same box chrome | + +```typst +#code-box( + "def train_step(x):\n return model(x)", + type: "Source Code", + title: "Python", + lang: "python", +) + +#pseudo-code( + "for t <- 1 to T\n update theta\nend", + title: "Training Loop", +) +``` + +--- + +## Example slides (`example.typ`) + +The file `example.typ` is a complete demo presentation covering all template features. Below is a summary of each section. + +### Section 1 — Configuration + +- Template configuration table with name, expected value, default, and description. +- Exact Typst code used by the example presentation to import and configure the template. + +### Section 2 — Typography + +- Side-by-side comparison of the three IBM Plex families (Serif, Sans, Mono) with representative weight and style variants. +- Size scale from 8 pt to 18 pt; semantic use of bold, italic, color, and underline. + +### Section 3 — Lists & Enumerations + +- Unordered bullet list (3 levels) and ordered enumeration (3 levels) side by side in two columns. + +### Section 4 — Figures + +- Two numbered figures with placeholder rectangles and captions. Explains how to replace placeholders with real images. + +### Section 5 — Layouts + +- A two-column layout with explanatory text on the left and a content block on the right. + +### Section 6 — Code Blocks + +- Python source code and pseudo-code side by side, both rendered as framed boxes with a theorem-style `type | title` header. + +### Section 7 — Tables + +- Paper-style booktabs table (horizontal rules only) and grid-style table (full borders, colored header, alternating row shading) side by side. + +### Section 8 — Diagrams & Charts + +- Two block diagrams — a Transformer encoder block (matching the original color scheme: orange FFN, blue Add & Norm, green attention) and a state-space system block diagram. +- A linear-algebra kernel/image decomposition diagram and a three-state Markov chain transition graph with labeled arcs. +- A line chart of model accuracy vs. epoch (data from `assets/curves.csv`) and a grouped bar histogram of test scores by group and year (2020–2024). + +### Section 9 — Theorem-style Boxes + +- `#definition`, `#theorem`, `#lemma`, `#corollary`, and `#proof` environments demonstrated with the Banach fixed-point theorem and Picard–Lindelöf corollary. +- `#example`, `#exercise`, `#proposition`, and three `#fs-box` calls with custom kind labels ("note", "warning", "custom") and custom colors. + +--- + +## Repository layout + +```text +black-angular-frame/ +├── typst.toml # Package manifest +├── black-angular-frame.typ # Package entrypoint +├── template/ +│ ├── main.typ # typst init starter file +│ └── assets/ # Starter assets copied by typst init +│ ├── typst-logo.png # Demo logo asset +│ └── github-logo.png # Demo logo asset +├── thumbnail.png # Universe thumbnail +├── example.typ # Full local demo presentation +├── example.pdf # Pre-compiled local demo PDF +├── assets/ +│ ├── typst-logo.png # Local demo logo asset +│ ├── github-logo.png # Local demo logo asset +│ ├── fonts/ # IBM Plex Serif / Sans / Mono TTF files +│ └── curves.csv # Sample data for the line chart slide +└── README.md +``` + +The Typst Universe bundle is controlled by `exclude` in `typst.toml`. The local demo, generated PDFs, local fonts, Tinymist lockfile, and temporary files stay in the repository but are excluded from the published package archive. + +--- + +## Acknowledgements + +This template was visually inspired by academic Beamer themes, especially UChicago-style slide layouts. It is not affiliated with or endorsed by the University of Chicago. No University of Chicago logos or brand assets are included. + +--- + +## License + +The template code is released under the MIT License. The IBM Plex fonts kept for local demo builds are distributed under the SIL Open Font License 1.1; see `assets/fonts/OFL-1.1.txt`. Font files are excluded from the Typst Universe package bundle. diff --git a/packages/preview/black-angular-frame/0.1.0/black-angular-frame.typ b/packages/preview/black-angular-frame/0.1.0/black-angular-frame.typ new file mode 100644 index 0000000000..3b3a3bc941 --- /dev/null +++ b/packages/preview/black-angular-frame/0.1.0/black-angular-frame.typ @@ -0,0 +1,1275 @@ +// ============================================================ +// black-angular-frame -- Typst presentation template +// Formal academic presentation theme for Typst +// Usage: +// #import "black-angular-frame.typ": * +// #show: black-angular-frame.with(config: (title: "My Talk", ...)) +// #slide(title: "First")[...] +// ============================================================ + +// ---- Internal state ---------------------------------------- +#let _fs-tc = state("_fs-tc", rgb("#1C1C1C")) +#let _fs-sc = state("_fs-sc", rgb("#D9D9D9")) +#let _fs-bg = state("_fs-bg", white) +#let _fs-w = state("_fs-w", 254mm) +#let _fs-h = state("_fs-h", 143mm) +#let _fs-ft = state("_fs-ft", none) +#let _fs-fst = state("_fs-fst", none) +#let _fs-title = state("_fs-title", "") +#let _fs-subt = state("_fs-subt", "") +#let _fs-inst = state("_fs-inst", none) +#let _fs-auth = state("_fs-auth", none) +#let _fs-fc = state("_fs-fc", luma(20)) +#let _fs-hfc1 = state("_fs-hfc1", rgb("#999999")) +#let _fs-hfc2 = state("_fs-hfc2", rgb("#1C1C1C")) +#let _fs-hfc1h = state("_fs-hfc1h", white) +#let _fs-final = state("_fs-final", "") +#let _fs-cctr = state("_fs-cctr", 0.3) +#let _fs-cupad = state("_fs-cupad", 0.05) +#let _fs-clpad = state("_fs-clpad", 0.05) +#let _fs-pages = state("_fs-pages", ()) +#let _fs-sec-targets = state("_fs-sec-targets", ()) + +#let _sec-ctr = counter("_fs-sec") +#let _fig-ctr = counter("_fs-fig") +#let _cur-sec = state("_fs-cursec", "") +#let _toc-data = state("_fs-toc", ()) + +#let _thm-ctrs = ( + theorem: counter("_fs-thm"), + lemma: counter("_fs-lem"), + corollary: counter("_fs-cor"), + proposition: counter("_fs-prp"), + definition: counter("_fs-def"), + example: counter("_fs-exa"), + exercise: counter("_fs-exr"), + remark: counter("_fs-rmk"), +) + +// ---- Color helpers ----------------------------------------- +#let _lmix(c, pct) = color.mix((c, 100% - pct), (white, pct)) +#let _title-bg(c) = _lmix(c, 88%) +#let _sub-hdr(c) = _lmix(c, 40%) +#let _soft-rule(c) = _lmix(c, 72%) +#let _muted-nav(c) = color.mix((white, 55%), (c, 45%)) + +// ---- Font stacks ------------------------------------------- +#let _sans = ("IBM Plex Sans", "Liberation Sans", "DejaVu Sans") +#let _serif = ("IBM Plex Serif", "Liberation Serif", "DejaVu Serif") +#let _mono = ("IBM Plex Mono", "Liberation Mono", "DejaVu Sans Mono") + +#let _slide-x-margin = 22.4pt +#let _single-col-x-margin = 2 * _slide-x-margin +#let _nav-compact-threshold = 4 +#let _cover-image-gap = 18pt + +#let _clamp01(value) = { + if value < 0 { 0 } else if value > 1 { 1 } else { value } +} + +// ============================================================ +// Shared chrome +// ============================================================ + +#let _single-line(content) = { + if content == none { none } else if type(content) == str { content.replace("\n", " ") } else { content } +} + +#let _fit-ellipsis(value, remaining, width, text-width, suffix: "...") = { + if remaining <= 0 { suffix } else { + let omitted = value.len() - remaining + let candidate = if omitted < suffix.len() { + value.slice(0, remaining) + value.slice(remaining) + } else { + value.slice(0, remaining) + suffix + } + if text-width(candidate) <= width { + candidate + } else { + _fit-ellipsis(value, remaining - 1, width, text-width, suffix: suffix) + } + } +} + +#let _ellipsis-text( + content, + width, + font: _sans, + size: 10pt, + fill: black, + reserve: 8pt, +) = context { + let effective-width = calc.max(0pt, width - reserve) + let text-width = value => measure(text(font: font, size: size, fill: fill, value)).width + let value = _single-line(content) + + if value == none or type(value) != str or text-width(value) <= effective-width { + text(font: font, size: size, fill: fill, value) + } else { + let suffix = "..." + text( + font: font, + size: size, + fill: fill, + _fit-ellipsis(value, value.len(), effective-width, text-width, suffix: suffix), + ) + } +} + +#let _nav-dot(active: false, color, inactive-fill: auto, active-fill: white) = box( + width: 6.2pt, + height: 6.2pt, + inset: 0pt, + align(center + horizon, circle( + radius: 2pt, + fill: if active { active-fill } else { none }, + stroke: if active { none } else { (if inactive-fill == auto { _muted-nav(color) } else { inactive-fill }) + 0.7pt }, + )), +) + +#let _cover-image-item(logo, height: 45pt) = { + if type(logo) == str { + image(logo, height: height) + } else { + logo + } +} + +#let _cover-image-row(logos, height: 45pt) = { + grid( + columns: logos.map(_ => auto), + column-gutter: _cover-image-gap, + align: horizon, + ..logos.map(logo => _cover-image-item(logo, height: height)), + ) +} + +#let _register-page(section: none, intro: false) = { + _fs-pages.update(p => p + ((section: section, intro: intro),)) +} + +#let _register-section-target(title, loc) = { + if title != none and title != "" { + _fs-sec-targets.update(t => { + if t.find(entry => entry.title == title) == none { + t + ((title: title, loc: loc),) + } else { + t + } + }) + } +} + +#let _header-band( + w, + section, + slide-title: none, + primary, + secondary, + header-font-color-1: auto, + header-font-color-2: auto, + header-font-color-1-highlight: white, +) = context { + let hfc1 = if header-font-color-1 == auto { _muted-nav(primary) } else { header-font-color-1 } + let hfc2 = if header-font-color-2 == auto { primary } else { header-font-color-2 } + let titles = _toc-data.final().filter(e => e.kind == "section").map(e => e.title) + let sec-targets = _fs-sec-targets.final() + let pages = _fs-pages.final() + let page-no = counter(page).get().first() + let nav-margin = 22.4pt + let nav-top = 3.425pt + let nav-bottom = 2.025pt + let nav-item-x-inset = 0.4pt + let active-dot-index = { + if ( + page-no <= pages.len() + and section != none + and section != "" + and not pages.at(page-no - 1, default: (intro: false)).intro + ) { + let seen = 0 + for (idx, entry) in pages.enumerate() { + if entry.section == section and not entry.intro { + seen += 1 + } + if idx + 1 == page-no { break } + } + if seen == 0 { none } else { seen } + } else { + none + } + } + let nav-slide-count(title) = { + calc.max(1, pages.filter(entry => entry.section == title and not entry.intro).len()) + } + let nav-progress-label(title) = { + let slide-count = nav-slide-count(title) + let current-page = pages.at(page-no - 1, default: (intro: false)) + let is-current-section = section == title + let is-section-slide = is-current-section and active-dot-index != none + if is-section-slide { + str(active-dot-index) + "/" + str(slide-count) + } else { + str(slide-count) + } + } + let nav-progress-width(title) = { + let slide-count = nav-slide-count(title) + if slide-count > _nav-compact-threshold { + let label-width = measure(text(font: _sans, size: 6.8pt, nav-progress-label(title))).width + 6.2pt + 1.6pt + label-width + } else { + 6.2pt * slide-count + 1.4pt * calc.max(0, slide-count - 1) + } + } + let nav-title-width(title, item-width) = { + let inner-width = calc.max(0pt, item-width - 2 * nav-item-x-inset) + let value = _single-line(title) + if value == none or type(value) != str { + 0pt + } else { + let raw-width = measure(text(font: _sans, size: 7.1pt, value)).width + calc.min(raw-width, inner-width) + } + } + let nav-visible-width(title, item-width) = { + calc.max(nav-title-width(title, item-width), nav-progress-width(title)) + } + let nav-item(title, item-width) = { + let slide-count = nav-slide-count(title) + let target = sec-targets.rev().find(entry => entry.title == title) + block( + width: item-width, + inset: (left: nav-item-x-inset, right: nav-item-x-inset, top: 1.3pt, bottom: 1.1pt), + { + layout(size => { + let title-fill = if section == title { header-font-color-1-highlight } else { hfc1 } + let current-page = pages.at(page-no - 1, default: (intro: false)) + let is-current-section = section == title + let is-section-intro = is-current-section and current-page.intro + let is-section-slide = is-current-section and active-dot-index != none + let use-compact-progress = slide-count > _nav-compact-threshold + let progress-label = nav-progress-label(title) + let progress-fill = if is-current-section { header-font-color-1-highlight } else { hfc1 } + + let title-content = if target != none { + link(target.at("loc"))[ + #_ellipsis-text( + title, + size.width, + font: _sans, + size: 7.1pt, + fill: title-fill, + ) + ] + } else { + _ellipsis-text( + title, + size.width, + font: _sans, + size: 7.1pt, + fill: title-fill, + ) + } + + let progress-content = if use-compact-progress { + grid( + columns: (auto, auto), + column-gutter: 1.6pt, + align: horizon, + _nav-dot( + active: is-section-slide or is-section-intro, + primary, + inactive-fill: hfc1, + active-fill: header-font-color-1-highlight, + ), + move( + dy: -0.2pt, + text(font: _sans, fill: progress-fill, size: 6.8pt, progress-label), + ), + ) + } else { + box[ + #for dot-idx in range(slide-count) { + _nav-dot( + active: is-section-slide and active-dot-index == dot-idx + 1, + primary, + inactive-fill: hfc1, + active-fill: header-font-color-1-highlight, + ) + if dot-idx + 1 < slide-count { h(1.4pt) } + } + ] + } + + grid( + columns: (1fr,), + rows: (8.6pt, 6.2pt), + row-gutter: 0pt, + block( + width: 100%, + height: 8.6pt, + align(left + horizon, title-content), + ), + block( + width: 100%, + height: 6.2pt, + align(left + horizon, move(dy: 2pt, progress-content)), + ), + ) + }) + }, + ) + } + stack( + dir: ttb, + spacing: 0pt, + block( + width: w, + height: 24.75pt, + fill: primary, + inset: (x: 0pt, y: 0pt), + { + if titles != () { + layout(size => { + let usable-width = size.width - 2 * nav-margin + let positions = if titles.len() == 1 { + (0.5,) + } else if titles.len() == 2 { + (1 / 3, 2 / 3) + } else if titles.len() == 3 { + (1 / 4, 1 / 2, 3 / 4) + } else { + range(titles.len()).map(idx => (idx + 0.5) / titles.len()) + } + let item-width = if titles.len() == 1 { + calc.min(usable-width, 260pt) + } else if titles.len() == 2 { + calc.min(usable-width / 3, 210pt) + } else if titles.len() == 3 { + calc.min(usable-width / 4, 160pt) + } else { + usable-width / titles.len() + } + let relative-lefts = positions.map(pos => usable-width * pos - item-width / 2) + let first-left = relative-lefts.at(0) + let last-left = relative-lefts.at(relative-lefts.len() - 1) + let last-title = titles.at(titles.len() - 1) + let last-visible-width = nav-visible-width(last-title, item-width) + let centering-shift = ( + ( + usable-width - first-left - last-left - last-visible-width - 2 * nav-item-x-inset + ) + / 2 + ) + for (idx, title) in titles.enumerate() { + place( + top + left, + dx: nav-margin + centering-shift + relative-lefts.at(idx), + dy: nav-top, + nav-item(title, item-width), + ) + } + }) + } + }, + ), + if slide-title != none { + block( + width: w, + height: 25.5pt, + fill: secondary, + inset: (left: _single-col-x-margin, right: _single-col-x-margin, top: 0pt, bottom: 0pt), + block( + width: 100%, + height: 100%, + align(horizon, align(left, text(font: _sans, fill: hfc2, size: 14pt, slide-title))), + ), + ) + }, + ) +} +#let _footer-band( + w, + left-top, + right-top, + left-bottom, + page-no, + primary, + secondary, + header-font-color-1: auto, + header-font-color-2: auto, + header-font-color-1-highlight: white, +) = { + let hfc1 = if header-font-color-1 == auto { _muted-nav(primary) } else { header-font-color-1 } + let hfc2 = if header-font-color-2 == auto { primary } else { header-font-color-2 } + stack( + dir: ttb, + spacing: 0pt, + block(width: w, height: 14.1pt, fill: secondary, inset: (left: 22.4pt, right: 22.4pt, top: 0pt, bottom: 0pt), { + grid( + columns: (1fr, 36%), + column-gutter: 10pt, + block( + width: 100%, + height: 100%, + align(horizon, align(left, if _single-line(left-top) != none { + text(font: _sans, fill: hfc2, size: 7pt, _single-line(left-top)) + } else { [] })), + ), + block( + width: 100%, + height: 100%, + align(horizon, align(right, if _single-line(right-top) != none { + text(font: _sans, fill: hfc2, size: 7pt, _single-line(right-top)) + } else { [] })), + ), + ) + }), + block(width: w, height: 15.51pt, fill: primary, inset: (left: 22.4pt, right: 22.4pt, top: 0pt, bottom: 0pt), { + grid( + columns: (1fr, 36%), + column-gutter: 10pt, + block( + width: 100%, + height: 100%, + align(horizon, align(left, if _single-line(left-bottom) != none { + move(dy: -1pt, text(font: _sans, fill: hfc1, size: 7pt, _single-line(left-bottom))) + } else { [] })), + ), + block( + width: 100%, + height: 100%, + align(horizon, align(right, move(dy: -1pt, text( + font: _sans, + fill: hfc1, + size: 6.8pt, + _single-line(page-no), + )))), + ), + ) + }), + ) +} + +#let _slide-body-area(w, h, body) = context { + let center = _clamp01(_fs-cctr.get()) + let upper = _clamp01(_fs-cupad.get()) + let lower = _clamp01(_fs-clpad.get()) + let usable-ratio = calc.max(0, 1 - upper - lower) + + block( + width: w, + height: h, + inset: (x: _single-col-x-margin, y: 0pt), + clip: true, + layout(size => { + let content-width = size.width + let inner-top = size.height * upper + let inner-height = size.height * usable-ratio + let content = block(width: content-width, body) + let measured = measure(content) + let free-height = calc.max(0pt, inner-height - measured.height) + let dy = inner-top + if center <= 0 { 0pt } else { free-height * center } + place(top + left, dy: dy, content) + }), + ) +} + +// ============================================================ +// Public API -- layout helpers +// ============================================================ + +/// Two-column layout inside a slide. +/// #two-col([left content], [right content], left-width: 50%) +#let two-col(left, right, gutter: 22.4pt, left-width: 48%) = { + block( + width: 100%, + grid( + columns: (left-width, 1fr), + column-gutter: gutter, + grid.cell(align: top + start)[#left], + grid.cell(align: top + start)[#right], + ), + ) +} + +// ============================================================ +// Figure +// ============================================================ + +#let _visual-y-margin = 15.75pt +#let _caption-gap = 2.3pt +#let _diagram-caption-gap = _caption-gap +#let _table-caption-gap = 6pt +#let _table-after-caption-margin = _visual-y-margin + +/// Generic visual block without a caption. +#let fs-visual(body) = block( + width: 100%, + above: _visual-y-margin, + below: _visual-y-margin, + align(center, body), +) + +/// Cell helper for grid-built tables. Uses the template's table font. +#let fs-table-cell(body, fill: none, stroke: none, pos: left, inset: (x: 5pt, y: 4pt)) = block( + width: 100%, + fill: fill, + stroke: stroke, + inset: inset, + align(pos, text(font: _sans, body)), +) + +/// Numbered figure with optional italic caption. +/// Counters reset per section. +#let fs-figure(body, caption: none, caption-gap: _caption-gap) = { + _fig-ctr.step() + block(above: _visual-y-margin, below: _visual-y-margin, spacing: 0pt, width: 100%, { + align(center, body) + if caption != none { + v(caption-gap) + align(center, context text(size: 0.72em, style: "italic", [*Figure #_fig-ctr.display().* #caption])) + } + }) +} + +/// Diagram figure with template-level caption spacing. +#let fs-diagram(body, caption: none) = fs-figure( + caption: caption, + caption-gap: _diagram-caption-gap, + body, +) + +// ============================================================ +// Theorem-style boxes +// ============================================================ +#let _panel-box(type-label, title, color, body, body-fill: none, width: 100%) = block( + width: width, + breakable: false, + above: _visual-y-margin, + below: _visual-y-margin, + stroke: color + 0.8pt, + fill: body-fill, + inset: 0pt, + spacing: 0pt, + align(top + left, stack( + dir: ttb, + spacing: 0pt, + if title != none { + grid( + columns: (auto, 1fr), + column-gutter: 0pt, + block( + fill: color, + inset: (left: 5pt, right: 5pt, top: 3.5pt, bottom: 5.3pt), + text(font: _sans, fill: white, weight: "bold", size: 0.80em, type-label), + ), + block( + fill: _sub-hdr(color), + inset: (left: 5pt, right: 5pt, top: 3.5pt, bottom: 5.3pt), + width: 100%, + text(font: _sans, fill: white, size: 0.80em, title), + ), + ) + } else { + block( + width: 100%, + fill: color, + inset: (left: 5pt, right: 5pt, top: 3.5pt, bottom: 5.3pt), + text(font: _sans, fill: white, weight: "bold", size: 0.80em, type-label), + ) + }, + if body-fill != none { + block(width: 100%, height: 1.2pt, fill: body-fill) + }, + block( + width: 100%, + inset: (left: 5pt, right: 5pt, top: 5pt, bottom: 7pt), + fill: body-fill, + align(left, body), + ), + )), +) + +#let _tbox(kind, name, color, body, width: 100%) = { + let ctr = _thm-ctrs.at(kind, default: _thm-ctrs.theorem) + ctr.step() + let cap = upper(kind.first()) + kind.slice(1) + context { + let sec = _sec-ctr.get().first() + let num = ctr.get().first() + _panel-box([#cap #sec.#num], name, color, body, width: width) + } +} + +/// Theorem box +#let theorem(name: none, color: blue.darken(50%), width: 100%, body) = { + _tbox("theorem", name, color, body, width: width) +} +/// Lemma box +#let lemma(name: none, color: blue.darken(30%), width: 100%, body) = { _tbox("lemma", name, color, body, width: width) } +/// Corollary box +#let corollary(name: none, color: blue.darken(40%), width: 100%, body) = { + _tbox("corollary", name, color, body, width: width) +} +/// Proposition box +#let proposition(name: none, color: teal.darken(30%), width: 100%, body) = { + _tbox("proposition", name, color, body, width: width) +} +/// Definition box +#let definition(name: none, color: purple.darken(20%), width: 100%, body) = { + _tbox("definition", name, color, body, width: width) +} +/// Example box +#let example(name: none, color: green.darken(30%), width: 100%, body) = { + _tbox("example", name, color, body, width: width) +} +/// Exercise box +#let exercise(name: none, color: orange.darken(20%), width: 100%, body) = { + _tbox("exercise", name, color, body, width: width) +} +/// Remark box +#let remark(name: none, color: luma(90), width: 100%, body) = { _tbox("remark", name, color, body, width: width) } + +/// Generic custom box -- any kind label and color. +/// #fs-box("warning", name: "Watch out", color: red)[...] +#let fs-box(kind, name: none, color: blue.darken(50%), width: 100%, body) = { + _tbox(kind, name, color, body, width: width) +} + +/// Full-width separator for use inside theorem-style boxes. +#let box-separator(label, color: luma(80)) = { + v(5pt) + move( + dx: -5pt, + block( + width: 100% + 10pt, + fill: color, + inset: (x: 5pt, y: 3pt), + text(font: _sans, fill: white, weight: "bold", size: 0.80em, label), + ), + ) + v(5pt) +} + +/// Display equation with the same vertical rhythm as visual boxes. +#let fs-equation(body) = block( + width: 100%, + above: _visual-y-margin, + below: _visual-y-margin, + align(center, body), +) + +/// Proof environment with QED box. +#let proof(width: 100%, body) = { + block( + width: width, + inset: (x: 6pt, y: 5pt), + above: _visual-y-margin, + below: _visual-y-margin, + stroke: (left: luma(80) + 2pt), + spacing: 0.4em, + align(left, text(style: "italic", [_Proof._ ]) + body + h(1fr) + box(width: 6pt, height: 6pt, fill: luma(80))), + ) +} + +// ============================================================ +// Code and pseudo-code boxes +// ============================================================ + +/// Generic code-style box with theorem-like header. +/// #code-box("print('hi')", type: "Code", title: "Python", lang: "python") +#let code-box( + code, + type: "Code", + title: none, + lang: none, + color: blue.darken(50%), + fill: luma(245), + text-size: 8pt, +) = { + _panel-box( + type, + title, + color, + block( + width: 100%, + inset: 0pt, + spacing: 0pt, + move( + dy: -1.6pt, + text( + font: _mono, + size: text-size, + if lang == none { raw(code) } else { raw(lang: lang, code) }, + ), + ), + ), + body-fill: fill, + ) +} + +/// Pseudo-code box with theorem-like header. +/// #pseudo-code("for i <- 1 to n", title: "Mini-Batch SGD") +#let pseudo-code( + code, + type: "Pseudo-code", + title: none, + color: teal.darken(30%), + fill: luma(252), + text-size: 8pt, +) = { + code-box( + code, + type: type, + title: title, + color: color, + fill: fill, + text-size: text-size, + ) +} + +// ============================================================ +// Section management +// ============================================================ + +/// Register a subsection in the TOC (does not create a slide). +#let new-subsection(title) = { + _toc-data.update(t => t + ((kind: "sub", title: title),)) +} + +// ============================================================ +// Slide functions +// ============================================================ + +/// Standard content slide. +/// #slide(title: "My Slide")[Content] +#let slide(title: none, section: auto, show-title-band: auto, body) = { + context { + let tc = _fs-tc.get() + let sc = _fs-sc.get() + let bg = _fs-bg.get() + let w = _fs-w.get() + let h = _fs-h.get() + let ft = _fs-ft.get() + let auth = _fs-auth.get() + let inst = _fs-inst.get() + let hfc1 = _fs-hfc1.get() + let hfc2 = _fs-hfc2.get() + let hfc1h = _fs-hfc1h.get() + let cur = _cur-sec.get() + let sec = if section == auto { cur } else { section } + let header-title = if show-title-band == auto { + if sec == none or sec == "" { none } else { title } + } else if show-title-band and title != none { + title + } else { + none + } + + let header-h = if header-title == none { 24.75pt } else { 50.25pt } + let footer-h = 29.61pt + let avail = h - header-h - footer-h + let slide-loc = here() + _register-section-target(sec, slide-loc) + _register-page(section: sec) + + page( + width: w, + height: h, + margin: 0pt, + background: rect(width: 100%, height: 100%, fill: bg), + header: none, + footer: none, + { + place( + top + left, + float: false, + _header-band( + w, + if sec == none { "" } else { sec }, + slide-title: header-title, + tc, + sc, + header-font-color-1: hfc1, + header-font-color-2: hfc2, + header-font-color-1-highlight: hfc1h, + ), + ) + place( + bottom + left, + float: false, + _footer-band( + w, + auth, + inst, + ft, + counter(page).display() + " / " + str(counter(page).final().first()), + tc, + sc, + header-font-color-1: hfc1, + header-font-color-2: hfc2, + header-font-color-1-highlight: hfc1h, + ), + ) + place( + top + left, + dy: header-h, + float: false, + _slide-body-area(w, avail, body), + ) + }, + ) + } +} + +/// Section divider slide (typically called right after new-section). +#let section-slide(sec-title) = { + context { + let tc = _fs-tc.get() + let sc = _fs-sc.get() + let bg = _fs-bg.get() + let w = _fs-w.get() + let h = _fs-h.get() + let ft = _fs-ft.get() + let auth = _fs-auth.get() + let inst = _fs-inst.get() + let hfc1 = _fs-hfc1.get() + let hfc2 = _fs-hfc2.get() + let hfc1h = _fs-hfc1h.get() + let nav-sec = { + let cur = _cur-sec.get() + if cur == none or cur == "" { sec-title } else { cur } + } + let sec-loc = here() + _register-page(section: nav-sec, intro: true) + _register-section-target(nav-sec, sec-loc) + + page( + width: w, + height: h, + margin: 0pt, + background: rect(width: 100%, height: 100%, fill: bg), + header: none, + footer: none, + { + place( + top + left, + float: false, + _header-band( + w, + nav-sec, + tc, + sc, + header-font-color-1: hfc1, + header-font-color-2: hfc2, + header-font-color-1-highlight: hfc1h, + ), + ) + place( + bottom + left, + float: false, + _footer-band( + w, + auth, + inst, + ft, + counter(page).display() + " / " + str(counter(page).final().first()), + tc, + sc, + header-font-color-1: hfc1, + header-font-color-2: hfc2, + header-font-color-1-highlight: hfc1h, + ), + ) + block( + width: w, + height: h - 24.75pt - 29.61pt, + align(center + horizon, block( + width: 65%, + stroke: tc + 0.8pt, + inset: 0pt, + { + block( + width: 100%, + fill: tc, + inset: (x: 12pt, y: 4pt), + context { + let n = _sec-ctr.get().first() + text(font: _sans, fill: hfc1h, size: 8pt, [Section #n]) + }, + ) + block( + width: 100%, + height: 38pt, + inset: (x: 12pt, y: 0pt), + align(horizon, move(dy: -3pt, text(font: _sans, size: 15pt, weight: "bold", fill: tc, sec-title))), + ) + }, + )), + ) + }, + ) + } +} + +/// Declare a new section and render its intro slide. +#let new-section(title, slide-title: auto) = { + _sec-ctr.step() + for (_, c) in _thm-ctrs { c.update(0) } + _fig-ctr.update(0) + _cur-sec.update(title) + _toc-data.update(t => t + ((kind: "section", title: title),)) + section-slide(if slide-title == auto { title } else { slide-title }) +} + +/// Final message slide. +#let final-slide = context { + let tc = _fs-tc.get() + let sc = _fs-sc.get() + let bg = _fs-bg.get() + let w = _fs-w.get() + let h = _fs-h.get() + let ft = _fs-ft.get() + let auth = _fs-auth.get() + let inst = _fs-inst.get() + let hfc1 = _fs-hfc1.get() + let hfc2 = _fs-hfc2.get() + let hfc1h = _fs-hfc1h.get() + let final-message = _fs-final.get() + _register-page() + + page( + width: w, + height: h, + margin: 0pt, + background: rect(width: 100%, height: 100%, fill: bg), + header: none, + footer: none, + { + place( + top + left, + float: false, + _header-band( + w, + "", + tc, + sc, + header-font-color-1: hfc1, + header-font-color-2: hfc2, + header-font-color-1-highlight: hfc1h, + ), + ) + place( + bottom + left, + float: false, + _footer-band( + w, + auth, + inst, + ft, + counter(page).display() + " / " + str(counter(page).final().first()), + tc, + sc, + header-font-color-1: hfc1, + header-font-color-2: hfc2, + header-font-color-1-highlight: hfc1h, + ), + ) + block( + width: w, + height: h - 24.75pt - 29.61pt, + align(center + horizon, text(font: _serif, size: 24pt, style: "italic", fill: tc, final-message)), + ) + }, + ) +} + +// ============================================================ +// Main entry point -- use via: +// #show: black-angular-frame.with(config: (title: "...", ...)) +// ============================================================ +#let black-angular-frame( + config: (:), + title: none, + subtitle: none, + institution: none, + institute: none, + date: none, + authors: none, + final-message: none, + primary-color: none, + secondary-color: none, + background-color: none, + font-color: none, + header-font-color-1: none, + header-font-color-2: none, + header-font-color-1-highlight: none, + content-center: none, + content-upper-padding: none, + content-lower-padding: none, + logos: none, + ratio: 16 / 9, + title-color: none, + bg-color: none, + toc: none, + footer-title: auto, + footer-subtitle: auto, + logo: none, + cover-images: none, + cover-image-height: 45pt, + body, +) = { + let cfg-title = config.at("title", default: if title == none { "" } else { title }) + let cfg-subtitle = config.at("subtitle", default: if subtitle == none { "" } else { subtitle }) + let cfg-authors = config.at("authors", default: if authors == none { "" } else { authors }) + let cfg-institution = config.at( + "institution", + default: if institution != none { institution } else if institute != none { institute } else { "" }, + ) + let cfg-date = config.at("date", default: if date == none { "" } else { date }) + let cfg-final-message = config.at( + "final-message", + default: if final-message == none { "" } else { final-message }, + ) + let cfg-primary = config.at( + "primary-color", + default: if primary-color != none { primary-color } else if title-color != none { title-color } else { + rgb("#1C1C1C") + }, + ) + let cfg-secondary = config.at( + "secondary-color", + default: if secondary-color == none { rgb("#D9D9D9") } else { secondary-color }, + ) + let cfg-background = config.at( + "background-color", + default: if background-color != none { background-color } else if bg-color != none { bg-color } else { + rgb("#FFFFFF") + }, + ) + let cfg-font-color = config.at( + "font-color", + default: if font-color == none { luma(20) } else { font-color }, + ) + let cfg-hfc1 = config.at( + "header-font-color-1", + default: if header-font-color-1 == none { _muted-nav(cfg-primary) } else { header-font-color-1 }, + ) + let cfg-hfc2 = config.at( + "header-font-color-2", + default: if header-font-color-2 == none { cfg-primary } else { header-font-color-2 }, + ) + let cfg-hfc1h = config.at( + "header-font-color-1-highlight", + default: if header-font-color-1-highlight == none { white } else { header-font-color-1-highlight }, + ) + let cfg-content-center = config.at( + "content-center", + default: if content-center == none { 0.3 } else { content-center }, + ) + let cfg-content-upper-padding = config.at( + "content-upper-padding", + default: if content-upper-padding == none { 0.05 } else { content-upper-padding }, + ) + let cfg-content-lower-padding = config.at( + "content-lower-padding", + default: if content-lower-padding == none { 0.05 } else { content-lower-padding }, + ) + let cfg-logos = config.at( + "logos", + default: if logos != none { logos } else if cover-images != none { cover-images } else if logo != none { + (logo,) + } else { () }, + ) + let cfg-toc = config.at("TOC", default: if toc == none { true } else { toc }) + + let ft = if footer-title == auto { cfg-title } else { footer-title } + let fst = if footer-subtitle == auto { cfg-subtitle } else { footer-subtitle } + let inst = if cfg-institution == "" { none } else { cfg-institution } + let subt = if cfg-subtitle == "" { none } else { cfg-subtitle } + let date-text = if cfg-date == "" { none } else { cfg-date } + let cover-imgs = if cfg-logos == none { + () + } else if type(cfg-logos) == array { + cfg-logos + } else { + (cfg-logos,) + } + let auth = if cfg-authors != "" and cfg-authors != none { + let al = if type(cfg-authors) == array { cfg-authors } else { (cfg-authors,) } + al.join([, ]) + } else { none } + + let w = if ratio > (16 / 9 - 0.01) { 254mm } else { 190mm } + let h = w / ratio + + // Push config into state so slide() can read it contextually + _fs-tc.update(cfg-primary) + _fs-sc.update(cfg-secondary) + _fs-bg.update(cfg-background) + _fs-w.update(w) + _fs-h.update(h) + _fs-ft.update(ft) + _fs-fst.update(fst) + _fs-title.update(cfg-title) + _fs-subt.update(cfg-subtitle) + _fs-inst.update(inst) + _fs-auth.update(auth) + _fs-fc.update(cfg-font-color) + _fs-hfc1.update(cfg-hfc1) + _fs-hfc2.update(cfg-hfc2) + _fs-hfc1h.update(cfg-hfc1h) + _fs-final.update(cfg-final-message) + _fs-cctr.update(cfg-content-center) + _fs-cupad.update(cfg-content-upper-padding) + _fs-clpad.update(cfg-content-lower-padding) + + // Global text defaults + set text(font: _serif, size: 10.5pt, fill: cfg-font-color) + set figure(gap: _caption-gap) + show figure.where(kind: table): it => block( + above: _visual-y-margin, + below: _table-after-caption-margin, + { + set figure(gap: _table-caption-gap) + it + }, + ) + show figure.caption.where(kind: table): it => { + v(_table-caption-gap) + align(center, text(size: 0.72em, style: "italic", [*#it.supplement #it.counter.display().* #it.body])) + } + show table.cell: set text(font: _sans) + show table: it => figure(kind: table, caption: [Table caption.], gap: _table-caption-gap, it) + set par(justify: true, leading: 0.55em, spacing: 0.65em) + set list(indent: 6pt) + set enum(indent: 6pt) + set page(width: w, height: h, margin: 0pt, header: none, footer: none) + + // ---- Title slide ----------------------------------------- + page( + width: w, + height: h, + margin: 0pt, + background: rect(width: 100%, height: 100%, fill: cfg-background), + header: none, + footer: none, + { + _register-page() + place( + top + left, + float: false, + _header-band( + w, + "", + cfg-primary, + cfg-secondary, + header-font-color-1: cfg-hfc1, + header-font-color-2: cfg-hfc2, + header-font-color-1-highlight: cfg-hfc1h, + ), + ) + place( + bottom + left, + float: false, + _footer-band( + w, + auth, + inst, + ft, + context counter(page).display() + " / " + str(counter(page).final().first()), + cfg-primary, + cfg-secondary, + header-font-color-1: cfg-hfc1, + header-font-color-2: cfg-hfc2, + header-font-color-1-highlight: cfg-hfc1h, + ), + ) + block( + width: w, + height: h - 24.75pt - 29.61pt, + align(center + horizon, { + block( + width: 84%, + fill: cfg-primary, + inset: (x: 14pt, y: 10pt), + align(center + horizon, { + set text(font: _sans, fill: cfg-hfc1h) + text(size: 19pt, cfg-title) + if subt != none { + linebreak() + v(4pt) + text(size: 11pt, subt) + } + }), + ) + v(12pt) + if auth != none { + text(font: _sans, size: 11pt, auth) + linebreak() + v(6pt) + } + if inst != none { + text(font: _sans, size: 9.5pt, inst) + linebreak() + v(6pt) + } + if date-text != none { + text(font: _sans, size: 10pt, date-text) + linebreak() + } + v(26pt) + if cover-imgs != () { + _cover-image-row(cover-imgs, height: cover-image-height) + } + }), + ) + }, + ) + + // ---- TOC slide ------------------------------------------- + if cfg-toc { + slide(title: "Table of Contents", section: none, show-title-band: false, { + context { + let entries = _toc-data.final() + let sec-targets = _fs-sec-targets.final() + let sec-n = 0 + text(font: _sans, size: 14pt, weight: "bold", fill: cfg-primary, [Table of Contents]) + v(8pt) + for e in entries { + if e.kind == "section" { + sec-n += 1 + let target = sec-targets.rev().find(entry => entry.title == e.title) + let number-cell = box( + width: 16pt, + height: 16pt, + fill: cfg-primary, + align(center + horizon, text(font: _sans, fill: cfg-hfc1h, size: 8pt, weight: "bold", str(sec-n))), + ) + let title-cell = align(left + horizon, text( + font: _sans, + size: 10pt, + weight: "bold", + fill: cfg-primary, + e.title, + )) + v(3pt) + grid( + columns: (18pt, 1fr), + column-gutter: 4pt, + if target != none { link(target.at("loc"))[#number-cell] } else { number-cell }, + if target != none { link(target.at("loc"))[#title-cell] } else { title-cell }, + ) + } else { + pad(left: 22pt, text(font: _sans, size: 9pt, fill: luma(40), "- " + e.title)) + } + } + } + }) + } + + // ---- User content ---------------------------------------- + body +} diff --git a/packages/preview/black-angular-frame/0.1.0/template/assets/curves.csv b/packages/preview/black-angular-frame/0.1.0/template/assets/curves.csv new file mode 100644 index 0000000000..00d583ed4a --- /dev/null +++ b/packages/preview/black-angular-frame/0.1.0/template/assets/curves.csv @@ -0,0 +1,12 @@ +x,model_a,model_b,baseline +0.0,0.52,0.48,0.50 +0.5,0.57,0.51,0.50 +1.0,0.63,0.55,0.50 +1.5,0.70,0.60,0.50 +2.0,0.76,0.65,0.50 +2.5,0.81,0.69,0.50 +3.0,0.85,0.73,0.50 +3.5,0.88,0.76,0.50 +4.0,0.90,0.79,0.50 +4.5,0.91,0.81,0.50 +5.0,0.92,0.83,0.50 diff --git a/packages/preview/black-angular-frame/0.1.0/template/assets/github-logo.png b/packages/preview/black-angular-frame/0.1.0/template/assets/github-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..34966ac65567e4180fa43cc212fc1ec5074e4765 GIT binary patch literal 6362 zcmYjWc{o&I+gDLZq9U4-tjQi@iIANvg~&3*p`+nDVeSe&Dt~t+~Gv_?degC$Je4?vPe*toVii(O}Q$s}`90$Qxc>XNd zd(ir4gTpBgeRUSM7u7NmV+`%q!!&bTC(iOn@Ueev_RDsRy6P)WqKf$u+<7)>}YOANis)l|8l zCDG|bv8^h6#MvL0Woi)DuJOX4@gZs{HbtS0_$bl-iI8b!r{Jeh`##=~;as{$ZrYHA znom%_pyM6ZFw0qX@22gcnWe)Iq~izO@=jeM<6|Ss{+6d~ViXyvmyX!!sTC)3A1>6b z89*U;GMV$%1D`i;(lOx->;$3lFIHy+uhBKFC6#9t7M>3c4MlW>j$Xwl*?fP_XkcKl zvw~x|cmF&9VxY#LzlTh^ z;LS^YwE~5=FVRWtEV>3ovsebi5yHr;K5hp)2ngMU2=dQw8yipv1R^RWrOt9)UsIE| z%%U;s=g&u24GqPMLHb4Y^*0O+4bgW6sXnEqDjOOyW9#cFoE$V2v-*7C^>=48yLXqL z)*0#P1vejUmghXvI|YWGJ2_p%M&MeQ-rtgosvUowN@#&NJ3C{1RymP0da!m0Nl9`DgM3!Shi{}tf4r6T z=^V_JKaKw01XSRsq)s~=@`jYX6P%nD%@~@L3>IyH)R;;@^F}!$_xAksj3L_E+F4Ca z83Z&>l)y||92e&Gp$w8Hsm!wZJ(v3XoeX%+1X` zlVb2?8cWN|FL`))Drf(C%u^^<@!Y+&I98iqv+M1*%F*|4Ucx>y5PgAgIQ&BO{B?6? znGs=jy&^2^?2yNLzKP&9Nhnms(o1Qh4xZXQn_CQhU~r9PY$X{gaqv2b)Ok9;s?eya zwzgv+yCxY)>8k6>kSq4Jjeb(CVRJ`c8#>CA0-MZpA zE{vY)4QL(Co0q1dMVh{aoMUjC?PAl2e;0Hx#qQ9moFVIdsd)!4VxKnf?Hkqb$cRHp zRL69SKzW0b%~VdmoozgKdy|g1gPfe4XaDCCZF`%qLx1W#X8+91*%Q~(wcjam%&hNz zYMrtY5)vv7(&`vr**~m2R_4{|a5Q$+HiQYVLrZlQISe_qw6vf<9oN^_Pex1g^Fzyq zzi5jq*|@vw8+fmos;a6&^_?5XlHRrYpM3)+njs+}0V>Je$28dedP^Mg;+J(rPuq&m z>+36{*LHs~;U(_hzk7CiCT@PPJk@&nMHJa@v3k%xPgD9PI&M;x^D@Mt#Xt2g7mnqb zDC=K&a-f&jN42opFJB&V>3~`R&55MPui!UlQeBA}Unvv^$aRa`j9GDZeomX(`n3*|R$r1PD{B zJsJsSwJsbGB++L~+!is`;G3Y966w2hR8U%qR7;^$bh>wbz~{GesM*+XA+?LYd||v4 z9zsZ&`|kkW|3`HnB`=PWpIY`%h%_R|&RU5%I+#`H(@2<^v4bRAI6Dg=p_7fBYr!1; zbiUhmo!#BhH=OP~F*S{CyiXb;u0>4gii!- zC$;-I)x*SKh*hlBH&gFrQhpp`ebMR1|9h5#NT4Q-_o!vHl3=mUC@t{prDnB64RJ1u zwt{{Dw;~V+bZboq4~}`z;IW)4S0^cO+Z>Wi`tgGr3GF+X6wF_h?EHM5vh70CWyp;i zeWV~0q1-?w`r28g1@u_MA?0yUPMiZM_NCDL!I-8x+cq-X-X0Y3nKNggVx^OlRuBim z{?D0!=WS%andF!DdCAw8?&SY&PXH5|d}~B-@S+KKKUT9fjJp>k$@{*CI^2XJ=(mJu8D{M-LzY0s!WJS-QP+u)7WwV^a3Ezb)DJ zl%nc0Jw5FvlU|IHA8qixTHdtBA82f9RLp!U_FVWSww&lSBESNeFbgT@I1sDxY|@Q2 zRmv4zfe^h4uDbYem{;>kAiqsaMRqpFkWG?gIPSZey7~~2c*WdVclYr1Kz~1vf8!8b zw;-X38+Y#@gO1AX^ToV#R?{hgDYed-*;tsh^z<)c4; z=06c3;d7jT$#Dz{KYssa0=J$73Pib5H$JbV@i;BJ-|i`(<4V_Vl}?x1g3%7J8bcmc zH6F8Fy$ZQQb8~!OO-n1AlNd??05rjr#dHvw@!TVAMZ;}v=gsl_shzz30q9Pq zxf^cMBSJ%ywY{Iv7(i{4Zf%mDpHBo2KdVhlD9&ogM%)?TyXPjXw;45W7v@pQjOH)X zj%r!n?&}vNu&(xQnsHD#*h*=-?11 zXQV?%ecCe#n-wJWthBi~vk9s!ZVrTqv8rmQ6RrRbaH-7N(AT$aVf9f8-a5OOh#baQ z$pIdr2{Z|NxpNs3850xJ!5$vi%|lRJdA2v}8xP|!t1k!mId(|2Mv zeD>j+%N3Wz5FWFMN; z`V5F~j-UJvXEn3xW2~$kvQQVnW^sw0o<}acPm7N!zVV||HTdeDyEcxA3mUH{zQjni z;3Z4)on?dEVUAS!($J8$PRF0y%O~kFsxeg}da(1IN!uJoC3^pUqa&6}UyAo>cuS~+t6doz8~dz| z_@wvvaqPd|yLKFx==xXzuDx)ws0kA3oBHBrYfBAOOl}1u*A~nHDk-TH&uz$ce@zDp z$&a&(=Z!a{gNrTZ zIpsxDhpHeDJ;TEn2%rAxvF)}^t-iSzX4Cq-~O9h z!ZHXjR@kyB1}yz{3h%6|gTr?K)V-slg2F+JYx)CG0sx1mQI-QQBA%F=vyuGfOVbWt z=k-ra+xgnJ;ejba&nf>j!1~gJhle{P{$5xRM3RjKuQ9Q(;NaD~PJq`VBhrI50BcSpv;&v4KN4w*T?#;x!_LAZ%69&$JPTMLwPZhuAZRX* z)w66bjcL^nYO1$qpi4>woeRRoTv$+8Iv7GgSR(x_XeH3%xfNvqp(fXNuVgUPjk(-Q zDEY=#`(MeP_`AROnL{G8|JK5H;P+1lQvo%5@EUa0iPsJ+ylqd&#{j+FGAW9@3U_sV zyc^;w`P9hBx;sNIg+X&Lr1h46jGEF%x4UI0vX^JGFY_o-&E#^VihfOKD9v7C3@}(K zxq3t65h|S6(Ju7}7Ni?SGYH#$f9(E*6Djn(>{xbesD z!L6b7D-xOAEZYzA%bNklhXRk}CT-h6tUfwA!Zf$jq2vZTQ>7ddKj0>_^o$<(th`a9 zEp5CY13{93!{y`SdkCC5V2WDDUa{nQwhDo-{Y3X-&U}9lp5yWVs9f5yX0U8gV3|Ry z0*1r#m2Pc$-IR8FLr8g1jRBE>ePZnru+laA@*`p4;rB#Evp%oxDT!jY$AdDDRf;^H zeWb;v>I}>YE0{G{lrL0s!-2&s?$+DGdGG>SZf%s$4-fULQ3($P;YMXE4?Otc8+k)D zO3@j)4MRWUw&K#mi}v_wv7mgZuSV_(izE^$=J6g1g+fexJ7pp=F){IcIBq>+ESW^{ zJY8|B0jQ`A4j4dNOe$BJd>{k}Dfp;OWocd>H9)cV9YDP?nSai;-3um>EE+b5TF{T# zQui#4jo6VE3gpnRJ#!V!4*a2_7b_=R18c|JkGJO&nM<&e_{Zdx1;XYcH+ z`r-vojmuaR5cSrxUFkkkNU5irX-6Qb$vwG^eql*Tj3Y(HW&pr*f}G`|4E1zI;MP#p z@d)4qt7TFnRCi7!XqOK8v;&TatmAnH)AB6nR{0H^j*FQg>wB0qq;xSTmX(&$14rjp z2a8H2G9;M%9vC3-)t^OG%-;h&qZrX1w8p$rJi8kV8Qq@sA5nG5&gqK*Xuh%B}vm z3e^wQLiFm*6{S7!6`Rsqe)|pAe0=B%;mLc&O^+fgEx>j2f4^V9LY?vZS9uXlEqQN} zAxI-PZ*PCUcVtA%2B!g)%K5;aND4u=8m^B)MH)Bat(L>_(}RbvR4A|;yWfER zODQcRF!_{<~o14;s zdr*)BMT7A&T`g3*=gVZ^;>-@?<=y|Dmywl4>}^o`2i=bjeD@Obes8?Fqk8VfTF<}$ z?+xnYQuOvW^ZW!eG(4UgUur=4W-e1`NGnv!s4tVu->o*DOAM}mklIIy@uyEFH_e`Y z6U`!$gT06;6dq>UfUNI}I)ERWVuASbCM`9-T;?<>U^0oiscH#ZkL z5ZXHR{XY@Z($AkiFODO=S_2&dtF<&YPbX_7CeHo)NA}wJx4plseHN1IkKoukBW`oi zq8zoqPz2~^Zh85f&BfMO_JU|b-xISRKV0jYNoMEnGch&I(HrRO?CiBS1PKS=8KhxR zVc|DmcFN4_KUQMq=x-366I=u5%`jezDW8+muh3GT4)8laI3bo^ZQ9q@$9n4)VY&mg z`u!Yx58h#MyqWzZnD%boH&<<|2$m@N29u1u{QC}+NI_bUr?2mIh2!0B=CLy=|CW|?cS)D$y`H&0o(m2>b@1<5 zvp%#_f(io@zWKmMbSXquP7Y~#yf^p(M-!oO#ewPjD)6uc67oALZaZmfLEoy>r@Ad?5^)xpQl z|F$2l3XDM4_%cYNxiyvem`odOgQmY$iIrz(tmR;+zeiNMck!IJ+c-Xz!DMTW8II}# Q|3^@1s_LqgDLsGtKW)v!>i_@% literal 0 HcmV?d00001 diff --git a/packages/preview/black-angular-frame/0.1.0/template/assets/typst-logo.png b/packages/preview/black-angular-frame/0.1.0/template/assets/typst-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..773232fdf66739d77f7ed68d614ff771fb96b5b4 GIT binary patch literal 12710 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 literal 0 HcmV?d00001 diff --git a/packages/preview/black-angular-frame/0.1.0/template/main.typ b/packages/preview/black-angular-frame/0.1.0/template/main.typ new file mode 100644 index 0000000000..1b30d5bc01 --- /dev/null +++ b/packages/preview/black-angular-frame/0.1.0/template/main.typ @@ -0,0 +1,1796 @@ +// ============================================================ +// black-angular-frame -- Example / Demo Presentation +// This file demonstrates all features of the template. +// ============================================================ + +#import "@preview/black-angular-frame:0.1.0": * + +#let presentation-config = ( + title: "Black Angular Frame", + subtitle: "A Typst Template for Academic Presentations", + authors: "Author One, Author Two", + institution: "Institution Name", + date: "May 2026", + final-message: "Thank you for your attention", + primary-color: rgb("#1C1C1C"), + secondary-color: rgb("#D9D9D9"), + background-color: rgb("#FFFFFF"), + font-color: luma(20), + header-font-color-1: rgb("#999999"), + header-font-color-2: rgb("#1C1C1C"), + header-font-color-1-highlight: rgb("#FFFFFF"), + content-center: 0.3, + content-upper-padding: 0.05, + content-lower-padding: 0.05, + logos: ( + image("assets/typst-logo.png", height: 45pt), + image("assets/github-logo.png", height: 45pt), + ), + TOC: true, +) + +#show: black-angular-frame.with(config: presentation-config) + +// ============================================================ +// CONFIGURATION +// ============================================================ +#new-section("Configuration") + +#slide(title: "Template Configuration")[ + The template is configured from a single Typst dictionary. The table below lists the keys that can be passed through `config`, the expected value shape, and the defaults used when a key is omitted. + + #v(3pt) + + #let cell(body, fill: white, pos: left, weight: "regular") = fs-table-cell( + fill: fill, + stroke: luma(200) + 0.45pt, + pos: pos, + inset: (x: 3pt, y: 2pt), + )[ + #text(font: "IBM Plex Sans", size: 5.6pt, weight: weight, body) + ] + + #grid( + columns: (23%, 20%, 20%, 37%), + cell(fill: rgb("#1C1C1C"), weight: "bold")[#text(fill: white)[Name]], + cell(fill: rgb("#1C1C1C"), weight: "bold")[#text(fill: white)[Expected value]], + cell(fill: rgb("#1C1C1C"), weight: "bold")[#text(fill: white)[Default]], + cell(fill: rgb("#1C1C1C"), weight: "bold")[#text(fill: white)[Description]], + + cell[`title`], cell[String], cell[`""`], cell[Presentation title.], + cell[`subtitle`], cell[String], cell[`""`], cell[Presentation subtitle.], + cell[`authors`], cell[String], cell[`""`], cell[Author line shown on the cover and footer.], + cell[`institution`], cell[String], cell[`""`], cell[Institution shown on the cover and footer.], + cell[`date`], cell[String], cell[`""`], cell[Date shown on the cover.], + cell[`final-message`], cell[String], cell[`""`], cell[Message shown on the last slide.], + cell[`primary-color`], cell[Color], cell[`rgb("#1C1C1C")`], cell[Main bars, highlights, numbering, and accents.], + cell[`secondary-color`], cell[Color], cell[`rgb("#D9D9D9")`], cell[Secondary header and footer bands.], + cell[`background-color`], cell[Color], cell[`rgb("#FFFFFF")`], cell[Slide background color.], + cell[`font-color`], cell[Color], cell[`luma(20)`], cell[Default body text color.], + cell[`header-font-color-1`], + cell[Color], + cell[`_muted-nav(primary-color)`], + cell[Inactive text in the primary header band and text in the lower footer band.], + cell[`header-font-color-2`], + cell[Color], + cell[`primary-color`], + cell[Text in the secondary header and footer bands.], + cell[`header-font-color-1-highlight`], + cell[Color], + cell[`rgb("#FFFFFF")`], + cell[Active text in the primary header band.], + cell[`content-center`], + cell[Float 0-1], + cell[`0.3`], + cell[Vertical position used to center content; 0 starts at the top, 1 at the bottom.], + cell[`content-upper-padding`], + cell[Float 0-1], + cell[`0.05`], + cell[Top proportion of the available content area kept empty.], + cell[`content-lower-padding`], + cell[Float 0-1], + cell[`0.05`], + cell[Bottom proportion of the available content area kept empty.], + cell[`logos`], cell[`array[content]`], cell[`()`], cell[Logo images or custom content shown on the cover.], + cell[`TOC`], cell[Bool], cell[`true`], cell[Whether to add the table of contents slide with section links.], + ) + + #v(3pt) + These names are intentionally presentation-level settings, so changing the theme does not require editing the template internals. +] + +#slide(title: "Template Configuration Code")[ + The example presentation stores its theme and metadata in `presentation-config`, then passes that dictionary to the template with `black-angular-frame.with`. + + #v(3pt) + + #code-box( + "#import \"@preview/black-angular-frame:0.1.0\": * + +#let presentation-config = ( + title: \"Black Angular Frame\", + subtitle: \"A Typst Template for Academic Presentations\", + authors: \"Author One, Author Two\", + institution: \"Institution Name\", + date: \"May 2026\", + final-message: \"Thank you for your attention\", + primary-color: rgb(\"#1C1C1C\"), + secondary-color: rgb(\"#D9D9D9\"), + background-color: rgb(\"#FFFFFF\"), + font-color: luma(20), + header-font-color-1: rgb(\"#999999\"), + header-font-color-2: rgb(\"#1C1C1C\"), + header-font-color-1-highlight: rgb(\"#FFFFFF\"), + content-center: 0.3, + content-upper-padding: 0.05, + content-lower-padding: 0.05, + logos: (\n image(\"assets/typst-logo.png\", height: 45pt),\n image(\"assets/github-logo.png\", height: 45pt),\n ), + TOC: true, +) + +#show: black-angular-frame.with(config: presentation-config)", + type: "Typst", + title: "Import and configure the template", + lang: "typst", + color: luma(90), + fill: luma(248), + text-size: 5.6pt, + ) + + #v(3pt) + The rest of the document can focus on sections and slides while the template reads these values for the cover, navigation, footer, body text, logos, TOC, and final slide. +] + +// ============================================================ +// SECTION 1 -- TYPOGRAPHY +// ============================================================ +#new-section("Typography") + +#slide(title: "Default Fonts")[ + The template uses three IBM Plex families when available, with standard fallback fonts if they are not installed: + + #v(5pt) + #grid( + columns: (1fr, 1fr, 1fr), + column-gutter: 5pt, + block(stroke: blue.darken(50%) + 0.6pt, inset: 7pt, width: 100%, text(font: "IBM Plex Serif", size: 11.5pt)[ + *IBM Plex Serif* \ + body text \ + Regular, _italic_, \ + *bold*, _*bold-italic*_ + ]), + block(stroke: blue.darken(50%) + 0.6pt, inset: 7pt, width: 100%, text(font: "IBM Plex Sans", size: 11.5pt)[ + *IBM Plex Sans* \ + titles & headings \ + Regular, _italic_, \ + *bold*, _*bold-italic*_ + ]), + block(stroke: blue.darken(50%) + 0.6pt, inset: 7pt, width: 100%, text(font: "IBM Plex Mono", size: 11.5pt)[ + *IBM Plex Mono* \ + code & verbatim \ + Regular, _italic_, \ + *bold* + ]), + ) + #v(6pt) + Inline styling: #text(weight: "bold")[bold], #text(style: "italic")[italic], #text(fill: blue.darken(50%))[colored], #underline[underlined], #text(size: 13pt)[large (13 pt)], #text(size: 7.5pt)[small (7.5 pt)]. +] + +#slide(title: "Text Sizing and Semantic Emphasis")[ + #v(2pt) + #text(size: 18pt, weight: "bold", fill: blue.darken(50%))[Headline -- 18 pt bold] \ + #text(size: 14pt, weight: "bold")[Subheading -- 14 pt bold] \ + #text(size: 12pt)[Section heading -- 12 pt regular] \ + #text(size: 10pt)[Body text -- 10 pt (default)] \ + #text(size: 8pt, fill: luma(60))[Caption / footnote -- 8 pt muted] + + #v(8pt) + Semantic use: #text(style: "italic")[italics for emphasis], #text(weight: "bold")[bold for key terms], #text(fill: red.darken(20%))[red for warnings], #text(fill: green.darken(25%))[green for results]. Combine for #text(style: "italic", weight: "bold", fill: blue.darken(50%))[critical highlighted points]. + + #v(4pt) + Change the global accent via `primary-color`, the secondary bands via `secondary-color`, and the page fill via `background-color`. These parameters propagate through the navigation bar, footer, section dividers, TOC numbering squares, and theorem environments. +] + +// ============================================================ +// SECTION 2 -- LISTS AND ENUMERATIONS +// ============================================================ +#new-section("Lists & Enumerations") + +#slide(title: "Bullet Points and Nested Lists")[ + #two-col( + [ + *Unordered list (three levels):* + - First top-level item + - Nested child A + - Nested child B + - Deeply nested + - Another deep item + - Second top-level item + - Another child + - Third top-level item + ], + [ + *Ordered enumeration (three levels):* + + Step one: initialise + + Sub-step 1a + + Sub-step 1b + + Step two: process data + + Sub-step 2a + + Detail 2a-i + + Step three: evaluate output + + Step four: report results + ], + ) + + #v(5pt) + Typst automatically styles bullet symbols and numerals at each nesting depth. Ordered and unordered lists can be freely mixed at any level. +] + +// ============================================================ +// SECTION 3 -- FIGURES +// ============================================================ +#new-section("Figures") + +#slide(title: "Inserting and Referencing Figures")[ + #two-col( + [ + Figures use `#fs-figure(caption: [...])`. The counter resets per section; reference figures by their auto-assigned number. Longer surrounding paragraphs make it easier to inspect the vertical rhythm before and after visual material. + + #v(5pt) + As shown in *Figure 1*, a colored rectangle acts as a placeholder. In practice pass `image("diagram.svg")` or any Typst content as the figure body. This text intentionally spans multiple lines so the figure margins can be judged against realistic prose. + + #v(4pt) + *Figure 2* illustrates that captions appear italic below the figure, numbered automatically within the current section. The paragraph below the visual block should feel close enough to belong to the same slide, but not so close that the caption looks cramped. + ], + [ + The first placeholder represents a diagram or image inserted into the slide flow. A few lines of prose above it help show how the template separates ordinary text from framed visual content. + + #fs-figure(caption: [Placeholder -- replace with `image("diagram.svg")`.])[ + #rect( + width: 100%, + height: 62pt, + fill: blue.darken(50%).lighten(88%), + stroke: blue.darken(50%) + 0.8pt, + align(center + horizon, text(fill: blue.darken(50%), size: 9pt)[Diagram / Image here]), + ) + ] + #fs-figure(caption: [Second figure -- captions are italic, automatically numbered.])[ + #rect( + width: 100%, + height: 38pt, + fill: luma(240), + stroke: luma(190) + 0.6pt, + align(center + horizon, text(fill: luma(80), size: 9pt)[Another placeholder]), + ) + ] + + After the second figure, this short paragraph checks the lower margin beneath a caption. It should read as a continuation of the slide narrative rather than as text accidentally attached to the figure. + ], + ) +] + +#slide(title: "Full-Width and Fractional-Width Figures (With Captions)")[ + #two-col( + [ + A figure can expand to the full width of its column when the content should dominate the layout. This is useful for diagrams, screenshots, or images that need as much horizontal room as possible. + + #fs-figure(caption: [Full-width placeholder figure spanning the whole column.])[ + #rect( + width: 100%, + height: 60pt, + fill: blue.darken(50%).lighten(88%), + stroke: blue.darken(50%) + 0.8pt, + align(center + horizon, text(fill: blue.darken(50%), size: 9pt)[Full-width figure]), + ) + ] + + The caption should stay centered under the figure even when the visual takes the entire available width of the column. + ], + [ + Smaller visuals often read better when they keep some white space around them. A fractional-width figure makes that possible while still preserving the same numbering and caption behavior. + + #fs-figure(caption: [Fractional-width placeholder figure centered inside the column.])[ + #align(center, rect( + width: 68%, + height: 60pt, + fill: luma(240), + stroke: luma(190) + 0.6pt, + align(center + horizon, text(fill: luma(80), size: 9pt)[Fractional-width figure]), + )) + ] + + This example checks that a narrower figure still aligns cleanly in the column and that the caption feels attached to the centered image rather than to the whole column width. + ], + ) +] + +#slide(title: "Full-Width and Fractional-Width Figures (No Captions)")[ + #two-col( + [ + The same pair can also be shown without captions when the slide is purely illustrative and the surrounding prose already provides enough context for the audience. + + #fs-visual[ + #rect( + width: 100%, + height: 60pt, + fill: blue.darken(50%).lighten(88%), + stroke: blue.darken(50%) + 0.8pt, + align(center + horizon, text(fill: blue.darken(50%), size: 9pt)[Full-width figure]), + ) + ] + + Without a caption, the lower margin should still separate the figure from the next paragraph and keep the slide from feeling cramped. + ], + [ + The fractional-width version below uses the same visual content but keeps the narrower footprint. This lets us compare centered image placement with and without the caption layer. + + #fs-visual[ + #rect( + width: 68%, + height: 60pt, + fill: luma(240), + stroke: luma(190) + 0.6pt, + align(center + horizon, text(fill: luma(80), size: 9pt)[Fractional-width figure]), + ) + ] + + The surrounding text remains multi-line on purpose so we can judge the spacing around a centered narrow figure in the same way as we do for captioned figures. + ], + ) +] + +// ============================================================ +// SECTION 4 -- TWO-COLUMN LAYOUT +// ============================================================ +#new-section("Layouts", slide-title: "Two-Column Layouts") + +#slide(title: "Two-Column Layout")[ + #two-col( + left-width: 50%, + [ + == Left column + + The `#two-col(left, right)` helper builds a two-column `grid`. Parameters: + - `left-width` -- fraction of slide width (default 48%) + - `gutter` -- gap between columns (default 4%) + + #v(4pt) + Works well for: text + figure, code + output, comparative tables, side-by-side theorem boxes. + ], + [ + == Right column + + Any Typst content fits inside a column, including nested `two-col` calls, theorem boxes, figures, and tables. + + #fs-visual[ + #rect( + width: 100%, + height: 58pt, + fill: luma(245), + stroke: luma(200) + 0.6pt, + align(center + horizon, text(size: 9pt, fill: luma(60))[Arbitrary block inside a column]), + ) + ] + ], + ) +] + +// ============================================================ +// SECTION 5 -- CODE BLOCKS +// ============================================================ +#new-section("Code Blocks", slide-title: "Source Code & Pseudo-code") + +#slide(title: "Source Code and Pseudo-code Side by Side")[ + #two-col( + [ + #code-box( + "def softmax(x): + e = np.exp(x - x.max(axis=-1, keepdims=True)) + return e / e.sum(axis=-1, keepdims=True) + +def cross_entropy(logits, labels): + probs = softmax(logits) + n = labels.shape[0] + log_p = np.log(probs[range(n), labels]) + return -log_p.mean()", + type: "Source Code", + title: "Python", + lang: "python", + color: luma(110), + fill: luma(245), + ) + Uses `IBM Plex Mono` on a light grey background. Pass a language name to `#code-box(..., lang: "...")` for syntax highlighting. + ], + [ + #pseudo-code( + "Algorithm: Mini-Batch SGD +Input: loss L, data D, lr eta, T, B +------------------------------------- +for t = 1 to T do + B_t <- sample B examples from D + g <- grad_theta L(theta; B_t) + theta <- theta - eta * g +end for +return theta", + title: "Mini-Batch SGD", + ) + Pseudo-code now uses the same framed box language as the theorem environments, with a `type` label and `title`. + ], + ) +] + +// ============================================================ +// SECTION 6 -- TABLES +// ============================================================ +#new-section("Tables") + +#slide(title: "Paper-style and Grid-style Tables (No Captions)")[ + #two-col( + [ + *Paper style* (booktabs-like -- horizontal rules only) + + This table is introduced by a short paragraph rather than a single label. The extra prose makes the spacing above the table visible in a realistic slide, where a table usually follows a sentence or two of setup. + + #let paper-cell(body, pos: left, header: false, model-col: false, first-data: false) = grid.cell( + stroke: ( + bottom: if header { 0.6pt } else { none }, + right: if model-col { 0.6pt } else { none }, + ), + inset: ( + left: 5pt, + right: 5pt, + top: if first-data { 6pt } else { 4pt }, + bottom: if header { 7pt } else { 4pt }, + ), + align: pos, + text(font: "IBM Plex Serif", body), + ) + #fs-visual[ + #block(width: 100%, { + line(length: 100%, stroke: 0.9pt) + grid( + columns: (28%, 24%, 24%, 24%), + paper-cell(header: true, model-col: true)[*Method*], + paper-cell(header: true, pos: center)[*Acc. (%)*], + paper-cell(header: true, pos: center)[*F#sub[1]*], + paper-cell(header: true, pos: center)[*AUC*], + + paper-cell(model-col: true, first-data: true)[Baseline], + paper-cell(pos: center, first-data: true)[72.3], + paper-cell(pos: center, first-data: true)[0.701], + paper-cell(pos: center, first-data: true)[0.743], + + paper-cell(model-col: true)[Model A], + paper-cell(pos: center)[81.5], + paper-cell(pos: center)[0.803], + paper-cell(pos: center)[0.851], + + paper-cell(model-col: true)[Model B], + paper-cell(pos: center)[*88.9*], + paper-cell(pos: center)[*0.876*], + paper-cell(pos: center)[*0.903*], + ) + line(length: 100%, stroke: 0.9pt) + }) + ] + Classic academic style: only top and bottom rules, no vertical lines. The text after the table deliberately runs for a couple of lines so the lower margin can be compared with the upper margin. + ], + [ + *Grid style* (full borders, colored header, alternating rows) + + The grid version is meant for dense numeric summaries or dashboard-like reporting. A longer lead-in makes it easier to see whether the table feels attached to the explanation or floats too far away. + + #let grid-cell(body, fill: white, stroke: luma(200) + 0.6pt, pos: center) = fs-table-cell( + fill: fill, + stroke: stroke, + pos: pos, + body, + ) + #fs-visual[ + #block(width: 100%, grid( + columns: (28%, 24%, 24%, 24%), + grid-cell(fill: blue.darken(50%), pos: left)[#text(fill: white)[Method]], + grid-cell(fill: blue.darken(50%))[#text(fill: white)[Acc. (%)]], + grid-cell(fill: blue.darken(50%))[#text(fill: white)[F#sub[1]]], + grid-cell(fill: blue.darken(50%))[#text(fill: white)[AUC]], + + grid-cell(fill: luma(248), pos: left)[Baseline], + grid-cell(fill: luma(248))[72.3], + grid-cell(fill: luma(248))[0.701], + grid-cell(fill: luma(248))[0.743], + + grid-cell(fill: white, pos: left)[Model A], + grid-cell(fill: white)[81.5], + grid-cell(fill: white)[0.803], + grid-cell(fill: white)[0.851], + + grid-cell(fill: luma(248), pos: left)[Model B], + grid-cell(fill: luma(248))[*88.9*], + grid-cell(fill: luma(248))[*0.876*], + grid-cell(fill: luma(248))[*0.903*], + )) + ] + Dashboard style: solid grid, alternating row shading. This closing note also spans multiple lines, which helps reveal whether the table block leaves enough room before normal prose resumes. + ], + ) +] + +#slide(title: "Paper-style and Grid-style Tables (With Captions)")[ + #two-col( + [ + *Paper style* (booktabs-like -- horizontal rules only) + + Captions are useful when the table needs to be referenced later in the talk or connected to a source. This paragraph gives the captioned table enough surrounding prose to test both the top margin and the caption spacing. + + #let paper-cell(body, pos: left, header: false, model-col: false, first-data: false) = grid.cell( + stroke: ( + bottom: if header { 0.6pt } else { none }, + right: if model-col { 0.6pt } else { none }, + ), + inset: ( + left: 5pt, + right: 5pt, + top: if first-data { 6pt } else { 4pt }, + bottom: if header { 7pt } else { 4pt }, + ), + align: pos, + text(font: "IBM Plex Serif", body), + ) + #figure( + kind: table, + caption: [Placeholder caption for the paper-style table.], + { + block(width: 100%, { + line(length: 100%, stroke: 0.9pt) + grid( + columns: (28%, 24%, 24%, 24%), + paper-cell(header: true, model-col: true)[*Method*], + paper-cell(header: true, pos: center)[*Acc. (%)*], + paper-cell(header: true, pos: center)[*F#sub[1]*], + paper-cell(header: true, pos: center)[*AUC*], + + paper-cell(model-col: true, first-data: true)[Baseline], + paper-cell(pos: center, first-data: true)[72.3], + paper-cell(pos: center, first-data: true)[0.701], + paper-cell(pos: center, first-data: true)[0.743], + + paper-cell(model-col: true)[Model A], + paper-cell(pos: center)[81.5], + paper-cell(pos: center)[0.803], + paper-cell(pos: center)[0.851], + + paper-cell(model-col: true)[Model B], + paper-cell(pos: center)[*88.9*], + paper-cell(pos: center)[*0.876*], + paper-cell(pos: center)[*0.903*], + ) + line(length: 100%, stroke: 0.9pt) + }) + }, + ) + Classic academic style: only top and bottom rules, no vertical lines. With a caption present, the paragraph after the table should sit beneath the full table block rather than feeling glued to the caption. + ], + [ + *Grid style* (full borders, colored header, alternating rows) + + The captioned grid table shows how a more operational table behaves inside the same layout. The text before it is intentionally longer so vertical spacing is visible without relying on empty slide area. + + #let grid-cell(body, fill: white, stroke: luma(200) + 0.6pt, pos: center) = fs-table-cell( + fill: fill, + stroke: stroke, + pos: pos, + body, + ) + #figure( + kind: table, + caption: [Placeholder caption for the grid-style table.], + { + block(width: 100%, grid( + columns: (28%, 24%, 24%, 24%), + grid-cell(fill: blue.darken(50%), pos: left)[#text(fill: white)[Method]], + grid-cell(fill: blue.darken(50%))[#text(fill: white)[Acc. (%)]], + grid-cell(fill: blue.darken(50%))[#text(fill: white)[F#sub[1]]], + grid-cell(fill: blue.darken(50%))[#text(fill: white)[AUC]], + + grid-cell(fill: luma(248), pos: left)[Baseline], + grid-cell(fill: luma(248))[72.3], + grid-cell(fill: luma(248))[0.701], + grid-cell(fill: luma(248))[0.743], + + grid-cell(fill: white, pos: left)[Model A], + grid-cell(fill: white)[81.5], + grid-cell(fill: white)[0.803], + grid-cell(fill: white)[0.851], + + grid-cell(fill: luma(248), pos: left)[Model B], + grid-cell(fill: luma(248))[*88.9*], + grid-cell(fill: luma(248))[*0.876*], + grid-cell(fill: luma(248))[*0.903*], + )) + }, + ) + Dashboard style: solid grid, alternating row shading. This final description should have comfortable breathing room after the caption while still reading as part of the same explanatory unit. + ], + ) +] + +// ============================================================ +// SECTION 7 -- DIAGRAMS AND CHARTS (three slides) +// ============================================================ +#new-section("Diagrams & Charts") + +// ---- Diagram drawing helpers ------------------------------- +#let _arrow-head(x, y, dir: "r", color: luma(25)) = { + if dir == "r" { + place(top + left, dx: x - 6pt, dy: y - 3pt, polygon((0pt, 0pt), (6pt, 3pt), (0pt, 6pt), fill: color)) + } else if dir == "l" { + place(top + left, dx: x, dy: y - 3pt, polygon((0pt, 3pt), (6pt, 0pt), (6pt, 6pt), fill: color)) + } else if dir == "d" { + place(top + left, dx: x - 3pt, dy: y - 6pt, polygon((0pt, 0pt), (6pt, 0pt), (3pt, 6pt), fill: color)) + } else if dir == "u" { + place(top + left, dx: x - 3pt, dy: y, polygon((0pt, 6pt), (3pt, 0pt), (6pt, 6pt), fill: color)) + } else if dir == "dr" { + place(top + left, dx: x - 6pt, dy: y - 6pt, polygon((6pt, 6pt), (1pt, 4pt), (4pt, 1pt), fill: color)) + } else if dir == "dl" { + place(top + left, dx: x, dy: y - 6pt, polygon((0pt, 6pt), (5pt, 4pt), (2pt, 1pt), fill: color)) + } else if dir == "ur" { + place(top + left, dx: x - 6pt, dy: y, polygon((6pt, 0pt), (1pt, 2pt), (4pt, 5pt), fill: color)) + } else { + place(top + left, dx: x, dy: y, polygon((0pt, 0pt), (5pt, 2pt), (2pt, 5pt), fill: color)) + } +} + +#let _arr-r(x1, y, x2, color: luma(25), weight: 0.8pt, label: none, label-dy: -8pt) = { + place(top + left, line(start: (x1, y), end: (x2 - 5pt, y), stroke: color + weight)) + _arrow-head(x2, y, dir: "r", color: color) + if label != none { + place(top + left, dx: (x1 + x2) / 2 - 6pt, dy: y + label-dy, text(size: 6.4pt, fill: color, label)) + } +} + +#let _arr-l(x1, y, x2, color: luma(25), weight: 0.8pt, label: none, label-dy: -8pt) = { + place(top + left, line(start: (x1, y), end: (x2 + 5pt, y), stroke: color + weight)) + _arrow-head(x2, y, dir: "l", color: color) + if label != none { + place(top + left, dx: (x1 + x2) / 2 - 6pt, dy: y + label-dy, text(size: 6.4pt, fill: color, label)) + } +} + +#let _arr-v(x, y1, y2, color: luma(25), weight: 0.8pt, label: none, label-dx: 4pt) = { + if y2 > y1 { + place(top + left, line(start: (x, y1), end: (x, y2 - 5pt), stroke: color + weight)) + _arrow-head(x, y2, dir: "d", color: color) + } else { + place(top + left, line(start: (x, y1), end: (x, y2 + 5pt), stroke: color + weight)) + _arrow-head(x, y2, dir: "u", color: color) + } + if label != none { + place(top + left, dx: x + label-dx, dy: (y1 + y2) / 2 - 4pt, text(size: 6.4pt, fill: color, label)) + } +} + +#let _arr-diag(x1, y1, x2, y2, dir, color: luma(25), weight: 0.8pt, label: none, label-dx: 0pt, label-dy: 0pt) = { + place(top + left, line(start: (x1, y1), end: (x2, y2), stroke: color + weight)) + _arrow-head(x2, y2, dir: dir, color: color) + if label != none { + place(top + left, dx: (x1 + x2) / 2 + label-dx, dy: (y1 + y2) / 2 + label-dy, text(size: 6.4pt, fill: color, label)) + } +} + +#let _diagram-block(label, w: 52pt, h: 18pt, fill: luma(245), stroke: luma(25), text-size: 6.6pt, radius: 2pt) = box( + width: w, + height: h, + fill: fill, + stroke: stroke + 0.7pt, + radius: radius, + align(center + horizon, text(size: text-size, fill: luma(15), label)), +) + +#let transformer-diagram = align(center, { + let W = 278pt + let H = 232pt + let ink = luma(15) + let block-w = 54pt + let frame-w = 90pt + let frame-pad-x = (frame-w - block-w) / 2 + let enc-x = 30pt + let dec-x = 154pt + let label-size = 5.7pt + let small-size = 4.9pt + let arrow-weight = 0.9pt + let node-r = 3.5pt + let positional-center-y = 180pt + let input-sum-y = positional-center-y + let nx-y = 118pt + let decoder-bottom-label-width = 94pt + let pink = rgb("#F9DCDD") + let peach = rgb("#FFE3B8") + let bluefill = rgb("#C5E8F5") + let normfill = rgb("#F3F5C2") + let greenfill = rgb("#D8F0D9") + let violet = rgb("#E4E7F8") + + let encoder = ( + x: enc-x, + frame-y: 73pt, + frame-h: 100pt, + stack-x: enc-x + frame-pad-x, + nx-x: enc-x - 25pt, + nx-y: nx-y, + pos-side: "left", + pos-label-x: -15pt, + pos-circle-x: 49pt, + emb-label: [Input\ Embedding], + bottom-label: [Inputs], + bottom-label-x: enc-x + 8pt, + bottom-label-width: 74pt, + ) + let decoder = ( + x: dec-x, + frame-y: 41pt, + frame-h: 132pt, + stack-x: dec-x + frame-pad-x, + nx-x: dec-x + frame-w + 8pt, + nx-y: nx-y, + pos-side: "right", + pos-label-x: 229pt, + pos-circle-x: 224pt, + emb-label: [Output\ Embedding], + bottom-label: [Outputs (shifted right)], + bottom-label-x: dec-x + frame-pad-x + block-w / 2 - decoder-bottom-label-width / 2, + bottom-label-width: decoder-bottom-label-width, + ) + + let cx(col) = col.stack-x + block-w / 2 + let cy(layer) = layer.y + layer.h / 2 + + let diagram-text(x, y, body, size: label-size, width: auto, align-pos: center) = place( + top + left, + dx: x, + dy: y, + block(width: width, align(align-pos, text(size: size, fill: ink, body))), + ) + let diagram-text-centered-on-y(x, center-y, body, size: label-size, width: auto, align-pos: center) = context { + let label = block(width: width, align(align-pos, text(size: size, fill: ink, body))) + place(top + left, dx: x, dy: center-y - measure(label).height / 2, label) + } + let block-at(x, y, label, fill, h: 16pt, size: label-size) = place( + top + left, + dx: x, + dy: y, + _diagram-block(label, w: block-w, h: h, fill: fill, stroke: ink, text-size: size, radius: 2.2pt), + ) + let frame(col) = place( + top + left, + dx: col.x, + dy: col.frame-y, + block( + width: frame-w, + height: col.frame-h, + stroke: ink + 1.3pt, + radius: 7pt, + fill: luma(250), + ), + ) + let plus(x, y) = { + let r = node-r + let d = 2.45pt + place(top + left, dx: x - r, dy: y - r, circle(radius: r, stroke: ink + 0.85pt, fill: white)) + place(top + left, line(start: (x - d, y), end: (x + d, y), stroke: ink + 0.65pt)) + place(top + left, line(start: (x, y - d), end: (x, y + d), stroke: ink + 0.65pt)) + } + let pos-signal(x, y) = { + let r = node-r + place(top + left, dx: x - r, dy: y - r, circle(radius: r, stroke: ink + 0.9pt, fill: white)) + place(top + left, curve( + stroke: ink + 0.65pt, + fill: none, + curve.move((x - 2.2pt, y + 1.1pt)), + curve.cubic((x - 1.2pt, y + 1.1pt), (x - 1.1pt, y - 1.1pt), (x, y)), + curve.cubic((x + 1.1pt, y + 1.1pt), (x + 1.2pt, y - 1.1pt), (x + 2.2pt, y - 1.1pt)), + )) + } + let poly(points, weight: arrow-weight) = { + for i in range(points.len() - 1) { + let a = points.at(i) + let b = points.at(i + 1) + place(top + left, line(start: a, end: b, stroke: ink + weight)) + } + } + let arrow-head(x, y, dir: "r") = { + let len = 2.4pt + let half = 1.5pt + if dir == "r" { + place(top + left, polygon((x, y), (x - len, y - half), (x - len, y + half), fill: ink)) + } else if dir == "l" { + place(top + left, polygon((x, y), (x + len, y - half), (x + len, y + half), fill: ink)) + } else if dir == "d" { + place(top + left, polygon((x, y), (x - half, y - len), (x + half, y - len), fill: ink)) + } else { + place(top + left, polygon((x, y), (x - half, y + len), (x + half, y + len), fill: ink)) + } + } + let arrow-poly(points, dir: "r") = { + poly(points) + let end = points.at(points.len() - 1) + arrow-head(end.at(0), end.at(1), dir: dir) + } + let arr-v(x, y1, y2, weight: arrow-weight) = { + let len = 2.4pt + if y2 > y1 { + place(top + left, line(start: (x, y1), end: (x, y2 - len), stroke: ink + weight)) + arrow-head(x, y2, dir: "d") + } else { + place(top + left, line(start: (x, y1), end: (x, y2 + len), stroke: ink + weight)) + arrow-head(x, y2, dir: "u") + } + } + let attention-fork(layer, gap: 6pt) = { + let xs = (layer.x + 9pt, layer.x + block-w / 2, layer.x + block-w - 9pt) + let y = layer.y + layer.h + gap + place(top + left, line(start: (xs.first(), y), end: (xs.last(), y), stroke: ink + arrow-weight)) + for x in xs { + arr-v(x, y, layer.y + layer.h) + } + } + let branch-y(start, end, pct: 0.3) = start + pct * (end - start) + let residual(col, layer, norm, branch-from, branch-to, side: "left", branch-pct: 0.3) = { + let bus = if side == "left" { col.x + 6pt } else { col.x + frame-w - 6pt } + let by = branch-y(branch-from, branch-to, pct: branch-pct) + let from = (cx(col), by) + let mid = (bus, by) + let into = if side == "left" { + (norm.x, cy(norm)) + } else { + (norm.x + block-w, cy(norm)) + } + arrow-poly((from, mid, (bus, cy(norm)), into), dir: if side == "left" { "r" } else { "l" }) + } + let column-bottom(col) = { + let emb-y = 188pt + let emb-h = 14pt + let plus-y = positional-center-y + block-at(col.stack-x, emb-y, col.emb-label, pink, h: emb-h, size: 4.2pt) + plus(cx(col), plus-y) + arr-v(cx(col), emb-y + emb-h + 10pt, emb-y + emb-h) + arr-v(cx(col), emb-y, plus-y + 3.5pt) + diagram-text(col.bottom-label-x, 221pt, col.bottom-label, size: 7.2pt, width: col.bottom-label-width) + diagram-text-centered-on-y(col.pos-label-x, positional-center-y, [Positional Embedding], size: 5.0pt, width: 60pt) + pos-signal(col.pos-circle-x, plus-y) + if col.pos-side == "left" { + place(top + left, line( + start: (col.pos-circle-x + node-r, plus-y), + end: (cx(col) - node-r, plus-y), + stroke: ink + arrow-weight, + )) + } else { + place(top + left, line( + start: (cx(col) + node-r, plus-y), + end: (col.pos-circle-x - node-r, plus-y), + stroke: ink + arrow-weight, + )) + } + } + let sum-to-attention(col, layer, gap: 6pt) = place( + top + left, + line( + start: (cx(col), input-sum-y - 3.5pt), + end: (cx(col), layer.y + layer.h + gap), + stroke: ink + arrow-weight, + ), + ) + let lower-attn-shift = 3pt + + let enc-layers = ( + ( + key: "attn", + x: encoder.stack-x, + y: 138pt + lower-attn-shift, + h: 16pt, + label: [Multi-Head\ Attention], + fill: peach, + size: 4.5pt, + ), + ( + key: "norm1", + x: encoder.stack-x, + y: 123pt + lower-attn-shift, + h: 9pt, + label: [Add & Norm], + fill: normfill, + size: 4.8pt, + ), + (key: "ff", x: encoder.stack-x, y: 92pt, h: 15pt, label: [Feed\ Forward], fill: bluefill, size: 4.7pt), + (key: "norm2", x: encoder.stack-x, y: 77pt, h: 9pt, label: [Add & Norm], fill: normfill, size: 4.8pt), + ) + let dec-layers = ( + ( + key: "masked", + x: decoder.stack-x, + y: 139pt + lower-attn-shift, + h: 18pt, + label: [Masked\ Multi-Head\ Attention], + fill: peach, + size: 4.0pt, + ), + ( + key: "norm1", + x: decoder.stack-x, + y: 125pt + lower-attn-shift, + h: 9pt, + label: [Add & Norm], + fill: normfill, + size: 4.8pt, + ), + (key: "cross", x: decoder.stack-x, y: 98pt, h: 14pt, label: [Multi-Head\ Attention], fill: peach, size: 4.2pt), + (key: "norm2", x: decoder.stack-x, y: 84pt, h: 9pt, label: [Add & Norm], fill: normfill, size: 4.8pt), + (key: "ff", x: decoder.stack-x, y: 59pt, h: 14pt, label: [Feed\ Forward], fill: bluefill, size: 4.4pt), + (key: "norm3", x: decoder.stack-x, y: 45pt, h: 9pt, label: [Add & Norm], fill: normfill, size: 4.8pt), + (key: "linear", x: decoder.stack-x, y: 27pt, h: 9pt, label: [Linear], fill: violet, size: 4.9pt), + (key: "softmax", x: decoder.stack-x, y: 15.5pt, h: 9pt, label: [Softmax], fill: greenfill, size: 4.9pt), + ) + let enc(key) = enc-layers.find(layer => layer.key == key) + let dec(key) = dec-layers.find(layer => layer.key == key) + + box(width: W, height: H, { + frame(encoder) + frame(decoder) + diagram-text(encoder.nx-x, encoder.nx-y, [N×], size: 12pt) + diagram-text(decoder.nx-x, decoder.nx-y, [N×], size: 12pt) + diagram-text(decoder.stack-x - 7pt, 0pt, [Output Probabilities], size: 7.2pt, width: block-w + 14pt) + + column-bottom(encoder) + column-bottom(decoder) + + for layer in enc-layers { + block-at(layer.x, layer.y, layer.label, layer.fill, h: layer.h, size: layer.size) + } + for layer in dec-layers { + block-at(layer.x, layer.y, layer.label, layer.fill, h: layer.h, size: layer.size) + } + + attention-fork(enc("attn")) + attention-fork(dec("masked")) + sum-to-attention(encoder, enc("attn")) + sum-to-attention(decoder, dec("masked")) + + residual( + encoder, + enc("attn"), + enc("norm1"), + input-sum-y - 3.5pt, + enc("attn").y + enc("attn").h + 6pt, + side: "left", + branch-pct: 0.65, + ) + residual(encoder, enc("ff"), enc("norm2"), enc("norm1").y, enc("ff").y + enc("ff").h, side: "left", branch-pct: 0.5) + residual( + decoder, + dec("masked"), + dec("norm1"), + input-sum-y - 3.5pt, + dec("masked").y + dec("masked").h + 6pt, + side: "right", + branch-pct: 0.65, + ) + residual(decoder, dec("cross"), dec("norm2"), dec("norm1").y, dec("cross").y + dec("cross").h, side: "right") + residual( + decoder, + dec("ff"), + dec("norm3"), + dec("norm2").y, + dec("ff").y + dec("ff").h, + side: "right", + branch-pct: 0.5, + ) + + arr-v(cx(encoder), enc("attn").y, enc("norm1").y + enc("norm1").h) + arr-v(cx(encoder), enc("norm1").y, enc("ff").y + enc("ff").h) + arr-v(cx(encoder), enc("ff").y, enc("norm2").y + enc("norm2").h) + arr-v(cx(decoder), dec("masked").y, dec("norm1").y + dec("norm1").h) + let cross-input-xs = ( + dec("cross").x + 9pt, + dec("cross").x + block-w / 2, + dec("cross").x + block-w - 9pt, + ) + let cross-right-route-y = branch-y(dec("norm1").y, dec("cross").y + dec("cross").h, pct: 0.3) - 4pt + arrow-poly( + ( + (cx(decoder), dec("norm1").y), + (cx(decoder), cross-right-route-y), + (cross-input-xs.last(), cross-right-route-y), + (cross-input-xs.last(), dec("cross").y + dec("cross").h), + ), + dir: "u", + ) + arr-v(cx(decoder), dec("cross").y, dec("norm2").y + dec("norm2").h) + arr-v(cx(decoder), dec("norm2").y, dec("ff").y + dec("ff").h) + arr-v(cx(decoder), dec("ff").y, dec("norm3").y + dec("norm3").h) + arr-v(cx(decoder), dec("norm3").y, dec("linear").y + dec("linear").h) + arr-v(cx(decoder), dec("linear").y, dec("softmax").y + dec("softmax").h) + arr-v(cx(decoder), dec("softmax").y, 10pt) + + let enc-out-x = cx(encoder) + let enc-out-y = enc("norm2").y + let enc-route-y = encoder.frame-y - 5pt + let enc-dec-route-x = (encoder.x + frame-w + decoder.x) / 2 + let cross-in-y = dec("cross").y + dec("cross").h + 5pt + poly(( + (enc-out-x, enc-out-y), + (enc-out-x, enc-route-y), + (enc-dec-route-x, enc-route-y), + (enc-dec-route-x, cross-in-y), + (cross-input-xs.at(1), cross-in-y), + )) + arr-v(cross-input-xs.at(1), cross-in-y, dec("cross").y + dec("cross").h) + arrow-poly( + ( + (enc-dec-route-x, cross-in-y), + (cross-input-xs.at(0), cross-in-y), + (cross-input-xs.at(0), dec("cross").y + dec("cross").h), + ), + dir: "u", + ) + }) +}) + + +#let closed-loop-diagram = align(center, { + let W = 248pt + let H = 112pt + let ink = luma(0) + let stroke = ink + 1.0pt + let label(x, y, body, size: 7.2pt) = place(top + left, dx: x, dy: y, text(size: size, fill: ink, body)) + let arrow-r(x1, y, x2) = { + place(top + left, line(start: (x1, y), end: (x2 - 5pt, y), stroke: stroke)) + _arrow-head(x2, y, dir: "r", color: ink) + } + let arrow-l(x1, y, x2) = { + place(top + left, line(start: (x1, y), end: (x2 + 5pt, y), stroke: stroke)) + _arrow-head(x2, y, dir: "l", color: ink) + } + let arrow-u(x, y1, y2) = { + place(top + left, line(start: (x, y1), end: (x, y2 + 5pt), stroke: stroke)) + _arrow-head(x, y2, dir: "u", color: ink) + } + let arrow-d(x, y1, y2) = { + place(top + left, line(start: (x, y1), end: (x, y2 - 5pt), stroke: stroke)) + _arrow-head(x, y2, dir: "d", color: ink) + } + let labeled-arrow(x1, y1, x2, y2, body, label-dx: 0pt, label-dy: 0pt, label-size: 8pt) = { + if y1 == y2 and x2 > x1 { + arrow-r(x1, y1, x2) + } else if y1 == y2 and x2 < x1 { + arrow-l(x1, y1, x2) + } else if x1 == x2 and y2 > y1 { + arrow-d(x1, y1, y2) + } else if x1 == x2 and y2 < y1 { + arrow-u(x1, y1, y2) + } else { + place(top + left, line(start: (x1, y1), end: (x2, y2), stroke: stroke)) + } + if body != none { + label((x1 + x2) / 2 + label-dx, (y1 + y2) / 2 + label-dy, body, size: label-size) + } + } + + + let sys-box(x, y, label, w: 26pt, h: 26pt) = { + place( + top + left, + dx: x, + dy: y, + box( + width: w, + height: h, + fill: white, + stroke: ink + 1.2pt, + align(center + horizon, text(size: 8pt, fill: ink, label)), + ), + ) + } + + let sum-sign(x, y, body) = place( + top + left, + dx: x, + dy: y, + box(width: 5.5pt, height: 5.5pt, align(center + horizon, text(size: 5.8pt, fill: ink, body))), + ) + let sum-node(x, y, kind: "feedback") = { + let r = 10pt + let d = 7.1pt + let signs = if kind == "disturbance" { + ((-8.6pt, -3.0pt, [+]), (-2.8pt, -8.6pt, [+])) + } else { + ((-8.6pt, -3.0pt, [+]), (-2.8pt, 3.2pt, [−])) + } + place(top + left, dx: x - r, dy: y - r, circle(radius: r, stroke: ink + 1.0pt, fill: white)) + place(top + left, line(start: (x - d, y - d), end: (x + d, y + d), stroke: ink + 0.65pt)) + place(top + left, line(start: (x - d, y + d), end: (x + d, y - d), stroke: ink + 0.65pt)) + for (sx, sy, sign) in signs { + sum-sign(x + sx, y + sy, sign) + } + } + box(width: W, height: H, { + let y = 42pt + let s1 = 48pt + let s2 = 154pt + let low = 85pt + let arrow-len = 30pt + let disturbance-arrow-len = 20pt + let node-r = 10pt + let box-w = 26pt + let k-x = s1 + node-r + arrow-len + let g-x = s2 + node-r + arrow-len + let branch = g-x + box-w + arrow-len / 2 + let h-x = 144pt + + sum-node(s1, y) + label(k-x - 4pt, 17pt, [Controller], size: 7pt) + + sys-box(k-x, 29pt, [$K(z)$]) + // sys-box(50pt, 29pt, [$K(z)$]) + + sum-node(s2, y, kind: "disturbance") + label(g-x - 11pt, 17pt, [Target System], size: 7pt) + sys-box(g-x, 29pt, [$G(z)$]) + sys-box(h-x, 72pt, [$H(z)$]) + label(h-x - 7pt, 103pt, [Transducer], size: 7pt) + + labeled-arrow(s1 - node-r - arrow-len, y, s1 - node-r, y, [$R(z)$], label-dx: -10pt, label-dy: -10pt) + labeled-arrow(s1 + node-r, y, k-x, y, [$E(z)$], label-dx: -10pt, label-dy: -10pt) + labeled-arrow(k-x + box-w, y, s2 - node-r, y, [$U(z)$], label-dx: -10pt, label-dy: -10pt) + labeled-arrow(s2 + node-r, y, g-x, y, [$V(z)$], label-dx: -10pt, label-dy: -10pt) + labeled-arrow(g-x + box-w, y, g-x + box-w + arrow-len, y, [$Y(z)$], label-dx: -10pt, label-dy: -10pt) + labeled-arrow(s2, y - node-r - disturbance-arrow-len, s2, y - node-r, [$D(z)$], label-dx: -9pt, label-dy: -21pt) + place(top + left, line(start: (branch, y), end: (branch, low), stroke: stroke)) + arrow-l(branch, low, h-x + box-w) + place(top + left, line(start: (h-x, low), end: (s1, low), stroke: stroke)) + arrow-u(s1, low, 52pt) + label(94pt, 90pt, [$W(z)$], size: 8pt) + }) +}) + + + +#let kernel-image-diagram = align(center, { + let W = 220pt + let H = 86pt + let ink = luma(15) + let label-size = 9pt + let node(x, y, w, h, body, size) = place( + top + left, + dx: x - w / 2, + dy: y - h / 2, + box(width: w, height: h, align(center + horizon, text(size: size, fill: ink, body))), + ) + let arrow-r(x1, y, x2, body, label-dx: 0pt, label-dy: -13pt) = { + place(top + left, line(start: (x1, y), end: (x2 - 5pt, y), stroke: ink + 0.8pt)) + _arrow-head(x2, y, dir: "r", color: ink) + place(top + left, dx: (x1 + x2) / 2 + label-dx, dy: y + label-dy, text(size: label-size, fill: ink, body)) + } + let arrow-v(x, y1, y2, body, label-dx: 7pt, label-dy: -4pt) = { + if y2 > y1 { + place(top + left, line(start: (x, y1), end: (x, y2 - 5pt), stroke: ink + 0.8pt)) + _arrow-head(x, y2, dir: "d", color: ink) + } else { + place(top + left, line(start: (x, y1), end: (x, y2 + 5pt), stroke: ink + 0.8pt)) + _arrow-head(x, y2, dir: "u", color: ink) + } + place(top + left, dx: x + label-dx, dy: (y1 + y2) / 2 + label-dy, text(size: label-size, fill: ink, body)) + } + box(width: W, height: H, { + let top-y = 20pt + let bot-y = 68pt + let left-x = 66pt + let right-x = 162pt + let g-w = 24pt + let gp-w = 30pt + let q-w = 92pt + let im-w = 58pt + let g-arrow-w = 20pt + let gp-arrow-w = 24pt + let q-arrow-w = 74pt + let im-arrow-w = 42pt + let node-h = 18pt + + node(left-x, top-y, g-w, node-h, [$G$], 18pt) + node(right-x, top-y, gp-w, node-h, [$G'$], 18pt) + node(left-x, bot-y, q-w, node-h, [$G slash ker phi$], 16pt) + node(right-x, bot-y, im-w, node-h, [$im phi$], 16pt) + + arrow-r(left-x + g-arrow-w / 2 + 2pt, top-y, right-x - gp-arrow-w / 2 + 1pt, [$phi$], label-dx: -4pt) + arrow-v(left-x, top-y + node-h / 2 + 2pt, bot-y - node-h / 2 + 1pt, [$-$], label-dx: -13pt) + arrow-r( + left-x + q-arrow-w / 2 + 2pt, + bot-y, + right-x - im-arrow-w / 2 + 1pt, + [$overline(phi)$], + label-dx: -8pt, + label-dy: 5pt, + ) + arrow-v(right-x, bot-y - node-h / 2 + 1pt, top-y + node-h / 2 + 2pt, [inc], label-dx: 7pt) + }) +}) + +#let weighted-transition-graph = align(center, { + let W = 176pt + let H = 162pt + let ink = luma(25) + let edge = rgb("#C66A00") + let node-r = 13pt + let nodes = ( + P: (x: 17pt, y: 99pt), + B: (x: 64pt, y: 58pt), + D: (x: 110pt, y: 17pt), + C: (x: 110pt, y: 98pt), + M: (x: 64pt, y: 139pt), + L: (x: 158pt, y: 139pt), + ) + let pos(name) = nodes.at(name) + let arrow-dir(dx, dy) = { + let ax = calc.abs(dx) + let ay = calc.abs(dy) + if ax < ay * 0.45 { + if dy > 0pt { "d" } else { "u" } + } else if ay < ax * 0.45 { + if dx > 0pt { "r" } else { "l" } + } else if dx > 0pt and dy > 0pt { + "dr" + } else if dx < 0pt and dy > 0pt { + "dl" + } else if dx > 0pt and dy < 0pt { + "ur" + } else { + "ul" + } + } + let node(x, y, short) = { + place(top + left, dx: x - node-r, dy: y - node-r, box( + width: 2 * node-r, + height: 2 * node-r, + radius: node-r, + stroke: ink + 0.7pt, + fill: white, + align(center + horizon, text(size: 8.8pt, style: "italic", short)), + )) + } + let node-by-name(name, body) = { + let p = pos(name) + node(p.x, p.y, body) + } + let label-at(x, y, body) = { + place(top + left, dx: x - 7.5pt, dy: y - 5pt, box( + width: 15pt, + height: 10pt, + fill: white, + inset: 0pt, + align(center + horizon, text(size: 7.6pt, fill: ink, body)), + )) + } + let edge-line(a, b, label: none, label-dx: 0pt, label-dy: 0pt, both: false) = { + let pa = pos(a) + let pb = pos(b) + let dx = pb.x - pa.x + let dy = pb.y - pa.y + let ndx = dx / 1pt + let ndy = dy / 1pt + let len = calc.sqrt(ndx * ndx + ndy * ndy) + let ux = ndx / len + let uy = ndy / len + let sx = pa.x + ux * (node-r + 1pt) + let sy = pa.y + uy * (node-r + 1pt) + let ex = pb.x - ux * (node-r + 1pt) + let ey = pb.y - uy * (node-r + 1pt) + + place(top + left, line(start: (sx, sy), end: (ex, ey), stroke: edge + 0.85pt)) + _arrow-head(ex, ey, dir: arrow-dir(dx, dy), color: edge) + if both { + _arrow-head(sx, sy, dir: arrow-dir(-dx, -dy), color: edge) + } + if label != none { + label-at((sx + ex) / 2 + label-dx, (sy + ey) / 2 + label-dy, label) + } + } + let edge-curve(a, b, label: none, label-dx: 0pt, label-dy: 0pt) = { + let pa = pos(a) + let pb = pos(b) + let c1x = W + 18pt + let c1y = H - 42pt + let c2x = W + 8pt + let c2y = 22pt + let sdx = (c1x - pa.x) / 1pt + let sdy = (c1y - pa.y) / 1pt + let slen = calc.sqrt(sdx * sdx + sdy * sdy) + let sx = pa.x + sdx / slen * (node-r + 1pt) + let sy = pa.y + sdy / slen * (node-r + 1pt) + let edx = (c2x - pb.x) / 1pt + let edy = (c2y - pb.y) / 1pt + let elen = calc.sqrt(edx * edx + edy * edy) + let ex = pb.x + edx / elen * (node-r + 1pt) + let ey = pb.y + edy / elen * (node-r + 1pt) + + place(top + left, curve( + stroke: edge + 0.85pt, + fill: none, + curve.move((sx, sy)), + curve.cubic((c1x, c1y), (c2x, c2y), (ex, ey)), + )) + _arrow-head(ex, ey, dir: "l", color: edge) + if label != none { + let mx = 0.125 * sx + 0.375 * c1x + 0.375 * c2x + 0.125 * ex + let my = 0.125 * sy + 0.375 * c1y + 0.375 * c2y + 0.125 * ey + label-at(mx + label-dx, my + label-dy, label) + } + } + box(width: W, height: H, { + edge-line("D", "B", label: [10]) + edge-line("B", "P", label: [10]) + edge-line("P", "M", label: [4]) + edge-line("B", "M", label: [5], both: true) + edge-line("C", "B", label: [3]) + edge-line("M", "C", label: [9]) + edge-line("D", "C", label: [4], both: true) + edge-line("L", "M", label: [10]) + edge-curve("L", "D", label: [10]) + + node-by-name("P", [$P$]) + node-by-name("B", [$B$]) + node-by-name("D", [$D$]) + node-by-name("C", [$C$]) + node-by-name("M", [$M$]) + node-by-name("L", [$L$]) + }) +}) + +// ---- Chart slide 1: Transformer + Dynamic system ----------- +#slide(title: "Block Diagrams")[ + #two-col( + left-width: 49%, + [ + Transformer encoder-decoder stack with attention, feed-forward blocks, residual paths, and layer normalisation. + + #fs-diagram(caption: [Transformer encoder-decoder block diagram (Vaswani et al., 2017).])[ + #transformer-diagram + ] + ], + [ + A closed-loop controller compares the reference signal with the measured output, drives the plant, and routes the response through a feedback transducer. + + #fs-diagram( + caption: [Closed-loop control system with controller, plant, disturbance input, and feedback transducer.], + )[ + #closed-loop-diagram + ] + The disturbance $D(z)$ enters before the plant, while $H(z)$ shapes the measured feedback signal $W(z)$ returned to the summing junction. + ], + ) +] + +// ---- Chart slide 2: Linear algebra + Graph ----------------- +#slide(title: "Linear Algebra Diagram and Transition Graph")[ + #two-col( + left-width: 49%, + [ + For a homomorphism $phi: G -> G'$, the *kernel* determines the quotient $G slash ker phi$, while the *image* is the subgroup of reachable outputs in $G'$. + + #fs-diagram(caption: [Kernel-image decomposition for a homomorphism $phi: G -> G'$.])[ + #kernel-image-diagram + ] + The induced map $overline(phi)$ sends cosets modulo $ker phi$ onto $im phi$, and the inclusion embeds that image back into $G'$. + ], + [ + A weighted directed graph encodes reachable states as nodes and transition costs as labels on the arcs. This version keeps the notation compact to match the reference diagram. + + #fs-diagram(caption: [Weighted directed transition graph; edge labels denote transition costs.])[ + #weighted-transition-graph + ] + Parallel and long-range transitions are shown with separate arrows, making bidirectional moves and high-cost paths visible at a glance. + ], + ) +] + +// ---- Chart slide 3: Line chart + Histogram ----------------- +#let accuracy-chart = align(center, { + let W = 248pt + let H = 126pt + let pl = 32pt + let pr = 13pt + let pt = 21pt + let pb = 24pt + let iw = W - pl - pr + let ih = H - pt - pb + let xs = (0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0) + let ya = (0.52, 0.57, 0.63, 0.70, 0.76, 0.81, 0.85, 0.88, 0.90, 0.91, 0.92) + let yb = (0.48, 0.51, 0.55, 0.60, 0.65, 0.69, 0.73, 0.76, 0.79, 0.81, 0.83) + let yc = (0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50) + let xmin = 0.0 + let xmax = 5.0 + let ymin = 0.44 + let ymax = 0.96 + let px(xi) = pl + (xi - xmin) / (xmax - xmin) * iw + let py(yi) = pt + (ymax - yi) / (ymax - ymin) * ih + let tick-label(x, y, width, body, size: 5.4pt, fill: luma(70)) = { + place(top + left, dx: x - width / 2, dy: y - 4.5pt, box( + width: width, + height: 9pt, + inset: 0pt, + align(center + horizon, text(size: size, fill: fill, body)), + )) + } + let line-legend-item(x, y, col, body, text-width: 18pt) = { + place(top + left, line(start: (x, y), end: (x + 9pt, y), stroke: col + 1.1pt)) + place(top + left, dx: x + 12pt, dy: y - 6.5pt, box( + width: text-width, + height: 13pt, + inset: 0pt, + align(left + horizon, text(size: 5.3pt, fill: luma(45), body)), + )) + } + let mkpath(ys, col) = { + let pts = xs.zip(ys).map(p => (px(p.first()), py(p.last()))) + curve( + stroke: col + 1.35pt, + fill: none, + curve.move(pts.first()), + ..pts.slice(1).map(curve.line), + ) + } + let markers(ys, col) = { + for (xv, yv) in xs.zip(ys) { + place(top + left, dx: px(xv) - 1.6pt, dy: py(yv) - 1.6pt, circle(radius: 1.6pt, fill: white, stroke: col + 0.7pt)) + } + } + box(width: W, height: H, stroke: luma(180) + 0.45pt, fill: luma(252), { + place(top + left, dx: 8pt, dy: 5pt, text(size: 6.8pt, weight: "bold", fill: luma(25))[Validation accuracy by epoch]) + place(top + left, dx: W - 91pt, dy: 5pt, box(width: 83pt, height: 13pt, fill: white, stroke: luma(220) + 0.35pt, { + line-legend-item(5pt, 6.5pt, blue.darken(50%), [A], text-width: 8pt) + line-legend-item(28pt, 6.5pt, red.darken(20%), [B], text-width: 8pt) + line-legend-item(52pt, 6.5pt, luma(150), [base], text-width: 17pt) + })) + place(top + left, dx: pl + 3pt, dy: pt + 3pt, text(size: 5.2pt, fill: luma(75))[Accuracy]) + for yi in (0.5, 0.6, 0.7, 0.8, 0.9) { + place(top + left, line(start: (pl, py(yi)), end: (W - pr, py(yi)), stroke: luma(222) + 0.45pt)) + tick-label(pl / 2, py(yi), pl - 8pt, str(yi)) + } + place(top + left, line(start: (pl, pt), end: (pl, H - pb), stroke: luma(95) + 0.65pt)) + place(top + left, line(start: (pl, H - pb), end: (W - pr, H - pb), stroke: luma(95) + 0.65pt)) + for xi in (0, 1, 2, 3, 4, 5) { + place(top + left, line( + start: (px(float(xi)), H - pb), + end: (px(float(xi)), H - pb + 2pt), + stroke: luma(95) + 0.45pt, + )) + tick-label(px(float(xi)), H - pb + 8pt, 12pt, str(xi)) + } + place(top + left, dx: W / 2 - 12pt, dy: H - 9pt, text(size: 5.5pt, fill: luma(65))[Epoch]) + place(top + left, mkpath(ya, blue.darken(50%))) + place(top + left, mkpath(yb, red.darken(20%))) + place(top + left, mkpath(yc, luma(160))) + markers(ya, blue.darken(50%)) + markers(yb, red.darken(20%)) + }) +}) + +#let grouped-bar-chart = align(center, { + let W = 248pt + let H = 126pt + let pl = 32pt + let pr = 12pt + let pt = 21pt + let pb = 26pt + let iw = W - pl - pr + let ih = H - pt - pb + let baseline = H - pb + let years = (2020, 2021, 2022, 2023, 2024) + let va = (62, 67, 71, 74, 78) + let vb = (55, 58, 61, 65, 69) + let maxv = 85.0 + let bw = 10.5pt + let gap = 4pt + let group-w = 2 * bw + gap + let group-step = iw / years.len() + let ca = blue.darken(50%) + let cb = red.darken(20%) + let py(score) = baseline - (float(score) / maxv) * ih + let group-center(i) = pl + (float(i) + 0.5) * group-step + let group-left(i) = group-center(i) - group-w / 2 + let bar-center(i, which) = group-left(i) + if which == "a" { bw / 2 } else { bw + gap + bw / 2 } + let tick-label(x, y, width, body, size: 5.2pt, fill: luma(70)) = { + place(top + left, dx: x - width / 2, dy: y - 4.5pt, box( + width: width, + height: 9pt, + inset: 0pt, + align(center + horizon, text(size: size, fill: fill, body)), + )) + } + let value-label(x, y, body, fill) = { + place(top + left, dx: x - 7pt, dy: y - 9pt, box( + width: 14pt, + height: 8pt, + inset: 0pt, + align(center + horizon, text(size: 4.8pt, fill: fill, body)), + )) + } + let swatch-legend-item(x, y, col, body, text-width: 8pt) = { + place(top + left, dx: x, dy: y - 2.5pt, rect(width: 6pt, height: 5pt, fill: col)) + place(top + left, dx: x + 9pt, dy: y - 6.5pt, box( + width: text-width, + height: 13pt, + inset: 0pt, + align(left + horizon, text(size: 5.3pt, fill: luma(45), body)), + )) + } + box(width: W, height: H, stroke: luma(180) + 0.45pt, fill: luma(252), { + place(top + left, dx: 8pt, dy: 5pt, text(size: 6.8pt, weight: "bold", fill: luma(25))[Mean score by cohort]) + place(top + left, dx: W - 58pt, dy: 5pt, box(width: 50pt, height: 13pt, fill: white, stroke: luma(220) + 0.35pt, { + swatch-legend-item(5pt, 6.5pt, ca, [A]) + swatch-legend-item(27pt, 6.5pt, cb, [B]) + })) + place(top + left, dx: pl + 3pt, dy: pt + 3pt, text(size: 5.2pt, fill: luma(75))[Score]) + for score in (20, 40, 60, 80) { + let yp = py(score) + place(top + left, line(start: (pl, yp), end: (W - pr, yp), stroke: luma(222) + 0.45pt)) + tick-label(pl / 2, yp, pl - 8pt, str(score)) + } + place(top + left, line(start: (pl, pt), end: (pl, baseline), stroke: luma(95) + 0.65pt)) + place(top + left, line(start: (pl, baseline), end: (W - pr, baseline), stroke: luma(95) + 0.65pt)) + for (i, yr) in years.enumerate() { + let a = va.at(i) + let b = vb.at(i) + let x0 = group-left(i) + let ya = py(a) + let yb = py(b) + place(top + left, dx: x0, dy: ya, rect(width: bw, height: baseline - ya, fill: ca)) + place(top + left, dx: x0 + bw + gap, dy: yb, rect(width: bw, height: baseline - yb, fill: cb)) + tick-label(group-center(i), baseline + 8pt, group-step - 2pt, str(yr), size: 5.1pt, fill: luma(55)) + value-label(bar-center(i, "a"), ya, str(a), ca) + value-label(bar-center(i, "b"), yb, str(b), cb) + } + place(top + left, dx: W / 2 - 9pt, dy: H - 9pt, text(size: 5.5pt, fill: luma(65))[Year]) + }) +}) + +#slide(title: "Model Accuracy and Cohort Scores (No Captions)")[ + #two-col( + left-width: 49%, + [ + The accuracy curves compare Model A, Model B, and a 50% baseline across epochs $x in [0,5]$. Model A stays ahead throughout, while both learned models rise well above the baseline. + + #fs-visual[#accuracy-chart] + At epoch 5, *Model A* reaches 92% and *Model B* reaches 83%. Their gap grows up to epoch 3 and then narrows from 12 to 9 percentage points by the final epoch. + ], + [ + The grouped bars compare mean test scores for Group A and Group B from 2020 to 2024. Both cohorts improve each year, with *Group A* leading every annual pair. + + #fs-visual[#grouped-bar-chart] + Scores rise from 62 to 78 for *Group A* and from 55 to 69 for *Group B*. The gap widens from 7 points in 2020 to 9 points in 2024. + ], + ) +] + +#slide(title: "Model Accuracy and Cohort Scores (With Captions)")[ + #two-col( + left-width: 49%, + [ + The accuracy curves compare Model A, Model B, and a 50% baseline across epochs $x in [0,5]$. Model A stays ahead throughout, while both learned models rise well above the baseline. + + #fs-figure(caption: [Accuracy vs. epoch for Model A, Model B, and random baseline.])[ + #accuracy-chart + ] + At epoch 5, *Model A* reaches 92% and *Model B* reaches 83%. Their gap grows up to epoch 3 and then narrows from 12 to 9 percentage points by the final epoch. + ], + [ + The grouped bars compare mean test scores for Group A and Group B from 2020 to 2024. Both cohorts improve each year, with *Group A* leading every annual pair. + + #fs-figure(caption: [Mean test score by group and year (2020-2024).])[ + #grouped-bar-chart + ] + Scores rise from 62 to 78 for *Group A* and from 55 to 69 for *Group B*. The gap widens from 7 points in 2020 to 9 points in 2024. + ], + ) +] + +// ============================================================ +// SECTION 8 -- THEOREM-STYLE BOXES +// ============================================================ +#new-section("Theorem-style Boxes") + +#slide(title: "Definitions, Theorems, and Lemmas")[ + #two-col( + left-width: 49%, + [ + #definition(name: "Metric Space")[ + A *metric space* $(M, d)$ is a set $M$ with $d: M times M -> RR_(>=0)$ satisfying non-negativity, identity ($d(x,y)=0 <=> x=y$), symmetry, and the triangle inequality. + ] + #theorem(name: "Banach Fixed-Point Theorem")[ + Let $(M, d)$ be complete and $f: M -> M$ a contraction with constant $k < 1$. Then $f$ has a *unique* fixed point $x^* in M$. + ] + #lemma()[ + Any contraction $f$ on $(M,d)$ is uniformly continuous and extends uniquely to the completion $overline(M)$. + ] + ], + [ + #corollary(name: "Picard-Lindelof")[ + Under Lipschitz continuity in $y$, the IVP $dot(y)=f(t,y)$, $y(t_0)=y_0$ has a unique local solution. + ] + #proof[ + Apply Banach's theorem to the Picard operator $T phi = y_0 + integral_(t_0)^t f(s,phi(s)) d s$, which is contractive on $C([t_0-delta, t_0+delta])$ for small $delta > 0$. + ] + #remark(name: "Completeness is necessary")[ + On $(0,1)$ with $f(x)=x/2$, the fixed point $0$ lies outside the space -- Banach's theorem fails. + ] + ], + ) +] + +#slide(title: "Examples, Exercises, Propositions, and Custom Boxes")[ + #two-col( + left-width: 49%, + [ + #example(name: "Euclidean Space")[ + $RR^n$ with $d(x,y)=norm(x-y)_2$ is complete. The iteration $x_(k+1)=A x_k+b$ converges iff $rho(A)<1$, to the unique solution of $x=A x+b$. + ] + #exercise()[ + Show that $ZZ_p$ is complete under the $p$-adic metric and use Banach's theorem to prove Hensel's lemma. + ] + #proposition(name: "Closed Subsets are Complete")[ + A closed subset of a complete metric space is itself a complete metric space. + ] + ], + [ + #fs-box("note", name: "Implementation tip", color: green.darken(30%))[ + Monitor $norm(x_(k+1)-x_k)$ as a stopping criterion. A-priori error: $norm(x_k - x^*) <= k^m/(1-k) norm(x_1-x_0)$. + ] + #fs-box("warning", name: "Common pitfall", color: red.darken(20%))[ + Contraction ($k<1$) is strictly stronger than nonexpansive ($k=1$). Rotations on $S^1$ are nonexpansive but have no fixed points. + ] + #fs-box("custom", name: "Any label works", color: purple.darken(15%))[ + Use `#fs-box("kind", name: "...", color: ...)` for any label and color -- observations, facts, algorithms, warnings. + ] + ], + ) +] + +#slide(title: "Custom Box Widths")[ + #fs-box("custom", name: "Default width", color: purple.darken(15%))[ + This box uses the default width, so it fills the available slide content area. It is the recommended form when the material belongs to the main flow of the slide. + ] + + #align(center)[ + #fs-box("custom", name: "Narrow custom width", color: purple.darken(15%), width: 62%)[ + This box has a smaller explicit width. It is useful for short claims, reminders, or side notes that should not dominate the slide. + ] + ] + + #fs-box("custom", name: "Short content, default width", color: purple.darken(15%))[ + A short phrase. + ] +] + +#slide(title: "Full-Width Mathematical Proof")[ + #theorem(name: "Cauchy-Schwarz Inequality")[ + For all vectors $u, v in RR^n$, + #fs-equation[$ + abs(u dot v) <= norm(u) norm(v). + $] + ] + + #proof[ + If $v = 0$, the claim is immediate, so assume $v != 0$. For every real $t$, the squared norm of $u - t v$ is non-negative: + + #fs-equation[$ + 0 <= norm(u - t v)^2 = norm(u)^2 - 2 t (u dot v) + t^2 norm(v)^2. + $] + + Choose the minimising value $t = (u dot v) / norm(v)^2$. Substitution gives + + #fs-equation[$ + 0 <= norm(u)^2 - (u dot v)^2 / norm(v)^2. + $] + + Multiplying by the positive number $norm(v)^2$, we obtain + + #fs-equation[$ + (u dot v)^2 <= norm(u)^2 norm(v)^2. + $] + + Taking square roots on both sides yields $abs(u dot v) <= norm(u) norm(v)$. Equality occurs precisely when the non-negative quadratic has a zero at its minimum, which means $u - t v = 0$ for some scalar $t$; equivalently, the two vectors are linearly dependent. + ] +] + +#slide(title: "Narrow Mathematical Proof")[ + Proof environments fill the available content width by default, matching the rhythm of ordinary slide material. When a shorter line length reads better, pass an explicit `width` and center the proof as below. + + #align(center)[ + #proof(width: 68%)[ + We prove Young's inequality in its weighted quadratic form. Let $a, b >= 0$ and fix $epsilon > 0$. Since every square is non-negative, + + #fs-equation[$ + 0 <= (sqrt(epsilon) a - b / sqrt(epsilon))^2. + $] + + Expanding the square gives + + #fs-equation[$ + 0 <= epsilon a^2 - 2 a b + b^2 / epsilon. + $] + + Rearranging terms and dividing by $2$ yields + + #fs-equation[$ + a b <= epsilon a^2 / 2 + b^2 / (2 epsilon). + $] + + This form is especially useful when a product term must be absorbed into a coercive estimate: one chooses $epsilon$ small enough for the first term and pays for it in the second term. + ] + ] +] + +#slide(title: "Theorem Box with Internal Proof")[ + #theorem(name: "Cauchy-Schwarz Inequality")[ + For all vectors $u, v in RR^n$, + #fs-equation[$ + abs(u dot v) <= norm(u) norm(v). + $] + + #proof[ + If $v = 0$, the claim is immediate. Otherwise, the quadratic expression $norm(u - t v)^2$ is non-negative for every $t in RR$. Expanding and choosing $t = (u dot v) / norm(v)^2$ gives + #fs-equation[$ + 0 <= norm(u)^2 - (u dot v)^2 / norm(v)^2. + $] + Multiplying by $norm(v)^2$ and taking square roots yields the desired inequality. + ] + ] +] + +#slide(title: "Exercise Box with Solution")[ + #exercise(name: "Spectral radius criterion")[ + Let $A in RR^(n times n)$ and suppose $norm(A) < 1$ for a matrix norm compatible with the vector norm. Prove that $I - A$ is invertible and derive a convergent series for its inverse. + + #box-separator("Solution", color: orange.darken(20%)) + + Since $norm(A^k) <= norm(A)^k$, the Neumann series $sum_(k=0)^infinity A^k$ converges absolutely. Multiplying partial sums by $I-A$ gives $I - A^(m+1)$, which tends to $I$. Thus, + #fs-equation[$ + (I - A)^(-1) = sum_(k=0)^infinity A^k. + $] + ] +] + +#slide(title: "Exercise Box with Two Solutions")[ + #exercise(name: "Arithmetic-geometric mean")[ + Prove that for $x, y >= 0$ one has $sqrt(x y) <= (x + y) / 2$. + + #box-separator("Solution 1", color: orange.darken(20%)) + + The square $(sqrt(x) - sqrt(y))^2$ is non-negative, so $x + y - 2 sqrt(x y) >= 0$. + + #box-separator("Solution 2", color: orange.darken(20%)) + + The function $log$ is concave on $(0, infinity)$. Applying Jensen's inequality to $x$ and $y$ gives + #fs-equation[$ + log((x + y) / 2) >= (log x + log y) / 2 = log(sqrt(x y)). + $] + ] +] + +#slide(title: "Theorem, Text, and Lemma")[ + The next result is stated in the same theorem-style box used throughout the template. The surrounding prose is deliberately included to show how normal paragraphs sit before, between, and after formal statements. + + #theorem(name: "Compactness criterion")[ + Every sequence in a compact metric space has a convergent subsequence whose limit belongs to the same space. + ] + + This theorem is often the bridge between qualitative assumptions and quantitative estimates. The intermediate text lets the slide show how ordinary prose separates theorem-style boxes without requiring a two-column layout. + + #lemma(name: "Closed image of a convergent sequence")[ + If $x_n -> x$ and $F$ is closed, then every sequence contained in $F$ can only converge to a point of $F$. + ] + + Together, the theorem and lemma give a compact workflow: first extract convergence, then use closedness to keep the limiting object inside the admissible set. This final paragraph checks the lower spacing after the last formal box. +] + +// ============================================================ +// Thank you +// ============================================================ +#final-slide diff --git a/packages/preview/black-angular-frame/0.1.0/thumbnail.png b/packages/preview/black-angular-frame/0.1.0/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..da27cc776d00080a976b99d1bd267655f90af901 GIT binary patch literal 138305 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

T*129;0~u(5r76qOSxI*Cp=pIq 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ɸ> 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�$Ay+jMF%;X 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 V@2g*ZuHgShzwpwCeZ!|O`(LP3Q;z@u literal 0 HcmV?d00001 diff --git a/packages/preview/black-angular-frame/0.1.0/typst.toml b/packages/preview/black-angular-frame/0.1.0/typst.toml new file mode 100644 index 0000000000..559e91efd2 --- /dev/null +++ b/packages/preview/black-angular-frame/0.1.0/typst.toml @@ -0,0 +1,23 @@ +[package] +name = "black-angular-frame" +version = "0.1.0" +entrypoint = "black-angular-frame.typ" +authors = ["Miguel Montes <@miguelm7654>"] +license = "MIT" +description = "Formal academic slide decks." +keywords = ["slides", "presentation", "academic", "beamer"] +categories = ["presentation"] +exclude = [ + "example.typ", + "example.pdf", + "assets/fonts/**", + "assets/curves.csv", + "tinymist.lock", + ".vscode/**", + "tmp/**", +] + +[template] +path = "template" +entrypoint = "main.typ" +thumbnail = "thumbnail.png"