From 2d66774f83c670b2a725187461ad8dbceac20f9b Mon Sep 17 00:00:00 2001
From: xwysyy <3185205565@qq.com>
Date: Tue, 29 Sep 2026 22:42:28 +0800
Subject: [PATCH] xwysyy:0.5.0
---
packages/preview/xwysyy/0.5.0/LICENSE | 22 +
packages/preview/xwysyy/0.5.0/README.md | 210 +++
.../preview/xwysyy/0.5.0/src/elements.typ | 139 ++
packages/preview/xwysyy/0.5.0/src/layout.typ | 1366 +++++++++++++++++
packages/preview/xwysyy/0.5.0/src/slides.typ | 359 +++++
packages/preview/xwysyy/0.5.0/src/themes.typ | 95 ++
.../preview/xwysyy/0.5.0/template/main.typ | 37 +
packages/preview/xwysyy/0.5.0/thumbnail.png | Bin 0 -> 188834 bytes
packages/preview/xwysyy/0.5.0/typst.toml | 18 +
packages/preview/xwysyy/0.5.0/xwysyy.typ | 9 +
10 files changed, 2255 insertions(+)
create mode 100644 packages/preview/xwysyy/0.5.0/LICENSE
create mode 100644 packages/preview/xwysyy/0.5.0/README.md
create mode 100644 packages/preview/xwysyy/0.5.0/src/elements.typ
create mode 100644 packages/preview/xwysyy/0.5.0/src/layout.typ
create mode 100644 packages/preview/xwysyy/0.5.0/src/slides.typ
create mode 100644 packages/preview/xwysyy/0.5.0/src/themes.typ
create mode 100644 packages/preview/xwysyy/0.5.0/template/main.typ
create mode 100644 packages/preview/xwysyy/0.5.0/thumbnail.png
create mode 100644 packages/preview/xwysyy/0.5.0/typst.toml
create mode 100644 packages/preview/xwysyy/0.5.0/xwysyy.typ
diff --git a/packages/preview/xwysyy/0.5.0/LICENSE b/packages/preview/xwysyy/0.5.0/LICENSE
new file mode 100644
index 0000000000..45bb67b23e
--- /dev/null
+++ b/packages/preview/xwysyy/0.5.0/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2025 Carlos-Mero
+Copyright (c) 2026 xwysyy
+
+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/xwysyy/0.5.0/README.md b/packages/preview/xwysyy/0.5.0/README.md
new file mode 100644
index 0000000000..d61369a834
--- /dev/null
+++ b/packages/preview/xwysyy/0.5.0/README.md
@@ -0,0 +1,210 @@
+
xwysyy
+
+
+
+
+
+
+
+
+
+
+ 中文 | English
+
+
+Academic presentation templates built on [touying](https://github.com/touying-typ/touying). The package covers slide decks, handouts, speaker notes, and pdfpc metadata. The visual theme is derived from [Carlos-Mero/may](https://github.com/Carlos-Mero/may) under MIT.
+
+## Features
+
+- Universe template support: `typst init @preview/xwysyy:0.5.0` creates a ready-to-compile deck.
+- Six built-in themes: `sky`, `sunset`, `forest`, `midnight`, `violet`, and `graphite`.
+- Custom theme dictionaries can be passed directly to `theme`, so users can customize colors without forking the package.
+- `xwysyy-pre` takes `font`, `code-font`, and `lang`, plus `heading-font` for the header title.
+- Touying handout mode, `#speaker-note`, and pdfpc export are documented with tagged source examples.
+- Eight semantic layout components (`duo-slide`, `grid-slide`, `figure-slide`, `stat-slide`, ...) measure every block at compile time, distribute space fill-first, and export layout telemetry without hand-written `#v()` spacing.
+
+## Preview
+
+Rendered previews are generated from the [tagged source examples](https://github.com/xwysyy/xwysyy-typst/tree/v0.5.0/examples).
+
+### Slide Themes
+
+| sky | sunset | forest |
+|:---:|:---:|:---:|
+|  |  |  |
+
+| midnight | violet | graphite |
+|:---:|:---:|:---:|
+|  |  |  |
+
+### Component Pages
+
+| Sky cover | Sky components |
+|:---:|:---:|
+|  |  |
+
+| Sunset cover | Sunset components |
+|:---:|:---:|
+|  |  |
+
+## Quick Start
+
+Create a new project from the Universe template:
+
+```bash
+typst init @preview/xwysyy:0.5.0 my-talk
+cd my-talk
+typst compile main.typ
+```
+
+Use the package in an existing Typst project:
+
+```typst
+#import "@preview/xwysyy:0.5.0": *
+
+#show: xwysyy-pre.with(
+ theme: "sunset",
+ config-info(
+ title: [My Presentation Title],
+ subtitle: [Subtitle],
+ author: " ",
+ date: datetime.today(),
+ institution: " ",
+ ),
+)
+
+#title-slide()
+#outline-slide()
+
+= Section Title
+
+== Slide Title
+
+Body text with *bold* and #red[red highlight].
+
+#textbox(
+ [*Module A*
+
+ First column],
+
+ [*Module B*
+
+ Second column],
+)
+
+#end-slide(title: [Thank You!], body: [Questions?])
+```
+
+## Themes
+
+Select a built-in theme by name:
+
+```typst
+#show: xwysyy-pre.with(
+ theme: "forest",
+ config-info(title: [My Presentation]),
+)
+```
+
+Pass a custom dictionary directly:
+
+```typst
+#let my-theme = (
+ sea: rgb("#1f5d45"),
+ sky: rgb("#a8d5ba"),
+ skyll: rgb("#f5fbf7"),
+ paper: rgb("#f7faf8"),
+ page-fill: white,
+)
+
+#show: xwysyy-pre.with(
+ theme: my-theme,
+ config-info(title: [My Presentation]),
+)
+```
+
+All five fields are required:
+
+| Field | Purpose |
+|-------|---------|
+| `sea` | Primary dark color for the header title, links, table heads, and badges |
+| `sky` | Accent color, also the fade-out end of the header rule |
+| `skyll` | Code block, table row, and textbox fill |
+| `paper` | Text on dark backgrounds |
+| `page-fill` | Slide page background |
+
+## Component Reference
+
+| Category | API | Usage |
+|----------|-----|-------|
+| Slide entry | `xwysyy-pre` | `#show: xwysyy-pre.with(theme: "sky", ...)` |
+| Title slide | `title-slide` | `#title-slide()` |
+| Outline | `outline-slide` | `#outline-slide()` auto-collects section headings |
+| Content slide | `xwysyy-slide` | `== Title` auto-triggers |
+| Section transition | `new-section-slide` | `= Title` auto-triggers |
+| Full-screen image | `image-slide` | `#image-slide(img: image("bg.png"))` |
+| End slide | `end-slide` | `#end-slide(title: [...])` |
+| Layout · pair | `duo-slide` | figure over text, measured spacing + telemetry |
+| Layout · single | `focus-slide` | one centered block for sparse pages |
+| Layout · columns | `grid-slide` | N equal-height peer columns |
+| Layout · stack | `stack-slide` | N vertical blocks, the visual grows dominant or cards grow tall |
+| Layout · compare | `compare-slide` | two top-aligned blocks read as a contrast |
+| Layout · stats | `stat-slide` | a row of big-number metric tiles |
+| Layout · figure | `figure-slide` | figure, tight caption, optional takeaway |
+| Layout · sidebar | `sidebar-slide` | a label tab beside a content card |
+| Text box | `textbox` | `#textbox[Content]` or `#textbox([Col 1], [Col 2])` |
+| Highlight | `red` / `bred` | `#red[text]` / `#bred[bold red]` |
+| Highlight | `yellow` / `byellow` | `#yellow[text]` / `#byellow[bold yellow]` |
+
+The layout components (`duo-slide`, `focus-slide`, `grid-slide`, `stack-slide`, `compare-slide`, `stat-slide`, `figure-slide`, `sidebar-slide`) take typed content items (`visual` / `card` / `takeaway` / `plain`, with `metric` entries for `stat-slide`; `sidebar-slide` takes plain content) with declared sizing, measure every block, distribute space fill-first, and export `` v4 telemetry (allocated frame, natural preferred size, 2-D payload bbox with a measured/declared source, and paint box + fill per object). For stepwise reveal use the `reveal: true` parameter of the multi-block components (all but `focus-slide` and `sidebar-slide`) instead of `#pause`, which cannot appear inside the components (touying panics). See the [layout guide](https://github.com/xwysyy/xwysyy-typst/blob/v0.5.0/docs/LAYOUT.md).
+
+## Handouts And Speaker Notes
+
+Pass touying's handout setting through `xwysyy-pre`. A command-line switch can be wired as follows:
+
+```typst
+#let handout = sys.inputs.at("handout", default: "false") == "true"
+
+#show: xwysyy-pre.with(
+ config-common(handout: handout),
+ config-info(title: [My Presentation]),
+)
+```
+
+```bash
+typst compile main.typ slides.pdf
+typst compile --input handout=true main.typ slides-handout.pdf
+```
+
+Speaker notes are available because `xwysyy.typ` re-exports touying:
+
+```typst
+#speaker-note[
+ Mention the ablation table before moving to the next section.
+]
+```
+
+Export pdfpc metadata:
+
+```bash
+typst eval --in main.typ --format json 'query().first().value' > slides.pdfpc
+```
+
+## Requirements
+
+- Typst >= 0.15.0
+- touying 0.8.0, downloaded on first compile
+- physica 0.9.8, downloaded on first compile
+- Default local fonts: Times New Roman, Noto Serif CJK SC, Libertinus Sans, Noto Sans CJK SC, Maple Mono, and Noto Sans Mono CJK SC
+- Typst web app users can pass web-available fonts with `font:`, `heading-font:`, and `code-font:`
+
+Full API reference: [docs/USAGE.md](https://github.com/xwysyy/xwysyy-typst/blob/v0.5.0/docs/USAGE.md). Customization guide: [docs/CUSTOMIZATION.md](https://github.com/xwysyy/xwysyy-typst/blob/v0.5.0/docs/CUSTOMIZATION.md). Theme generator: [docs/THEME-GENERATOR.md](https://github.com/xwysyy/xwysyy-typst/blob/v0.5.0/docs/THEME-GENERATOR.md).
+
+## Acknowledgements
+
+- Theme derived from [Carlos-Mero/may](https://github.com/Carlos-Mero/may) under MIT
+- Built on [touying](https://github.com/touying-typ/touying)
+
+## License
+
+[MIT](./LICENSE)
diff --git a/packages/preview/xwysyy/0.5.0/src/elements.typ b/packages/preview/xwysyy/0.5.0/src/elements.typ
new file mode 100644
index 0000000000..a7e8d5ea7f
--- /dev/null
+++ b/packages/preview/xwysyy/0.5.0/src/elements.typ
@@ -0,0 +1,139 @@
+// Shared show-chain (xwysyy-elements) and reusable components (info, textbox).
+
+#import "@preview/touying:0.8.0": components
+#import "themes.typ": *
+
+#let xwysyy-elements(
+ doc,
+ code-font: ("Maple Mono", "Noto Sans Mono CJK SC"),
+ t-sea: sea,
+ t-sky: sky,
+ t-skyll: skyll,
+ t-paper: paper,
+) = [
+ // Bold enhancement (recipe in themes.typ `_bold-run`)
+ #show strong: it => _bold-run(it.body)
+
+ // List style
+ #set list(marker: (text(fill: t-sea, [❖]), text(fill: t-sky, [⬦]), text(fill: t-sky, [–])), spacing: 1.2em, indent: 0.5em, body-indent: 0.8em)
+ #set enum(spacing: 1.2em, indent: 0.5em)
+
+ // Italic — per-character synthetic skew for CJK
+ #show emph: it => {
+ if type(it.body) == content and it.body.has("text") {
+ for c in it.body.text {
+ box(skew(ax: -8deg, c))
+ }
+ } else {
+ box(skew(ax: -8deg, it.body))
+ }
+ }
+
+ // No image shadow: wrapping images in a box sized by measure() collapses
+ // percentage-width images to zero size.
+
+ // Figure captions — smaller
+ #show figure.caption: it => {
+ set text(size: 0.78em, fill: luma(100))
+ v(0.3em)
+ it
+ }
+
+ // Table captions on top
+ #show figure.where(kind: table): set figure.caption(position: top)
+
+ // Codeblocks
+ #show raw.where(block: true): it => {
+ set text(font: code-font, size: 0.9em)
+ block(
+ width: 100%,
+ height: auto,
+ fill: t-skyll,
+ inset: 0.6em,
+ radius: 0.5em,
+ it
+ )
+ }
+ // Inline code — the chip fill is painted with `outset` so it does not join
+ // baseline/line-height layout (the old inset+baseline recipe sank the code
+ // text ~0.1em below the surrounding baseline).
+ #show raw.where(block: false): it => {
+ set text(font: code-font)
+ box(
+ fill: t-skyll,
+ inset: (x: 0.3em),
+ outset: (y: 0.2em),
+ radius: 0.3em,
+ it
+ )
+ }
+
+ // Links
+ #show link: underline
+ #show link: it => {
+ set text(fill: t-sea)
+ it
+ }
+
+ // Detail Decoration — longer patterns first to avoid partial matches.
+ // Each rule is guarded so it never rewrites code content: string show rules
+ // also match text inside raw, which used to turn `<=` in code into ⇐.
+ // NB: `text.font` reports lowercased family names — compare case-insensitively.
+ #let code-head = lower(if type(code-font) == array { code-font.first() } else { code-font })
+ #let non-code(arrow) = it => context {
+ let f = text.font
+ let head = if type(f) == array and f.len() > 0 { f.first() } else { f }
+ if lower(head) == code-head { it } else { arrow }
+ }
+ #show "<==>": non-code([$arrow.l.r.double.long$])
+ #show "<=>": non-code([$<=>$])
+ #show "-->": non-code([$-->$])
+ #show "<--": non-code([$<--$])
+ #show "==>": non-code([$==>$])
+ #show "<==": non-code([$arrow.l.double.long$])
+ #show "->": non-code([$->$])
+ #show "<-": non-code([$<-$])
+ #show "=>": non-code([$=>$])
+ #show "<=": non-code([$arrow.l.double$])
+ #show "|->": non-code([$|->$])
+
+ // Tables — seamless filled header + zebra body (no gutter: cell gaps used to
+ // slice the fills into fragments), theme-toned thin hlines
+ #set table(
+ stroke: none,
+ inset: (x: 0.6em, y: 0.42em),
+ align: center,
+ fill: (x, y) => if y == 0 { t-sea } else if calc.even(y) { t-skyll } else { none },
+ )
+ #set table.hline(stroke: 0.5pt + t-sea.lighten(30%))
+ #show table.cell: it => {
+ if it.y == 0 {
+ set text(t-paper, weight: "bold")
+ it
+ } else {it}
+ }
+
+ #doc
+]
+
+// information item
+#let info(something, description) = [
+ *#something* #h(1fr) *#description*\
+]
+
+#let textbox(inset: 0.8em, radius: 0.4em, width: 100%, gutter: 0.6em, ..bodies) = context {
+ let t = _theme-state.get()
+ let bodies = bodies.pos()
+ if bodies.len() == 1 {
+ block(width: width, fill: t.skyll, inset: inset, radius: radius, bodies.first())
+ } else {
+ components.lazy-layout(grid(
+ columns: (1fr,) * bodies.len(),
+ gutter: gutter,
+ ..bodies.map(b => block(width: 100%, fill: t.skyll, inset: inset, radius: radius, {
+ b
+ components.lazy-v(1fr)
+ })),
+ ))
+ }
+}
diff --git a/packages/preview/xwysyy/0.5.0/src/layout.typ b/packages/preview/xwysyy/0.5.0/src/layout.typ
new file mode 100644
index 0000000000..25c323351c
--- /dev/null
+++ b/packages/preview/xwysyy/0.5.0/src/layout.typ
@@ -0,0 +1,1366 @@
+// Semantic layout layer for xwysyy-typst — telemetry schema v4.
+//
+// Authors (human or AI) pick a semantic component and provide typed content
+// items; each component measures the real rendered size of every block, runs
+// a shared allocator, and exports the *measured* normalized geometry as
+// `` v4 metadata for scripts/slide-check.py.
+//
+// Honesty rules baked into the schema:
+// * sizing is DECLARED, never inferred: percent-sized content must be
+// wrapped in `visual(...)` (fit: "stretch"); required slots panic on
+// `none`; text slots panic on content with no measurable width or height
+// (spacers, empty strings, bare rules) instead of masquerading as a
+// payload;
+// * every object carries `frame` (allocated), `preferred` (what the
+// allocator saw), `payload` (a 2-D flow bbox of the inner content —
+// approximate for wrapped text, exact for fixed-size content) and
+// `paint` (the visible card box + its fill colour, or none);
+// * `payload_source` separates "measured" payloads from "declared" ones
+// (stretch slots and percent-width media): a declared payload is a
+// claim, not evidence — the checker verifies it against rendered pixels
+// instead of trusting it;
+// * unbreakable content wider than its slot is exported as `overflow_x`
+// (wrapped text is clamped honestly; a long URL that cannot wrap is not);
+// * the allocator has four states — normal / compressed (gaps squeezed
+// below their preferred size) / tight (outer margins consumed) /
+// overflow — with the invariants overflow => body_overflow_ratio > 0 and
+// compressed => gap_ratio < 1;
+// * one `` mapping is emitted per physically rendered
+// subslide, carrying the physical body geometry in pt, and the slide id
+// is stable across subslides (touying's logical slide counter), so every
+// frame joins its layout record and coverage counts real pages.
+//
+// Authors never write raw `v()` spacing or absolute coordinates; they adjust
+// `mode` and read the numeric feedback the checker returns after each
+// compile. Numeric layout knobs live in the `tuning` dictionary (validated
+// for key, type, and range); the AI generation contract treats `tuning` as
+// off-limits and the telemetry records whether it was used.
+//
+// Stepwise reveal: touying's `#pause` cannot be used inside these components
+// (marks do not survive `context` / `layout` closures; touying panics).
+// Components take `reveal: true` (or per-item `reveal-from`) instead, which
+// shows blocks one subslide at a time via the callback-style `utils.uncover`
+// — hidden steps keep their measured space, so the layout is identical on
+// every subslide. One resolver (`_steps`) serves every component and an
+// explicit `reveal-from` always wins over the `reveal: true` sugar;
+// components without reveal steps have a single frame and reject any
+// `reveal-from` beyond step 1 instead of silently ignoring it.
+
+#import "@preview/touying:0.8.0": utils
+#import "slides.typ": xwysyy-slide
+#import "themes.typ": _theme-state
+
+// Capture `top` / `left` before the parameters of the same name (duo's `top`,
+// compare's `left`) shadow them inside a component body.
+#let _atop = top
+#let _aleft = left
+
+#let _clamp(x, lo, hi) = calc.min(calc.max(x, lo), hi)
+
+// Optical center sits slightly above the geometric center so a centered group
+// does not read as sinking. Only focus-slide centers a group; every other
+// component fills the body instead.
+#let _OPTICAL-CENTER = 0.46
+
+// Fill-first distribution. Outer margins are pinned small; leftover space
+// grows the content (figures become dominant, cards become tall) instead of
+// becoming blank margin.
+#let _MARGIN-TOP = 0.07
+#let _MARGIN-BOT = 0.09
+#let _FILL = 1.0 - _MARGIN-TOP - _MARGIN-BOT
+// A card row occupies at least this fraction of the body height, so a row of
+// short cards reads as substantial cards rather than tiles floating in space.
+#let _CARD-FILL-MIN = 0.60
+// Hard minimum for a stretch visual: below this the visual is starved and the
+// slide must degrade to tight/overflow instead of reporting normal.
+#let _VISUAL-MIN = 0.28
+// Semantic gaps compress down to this fraction of their preferred size before
+// the slide starts consuming its outer margins.
+#let _GAP-MIN = 0.4
+// Card inner padding.
+#let _CARD-PAD = 0.9em
+// stat-slide shrinks a long value to fit its tile, but never below this scale
+// (below it the value wraps and the tile grows, reported honestly by fit).
+#let _STAT-SCALE-MIN = 0.6
+// Measurement epsilon: below this a dimension counts as zero.
+#let _EPS-L = 0.01pt
+
+#let _MODES = ("compact", "balanced", "separated")
+#let _check-mode(mode) = {
+ if mode not in _MODES {
+ panic("unknown mode " + repr(mode) + "; use \"compact\" | \"balanced\" | \"separated\"")
+ }
+}
+
+// Gap between semantically-related blocks, as a fraction of body height. The
+// three values land inside the checker's proximity ranges so a correctly
+// generated slide passes without diagnostics.
+#let _mode-gap(mode) = {
+ if mode == "compact" { 0.06 } else if mode == "separated" { 0.22 } else { 0.13 }
+}
+#let _mode-proximity(mode) = {
+ if mode == "compact" { "compact" } else if mode == "separated" { "loose" } else { "medium" }
+}
+
+// Numeric layout knobs, validated for key, type, and range so a typo or a
+// nonsense value fails loudly instead of being silently ignored.
+// `spec` maps key -> (default:, lo:, hi:).
+#let _tuning(comp, given, spec) = {
+ for (k, v) in given {
+ if k not in spec {
+ panic(comp + ": unknown tuning key " + repr(k) + "; allowed: " + repr(spec.keys()))
+ }
+ if type(v) != float and type(v) != int {
+ panic(comp + ": tuning " + repr(k) + " must be a number, got " + repr(v))
+ }
+ let s = spec.at(k)
+ if v < s.lo or v > s.hi {
+ panic(comp + ": tuning " + repr(k) + " = " + repr(v) + " outside [" + repr(s.lo) + ", " + repr(s.hi) + "]")
+ }
+ }
+ let out = (:)
+ for (k, s) in spec { out.insert(k, float(given.at(k, default: s.default))) }
+ out
+}
+
+#let _require(comp, slot, v) = {
+ if v == none { panic(comp + ": " + slot + " is required") }
+}
+
+// Reveal gating. The block becomes visible from subslide `step` (1-based);
+// hidden steps keep their measured space. Uses the callback-style
+// `utils.uncover` because mark-based `#pause` / global `#uncover` panic inside
+// `context` / `layout`.
+#let _from(self, step, body) = {
+ if step <= 1 { body } else { utils.uncover(self: self, str(step) + "-", body) }
+}
+
+// ---------------------------------------------------------------------------
+// typed items (declared sizing; sidebar slots take plain content instead)
+// ---------------------------------------------------------------------------
+//
+// `visual(...)` marks the slide's visual payload: never carded, and with
+// fit: "stretch" (the default) it absorbs free space and its content is told
+// to fill the allocated frame (use `rect(height: 100%)`, or
+// `image(width: 100%, height: 100%, fit: "contain")` for real images).
+// fit: "natural" keeps the visual at its measured size (`image(width: 100%)`).
+// `card(...)` / `takeaway(...)` draw the theme card behind their content.
+// `plain(...)` is an uncarded text block. Plain (untyped) content passed to
+// a slot is coerced per component (documented at each component).
+//
+// Roles form a closed set: they weight the checker's optical-centre estimate
+// and must not be able to change its control flow, so there is no
+// "decorative" escape hatch.
+
+#let _ROLES = (
+ "main_visual", "figure", "image", "chart", "visual", "table", "formula",
+ "callout", "takeaway", "option", "column", "explanation", "label",
+ "metric", "text", "content", "caption",
+)
+#let _check-role(ctor, role) = {
+ if role not in _ROLES {
+ panic(ctor + ": role " + repr(role) + " is not in the closed role set " + repr(_ROLES))
+ }
+}
+
+#let visual(body, fit: "stretch", role: "main_visual", reveal-from: auto) = {
+ if fit not in ("stretch", "natural") {
+ panic("visual: fit must be \"stretch\" or \"natural\", got " + repr(fit))
+ }
+ _check-role("visual", role)
+ (xwysyy-item: true, kind: "visual", fit: fit, role: role, body: body, reveal-from: reveal-from)
+}
+#let card(body, role: "explanation", reveal-from: auto) = {
+ _check-role("card", role)
+ (xwysyy-item: true, kind: "card", fit: "natural", role: role, body: body, reveal-from: reveal-from)
+}
+#let takeaway(body, reveal-from: auto) = (
+ xwysyy-item: true, kind: "takeaway", fit: "natural", role: "takeaway", body: body, reveal-from: reveal-from,
+)
+#let plain(body, role: "text", reveal-from: auto) = {
+ _check-role("plain", role)
+ (xwysyy-item: true, kind: "plain", fit: "natural", role: role, body: body, reveal-from: reveal-from)
+}
+
+#let _as-item(x, default-kind) = {
+ if type(x) == dictionary and x.at("xwysyy-item", default: false) { x }
+ else if default-kind == "visual" {
+ (xwysyy-item: true, kind: "visual", fit: "natural", role: "main_visual", body: x, reveal-from: auto)
+ } else if default-kind == "takeaway" {
+ (xwysyy-item: true, kind: "takeaway", fit: "natural", role: "takeaway", body: x, reveal-from: auto)
+ } else {
+ (xwysyy-item: true, kind: "card", fit: "natural", role: "explanation", body: x, reveal-from: auto)
+ }
+}
+
+#let _painted(it) = it.kind in ("card", "takeaway")
+#let _stretchy(it) = it.kind == "visual" and it.fit == "stretch"
+
+#let _KINDS = ("visual", "card", "takeaway", "plain")
+
+// Central item validation, run up front in every component. The constructor
+// marker is not trusted: a hand-built dictionary with an unknown kind, fit, or
+// role fails here instead of silently taking some other code path.
+// `stretch: false` marks slots sized by their natural height; `reveal: false`
+// marks components without reveal steps, which accept `reveal-from: 1` (their
+// single frame) and reject any later step instead of ignoring it.
+#let _validate-item(comp, slot, it, stretch: true, reveal: true) = {
+ if it.at("kind", default: none) not in _KINDS {
+ panic(comp + ": " + slot + " item kind must be one of " + repr(_KINDS)
+ + ", got " + repr(it.at("kind", default: none)))
+ }
+ if it.at("fit", default: none) not in ("stretch", "natural") {
+ panic(comp + ": " + slot + " fit must be \"stretch\" or \"natural\", got "
+ + repr(it.at("fit", default: none)))
+ }
+ if it.at("role", default: none) not in _ROLES {
+ panic(comp + ": " + slot + " role " + repr(it.at("role", default: none))
+ + " is not in the closed role set " + repr(_ROLES))
+ }
+ if it.at("body", default: none) == none {
+ panic(comp + ": " + slot + " body is required")
+ }
+ if not stretch and _stretchy(it) {
+ panic(comp + ": " + slot + " cannot be visual(fit: \"stretch\") — this slot is "
+ + "sized by its natural height; use fit: \"natural\"")
+ }
+ let rf = it.at("reveal-from", default: auto)
+ if not reveal and rf != auto and rf != 1 {
+ panic(comp + ": " + slot + " has no reveal steps; remove reveal-from")
+ }
+ it
+}
+
+// Relation kinds an author may declare between blocks. Structural kinds
+// (peer / caption / labels) are emitted by the components themselves.
+#let _AUTHOR-RELS = ("supports", "contrast")
+#let _check-relation(comp, rel) = {
+ if rel not in _AUTHOR-RELS {
+ panic(comp + ": relation must be one of " + repr(_AUTHOR-RELS) + ", got " + repr(rel))
+ }
+}
+
+// Resolve per-item reveal steps — the single resolver for every component.
+// `reveal: true` reveals auto items in order (item i on subslide
+// min(i+1, max-step)); an explicit `reveal-from` must be an integer in
+// [1, max-step] and ALWAYS wins over the sugar.
+#let _steps(comp, items, reveal, max-step) = {
+ items.enumerate().map(((i, it)) => {
+ let rf = it.at("reveal-from", default: auto)
+ if rf == auto {
+ if reveal { calc.min(i + 1, max-step) } else { 1 }
+ } else {
+ if type(rf) != int {
+ panic(comp + ": reveal-from must be an integer >= 1, got " + repr(rf))
+ }
+ if rf < 1 or rf > max-step {
+ panic(comp + ": reveal-from = " + str(rf) + " outside [1, " + str(max-step) + "]")
+ }
+ rf
+ }
+ })
+}
+
+// ---------------------------------------------------------------------------
+// allocator
+// ---------------------------------------------------------------------------
+//
+// Column: every item is a spec (min:, pref:, max:, grow:) in absolute
+// lengths (max: none = unbounded); every gap is (min:, pref:). Resolution
+// order and states:
+// normal — everything at preferred fits the safe area; free space goes
+// to grow-weighted items (water-filling against max); residual
+// free space centers the group inside the safe area
+// compressed — fits the safe area only with gaps squeezed below preferred
+// (but not below their min); gap_ratio in [_GAP-MIN, 1)
+// tight — fits the page only by consuming the outer margins; gaps at
+// min, group centered on the page; margin_deficit_ratio > 0
+// overflow — taller than the page even at minimums;
+// body_overflow_ratio > 0 (hard invariant)
+// `gap_ratio` is the REAL ratio of the summed actual gaps to the summed
+// preferred gaps (1.0 when the layout has no gaps), not an interpolation
+// parameter.
+#let _alloc-column(specs, gaps, H) = {
+ let budget = H * _FILL
+ let prefs = specs.map(s => calc.max(s.pref, s.min))
+ let body = prefs.fold(0pt, (a, h) => a + h)
+ let gap-pref = gaps.fold(0pt, (a, g) => a + g.pref)
+ let gap-min = gaps.fold(0pt, (a, g) => a + g.min)
+ let base = body + gap-pref
+ let min-ratio = if gap-pref > 0pt { gap-min / gap-pref } else { 1.0 }
+ if base <= budget {
+ let heights = prefs
+ let free = budget - base
+ let frozen = specs.map(s => s.grow <= 0)
+ while free > 0.05pt {
+ let gsum = 0.0
+ for (i, s) in specs.enumerate() {
+ if not frozen.at(i) { gsum += s.grow }
+ }
+ if gsum == 0 { break }
+ let spent = 0pt
+ for (i, s) in specs.enumerate() {
+ if frozen.at(i) { continue }
+ let add = free * (s.grow / gsum)
+ if s.max != none and heights.at(i) + add >= s.max {
+ add = calc.max(s.max - heights.at(i), 0pt)
+ frozen.at(i) = true
+ }
+ heights.at(i) += add
+ spent += add
+ }
+ free -= spent
+ if spent <= 0.05pt { break }
+ }
+ let used = heights.fold(0pt, (a, h) => a + h) + gap-pref
+ (heights: heights, gaps: gaps.map(g => g.pref),
+ y0: H * _MARGIN-TOP + calc.max(budget - used, 0pt) / 2,
+ fit: (state: "normal", required_height_ratio: base / H, gap_ratio: 1.0,
+ margin_deficit_ratio: 0.0, body_overflow_ratio: 0.0))
+ } else if body + gap-min <= budget {
+ let span = gap-pref - gap-min
+ let t = if span > 0pt { (budget - body - gap-min) / span } else { 1.0 }
+ let actual = gaps.map(g => g.min + (g.pref - g.min) * t)
+ let actual-sum = actual.fold(0pt, (a, g) => a + g)
+ (heights: prefs, gaps: actual,
+ y0: H * _MARGIN-TOP,
+ fit: (state: "compressed", required_height_ratio: base / H,
+ gap_ratio: if gap-pref > 0pt { actual-sum / gap-pref } else { 1.0 },
+ margin_deficit_ratio: 0.0, body_overflow_ratio: 0.0))
+ } else if body + gap-min <= H {
+ let total = body + gap-min
+ (heights: prefs, gaps: gaps.map(g => g.min), y0: (H - total) / 2,
+ fit: (state: "tight", required_height_ratio: base / H, gap_ratio: min-ratio,
+ margin_deficit_ratio: (total - budget) / H, body_overflow_ratio: 0.0))
+ } else {
+ let total = body + gap-min
+ (heights: prefs, gaps: gaps.map(g => g.min), y0: 0pt,
+ fit: (state: "overflow", required_height_ratio: base / H, gap_ratio: min-ratio,
+ margin_deficit_ratio: (H - budget) / H, body_overflow_ratio: (total - H) / H))
+ }
+}
+
+// Row variant: one shared height for side-by-side cards. No gaps to
+// compress, so the states are normal / tight / overflow and gap_ratio is 1.
+#let _fit-row(naturals, H) = {
+ let natural = naturals.fold(0pt, (a, h) => calc.max(a, h))
+ let budget = H * _FILL
+ if natural <= budget {
+ let row = _clamp(natural, _CARD-FILL-MIN * H, budget)
+ (row: row, y: H * _MARGIN-TOP + (budget - row) / 2,
+ fit: (state: "normal", required_height_ratio: natural / H, gap_ratio: 1.0,
+ margin_deficit_ratio: 0.0, body_overflow_ratio: 0.0))
+ } else if natural <= H {
+ (row: natural, y: (H - natural) / 2,
+ fit: (state: "tight", required_height_ratio: natural / H, gap_ratio: 1.0,
+ margin_deficit_ratio: (natural - budget) / H, body_overflow_ratio: 0.0))
+ } else {
+ (row: natural, y: 0pt,
+ fit: (state: "overflow", required_height_ratio: natural / H, gap_ratio: 1.0,
+ margin_deficit_ratio: (H - budget) / H, body_overflow_ratio: (natural - H) / H))
+ }
+}
+
+// ---------------------------------------------------------------------------
+// measurement
+// ---------------------------------------------------------------------------
+
+// Content signatures (verified against typst 0.15 `measure`):
+// * `v(...)` and an empty string have zero unconstrained WIDTH;
+// * a bare horizontal rule has zero constrained HEIGHT;
+// * percent-width media (`image(width: 100%)`) measures exactly like a
+// spacer — zero unconstrained width — so only visual() slots may fall
+// back to the slot width, and that payload is marked "declared";
+// * `hide(...)` keeps its full layout size and can only be caught by the
+// pixel cross-check.
+// Text slots (card / takeaway / plain / metric fields) therefore require a
+// positive unconstrained width AND a positive constrained height; visuals
+// only panic when every dimension is zero (genuinely empty content).
+#let _assert-measurable(comp, slot, kind, body, w) = {
+ let mu = measure(body)
+ let mc = measure(block(width: w, body))
+ if kind == "visual" {
+ if mu.width < _EPS-L and mu.height < _EPS-L and mc.height < _EPS-L {
+ panic(comp + ": " + slot + " renders empty — pass real content; percent-sized "
+ + "content must be declared visual() (fit: \"stretch\")")
+ }
+ } else {
+ if mu.width < _EPS-L {
+ panic(comp + ": " + slot + " has no measurable width — spacers and empty "
+ + "strings are not content, and percent-width media belongs in visual()")
+ }
+ if mc.height < _EPS-L {
+ panic(comp + ": " + slot + " has no measurable height — a bare rule is "
+ + "decoration, not content")
+ }
+ }
+ (mu: mu, mc: mc)
+}
+
+// Horizontal-overflow evidence. Unbreakable content wider than its slot
+// cannot wrap: the constrained height (`mc`) stays at the unconstrained
+// height (`mu`) while the natural width exceeds the slot width `w`. Wrapped
+// text is clamped honestly instead.
+#let _overflows-x(mu, mc, w) = mu.width > w + _EPS-L and mc.height <= mu.height + _EPS-L
+
+// Measure a natural item for a slot of width `w`. Returns the outer size the
+// allocator uses (including card padding), the 2-D payload flow bbox, the
+// payload source, and horizontal-overflow evidence.
+#let _measure-item(comp, slot, it, w) = {
+ let pad = if _painted(it) { _CARD-PAD.to-absolute() } else { 0pt }
+ let iw = w - 2 * pad
+ let m = _assert-measurable(comp, slot, it.kind, it.body, iw)
+ // Only reachable for visual items: percent-width media measures at zero
+ // width, claims the slot width, and is marked "declared" so the checker
+ // demands pixel evidence instead of counting it as measured payload.
+ let fallback = m.mu.width < _EPS-L
+ (
+ outer: m.mc.height + 2 * pad,
+ pay-w: if fallback { iw } else { calc.min(m.mu.width, iw) },
+ pay-h: m.mc.height,
+ pad: pad,
+ src: if fallback { "declared" } else { "measured" },
+ over-x: _overflows-x(m.mu, m.mc, iw),
+ )
+}
+
+// Build the allocator spec and telemetry seed for one column/slot item.
+// Stretch visuals get the hard minimum and a grow weight; natural items are
+// pinned at their measured size (max = pref) unless the component grants
+// cards a grow weight.
+#let _item-slot(comp, slot, it, w, H) = {
+ if _stretchy(it) {
+ (stretch: true, painted: false, pad: 0pt,
+ spec: (min: H * _VISUAL-MIN, pref: H * _VISUAL-MIN, max: none, grow: 1.0),
+ pay-w: none, pay-h: none, src: "declared", over-x: false)
+ } else {
+ let m = _measure-item(comp, slot, it, w)
+ (stretch: false, painted: _painted(it), pad: m.pad,
+ spec: (min: m.outer, pref: m.outer, max: m.outer, grow: 0.0),
+ pay-w: m.pay-w, pay-h: m.pay-h, src: m.src, over-x: m.over-x)
+ }
+}
+
+// ---------------------------------------------------------------------------
+// telemetry primitives (schema v4)
+// ---------------------------------------------------------------------------
+
+// One telemetry object.
+// frame — the box the component allocated (normalized to the body)
+// preferred — the natural outer size the allocator saw
+// payload — 2-D flow bbox of the inner content (inside card
+// padding); approximate for wrapped text (flow bbox, not
+// glyph ink); equals the padded frame for declared-stretch
+// content
+// paint — the visible card box, or none for unpainted objects
+// paint_fill — hex colour of the card fill (pixel-check evidence)
+// payload_source — "measured" | "declared"; declared payloads are claims
+// the checker verifies against pixels, never evidence
+// overflow_x — unbreakable content wider than the slot (see
+// _measure-item)
+// Payload height is NOT clamped to the frame: overflowing content reports a
+// payload that runs past its frame, and the checker sees it.
+#let _obj(
+ id, kind, role,
+ fx, fy, fw, fh,
+ pref-h: none,
+ pay-w: none, pay-h: none,
+ pay-src: "measured", over-x: false,
+ pad-x: 0.0, pad-y: 0.0,
+ calign: "center", halign: "center",
+ painted: false, paint-fill: none, visible-from: 1,
+) = {
+ let ix = fx + pad-x
+ let iy = fy + pad-y
+ let iw = calc.max(fw - 2 * pad-x, 0.0)
+ let ih = calc.max(fh - 2 * pad-y, 0.0)
+ let sx = if pay-w == none { "stretch" } else { "natural" }
+ let sy = if pay-h == none { "stretch" } else { "natural" }
+ let pw = if pay-w == none { iw } else { calc.min(pay-w, iw) }
+ let ph = if pay-h == none { ih } else { pay-h }
+ let px = if sx == "stretch" or halign == "left" { ix } else { ix + (iw - pw) / 2 }
+ let py = if sy == "stretch" or calign == "top" { iy } else { iy + (ih - ph) / 2 }
+ (
+ id: id,
+ object_kind: kind,
+ semantic_role: role,
+ frame: (x: fx, y: fy, w: fw, h: fh),
+ preferred: (w: fw, h: if pref-h == none { fh } else { pref-h }),
+ payload: (x: px, y: py, w: pw, h: ph),
+ paint: if painted { (x: fx, y: fy, w: fw, h: fh) } else { none },
+ paint_fill: if painted { paint-fill } else { none },
+ payload_source: if pay-w == none or pay-h == none { "declared" } else { pay-src },
+ overflow_x: over-x,
+ sizing: (x: sx, y: sy),
+ visible_from: visible-from,
+ )
+}
+
+#let _rel(from, to, kind, axis, proximity) = (
+ from: from,
+ to: to,
+ kind: kind,
+ axis: axis,
+ desired_proximity: proximity,
+)
+
+// One frame mapping per physically rendered subslide, carrying the physical
+// body geometry (pt) so the pixel checker maps normalized telemetry onto the
+// render without hard-coded page constants. In handout mode only the
+// surviving subslide emits, so coverage counts real pages.
+#let _frame-mark(sid, self, size) = {
+ let pos = here().position()
+ [#metadata((
+ schema: "xwysyy-frame/v2",
+ id: sid,
+ step: self.subslide,
+ steps: self.repeat,
+ page: pos.page,
+ handout: self.handout,
+ body: (x: pos.x.pt(), y: pos.y.pt(), w: size.width.pt(), h: size.height.pt()),
+ page_size: (w: page.width.pt(), h: page.height.pt()),
+ )) ]
+}
+
+// Emit one slide's telemetry. Must be produced in markup so the label binds
+// (a label on `metadata(...)` in code mode is a Typst syntax error).
+#let _emit(id, archetype, engine, page, frame-count, objects, relations, fit, extra) = [
+ #metadata((
+ schema: "xwysyy-slide-layout/v4",
+ id: id,
+ archetype: archetype,
+ layout_engine: engine,
+ page: page,
+ frame_count: frame-count,
+ coordinate_system: "normalized-slide-body",
+ objects: objects,
+ relations: relations,
+ fit: fit,
+ extra: extra,
+ ))
+]
+
+// Debug overlay: solid rect = frame, dashed rect = payload bbox.
+#let _debug-palette = (
+ rgb("#d7263d"), rgb("#2e86ab"), rgb("#4f772d"),
+ rgb("#7b2cbf"), rgb("#f77f00"), rgb("#006d77"),
+)
+#let _debug-layer(objects, W, H) = {
+ for (i, o) in objects.enumerate() {
+ let c = _debug-palette.at(calc.rem(i, _debug-palette.len()))
+ let f = o.frame
+ place(_atop + _aleft, dx: W * f.x, dy: H * f.y, rect(
+ width: W * f.w, height: H * f.h,
+ stroke: (paint: c, thickness: 0.7pt), fill: c.transparentize(93%),
+ ))
+ if o.sizing.y == "natural" or o.sizing.x == "natural" {
+ let p = o.payload
+ place(_atop + _aleft, dx: W * p.x, dy: H * p.y, rect(
+ width: W * p.w, height: H * p.h,
+ stroke: (paint: c, thickness: 0.5pt, dash: "dashed"),
+ ))
+ }
+ place(_atop + _aleft, dx: W * f.x + 0.2em, dy: H * f.y + 0.12em,
+ text(fill: c, size: 0.5em, weight: "bold", o.id))
+ }
+}
+
+// Card container for slot rendering: the theme's rounded skyll box; `calign`
+// controls where content sits inside a fixed-height box.
+#let _card-box(cw, h, fill, body, calign: horizon) = {
+ block(width: cw, height: h, fill: fill, inset: _CARD-PAD, radius: 0.4em, align(calign, body))
+}
+
+// Render one typed item into its allocated slot.
+#let _render-item(it, w, h, fill, calign: horizon) = {
+ if _painted(it) {
+ _card-box(w, h, fill, it.body, calign: calign)
+ } else {
+ block(width: w, height: h, align(center + calign, it.body))
+ }
+}
+
+// Resolve the slide id: `auto` becomes "@s" from touying's logical
+// slide counter, which is STABLE across reveal subslides and handout mode
+// (the physical page number is not: it changes per subslide and would detach
+// early frames from their layout record). Must run inside a context.
+#let _sid(id, base) = {
+ if id == auto { base + "@s" + str(utils.slide-counter.get().first()) } else { id }
+}
+
+// ---------------------------------------------------------------------------
+// duo-slide — one vertical semantic pair (e.g. figure over takeaway)
+// ---------------------------------------------------------------------------
+//
+// `top` coerces plain content to visual(fit: "natural"); wrap percent-height
+// content in visual() to make it the growing dominant block. `bottom`
+// coerces plain content to card() (painted).
+
+#let duo-slide(
+ title: auto,
+ id: auto,
+ top: none,
+ bottom: none,
+ mode: "balanced",
+ relation: "supports",
+ reveal: false,
+ debug: false,
+ tuning: (:),
+) = {
+ _check-mode(mode)
+ _check-relation("duo-slide", relation)
+ _require("duo-slide", "top", top)
+ _require("duo-slide", "bottom", bottom)
+ let tn = _tuning("duo-slide", tuning, (
+ "top-width": (default: 0.82, lo: 0.3, hi: 0.95),
+ "bottom-width": (default: 0.74, lo: 0.3, hi: 0.95),
+ ))
+ let ti = _validate-item("duo-slide", "top", _as-item(top, "visual"))
+ let bi = _validate-item("duo-slide", "bottom", _as-item(bottom, "card"))
+ let steps = _steps("duo-slide", (ti, bi), reveal, 2)
+ let rep = calc.max(..steps, 1)
+ xwysyy-slide(title: title, repeat: rep, self => context {
+ let cfill = _theme-state.get().skyll
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "duo")
+ _frame-mark(sid, self, size)
+ let twn = tn.at("top-width")
+ let bwn = tn.at("bottom-width")
+ let tw = W * twn
+ let bw = W * bwn
+ let gap = H * _mode-gap(mode)
+ let ts = _item-slot("duo-slide", "top", ti, tw, H)
+ let bs = _item-slot("duo-slide", "bottom", bi, bw, H)
+ let alloc = _alloc-column((ts.spec, bs.spec), ((min: gap * _GAP-MIN, pref: gap),), H)
+ let th = alloc.heights.at(0)
+ let bh = alloc.heights.at(1)
+ let g = alloc.gaps.at(0)
+ let y0 = alloc.y0
+
+ place(_atop + _aleft, dx: (W - tw) / 2, dy: y0,
+ _from(self, steps.at(0), _render-item(ti, tw, th, cfill)))
+ place(_atop + _aleft, dx: (W - bw) / 2, dy: y0 + th + g,
+ _from(self, steps.at(1), _render-item(bi, bw, bh, cfill)))
+
+ let objects = (
+ _obj(sid + ":top", ti.kind, ti.role,
+ (1.0 - twn) / 2, y0 / H, twn, th / H,
+ pref-h: ts.spec.pref / H,
+ pay-w: if ts.stretch { none } else { ts.pay-w / W },
+ pay-h: if ts.stretch { none } else { ts.pay-h / H },
+ pay-src: ts.src, over-x: ts.over-x,
+ pad-x: ts.pad / W, pad-y: ts.pad / H,
+ halign: if ts.painted { "left" } else { "center" },
+ painted: ts.painted, paint-fill: cfill.to-hex(),
+ visible-from: steps.at(0)),
+ _obj(sid + ":bottom", bi.kind, bi.role,
+ (1.0 - bwn) / 2, (y0 + th + g) / H, bwn, bh / H,
+ pref-h: bs.spec.pref / H,
+ pay-w: if bs.stretch { none } else { bs.pay-w / W },
+ pay-h: if bs.stretch { none } else { bs.pay-h / H },
+ pay-src: bs.src, over-x: bs.over-x,
+ pad-x: bs.pad / W, pad-y: bs.pad / H,
+ halign: if bs.painted { "left" } else { "center" },
+ painted: bs.painted, paint-fill: cfill.to-hex(),
+ visible-from: steps.at(1)),
+ )
+ let relations = (
+ _rel(sid + ":top", sid + ":bottom", relation, "vertical", _mode-proximity(mode)),
+ )
+ if self.subslide == rep {
+ _emit(sid, "duo", "column", here().position().page, rep, objects, relations,
+ alloc.fit, (mode: mode, tuned: tuning.len() > 0))
+ if debug { _debug-layer(objects, W, H) }
+ }
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+// focus-slide — a single centered block for low-content pages
+// ---------------------------------------------------------------------------
+//
+// The one deliberate exception to fill-first: a focus page states a single
+// point and is allowed its symmetric whitespace. Plain content is coerced to
+// card(). A focus page has exactly one frame: it rejects reveal-from beyond
+// step 1 and stretch visuals (there is no free space to absorb). Fit still honours the
+// safe area: content taller than it reports tight, taller than the page
+// reports overflow.
+
+#let focus-slide(
+ title: auto,
+ id: auto,
+ body: none,
+ debug: false,
+ tuning: (:),
+) = {
+ _require("focus-slide", "body", body)
+ let tn = _tuning("focus-slide", tuning, (
+ "width": (default: 0.76, lo: 0.3, hi: 0.95),
+ "center-y": (default: _OPTICAL-CENTER, lo: 0.30, hi: 0.70),
+ ))
+ let it = _validate-item("focus-slide", "body", _as-item(body, "card"),
+ stretch: false, reveal: false)
+ xwysyy-slide(title: title, self => context {
+ let cfill = _theme-state.get().skyll
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "focus")
+ _frame-mark(sid, self, size)
+ let wn = tn.at("width")
+ let cw = W * wn
+ let m = _measure-item("focus-slide", "body", it, cw)
+ let hr = m.outer / H
+ let fit = if hr <= _FILL {
+ (state: "normal", required_height_ratio: hr, gap_ratio: 1.0,
+ margin_deficit_ratio: 0.0, body_overflow_ratio: 0.0)
+ } else if hr <= 1.0 {
+ (state: "tight", required_height_ratio: hr, gap_ratio: 1.0,
+ margin_deficit_ratio: hr - _FILL, body_overflow_ratio: 0.0)
+ } else {
+ (state: "overflow", required_height_ratio: hr, gap_ratio: 1.0,
+ margin_deficit_ratio: 1.0 - _FILL, body_overflow_ratio: hr - 1.0)
+ }
+ let y = if fit.state == "normal" {
+ _clamp(tn.at("center-y") - hr / 2, _MARGIN-TOP,
+ calc.max(1.0 - _MARGIN-BOT - hr, _MARGIN-TOP))
+ } else if fit.state == "tight" { (1.0 - hr) / 2 } else { 0.0 }
+
+ place(_atop + _aleft, dx: (W - cw) / 2, dy: H * y,
+ _render-item(it, cw, m.outer, cfill))
+
+ let objects = (
+ _obj(sid + ":focus", it.kind, it.role,
+ (1.0 - wn) / 2, y, wn, hr,
+ pref-h: hr,
+ pay-w: m.pay-w / W, pay-h: m.pay-h / H,
+ pay-src: m.src, over-x: m.over-x,
+ pad-x: m.pad / W, pad-y: m.pad / H,
+ halign: if _painted(it) { "left" } else { "center" },
+ painted: _painted(it), paint-fill: cfill.to-hex()),
+ )
+ _emit(sid, "focus", "single", here().position().page, 1, objects, (), fit,
+ (tuned: tuning.len() > 0))
+ if debug { _debug-layer(objects, W, H) }
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+// stack-slide — N vertical blocks, fill-first (duo generalized)
+// ---------------------------------------------------------------------------
+//
+// Takes typed items; plain content is coerced to card(). Stretch visuals
+// absorb the free space; a stack with no stretch visual grows its cards so
+// every card reads tall. With `reveal: true` item i appears on subslide i
+// (override per item with `reveal-from`).
+
+#let stack-slide(
+ title: auto,
+ id: auto,
+ items: (),
+ relation: "supports",
+ mode: "balanced",
+ reveal: false,
+ debug: false,
+ tuning: (:),
+) = {
+ _check-mode(mode)
+ _check-relation("stack-slide", relation)
+ let tn = _tuning("stack-slide", tuning, (
+ "width": (default: 0.82, lo: 0.3, hi: 0.95),
+ ))
+ let its = items.enumerate().map(((i, x)) =>
+ _validate-item("stack-slide", "items[" + str(i) + "]", _as-item(x, "card")))
+ let n = its.len()
+ if n == 0 {
+ panic("stack-slide: items is empty")
+ }
+ let steps = _steps("stack-slide", its, reveal, n)
+ let rep = calc.max(..steps, 1)
+ xwysyy-slide(title: title, repeat: rep, self => context {
+ let cfill = _theme-state.get().skyll
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "stack")
+ _frame-mark(sid, self, size)
+ let wn = tn.at("width")
+ let bw = W * wn
+ let slots = its.enumerate().map(((i, it)) =>
+ _item-slot("stack-slide", "items[" + str(i) + "]", it, bw, H))
+ // Grow policy: stretch visuals absorb free space; without one, the
+ // cards share it so a pure-text stack reads as tall cards.
+ let has-stretch = slots.any(s => s.stretch)
+ let specs = slots.enumerate().map(((i, s)) => {
+ if s.stretch { s.spec }
+ else if not has-stretch and its.at(i).kind == "card" {
+ (min: s.spec.min, pref: s.spec.pref, max: none, grow: 1.0)
+ } else { s.spec }
+ })
+ let gap = H * _mode-gap(mode)
+ let alloc = _alloc-column(specs, ((min: gap * _GAP-MIN, pref: gap),) * (n - 1), H)
+
+ let objects = ()
+ let relations = ()
+ let cy = alloc.y0
+ for (i, it) in its.enumerate() {
+ let h = alloc.heights.at(i)
+ let s = slots.at(i)
+ place(_atop + _aleft, dx: (W - bw) / 2, dy: cy,
+ _from(self, steps.at(i), _render-item(it, bw, h, cfill)))
+ let oid = sid + ":" + str(i)
+ objects.push(_obj(oid, it.kind, it.role,
+ (1.0 - wn) / 2, cy / H, wn, h / H,
+ pref-h: specs.at(i).pref / H,
+ pay-w: if s.stretch { none } else { s.pay-w / W },
+ pay-h: if s.stretch { none } else { s.pay-h / H },
+ pay-src: s.src, over-x: s.over-x,
+ pad-x: s.pad / W, pad-y: s.pad / H,
+ halign: if s.painted { "left" } else { "center" },
+ painted: s.painted, paint-fill: cfill.to-hex(),
+ visible-from: steps.at(i)))
+ if i > 0 {
+ relations.push(_rel(sid + ":" + str(i - 1), oid, relation, "vertical", _mode-proximity(mode)))
+ }
+ cy = cy + h + if i < n - 1 { alloc.gaps.at(i) } else { 0pt }
+ }
+ if self.subslide == rep {
+ _emit(sid, "stack", "column", here().position().page, rep, objects, relations,
+ alloc.fit, (mode: mode, tuned: tuning.len() > 0))
+ if debug { _debug-layer(objects, W, H) }
+ }
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+// grid-slide — N equal-height peer columns (N >= 2)
+// ---------------------------------------------------------------------------
+//
+// Columns take typed items; plain content is coerced to card(). Row layouts
+// are sized by their natural height, so stretch visuals are rejected up
+// front. Consecutive columns carry a `peer` relation so the checker
+// validates the gutter.
+
+#let grid-slide(
+ title: auto,
+ id: auto,
+ columns: (),
+ reveal: false,
+ debug: false,
+ tuning: (:),
+) = {
+ let tn = _tuning("grid-slide", tuning, (
+ "gutter": (default: 0.04, lo: 0.0, hi: 0.2),
+ ))
+ let its = columns.enumerate().map(((i, x)) =>
+ _validate-item("grid-slide", "columns[" + str(i) + "]", _as-item(x, "card"),
+ stretch: false))
+ let n = its.len()
+ if n < 2 {
+ panic("grid-slide: needs at least 2 columns (use stack-slide or focus-slide for one block)")
+ }
+ let steps = _steps("grid-slide", its, reveal, n)
+ let rep = calc.max(..steps, 1)
+ xwysyy-slide(title: title, repeat: rep, self => context {
+ let cfill = _theme-state.get().skyll
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "grid")
+ _frame-mark(sid, self, size)
+ let gutter = tn.at("gutter")
+ let cwn = (1.0 - gutter * (n - 1)) / n
+ if cwn <= 0.0 {
+ panic("grid-slide: gutter " + repr(gutter) + " leaves no width for " + repr(n) + " columns")
+ }
+ let cw = W * cwn
+ let slots = its.enumerate().map(((i, it)) =>
+ _item-slot("grid-slide", "columns[" + str(i) + "]", it, cw, H))
+ let naturals = slots.map(s => s.spec.pref)
+ let row = _fit-row(naturals, H)
+
+ let objects = ()
+ let relations = ()
+ for (i, it) in its.enumerate() {
+ let s = slots.at(i)
+ let xn = i * (cwn + gutter)
+ place(_atop + _aleft, dx: W * xn, dy: row.y,
+ _from(self, steps.at(i), _render-item(it, cw, row.row, cfill)))
+ let oid = sid + ":" + str(i)
+ objects.push(_obj(oid, it.kind, it.role,
+ xn, row.y / H, cwn, row.row / H,
+ pref-h: s.spec.pref / H,
+ pay-w: s.pay-w / W, pay-h: s.pay-h / H,
+ pay-src: s.src, over-x: s.over-x,
+ pad-x: s.pad / W, pad-y: s.pad / H,
+ halign: if s.painted { "left" } else { "center" },
+ painted: s.painted, paint-fill: cfill.to-hex(),
+ visible-from: steps.at(i)))
+ if i > 0 {
+ relations.push(_rel(sid + ":" + str(i - 1), oid, "peer", "horizontal", "gutter"))
+ }
+ }
+ if self.subslide == rep {
+ _emit(sid, "grid", "row", here().position().page, rep, objects, relations, row.fit,
+ (tuned: tuning.len() > 0))
+ if debug { _debug-layer(objects, W, H) }
+ }
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+// compare-slide — two equal-height cards read as a contrast
+// ---------------------------------------------------------------------------
+//
+// Cards share one row height; content is TOP-aligned so the two openings sit
+// on the same line, which is how a contrast is read. Both sides are
+// required; plain content is coerced to card(); stretch visuals are rejected
+// up front (row layouts are sized by their natural height).
+
+#let compare-slide(
+ title: auto,
+ id: auto,
+ left: none,
+ right: none,
+ reveal: false,
+ debug: false,
+ tuning: (:),
+) = {
+ _require("compare-slide", "left", left)
+ _require("compare-slide", "right", right)
+ let tn = _tuning("compare-slide", tuning, (
+ "gutter": (default: 0.06, lo: 0.0, hi: 0.2),
+ ))
+ let li = _validate-item("compare-slide", "left", _as-item(left, "card"), stretch: false)
+ let ri = _validate-item("compare-slide", "right", _as-item(right, "card"), stretch: false)
+ let steps = _steps("compare-slide", (li, ri), reveal, 2)
+ let rep = calc.max(..steps, 1)
+ xwysyy-slide(title: title, repeat: rep, self => context {
+ let cfill = _theme-state.get().skyll
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "compare")
+ _frame-mark(sid, self, size)
+ let gutter = tn.at("gutter")
+ let cwn = (1.0 - gutter) / 2
+ let cw = W * cwn
+ let ls = _item-slot("compare-slide", "left", li, cw, H)
+ let rs = _item-slot("compare-slide", "right", ri, cw, H)
+ let row = _fit-row((ls.spec.pref, rs.spec.pref), H)
+
+ place(_atop + _aleft, dx: 0pt, dy: row.y,
+ _from(self, steps.at(0), _render-item(li, cw, row.row, cfill, calign: _atop)))
+ place(_atop + _aleft, dx: W * (cwn + gutter), dy: row.y,
+ _from(self, steps.at(1), _render-item(ri, cw, row.row, cfill, calign: _atop)))
+
+ let objects = (
+ _obj(sid + ":left", li.kind, li.role,
+ 0.0, row.y / H, cwn, row.row / H,
+ pref-h: ls.spec.pref / H,
+ pay-w: ls.pay-w / W, pay-h: ls.pay-h / H,
+ pay-src: ls.src, over-x: ls.over-x,
+ pad-x: ls.pad / W, pad-y: ls.pad / H,
+ calign: "top", halign: if ls.painted { "left" } else { "center" },
+ painted: ls.painted, paint-fill: cfill.to-hex(),
+ visible-from: steps.at(0)),
+ _obj(sid + ":right", ri.kind, ri.role,
+ cwn + gutter, row.y / H, cwn, row.row / H,
+ pref-h: rs.spec.pref / H,
+ pay-w: rs.pay-w / W, pay-h: rs.pay-h / H,
+ pay-src: rs.src, over-x: rs.over-x,
+ pad-x: rs.pad / W, pad-y: rs.pad / H,
+ calign: "top", halign: if rs.painted { "left" } else { "center" },
+ painted: rs.painted, paint-fill: cfill.to-hex(),
+ visible-from: steps.at(1)),
+ )
+ let relations = (
+ _rel(sid + ":left", sid + ":right", "contrast", "horizontal", "gutter"),
+ )
+ if self.subslide == rep {
+ _emit(sid, "compare", "row", here().position().page, rep, objects, relations, row.fit,
+ (tuned: tuning.len() > 0))
+ if debug { _debug-layer(objects, W, H) }
+ }
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+// stat-slide — a row of metric tiles (big value + label)
+// ---------------------------------------------------------------------------
+//
+// Standalone row engine (shares `_fit-row`), so the value auto-shrink scale
+// is measured here and exported in the telemetry. `stats` takes typed
+// `metric(value, label, reveal-from: ...)` entries; the value and the label
+// are measured separately (never through the tile's forced width) and both
+// must render non-empty.
+
+#let metric(value, label, reveal-from: auto) = {
+ if value == none { panic("metric: value is required") }
+ if label == none { panic("metric: label is required") }
+ (xwysyy-metric: true, value: value, label: label, reveal-from: reveal-from)
+}
+
+#let stat-slide(
+ title: auto,
+ id: auto,
+ stats: (),
+ reveal: false,
+ debug: false,
+ tuning: (:),
+) = {
+ let n = stats.len()
+ if n == 0 {
+ panic("stat-slide: stats is empty")
+ }
+ for (i, s) in stats.enumerate() {
+ if type(s) != dictionary or not s.at("xwysyy-metric", default: false) {
+ panic("stat-slide: stats[" + str(i) + "] must be built with metric(value, label)")
+ }
+ if s.at("value", default: none) == none or s.at("label", default: none) == none {
+ panic("stat-slide: stats[" + str(i) + "] must carry both value and label")
+ }
+ }
+ let tn = _tuning("stat-slide", tuning, (
+ "gutter": (default: 0.04, lo: 0.0, hi: 0.2),
+ ))
+ let steps = _steps("stat-slide", stats, reveal, n)
+ let rep = calc.max(..steps, 1)
+ // Both metric fields must render non-empty (an empty string still has line
+ // height, but zero width).
+ let assert-metric(i, s) = {
+ if measure(text(weight: 700, s.value)).width < _EPS-L {
+ panic("stat-slide: stats[" + str(i) + "] value renders empty")
+ }
+ if measure(s.label).width < _EPS-L {
+ panic("stat-slide: stats[" + str(i) + "] label renders empty")
+ }
+ }
+ xwysyy-slide(title: title, repeat: rep, self => context {
+ let t = _theme-state.get()
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "stat")
+ _frame-mark(sid, self, size)
+ for (i, s) in stats.enumerate() { assert-metric(i, s) }
+ let gutter = tn.at("gutter")
+ let cwn = (1.0 - gutter * (n - 1)) / n
+ if cwn <= 0.0 {
+ panic("stat-slide: gutter " + repr(gutter) + " leaves no width for " + repr(n) + " tiles")
+ }
+ let cw = W * cwn
+ let pad = _CARD-PAD.to-absolute()
+ let iw = cw - 2 * pad
+ // Fit each value to its tile: shrink down to the floor scale, below
+ // which the value wraps and the tile grows (reported by fit).
+ let scales = stats.map(s => {
+ let vw = measure(text(size: 2.6em, weight: 700, s.value)).width
+ if vw > iw and vw > 0pt { calc.max(iw / vw, _STAT-SCALE-MIN) } else { 1.0 }
+ })
+ let tile(i) = {
+ let s = stats.at(i)
+ align(center + horizon, stack(
+ spacing: 0.2em,
+ align(center, text(size: 2.6em * scales.at(i), weight: 700, fill: t.sea, s.value)),
+ align(center, text(size: 0.95em, fill: t.sea.lighten(12%), s.label)),
+ ))
+ }
+ // Payload width comes from the value and the label measured
+ // separately, never from the tile's forced full width.
+ let pays = range(n).map(i => {
+ let s = stats.at(i)
+ let vstyled = text(size: 2.6em * scales.at(i), weight: 700, s.value)
+ let vm = measure(vstyled)
+ let lm = measure(text(size: 0.95em, s.label))
+ let vc = measure(block(width: iw, vstyled))
+ (
+ w: calc.max(vm.width, lm.width),
+ h: measure(block(width: iw, tile(i))).height,
+ over-x: _overflows-x(vm, vc, iw),
+ )
+ })
+ let naturals = pays.map(p => p.h + 2 * pad)
+ let row = _fit-row(naturals, H)
+
+ let objects = ()
+ let relations = ()
+ for i in range(n) {
+ let xn = i * (cwn + gutter)
+ place(_atop + _aleft, dx: W * xn, dy: row.y,
+ _from(self, steps.at(i),
+ block(width: cw, height: row.row, fill: t.skyll, inset: _CARD-PAD,
+ radius: 0.4em, align(horizon, tile(i)))))
+ let oid = sid + ":" + str(i)
+ objects.push(_obj(oid, "card", "metric",
+ xn, row.y / H, cwn, row.row / H,
+ pref-h: naturals.at(i) / H,
+ pay-w: calc.min(pays.at(i).w, iw) / W, pay-h: pays.at(i).h / H,
+ pay-src: "measured", over-x: pays.at(i).over-x,
+ pad-x: pad / W, pad-y: pad / H,
+ painted: true, paint-fill: t.skyll.to-hex(),
+ visible-from: steps.at(i)))
+ if i > 0 {
+ relations.push(_rel(sid + ":" + str(i - 1), oid, "peer", "horizontal", "gutter"))
+ }
+ }
+ if self.subslide == rep {
+ _emit(sid, "stat", "row", here().position().page, rep, objects, relations, row.fit, (
+ value_scales: scales,
+ tuned: tuning.len() > 0,
+ ))
+ if debug { _debug-layer(objects, W, H) }
+ }
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+// figure-slide — figure, tight caption, optional takeaway
+// ---------------------------------------------------------------------------
+//
+// The caption is measured first and subtracted, then the figure slot receives
+// an explicit height, so a stretch figure fills its slot instead of escaping
+// the wrapper (Typst measures percentage heights as 0 in an unbounded
+// context). `fig` coerces plain content to visual(fit: "natural"); wrap it
+// in visual() to make it fill. `takeaway` coerces to a painted takeaway card
+// and rejects stretch. Reveal goes through the shared resolver: with
+// `reveal: true` the figure shows first and the takeaway follows; per-item
+// `reveal-from` overrides that order, and the caption always follows the
+// figure.
+
+#let figure-slide(
+ title: auto,
+ id: auto,
+ fig: none,
+ caption: none,
+ takeaway: none,
+ mode: "balanced",
+ reveal: false,
+ debug: false,
+ tuning: (:),
+) = {
+ _check-mode(mode)
+ _require("figure-slide", "fig", fig)
+ let tn = _tuning("figure-slide", tuning, (
+ "figure-width": (default: 0.80, lo: 0.3, hi: 0.95),
+ "takeaway-width": (default: 0.74, lo: 0.3, hi: 0.95),
+ ))
+ let fi = _validate-item("figure-slide", "fig", _as-item(fig, "visual"))
+ let ki = if takeaway == none { none } else {
+ _validate-item("figure-slide", "takeaway", _as-item(takeaway, "takeaway"), stretch: false)
+ }
+ let items = if ki == none { (fi,) } else { (fi, ki) }
+ let steps = _steps("figure-slide", items, reveal, items.len())
+ let fig-step = steps.at(0)
+ let tk-step = if ki != none { steps.at(1) } else { 1 }
+ let rep = calc.max(..steps, 1)
+ let cap = if caption == none { none } else {
+ context text(size: 0.85em, fill: _theme-state.get().sea.lighten(12%), style: "italic", caption)
+ }
+ xwysyy-slide(title: title, repeat: rep, self => context {
+ let cfill = _theme-state.get().skyll
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "figure")
+ _frame-mark(sid, self, size)
+ let fwn = tn.at("figure-width")
+ let twn = tn.at("takeaway-width")
+ let fw = W * fwn
+ let tw = W * twn
+ let cap-gap = 0.5em.to-absolute()
+ let mode-gap = H * _mode-gap(mode)
+
+ let fs = _item-slot("figure-slide", "fig", fi, fw, H)
+ let specs = (fs.spec,)
+ let gaps = ()
+ let cap-m = if cap != none {
+ let m = measure(block(width: fw, cap))
+ specs.push((min: m.height, pref: m.height, max: m.height, grow: 0.0))
+ // The caption gap is tight by design and not compressible.
+ gaps.push((min: cap-gap, pref: cap-gap))
+ m
+ } else { none }
+ let ks = if ki != none {
+ let s = _item-slot("figure-slide", "takeaway", ki, tw, H)
+ specs.push(s.spec)
+ gaps.push((min: mode-gap * _GAP-MIN, pref: mode-gap))
+ s
+ } else { none }
+ let alloc = _alloc-column(specs, gaps, H)
+
+ let objects = ()
+ let relations = ()
+ let cy = alloc.y0
+ let idx = 0
+ let fh = alloc.heights.at(idx)
+ place(_atop + _aleft, dx: (W - fw) / 2, dy: cy,
+ _from(self, fig-step, _render-item(fi, fw, fh, cfill)))
+ objects.push(_obj(sid + ":figure", fi.kind, fi.role,
+ (1.0 - fwn) / 2, cy / H, fwn, fh / H,
+ pref-h: fs.spec.pref / H,
+ pay-w: if fs.stretch { none } else { fs.pay-w / W },
+ pay-h: if fs.stretch { none } else { fs.pay-h / H },
+ pay-src: fs.src, over-x: fs.over-x,
+ pad-x: fs.pad / W, pad-y: fs.pad / H,
+ painted: fs.painted, paint-fill: cfill.to-hex(),
+ visible-from: fig-step))
+ cy = cy + fh
+ idx += 1
+ if cap != none {
+ cy = cy + alloc.gaps.at(idx - 1)
+ let ch = alloc.heights.at(idx)
+ place(_atop + _aleft, dx: (W - fw) / 2, dy: cy,
+ _from(self, fig-step, block(width: fw, align(center, cap))))
+ let cm = measure(cap)
+ objects.push(_obj(sid + ":caption", "plain", "caption",
+ (1.0 - fwn) / 2, cy / H, fwn, ch / H,
+ pref-h: ch / H,
+ pay-w: calc.min(cm.width, fw) / W, pay-h: cap-m.height / H,
+ pay-src: "measured",
+ over-x: _overflows-x(cm, cap-m, fw),
+ visible-from: fig-step))
+ relations.push(_rel(sid + ":figure", sid + ":caption", "caption", "vertical", "tight"))
+ cy = cy + ch
+ idx += 1
+ }
+ if ki != none {
+ cy = cy + alloc.gaps.at(idx - 1)
+ let th = alloc.heights.at(idx)
+ place(_atop + _aleft, dx: (W - tw) / 2, dy: cy,
+ _from(self, tk-step, _render-item(ki, tw, th, cfill)))
+ let anchor = if cap != none { sid + ":caption" } else { sid + ":figure" }
+ objects.push(_obj(sid + ":takeaway", ki.kind, ki.role,
+ (1.0 - twn) / 2, cy / H, twn, th / H,
+ pref-h: ks.spec.pref / H,
+ pay-w: ks.pay-w / W, pay-h: ks.pay-h / H,
+ pay-src: ks.src, over-x: ks.over-x,
+ pad-x: ks.pad / W, pad-y: ks.pad / H,
+ halign: if ks.painted { "left" } else { "center" },
+ painted: ks.painted, paint-fill: cfill.to-hex(),
+ visible-from: tk-step))
+ relations.push(_rel(anchor, sid + ":takeaway", "supports", "vertical", _mode-proximity(mode)))
+ }
+ if self.subslide == rep {
+ _emit(sid, "figure", "column", here().position().page, rep, objects, relations,
+ alloc.fit, (mode: mode, tuned: tuning.len() > 0))
+ if debug { _debug-layer(objects, W, H) }
+ }
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+// sidebar-slide — a narrow label tab beside a wide content card
+// ---------------------------------------------------------------------------
+//
+// No `reveal`: the label and its content have no presentation order. Both
+// slots take plain content only (the component paints its own boxes; do not
+// wrap the body in `textbox` or pass typed items).
+
+#let sidebar-slide(
+ title: auto,
+ id: auto,
+ label: none,
+ body: none,
+ debug: false,
+ tuning: (:),
+) = {
+ _require("sidebar-slide", "label", label)
+ _require("sidebar-slide", "body", body)
+ if type(label) == dictionary or type(body) == dictionary {
+ panic("sidebar-slide: label and body take plain content, not typed items")
+ }
+ let tn = _tuning("sidebar-slide", tuning, (
+ "label-width": (default: 0.26, lo: 0.1, hi: 0.5),
+ "gutter": (default: 0.04, lo: 0.0, hi: 0.2),
+ ))
+ // Both slots are text cards: spacers, empty strings, and bare rules panic.
+ let assert-slot(slot, body, w) = {
+ let mu = measure(body)
+ let mc = measure(block(width: w, body))
+ if mu.width < _EPS-L {
+ panic("sidebar-slide: " + slot + " has no measurable width — spacers and "
+ + "empty strings are not content")
+ }
+ if mc.height < _EPS-L {
+ panic("sidebar-slide: " + slot + " has no measurable height — a bare rule "
+ + "is decoration, not content")
+ }
+ (mu: mu, mc: mc)
+ }
+ xwysyy-slide(title: title, self => context {
+ let t = _theme-state.get()
+ layout(size => {
+ let W = size.width
+ let H = size.height
+ let sid = _sid(id, "sidebar")
+ _frame-mark(sid, self, size)
+ let lwn = tn.at("label-width")
+ let gutter = tn.at("gutter")
+ let bwn = 1.0 - lwn - gutter
+ let lw = W * lwn
+ let bw = W * bwn
+ let pad = _CARD-PAD.to-absolute()
+ // The label is measured with the same styled content it is rendered
+ // with (bold via `set text`, not `strong`, whose show rule enlarges
+ // the run and would make the measurement disagree with the render).
+ let label-inner = {
+ set text(weight: "bold")
+ show raw: set text(fill: t.sea)
+ label
+ }
+ let lmm = assert-slot("label", label-inner, lw - 2 * pad)
+ let bmm = assert-slot("body", body, bw - 2 * pad)
+ let lh = lmm.mc.height + 2 * pad
+ let bh = bmm.mc.height + 2 * pad
+ let row = _fit-row((lh, bh), H)
+
+ place(_atop + _aleft, dx: 0pt, dy: row.y,
+ block(width: lw, height: row.row, fill: t.sea, inset: pad, radius: 0.4em,
+ align(horizon + _aleft, {
+ // Label text is light (paper) on the dark sea tab; inline code
+ // keeps its light chip but takes dark (sea) text so it stays
+ // readable instead of light-on-light.
+ set text(fill: t.paper)
+ label-inner
+ })))
+ place(_atop + _aleft, dx: W * (lwn + gutter), dy: row.y,
+ block(width: bw, height: row.row, fill: t.skyll, inset: pad, radius: 0.4em,
+ align(horizon, body)))
+
+ let objects = (
+ _obj(sid + ":label", "card", "label",
+ 0.0, row.y / H, lwn, row.row / H,
+ pref-h: lh / H,
+ pay-w: calc.min(lmm.mu.width, lw - 2 * pad) / W,
+ pay-h: lmm.mc.height / H,
+ pay-src: "measured",
+ over-x: _overflows-x(lmm.mu, lmm.mc, lw - 2 * pad),
+ pad-x: pad / W, pad-y: pad / H,
+ halign: "left", painted: true, paint-fill: t.sea.to-hex()),
+ _obj(sid + ":body", "card", "content",
+ lwn + gutter, row.y / H, bwn, row.row / H,
+ pref-h: bh / H,
+ pay-w: calc.min(bmm.mu.width, bw - 2 * pad) / W,
+ pay-h: bmm.mc.height / H,
+ pay-src: "measured",
+ over-x: _overflows-x(bmm.mu, bmm.mc, bw - 2 * pad),
+ pad-x: pad / W, pad-y: pad / H,
+ halign: "left", painted: true, paint-fill: t.skyll.to-hex()),
+ )
+ let relations = (_rel(sid + ":label", sid + ":body", "labels", "horizontal", "gutter"),)
+ _emit(sid, "sidebar", "row", here().position().page, 1, objects, relations, row.fit,
+ (tuned: tuning.len() > 0))
+ if debug { _debug-layer(objects, W, H) }
+ })
+ })
+}
diff --git a/packages/preview/xwysyy/0.5.0/src/slides.typ b/packages/preview/xwysyy/0.5.0/src/slides.typ
new file mode 100644
index 0000000000..88234f0869
--- /dev/null
+++ b/packages/preview/xwysyy/0.5.0/src/slides.typ
@@ -0,0 +1,359 @@
+// Slides: touying-based slide variants and the xwysyy-pre entry.
+
+#import "@preview/touying:0.8.0": *
+#import "themes.typ": *
+#import "elements.typ": *
+
+#let _auto-outline-title(title) = {
+ if title == auto {
+ context {
+ if text.lang == "zh" { [目录] } else { [Contents] }
+ }
+ } else {
+ title
+ }
+}
+
+// Per-page manifest: every slide layout declares its kind and physical page,
+// so the layout checker can compute telemetry coverage (which content pages
+// carry no layout telemetry) and exempt title/section/end/image/outline pages.
+#let _page-manifest(kind) = context [
+ #metadata((schema: "xwysyy-page/v1", kind: kind, page: here().position().page))
+]
+
+// Checker-exempt page kinds (title / section / end / image / outline) are
+// emitted by their own slide functions only: the public `xwysyy-slide` has no
+// kind parameter, so a content page cannot spoof an exemption.
+#let _kinded-slide(kind, title: auto, ..args) = {
+ touying-slide-wrapper(self => {
+ if title != auto {
+ self.store.title = title
+ }
+ // Open header (ported from the poster's section header): sans title in
+ // the theme color over a thin gradient rule fading to the right. The
+ // title shrinks to fit one line (floor 0.65×): the page's top margin is
+ // fixed, so a wrapped title would collide with the rule and the body.
+ // The applied scale is exported as `` telemetry; a title
+ // still wider than the header at the floor scale reports fits: false.
+ let header(self) = {
+ set align(top)
+ block(
+ width: 100% + 2em,
+ inset: (x: 1em, top: 1.1em),
+ {
+ context {
+ let avail = page.width - 2em.to-absolute()
+ let title-body = if self.store.title != none {
+ utils.call-or-display(self, self.store.title)
+ } else {
+ utils.display-current-heading(level: 2)
+ }
+ let styled(s) = text(
+ font: self.store.heading-font,
+ fill: self.colors.neutral-dark,
+ weight: "bold",
+ size: 1.45em * s,
+ title-body,
+ )
+ let natural = measure(box(styled(1.0))).width
+ let scale = if natural > avail and natural > 0pt {
+ calc.max(avail / natural, 0.65)
+ } else { 1.0 }
+ let hsize = measure(box(styled(scale)))
+ // Vertical fit: top inset + title + gap + rule must stay inside
+ // the fixed top margin, or an explicitly wrapped / oversized
+ // title collides with the rule and the body.
+ let used = (1.1em + 0.65em + 0.12em).to-absolute() + hsize.height
+ [#metadata((
+ schema: "xwysyy-header/v2",
+ page: here().position().page,
+ scale: scale,
+ fits: natural * scale <= avail + 0.01pt,
+ height: hsize.height.pt(),
+ fits_v: used <= 4.35em.to-absolute() + 0.01pt,
+ )) ]
+ block(styled(scale))
+ }
+ v(0.65em, weak: true)
+ // Full-width track whose ink fades out completely by 92% — the
+ // tail dies before the page edge instead of running into it.
+ rect(
+ width: 100%,
+ height: 0.12em,
+ radius: (left: 0.06em),
+ fill: gradient.linear(
+ (self.colors.neutral-dark, 0%),
+ (self.colors.primary, 42%),
+ (self.colors.primary.transparentize(100%), 92%),
+ (self.colors.primary.transparentize(100%), 100%),
+ ),
+ )
+ },
+ )
+ }
+ // Footer: page number in the bottom-right corner only.
+ let footer(self) = {
+ set align(bottom + right)
+ set text(fill: self.colors.neutral-dark, size: .9em)
+ block(
+ inset: (x: 0.5em, bottom: 0.4em),
+ context utils.slide-counter.display(),
+ )
+ }
+ self = utils.merge-dicts(
+ self,
+ config-page(
+ header: header,
+ footer: footer,
+ ),
+ )
+ // Prepend the page manifest to the slide body (works for both plain
+ // content and callback-style `self => ...` bodies).
+ let bodies = args.pos()
+ let raw-body = if bodies.len() > 0 { bodies.first() } else { [] }
+ let tagged = if type(raw-body) == function {
+ s => { _page-manifest(kind); raw-body(s) }
+ } else {
+ { _page-manifest(kind); raw-body }
+ }
+ touying-slide(self: self, ..args.named(), tagged, ..bodies.slice(1))
+ })
+}
+
+#let xwysyy-slide(title: auto, ..args) = {
+ if "kind" in args.named() {
+ panic("xwysyy-slide: kind is not a parameter; outline pages use outline-slide, "
+ + "full-bleed pages use title-slide / new-section-slide / end-slide / image-slide")
+ }
+ _kinded-slide("content", title: title, ..args)
+}
+
+#let title-slide(..args) = {
+ touying-slide-wrapper(self => {
+ self = utils.merge-dicts(
+ self,
+ config-page(margin: 0em),
+ )
+ self.info = self.info + args.named()
+ let info = self.info
+ let body = {
+ _page-manifest("title")
+ set align(center + horizon)
+ line(length: 100%, stroke: self.colors.neutral-dark)
+ v(-0.9em)
+ block(
+ fill: self.colors.neutral-dark,
+ width: 100%,
+ inset: (y: 1.2em),
+ text(size: 2.2em, fill: self.colors.neutral-lightest, weight: "bold", info.title),
+ )
+ if info.subtitle != none {
+ v(-0.9em)
+ block(
+ fill: self.colors.neutral-lighter,
+ width: 100%,
+ {
+ line(length: 100%, stroke: self.colors.neutral-dark)
+ v(-0.65em)
+ text(size: 1.6em, fill: self.colors.neutral-dark, weight: "bold", info.subtitle)
+ v(-0.65em)
+ line(length: 100%, stroke: self.colors.neutral-dark)
+ }
+ )
+ }
+ set text(fill: self.colors.neutral-darkest)
+ v(1em)
+ if info.author != none {
+ block(text(size: 1.1em, info.author))
+ }
+ if info.institution != none {
+ block(text(size: 0.9em, style: "italic", info.institution))
+ }
+ if info.date != none {
+ block(utils.display-info-date(self))
+ }
+ }
+ touying-slide(self: self, body)
+ })
+}
+
+#let new-section-slide(body) = {
+ touying-slide-wrapper(self => {
+ self = utils.merge-dicts(
+ self,
+ config-page(
+ margin: 0em,
+ ),
+ )
+ let main-body = {
+ _page-manifest("section")
+ set align(center + horizon)
+ set text(size: 2em, fill: self.colors.neutral-dark, weight: "bold", style: "italic")
+ line(start: (17%, 0em), length: 83%, stroke: self.colors.neutral-dark)
+ v(-0.97em)
+ block(
+ fill: self.colors.neutral-lighter,
+ inset: (y: 0.42em),
+ width: 100%,
+ utils.display-current-heading(level: 1)
+ )
+ v(-0.97em)
+ line(start: (-17%, 0em), length: 83%, stroke: self.colors.neutral-dark)
+ }
+ touying-slide(self: self, main-body)
+ })
+}
+
+#let end-slide(title: [Thank You!], body: none) = {
+ touying-slide-wrapper(self => {
+ self = utils.merge-dicts(self, config-page(margin: 2em))
+ let main-body = {
+ _page-manifest("end")
+ set align(center + horizon)
+ text(fill: self.colors.neutral-dark, size: 3em, weight: "bold", title)
+ if body != none {
+ v(0.5em)
+ line(length: 20%, stroke: (thickness: 1.5pt, paint: self.colors.primary))
+ v(0.5em)
+ text(size: 1.3em, fill: self.colors.neutral-dark.lighten(30%), body)
+ }
+ }
+ touying-slide(self: self, main-body)
+ })
+}
+
+#let outline-slide(chapters: auto, title: auto) = {
+ _kinded-slide("outline", title: _auto-outline-title(title))[
+ #context {
+ let t = _theme-state.get()
+ let chs = if chapters == auto {
+ query(heading.where(level: 1))
+ .filter(h => not h.has("label") or str(h.label) != "touying:hidden")
+ .map(h => h.body)
+ } else {
+ chapters
+ }
+ let two-col = chs.len() > 5
+ let bsz = if two-col { 2em } else { 2.6em }
+ let tsz = if two-col { 1.4em } else { 2em }
+ let badge(n) = box(
+ width: bsz, height: bsz, fill: t.sea, radius: 50%,
+ align(center + horizon, text(fill: t.paper, weight: "bold", size: bsz * 0.58, n))
+ )
+ let items = chs.enumerate().map(((i, ch)) => grid(
+ columns: (auto, 1fr),
+ column-gutter: if two-col { 1em } else { 1.4em },
+ align: (center + horizon, left + horizon),
+ badge(numbering("01", i + 1)),
+ text(size: tsz, weight: "semibold", ch),
+ ))
+ if two-col {
+ let mid = calc.ceil(items.len() / 2)
+ v(0.5em)
+ grid(
+ columns: (1fr, 1fr),
+ gutter: 1.5em,
+ stack(spacing: 1.6em, ..items.slice(0, mid)),
+ stack(spacing: 1.6em, ..items.slice(mid)),
+ )
+ } else {
+ v(1em)
+ stack(spacing: 2.4em, ..items)
+ }
+ }
+ ]
+}
+
+#let image-slide(body: none, img: none) = {
+ // An image slide without an image is a spoofed full-bleed exemption: the
+ // page would carry arbitrary body content while the checker skips it.
+ if img == none {
+ panic("image-slide: img is required; use xwysyy-slide for text-only pages")
+ }
+ touying-slide-wrapper(self => {
+ self = utils.merge-dicts(
+ self,
+ config-page(
+ background: img,
+ margin: (x: 0em, y: 1.4em),
+ ),
+ )
+ set text(fill: self.colors.neutral-dark, size: 1.4em)
+ set image(width: 100%, height: auto)
+ touying-slide(self: self, {
+ _page-manifest("image")
+ align(left + bottom,
+ align(
+ center,
+ block(
+ fill: self.colors.neutral-lighter,
+ if body != none {
+ line(length: 100%, stroke: self.colors.neutral-dark)
+ v(-0.85em)
+ body
+ v(-0.85em)
+ line(length: 100%, stroke: self.colors.neutral-dark)
+ }
+ )
+ )
+ )
+ })
+ })
+}
+
+#let xwysyy-pre(
+ aspect-ratio: "16-9",
+ theme: "sky",
+ font: ("Times New Roman", "Noto Serif CJK SC"),
+ heading-font: ("Libertinus Sans", "Noto Sans CJK SC"),
+ code-font: ("Maple Mono", "Noto Sans Mono CJK SC"),
+ lang: "en",
+ ..args,
+ body,
+) = {
+ let t = _resolve-theme(theme)
+ _theme-state.update(t)
+ set text(
+ font: font,
+ lang: lang,
+ size: 5.5mm,
+ weight: "semibold",
+ style: "normal",
+ )
+ show: touying-slides.with(
+ config-page(
+ paper: "presentation-" + aspect-ratio,
+ margin: (top: 4.35em, x: 1em, bottom: 1.4em)
+ ),
+ config-common(
+ slide-fn: xwysyy-slide,
+ new-section-slide-fn: new-section-slide,
+ frozen-counters: (counter(figure), counter(math.equation)),
+ ),
+ config-colors(
+ primary: t.sky,
+ neutral-lighter: t.skyll,
+ neutral-lightest: t.paper,
+ neutral-dark: t.sea,
+ neutral-darkest: black,
+ ),
+ config-store(
+ title: none,
+ heading-font: heading-font,
+ ),
+ config-page(
+ fill: t.page-fill,
+ ),
+ ..args,
+ )
+
+ show: xwysyy-elements.with(
+ code-font: code-font,
+ t-sea: t.sea,
+ t-sky: t.sky,
+ t-skyll: t.skyll,
+ t-paper: t.paper,
+ )
+
+ body
+}
diff --git a/packages/preview/xwysyy/0.5.0/src/themes.typ b/packages/preview/xwysyy/0.5.0/src/themes.typ
new file mode 100644
index 0000000000..dd1c300639
--- /dev/null
+++ b/packages/preview/xwysyy/0.5.0/src/themes.typ
@@ -0,0 +1,95 @@
+// Theme color palettes, shared theme state, and inline highlight macros.
+
+#let themes = (
+ sky: (
+ sea: rgb("#3b60a0"),
+ sky: rgb("#bdd0f1"),
+ skyll: rgb("#f4f9ff"),
+ paper: rgb("#f5f6f8"),
+ page-fill: white,
+ ),
+ sunset: (
+ sea: rgb("#970014"),
+ sky: rgb("#D8A6A2"),
+ skyll: rgb("#FFF8F6"),
+ paper: rgb("#f5f6f8"),
+ page-fill: rgb("#fffefd"),
+ ),
+ forest: (
+ sea: rgb("#1f5d45"),
+ sky: rgb("#a8d5ba"),
+ skyll: rgb("#f5fbf7"),
+ paper: rgb("#f7faf8"),
+ page-fill: white,
+ ),
+ midnight: (
+ sea: rgb("#1f2a44"),
+ sky: rgb("#8fa8d8"),
+ skyll: rgb("#f7faff"),
+ paper: rgb("#f7f8fb"),
+ page-fill: white,
+ ),
+ violet: (
+ sea: rgb("#5a3e85"),
+ sky: rgb("#c7b7e8"),
+ skyll: rgb("#faf7ff"),
+ paper: rgb("#f8f6fb"),
+ page-fill: white,
+ ),
+ graphite: (
+ sea: rgb("#31343a"),
+ sky: rgb("#b9c0c9"),
+ skyll: rgb("#f8f9fa"),
+ paper: rgb("#f7f7f5"),
+ page-fill: white,
+ ),
+)
+
+#let _theme-required-fields = (
+ "sea",
+ "sky",
+ "skyll",
+ "paper",
+ "page-fill",
+)
+
+#let _resolve-theme(theme) = {
+ if type(theme) == str {
+ themes.at(theme)
+ } else if type(theme) == dictionary {
+ for field in _theme-required-fields {
+ if theme.at(field, default: auto) == auto {
+ panic("xwysyy-pre theme dictionary is missing field `" + field + "`")
+ }
+ }
+ theme
+ } else {
+ panic("xwysyy-pre theme must be a string name or a dictionary")
+ }
+}
+
+// Default theme colors (sky)
+#let sea = themes.sky.sea
+#let sky = themes.sky.sky
+#let skyll = themes.sky.skyll
+#let paper = themes.sky.paper
+
+// Theme state for dynamic components
+#let _theme-state = state("xwysyy-theme", themes.sky)
+
+// Bold recipe shared by `strong` and the colored bold macros: 1.1em + true
+// bold weight, no stroke (stroking fills the CJK glyph counters and looks
+// muddy). 0.03em tracking opens the run internally; 0.05em hair spacing on
+// both sides keeps it off the neighbors (non-weak: weak spacing would swallow
+// adjacent Latin word spaces); the 0.035em baseline drop re-centers the
+// enlarged CJK glyphs optically.
+#let _bold-run(body, fill: none) = {
+ set text(fill: fill) if fill != none
+ h(0.05em)
+ text(size: 1.1em, weight: 700, tracking: 0.03em, baseline: 0.035em, body)
+ h(0.05em)
+}
+#let red(body) = text(fill: rgb("#9c1d11"), body)
+#let bred(body) = _bold-run(body, fill: rgb("#9c1d11"))
+#let yellow(body) = text(fill: rgb("#d9ad20"), body)
+#let byellow(body) = _bold-run(body, fill: rgb("#d9ad20"))
diff --git a/packages/preview/xwysyy/0.5.0/template/main.typ b/packages/preview/xwysyy/0.5.0/template/main.typ
new file mode 100644
index 0000000000..dcee4c28cc
--- /dev/null
+++ b/packages/preview/xwysyy/0.5.0/template/main.typ
@@ -0,0 +1,37 @@
+#import "@preview/xwysyy:0.5.0": *
+
+#show: xwysyy-pre.with(
+ theme: "sky",
+ config-info(
+ title: [xwysyy Starter Deck],
+ subtitle: [Academic slides in Typst],
+ author: " ",
+ date: datetime.today(),
+ institution: " ",
+ ),
+)
+
+#title-slide()
+
+#outline-slide()
+
+= Motivation
+
+== One Minute Setup
+
+Use `typst init @preview/xwysyy:0.5.0` to create this deck, then edit `main.typ`.
+
+#textbox(
+ [*Reusable components*
+
+ `textbox`, #red[red highlights], #yellow[yellow highlights], tables, code blocks, and touying animations share one theme.],
+
+ [*Theme control*
+
+ Switch built-in themes with `theme: "sunset"` or pass a custom color dictionary directly.],
+)
+
+#end-slide(
+ title: [Thank You!],
+ body: [Questions?],
+)
diff --git a/packages/preview/xwysyy/0.5.0/thumbnail.png b/packages/preview/xwysyy/0.5.0/thumbnail.png
new file mode 100644
index 0000000000000000000000000000000000000000..3675e91a2aa84f97aa1506a902cc5f1a9a9347e2
GIT binary patch
literal 188834
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