diff --git a/packages/preview/fun-article/0.2.0/LICENSE b/packages/preview/fun-article/0.2.0/LICENSE new file mode 100644 index 0000000000..fdd6524a08 --- /dev/null +++ b/packages/preview/fun-article/0.2.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Casper van Elteren + +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/fun-article/0.2.0/README.md b/packages/preview/fun-article/0.2.0/README.md new file mode 100644 index 0000000000..ebcc859d9c --- /dev/null +++ b/packages/preview/fun-article/0.2.0/README.md @@ -0,0 +1,50 @@ +# Fun Article + +Fun Article is a compact two-column article template for Typst. It provides a bold justified drop-cap abstract, structured authors and affiliations, ORCID badges, running headers, page totals, and an optional significance note. + +## Usage + +Create a new project from the template: + +```sh +typst init @preview/fun-article:0.2.0 +``` + +Or import it in an existing document: + +```typ +#import "@preview/fun-article:0.2.0": appendix, fun-article + +#show: fun-article.with( + title: "A Short Article", + authors: ( + (name: "Ada Lovelace", affils: "1", is-corresponding: true), + ), + affiliations: ( + (id: "*", name: "Corresponding author: ada@example.com"), + (id: "1", name: "Example University"), + ), + abstract: "A concise abstract goes here.", + significance: [ + State why the article matters in one or two sentences. + ], +) + += Introduction + +Write the article here. +``` + +## Parameters + +- `title`: Article title. +- `authors`: Array of dictionaries with `name`, optional `affils`, optional `orcid`, and optional `is-corresponding`. +- `affiliations`: Array of dictionaries with `id` and `name`. +- `abstract`: Abstract text or content. The abstract renders in bold with the drop cap kept inside the paragraph. +- `significance`: Optional content for the significance box. +- `paper-size`: Page size, defaulting to `"a4"`. +- `cols`: Number of body columns, defaulting to `2`. + +## License + +MIT diff --git a/packages/preview/fun-article/0.2.0/lib.typ b/packages/preview/fun-article/0.2.0/lib.typ new file mode 100644 index 0000000000..a4dd06317e --- /dev/null +++ b/packages/preview/fun-article/0.2.0/lib.typ @@ -0,0 +1,245 @@ +#import "@preview/droplet:0.3.1": dropcap + +#let fun-article( + title: "", + authors: (), + affiliations: (), + abstract: [], + significance: none, + paper-size: "a4", + cols: 2, + body, +) = { + set document(title: title, author: authors.map(a => a.name)) + + let dark-red = rgb("#8b0000") + let dark-goldenrod = rgb("#b8860b") + let linen = rgb("#faf0e6") + let orcid-color = rgb("#a6ce39") + let body-font = ("Libertinus Serif", "New Computer Modern") + let sans-font = ("Noto Sans", "DejaVu Sans") + + set page( + paper: paper-size, + margin: (top: 1.5cm, bottom: 1.5cm, left: 2cm, right: 2cm), + header: context { + if counter(page).get().at(0) > 1 { + set align(center) + set text(style: "italic", size: 9pt, font: body-font) + title + } + }, + footer: context { + line(length: 100%, stroke: 0.4pt) + set text(size: 8pt, font: sans-font) + let page-count = counter(page).get().at(0) + let total-pages = counter(page).final().at(0) + grid( + columns: (1fr, 1fr), + [], + align(right)[ + Page #page-count of #total-pages + ], + ) + }, + ) + + set text(font: body-font, size: 9pt) + set par(leading: 0.45em, justify: true) + show heading: set text(font: sans-font, weight: "bold", fill: dark-red) + + let split-first-sentence(body) = { + if type(body) == str { + let sentence-end = body.position(regex("[.!?](\\s|$)")) + if sentence-end == none { + (lead: body, rest: none, found-end: false) + } else { + let end = sentence-end + 1 + let rest = body.slice(end) + ( + lead: body.slice(0, end), + rest: if rest == "" { none } else { rest }, + found-end: true, + ) + } + } else if type(body) != content { + (lead: body, rest: none, found-end: false) + } else if body.func() == text { + let sentence-end = body.text.position(regex("[.!?](\\s|$)")) + if sentence-end == none { + (lead: body, rest: none, found-end: false) + } else { + let end = sentence-end + 1 + let rest = body.text.slice(end) + ( + lead: [#body.text.slice(0, end)], + rest: if rest == "" { none } else { [#rest] }, + found-end: true, + ) + } + } else if body.fields().at("children", default: none) != none { + let lead = () + let rest = () + let found-end = false + for child in body.fields().at("children") { + if found-end { + rest.push(child) + } else { + let split = split-first-sentence(child) + lead.push(split.lead) + if split.rest != none { + rest.push(split.rest) + } + found-end = split.found-end + } + } + ( + lead: lead.sum(), + rest: if rest.len() == 0 { none } else { rest.sum() }, + found-end: found-end, + ) + } else { + (lead: body, rest: none, found-end: false) + } + } + + show heading.where(level: 1): it => { + set text(size: 11pt) + v(1.5em, weak: true) + it + v(0.5em, weak: true) + } + + show figure.caption.where(kind: image): it => block(width: 100%)[ + #set align(start) + #set par(justify: true) + #let sentence = split-first-sentence(it.body) + #if sentence.rest == none { + strong([#it.supplement #context it.counter.display(it.numbering)#it.separator #sentence.lead]) + } else { + [#strong([#it.supplement #context it.counter.display(it.numbering)#it.separator #sentence.lead])#sentence.rest] + } + ] + + let orcid-icon(id) = if id != none [ + #h(2pt) + #link("https://orcid.org/" + id)[ + #box(baseline: 20%, width: 1.1em, height: 1.1em)[ + #place(center + horizon, circle(radius: 0.45em, fill: orcid-color, stroke: none)) + #place(center + horizon, text(size: 0.5em, fill: white, weight: "bold", font: sans-font, "iD")) + ] + ] + ] + + let title-block = { + set align(left) + v(1em) + set text(font: sans-font, weight: "bold", fill: dark-red) + block(text(size: 14pt, title)) + + v(8pt) + + let author-blocks = () + for (i, author) in authors.enumerate() { + let affils = author.at("affils", default: ()) + let markers = if type(affils) == array { affils.join(",") } else { str(affils) } + if author.at("is-corresponding", default: false) { + markers = "*" + if markers != "" { "," + markers } else { "" } + } + let orcid = author.at("orcid", default: none) + author-blocks.push([ + #text(size: 11pt, weight: "bold", fill: black, author.name)#super(markers)#orcid-icon(orcid) + ]) + if i < authors.len() - 1 { + author-blocks.push([, ]) + } + } + block(author-blocks.sum()) + + v(4pt) + + set text(size: 8pt, font: sans-font, style: "italic", weight: "regular", fill: black) + for affiliation in affiliations { + block([#super(affiliation.id) #affiliation.name]) + } + + v(4pt) + line(length: 100%, stroke: 1pt + dark-goldenrod) + v(12pt) + } + + let has-abstract = abstract != none and abstract != [] and abstract != "" + let abstract-content = if has-abstract { + block(width: 100%)[ + #set text(weight: "bold") + #set par(justify: true) + #dropcap( + height: 3, + gap: 3pt, + hanging-indent: 1em, + overhang: 0pt, + fill: dark-goldenrod, + justify: true, + )[#abstract] + ] + } + + let has-significance = significance != none and significance != [] and significance != "" + let significance-box = if has-significance { + v(0.5em) + block( + width: 100%, + fill: linen.lighten(85%), + stroke: 0.8pt + dark-goldenrod, + radius: 2mm, + inset: (top: 14pt, rest: 10pt), + { + place( + top + left, + dx: 10pt, + dy: -21pt, + block( + fill: rgb("#f5f5f5"), + stroke: 0.7pt + dark-goldenrod, + radius: 1.5mm, + inset: (x: 6pt, y: 3pt), + text(size: 8pt, weight: "bold", fill: dark-red, font: sans-font, "Significance"), + ), + ) + set text(size: 8pt, weight: "regular") + significance + }, + ) + } + + title-block + + if has-abstract and has-significance { + grid( + columns: (2.2fr, 1fr), + column-gutter: 20pt, + abstract-content, + significance-box, + ) + } else if has-abstract { + block(width: 100%, abstract-content) + } else if has-significance { + block(width: 100%, significance-box) + } + + v(1em) + line(length: 100%, stroke: 0.5pt + dark-goldenrod) + v(1.5em) + + if cols > 1 { + columns(cols, gutter: 7mm, body) + } else { + body + } +} + +#let appendix(body) = { + set heading(numbering: "A.", supplement: [Appendix]) + counter(heading).update(0) + body +} diff --git a/packages/preview/fun-article/0.2.0/template/main.typ b/packages/preview/fun-article/0.2.0/template/main.typ new file mode 100644 index 0000000000..73a653911e --- /dev/null +++ b/packages/preview/fun-article/0.2.0/template/main.typ @@ -0,0 +1,48 @@ +#import "@preview/fun-article:0.2.0": appendix, fun-article + +#show: fun-article.with( + title: "When Good Ideas Wear Comfortable Shoes", + authors: ( + (name: "Ada Lovelace", affils: "1", orcid: "0000-0000-0000-0000", is-corresponding: true), + (name: "Grace Hopper", affils: "2"), + ), + affiliations: ( + (id: "*", name: "Corresponding author: ada@example.com"), + (id: "1", name: "Department of Curious Systems, Example University"), + (id: "2", name: "Institute for Practical Imagination"), + ), + abstract: "This article demonstrates a compact two-column research layout with a drop-cap abstract, author metadata, affiliations, running headers, and a significance note. It is intended as a lightweight starting point for papers that want a formal structure with a warmer editorial voice.", + significance: [ + Use the significance note for the shortest possible statement of why the work matters. + ], +) + += Introduction + +Fun Article is a small research-paper template for writing concise articles with a two-column body, a prominent abstract, and a highlighted significance statement. It keeps the page economical while leaving enough visual character for essays, working papers, and short reports. + +The template accepts structured author data, affiliation markers, ORCID identifiers, and a configurable page size. The body is ordinary Typst content, so equations, figures, tables, references, and appendices work as they do in any other document. + +== A Compact Section + +Use headings to divide the article into ordinary sections. The template sets restrained heading styles and a comfortable body measure for dense text. + +You can include mathematics inline, such as $a^2 + b^2 = c^2$, or display equations when the argument needs room: + +$ + f(x) = integral_0^x exp(-t^2) dif t +$ + += Methods + +Replace this sample text with your article. The initialized project imports the published package with an absolute package import, so it will keep compiling when created with `typst init`. + += Conclusion + +The template is intentionally small: import it, fill in the metadata, and write the article. + +#show: appendix + += Optional Notes + +Appendices use lettered headings after the `appendix` show rule. diff --git a/packages/preview/fun-article/0.2.0/thumbnail.png b/packages/preview/fun-article/0.2.0/thumbnail.png new file mode 100644 index 0000000000..51b5a1b465 Binary files /dev/null and b/packages/preview/fun-article/0.2.0/thumbnail.png differ diff --git a/packages/preview/fun-article/0.2.0/typst.toml b/packages/preview/fun-article/0.2.0/typst.toml new file mode 100644 index 0000000000..5de6d7b802 --- /dev/null +++ b/packages/preview/fun-article/0.2.0/typst.toml @@ -0,0 +1,15 @@ +[package] +name = "fun-article" +version = "0.2.0" +entrypoint = "lib.typ" +authors = ["Casper van Elteren "] +license = "MIT" +description = "Playful two-column research article." +categories = ["paper"] +keywords = ["article", "research", "two-column", "dropcap"] +compiler = "0.15.0" + +[template] +path = "template" +entrypoint = "main.typ" +thumbnail = "thumbnail.png"