diff --git a/packages/preview/ttuile/0.2.0/LICENSE b/packages/preview/ttuile/0.2.0/LICENSE
new file mode 100644
index 0000000000..e76575b62f
--- /dev/null
+++ b/packages/preview/ttuile/0.2.0/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 vitto4
+
+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/ttuile/0.2.0/README.md b/packages/preview/ttuile/0.2.0/README.md
new file mode 100644
index 0000000000..9953639018
--- /dev/null
+++ b/packages/preview/ttuile/0.2.0/README.md
@@ -0,0 +1,154 @@
+
+   +
+
+
+
+  
+     +  
+
+  
+     +  
+
+  
+     +
+
+
+A Typst template for students' lab reports at INSA Lyon.
+
+
+  
+     +  
+
+  
+
+
+> **Note**
+> 
+> This template targets French students, so you may see French words here and there.
+> Should you want to write your report in another language, there's a workaround, see [Notes](#-notes).
+
+## 🧠Table of contents
+
+1. [Usage](#-usage)
+1. [Documentation](#-documentation)
+1. [Notes](#-notes)
+1. [Contributing](#-contributing)
+
+
+## 📎 Usage
+
+Colorful lab report template more or less aligned with guidelines guidelines issued in 1st year at INSA Lyon.
+It is available on _Typst Universe_ : [`@preview/ttuile:0.2.0`](https://typst.app/universe/package/ttuile).
+
+If you wish to use it locally, you'll need to either manually include `ttuile.typ` and folder `internal/` in your project's root directory ; or upload them to the _Typst web app_ if that's what you use.
+
+You'll find these files in the [releases](https://github.com/vitto4/ttuile/releases) section.
+
+Your folder structure should then look something like this :
+
+```
+.
+├── ttuile.typ
+├── internal/
+│    ├── defaults.typ
+│    ├── helpers.typ
+│    └── logo-insa-lyon.png
+└── main.typ
+```
+
+The template is now ready to be used, and can be called supplying the following arguments.
+`?` means the argument can be null if not applicable.
+
+
+| Argument | Default value | Type | Description |
+|:--------:|:-------------:|:----:|:------------|
+| `headline` | `none` | `dictionary \| content?` | Title of the report |
+| `authors` | `none` | `array \| content?` | One or multiple authors to be credited in the report |
+| `group` | `none` | `content?` | Your class (or group) number/letter/identifier to be displayed right after the author(s) |
+| `footer-left` | `none` | `content?` | Usually your lab bench number |
+| `footer-right` | `none` | `content?` | Usually the date at which the lab work/practical was carried out |
+| `outlined` | `true` | `bool` | Display the table of contents ? |
+| `logo` | `image("internal/logo-insa-lyon.png")` | `image \| content?` | University logo to display |
+
+A single positional argument is accepted, being the report's body.
+
+You can call the template using the following syntax :
+
+```typ
+// Local import
+// #import "ttuile.typ": *
+
+// Universe import
+#import "@preview/ttuile:0.2.0": *
+
+#show: ttuile.with(
+  // This is one way to set the headline, but the following also works :
+  //  * headline: [You may supply just the title],
+  //  * headline: text(fill: blue, weight: "regular")[And even style it\ however you wish],
+  headline: (
+    lead: [Compte rendu de TP n°1 :],
+    title: [« #lorem(8) »],
+  ),
+  // Same as above :
+  //  * authors: [You may also supply whatever you want],
+  authors: (
+    "Theresa Tungsten",
+    "Jean Dupont",
+    "Eugene Deklan",
+  ),
+  group: "TD0",
+  footer-left: [Poste n°0],
+  footer-right: datetime.today().display("[day]/[month]/[year]"),
+  outlined: true,
+  // Also remove the uni logo or insert your own :
+  //  * logo: image("your-logo.png"),
+  //  * logo: none,
+)
+```
+
+## 📚 Documentation
+
+The package `ttuile.typ` exposes multiple functions, find out more about them in the _documentation_.
+
+
+  
+    To the documentation
+  
+
+
+An example file is also available in [`template/main.typ`](https://github.com/vitto4/ttuile/blob/main/template/main.typ)
+
+
+## 🔖 Notes
+
+- As mentioned previously, the template targets French students, so you'll get things like `Auteurs != Authors`, `Annexe != Appendix` and such. Thankfully I didn't cut corners there hehe, all of that can be tweaked :
+  ```typ
+  #show: ttuile.with(
+    headline: [Yay, the French's all gone !],
+    authors: [*Authors:* Theresa Tungsten, Jean Dupont and Eugene Deklan],
+    group: "Group 1",
+    // This is supposed to be the lab bench number, but you may as well put whatever in there
+    footer-left: [Hello, world!],
+    footer-right: datetime.today().display("[day]/[month]/[year]"),
+    // You can use your own university's logotype
+    logo: image("path_to/logo.png"),
+  )
+  
+  // This will fix tables and figures
+  #set text(lang: "en")
+
+  // And this the appendices section
+  #appendices-section(
+    ...,
+    headline: "Appendices",
+    supplement: "Appendix",
+    outline-title: "List of appendices",
+  )
+  ```
+- You may also tweak heading spacing : `#show heading: set block(above: 18pt, below: 5pt)`.
+- The MIT license doesn't apply to the file `logo-insa-lyon.png`, it was retrieved from [INSA Lyon - éléments graphiques](https://www.insa-lyon.fr/fr/elements-graphiques). It doesn't apply either to the "INSA" branding.
+
+
+## 🧩 Contributing
+
+Contributions are welcome ! Parts of the template are very much spaghetti code, so if you know the proper way of achieving what I'm going for, an issue or PR would be greatly appreciated :)
\ No newline at end of file
diff --git a/packages/preview/ttuile/0.2.0/internal/defaults.typ b/packages/preview/ttuile/0.2.0/internal/defaults.typ
new file mode 100644
index 0000000000..e457d5704a
--- /dev/null
+++ b/packages/preview/ttuile/0.2.0/internal/defaults.typ
@@ -0,0 +1,67 @@
+/* ------------------------------- Font sizes ------------------------------- */
+#let fsize-base = 12pt
+#let fsize-header = 10.5pt
+#let fsize-footer = 10pt
+#let fsize-caption = 10pt
+#let fsize-footnote = 9pt
+
+// Headline font sizes, in order : [lead, title]
+#let fsize-headline = (14pt, 18pt)
+
+// Heading font sizes, in order : [h1, h2, h3, h4]
+#let fsize-heading = (14pt, 13pt, 13pt, 12pt)
+
+#let fsize-appendices-outline-title = (16pt,)
+
+/* ---------------------------------- Fonts --------------------------------- */
+#let ffam-base = "HK Grotesk"
+#let ffam-heading = "HK Grotesk"
+#let ffam-special = "Liberation Serif"
+#let ffam-outline = "Libertinus Serif"
+
+
+/* --------------------------------- Lengths -------------------------------- */
+#let indent-base = 0.76cm
+#let indent-outline = 0.4cm
+#let indent-heading = 0.50cm
+#let indent-numbering = 0.2cm
+
+// Spacing around headings, in order : [above, below]
+#let vspacing-heading = (15pt, 12pt)
+
+#let vspacing-appendices-outline = 4.25pt
+#let vspacing-appendices-heading = 3 * 4.25pt
+
+/* --------------------------------- Colors --------------------------------- */
+
+// Used for the headline
+#let color-headline = color.red
+
+// This one for figures' captions
+#let color-blue-caption = color.rgb(0, 69, 134)
+
+// Heading colors, in order : [h1, h2, h3, h4]
+#let color-heading = (
+  color.rgb(0, 169, 51), // Green
+  color.rgb(52, 101, 164), // Blue
+  color.rgb(166, 77, 121), // Purple
+  black,
+)
+
+
+/* ---------------------------------- Misc ---------------------------------- */
+#let style-numbering-h1 = "I."
+#let style-numbering-h2 = "1."
+#let style-numbering-h3 = "A."
+#let style-numbering-base = style-numbering-h1 + style-numbering-h2 + style-numbering-h3
+#let style-numbering-appendices = "A.1.1.1"
+
+// Prefix showing before the error messages in `assert`s and `panic`s
+#let prefix-errors = "[ template / ttuile ] > "
+
+#let outline-depth = 3
+
+// These are supposed to be random IDs used as markers/tags,
+// so that we can tell for sure where the appendices begin and end.
+#let metadata-appendices-start = "6a359bbd-b367-4af5-bd82-c80e83a64c78"
+#let metadata-appendices-end = "ea2899f8-e9f3-4c70-b4b0-4e6afe77da9c"
diff --git a/packages/preview/ttuile/0.2.0/internal/helpers.typ b/packages/preview/ttuile/0.2.0/internal/helpers.typ
new file mode 100644
index 0000000000..b3a39b9be8
--- /dev/null
+++ b/packages/preview/ttuile/0.2.0/internal/helpers.typ
@@ -0,0 +1,239 @@
+#import "defaults.typ": *
+
+/// Deal with what was supplied through `authors` and `group` to the template
+///
+/// See also : https://typst.app/docs/reference/foundations/arguments/#argument-sinks
+#let authors-helper(group, authors) = {
+  let _type = type(authors)
+
+  let _prefix
+  let _authors
+  let _group = if (group != none and _type != type(none)) [ -- #group]
+
+  if (_type == array) {
+    // If there are many authors, we want something like `Auteurs : Auteur-1, Auteur-2 et Auteur-3`
+    let _len = authors.len()
+    _prefix = if (_len > 1) [Auteurs : ] else [Auteur : ]
+    _authors = authors.join(", ", last: " et ")
+  } else if (_type == content) {
+    // If a content block is provided, display with minimal tampering
+    _prefix = []
+    _authors = authors
+  } else if (_type != type(none)) {
+    panic(
+      prefix-errors + "Please provide either an `array`, `content` block or `none` (leave empty) for field `authors`.",
+    )
+  }
+
+  return text(weight: "bold", _prefix) + _authors + _group
+}
+
+/// Deal with what was supplied through `headline` to the template
+#let headline-helper(headline) = {
+  let _type = type(headline)
+  let output
+
+  if _type == dictionary {
+    let _keys = headline.keys()
+    assert(
+      "lead" in _keys and "title" in _keys,
+      message: prefix-errors
+        + "If you provide `headline` as a `dictionary`, it should possess keys `\"lead\"` and `\"title\"`. You may also pass `content?` to `headline`.",
+    )
+    output = [
+      // Lead
+      #v(-2pt)
+      #text(
+        size: fsize-headline.at(0),
+        headline.at("lead"),
+      )
+      #v(weak: true, 8pt)
+      // Title
+      #text(
+        headline.at("title"),
+      )
+    ]
+  } else { output = text(headline) }
+
+  return {
+    set text(weight: "bold", fill: color-headline, size: fsize-headline.at(1))
+    output
+  }
+}
+
+/// Handle the display of appendices headings
+#let appendix-heading-helper(title: none, lbl: none, supplement: none) = {
+  // These should not display in the document's main outline
+  set heading(supplement: supplement)
+
+  show heading.where(level: 1): it => block(width: 100%)[
+    #text(
+      size: fsize-heading.at(0),
+      weight: "bold",
+    )[
+      #it.supplement
+      #context counter(heading).display()
+      #h(indent-heading)
+      #it.body
+      #{ v(vspacing-appendices-heading) }
+    ]
+  ]
+
+  [#heading(level: 1, title) #lbl]
+}
+
+#let appendix(
+  title: none,
+  lbl: none,
+  body,
+) = {
+  if lbl != none {
+    assert.eq(
+      type(lbl),
+      label,
+      message: prefix-errors
+        + "`lbl` should be a `label`. You can use it in your document to reference the appendix in question.",
+    )
+  }
+  return (title: title, lbl: lbl, body: body)
+}
+
+#let appendices-section(
+  appendices: none,
+  outlined: true,
+  pagebreak-after-outline: false,
+  headline: "Annexes",
+  supplement: "Annexe",
+  outline-title: "Table des annexes",
+) = {
+  assert.eq(type(appendices), array, message: prefix-errors + "`appendices` should be an `array`.")
+
+  pagebreak(weak: true)
+
+  /* -------------------------------- Headline -------------------------------- */
+
+  {
+    show text: set align(center)
+    set text(font: ffam-special)
+
+    show heading.where(level: 1): it => text(
+      size: fsize-headline.at(1),
+      fill: black,
+      weight: "bold",
+      it.body,
+    )
+
+    rect(
+      width: 100%,
+      radius: 0%,
+      inset: (top: 8pt, bottom: 9pt),
+      stroke: 0.7pt,
+    )[
+      #heading(level: 1, headline)
+    ]
+  }
+
+  /* --------------------------------- Outline -------------------------------- */
+
+  if outlined {
+    // Outline title style
+    show heading.where(level: 1): it => {
+      set align(left)
+      text(
+        size: fsize-appendices-outline-title.at(0),
+        font: ffam-special,
+        weight: "bold",
+      )[
+        #it.body
+        #v(vspacing-appendices-outline)
+      ]
+    }
+
+    show outline.entry: set text(font: ffam-base)
+
+    show outline.entry: it => link(
+      it.element.location(),
+      it.indented(
+        {
+          set text(weight: 500)
+          [#it.element.supplement ] + it.prefix() + if it.element.body != [] [ :] // Do not display `:` if the body is empty
+        },
+        it.inner(),
+      ),
+    )
+
+    // Display the outline
+    outline(
+      title: outline-title,
+      target: heading.where(level: 1).after(label(metadata-appendices-start)).before(label(metadata-appendices-end)),
+    )
+    v(0.5cm) // To set it apart from a potential heading
+
+    if pagebreak-after-outline {
+      pagebreak()
+    }
+  }
+
+  /* --------------------------------- Display -------------------------------- */
+
+  let _len = appendices.len()
+
+  // Capture the context before displaying the appendices
+  context {
+    // This requires the `context`
+    let _counter-snapshot = counter(heading).get()
+
+    // Apply appendices-specific heading numbering
+    set heading(numbering: style-numbering-appendices, bookmarked: false)
+
+    // Forbid the use of level 1 headings, as it's already what appendices' titles are
+    show heading: it => if it.level == 1 {
+      panic(
+        prefix-errors
+          + "Headings of `level == 1` aren't supported in appendices ; but you may use headings of `level >= 2` instead.",
+      )
+    } else if it.level <= 4 {
+      block(
+        width: 100%,
+      )[ #context counter(heading).display() #it.body ] // "reset" the style of other headings (see https://github.com/typst/typst/issues/420)
+    } else { it }
+
+    // Flag this point as the beginning of the appendix section
+    [ #metadata("appendix-start") #label(metadata-appendices-start) ]
+    counter(heading).update(0)
+
+    // Craft and display the different appendices
+    for i in range(_len) {
+      assert.eq(
+        type(appendices.at(i)),
+        dictionary,
+        message: prefix-errors
+          + "Got incorrect values in `appendices`. Each element of the array should be created using the `appendix` function.",
+      )
+      let _keys = appendices.at(i).keys()
+      assert(
+        "title" in _keys and "lbl" in _keys and "body" in _keys,
+        message: prefix-errors
+          + "Got incorrect values in `appendices`. Each element of the array should be created using the `appendix` function.",
+      )
+
+      // Display the title
+      appendix-heading-helper(
+        title: appendices.at(i).at("title"),
+        lbl: appendices.at(i).at("lbl"),
+        supplement: supplement,
+      )
+
+      // Display the body
+      appendices.at(i).at("body")
+
+      // And remember to insert a page break ; except for the last one
+      if i != _len - 1 { pagebreak() }
+    }
+
+    // Flag this point as the end of the appendix section
+    [ #metadata("appendix-end") #label(metadata-appendices-end) ]
+    // Restore the initial state of the counter
+    counter(heading).update(_counter-snapshot)
+  }
+}
diff --git a/packages/preview/ttuile/0.2.0/internal/logo-insa-lyon.png b/packages/preview/ttuile/0.2.0/internal/logo-insa-lyon.png
new file mode 100644
index 0000000000..90fda0fcbd
Binary files /dev/null and b/packages/preview/ttuile/0.2.0/internal/logo-insa-lyon.png differ
diff --git a/packages/preview/ttuile/0.2.0/template/main.typ b/packages/preview/ttuile/0.2.0/template/main.typ
new file mode 100644
index 0000000000..8432a9b896
--- /dev/null
+++ b/packages/preview/ttuile/0.2.0/template/main.typ
@@ -0,0 +1,193 @@
+//                      ttuile
+//                    ~~~~~~~~~~
+//             Find out more on github
+//         https://github.com/vitto4/ttuile
+//
+//
+// English-language report ?
+//  >> https://github.com/vitto4/ttuile#-notes
+
+
+// Imports : ttuile, appendices-section, appendix
+#import "@preview/ttuile:0.2.0": *
+
+
+#show: ttuile.with(
+  // This is one way to set the headline, but the following also works :
+  //  * headline: [You may supply just the title],
+  //  * headline: text(fill: blue, weight: "regular")[And even style it\ however you wish],
+  headline: (
+    lead: [Compte rendu de TP n°1 :],
+    title: [« #lorem(8) »],
+  ),
+  // Same as above :
+  //  * authors: [You may also supply whatever you want],
+  authors: (
+    "Theresa Tungsten",
+    "Jean Dupont",
+    "Eugene Deklan",
+  ),
+  group: "TD0",
+  footer-left: [Poste n°0],
+  footer-right: datetime.today().display("[day]/[month]/[year]"),
+  outlined: true,
+  // Also remove the uni logo or insert your own :
+  //  * logo: image("your-logo.png"),
+  //  * logo: none,
+)
+
+
+// You may delete everything below this line to get stared.
+//
+// If you're feeling lazy, give `Ctrl + Shift + End` a try :p
+//
+// <><><><><><><><><><><><><><><><><><><><><><><><><><><><><> //
+
+
+// ---------------------------------------------------------- //
+//                      Corps du rapport                      //
+// ---------------------------------------------------------- //
+
+// ---------------------- Introduction ---------------------- //
+
+= #lorem(1)
+
+#lorem(30)
+
+- #lorem(30)
+- #lorem(25)
+
+
+// Les équations sont numérotées par défaut
+$
+  E = m c^2 + "AI"
+$
+
+// Note de bas de page
+#lorem(28) #footnote([#lorem(27)])
+
+
+#{ 2 * linebreak() }
+
+
+// Création d'une figure
+#let liste-couleurs = (black, gray, silver, white, navy, blue, aqua, teal, eastern, purple, fuchsia, maroon, red, orange, yellow, olive, green, lime)
+
+#let couleurs = grid(
+  columns: 9 * (1fr,),
+  rows: 2,
+  row-gutter: 1em,
+  ..liste-couleurs.map(c => {
+    square(
+      size: 45pt,
+      fill: c,
+      stroke: if (black, navy, maroon).contains(c) { 1.2pt + silver } else { 1.2pt },
+    )
+  })
+)
+
+#figure(
+  couleurs,
+  caption: [#lorem(10)],
+)
+
+
+// Saut de page après l'introduction
+#pagebreak()
+
+
+// ---------------------- Deuxième page --------------------- //
+
+= #lorem(10) 
+
+// L'espacement entre les titres peut être modifié :
+//  * #show heading: set block(above: 18pt, below: 5pt)
+== #lorem(7)
+
+#lorem(26)
+
+// Il est possible de référencer une annexe définie à la fin du rapport
+@annexe-1 #lorem(10)
+
+== #lorem(2)
+
+#lorem(18)
+
+=== #lorem(6)
+
+#lorem(45) #linebreak()
+#lorem(35)
+
+// Les titres de niveau 4 ne sont pas affichés dans la table des matière
+// C'est le plus bas niveau actuellement supporté par le template, un titre de niveau 5 ne sera pas stylisé adéquatement
+==== #lorem(1)
+
+#lorem(30)
+
+// Cette équation n'est pas numérotée (contrairement à celle en introduction)
+#math.equation(
+  numbering: none,
+  block: true,
+  $
+    tilde(cal(T)) =
+    mat(
+      delim: "|",
+      1, 2, ..., 10;
+      2, 2, ..., 10;
+      dots.v, dots.v, dots.down, dots.v;
+      10, 10, ..., 10;
+      gap: #0.3em
+    )
+  $,
+)
+
+#lorem(45)
+
+==== #lorem(5)
+
+#lorem(50) #linebreak()
+#lorem(23)
+
+// ------------------------- Annexes ------------------------ //
+
+// Définition d'une annexe, elle reste pour l'instant simplement
+// stockée dans une variable et n'est pas immédiatement affichée
+// 
+// https://github.com/vitto4/ttuile/blob/main/docs/DOCS.md#appendix
+#let annexe-1 = appendix(
+  title: [#lorem(5)],
+  lbl: ,
+)[
+  // Le corps de l'annexe, i.e. ce qui est affiché sur la page en dessous du titre
+  #lorem(50)
+
+  #align(
+    center,
+  )[
+    *95% of people cannot solve this!*
+    #let rr = $thin \u{1f98f} thin$
+    #let bb = $thin \u{1f37b} thin$
+    #let ll = $thin \u{1f3ee} thin$
+
+    #{
+      set math.equation(numbering: none)
+      $
+        ll / (bb + rr) + bb / (ll + rr) + rr / (ll + bb) = 4
+      $
+    }
+
+    *Can you find positive whole values for $ll$, $bb$ and $rr$?*
+    // https://www.quora.com/How-do-you-find-the-positive-integer-solutions-to-frac-x-y+z-+-frac-y-z+x-+-frac-z-x+y-4/answer/Alon-Amit   >:3
+  ]
+
+  #lorem(45)
+]
+
+// https://github.com/vitto4/ttuile/blob/main/docs/DOCS.md#appendices-section
+#appendices-section(
+  // Ajouter les éventuelles autres annexes dans la liste
+  appendices: (annexe-1,),
+  // `true` par défaut, on demande l'affichage de la table des annexes
+  outlined: true,
+  pagebreak-after-outline: false,
+)
diff --git a/packages/preview/ttuile/0.2.0/thumbnail.png b/packages/preview/ttuile/0.2.0/thumbnail.png
new file mode 100644
index 0000000000..44634f6915
Binary files /dev/null and b/packages/preview/ttuile/0.2.0/thumbnail.png differ
diff --git a/packages/preview/ttuile/0.2.0/ttuile.typ b/packages/preview/ttuile/0.2.0/ttuile.typ
new file mode 100644
index 0000000000..bb860e241e
--- /dev/null
+++ b/packages/preview/ttuile/0.2.0/ttuile.typ
@@ -0,0 +1,300 @@
+/// Expose these to the end user
+#import "internal/helpers.typ": appendices-section, appendix
+
+#let ttuile(
+  /// Title of the report ; `dictionary | content?`
+  headline: none,
+  /// Authors of the report ; `array | content?`
+  authors: none,
+  /// Group number ; `content?`
+  group: none,
+  /// Usually the lab bench number ; `content?`
+  footer-left: none,
+  /// Usually the date at which the lab work/practical was carried out ; `content?`
+  footer-right: none,
+  /// Display the table of contents ? ; `bool`
+  outlined: true,
+  /// University logo to display ; `image | content?`
+  logo: image("internal/logo-insa-lyon.png"),
+  /// Body of the report
+  doc,
+) = {
+  /// Private imports
+  import "internal/helpers.typ": authors-helper, headline-helper
+  import "internal/defaults.typ": *
+
+  /* -------------------------------------------------------------------------- */
+  /*                                   Styling                                  */
+  /* -------------------------------------------------------------------------- */
+
+  /// Language and text
+  set text(
+    lang: "fr",
+    size: fsize-base,
+    font: ffam-base,
+    weight: "regular",
+    stretch: 100%,
+  )
+
+  /// Paragraphs
+  set align(left)
+  set par(justify: true)
+
+  /// Page format
+  set page(
+    paper: "a4",
+    margin: (
+      left: 2cm,
+      right: 2cm,
+      top: 2.5cm,
+      bottom: 2.5cm,
+    ),
+    header-ascent: 30%,
+    // Header with authors and uni logo
+    header: [
+      #set text(
+        font: ffam-special,
+        size: fsize-header,
+      )
+      // Spacing from the top of the page, somewhat of a magic number
+      #v(38.51pt)
+      #grid(
+        columns: (5fr, 1fr),
+        column-gutter: 1em,
+        // Authors, left aligned
+        align(left + horizon)[
+          #authors-helper(group, authors)
+        ],
+        // Uni logo (if applicable)
+        if (logo != none) {
+          set image(fit: "contain")
+          align(center + horizon, logo)
+        },
+      )
+    ],
+    footer-descent: 30%,
+    // Footer with page numbers
+    footer: [
+      // Formatting
+      #set text(
+        font: ffam-special,
+        size: fsize-footer,
+      )
+      #grid(
+        columns: (1fr, 1fr, 1fr),
+        align(left)[#footer-left],
+        // Page number
+        align(center)[
+          #context counter(page).display(
+            "ï¹£1 / 1ï¹£",
+            both: true,
+          )
+        ],
+        align(right)[#footer-right],
+      )
+    ],
+  )
+
+  /// Underline outgoing links only
+  show link: it => {
+    if (type(it.dest) == str) {
+      underline(it)
+    } else { it }
+  }
+
+  /// Heading numbering
+  /// See : https://www.reddit.com/r/typst/comments/18exrv5/hide_previous_level_heading_counters/
+  set heading(
+    numbering: (..nums) => {
+      let nums = nums.pos()
+      // Level of the current heading
+      let level = nums.len()
+
+      if level < 3 {
+        let style = style-numbering-h1 + style-numbering-h2
+        let num = nums
+        return numbering(style, ..num)
+      } else if level == 3 {
+        let style = style-numbering-h3
+        let num = nums.last()
+        return numbering(style, num)
+      } else { return none }
+    },
+  )
+
+  /// Lists
+  set list(
+    marker: ([•], [--]),
+    indent: indent-base,
+  )
+
+  /// Footnotes
+  show footnote.entry: set text(size: fsize-footnote)
+
+  /// Figures
+  show figure.caption: set text(
+    size: fsize-caption,
+    fill: color-blue-caption,
+    weight: "bold",
+  )
+  show figure.caption: it => [
+    #it.supplement #context it.counter.display() :
+    #set text(style: "italic"); #it.body
+  ]
+
+  /// Equations
+  set math.equation(numbering: "(1)")
+
+  /// References
+  show ref: it => {
+    // Default return value
+    let _content = it
+
+    // Underlining style
+    let stroke-style = (dash: "dotted", thickness: 0.8pt)
+
+    if it.element != none and (it.element.func() == heading) {
+      // Array of headings from the appendices section
+      let _appendix-headings = query(
+        heading.where().after(label(metadata-appendices-start)).before(label(metadata-appendices-end)),
+      )
+
+      if (it.element not in _appendix-headings) {
+        if it.element.level <= 3 {
+          let number = numbering(
+            // Cannot use `it.element.numbering` because for level 3 headings it's not the same as `style-numbering-base`
+            style-numbering-base,
+            // Here we specifically want the whole "I.1.A." do be displayed
+            ..counter(heading).at(it.element.location()),
+          )
+          _content = text(weight: 500, number)
+        } else if it.element.level == 4 {
+          _content = text(weight: 400, it.element.body)
+        } else {
+          panic(
+            prefix-errors
+              + "Cannot reference headings of `level >= 5` at the moment. Try defining your own `show ref: it => if it.element != none and (it.element.func() == heading) and it.element.level == _ {...} else {it}` for the levels you're interested in referencing.",
+          )
+        }
+      } else {
+        // Else, we are referencing a heading from the appendices section
+        let number = numbering(
+          style-numbering-appendices,
+          ..counter(heading).at(it.element.location()),
+        )
+        if it.element.level == 1 {
+          _content = text(weight: 500, style: "normal")[#it.element.supplement #number]
+        } else {
+          // TODO For now the author is free to add `Annexe` before reference to subsections. Maybe enforce `Annexe #number` ?
+          _content = text(number)
+        }
+      }
+      // Don't forget to link to the element
+      _content = link(it.target, _content)
+    }
+    return underline(stroke: stroke-style, _content)
+  }
+
+  /* -------------------------------- Headings -------------------------------- */
+
+  /// Waiting on https://github.com/typst/typst/issues/1699 to adjust heading spacing
+  /// See : https://github.com/typst/typst/issues/1699#issuecomment-2144928281
+
+  /// Positioning
+  show heading: set block(width: 100%)
+  show heading: set align(left)
+
+  /// Spacing
+  show heading: set block(above: vspacing-heading.at(0), below: vspacing-heading.at(1))
+
+  /// Formatting
+  show heading: it => {
+    // Counter or decoration
+    if it.level < 4 { context counter(heading).display() } else if it.level == 4 {
+      str.from-unicode(10146) // ➢
+    }
+    // Spacing between the decoration and the body
+    h(indent-numbering)
+    // Only underline level 1 through 3
+    if it.level < 4 { underline(it.body) } else { it.body }
+  }
+
+  /// Styling
+  show heading: it => {
+    // For headings level 1 through 4
+    set text(
+      size: fsize-heading.at(it.level - 1),
+      fill: color-heading.at(it.level - 1),
+    ) if it.level <= 4
+
+    set text(
+      font: ffam-heading,
+      weight: if (it.level == 4) { 600 } else { "bold" },
+    )
+
+    it
+  }
+
+  /// Indenting
+  ///
+  /// See also :
+  /// * https://github.com/typst/typst/discussions/2812
+  /// * https://forum.typst.app/t/how-can-i-indent-my-headings/3185/4
+  show heading: it => pad(
+    left: if (it.level < 4) { (it.level - 1) * indent-heading } else { 1.5 * indent-heading },
+    it,
+  )
+
+  /// Safeguard, there is no style defined for level 5 and above.
+  /// Has to be implemented this way, see https://github.com/typst/typst/issues/4950
+  ///
+  /// This snippet also has to come after the others, because at some point we return `it.body`
+  /// instead of `it`, which "strips" this very safeguard (that was acting on `it` only).
+  show heading: it => if it.level > 4 {
+    panic(
+      prefix-errors
+        + "Headings of `level > 4` aren't supported by the template ; but you can define your own style `show heading.where(level: _): it => ...`. Get rid of the error by replacing the dots with `it.body` for example.",
+    )
+  } else { it }
+
+  /* -------------------------------------------------------------------------- */
+  /*                                   Display                                  */
+  /* -------------------------------------------------------------------------- */
+
+  /// Headline
+  {
+    set align(center)
+    set text(font: ffam-special)
+    set rect(
+      width: 100%,
+      radius: 0%,
+      inset: (top: 10pt, bottom: 10pt),
+      stroke: 0.7pt,
+    )
+    if headline != none {
+      rect(headline-helper(headline))
+    }
+  }
+
+  /// Outline
+  set outline(
+    title: none,
+    indent: indent-outline,
+    depth: outline-depth,
+    target: heading
+      .where()
+      .before(label(metadata-appendices-start))
+      .or(heading.where().after(label(metadata-appendices-end))),
+  )
+
+  show outline: set text(size: fsize-base, font: ffam-outline)
+
+  if outlined {
+    // Spacing under the headline
+    if headline != none { v(0.3cm) }
+    outline()
+  }
+
+  /// Document body
+  doc
+}
diff --git a/packages/preview/ttuile/0.2.0/typst.toml b/packages/preview/ttuile/0.2.0/typst.toml
new file mode 100644
index 0000000000..1bbee06fd4
--- /dev/null
+++ b/packages/preview/ttuile/0.2.0/typst.toml
@@ -0,0 +1,17 @@
+[package]
+name = "ttuile"
+version = "0.2.0"
+compiler = "0.13.0"
+entrypoint = "ttuile.typ"
+authors = ["vitto <@vitto4>"]
+license = "MIT"
+categories = ["report"]
+repository = "https://github.com/vitto4/ttuile"
+keywords = ["insa", "engineering", "report", "tp", "cr", "compte-rendu"]
+disciplines = ["engineering"]
+description = "Students' lab reports at INSA Lyon, a french engineering school."
+
+[template]
+path = "template"
+entrypoint = "main.typ"
+thumbnail = "thumbnail.png"
\ No newline at end of file