diff --git a/packages/preview/sorbonne-presentation/0.1.0/README.md b/packages/preview/sorbonne-presentation/0.1.0/README.md new file mode 100644 index 0000000000..51a3596e5e --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/README.md @@ -0,0 +1,152 @@ +# Sorbonne Presentation Theme + +A non-official structured presentation theme for **Sorbonne University**, built on top of the [presentate](https://typst.app/universe/package/presentate) and [navigator](https://typst.app/universe/package/navigator) packages. + +--- + +## Overview + +The theme provides academic and institutional slide decks that respect the visual identity of Sorbonne University while offering powerful dynamic features. + +- **Faculty Presets**: Built-in colors and logos for Health, Science, Humanities, and University-wide presentations. +- **Smart Navigation**: Automatic breadcrumbs, transition slides with roadmaps, and flexible hierarchy mapping. +- **Dynamic Content**: Seamless integration of `pause`, `uncover`, and `only` for step-by-step reveals. +- **Scientific Ready**: Specialized slides for equations, figures, and algorithms. + +### 🎨 Faculty Presets +Switch visual identities instantly using the `faculty` parameter: + +| Univ | Sante | Sciences | Lettres | +|:---:|:---:|:---:|:---:| +| ![Univ](assets/docs/faculty-univ.png) | ![Sante](assets/docs/faculty-sante.png) | ![Sciences](assets/docs/faculty-sciences.png) | ![Lettres](assets/docs/faculty-lettres.png) | + +- `univ`: Sorbonne Blue (University-wide) +- `sante`: Sorbonne Red (Faculty of Health) +- `sciences`: Sorbonne Light Blue (Faculty of Science & Engineering) +- `lettres`: Sorbonne Yellow/Ocre (Faculty of Humanities) + +## Documentation + +For a comprehensive visual tour of all components and features, please refer to the pre-compiled PDF documentation: + +- **[Main Demo Guide](examples/demo.typ)** (⇒ [pdf results](https://github.com/eusebe/typst-sorbonne-presentation/blob/0.1.0/examples/demo.pdf)): All components, boxes, and slide types. +- **[2-Levels Mapping Guide](examples/demo-mapping-2levels.typ)** (⇒ [pdf results](https://github.com/eusebe/typst-sorbonne-presentation/blob/0.1.0/examples/demo-mapping-2levels.pdf)): Using Section/Subsection hierarchy. +- **[3-Levels Mapping Guide](examples/demo-mapping-3levels.typ)** (⇒ [pdf results](https://github.com/eusebe/typst-sorbonne-presentation/blob/0.1.0/examples/demo-mapping-3levels.pdf)): Using Part/Section/Subsection hierarchy. + +## Quick Start + +```typ +#import "@preview/sorbonne-presentation:0.1.0": * + +#show: template.with( + title: [Scientific Discovery], + author: [John Doe], + faculty: "sciences", + show-outline: true, +) + += Introduction +#slide[ + - High performance + - Intuitive syntax + #show: pause + - *Dynamic* animations +] + +#ending-slide() +``` + +## Configuration Reference + +### The `template` function + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `title` | content | `none` | Main presentation title | +| `short-title` | content | `none` | Short version of title for footer | +| `subtitle` | content | `none` | Optional subtitle | +| `author` | content | `none` | Presenter's name | +| `short-author` | content | `none` | Short version of author for footer | +| `affiliation` | content | `none` | Department or Laboratory | +| `date` | content | `datetime...` | Custom date display | +| `faculty` | string | `"sante"` | Preset: `"sante"`, `"sciences"`, `"lettres"`, `"univ"` | +| `primary-color` | color | `none` | Manual override for theme color | +| `alert-color` | color | `none` | Manual override for alert text color | +| `logo-slide` | string | `none` | Path to custom logo for content slides | +| `logo-transition` | string | `none` | Path to custom logo for transition slides | +| `text-font` | string | `"Fira Sans"` | Main font family | +| `text-size` | length | `20pt` | Base text size | +| `aspect-ratio` | string | `"16-9"` | `"16-9"` or `"4-3"` | +| `show-outline` | bool | `false` | Toggle summary slide | +| `outline-title` | content | `[Sommaire]` | Title of the summary slide | +| `outline-depth` | int | `2` | Levels shown in summary | +| `outline-columns` | int | `1` | Number of columns for summary | +| `mapping` | dict | `(sec: 1, sub: 2)` | Logic mapping for headings | +| `auto-title` | bool | `true` | Use section name as slide title if none provided | +| `show-header-numbering` | bool | `true` | Toggle all heading numbers | +| `numbering-format` | string | `"1.1"` | Format for sections and subsections | +| `part-numbering-format` | string | `"I"` | Format for parts | +| `annex-title` | content | `[Annexe]` | Prefix for single appendix | +| `annex-main-title` | content | `[Annexes]` | Focus slide text for appendix start | +| `annex-numbering-format` | string | `"I"` | Numbering style for appendices | +| `bib-style` | string | `"apa"` | Bibliography and citation style | +| `progress-bar` | string | `"none"` | Position: `"none"`, `"top"`, or `"bottom"` | +| `slide-break-suffix` | content | `[ (cont.)]` | Suffix appended to titles on broken slides | +| `footer-author` | bool | `true` | Toggle author display in footer | +| `footer-title` | bool | `true` | Toggle title display in footer | +| `max-length` | int \| dict | `none` | Max length for breadcrumb titles before truncation | + +## Component Reference + +### Slide Types +- `#slide(title: none, subtitle: none, allow-slide-breaks: false, background: none, body)`: Standard content slide. + - `allow-slide-breaks`: If `true`, allows content to overflow onto multiple slides. A suffix (defined by `slide-break-suffix`) is automatically appended to the title from the second page. *Note: This feature is incompatible with dynamic animations like `#pause`.* + - `background`: Optional content (e.g., an image) to display behind the slide content. + ![Slide](assets/docs/component-slide.png) +- `#slide-break()`: Manually forces a page break within a slide. *Note: Only works when `allow-slide-breaks: true` is set on the `#slide()`.* +- `#focus-slide(body, subtitle: none)`: Highlight slide on solid theme background. + ![Focus Slide](assets/docs/component-focus-slide.png) +- `#figure-slide(fig, title: none, subtitle: none, caption: none, ..)`: Centered figure slide. + ![Figure Slide](assets/docs/component-figure-slide.png) +- `#equation-slide(equation, title: none, subtitle: none, definitions: none, citation: none, ..)`: Large equation with "signature" citation. + ![Equation Slide](assets/docs/component-equation-slide.png) +- `#acknowledgement-slide(title: none, subtitle: none, people: (), institutions: (), ..)`: Thank-you slide. + ![Acknowledgement Slide](assets/docs/component-acknowledgement-slide.png) +- `#ending-slide(title: none, subtitle: none, contact: ())`: Closing slide with contact information. + ![Ending Slide](assets/docs/component-ending-slide.png) + +### Text Helpers +- `#alert[text]`: Highlighted bold text. +- `#muted[text]`: Gray secondary text. +- `#subtle[text]`: Light gray tertiary text. + +![Text Helpers](assets/docs/helper-text.png) + +### Citations & References +- Inline and corner citations. + `#cite-box("smith2023", position: "bottom-right")` + ![Citations](assets/docs/component-citation.png) + +### Layout & Boxes +- `#two-col(left, right, columns: (1fr, 1fr), gutter: 2em)`: Balanced columns. + ![Layout 2-col](assets/docs/layout-2col.png) +- `#three-col(left, center, right, ..)`: Three column layout. + ![Layout 3-col](assets/docs/layout-3col.png) +- `#grid-2x2(tl, tr, bl, br, ..)`: Four-quadrant grid layout. + ![Layout Grid-2x2](assets/docs/layout-grid2x2.png) +- **Boxes**: All boxes support the `fill-mode` parameter (`"outline"`, `"fill"`, `"full"`, or `"transparent"`). + ![Boxes](assets/docs/component-boxes.png) + - `#highlight-box(title, body)`: Blue university-styled box for key points. + - `#alert-box(title, body)`: Red cautionary box for warnings. + - `#example-box(title, body)`: Green academic box for examples. + - `#algorithm-box(title, body)`: Monospace box for algorithmic logic. + - `#themed-block(title, body)`: Box automatically matching the faculty color. + +## Credits + +- **Underlying Packages**: Built with [presentate](https://typst.app/universe/package/presentate) and [navigator](https://typst.app/universe/package/navigator). +- **Inspiration**: Layout features and component designs were inspired by the [calmly-touying](https://typst.app/universe/package/calmly-touying) theme. A special thanks to its author for the high-quality design inspiration. + +## License + +MIT License. See [LICENSE](LICENSE) for details. diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-acknowledgement-slide.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-acknowledgement-slide.png new file mode 100644 index 0000000000..67ce274a41 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-acknowledgement-slide.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-boxes.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-boxes.png new file mode 100644 index 0000000000..3178f0357a Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-boxes.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-citation.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-citation.png new file mode 100644 index 0000000000..84ad8469d9 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-citation.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-ending-slide.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-ending-slide.png new file mode 100644 index 0000000000..04a4b5d995 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-ending-slide.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-equation-slide.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-equation-slide.png new file mode 100644 index 0000000000..9e16152ce6 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-equation-slide.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-figure-slide.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-figure-slide.png new file mode 100644 index 0000000000..8c2a48b5ae Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-figure-slide.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-focus-slide.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-focus-slide.png new file mode 100644 index 0000000000..40c2ebd0ce Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-focus-slide.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-slide.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-slide.png new file mode 100644 index 0000000000..84fb50ccfc Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/component-slide.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-lettres.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-lettres.png new file mode 100644 index 0000000000..fb5776fec4 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-lettres.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-sante.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-sante.png new file mode 100644 index 0000000000..502e7046ea Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-sante.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-sciences.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-sciences.png new file mode 100644 index 0000000000..9cd5e92c2a Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-sciences.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-univ.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-univ.png new file mode 100644 index 0000000000..3a0375d62e Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/faculty-univ.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/helper-text.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/helper-text.png new file mode 100644 index 0000000000..f54e45b147 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/helper-text.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-2col.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-2col.png new file mode 100644 index 0000000000..0ec77eae21 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-2col.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-3col.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-3col.png new file mode 100644 index 0000000000..91c979d76d Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-3col.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-grid2x2.png b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-grid2x2.png new file mode 100644 index 0000000000..a34343143c Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/docs/layout-grid2x2.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-lettres-white.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-lettres-white.png new file mode 100644 index 0000000000..22abfb576a Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-lettres-white.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-lettres.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-lettres.png new file mode 100644 index 0000000000..89557b4339 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-lettres.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sante-white.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sante-white.png new file mode 100644 index 0000000000..8df479cf7a Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sante-white.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sante.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sante.png new file mode 100644 index 0000000000..1a64545fb9 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sante.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sciences-white.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sciences-white.png new file mode 100644 index 0000000000..9f119dfa2a Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sciences-white.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sciences.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sciences.png new file mode 100644 index 0000000000..89f87d1658 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-sciences.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-univ-white.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-univ-white.png new file mode 100644 index 0000000000..3288c7501f Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-univ-white.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-univ.png b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-univ.png new file mode 100644 index 0000000000..eabaf7f52d Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/assets/logo/sorbonne-univ.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/examples/demo-mapping-2levels.typ b/packages/preview/sorbonne-presentation/0.1.0/examples/demo-mapping-2levels.typ new file mode 100644 index 0000000000..a1f734f165 --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/examples/demo-mapping-2levels.typ @@ -0,0 +1,43 @@ +#import "@preview/sorbonne-presentation:0.1.0": * + +#show: template.with( + title: [Standard Mapping Guide], + subtitle: [Section & Subsection Hierarchy], + author: [David Hajage], + // Standard mapping: Level 1 (=) is a Section, Level 2 (==) is a Subsection + mapping: (section: 1, subsection: 2), + // Numbering format for sections and subsections + numbering-format: "1.1", + show-outline: true, +) + += Introduction +== Description +#slide[ + In this configuration: + - Heading level 1 (`=`) acts as a *Section*. + - Heading level 2 (`==`) acts as a *Subsection*. +] + +== Roadmap +#slide[ + Section transitions will display a "Roadmap" (mini table of contents) listing all the subsections within that section. + + Subsections (Level 2) are displayed in the roadmap of the parent section transition slide. + + They also appear in the breadcrumb at the bottom of the slide. +] + += Technical Details + +== Implementation +#slide(title: "The numbering-format option")[ + The `numbering-format` parameter controls how sections and subsections are numbered. + + For example, `numbering-format: "1.1"` will produce: + - *1.* for the first section. + - *1.1* for the first subsection. +] +#slide[ + You can change it to `"1.a"` or `"I.1"` depending on your preferences. +] diff --git a/packages/preview/sorbonne-presentation/0.1.0/examples/demo-mapping-3levels.typ b/packages/preview/sorbonne-presentation/0.1.0/examples/demo-mapping-3levels.typ new file mode 100644 index 0000000000..cad85a84e8 --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/examples/demo-mapping-3levels.typ @@ -0,0 +1,46 @@ +#import "@preview/sorbonne-presentation:0.1.0": * + +#show: template.with( + title: [Complex Mapping Guide], + subtitle: [Part, Section & Subsection Hierarchy], + author: [David Hajage], + // Complex mapping: 3 levels of hierarchy + mapping: (part: 1, section: 2, subsection: 3), + // Numbering for the Part (Level 1) + part-numbering-format: "I", + // Numbering for Sections (Level 2) and Subsections (Level 3) + numbering-format: "1.a", + show-outline: true, +) + +#slide[ + When a level is mapped to `part`: + - The transition slide is centered and "quiet" (no roadmap). + - It usually represents a major thematic block. + - The numbering follows `part-numbering-format` (here: "I"). +] + += First Part +== Introduction Section +=== Context +#slide[ + In this 3-level setup: + - Level 1 (`=`) is a *Part*. + - Level 2 (`==`) is a *Section*. + - Level 3 (`===`) is a *Subsection*. + + The Section transition (Level 2) will show a roadmap of all Subsections (Level 3) within it. +] + +=== Problem Statement +#slide[ + Look at the breadcrumb: it now tracks three levels of depth. +] + += Second Part +== Results Section +=== Data Analysis +#slide[ + The `numbering-format` starts from the Section level. + Here, `numbering-format: "1.a"` means sections are "1", "2", and subsections are "1.a", "1.b". +] diff --git a/packages/preview/sorbonne-presentation/0.1.0/examples/demo.typ b/packages/preview/sorbonne-presentation/0.1.0/examples/demo.typ new file mode 100644 index 0000000000..751f4910c4 --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/examples/demo.typ @@ -0,0 +1,289 @@ +#import "@preview/sorbonne-presentation:0.1.0": * +#import "@preview/physica:0.9.8": * + +#show: template.with( + title: [Sorbonne Template: Complete Guide], + subtitle: [Demonstration of all features and components], + author: [David Hajage], + affiliation: [Sorbonne University], + faculty: "univ", + show-outline: true, + mapping: (section: 1), +) + +// ========================================== += Basic Components & Layout +// ========================================== + +#slide(title: "Slides & Lists")[ + The `#slide()` function is the core component. It supports standard Typst content. + + + First item + + Second item + - Sub-item +] + +#slide(title: "Text Styles")[ + Three functions to prioritize information: + + - *Alert*: For #alert[critical] information. + - *Muted*: For #muted[secondary] information. + - *Subtle*: For #subtle[tertiary] information. +] + +#slide(title: "Multi-column Layouts", allow-slide-breaks: true)[ + The template provides dedicated functions for balanced layouts: + + - `two-col(left, right, ..)` and `three-col(left, center, right, ..)` + - *Parameters*: + - `columns`: Array of widths (e.g., `(1fr, 2fr)`). Defaults to equal widths. + - `gutter`: Spacing between columns (default: `2em`). + + #v(0.5em) + #two-col( + [*Two-col*: Default equal width. #lorem(5)], + [#lorem(10)] + ) + #v(0.5em) + #two-col( + [*Custom width*: Using `columns: (1fr, 2fr)`.], [#lorem(10)], + columns: (1fr, 2fr) + ) + #v(0.5em) + #three-col( + [*Three-col*], [Equal width], [distribution] + ) + #v(0.5em) + #grid-2x2( + [*Grid-2x2*], [Top Right], + [Bottom Left], [Bottom Right] + ) +] + +#slide(title: "Automatic Page Breaks", allow-slide-breaks: true)[ + When a slide contains too much content (like a long list or a bibliography), you can use `allow-slide-breaks: true`. + + - Content flows naturally to the next physical slide. + - Headers and footers are automatically repeated. + - A suffix (default: " (cont.)") is added to the title from the 2nd page. + + *Long List Example:* + #for i in range(1, 16) [ + + Item number #i + ] +] + +#slide(title: "Manual slide Breaks", allow-slide-breaks: true)[ + You can also force a break manually using `#slide-break()`. + + - This is the first part of the slide. + - Useful for logically separating long content. + + #slide-break() + + - This is the second part, after a manual break. + - The title is automatically suffixed with "(cont.)". +] + +#slide(subtitle: "Demonstrating auto-title with manual subtitle")[ + This slide has no manual `title` parameter. + + Because `auto-title` is `true` (default), it automatically uses the name of the current section ("Basic Components & Layout") as the title, while displaying the provided `subtitle` below it. +] + +#slide( + title: "Slide with Background", + background: block(width: 100%, height: 100%, { + place(center + horizon, image("../assets/logo/sorbonne-univ.png", width: 40%)) + place(top + left, rect(fill: white.transparentize(50%), width: 100%, height: 100%)) + }) +)[ + You can add a background to any slide using the `background` parameter. + + In this example, we use the university logo with a semi-transparent white overlay to ensure content readability. +] + +// ========================================== += Boxes & Blocks +// ========================================== + +#slide(title: "Institutional Boxes")[ + #highlight-box(title: "Highlight Box")[Key points using theme blue.] + #v(0.5em) + #alert-box(title: "Alert Box", fill-mode: "fill")[Warnings using theme red.] + #v(0.5em) + #example-box(title: "Example Box", fill-mode: "full")[Examples using green.] +] + +#slide(title: "Technical Blocks")[ + #algorithm-box(title: "Algorithm Box")[ + + Step 1: Initialize + + Step 2: Process + ] + #v(1em) + #themed-block(title: "Themed Block")[Adapts to the chosen faculty color.] +] + +// ========================================== += Citations & References +// ========================================== + +#slide(title: "Citations Style")[ + Inline citations like @smith2023 or @einstein1905 are highlighted. + + You can also use corner boxes: + + #cite-box("smith2023", position: "bottom-right") + #cite-box("doe2024", display-label: "Jane Doe (2024)", position: "top-right") + + _Note: The citation style can be customized via the `bib-style` parameter (default: "apa")._ +] + +#slide(title: "Bibliography Slide")[ + The bibliography is standard and should be placed in a `#slide()`. + + #bibliography("refs.bib", title: none) +] + +// ========================================== += Special Slide Types +// ========================================== + +#focus-slide[ + This is a `#focus-slide` for impactful messages. +] + +#figure-slide( + rect(width: 40%, height: 30%, fill: sorbonne-lightblue), + title: "Figure Slide", + caption: [A centered caption] +) + +#acknowledgement-slide( + subtitle: [Special thanks to my supervisor:], + people: ((name: "Prof. Smith", role: "Supervisor"),), + institutions: ("Sorbonne University",), +) + +#equation-slide( + $ i hbar pdv(Psi, t) = - hbar^2 / (2m) laplacian(Psi) + V Psi $, + title: [Equation Slide], + definitions: [ + / $Psi$: Wavefunction + / $V$: Potential energy + ], + citation: (bib-key: "einstein1905", label: "Quantum Origins") +) + +// ========================================== += Dynamic Features +// ========================================== + +#slide(title: "Sequential Reveal with pause")[ + The `presentate` package allows for step-by-step reveals: + + - Point 1: Always visible. + #show: pause + - Point 2: Appears after a click. + #show: pause + - Point 3: Final point. + +] + +#slide(title: "Precise Control: only and uncover")[ + You can control exactly which subslide an element appears on: + + #two-col( + [ + #uncover(2, 3, 4)[Visible from step 2.] \ + #uncover(3, 4)[Visible only at step 3.] + ], + [ + #only(1)[Step 1 content.] + #only(2)[Step 2 content.] + #only(3, 4)[Step 3 content.] + ] + ) + + #uncover(4)[ + #v(1em) + #alert-box(title: "Important Limitation")[ + Dynamic animations are *incompatible* with the `allow-slide-breaks: true` option. + ] + ] + +] + +// ========================================== += Template Configuration +// ========================================== + +#slide(title: "Appendix & Hierarchy Control")[ + #two-col( + [ + *Using Appendices* + - Call `#appendix()` to start. + - Resets heading counters. + - Displays a focus slide using `annex-main-title`. + - Changes numbering style to `annex-title` + `annex-numbering-format`. + ], + [ + *Mapping Logic* + - `mapping` defines roles for levels: + - `(section: 1)` : Level 1 is a section. + - `(part: 1, section: 2)` : Level 1 is a Part, Level 2 is a Section. + - Transition slides and breadcrumbs adapt to these roles. + ] + ) +] + +#slide(title: "Theme Configuration Reference (1/2)")[ + #set text(size: 0.72em) + #two-col( + [ + *Identification & Date* + - `title`, `short-title`, `subtitle`. + - `author`, `short-author`, `affiliation`. + - `date`: Defaults to today. + + *Visual Identity* + - `faculty`: `"univ"` (default), `"sante"`, `"sciences"`, `"lettres"`. + - `primary-color` / `alert-color`: Manual hex/rgb overrides. + - `logo-slide` / `logo-transition`: Image paths. + + *Typography & Global* + - `text-font` / `text-size`: e.g., `"Fira Sans"`, `20pt`. + - `aspect-ratio`: `"16-9"` or `"4-3"`. + ], + [ + *Outline (TOC)* + - `show-outline`: Toggle summary slide. + - `outline-title`: Title of the TOC. + - `outline-depth`: Levels shown in TOC. + - `outline-columns`: Number of columns for TOC. + + *Header & Numbering* + - `show-header-numbering`: Toggle all numbers. + - `numbering-format`: For sections (e.g., `"1.1"`). + - `part-numbering-format`: For parts (e.g., `"I"`). + ] + ) +] + +#slide(title: "Theme Configuration Reference (2/2)")[ + #set text(size: 0.8em) + *Navigation & Appendix* + - `mapping`: Dict of roles (part/section/subsection) vs levels. + - `auto-title`: Boolean. If true, slides without a title use the section name. + - `transitions`: Dictionary for `navigator` roadmap customization. + - `bib-style`: Bibliography style (default: `"apa"`). + - `annex-title`: Prefix for single appendix (e.g., `"Appendix"`). + - `annex-main-title`: Focus slide text (e.g., `"Technical Annexes"`). + - `annex-numbering-format`: Numbering style (e.g., `"A"`, `"I"`, `"1"`). + - `progress-bar`: Position of the bar (`"none"`, `"top"`, `"bottom"`). + - `slide-break-suffix`: Suffix for broken slides (default: `" (cont.)"`). + - `footer-author` / `footer-title`: Boolean toggles for footer info. + - `max-length`: (`int` or `dict`) Truncate breadcrumb titles. Ex: `20` or `(section: 10, subsection: 20)`. +] + +#ending-slide() diff --git a/packages/preview/sorbonne-presentation/0.1.0/examples/refs.bib b/packages/preview/sorbonne-presentation/0.1.0/examples/refs.bib new file mode 100644 index 0000000000..419a6e63fb --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/examples/refs.bib @@ -0,0 +1,22 @@ +@article{smith2023, + title={Improvements in Typst Templates}, + author={Smith, John}, + journal={Journal of Typesetting}, + year={2023} +} + +@book{doe2024, + title={The Art of Presentations}, + author={Doe, Jane}, + publisher={Sorbonne Press}, + year={2024} +} + +@article{einstein1905, + title={Does the Inertia of a Body Depend Upon Its Energy Content?}, + author={Einstein, Albert}, + journal={Annalen der Physik}, + volume={18}, + pages={639--641}, + year={1905} +} \ No newline at end of file diff --git a/packages/preview/sorbonne-presentation/0.1.0/lib.typ b/packages/preview/sorbonne-presentation/0.1.0/lib.typ new file mode 100644 index 0000000000..22b7c815d5 --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/lib.typ @@ -0,0 +1,2 @@ +#import "sorbonne.typ": template, slide, focus-slide, figure-slide, figure-slide-split, acknowledgement-slide, equation-slide, ending-slide, cite-box, alert, muted, subtle, appendix, slide-break, two-col, three-col, grid-2x2, highlight-box, alert-box, example-box, algorithm-box, themed-block, sorbonne-red, sorbonne-blue, sorbonne-lightblue, sorbonne-yellow, sorbonne-text +#import "@preview/presentate:0.2.4": pause, uncover, only, fragments, step-item \ No newline at end of file diff --git a/packages/preview/sorbonne-presentation/0.1.0/sorbonne.typ b/packages/preview/sorbonne-presentation/0.1.0/sorbonne.typ new file mode 100644 index 0000000000..ceff491234 --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/sorbonne.typ @@ -0,0 +1,857 @@ +#import "@preview/presentate:0.2.4" as p +#import p.store: states +// On importe la version locale modifiée de navigator +#import "@preview/navigator:0.1.3" as nav + +// --- Configuration et Couleurs --- +#let sorbonne-red = rgb("#AC182E") +#let sorbonne-blue = rgb("#1D2769") +#let sorbonne-lightblue = rgb("#52B5E5") +#let sorbonne-yellow = rgb("#FFB700") +#let sorbonne-text = rgb("#263068") + +// État pour la configuration du thème +#let config-state = state("sorbonne-config", none) +#let last-main-page = state("last-main-page", none) +#let logical-slide-counter = counter("sorbonne-logical-slide") + +// --- Composants --- + +#let progress-bar-line() = context { + let conf = config-state.get() + if conf == none or conf.progress-bar == "none" { return none } + + let current = logical-slide-counter.get().at(0) + let appendix-marker = query() + let total = if appendix-marker.len() > 0 { + logical-slide-counter.at(appendix-marker.first().location()).at(0) - 1 + } else { + logical-slide-counter.final().at(0) + } + + if total == 0 { return none } + + let is-annex = if appendix-marker.len() > 0 { + appendix-marker.first().location().page() <= here().page() + } else { false } + + let ratio = if is-annex { 1.0 } else { calc.min(1.0, current / total) } + + block(width: 100% * ratio, height: 2pt, fill: conf.primary-color) +} + +#let empty-slide(fill: none, body) = { + set page(margin: 0pt, fill: fill, header: none, footer: none, foreground: none) + [ + #logical-slide-counter.step() + #p.slide(logical-slide: true, { + [#metadata((title: none, subtitle: none, allow-slide-breaks: false)) ] + body + }) + ] +} + +#let breadcrumb() = context { + let conf = config-state.get() + if conf == none { return none } + set text(size: 0.8em, fill: gray.darken(20%)) + + let mapping = conf.mapping + let level-modes = (:) + for role in ("part", "section", "subsection") { + let lvl = mapping.at(role, default: none) + if lvl != none { level-modes.insert("level-" + str(lvl) + "-mode", "current") } + } + + nav.progressive-outline( + ..level-modes, + layout: "horizontal", + separator: text(fill: gray.lighten(50%), " / "), + clickable: false, + ) +} + +#let sorbonne-header() = context { + let conf = config-state.get() + if conf == none { return none } + + let markers = query() + if markers.len() == 0 { return none } + + // On cherche le marqueur qui s'applique à cette page. + // C'est soit un marqueur sur cette page, soit le dernier marqueur avant. + let current-page = here().page() + let marker = markers.filter(m => m.location().page() <= current-page).last() + let h = marker.value + + // On n'affiche (suite) que si allow-slide-breaks est activé ET qu'on est sur une page physique suivante + let allow-breaks = if type(h) == dictionary { h.at("allow-slide-breaks", default: false) } else { false } + let is-continuation = current-page > marker.location().page() and allow-breaks + + let resolved-title = if type(h) == dictionary and h.title != none { h.title } else { nav.resolve-slide-title(none) } + if resolved-title == none and h.subtitle == none { return none } + + let title-display = if is-continuation and resolved-title != none { + resolved-title + text(size: 0.8em, weight: "regular", fill: sorbonne-text.lighten(40%), conf.slide-break-suffix) + } else { + resolved-title + } + + block(width: 100%, inset: (x: 2em, top: 0.8em, bottom: 0.2em), { + grid( + columns: (4.5em, 1fr), + column-gutter: 1.5em, + align: horizon, + image(conf.logo-slide, width: 4.5em), + stack(dir: ttb, spacing: 0.3em, + if resolved-title != none { + text(size: 1.1em, weight: "bold", fill: sorbonne-text, smallcaps(title-display)) + }, + if h.subtitle != none { + text(size: 0.85em, style: "italic", fill: sorbonne-text.lighten(20%), h.subtitle) + } + ) + ) + }) +} + +#let sorbonne-footer() = context { + let conf = config-state.get() + if conf == none { return none } + + block(width: 100%, inset: (x: 2.5em, bottom: 0.8em, top: 0.2em), { + set text(size: 0.65em, fill: gray.darken(20%)) + line(length: 100%, stroke: 0.5pt + gray.lighten(80%)) + v(0.5em) + + let show-author = conf.footer-author + let show-title = conf.footer-title + + if not show-author and not show-title { + grid( + columns: (1fr, auto), + align: (left, right), + breadcrumb(), + context { + let current = logical-slide-counter.get().at(0) + let appendix-marker = query() + let total = if appendix-marker.len() > 0 { + logical-slide-counter.at(appendix-marker.first().location()).at(0) - 1 + } else { + logical-slide-counter.final().at(0) + } + [#current / #total] + } + ) + } else { + grid( + columns: (1fr, 1fr, 1fr), + align: (left, center, right), + text(size: 0.9em, weight: "regular", { + if show-author { conf.short-author } + if show-author and show-title [ #h(0.5em) · #h(0.5em) ] + if show-title { conf.short-title } + }), + breadcrumb(), + context { + let current = logical-slide-counter.get().at(0) + let appendix-marker = query() + let total = if appendix-marker.len() > 0 { + logical-slide-counter.at(appendix-marker.first().location()).at(0) - 1 + } else { + logical-slide-counter.final().at(0) + } + [#current / #total] + } + ) + } + }) +} + +#let apply-layout(breakable: true, body) = context { + let config = config-state.get() + set text(font: config.text-font, size: config.text-size, fill: sorbonne-text) + + if not breakable { + // Le grid 1fr occupe tout l'espace disponible, permettant le centrage vertical (horizon) + // tout en s'ajustant si des notes de bas de page sont présentes. + grid( + columns: 100%, + rows: 1fr, + inset: (x: 2.5em, top: 0.5em, bottom: 0pt), + { + metadata((t: "ContentSlide")) + body + } + ) + } else { + block(width: 100%, breakable: true, inset: (x: 2.5em, top: 0.5em, bottom: 0pt), { + metadata((t: "ContentSlide")) + body + }) + } +} + +#let focus-slide(body, subtitle: none) = context { + let conf = config-state.get() + empty-slide(fill: conf.primary-color, { + place(top + left, pad(top: 2em, left: 2em, image(conf.logo-transition, width: 5em))) + set text(fill: white, weight: "bold") + align(center + horizon, stack(dir: ttb, spacing: 1em, + text(size: 2.5em, body), + if subtitle != none { + text(size: 1.5em, weight: "regular", style: "italic", fill: white.transparentize(20%), subtitle) + } + )) + }) +} + +#let alert(body) = context { + let conf = config-state.get() + text(fill: conf.alert-color, weight: "bold", body) +} + +#let muted(body) = text(fill: gray, body) + +#let subtle(body) = text(fill: gray.lighten(40%), body) + +#let two-col(left, right, columns: (1fr, 1fr), gutter: 2em) = { + grid( + columns: columns, + column-gutter: gutter, + left, + right + ) +} + +#let three-col(left, center, right, columns: (1fr, 1fr, 1fr), gutter: 2em) = { + grid( + columns: columns, + column-gutter: gutter, + left, + center, + right + ) +} + +#let grid-2x2(tl, tr, bl, br, columns: (1fr, 1fr), rows: (auto, auto), gutter: 2em) = { + grid( + columns: columns, + rows: rows, + column-gutter: gutter, + row-gutter: gutter, + tl, + tr, + bl, + br + ) +} + +// --- API --- + +#let slide(..args) = { + let pos = args.pos() + let named = args.named() + let manual-title = named.at("title", default: none) + let subtitle = named.at("subtitle", default: none) + let allow-slide-breaks = named.at("allow-slide-breaks", default: false) + let background = named.at("background", default: none) + let body = if pos.len() > 0 { pos.at(0) } else { none } + + let clean-named = named + for key in ("title", "subtitle", "allow-slide-breaks", "background") { + if key in clean-named { + let _ = clean-named.remove(key) + } + } + + [ + #logical-slide-counter.step() + #p.slide(..clean-named, { + if background != none { + place(top + left, dx: 0pt, dy: -4.5em, block(width: 100%, height: 100% + 4.5em + 3.0em, background)) + } + [#metadata((title: manual-title, subtitle: subtitle, allow-slide-breaks: allow-slide-breaks)) ] + apply-layout(breakable: allow-slide-breaks, body) + }) + ] +} + +#let figure-slide(fig, title: none, subtitle: none, caption: none, ..args) = { + slide(title: title, subtitle: subtitle, ..args, { + set align(center + horizon) + figure(fig, caption: caption) + }) +} + +#let figure-slide-split(fig-left, fig-right, title: none, subtitle: none, caption-left: none, caption-right: none, ..args) = { + slide(title: title, subtitle: subtitle, ..args, { + set align(center + horizon) + grid( + columns: (1fr, 1fr), + column-gutter: 2em, + figure(fig-left, caption: caption-left), + figure(fig-right, caption: caption-right) + ) + }) +} + +#let acknowledgement-slide( + title: "Acknowledgements", + subtitle: none, + people: (), + institutions: (), + extra: none, + ..args +) = { + slide(title: title, subtitle: subtitle, ..args, { + set align(center + horizon) + stack( + dir: ttb, + spacing: 1.5em, + + if people.len() > 0 { + align(center, grid( + columns: (auto, auto), + column-gutter: 2em, + row-gutter: 1em, + ..people.map(p => ( + align(right, text(weight: "bold", p.name)), + align(left, p.role) + )).flatten() + )) + }, + + if institutions.len() > 0 { + v(0.5em) + align(center, institutions.join([ #h(2em) ])) + }, + + if extra != none { + v(1em) + extra + } + ) + }) +} + +#let cite-box(bib-key, display-label: none, position: "bottom-right", form: "normal") = context { + let conf = config-state.get() + + let align-pos = if position == "top-right" { top + right } + else if position == "bottom-left" { bottom + left } + else { bottom + right } + + let dx = if "right" in position { 1em } else { -1em } + let dy = if "top" in position { -0.3em } else { 0.3em } + + let keys = if type(bib-key) == array { bib-key } else if bib-key != none { (bib-key,) } else { () } + let labels = keys.map(k => if type(k) == str { label(k) } else { k }) + + let content = if display-label != none { + // On "cite" de manière invisible pour forcer l'inclusion en bibliographie + if labels.len() > 0 { + place(hide(labels.map(l => cite(l, form: form)).join())) + } + display-label + } else if labels.len() > 0 { + labels.map(l => cite(l, form: form)).join(", ") + } else { + none + } + + if content != none { + place(align-pos, dx: dx, dy: dy, block( + fill: conf.primary-color.lighten(95%), + stroke: 0.5pt + conf.primary-color, + radius: 3pt, + inset: 0.4em, + text(size: 0.65em, fill: conf.primary-color, content) + )) + } +} + +#let equation-slide( + equation, + title: "Equation", + subtitle: none, + definitions: none, + citation: none, + ..args +) = { + slide(title: title, subtitle: subtitle, ..args, { + set align(center + horizon) + + stack( + dir: ttb, + spacing: 2em, + + // Bloc Equation + Signature + stack( + dir: ttb, + spacing: 0.8em, + block( + text(size: 2.5em, weight: "bold", equation) + ), + if citation != none { + let key = if type(citation) == dictionary { citation.at("bib-key", default: none) } else { citation } + let lbl = if type(citation) == dictionary { citation.at("label", default: none) } else { none } + let keys = if type(key) == array { key } else if key != none { (key,) } else { () } + let labels = keys.map(k => if type(k) == str { label(k) } else { k }) + + if labels.len() > 0 { place(hide(labels.map(l => cite(l)).join())) } + + let cite-content = if lbl != none { lbl } else { labels.map(l => cite(l)).join(", ") } + + // Style "Signature" : à droite, en gris, avec tiret + align(right, pad(right: 15%, text(fill: gray.darken(20%), size: 0.9em, [--- #cite-content]))) + } + ), + + // Boîte de définitions + if definitions != none { + context { + let conf = config-state.get() + block( + width: 85%, + fill: conf.primary-color.lighten(95%), + stroke: (left: 3pt + conf.primary-color), // Bordure gauche élégante + inset: 1.5em, + radius: (right: 4pt), + align(left, { + set par(leading: 0.8em) + definitions + }) + ) + } + } + ) + }) +} + +#let ending-slide( + title: [Thanks for watching!], + subtitle: [Questions?], + contact: ("email@example.com", "github.com/username") +) = context { + let conf = config-state.get() + empty-slide(fill: conf.primary-color, { + place(top + left, pad(top: 2em, left: 2em, image(conf.logo-transition, width: 5em))) + set text(fill: white) + align(center + horizon, stack( + spacing: 1.5em, + text(size: 2.5em, weight: "bold", title), + if subtitle != none { text(size: 1.5em, style: "italic", fill: white.transparentize(20%), subtitle) }, + if contact != none and contact != () { + v(1em) + set text(size: 1em, weight: "regular") + if type(contact) == array { + contact.join([ #h(2em) ]) + } else { + contact + } + } + )) + }) +} + +// --- Boîtes et Blocs --- + +#let _base-box(title: none, body, color: black, fill-mode: "outline") = { + let (fill-body, stroke-box) = if fill-mode == "fill" { + (color.lighten(90%), 0.5pt + color) + } else if fill-mode == "full" { + (color.lighten(80%), 0.5pt + color) + } else if fill-mode == "transparent" { + (none, none) + } else { + // outline + (none, 0.5pt + color) + } + + block( + width: 100%, + radius: 4pt, + clip: true, + stroke: stroke-box, + stack( + spacing: 0pt, + if title != none { + block( + width: 100%, + fill: color, + inset: 0.6em, + text(fill: white, weight: "bold", title) + ) + }, + block( + width: 100%, + fill: fill-body, + inset: 0.8em, + body + ) + ) + ) +} + +#let highlight-box(title: "Key Point", fill-mode: "outline", body) = { + _base-box(title: title, body, color: sorbonne-blue, fill-mode: fill-mode) +} + +#let alert-box(title: "Warning", fill-mode: "outline", body) = { + _base-box(title: title, body, color: sorbonne-red, fill-mode: fill-mode) +} + +#let example-box(title: "Example", fill-mode: "outline", body) = { + _base-box(title: title, body, color: rgb("#2E7D32"), fill-mode: fill-mode) +} + +#let algorithm-box(title: "Algorithm", fill-mode: "outline", body) = { + let algorithm-body = { + set text(font: ("Fira Code", "DejaVu Sans Mono"), size: 0.9em) + // On formate les listes numérotées comme des lignes de code + show enum: it => { + grid( + columns: (1.5em, 1fr), + column-gutter: 0.8em, + row-gutter: 0.5em, + ..it.children.enumerate().map(((i, child)) => ( + align(right, text(fill: gray, str(i + 1) + ":")), + child.body + )).flatten() + ) + } + body + } + _base-box(title: title, algorithm-body, color: rgb("#455A64"), fill-mode: fill-mode) +} + +#let themed-block(title: none, fill-mode: "outline", body) = context { + let conf = config-state.get() + let color = conf.primary-color + _base-box(title: title, body, color: color, fill-mode: fill-mode) +} + +#let appendix() = { + counter(heading).update(0) + [#metadata(none) ] + context { + let conf = config-state.get() + focus-slide(upper(conf.annex-main-title)) + } +} + +#let slide-break() = colbreak(weak: true) + +// --- Template --- + +#let template( + title: none, + author: none, + short-title: none, + short-author: none, + affiliation: none, + subtitle: none, + date: datetime.today().display(), + aspect-ratio: "16-9", + text-font: "Fira Sans", + text-size: 20pt, + faculty: "sante", + // Surcharges optionnelles + primary-color: none, + alert-color: none, + logo-slide: none, + logo-transition: none, + show-header-numbering: true, + numbering-format: "1.1", + part-numbering-format: "I", + annex-title: [Annexe], + annex-main-title: [Annexes], + annex-numbering-format: "I", + mapping: (section: 1, subsection: 2), + bib-style: "apa", + transitions: (:), + show-outline: false, + outline-title: [Sommaire], + outline-depth: 2, + outline-columns: 1, + auto-title: true, + progress-bar: "none", // "none", "top", "bottom" + slide-break-suffix: [ (cont.)], + footer-author: true, + footer-title: true, + max-length: none, + use-short-title: false, + body +) = { + // 1. Détermination des valeurs par défaut basées sur faculty + let (def-primary, def-alert, def-logo-transition, def-logo-slide) = if faculty == "sciences" { + (sorbonne-lightblue, sorbonne-lightblue.darken(40%), "assets/logo/sorbonne-sciences-white.png", "assets/logo/sorbonne-sciences.png") + } else if faculty == "lettres" { + (sorbonne-yellow, sorbonne-yellow.darken(45%), "assets/logo/sorbonne-lettres-white.png", "assets/logo/sorbonne-lettres.png") + } else if faculty == "univ" or faculty == none { + (sorbonne-blue, sorbonne-blue.darken(20%), "assets/logo/sorbonne-univ-white.png", "assets/logo/sorbonne-univ.png") + } else { + // Default is sante + (sorbonne-red, sorbonne-red.darken(15%), "assets/logo/sorbonne-sante-white.png", "assets/logo/sorbonne-sante.png") + } + + // 2. Application des surcharges si fournies + let final-primary = if primary-color != none { primary-color } else { def-primary } + let final-alert = if alert-color != none { alert-color } else { def-alert } + let final-logo-transition = if logo-transition != none { logo-transition } else { def-logo-transition } + let final-logo-slide = if logo-slide != none { logo-slide } else { def-logo-slide } + + config-state.update(c => ( + title: title, + author: author, + short-title: if short-title != none { short-title } else { title }, + short-author: if short-author != none { short-author } else { author }, + affiliation: affiliation, + show-header-numbering: show-header-numbering, + numbering-format: numbering-format, + part-numbering-format: part-numbering-format, + annex-title: annex-title, + annex-main-title: annex-main-title, + annex-numbering-format: annex-numbering-format, + mapping: mapping, + primary-color: final-primary, + alert-color: final-alert, + logo-transition: final-logo-transition, + logo-slide: final-logo-slide, + text-font: text-font, + text-size: text-size, + progress-bar: progress-bar, + slide-break-suffix: slide-break-suffix, + footer-author: footer-author, + footer-title: footer-title, + max-length: max-length, + use-short-title: use-short-title, + )) + + nav.navigator-config.update(c => { + c.mapping = mapping + c.auto-title = auto-title + c.show-heading-numbering = show-header-numbering + c.slide-func = empty-slide + c.theme-colors = (primary: final-primary) + c.max-length = max-length + c.use-short-title = use-short-title + c.transitions = ( + parts: (visibility: (part: "none", section: "none", subsection: "none")), + sections: (visibility: (part: "none", section: "none", subsection: "current-parent")), + subsections: (visibility: (part: "none", section: "none", subsection: "current-parent")), + style: (active-weight: "bold", active-color: white, inactive-opacity: 0.6, completed-opacity: 0.6), + marker: none, + ) + transitions + c.progressive-outline = nav.merge-dicts( + ( + level-1-mode: "none", + level-2-mode: "none", + level-3-mode: "none", + text-styles: ( + level-1: (active: (weight: "bold", fill: final-primary), completed: (weight: "bold"), inactive: (weight: "bold")), + level-2: (active: (weight: "regular", fill: final-primary), completed: (weight: "regular"), inactive: (weight: "regular")), + level-3: (active: (weight: "regular", fill: final-primary), completed: (weight: "regular"), inactive: (weight: "regular")) + ), + ), + base: c.at("progressive-outline", default: (:)) + ) + c + }) + + set page( + paper: "presentation-" + aspect-ratio, + margin: (top: 4.5em, bottom: 3.0em, x: 0pt), + header: none, + footer: none, + foreground: context { + let conf = config-state.get() + if conf == none { return none } + + // Header & Footer + place(top + left, sorbonne-header()) + place(bottom + left, sorbonne-footer()) + + // Progress bar + if conf.progress-bar != "none" { + let line = progress-bar-line() + if conf.progress-bar == "top" { + place(top + left, line) + } else if conf.progress-bar == "bottom" { + place(bottom + left, line) + } + } + } + ) + set text(font: text-font, size: text-size, fill: sorbonne-text) + show math.equation: set text(font: "Fira Math") + + // Listes à puces et énumérations thématiques + set list(marker: ([•], [‣], [–]).map(m => text(fill: final-primary, m))) + set enum(numbering: (n) => text(fill: final-primary, weight: "bold", str(n) + ".")) + + // Définit le style de bibliographie + set bibliography(style: bib-style) + + // Style des citations + show cite: it => context { + let conf = config-state.get() + box( + inset: (x: 2pt), + outset: (y: 2pt), + radius: 2pt, + fill: conf.primary-color.lighten(90%), + text(fill: conf.primary-color, it) + ) + } + + set heading(numbering: (..nums) => context { + + if not show-header-numbering { return none } + let n = nums.pos() + + // Check if we are in appendix + let appendix-marker = query() + let is-annex = if appendix-marker.len() > 0 { + appendix-marker.first().location().page() < here().page() or (appendix-marker.first().location().page() == here().page() and appendix-marker.first().location().position().y < here().position().y) + } else { false } + + if is-annex { + let formats = (annex-numbering-format, "A", "1") + let parts = () + for i in range(n.len()) { + parts.push(numbering(formats.at(i, default: "1"), n.at(i))) + } + return annex-title + " " + parts.join("") + } + + let role = none + for (r, lvl) in mapping { if lvl == n.len() { role = r; break } } + + if role == "part" { + numbering(part-numbering-format, ..n) + } else if role == "section" or role == "subsection" { + let start-idx = if mapping.keys().contains("part") { 1 } else { 0 } + if n.len() > start-idx { + numbering(numbering-format, ..n.slice(start-idx)) + } + } else { + none + } + }) + + // Page de Titre + empty-slide(fill: final-primary, { + set text(fill: white) + place(bottom + right, pad(bottom: 2em, right: 2em, image(final-logo-transition, width: 6em))) + align(horizon, pad(x: 3em, y: 2em, stack( + spacing: 1.2em, + text(size: 2.5em, weight: "bold", smallcaps(title)), + if subtitle != none { text(size: 1.4em, style: "italic", subtitle) }, + v(1.5em), + text(size: 1.2em, weight: "bold", author), + text(size: 1em, affiliation), + text(size: 0.9em, fill: white.transparentize(20%), date), + ))) + }) + + // Sommaire automatique + if show-outline { + slide(title: outline-title, { + if outline-columns > 1 { + columns(outline-columns, outline(title: none, depth: outline-depth, indent: 2em)) + } else { + outline(title: none, depth: outline-depth, indent: 2em) + } + }) + } + + show heading: h => context { + if h.level > 3 { return h } + + let conf = config-state.get() + + // Check if we are in appendix for this heading + let appendix-marker = query() + let is-annex = if appendix-marker.len() > 0 { + appendix-marker.first().location().page() < h.location().page() or (appendix-marker.first().location().page() == h.location().page() and appendix-marker.first().location().position().y < h.location().position().y) + } else { false } + + // En annexe, on ne fait des transitions que pour le niveau le plus haut mappé + let top-level = calc.min(..mapping.values()) + if is-annex and h.level > top-level { + return place(hide(h)) + } + + nav.render-transition( + h, + top-padding: 0pt, + use-short-title: false, + content-wrapper: (roadmap, h, active) => { + set text(fill: white, font: "Fira Sans") + place(top + left, pad(top: 2em, left: 2em, image(conf.logo-transition, width: 5em))) + + let role = none + for (r, lvl) in mapping { if lvl == h.level { role = r; break } } + + // --- CASE 1: PART TRANSITION (Centered Title, No Roadmap) --- + if role == "part" or (is-annex and role == "section" and not mapping.keys().contains("part")) { + align(center + horizon, stack( + spacing: 1.5em, + if conf.show-header-numbering { + let num = if is-annex { + conf.annex-title + " " + numbering(conf.annex-numbering-format, counter(heading).at(h.location()).at(0)) + } else { + numbering(conf.part-numbering-format, counter(heading).at(h.location()).at(0)) + } + text(size: if is-annex { 4em } else { 6em }, weight: "bold", num) + }, + text(size: 3em, weight: "bold", upper(h.body)) + )) + + // --- CASE 2: SECTION TRANSITION (Split Layout with Roadmap) --- + } else { + // 2.1. Active Part display (Top Right) + let part-lvl = mapping.at("part", default: none) + let active-part = if part-lvl != none { active.at("h" + str(part-lvl), default: none) } else { none } + if active-part != none { + place(top + right, pad(top: 2.5em, right: 3em, text(size: 0.8em, fill: white.transparentize(30%), weight: "bold", upper(active-part.body)))) + } + + // 2.2. Main Content + let section-lvl = mapping.at("section", default: 1) + let section-head = active.at("h" + str(section-lvl), default: h) + let count = counter(heading).at(section-head.location()) + let start-idx = if mapping.keys().contains("part") { 1 } else { 0 } + let nums = count.slice(start-idx) + + pad(x: 2em, stack( + dir: ttb, + v(15%), + align(center, stack( + spacing: 0.8em, + if conf.show-header-numbering { + let fmt-num = if is-annex { + conf.annex-title + " " + numbering(conf.annex-numbering-format, ..nums) + } else { + numbering(conf.numbering-format, ..nums) + } + if is-annex { + text(size: 3.5em, weight: "bold", fmt-num + " " + smallcaps(section-head.body)) + } else { + text(size: 6em, weight: "bold", fmt-num) + } + }, + if not is-annex { text(size: 2.2em, weight: "bold", smallcaps(section-head.body)) }, + v(1.2em), + block(width: 60%, align(left, roadmap)) + )) + )) + } + } + ) + } + + body +} diff --git a/packages/preview/sorbonne-presentation/0.1.0/template/main.typ b/packages/preview/sorbonne-presentation/0.1.0/template/main.typ new file mode 100644 index 0000000000..a39d75d131 --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/template/main.typ @@ -0,0 +1,46 @@ +#import "@preview/sorbonne-presentation:0.1.0": * + +// --- Theme Configuration --- +#show: template.with( + title: [Presentation Title], + subtitle: [Subtitle or Context], + author: [Your Name], + affiliation: [Your Laboratory / Department], + // faculty: "univ", // Presets: "univ" (blue), "sante" (red), "sciences" (light blue), "lettres" (yellow) + date: datetime.today().display(), + show-outline: true, // Show the table of contents at the beginning +) + +// --- Content --- + += Introduction + +#slide(title: "Welcome")[ + This is a sample presentation using the Sorbonne theme. + + - Respects the university's visual identity. + - Built on top of `presentate` and `navigator` packages. +] + += First Part + +== Key Concepts + +#slide[ + You can use numbered and bulleted lists: + + First important point + + Second crucial point + - Technical detail +] + +#focus-slide[ + "Focus" slides are designed for impactful messages or major transitions. +] + += Conclusion + +#ending-slide( + title: [Thank you for your attention!], + subtitle: [Any questions?], + contact: ("first.name@sorbonne-universite.fr",) +) diff --git a/packages/preview/sorbonne-presentation/0.1.0/thumbnail.png b/packages/preview/sorbonne-presentation/0.1.0/thumbnail.png new file mode 100644 index 0000000000..3f830edfc0 Binary files /dev/null and b/packages/preview/sorbonne-presentation/0.1.0/thumbnail.png differ diff --git a/packages/preview/sorbonne-presentation/0.1.0/typst.toml b/packages/preview/sorbonne-presentation/0.1.0/typst.toml new file mode 100644 index 0000000000..40481f6349 --- /dev/null +++ b/packages/preview/sorbonne-presentation/0.1.0/typst.toml @@ -0,0 +1,17 @@ +[package] +name = "sorbonne-presentation" +version = "0.1.0" +entrypoint = "lib.typ" +authors = ["David Hajage"] +license = "MIT" +keywords = ["presentation", "sorbonne", "academic", "slides"] +description = "A structured presentation theme for Sorbonne University, based on presentate and navigator." +compiler = "0.12.0" +categories = ["presentation"] +repository = "https://github.com/eusebe/typst-sorbonne-presentation" +exclude = ["tests", "*.pdf", "**/*.pdf", ".gitignore", ".DS_Store"] + +[template] +path = "template" +entrypoint = "main.typ" +thumbnail = "thumbnail.png"