diff --git a/content/CV.md b/content/CV.md
index b968ace..787c6ef 100644
--- a/content/CV.md
+++ b/content/CV.md
@@ -139,7 +139,9 @@ All supervision is at TU Delft, 2018-2025, unless noted otherwise.
| 7/2020 | Volunteer, Redders van Dordt, Dordrecht, Netherlands. _City-sponsored program to educate Dutch primary school children about flood risk and emergency response. Students built rafts and practiced water evacuation._ |
| 2020-2023 | Participant, Polder2Cās Project, Hedwige-Prosperpolder, Netherlands/Belgium. _Interreg project focused on flood risk and climate change adaptation along the North Sea and English Channel; collaborators in UK, NL, BE, FR._ |
-## INVITED PRESENTATIONS
+## OUTPUTS AND PUBLICATIONS
+
+### INVITED PRESENTATIONS
| | |
| :--- | :--- |
@@ -158,7 +160,7 @@ All supervision is at TU Delft, 2018-2025, unless noted otherwise.
| 12/2018 | Flood defense levees: a perspective from California. _Hydraulic Engineering Department Symposium, TU Delft._ |
| 2016 | Incremental risk of levee failure due to vegetation and animal burrowing. _Association of Engineering Geologists Student Night, Oakland, CA._ |
-## JOURNAL AND CONFERENCE PUBLICATION REVIEWER
+### JOURNAL AND CONFERENCE PUBLICATION REVIEWER
- Geotechnical Engineering Education 2025 Conference: International Society for Soil Mechanics and Geotechnical Engineering
- Journal of Geotechnical and Geoenvironmental Engineering
@@ -166,7 +168,7 @@ All supervision is at TU Delft, 2018-2025, unless noted otherwise.
- Georisk: Assessment and Management of Risk for Engineered Systems and Geohazards
- 31st European Safety and Reliability Conference (ESREL), 2021.
-## ONLINE TEXTBOOKS
+### ONLINE TEXTBOOKS
Lanzafame, R. C. (2024). Risk and Reliability for Engineers. TU Delft OPEN Publishing. doi:10.59490/tb.89\
_š [Link here](https://interactivetextbooks.tudelft.nl/risk-reliability/intro.html)_
@@ -181,7 +183,7 @@ van Woudenberg, T. R., Lanzafame, R. C., Kirsch, J. A. A., Jungbacker, C. A. A.,
_š [Link here](https://teachbooks.io/manual/)_
-## JOURNAL PAPERS
+### JOURNAL PAPERS
Mares Nasarre, P., van Boldrik, N., Bakker, E., Lanzafame, R., & Morales Napoles, O. (2025). Unlocking Student Choices: Assessing Student Preferences in Courses in Engineering Education. Education Sciences, 15(7), Article 859. doi:10.3390/educsci.15070859\
_š Link: [](https://doi.org/10.3390/educsci15070859)_
@@ -193,7 +195,7 @@ Reinders, K., Pouliasis, G., Lanzafame, R., & Morales, O. (2020). Evaluating the
Lanzafame, R., & Sitar, N. (2019). Reliability analysis of the influence of seepage on levee stability. Environmental Geotechnics, 6(5), 284-293.
-## TECHNICAL REPORTS
+### TECHNICAL REPORTS
Tsimopoulou, V., Koelewijn, A., Lanzafame, R., Rikkert, S., Aljer, A., Nguyen, S. Karaoulis, M., Idsinga, J., Kieftenburg, A. (2023) Management of harmful animal activities on levees: Fact finding fieldwork in the Living Lab Hedwige-Prosperpolder, Polder2Cās Project, Interreg European Regional Development Fund. https://polder2cs.eu/results/reports/flood-defence
@@ -201,7 +203,7 @@ Lanzafame, R. & Sitar, N. (2018). Reliability analysis of the influence of veget
Cohen-Waeber, J., Lanzafame, R., & Bray, J. (2014). Section 4: Effects of Surface Fault Rupture on Infrastructure, Geotechnical Engineering Reconnaissance of the August 24, 2014 M6 South Napa Earthquake. In J. D. Bray, J. Cohen-Waeber, T. Dawson, T. Kishida & N. Sitar (Eds.), GEER Association Report No. GEER--037 (Vol. Version 2).
-## CONFERENCE PAPERS AND PRESENTATIONS
+### CONFERENCE PAPERS AND PRESENTATIONS
Lanzafame, R., van Woudenberg, T. (2024). Online interactive textbooks: creating a book and using it with your students is easier than you think - we'll prove it! SURF Onderwijsdagen, Den Haag, Netherlands, doi:10.5281/zenodo.14068656\
_š Link: [](https://doi.org/10.5281/zenodo.14068656)_
@@ -220,7 +222,7 @@ Cohen-Waeber, J., Lanzafame, R., Bray, J., & Sitar, N. (2015). The Performance o
Mathy, D., Lanzafame, R., Adams, W., & Gallyer, S. (2012). Guided Boring and the Lafayette-Pleasant Hill Road Trunk Sewer. Paper presented at the North American Society for Trenchless Technology (NASTT) No-Dig Show 2012, Nashville, TN.
-## SOFTWARE AND PROGRAMMING RESOURCES
+### SOFTWARE AND PROGRAMMING RESOURCES
Lanzafame, R. (2024) Modelling, Uncertainty and Data for Engineers (MUDE) Files. https://github.com/TUDelft-MUDE/2024-files. CC BY 4.0 License. doi:10.5281/zenodo.16782515\
_š [Link here](https://github.com/TUDelft-MUDE/2024-files)_
diff --git a/myst.yml b/myst.yml
index 547830b..6368557 100644
--- a/myst.yml
+++ b/myst.yml
@@ -5,7 +5,7 @@ project:
authors:
- name: Robert Lanzafame
title: CV
- date: 2025-09-1
+ date: 2025-09-11
abstract: |
A description of the work life of Robert Lanzafame: teacher, engineer, co-founder of TeachBooks.
@@ -16,7 +16,8 @@ project:
title: Download CV as PDF
exports:
- format: typst
- template: lapreprint-typst
+ # template: lapreprint-typst
+ template: ./template
output: exports/CV_Robert_Lanzafame.pdf
id: output-pdf
diff --git a/template/.gitignore b/template/.gitignore
new file mode 100644
index 0000000..bb092b1
--- /dev/null
+++ b/template/.gitignore
@@ -0,0 +1,3 @@
+lapreprint.pdf
+frontmatter.pdf
+template.pdf
\ No newline at end of file
diff --git a/template/LICENSE b/template/LICENSE
new file mode 100644
index 0000000..500a33f
--- /dev/null
+++ b/template/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Rowan Cockett
+
+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/template/README.md b/template/README.md
new file mode 100644
index 0000000..c2ceb98
--- /dev/null
+++ b/template/README.md
@@ -0,0 +1,229 @@
+
LaPreprint for Typst
+
+
+
+
+
+
+
+ Easily create beautiful preprints in Typst
+
+
+
+
+
+```typst
+#import "lapreprint.typ": template
+
+#show: template.with(
+ title: "A beautiful preprint template"
+)
+```
+
+# Features
+
+With simple options you can enable/disable:
+
+- color schemes of blue, red, purple
+- author, ORCID, and affiliation support
+- branding and logo support
+- left margin with custom headings
+- date accepted, published & submitted
+- customizable font choices
+- running title, affiliation
+- multiple abstracts, e.g. plain language summary or english/french!
+- keywords, page count, nice headings
+- easily customize APA or IEEE citation style
+- Optional: full width after first page
+
+# Quick start
+
+The preprint template uses Typst (not LaTeX[^latex]) for typesetting, see [typst docs](https://typst.app/docs).
+
+[^latex]: If you are looking for LaPreprint for LaTeX, please see: https://github.com/roaldarbol/LaPreprint
+
+Copy [`lapreprint.typ`](./lapreprint.typ) to your own folder that you are working in, you only need the single `lapreprint.typ` file:
+
+```typst
+#import "lapreprint.typ": template
+```
+
+Take a look at the examples in the [GitHub repository](https://github.com/rowanc1/LaPreprint), for example, [this file](https://github.com/rowanc1/lapreprint/blob/main/examples/pixels/main.typ#L5), the basics are to use the template with a `#show` command:
+
+```typst
+#show: template.with(
+ title: "A beautiful preprint template"
+ // ... all sorts of other options that are explained below!
+)
+```
+
+## Logos and Branding
+
+The theme of the document can be set to a specific color, which changes the headers and links. The default `theme` is blue, however, the following examples use:
+
+```typst
+theme: red.darken(50%),
+```
+
+You can also supply a logo, which is either an image file location or content, allowing you to add additional information about the journal or lab-group to the top-right of the document. You can also set the `paper-size`, `heading-numbering` and `font-face`. The default font-face used is Noto Sans, which you may need to [download](https://fonts.google.com/noto/specimen/Noto+Sans).
+
+```typst
+logo: "my-logo.png",
+theme: purple.darken(20%),
+paper-size: "us-letter",
+heading-numbering: "1.a.i",
+font-face: "Noto Sans",
+```
+
+
+
+## Title and Subtitle
+
+You can have both a title and a subtitle:
+
+```typst
+title: "Pixels and their Neighbours",
+subtitle: "A Tutorial on Finite Volume",
+```
+
+Will become:
+
+
+
+## Authors and Affiliations
+
+You can add both author and affiliations lists, each author should have a `name`, and can optionally add `orcid`, `email`, and `affiliations`. The affiliations are just content that is put in superscript, e.g. `"1,2"`, have corresponding identifiers in the top level `affiliations` list, which requires both an `id` and a `name`. If you wish to include any additional information in the affiliation (e.g. an address, department, etc.), it is content and can have whatever you want in it.
+
+```typst
+authors: (
+ (
+ name: "Rowan Cockett",
+ orcid: "0000-0002-7859-8394",
+ email: "rowan@curvenote.com",
+ affiliations: "1,2"
+ ),
+ // Add other authors here...
+),
+affiliations: (
+ (id: "1", name: "University of British Columbia"),
+ (id: "2", name: "Curvenote Inc."),
+ ),
+```
+
+
+
+Note that the orcid and email icons are actually aligned to the text. Details, details!
+
+For other information that you wish to affiliate with a specific author, you can use the `affiliations` field with any identifier you like (e.g. `ā `) and then use the margin content or affiliations fields on the preprint to explain what it means.
+
+## Abstract and Keywords
+
+You can include one or more abstracts as well as keywords. For a simple `abstract` the default title used is "Abstract" and you can include it with:
+
+```typst
+abstract: lorem(100),
+keywords: ("Finite Volume", "Tutorial", "Reproducible Research"),
+```
+
+To include one or more specific abstracts, for example, different languages or a "Plain Language Summary", provide a list, with `title` and `content` in a dictionary:
+
+```typst
+abstract: (
+ (title: "Abstract", content: lorem(100)),
+ (title: "Plain Language Summary", content: lorem(25)),
+),
+```
+
+
+
+## Margin content
+
+The content on the first page is customizable. The first content is the `kind`, for example, "Original Research", "Review Article", "Retrospective" etc. And then the `date`, which is by default the date you compiled the document.
+
+```typst
+kind: "Notebook Tutorial",
+date: datetime(year: 2023, month: 08, day: 21),
+```
+
+You can also set `date` to be a dictionary or list of dictionaries with `title` and `date` as the two required keys. The first date will be bolded as well as used in the document metadata and auto `short-citation`.
+
+```typst
+kind: "Notebook Tutorial",
+date: (
+ (title: "Published", date: datetime(year: 2023, month: 08, day: 21)),
+ (title: "Accepted", date: datetime(year: 2022, month: 12, day: 10)),
+ (title: "Submitted", date: datetime(year: 2022, month: 12, day: 10)),
+),
+```
+
+
+
+The rest of the margin content can be set with `margin` property, which takes a `title` and `content`, content is required, however the title is optional.
+
+```typst
+margin: (
+ (
+ title: "Correspondence to",
+ content: [
+ Rowan Cockett\
+ #link("mailto:rowan@curvenote.com")[rowan\@curvenote.com]
+ ],
+ ),
+ // ... other properties
+)
+```
+
+You can use the margin property for things like funding, data availability statements, explicit correspondence requests, key points, conflict of interest statements, etc.
+
+
+
+### Setting the Margin
+
+The first page has a left hand margin of 25%, if you do nothing this will continue for your whole document. To override this, you can reset the margin after the first page by placing `#set page(margin: auto)` in a place where you want the page break between the first and second pages[^margin].
+
+If you opt for resetting to a full-width margin, you will want to not supply a `bibliography-file` to the template, and instead do this in your content, this is because the template will revert to the first-page margin as well as inserting a page break[^bug]. See the section on bibliography below.
+
+[^margin]: If you know a better way of doing this to automate it in the template, please open an issue or pull-request!!
+[^bug]: This seems like a bug to me in typst, but maybe is by design?
+
+## Headers and Footers
+
+You can control the headers and footer by providing the following information:
+
+```typst
+open-access: true,
+doi: "10.1190/tle35080703.1",
+venue: [ar#text(fill: red.darken(20%))[X]iv],
+short-title: "Finite Volume Tutorial",
+short-citation: auto,
+date: datetime.today()
+```
+
+The first page will show an open-access statement and the `doi` if available. For DOIs, only include the actual identifier, not the URL portions:
+
+
+
+Subsequent pages will show the `short-title` and `short-citation`. If the citation is `auto` (the default) it will be created in APA formatting using the paper authors.
+
+
+
+The footers show the `venue` (e.g. the journal or preprint repository) the `date` (which is by default `today()`) as well as the page count.
+
+
+
+## Bibliography
+
+The bibliography is only included in the theme if you supply the `bibliography-file` and an optional `bibliography-style`. The default `bibliography-style` is `"apa"`, you can override this if you like, for example, to `"ieee"`.
+
+If you have opted for full-page margins or have an appendix, you may want to place the bibliography yourself as including in the template will default back to the same margin as the first page. You can also handle the bibliography yourself with:
+
+```typst
+#{
+ show bibliography: set text(8pt)
+ bibliography("main.bib", title: text(10pt, "References"), style: "apa")
+}
+```
+
+# Acknowledgements
+
+The Typst LaPreprint template (and this Readme!) is inspired by [@roaldarbol LaTeX template](https://github.com/roaldarbol/LaPreprint), which is in the style of eLife and PLoS. Portions of the template were based on the example IEEE template in Typst.
diff --git a/template/examples/pixels/files/dc-eqns.png b/template/examples/pixels/files/dc-eqns.png
new file mode 100644
index 0000000..0d36c68
Binary files /dev/null and b/template/examples/pixels/files/dc-eqns.png differ
diff --git a/template/examples/pixels/files/dc-results.png b/template/examples/pixels/files/dc-results.png
new file mode 100644
index 0000000..9a39f4a
Binary files /dev/null and b/template/examples/pixels/files/dc-results.png differ
diff --git a/template/examples/pixels/files/dc-setup.png b/template/examples/pixels/files/dc-setup.png
new file mode 100644
index 0000000..6aef572
Binary files /dev/null and b/template/examples/pixels/files/dc-setup.png differ
diff --git a/template/examples/pixels/files/divergence.png b/template/examples/pixels/files/divergence.png
new file mode 100644
index 0000000..7396202
Binary files /dev/null and b/template/examples/pixels/files/divergence.png differ
diff --git a/template/examples/pixels/files/logo.png b/template/examples/pixels/files/logo.png
new file mode 100644
index 0000000..cac8a04
Binary files /dev/null and b/template/examples/pixels/files/logo.png differ
diff --git a/template/examples/pixels/files/mesh.png b/template/examples/pixels/files/mesh.png
new file mode 100644
index 0000000..3694960
Binary files /dev/null and b/template/examples/pixels/files/mesh.png differ
diff --git a/template/examples/pixels/files/screenshot.png b/template/examples/pixels/files/screenshot.png
new file mode 100644
index 0000000..38452ac
Binary files /dev/null and b/template/examples/pixels/files/screenshot.png differ
diff --git a/template/examples/pixels/files/weak-formulation.png b/template/examples/pixels/files/weak-formulation.png
new file mode 100644
index 0000000..5326c3e
Binary files /dev/null and b/template/examples/pixels/files/weak-formulation.png differ
diff --git a/template/examples/pixels/main.bib b/template/examples/pixels/main.bib
new file mode 100644
index 0000000..d129107
--- /dev/null
+++ b/template/examples/pixels/main.bib
@@ -0,0 +1,13 @@
+@article{Cockett_2016,
+ doi = {10.1190/tle35080703.1},
+ url = {https://doi.org/10.1190%2Ftle35080703.1},
+ year = 2016,
+ month = {aug},
+ publisher = {Society of Exploration Geophysicists},
+ volume = {35},
+ number = {8},
+ pages = {703--706},
+ author = {Rowan Cockett and Lindsey J. Heagy and Douglas W. Oldenburg},
+ title = {Pixels and their neighbors: Finite volume},
+ journal = {The Leading Edge}
+}
\ No newline at end of file
diff --git a/template/examples/pixels/main.pdf b/template/examples/pixels/main.pdf
new file mode 100644
index 0000000..37fd8af
Binary files /dev/null and b/template/examples/pixels/main.pdf differ
diff --git a/template/examples/pixels/main.typ b/template/examples/pixels/main.typ
new file mode 100644
index 0000000..36af6a2
--- /dev/null
+++ b/template/examples/pixels/main.typ
@@ -0,0 +1,195 @@
+// Update this import to where you put the `lapreprint.typ` file
+// It should probably be in the same folder
+#import "../../lapreprint.typ": template
+#import "../../frontmatter.typ": loadFrontmatter
+
+#show: template.with(
+ title: "Pixels and their Neighbours",
+ subtitle: "A Tutorial on Finite Volume",
+ short-title: "Finite Volume Tutorial",
+ venue: [ar#text(fill: red.darken(20%))[X]iv],
+ // This is relative to the template file
+ // When importing normally, you should be able to use it relative to this file.
+ logo: "examples/pixels/files/logo.png",
+ doi: "10.1190/tle35080703.1",
+ // You can make all dates optional, however, `date` is by default `datetime.today()`
+ date: (
+ (title: "Published", date: datetime(year: 2023, month: 08, day: 21)),
+ (title: "Accepted", date: datetime(year: 2022, month: 12, day: 10)),
+ (title: "Submitted", date: datetime(year: 2022, month: 12, day: 10)),
+ ),
+ theme: red.darken(50%),
+ authors: (
+ (
+ name: "Rowan Cockett",
+ orcid: "0000-0002-7859-8394",
+ email: "rowan@curvenote.com",
+ affiliations: "1,2"
+ ),
+ (
+ name: "Lindsey Heagy",
+ orcid: "0000-0002-1551-5926",
+ affiliations: "1",
+ ),
+ (
+ name: "Douglas Oldenburg",
+ orcid: "0000-0002-4327-2124",
+ affiliations: "1"
+ ),
+ ),
+ kind: "Notebook Tutorial",
+ affiliations: (
+ (id: "1", name: "University of British Columbia"),
+ (id: "2", name: "Curvenote Inc."),
+ ),
+ abstract: (
+ (title: "Abstract", content: lorem(100)),
+ (title: "Plain Language Summary", content: lorem(25)),
+ ),
+ keywords: ("Finite Volume", "Tutorial", "Reproducible Research"),
+ open-access: true,
+ margin: (
+ (
+ title: "Key Points",
+ content: [
+ - #lorem(10)
+ - #lorem(5)
+ - #lorem(7)
+ ],
+ ),
+ (
+ title: "Correspondence to",
+ content: [
+ Rowan Cockett\
+ #link("mailto:rowan@curvenote.com")[rowan\@curvenote.com]
+ ],
+ ),
+ (
+ title: "Data Availability",
+ content: [
+ Associated notebooks are available on #link("https://github.com/simpeg/tle-finitevolume")[GitHub] and can be run online with #link("http://mybinder.org/repo/simpeg/tle-finitevolume")[MyBinder].
+ ],
+ ),
+ (
+ title: "Funding",
+ content: [
+ Funding was provided by the Vanier Award to each of Cockett and Heagy.
+ ],
+ ),
+ (
+ title: "Competing Interests",
+ content: [
+ The authors declare no competing interests.
+ ],
+ ),
+ ),
+)
+
+
+This is the frontmatter that is loaded:
+
+#{
+ let front = loadFrontmatter(yaml("myst.yml"))
+ [#front]
+}
+
+
+= DC Resistivity
+
+DC resistivity surveys obtain information about subsurface electrical conductivity, $sigma$. This physical property is often diagnostic in mineral exploration, geotechnical, environmental and hydrogeologic problems, where the target of interest has a significant electrical conductivity contrast from the background. In a DC resistivity survey, steady state currents are set up in the subsurface by injecting current through a positive electrode and completing the circuit with a return electrode (@dc-setup).
+
+
+#figure(
+ image("files/dc-setup.png", width: 50%),
+ caption: [Setup of a DC resistivity survey.],
+)
+
+// You can put this after the content that fits on the first page to set the margins back to full-width
+#set page(margin: auto)
+
+The equations for DC resistivity are derived in (@dc-eqns). Conservation of charge (which can be derived by taking the divergence of Ampere's law at steady state) connects the divergence of the current density everywhere in space to the source term which consists of two point sources, one positive and one negative.
+
+The flow of current sets up electric fields according to Ohm's law, which relates current density to electric fields through the electrical conductivity. From Faraday's law for steady state fields, we can describe the electric field in terms of a scalar potential, $phi$, which we sample at potential electrodes to obtain data in the form of potential differences.
+
+#figure(
+ image("files/dc-eqns.png", width: 100%),
+ caption: [Derivation of the DC resistivity equations.],
+)
+
+To set up a solvable system of equations, we need the same number of unknowns as equations, in this case two unknowns (one scalar, $phi$, and one vector $arrow(j)$) and two first-order equations (one scalar, one vector).
+
+In this tutorial, we walk through setting up these first order equations in finite volume in three steps: (1) defining where the variables live on the mesh; (2) looking at a single cell to define the discrete divergence and the weak formulation; and (3) moving from a cell based view to the entire mesh to construct and solve the resulting matrix system. The notebooks included with this tutorial leverage the #link("http://simpeg.xyz/")[SimPEG] package, which extends the methods discussed here to various mesh types.
+
+= Where do things live?
+
+To bring our continuous equations into the computer, we need to discretize the earth and represent it using a finite(!) set of numbers. In this tutorial we will explain the discretization in 2D and generalize to 3D in the notebooks. A 2D (or 3D!) mesh is used to divide up space, and we can represent functions (fields, parameters, etc.) on this mesh at a few discrete places: the nodes, edges, faces, or cell centers. For consistency between 2D and 3D we refer to faces having area and cells having volume, regardless of their dimensionality. Nodes and cell centers naturally hold scalar quantities while edges and faces have implied directionality and therefore naturally describe vectors. The conductivity, $sigma$, changes as a function of space, and is likely to have discontinuities (e.g. if we cross a geologic boundary). As such, we will represent the conductivity as a constant over each cell, and discretize it at the center of the cell. The electrical current density, $arrow(j)$, will be continuous across conductivity interfaces, and therefore, we will represent it on the faces of each cell. Remember that $arrow(j)$ is a vector; the direction of it is implied by the mesh definition (i.e. in $x$, $y$ or $z$), so we can store the array $bold(j)$ as _scalars_ that live on the face and inherit the face's normal. When $arrow(j)$ is defined on the faces of a cell the potential, $phi$, will be put on the cell centers (since $arrow(j)$ is related to $phi$ through spatial derivatives, it allows us to approximate centered derivatives leading to a staggered, second-order discretization). Once we have the functions placed on our mesh, we look at a single cell to discretize each first order equation. For simplicity in this tutorial we will choose to have all of the faces of our mesh be aligned with our spatial axes ($x$, $y$ or $z$), the extension to curvilinear meshes will be presented in the supporting notebooks.
+
+#figure(
+ image("files/mesh.png", width: 100%),
+ caption: [Anatomy of a finite volume cell.],
+)
+
+= One cell at a time
+
+To discretize the first order differential equations we consider a single cell in the mesh and we will work through the discrete description of equations (1) and (2) over that cell.
+
+== In and out
+
+#figure(
+ image("files/divergence.png", width: 100%),
+ caption: [Geometrical definition of the divergence and the discretization.],
+)
+
+So we have half of the equation discretized - the left hand side. Now we need to take care of the source: it contains two dirac delta functions - these are infinite at their origins, $r_(s^+)$ and $r_(s^-)$. However, the volume integral of a delta function _is_ well defined: it is _unity_ if the volume contains the origin of the delta function otherwise it is _zero_.
+
+As such, we can integrate both sides of the equation over the volume enclosed by the cell. Since $bold(D) bold(j)$ is constant over the cell, the integral is simply a multiplication by the volume of the cell $"v"bold(D) bold(j)$. The integral of the source is zero unless one of the source electrodes is located inside the cell, in which case it is $q = plus.minus I$. Now we have a discrete description of equation 1 over a single cell:
+
+$ "v"bold(D) bold(j) = q $
+
+== Scalar equations only, please
+
+Equation @eq:div is a vector equation, so really it is two or three equations involving multiple components of $arrow(j)$. We want to work with a single scalar equation, allow for anisotropic physical properties, and potentially work with non-axis-aligned meshes - how do we do this?! We can use the *weak formulation* where we take the inner product ($integral arrow(a) dot.op arrow(b) d v$) of the equation with a generic face function, $arrow(f)$. This reduces requirements of differentiability on the original equation and also allows us to consider tensor anisotropy or curvilinear meshes.
+
+In @fig-weak-formulation, we visually walk through the discretization of equation (b). On the left hand side, a dot product requires a _single_ cartesian vector, $bold(j_x comma j_y)$. However, we have a $j$ defined on each face (2 $j_x$ and 2 $j_y$ in 2D!). There are many different ways to evaluate this inner product: we could approximate the integral using trapezoidal, midpoint or higher order approximations. A simple method is to break the integral into four sections (or 8 in 3D) and apply the midpoint rule for each section using the closest $bold(j)$ components to compose a cartesian vector. A $bold(P)_i$ matrix (size $2 times 4$) is used to pick out the appropriate faces and compose the corresponding vector (these matrices are shown with colors corresponding to the appropriate face in the figure). On the right hand side, we use a vector identity to integrate by parts. The second term will cancel over the entire mesh (as the normals of adjacent cell faces point in opposite directions) and $phi$ on mesh boundary faces are zero by the Dirichlet boundary condition. This leaves us with the divergence, which we already know how to do!
+
+#figure(
+ image("files/weak-formulation.png", width: 90%),
+ caption: [Discretization using the weak formulation and inner products.],
+)
+
+The final step is to recognize that, now discretized, we can cancel the general face function $bold(f)$ and transpose the result (for convention's sake):
+
+$ frac(1, 4) sum_(i = 1)^4 bold(P)_i^top sqrt(v) bold(Sigma)^(-1) sqrt(v) bold(P)_i bold(j) = bold(D)^top v phi $
+
+= All together now
+
+We have now discretized the two first order equations over a single cell. What is left is to assemble and solve the DC system over the entire mesh. To implement the divergence on the full mesh, the stencil of $plus.minus$1's must index into $bold(j)$ on the entire mesh (instead of four elements). Although this can be done in a `for-loop`, it is conceptually, and often computationally, easier to create this stencil using nested Kronecker Products (see notebook). The volume and area terms in the divergence get expanded to diagonal matrices, and we multiply them together to get the discrete divergence operator. The discretization of the _face_ inner product can be abstracted to a function, $bold(M)_f (sigma^(-1))$, that completes the inner product on the entire mesh at once. The main difference when implementing this is the $bold(P)$ matrices, which must index into the entire mesh. With the necessary operators defined for both equations on the entire mesh, we are left with two discrete equations:
+
+$ "diag"(bold(v)) bold(D) bold(j) = bold(q) $
+
+$ bold(M)_f (sigma^(-1)) bold(j) = bold(D)^top "diag"(bold(v)) phi $
+
+Note that now all variables are defined over the entire mesh. We could solve this coupled system or we could eliminate $bold(j)$ and solve for $phi$ directly (a smaller, second-order system).
+
+$ "diag"(bold(v)) bold(D) bold(M)_f (sigma^(-1))^(-1) bold(D)^top "diag"(bold(v)) phi = bold(q) $
+
+By solving this system matrix, we obtain a solution for the electric potential $phi$ everywhere in the domain. Creating predicted data from this requires an interpolation to the electrode locations and subtraction to obtain potential differences!
+
+#figure(
+ image("files/dc-results.png", width: 90%),
+ caption: [Electric potential on (a) tensor and (b) curvilinear meshes.],
+)
+
+Moving from continuous equations to their discrete analogues is fundamental in geophysical simulations. In this tutorial, we have started from a continuous description of the governing equations for the DC resistivity problem, selected locations on the mesh to discretize the continuous functions, constructed differential operators by considering one cell at a time, assembled and solved the discrete DC equations. Composing the finite volume system in this way allows us to move to different meshes and incorporate various types of boundary conditions that are often necessary when solving these equations in practice.
+
+Associated notebooks are available on #link("https://github.com/simpeg/tle-finitevolume")[GitHub] and can be run online with #link("http://mybinder.org/repo/simpeg/tle-finitevolume")[MyBinder].
+
+All article content, except where otherwise noted (including republished material), is licensed under a Creative Commons Attribution 3.0 Unported License (CC BY-SA). See #link("https://creativecommons.org/licenses/by-sa/3.0/")[https:\/\/creativecommons.org/licenses/by-sa/3.0/]. Distribution or reproduction of this work in whole or in part commercially or noncommercially requires full attribution of the @Cockett_2016, including its digital object identifier (DOI). Derivatives of this work must carry the same license. All rights reserved.
+
+
+// If you are using full-width pages, you must take care of your own bibliography.
+// Otherwise it will be on a separate page
+#{
+ show bibliography: set text(8pt)
+ bibliography("main.bib", title: text(10pt, "References"), style: "apa")
+}
diff --git a/template/examples/pixels/myst.yml b/template/examples/pixels/myst.yml
new file mode 100644
index 0000000..657f2d0
--- /dev/null
+++ b/template/examples/pixels/myst.yml
@@ -0,0 +1,21 @@
+title: Hello *World*
+short_title: cool
+author:
+ - name: Rowan Cockett
+ orcid: 0000-0000-0000-0000
+ affiliations:
+ - ubc
+ - curvenote
+ - name: Lindsey Heagy
+ orcid: 0000-0000-0000-0000
+ affiliation: ubc
+ - name: Doug
+ orcid: 0000-0000-0000-0000
+ affiliation: ubc
+affiliations:
+ - id: ubc
+ institution: University of British Columbia
+ - id: curvenote
+ institution: Curvenote Inc.
+open_access: true
+doi: 10.1190/tle35080703.1
diff --git a/template/frontmatter.typ b/template/frontmatter.typ
new file mode 100644
index 0000000..8435d6b
--- /dev/null
+++ b/template/frontmatter.typ
@@ -0,0 +1,244 @@
+#let orcidLogo(
+ // The ORCID identifier with no URL, e.g. `0000-0000-0000-0000`
+ orcid: none,
+) = {
+ /* Logos */
+ let orcidSvg = ``````.text
+
+ if (orcid == none) {
+ // Do not
+ box(height: 1.1em, baseline: 13.5%, [#image.decode(orcidSvg)])
+ return
+ }
+
+ link("https://orcid.org/" + orcid)[#box(height: 1.1em, baseline: 13.5%)[#image(bytes(orcidSvg))]]
+}
+
+#let validateString(raw, name, alias: none) = {
+ if (name in raw) {
+ assert(type(raw.at(name)) == str, message: name + " must be a string")
+ return raw.at(name)
+ }
+ if (type(alias) != array) {
+ return
+ }
+ for a in alias {
+ if (a in raw) {
+ assert(type(raw.at(a)) == str, message: a + " must be a string")
+ return raw.at(a)
+ }
+ }
+}
+
+#let validateBoolean(raw, name, alias: none) = {
+ if (name in raw) {
+ assert(type(raw.at(name)) == bool, message: name + " must be a boolean")
+ return raw.at(name)
+ }
+ if (type(alias) != array) {
+ return
+ }
+ for a in alias {
+ if (a in raw) {
+ assert(type(raw.at(a)) == bool, message: a + " must be a boolean")
+ return raw.at(a)
+ }
+ }
+}
+
+
+#let validateAffiliation(raw) = {
+ let out = (:)
+ if (type(raw) == str) {
+ out.name = raw;
+ return out;
+ }
+ let id = validateString(raw, "id")
+ if (id != none) { out.id = id }
+ let name = validateString(raw, "name")
+ if (name != none) { out.name = name }
+ let institution = validateString(raw, "institution")
+ if (institution != none) { out.institution = institution }
+ let department = validateString(raw, "department")
+ if (department != none) { out.department = department }
+ let doi = validateString(raw, "doi")
+ if (doi != none) { out.doi = doi }
+ let ror = validateString(raw, "ror")
+ if (ror != none) { out.ror = ror }
+ let address = validateString(raw, "address")
+ if (address != none) { out.address = address }
+ let city = validateString(raw, "city")
+ if (city != none) { out.city = city }
+ let region = validateString(raw, "region", alias: ("state", "province"))
+ if (region != none) { out.region = region }
+ let postal-code = validateString(raw, "postal-code", alias: ("postal_code", "postalCode", "zip_code", "zip-code", "zipcode", "zipCode"))
+ if (postal-code != none) { out.postal-code = postal-code }
+ let country = validateString(raw, "country")
+ if (country != none) { out.country = country }
+ let phone = validateString(raw, "phone")
+ if (phone != none) { out.phone = phone }
+ let fax = validateString(raw, "fax")
+ if (fax != none) { out.fax = fax }
+ let email = validateString(raw, "email")
+ if (email != none) { out.email = email }
+ let url = validateString(raw, "url")
+ if (url != none) { out.url = url }
+ let collaboration = validateBoolean(raw, "collaboration")
+ if (collaboration != none) { out.collaboration = collaboration }
+ return out;
+}
+
+#let pickAffiliationsObject(raw) = {
+ if ("affiliation" in raw and "affiliations" in raw) {
+ panic("You can only use `affiliation` or `affiliations`, not both")
+ }
+ if ("affiliation" in raw) {
+ raw.affiliations = raw.affiliation
+ }
+ if ("affiliations" not in raw) { return; }
+ if (type(raw.affiliations) == str or type(raw.affiliations) == dictionary) {
+ // convert to a list
+ return (validateAffiliation(raw.affiliations),)
+ } else if (type(raw.affiliations) == array) {
+ // validate each entry
+ return raw.affiliations.map(validateAffiliation)
+ } else {
+ panic("The `affiliation` or `affiliations` must be a array, dictionary or string, got:", type(raw.affiliations))
+ }
+}
+
+
+#let validateAuthor(raw) = {
+ let out = (:)
+ if (type(raw) == str) {
+ out.name = raw;
+ return out;
+ }
+ let name = validateString(raw, "name")
+ if (name != none) { out.name = name }
+ let orcid = validateString(raw, "orcid")
+ if (orcid != none) { out.orcid = orcid }
+ let email = validateString(raw, "email")
+ if (email != none) { out.email = email }
+ let url = validateString(raw, "url")
+ if (url != none) { out.url = url }
+
+ let affiliations = pickAffiliationsObject(raw);
+ if (affiliations != none) { out.affiliations = affiliations } else { out.affiliations = () }
+
+ return out;
+}
+
+#let consolidateAffiliations(authors, affiliations) = {
+ let cnt = 0
+ for affiliation in affiliations {
+ if ("id" not in affiliation) {
+ affiliation.insert("id", "aff-" + str(cnt + 1))
+ }
+ affiliations.at(cnt) = affiliation
+ cnt += 1
+ }
+
+ let authorCnt = 0
+ for author in authors {
+ let affCnt = 0
+ for affiliation in author.affiliations {
+ let pos = affiliations.position(item => { ("id" in item and item.id == affiliation.name) or ("name" in item and item.name == affiliation.name) })
+ if (pos != none) {
+ affiliation.remove("name")
+ affiliation.id = affiliations.at(pos).id
+ affiliations.at(pos) = affiliations.at(pos) + affiliation
+ } else {
+ affiliation.id = if ("id" in affiliation) { affiliation.id } else { affiliation.name }
+ affiliations.push(affiliation)
+ }
+ author.affiliations.at(affCnt) = (id: affiliation.id)
+ affCnt += 1
+ }
+ authors.at(authorCnt) = author
+ authorCnt += 1
+ }
+
+ // Now that they are normalized, loop again and update the numbers
+ let fullAffCnt = 0
+ let authorCnt = 0
+ for author in authors {
+ let affCnt = 0
+ for affiliation in author.affiliations {
+ let pos = affiliations.position(item => { item.id == affiliation.id })
+ let aff = affiliations.at(pos)
+ if ("index" not in aff) {
+ fullAffCnt += 1
+ aff.index = fullAffCnt
+ affiliations.at(pos) = affiliations.at(pos) + (index: fullAffCnt)
+ }
+ author.affiliations.at(affCnt) = (id: affiliation.id, index: aff.index)
+ affCnt += 1
+ }
+ authors.at(authorCnt) = author
+ authorCnt += 1
+ }
+ return (authors: authors, affiliations: affiliations)
+}
+
+#let loadFrontmatter(raw) = {
+ let out = (:)
+ let title = validateString(raw, "title")
+ if (title != none) { out.title = title }
+ let subtitle = validateString(raw, "subtitle")
+ if (subtitle != none) { out.subtitle = subtitle }
+ let short-title = validateString(raw, "short-title", alias: ("short_title", "shortTitle", "runningHead",))
+ if (short-title != none) { out.short-title = short-title }
+
+ // author information
+ if ("author" in raw and "authors" in raw) {
+ panic("You can only use `author` or `authors`, not both")
+ }
+ if ("author" in raw) {
+ raw.authors = raw.author
+ }
+ if ("authors" in raw) {
+ if (type(raw.authors) == str or type(raw.authors) == dictionary) {
+ // convert to a list
+ out.authors = (validateAuthor(raw.authors),)
+ } else if (type(raw.authors) == array) {
+ // validate each entry
+ out.authors = raw.authors.map(validateAuthor)
+ } else {
+ panic("The `author` or `authors` must be a array, dictionary or string, got:", type(raw.authors))
+ }
+ }
+
+ let affiliations = pickAffiliationsObject(raw);
+ if (affiliations != none) { out.affiliations = affiliations } else { out.affiliations = () }
+
+ let open-access = validateBoolean(raw, "open-access", alias: ("open_access", "openAccess",))
+ if (open-access != none) { out.open-access = open-access }
+ let doi = validateString(raw, "doi")
+ if (doi != none) {
+ assert(not doi.starts-with("http"), message: "DOIs should not include the link, use only the part after `https://doi.org/[]`")
+ out.doi = doi
+ }
+
+ let date = none;
+ let citation = validateString(raw, "citation")
+ if (citation != none) {
+ out.citation = citation;
+ } else {
+ // Create a citation, e.g. Cockett et al., 2023
+ let year = if (date != none) { ", " + date.display("[year]") } else { ", " + datetime.today().display("[year]") }
+ if (out.authors.len() == 1) {
+ out.citation = out.authors.at(0).name.split(" ").last() + year
+ } else if (out.authors.len() == 2) {
+ out.citation = out.authors.at(0).name.split(" ").last() + " & " + out.authors.at(1).name.split(" ").last() + year
+ } else if (out.authors.len() > 2) {
+ out.citation = out.authors.at(0).name.split(" ").last() + " " + emph("et al.") + year
+ }
+ }
+
+ let consolidated = consolidateAffiliations(out.authors, out.affiliations)
+ out.authors = consolidated.authors
+ out.affiliations = consolidated.affiliations
+
+ return out
+}
diff --git a/template/lapreprint.typ b/template/lapreprint.typ
new file mode 100644
index 0000000..062959a
--- /dev/null
+++ b/template/lapreprint.typ
@@ -0,0 +1,310 @@
+#import "frontmatter.typ": orcidLogo, loadFrontmatter
+
+#let template(
+ // The paper's title.
+ title: "Paper Title",
+ subtitle: none,
+
+ // An array of authors. For each author you can specify a name, orcid, and affiliations.
+ // affiliations should be content, e.g. "1", which is shown in superscript and should match the affiliations list.
+ // Everything but but the name is optional.
+ authors: (),
+ // This is the affiliations list. Include an id and `name` in each affiliation. These are shown below the authors.
+ affiliations: (),
+ // The paper's abstract. Can be omitted if you don't have one.
+ abstract: none,
+ // The short-title is shown in the running header
+ short-title: none,
+ // The short-citation is shown in the running header, if set to auto it will show the author(s) and the year in APA format.
+ short-citation: auto,
+ // The venue is show in the footer
+ venue: none,
+ // An image path that is shown in the top right of the page. Can also be content.
+ logo: none,
+ // A DOI link, shown in the header on the first page. Should be just the DOI, e.g. `10.10123/123456` ,not a URL
+ doi: none,
+ heading-numbering: "1.a.i",
+ // Show an Open Access badge on the first page, and support open science, default is true, because that is what the default should be.
+ open-access: true,
+ // A list of keywords to display after the abstract
+ keywords: (),
+ // The "kind" of the content, e.g. "Original Research", this is shown as the title of the margin content on the first page.
+ kind: none,
+ // Content to put on the margin of the first page
+ // Should be a list of dicts with `title` and `content`
+ margin: (),
+ paper-size: "us-letter",
+ // A color for the theme of the document
+ theme: blue.darken(10%),
+ // Date published, for example, when you publish your preprint to an archive server.
+ // To hide the date, set this to `none`. You can also supply a list of dicts with `title` and `date`.
+ date: datetime.today(),
+ // Feel free to change this, the font applies to the whole document
+ font-face: none,
+ // The path to a bibliography file if you want to cite some external works.
+ bibliography-file: none,
+ bibliography-style: "apa",
+ // The paper's content.
+ body
+) = {
+ let spacer = text(fill: gray)[#h(8pt) | #h(8pt)]
+
+ let dates;
+ if (type(date) == datetime) {
+ dates = ((title: "Published", date: date),)
+ } else if (type(date) == dictionary) {
+ dates = (date,)
+ } else {
+ dates = date
+ }
+ date = dates.at(0).date
+
+ // Create a short-citation, e.g. Cockett et al., 2023
+ let year = if (date != none) { ", " + date.display("[year]") }
+ if (short-citation == auto and authors.len() == 1) {
+ short-citation = authors.at(0).name.split(" ").last() + year
+ } else if (short-citation == auto and authors.len() == 2) {
+ short-citation = authors.at(0).name.split(" ").last() + " & " + authors.at(1).name.split(" ").last() + year
+ } else if (short-citation == auto and authors.len() > 2) {
+ short-citation = authors.at(0).name.split(" ").last() + " " + emph("et al.") + year
+ } else if (short-citation == auto) {
+ short-citation = none
+ }
+
+ // Set document metadata.
+ set document(title: title, author: authors.map(author => author.name))
+
+ show link: it => [#text(fill: theme)[#it]]
+ show ref: it => [#text(fill: theme)[#it]]
+
+ set page(
+ paper-size,
+ margin: (left: 25%),
+ header: context {
+ let loc = here()
+ if(loc.page() == 1) {
+ let headers = (
+ if (open-access) {smallcaps[Open Access]},
+ if (doi != none) { link("https://doi.org/" + doi, "https://doi.org/" + doi)}
+ )
+ return align(left, text(size: 8pt, fill: gray, headers.filter(header => header != none).join(spacer)))
+ } else {
+ return align(right, text(size: 8pt, fill: gray.darken(50%),
+ (short-title, short-citation).join(spacer)
+ ))
+ }
+ },
+ footer: block(
+ width: 100%,
+ stroke: (top: 1pt + gray),
+ inset: (top: 8pt, right: 2pt),
+ context [
+ #grid(columns: (75%, 25%),
+ align(left, text(size: 9pt, fill: gray.darken(50%),
+ (
+ if(venue != none) {emph(venue)},
+ if(date != none) {date.display("[month repr:long] [day], [year]")}
+ ).filter(t => t != none).join(spacer)
+ )),
+ align(right)[
+ #text(
+ size: 9pt, fill: gray.darken(50%)
+ )[
+ #counter(page).display() of #counter(page).final().first()
+ ]
+ ]
+ )
+ ]
+ )
+ )
+
+ // Set the body font.
+ if (font-face != none) {
+ set text(font: font-face, size: 10pt)
+ } else {
+ set text(size: 10pt)
+ }
+ // Configure equation numbering and spacing.
+ set math.equation(numbering: "(1)")
+ show math.equation: set block(spacing: 1em)
+
+ // Configure lists.
+ set enum(indent: 10pt, body-indent: 9pt)
+ set list(indent: 10pt, body-indent: 9pt)
+
+ // Configure headings.
+ set heading(numbering: heading-numbering)
+ show heading: it => context {
+ let loc = here()
+ // Find out the final number of the heading counter.
+ let levels = counter(heading).at(loc)
+ set text(10pt, weight: 400)
+ if it.level == 1 [
+ // First-level headings are centered smallcaps.
+ // We don't want to number of the acknowledgment section.
+ #let is-ack = it.body in ([Acknowledgment], [Acknowledgement])
+ // #set align(center)
+ #set text(if is-ack { 10pt } else { 12pt })
+ #show: smallcaps
+ #v(20pt, weak: true)
+ #if it.numbering != none and not is-ack {
+ numbering(heading-numbering, ..levels)
+ [.]
+ h(7pt, weak: true)
+ }
+ #it.body
+ #v(13.75pt, weak: true)
+ ] else if it.level == 2 [
+ // Second-level headings are run-ins.
+ #set par(first-line-indent: 0pt)
+ #set text(style: "italic")
+ #v(10pt, weak: true)
+ #if it.numbering != none {
+ numbering(heading-numbering, ..levels)
+ [.]
+ h(7pt, weak: true)
+ }
+ #it.body
+ #v(10pt, weak: true)
+ ] else [
+ // Third level headings are run-ins too, but different.
+ #if it.level == 3 {
+ numbering(heading-numbering, ..levels)
+ [. ]
+ }
+ _#(it.body):_
+ ]
+ }
+
+
+ if (logo != none) {
+ place(
+ top,
+ dx: -33%,
+ float: false,
+ box(
+ width: 27%,
+ {
+ if (type(logo) == content) {
+ logo
+ } else {
+ image(logo, width: 100%)
+ }
+ },
+ ),
+ )
+ }
+
+
+ // Title and subtitle
+ box(inset: (bottom: 2pt), width: 100%, text(17pt, weight: "bold", fill: theme, title))
+ if subtitle != none {
+ parbreak()
+ box(width: 100%, text(14pt, fill: gray.darken(30%), subtitle))
+ }
+ // Authors and affiliations
+ if authors.len() > 0 {
+ box(inset: (y: 10pt), {
+ authors.map(author => {
+ text(11pt, weight: "semibold", author.name)
+ h(1pt)
+ if "affiliations" in author {
+ super(author.affiliations)
+ }
+ if "orcid" in author {
+ orcidLogo(orcid: author.orcid)
+ }
+ }).join(", ", last: ", and ")
+ })
+ }
+ if affiliations.len() > 0 {
+ box(inset: (bottom: 10pt), {
+ affiliations.map(affiliation => {
+ super(affiliation.id)
+ h(1pt)
+ affiliation.name
+ }).join(", ")
+ })
+ }
+
+
+ place(
+ left + bottom,
+ dx: -33%,
+ dy: -10pt,
+ box(width: 27%, {
+ if (kind != none) {
+ show par: set par(spacing: 0em)
+ text(11pt, fill: theme, weight: "semibold", smallcaps(kind))
+ parbreak()
+ }
+ if (dates != none) {
+ let formatted-dates
+
+ grid(columns: (40%, 60%), gutter: 7pt,
+ ..dates.zip(range(dates.len())).map((formatted-dates) => {
+ let d = formatted-dates.at(0);
+ let i = formatted-dates.at(1);
+ let weight = "light"
+ if (i == 0) {
+ weight = "bold"
+ }
+ return (
+ text(size: 7pt, fill: theme, weight: weight, d.title),
+ text(size: 7pt, d.date.display("[month repr:short] [day], [year]"))
+ )
+ }).flatten()
+ )
+ }
+ v(2em)
+ grid(columns: 1, gutter: 2em, ..margin.map(side => {
+ text(size: 7pt, {
+ if ("title" in side) {
+ text(fill: theme, weight: "bold", side.title)
+ [\ ]
+ }
+ set enum(indent: 0.1em, body-indent: 0.25em)
+ set list(indent: 0.1em, body-indent: 0.25em)
+ side.content
+ })
+ }))
+ }),
+ )
+
+
+ let abstracts
+ if (type(abstract) == content or type(abstract) == str) {
+ abstracts = ((title: "Abstract", content: abstract),)
+ } else {
+ abstracts = abstract
+ }
+
+if (abstracts != none and abstracts.len() > 0) {
+ box(inset: (top: 16pt, bottom: 16pt), width: 100%, stroke: (top: 1pt + gray, bottom: 1pt + gray), {
+ abstracts.map(abs => {
+ set par(justify: true)
+ text(fill: theme, weight: "semibold", size: 9pt, abs.title)
+ parbreak()
+ abs.content
+ }).join(parbreak())
+ })
+ }
+ if (keywords.len() > 0) {
+ text(size: 9pt, {
+ text(fill: theme, weight: "semibold", "Keywords")
+ h(8pt)
+ keywords.join(", ")
+ })
+ }
+ v(10pt)
+
+ show par: set par(spacing: 1.5em)
+
+ // Display the paper's contents.
+ body
+
+ if (bibliography-file != none) {
+ show bibliography: set text(8pt)
+ bibliography(bibliography-file, title: text(10pt, "References"), style: bibliography-style)
+ }
+}
diff --git a/template/template.typ b/template/template.typ
new file mode 100644
index 0000000..cfbe11c
--- /dev/null
+++ b/template/template.typ
@@ -0,0 +1,126 @@
+#import "lapreprint.typ": *
+#show: template.with(
+ title: align(center, "[-doc.title-]"),
+[# if parts.abstract or parts.summary #]
+ abstract: (
+[# if parts.abstract #]
+ (
+ title: "Abstract",
+ content: [
+[-parts.abstract-]
+ ]
+ ),
+[# endif #]
+[# if parts.summary #]
+ (
+ title: "Plain Language Summary",
+ content: [
+[-parts.summary-]
+ ]
+ ),
+[# endif #]
+ ),
+[# endif #]
+[# if doc.subtitle #]
+ subtitle: "[-doc.subtitle-]",
+[# endif #]
+[# if doc.short_title #]
+ short-title: "[-doc.short_title-]",
+[# endif #]
+[# if options.short_citation #]
+ short-citation: "[-options.short_citation-]",
+[# endif #]
+[# if options.heading_numbering #]
+ heading-numbering: "[-options.heading_numbering-]",
+[# endif #]
+[# if doc.open_access !== undefined #]
+ open-access: [-doc.open_access-],
+[# endif #]
+[# if doc.doi #]
+ doi: "[-doc.doi-]",
+[# endif #]
+[# if doc.date #]
+ date: datetime(
+ year: [-doc.date.year-],
+ month: [-doc.date.month-],
+ day: [-doc.date.day-],
+ ),
+[# endif #]
+[# if doc.keywords #]
+ keywords: (
+ [#- for keyword in doc.keywords -#]"[-keyword-]",[#- endfor -#]
+ ),
+[# endif #]
+[# if doc.bibtex #]
+ bibliography-file: "[-doc.bibtex-]",
+[# endif #]
+ authors: (
+[# for author in doc.authors #]
+ (
+ name: "[-author.name-]",
+[# if author.orcid #]
+ orcid: "[-author.orcid-]",
+[# endif #]
+[# if author.affiliations #]
+ affiliations: "[#- for aff in author.affiliations -#][-aff.index-][#- if not loop.last -#],[#- endif -#][#- endfor -#]",
+[# endif #]
+ ),
+[# endfor #]
+ ),
+ affiliations: (
+[# for aff in doc.affiliations #]
+ (
+ id: "[-aff.index-]",
+ name: "[-aff.name-]",
+ ),
+[# endfor #]
+ ),
+[# if doc.venue.title #]
+ venue: "[-doc.venue.title-]",
+[# endif #]
+[# if options.logo #]
+ logo: "[-options.logo-]",
+[# endif #]
+[# if options.kind #]
+ kind: "[-options.kind-]",
+[# endif #]
+ margin: (
+[# if parts.acknowledgements #]
+ (
+ title: "Acknowledgements",
+ content: [
+[-parts.acknowledgements-]
+ ],
+ ),
+[# endif #]
+[# if parts.availability #]
+ (
+ title: "Data Availability",
+ content: [
+[-parts.availability-]
+ ],
+ ),
+[# endif #]
+ ),
+)
+
+
+
+[-IMPORTS-]
+
+#pagebreak(
+ weak: false,
+ to: "odd",
+)
+
+#set heading(numbering: "1.")
+#outline(
+ depth: 2,
+)
+
+#pagebreak(
+ weak: false,
+ to: "odd",
+)
+
+[-CONTENT-]
diff --git a/template/template.yml b/template/template.yml
new file mode 100644
index 0000000..7917aa0
--- /dev/null
+++ b/template/template.yml
@@ -0,0 +1,76 @@
+jtex: v1
+kind: typst
+title: LaPreprint Typst Template
+description: Easily create beautiful preprints in Typst
+template: template.typ
+files:
+ - lapreprint.typ
+ - frontmatter.typ
+ - template.typ
+ - LICENSE
+authors:
+ - name: Rowan Cockett
+ github: rowanc1
+ affiliations:
+ - Curvenote
+ - name: Franklin Koch
+ github: fwkoch
+ affiliations:
+ - Curvenote
+version: 0.0.1
+license: MIT
+tags:
+ - preprint
+ - article
+ - paper
+thumbnail: ./examples/pixels/files/screenshot.png
+github: https://github.com/myst-templates/lapreprint-typst
+doc:
+ - id: title
+ required: true
+ - id: authors
+ required: true
+ - id: subtitle
+ - id: short_title
+ - id: open_access
+ - id: keywords
+ - id: doi
+ - id: venue
+options:
+ - id: logo
+ type: file
+ description: An image path that is shown in the top right of the page
+ - id: kind
+ type: string
+ description: The "kind" of the content, e.g. "Original Research" - shown as the title of the margin content on the first page
+ - id: short_citation
+ type: string
+ description: |
+ The short citation used in the document header. By default, it
+ is automatically determined from the author names.
+ - id: heading_numbering
+ type: string
+ description: |
+ Heading numbering style.
+ See also https://typst.app/docs/reference/model/numbering/.
+
+parts:
+ - id: abstract
+ description: >
+ An abstract is a short summary of your research paper or report. A good
+ abstract will prepare readers for the detailed information to follow,
+ communicate the essence of the article and help readers take away and remember key points.
+ max_words: 500
+ required: false
+ - id: summary
+ description: Plain language summary
+ max_words: 500
+ required: false
+ - id: acknowledgements
+ description: >
+ Acknowledgements printed in the margin
+ max_words: 500
+ - id: availability
+ description: >
+ Data availability statement printed in the margin
+ max_words: 500