@anja.codes/create-vitakit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +22 -2
- package/index.mjs +139 -0
- package/package.json +38 -4
- package/template/.gitignore.template +19 -0
- package/template/_forgejo/workflows/deploy.yml +31 -0
- package/template/_github/workflows/cloudflare.yml +31 -0
- package/template/_github/workflows/cv.yml +36 -0
- package/template/_github/workflows/deploy.yml +53 -0
- package/template/app/app.vue +5 -0
- package/template/app/assets/css/main.css +117 -0
- package/template/app/layouts/default.vue +39 -0
- package/template/app/pages/[...slug].vue +30 -0
- package/template/app/pages/cv.vue +39 -0
- package/template/app/pages/index.vue +62 -0
- package/template/app/pages/publications.vue +25 -0
- package/template/content/education.yml +18 -0
- package/template/content/employment.yml +18 -0
- package/template/content/pages/cv.md +9 -0
- package/template/content/pages/index.md +11 -0
- package/template/content/pages/publications.md +8 -0
- package/template/content/pages/research.md +32 -0
- package/template/content/profile.yml +34 -0
- package/template/content/publications.bib +43 -0
- package/template/content/supervision.yml +12 -0
- package/template/content/talks.yml +17 -0
- package/template/content/teaching.yml +17 -0
- package/template/cv-tex/cv.tex +38 -0
- package/template/cv-tex/cv.yml +7 -0
- package/template/cv-tex/latexmkrc +5 -0
- package/template/cv-tex/preamble.tex +10 -0
- package/template/cv-tex/prose/research-statement.tex +17 -0
- package/template/nuxt.config.ts +32 -0
- package/template/package.json.template +34 -0
- package/template/public/img/portrait.svg +6 -0
- package/template/public/img/tree.svg +10 -0
- package/template/tsconfig.json +9 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anja Ressmann, Benjamin Hackl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @anja.codes/create-vitakit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffold a [vitakit](https://github.com/anja-codes/vitakit) project: an academic
|
|
4
|
+
website and a LaTeX CV built from one set of files.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pnpm create @anja.codes/vitakit my-site
|
|
8
|
+
cd my-site
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm dev # http://localhost:3000
|
|
11
|
+
pnpm build:cv # cv-tex/cv.pdf (needs a TeX distribution)
|
|
12
|
+
pnpm check # what CI runs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It asks for your name, position, affiliation, email and a deployment target
|
|
16
|
+
(GitHub Pages, Cloudflare Pages, rsync, or none), copies the template and
|
|
17
|
+
substitutes those few values.
|
|
18
|
+
|
|
19
|
+
This is the only scaffolding step vitakit has. Everything afterwards — a new
|
|
20
|
+
page, a new CV variant, a new section — is adding or copying a file in a
|
|
21
|
+
repository you own.
|
|
22
|
+
|
|
23
|
+
MIT
|
package/index.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pnpm create @anja.codes/vitakit [directory]
|
|
4
|
+
*
|
|
5
|
+
* Copies the template, substitutes the few things that are personal, and picks
|
|
6
|
+
* a deployment workflow. Deliberately the *only* scaffolding step vitakit has:
|
|
7
|
+
* everything afterwards is editing files in a repository you own.
|
|
8
|
+
*/
|
|
9
|
+
import { cp, mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
10
|
+
import { existsSync } from 'node:fs'
|
|
11
|
+
import { basename, join, resolve } from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
import { createInterface } from 'node:readline/promises'
|
|
14
|
+
import process from 'node:process'
|
|
15
|
+
|
|
16
|
+
const TEMPLATE = fileURLToPath(new URL('template', import.meta.url))
|
|
17
|
+
|
|
18
|
+
const DEPLOYMENTS = {
|
|
19
|
+
'github-pages': { label: 'GitHub Pages (public repository)', keep: ['deploy.yml', 'cv.yml'], host: '_github' },
|
|
20
|
+
'cloudflare': { label: 'Cloudflare Pages (works with a private repository)', keep: ['cloudflare.yml', 'cv.yml'], host: '_github' },
|
|
21
|
+
'rsync': { label: 'Web space via rsync (Forgejo)', keep: ['deploy.yml'], host: '_forgejo' },
|
|
22
|
+
'none': { label: 'None for now', keep: [], host: undefined },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function slugify(value) {
|
|
26
|
+
return value.normalize('NFKD').replace(/[\u0300-\u036f]/g, '')
|
|
27
|
+
.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'my-site'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Replace the placeholders the template uses. */
|
|
31
|
+
function fill(text, answers) {
|
|
32
|
+
return text
|
|
33
|
+
.replaceAll('__NAME__', answers.name)
|
|
34
|
+
.replaceAll('__TITLE__', answers.title)
|
|
35
|
+
.replaceAll('__AFFILIATION__', answers.affiliation)
|
|
36
|
+
.replaceAll('__EMAIL__', answers.email)
|
|
37
|
+
.replaceAll('__SLUG__', answers.slug)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const TEXT_FILE = /\.(md|ya?ml|json|ts|vue|css|bib|tex|svg|txt|yml|template)$/
|
|
41
|
+
|
|
42
|
+
async function scaffold(target, answers) {
|
|
43
|
+
await mkdir(target, { recursive: true })
|
|
44
|
+
await cp(TEMPLATE, target, { recursive: true })
|
|
45
|
+
|
|
46
|
+
// Files that cannot be shipped under their final name: `.gitignore` is
|
|
47
|
+
// excluded by npm, and `package.json` would make the template a workspace.
|
|
48
|
+
for (const [from, to] of [['.gitignore.template', '.gitignore'], ['package.json.template', 'package.json']]) {
|
|
49
|
+
await rename(join(target, from), join(target, to))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const deployment = DEPLOYMENTS[answers.deployment]
|
|
53
|
+
for (const [dir, name] of [['_github', '.github'], ['_forgejo', '.forgejo']]) {
|
|
54
|
+
const path = join(target, dir)
|
|
55
|
+
if (deployment.host !== dir) {
|
|
56
|
+
await rm(path, { recursive: true, force: true })
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
const workflows = join(path, 'workflows')
|
|
60
|
+
for (const file of await readdir(workflows)) {
|
|
61
|
+
if (!deployment.keep.includes(file)) await rm(join(workflows, file))
|
|
62
|
+
}
|
|
63
|
+
await rename(path, join(target, name))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Substitute placeholders in every text file.
|
|
67
|
+
const walk = async (dir) => {
|
|
68
|
+
for (const item of await readdir(dir, { withFileTypes: true })) {
|
|
69
|
+
const path = join(dir, item.name)
|
|
70
|
+
if (item.isDirectory()) await walk(path)
|
|
71
|
+
else if (TEXT_FILE.test(item.name) || item.name === 'package.json' || item.name === '.gitignore') {
|
|
72
|
+
await writeFile(path, fill(await readFile(path, 'utf8'), answers))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
await walk(target)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
const [directoryArgument] = process.argv.slice(2).filter(argument => !argument.startsWith('-'))
|
|
81
|
+
const interactive = process.stdin.isTTY && !process.env.VITAKIT_CREATE_DEFAULTS
|
|
82
|
+
|
|
83
|
+
const answers = {
|
|
84
|
+
name: 'Your Name',
|
|
85
|
+
title: 'Postdoctoral Researcher',
|
|
86
|
+
affiliation: 'Department of Mathematics, University of Graz',
|
|
87
|
+
email: 'you@example.org',
|
|
88
|
+
deployment: 'github-pages',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let directory = directoryArgument
|
|
92
|
+
|
|
93
|
+
if (interactive) {
|
|
94
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
95
|
+
const ask = async (question, fallback) => {
|
|
96
|
+
const answer = (await rl.question(`${question} (${fallback}) `)).trim()
|
|
97
|
+
return answer || fallback
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log('\nvitakit — a website and a LaTeX CV from one set of files.\n')
|
|
101
|
+
answers.name = await ask('Your name:', answers.name)
|
|
102
|
+
answers.title = await ask('Your position:', answers.title)
|
|
103
|
+
answers.affiliation = await ask('Affiliation:', answers.affiliation)
|
|
104
|
+
answers.email = await ask('Email:', answers.email)
|
|
105
|
+
directory ??= await ask('Directory:', slugify(answers.name))
|
|
106
|
+
|
|
107
|
+
console.log('\nDeployment:')
|
|
108
|
+
const keys = Object.keys(DEPLOYMENTS)
|
|
109
|
+
keys.forEach((key, index) => console.log(` ${index + 1}. ${DEPLOYMENTS[key].label}`))
|
|
110
|
+
const choice = await ask('Choose:', '1')
|
|
111
|
+
answers.deployment = keys[Number(choice) - 1] ?? 'github-pages'
|
|
112
|
+
|
|
113
|
+
rl.close()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
directory ??= slugify(answers.name)
|
|
117
|
+
answers.slug = slugify(basename(directory))
|
|
118
|
+
const target = resolve(process.cwd(), directory)
|
|
119
|
+
|
|
120
|
+
if (existsSync(target) && (await readdir(target)).length) {
|
|
121
|
+
throw new Error(`${directory} already exists and is not empty`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await scaffold(target, answers)
|
|
125
|
+
|
|
126
|
+
console.log(`\nCreated ${directory}\n`)
|
|
127
|
+
console.log(' cd ' + directory)
|
|
128
|
+
console.log(' pnpm install')
|
|
129
|
+
console.log(' pnpm dev # http://localhost:3000')
|
|
130
|
+
console.log(' pnpm build:cv # cv-tex/cv.pdf (needs LaTeX)')
|
|
131
|
+
console.log(' pnpm check # what CI runs\n')
|
|
132
|
+
console.log('Then edit content/: profile.yml, the section files and publications.bib.')
|
|
133
|
+
console.log('The PDF is not part of the website build; assume this repository is private.\n')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main().catch((error) => {
|
|
137
|
+
console.error(`\n@anja.codes/create-vitakit: ${error instanceof Error ? error.message : String(error)}\n`)
|
|
138
|
+
process.exit(1)
|
|
139
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anja.codes/create-vitakit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a vitakit project: an academic website and a LaTeX CV from one set of files.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"vitakit",
|
|
8
|
+
"create",
|
|
9
|
+
"scaffold",
|
|
10
|
+
"cv",
|
|
11
|
+
"academic",
|
|
12
|
+
"nuxt",
|
|
13
|
+
"latex"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/anja-codes/vitakit/tree/main/create#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/anja-codes/vitakit/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/anja-codes/vitakit.git",
|
|
22
|
+
"directory": "create"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"create-vitakit": "index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.mjs",
|
|
30
|
+
"template",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.18"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Nuxt
|
|
2
|
+
.output
|
|
3
|
+
.nuxt
|
|
4
|
+
.data
|
|
5
|
+
.nitro
|
|
6
|
+
.cache
|
|
7
|
+
dist
|
|
8
|
+
node_modules
|
|
9
|
+
|
|
10
|
+
# vitakit / latexmk output
|
|
11
|
+
# Everything generated lives in cv-tex/generated/ (the .cls and all auxiliary
|
|
12
|
+
# files included); only the finished PDF is written beside the sources.
|
|
13
|
+
cv-tex/generated
|
|
14
|
+
cv-tex/*.pdf
|
|
15
|
+
|
|
16
|
+
# Misc
|
|
17
|
+
*.log
|
|
18
|
+
.DS_Store
|
|
19
|
+
.env
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Builds the site and deploys it to web space via rsync.
|
|
2
|
+
#
|
|
3
|
+
# Secret: SSH_DEPLOY_KEY (private key with write access to the target).
|
|
4
|
+
# Adjust NUXT_APP_BASE_URL and the rsync target below.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
deploy:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
env:
|
|
14
|
+
NUXT_APP_BASE_URL: /
|
|
15
|
+
steps:
|
|
16
|
+
- uses: https://github.com/actions/checkout@v7
|
|
17
|
+
- run: apt update && apt install -y rsync
|
|
18
|
+
- uses: https://github.com/pnpm/action-setup@v6
|
|
19
|
+
# The runner image may ship an older Node than Nuxt requires.
|
|
20
|
+
- uses: https://github.com/actions/setup-node@v7
|
|
21
|
+
with:
|
|
22
|
+
node-version: 22
|
|
23
|
+
cache: pnpm
|
|
24
|
+
- run: pnpm install --frozen-lockfile
|
|
25
|
+
- run: pnpm check
|
|
26
|
+
- uses: https://github.com/webfactory/ssh-agent@v0.9.0
|
|
27
|
+
with:
|
|
28
|
+
ssh-private-key: ${{ secrets.SSH_DEPLOY_KEY }}
|
|
29
|
+
- run: |
|
|
30
|
+
rsync -e "ssh -o StrictHostKeyChecking=no" -av --delete \
|
|
31
|
+
.output/public/ user@example.org:/var/www/your-site/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Builds the site and deploys it to Cloudflare Pages.
|
|
2
|
+
#
|
|
3
|
+
# Recommended when the repository is private: Cloudflare Pages builds from
|
|
4
|
+
# private repositories on the free tier, which GitHub Pages does not.
|
|
5
|
+
#
|
|
6
|
+
# Secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID.
|
|
7
|
+
# Set `projectName` to your Pages project.
|
|
8
|
+
name: Deploy (Cloudflare Pages)
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [main]
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
- uses: pnpm/action-setup@v6
|
|
21
|
+
- uses: actions/setup-node@v7
|
|
22
|
+
with:
|
|
23
|
+
node-version: 22
|
|
24
|
+
cache: pnpm
|
|
25
|
+
- run: pnpm install --frozen-lockfile
|
|
26
|
+
- run: pnpm check
|
|
27
|
+
- uses: cloudflare/wrangler-action@v4
|
|
28
|
+
with:
|
|
29
|
+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
30
|
+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
31
|
+
command: pages deploy .output/public --project-name=__SLUG__
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Builds the PDF CV and attaches it to the workflow run.
|
|
2
|
+
#
|
|
3
|
+
# The PDF is an artifact, not part of the website: it may contain entries
|
|
4
|
+
# marked `visibility: cv` (address, phone, referees). Download it from the run
|
|
5
|
+
# summary. Runs on demand, and on pushes that touch CV material.
|
|
6
|
+
name: CV
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
push:
|
|
11
|
+
branches: [main]
|
|
12
|
+
paths: ['content/**', 'cv-tex/**']
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
- uses: pnpm/action-setup@v6
|
|
20
|
+
- uses: actions/setup-node@v7
|
|
21
|
+
with:
|
|
22
|
+
node-version: 22
|
|
23
|
+
cache: pnpm
|
|
24
|
+
- run: pnpm install --frozen-lockfile
|
|
25
|
+
# Generate the .tex files here: the LaTeX action runs in its own container,
|
|
26
|
+
# without Node. cv-tex/latexmkrc lets it find the generated document class.
|
|
27
|
+
- run: pnpm build:cv --no-pdf
|
|
28
|
+
- uses: xu-cheng/latex-action@v4
|
|
29
|
+
with:
|
|
30
|
+
working_directory: cv-tex
|
|
31
|
+
root_file: cv.tex
|
|
32
|
+
latexmk_use_lualatex: false
|
|
33
|
+
- uses: actions/upload-artifact@v5
|
|
34
|
+
with:
|
|
35
|
+
name: cv
|
|
36
|
+
path: cv-tex/*.pdf
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Builds the site and publishes it to GitHub Pages.
|
|
2
|
+
#
|
|
3
|
+
# Setup: Settings -> Pages -> Source: "GitHub Actions".
|
|
4
|
+
#
|
|
5
|
+
# NUXT_APP_BASE_URL must match where the site is served from. For a project
|
|
6
|
+
# site at https://<user>.github.io/<repo>/ keep the line below; for a user site
|
|
7
|
+
# or a custom domain, delete it. A wrong value breaks every asset URL.
|
|
8
|
+
name: Deploy
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [main]
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
pages: write
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: pages
|
|
22
|
+
cancel-in-progress: true
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
env:
|
|
28
|
+
NUXT_APP_BASE_URL: /${{ github.event.repository.name }}/
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v7
|
|
31
|
+
# No `version:` here: the pnpm version comes from `packageManager` in
|
|
32
|
+
# package.json. Setting both fails with ERR_PNPM_BAD_PM_VERSION.
|
|
33
|
+
- uses: pnpm/action-setup@v6
|
|
34
|
+
- uses: actions/setup-node@v7
|
|
35
|
+
with:
|
|
36
|
+
node-version: 22 # keep in sync with engines.node
|
|
37
|
+
cache: pnpm
|
|
38
|
+
- run: pnpm install --frozen-lockfile
|
|
39
|
+
# typecheck + generate + assertions on the generated HTML
|
|
40
|
+
- run: pnpm check
|
|
41
|
+
- uses: actions/upload-pages-artifact@v5
|
|
42
|
+
with:
|
|
43
|
+
path: .output/public
|
|
44
|
+
|
|
45
|
+
deploy:
|
|
46
|
+
needs: build
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
environment:
|
|
49
|
+
name: github-pages
|
|
50
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
51
|
+
steps:
|
|
52
|
+
- id: deployment
|
|
53
|
+
uses: actions/deploy-pages@v5
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Design tokens: the first stop for restyling. Tailwind v4 reads these, so
|
|
4
|
+
`text-primary` and `font-serif` follow from here. */
|
|
5
|
+
@theme {
|
|
6
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
7
|
+
--font-serif: "Lora", ui-serif, Georgia, serif;
|
|
8
|
+
--color-primary: oklch(48% 0.13 250);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
@apply font-sans text-neutral-900 antialiased;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
a {
|
|
16
|
+
@apply text-primary underline underline-offset-2 hover:no-underline;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* --- vitakit components ------------------------------------------------ */
|
|
20
|
+
|
|
21
|
+
.vita-entry {
|
|
22
|
+
@apply grid gap-1 py-2 sm:grid-cols-[8rem_1fr] sm:gap-4;
|
|
23
|
+
}
|
|
24
|
+
.vita-entry__date {
|
|
25
|
+
@apply text-sm text-neutral-500 tabular-nums;
|
|
26
|
+
}
|
|
27
|
+
.vita-entry__title {
|
|
28
|
+
@apply font-medium;
|
|
29
|
+
}
|
|
30
|
+
.vita-entry__meta,
|
|
31
|
+
.vita-entry__note {
|
|
32
|
+
@apply text-sm text-neutral-600;
|
|
33
|
+
}
|
|
34
|
+
.vita-section {
|
|
35
|
+
@apply divide-y divide-neutral-100;
|
|
36
|
+
}
|
|
37
|
+
.vita-links {
|
|
38
|
+
@apply flex flex-wrap gap-x-4 gap-y-1;
|
|
39
|
+
}
|
|
40
|
+
.vita-publications__group + .vita-publications__group {
|
|
41
|
+
@apply mt-8;
|
|
42
|
+
}
|
|
43
|
+
.vita-publications__heading {
|
|
44
|
+
@apply text-xl font-semibold;
|
|
45
|
+
}
|
|
46
|
+
.vita-bib {
|
|
47
|
+
@apply mt-3 list-decimal space-y-3 pl-6;
|
|
48
|
+
}
|
|
49
|
+
.vita-badge {
|
|
50
|
+
@apply ml-1 rounded bg-neutral-100 px-1.5 py-0.5 text-xs no-underline hover:bg-neutral-200;
|
|
51
|
+
}
|
|
52
|
+
.vita-issues {
|
|
53
|
+
@apply border-b border-red-300 bg-red-50 px-4 py-3 text-sm text-red-900;
|
|
54
|
+
}
|
|
55
|
+
.vita-issues__title {
|
|
56
|
+
@apply font-semibold;
|
|
57
|
+
}
|
|
58
|
+
.vita-issues pre {
|
|
59
|
+
@apply whitespace-pre-wrap font-sans;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* --- Markdown output --------------------------------------------------- */
|
|
63
|
+
|
|
64
|
+
.vita-prose {
|
|
65
|
+
@apply leading-relaxed;
|
|
66
|
+
}
|
|
67
|
+
.vita-prose :where(p, ul, ol, blockquote, pre, table) {
|
|
68
|
+
@apply my-4;
|
|
69
|
+
}
|
|
70
|
+
.vita-prose :where(h2) {
|
|
71
|
+
@apply mt-8 mb-2 text-xl font-semibold;
|
|
72
|
+
}
|
|
73
|
+
.vita-prose :where(h3) {
|
|
74
|
+
@apply mt-6 mb-2 text-lg font-semibold;
|
|
75
|
+
}
|
|
76
|
+
.vita-prose :where(ul) {
|
|
77
|
+
@apply list-disc pl-6;
|
|
78
|
+
}
|
|
79
|
+
.vita-prose :where(ol) {
|
|
80
|
+
@apply list-decimal pl-6;
|
|
81
|
+
}
|
|
82
|
+
.vita-prose :where(code) {
|
|
83
|
+
@apply rounded bg-neutral-100 px-1 py-0.5 text-sm;
|
|
84
|
+
}
|
|
85
|
+
.vita-prose :where(pre) {
|
|
86
|
+
@apply overflow-x-auto rounded bg-neutral-100 p-4 text-sm;
|
|
87
|
+
}
|
|
88
|
+
.vita-prose :where(blockquote) {
|
|
89
|
+
@apply border-l-4 border-neutral-200 pl-4 text-neutral-600;
|
|
90
|
+
}
|
|
91
|
+
.vita-prose :where(figure) {
|
|
92
|
+
@apply my-4;
|
|
93
|
+
}
|
|
94
|
+
.vita-prose :where(figcaption) {
|
|
95
|
+
@apply mt-1 text-xs text-neutral-500;
|
|
96
|
+
}
|
|
97
|
+
/* A figure beside the text on wide screens, above it on narrow ones. */
|
|
98
|
+
.vita-prose :where(figure.figure-right) {
|
|
99
|
+
@apply sm:float-right sm:ml-6 sm:mt-0 sm:w-56;
|
|
100
|
+
}
|
|
101
|
+
.vita-prose :where(figure.figure-right) img {
|
|
102
|
+
@apply w-full rounded border border-neutral-200 bg-white p-2;
|
|
103
|
+
}
|
|
104
|
+
.vita-prose :where(table) {
|
|
105
|
+
@apply w-full border-collapse text-left text-sm;
|
|
106
|
+
}
|
|
107
|
+
.vita-prose :where(th, td) {
|
|
108
|
+
@apply border-b border-neutral-200 py-2 pr-4 align-top;
|
|
109
|
+
}
|
|
110
|
+
.vita-prose :where(th) {
|
|
111
|
+
@apply font-semibold;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Wide display formulas must not blow up the layout on narrow screens. */
|
|
115
|
+
.katex-display {
|
|
116
|
+
@apply overflow-x-auto overflow-y-hidden py-1;
|
|
117
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const profile = useProfile()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="min-h-screen flex flex-col">
|
|
7
|
+
<!-- Shows content problems while developing; a build fails on them instead. -->
|
|
8
|
+
<VitaIssues />
|
|
9
|
+
|
|
10
|
+
<header class="border-b border-neutral-200">
|
|
11
|
+
<div class="mx-auto flex max-w-3xl flex-wrap items-baseline gap-x-6 gap-y-2 px-4 py-6">
|
|
12
|
+
<NuxtLink to="/" class="text-lg font-semibold no-underline">
|
|
13
|
+
{{ profile.name }}
|
|
14
|
+
</NuxtLink>
|
|
15
|
+
<nav class="ml-auto flex gap-4 text-sm">
|
|
16
|
+
<NuxtLink
|
|
17
|
+
v-for="item in profile.nav"
|
|
18
|
+
:key="item.url"
|
|
19
|
+
:to="item.url"
|
|
20
|
+
class="no-underline hover:underline"
|
|
21
|
+
>
|
|
22
|
+
{{ item.label }}
|
|
23
|
+
</NuxtLink>
|
|
24
|
+
</nav>
|
|
25
|
+
</div>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
<main class="mx-auto w-full max-w-3xl flex-1 px-4 py-10">
|
|
29
|
+
<slot />
|
|
30
|
+
</main>
|
|
31
|
+
|
|
32
|
+
<footer class="border-t border-neutral-200">
|
|
33
|
+
<div class="mx-auto flex max-w-3xl flex-wrap items-baseline gap-2 px-4 py-6 text-sm text-neutral-600">
|
|
34
|
+
<p>© {{ new Date().getFullYear() }} {{ profile.name }}</p>
|
|
35
|
+
<VitaMarkdown class="ml-auto text-xs text-neutral-400" :html="profile.footerHtml" />
|
|
36
|
+
</div>
|
|
37
|
+
</footer>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Renders every Markdown page in `content/pages/`.
|
|
4
|
+
*
|
|
5
|
+
* Adding a page means adding a file. When a page needs more than prose, create
|
|
6
|
+
* `app/pages/<name>.vue`: a concrete route always wins over this catch-all.
|
|
7
|
+
*/
|
|
8
|
+
const profile = useProfile()
|
|
9
|
+
const route = useRoute()
|
|
10
|
+
const page = usePage(route.path)
|
|
11
|
+
|
|
12
|
+
if (!page) {
|
|
13
|
+
throw createError({ statusCode: 404, statusMessage: `No page for ${route.path}`, fatal: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
useHead({
|
|
17
|
+
title: page.route === '/' ? profile.name : `${page.title} – ${profile.name}`,
|
|
18
|
+
meta: [{ name: 'description', content: page.description ?? profile.summary }],
|
|
19
|
+
})
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<article>
|
|
24
|
+
<h1 class="text-3xl font-semibold" v-html="page!.titleHtml" />
|
|
25
|
+
<p v-if="page!.description" class="mt-2 text-neutral-600">
|
|
26
|
+
{{ page!.description }}
|
|
27
|
+
</p>
|
|
28
|
+
<VitaMarkdown class="mt-6" :html="page!.bodyHtml" />
|
|
29
|
+
</article>
|
|
30
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The CV page: several sections composed in the order you want them.
|
|
4
|
+
*
|
|
5
|
+
* Section order, headings and which sections appear are decided here, not in
|
|
6
|
+
* the data — exactly as `cv.tex` decides them for the PDF.
|
|
7
|
+
*/
|
|
8
|
+
const profile = useProfile()
|
|
9
|
+
const page = usePage('/cv/')!
|
|
10
|
+
|
|
11
|
+
const sections = [
|
|
12
|
+
{ id: 'employment', label: 'Employment' },
|
|
13
|
+
{ id: 'education', label: 'Education' },
|
|
14
|
+
{ id: 'teaching', label: 'Teaching' },
|
|
15
|
+
{ id: 'supervision', label: 'Supervised theses' },
|
|
16
|
+
{ id: 'talks', label: 'Talks' },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
useHead({
|
|
20
|
+
title: `${page.title} – ${profile.name}`,
|
|
21
|
+
meta: [{ name: 'description', content: page.description ?? profile.summary }],
|
|
22
|
+
})
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<article>
|
|
27
|
+
<h1 class="text-3xl font-semibold">
|
|
28
|
+
{{ page.title }}
|
|
29
|
+
</h1>
|
|
30
|
+
<VitaMarkdown class="mt-6" :html="page.bodyHtml" />
|
|
31
|
+
|
|
32
|
+
<section v-for="section in sections" :key="section.id" class="mt-10">
|
|
33
|
+
<h2 class="text-xl font-semibold">
|
|
34
|
+
{{ section.label }}
|
|
35
|
+
</h2>
|
|
36
|
+
<VitaSection :id="section.id" class="mt-3" />
|
|
37
|
+
</section>
|
|
38
|
+
</article>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The landing page: who you are, then the prose from
|
|
4
|
+
* `content/pages/index.md`, then a few recent items.
|
|
5
|
+
*
|
|
6
|
+
* This file is yours — reorder it, delete parts, add your own markup.
|
|
7
|
+
*/
|
|
8
|
+
const profile = useProfile()
|
|
9
|
+
const page = usePage('/')
|
|
10
|
+
const talks = useSection('talks')
|
|
11
|
+
|
|
12
|
+
useHead({
|
|
13
|
+
title: profile.name,
|
|
14
|
+
meta: [{ name: 'description', content: profile.summary }],
|
|
15
|
+
})
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<div>
|
|
20
|
+
<header class="flex flex-wrap items-start gap-6 sm:flex-nowrap">
|
|
21
|
+
<img
|
|
22
|
+
v-if="profile.avatar"
|
|
23
|
+
:src="profile.avatar"
|
|
24
|
+
alt=""
|
|
25
|
+
width="128"
|
|
26
|
+
height="128"
|
|
27
|
+
class="size-32 shrink-0 rounded-full object-cover ring-1 ring-neutral-200"
|
|
28
|
+
>
|
|
29
|
+
<div>
|
|
30
|
+
<h1 class="text-3xl font-semibold">
|
|
31
|
+
{{ profile.name }}
|
|
32
|
+
</h1>
|
|
33
|
+
<p v-if="profile.title" class="text-neutral-600">
|
|
34
|
+
{{ profile.title }}
|
|
35
|
+
<template v-if="profile.affiliation">
|
|
36
|
+
·
|
|
37
|
+
<a v-if="profile.affiliation.url" :href="profile.affiliation.url">{{ profile.affiliation.name }}</a>
|
|
38
|
+
<template v-else>{{ profile.affiliation.name }}</template>
|
|
39
|
+
</template>
|
|
40
|
+
</p>
|
|
41
|
+
<p v-if="profile.email" class="mt-1 text-sm">
|
|
42
|
+
<a :href="`mailto:${profile.email}`">{{ profile.email }}</a>
|
|
43
|
+
</p>
|
|
44
|
+
<VitaLinks :links="profile.links" class="mt-2 text-sm" />
|
|
45
|
+
</div>
|
|
46
|
+
</header>
|
|
47
|
+
|
|
48
|
+
<VitaMarkdown v-if="page" class="mt-8" :html="page.bodyHtml" />
|
|
49
|
+
|
|
50
|
+
<section v-if="talks.length" class="mt-10">
|
|
51
|
+
<h2 class="text-xl font-semibold">
|
|
52
|
+
Recent talks
|
|
53
|
+
</h2>
|
|
54
|
+
<VitaSection :entries="talks" :limit="3" class="mt-3" />
|
|
55
|
+
<p class="mt-3 text-sm">
|
|
56
|
+
<NuxtLink to="/cv/#talks">
|
|
57
|
+
All talks →
|
|
58
|
+
</NuxtLink>
|
|
59
|
+
</p>
|
|
60
|
+
</section>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Prose from `content/pages/publications.md`, then the generated list.
|
|
4
|
+
*
|
|
5
|
+
* This page shadows the Markdown catch-all for `/publications/` — the general
|
|
6
|
+
* escape hatch when a page needs prose *and* components.
|
|
7
|
+
*/
|
|
8
|
+
const profile = useProfile()
|
|
9
|
+
const page = usePage('/publications/')!
|
|
10
|
+
|
|
11
|
+
useHead({
|
|
12
|
+
title: `${page.title} – ${profile.name}`,
|
|
13
|
+
meta: [{ name: 'description', content: page.description ?? profile.summary }],
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<article>
|
|
19
|
+
<h1 class="text-3xl font-semibold">
|
|
20
|
+
{{ page.title }}
|
|
21
|
+
</h1>
|
|
22
|
+
<VitaMarkdown class="mt-6" :html="page.bodyHtml" />
|
|
23
|
+
<VitaPublications class="mt-8" />
|
|
24
|
+
</article>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
- date: { from: 2016, to: 2020 }
|
|
2
|
+
degree: PhD in Mathematics
|
|
3
|
+
institution: { name: University of Klagenfurt, url: https://www.aau.at }
|
|
4
|
+
thesis:
|
|
5
|
+
title: Cutting down random trees
|
|
6
|
+
type: Doctoral thesis
|
|
7
|
+
supervisor: { label: A. Supervisor, url: https://example.org/supervisor }
|
|
8
|
+
|
|
9
|
+
- date: { from: 2014, to: 2016 }
|
|
10
|
+
degree: MSc in Mathematics (with distinction)
|
|
11
|
+
institution: University of Klagenfurt
|
|
12
|
+
thesis:
|
|
13
|
+
title: Ascent sequences and their asymptotics
|
|
14
|
+
type: Master's thesis
|
|
15
|
+
|
|
16
|
+
- date: { from: 2011, to: 2014 }
|
|
17
|
+
degree: BSc in Technical Mathematics
|
|
18
|
+
institution: University of Klagenfurt
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
- date: { from: 2023, to: present }
|
|
2
|
+
position: Postdoctoral Researcher
|
|
3
|
+
institution: { name: University of Graz, url: https://www.uni-graz.at }
|
|
4
|
+
project:
|
|
5
|
+
title: Asymptotics of random discrete structures
|
|
6
|
+
funder: FWF
|
|
7
|
+
grant: P 12345
|
|
8
|
+
url: https://www.fwf.ac.at
|
|
9
|
+
|
|
10
|
+
- date: { from: 2020, to: 2023 }
|
|
11
|
+
position: University Assistant
|
|
12
|
+
institution: University of Klagenfurt
|
|
13
|
+
|
|
14
|
+
- date: { from: 2019, to: 2019 }
|
|
15
|
+
position: Visiting Researcher
|
|
16
|
+
institution: Uppsala University
|
|
17
|
+
note: Three-month research stay with the combinatorics group.
|
|
18
|
+
visibility: cv
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Curriculum Vitae
|
|
3
|
+
description: The same data that produces the PDF.
|
|
4
|
+
order: 30
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Build the PDF version with `pnpm build:cv`. The sections below come from the
|
|
8
|
+
YAML files in `content/`; their order is decided in `app/pages/cv.vue`, and the
|
|
9
|
+
order in the PDF in `cv-tex/cv.tex`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: __NAME__
|
|
3
|
+
order: 0
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Replace this page with a few sentences about yourself: it lives in
|
|
7
|
+
`content/pages/index.md`, and the heading above comes from its front matter.
|
|
8
|
+
|
|
9
|
+
Everything on this site comes from the files in `content/`, and the same data
|
|
10
|
+
produces `cv-tex/cv.pdf`. Add a page by adding a Markdown file; add a CV
|
|
11
|
+
section by adding a YAML file — `pnpm vitakit add outreach` sets one up.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Research
|
|
3
|
+
description: Analytic combinatorics and random discrete structures.
|
|
4
|
+
order: 10
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<figure class="figure-right">
|
|
8
|
+
<img src="/img/tree.svg" alt="A simulated random tree" width="240" height="140">
|
|
9
|
+
<figcaption>A random tree on eight nodes.</figcaption>
|
|
10
|
+
</figure>
|
|
11
|
+
|
|
12
|
+
My work concerns the asymptotic behaviour of large random trees. A simple
|
|
13
|
+
family is described by the equation $T(z) = z\,\phi(T(z))$, whose singular
|
|
14
|
+
expansion determines almost everything about the limit shape:
|
|
15
|
+
|
|
16
|
+
$$
|
|
17
|
+
T(z) = \tau - \gamma \sqrt{1 - z/\rho} + O(1 - z/\rho).
|
|
18
|
+
$$
|
|
19
|
+
|
|
20
|
+
From this, the expected height of a random tree with $n$ nodes satisfies
|
|
21
|
+
|
|
22
|
+
$$
|
|
23
|
+
\mathbb{E}[H_n] \sim c \sqrt{n}, \qquad n \to \infty.
|
|
24
|
+
$$
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
| Technique | Used for |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| Singularity analysis | asymptotics of coefficients |
|
|
31
|
+
| Analytic combinatorics in several variables | multivariate limit laws |
|
|
32
|
+
| Simulation | checking conjectures before proving them |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# You, and the shape of your site. The only file that is not a CV section.
|
|
2
|
+
name: __NAME__
|
|
3
|
+
title: __TITLE__
|
|
4
|
+
affiliation:
|
|
5
|
+
name: __AFFILIATION__
|
|
6
|
+
email: __EMAIL__
|
|
7
|
+
|
|
8
|
+
# Where the site is published. Only the PDF needs it, to link files on the
|
|
9
|
+
# site such as /slides/talk.pdf; delete the # once you know the address.
|
|
10
|
+
# url: https://yourname.github.io/
|
|
11
|
+
|
|
12
|
+
# Portrait, from public/. Delete the line if you would rather not have one.
|
|
13
|
+
avatar: /img/portrait.svg
|
|
14
|
+
|
|
15
|
+
links:
|
|
16
|
+
- { label: ORCID, url: https://orcid.org/0000-0002-1825-0097 }
|
|
17
|
+
# - { label: GitHub, url: https://github.com/yourname }
|
|
18
|
+
# - { label: arXiv, url: https://arxiv.org/a/yourname }
|
|
19
|
+
|
|
20
|
+
# One or two sentences for search engines and link previews. What visitors
|
|
21
|
+
# read is content/pages/index.md, not this.
|
|
22
|
+
summary: >-
|
|
23
|
+
__TITLE__ at __AFFILIATION__, working on your research area.
|
|
24
|
+
|
|
25
|
+
# Navigation, in the order it should appear. Adding a page does not change
|
|
26
|
+
# this list: the wording and the order are yours.
|
|
27
|
+
nav:
|
|
28
|
+
- { label: About, to: / }
|
|
29
|
+
- { label: Research, to: /research/ }
|
|
30
|
+
- { label: Publications, to: /publications/ }
|
|
31
|
+
- { label: CV, to: /cv/ }
|
|
32
|
+
|
|
33
|
+
# Inline Markdown, shown quietly at the bottom of every page.
|
|
34
|
+
footer: Built with [vitakit](https://github.com/anja-codes/vitakit).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
% Your publication list. Export it from Zotero, JabRef or arXiv and replace the
|
|
2
|
+
% examples below.
|
|
3
|
+
%
|
|
4
|
+
% The non-standard `status` field decides the grouping on /publications/ and in
|
|
5
|
+
% the PDF: published, accepted, preprint, software or other; without it, an
|
|
6
|
+
% entry is published. To change the groups:
|
|
7
|
+
% pnpm vitakit eject publications # -> schemas/publications.ts
|
|
8
|
+
%
|
|
9
|
+
% `doi`, `arxiv`, `mrnumber` and `zbl` become links; `eprint` with
|
|
10
|
+
% `eprinttype = {arxiv}` (or `archivePrefix`) works as `arxiv` does, so exports
|
|
11
|
+
% from Zotero, JabRef or arXiv need no editing. Mathematics in titles works:
|
|
12
|
+
% write $...$ as usual.
|
|
13
|
+
%
|
|
14
|
+
% The website formats this list with a CSL style. To change it:
|
|
15
|
+
% pnpm vitakit eject publications.csl # -> content/publications.csl
|
|
16
|
+
% or save any CSL style under that name.
|
|
17
|
+
|
|
18
|
+
@article{example-published,
|
|
19
|
+
author = {Doe, Jane and Roe, Richard},
|
|
20
|
+
title = {The shape of large random $k$-ary trees},
|
|
21
|
+
journal = {Random Structures \& Algorithms},
|
|
22
|
+
volume = {64},
|
|
23
|
+
pages = {201--240},
|
|
24
|
+
year = {2024},
|
|
25
|
+
doi = {10.5555/12345678},
|
|
26
|
+
status = {published},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@misc{example-preprint,
|
|
30
|
+
author = {Doe, Jane},
|
|
31
|
+
title = {Sharp bounds for ascent sequences of height $h$},
|
|
32
|
+
year = {2025},
|
|
33
|
+
arxiv = {2501.01234},
|
|
34
|
+
status = {preprint},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@software{example-software,
|
|
38
|
+
author = {Doe, Jane},
|
|
39
|
+
title = {treetools: utilities for random tree simulation},
|
|
40
|
+
url = {https://github.com/yourname/treetools},
|
|
41
|
+
year = {2025},
|
|
42
|
+
status = {software},
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
- date: 2025
|
|
2
|
+
student: A. Student
|
|
3
|
+
ongoing: true # still in progress
|
|
4
|
+
title: Simulating random trees with $n$ nodes
|
|
5
|
+
degree: Master's thesis
|
|
6
|
+
institution: University of Graz
|
|
7
|
+
|
|
8
|
+
- date: 2024
|
|
9
|
+
student: B. Student
|
|
10
|
+
title: Ascent sequences in combinatorics
|
|
11
|
+
degree: Bachelor's thesis
|
|
12
|
+
institution: University of Graz
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
- date: 2025-06-16
|
|
2
|
+
title: The shape of large random $k$-ary trees
|
|
3
|
+
event: AofA 2025
|
|
4
|
+
location: Bath, UK
|
|
5
|
+
kind: invited
|
|
6
|
+
|
|
7
|
+
- date: 2024-09-03
|
|
8
|
+
title: Cutting down trees, one leaf at a time
|
|
9
|
+
event: Austrian Mathematical Society Meeting
|
|
10
|
+
location: Graz, Austria
|
|
11
|
+
kind: contributed
|
|
12
|
+
|
|
13
|
+
- date: 2024-03-12
|
|
14
|
+
title: What is a random tree?
|
|
15
|
+
event: Mathematics for schools
|
|
16
|
+
location: Klagenfurt, Austria
|
|
17
|
+
kind: outreach
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
- date: 2025
|
|
2
|
+
title: Analytic Combinatorics
|
|
3
|
+
role: Lecturer
|
|
4
|
+
term: 2025S
|
|
5
|
+
institution: University of Graz
|
|
6
|
+
|
|
7
|
+
- date: 2024
|
|
8
|
+
title: Discrete Mathematics
|
|
9
|
+
role: Lecturer
|
|
10
|
+
term: 2024W
|
|
11
|
+
institution: University of Graz
|
|
12
|
+
|
|
13
|
+
- date: 2023
|
|
14
|
+
title: Calculus I
|
|
15
|
+
role: Teaching Assistant
|
|
16
|
+
term: 2023W
|
|
17
|
+
institution: University of Klagenfurt
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
% Your CV. This file decides the ORDER of things; cv.yml decides what goes in.
|
|
2
|
+
%
|
|
3
|
+
% Styling: redefine the macros in preamble.tex. For deeper changes run
|
|
4
|
+
% pnpm vitakit eject vitakit-cv.cls
|
|
5
|
+
% which copies the document class into cv-tex/, where it is yours to edit and
|
|
6
|
+
% takes precedence. Do not edit the copy in cv-tex/generated/ -- every build
|
|
7
|
+
% overwrites it.
|
|
8
|
+
%
|
|
9
|
+
% A variant (say, for one application) is just a copy of this file:
|
|
10
|
+
% cp cv-tex/cv.tex cv-tex/application-xyz.tex and change \vitadocument below.
|
|
11
|
+
\documentclass{vitakit-cv}
|
|
12
|
+
\vitadocument{cv}
|
|
13
|
+
|
|
14
|
+
% The class already loads biblatex; just point it at your .bib file.
|
|
15
|
+
\addbibresource{../content/publications.bib}
|
|
16
|
+
|
|
17
|
+
\input{preamble.tex}
|
|
18
|
+
\input{generated/cv/profile.tex}
|
|
19
|
+
|
|
20
|
+
\begin{document}
|
|
21
|
+
\vitaheader
|
|
22
|
+
|
|
23
|
+
% Prose (cover letters, statements) lives in cv-tex/prose/ as plain LaTeX and
|
|
24
|
+
% is included with \input, not \vitasection -- \vitasection is only for
|
|
25
|
+
% sections vitakit generates from content/.
|
|
26
|
+
% \input{prose/research-statement.tex}
|
|
27
|
+
|
|
28
|
+
\vitasection{employment}
|
|
29
|
+
\vitasection{education}
|
|
30
|
+
\vitasection{teaching}
|
|
31
|
+
|
|
32
|
+
\section{Publications}
|
|
33
|
+
\vitainput{publications}
|
|
34
|
+
|
|
35
|
+
\vitasection{supervision}
|
|
36
|
+
\vitasection{talks}
|
|
37
|
+
|
|
38
|
+
\end{document}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Lets a plain `latexmk cv.tex` (in CI, or from an editor) find the document
|
|
2
|
+
# class, which `pnpm build:cv` writes to generated/<document>/. A copy in this
|
|
3
|
+
# directory (`pnpm vitakit eject vitakit-cv.cls`) still wins: `.` comes first.
|
|
4
|
+
# The trailing separator keeps TeX's default search path.
|
|
5
|
+
$ENV{'TEXINPUTS'} = join($search_path_separator, '.', './generated//', $ENV{'TEXINPUTS'} // '');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
% Your styling lives here. The generated files only use the macros from
|
|
2
|
+
% vitakit-cv.cls, so redefining them changes the design without touching
|
|
3
|
+
% anything vitakit produces.
|
|
4
|
+
|
|
5
|
+
% Section headings as they should appear in this document:
|
|
6
|
+
\vitasectiontitle{employment}{Employment}
|
|
7
|
+
\vitasectiontitle{education}{Education}
|
|
8
|
+
\vitasectiontitle{teaching}{Teaching}
|
|
9
|
+
\vitasectiontitle{talks}{Selected talks}
|
|
10
|
+
\vitasectiontitle{supervision}{Supervised theses}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
% Prose for your CV and applications: plain LaTeX, written by you.
|
|
2
|
+
%
|
|
3
|
+
% Files in this directory are never read by the website build, which makes them
|
|
4
|
+
% the right place for a cover letter, a research or teaching statement, or
|
|
5
|
+
% anything else that should not end up online.
|
|
6
|
+
%
|
|
7
|
+
% Include one wherever you want it in a document:
|
|
8
|
+
%
|
|
9
|
+
% \input{prose/research-statement.tex}
|
|
10
|
+
%
|
|
11
|
+
% (\vitasection is only for sections vitakit generates from content/.)
|
|
12
|
+
|
|
13
|
+
\section{Research statement}
|
|
14
|
+
|
|
15
|
+
Replace this text. Because it is ordinary LaTeX, anything you would normally
|
|
16
|
+
write works here --- environments, figures, citations such as
|
|
17
|
+
\cite{example-published}, and mathematics like $\zeta(s) = \sum_{n \ge 1} n^{-s}$.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
2
|
+
|
|
3
|
+
const baseURL = process.env.NUXT_APP_BASE_URL || '/'
|
|
4
|
+
if (!baseURL.startsWith('/') || !baseURL.endsWith('/') || baseURL.includes('//')) {
|
|
5
|
+
throw new Error('NUXT_APP_BASE_URL must be a path with leading and trailing slashes, e.g. /my-site/')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default defineNuxtConfig({
|
|
9
|
+
compatibilityDate: '2026-09-06',
|
|
10
|
+
devtools: { enabled: false },
|
|
11
|
+
|
|
12
|
+
modules: ['@nuxt/fonts', '@anja.codes/vitakit/nuxt'],
|
|
13
|
+
|
|
14
|
+
// Formulas are rendered at build time; vitakit adds the matching KaTeX
|
|
15
|
+
// stylesheet itself, so no KaTeX is installed here.
|
|
16
|
+
css: ['~/assets/css/main.css'],
|
|
17
|
+
|
|
18
|
+
vite: { plugins: [tailwindcss()] },
|
|
19
|
+
|
|
20
|
+
app: {
|
|
21
|
+
baseURL,
|
|
22
|
+
head: { htmlAttrs: { lang: 'en' } },
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
nitro: {
|
|
26
|
+
prerender: {
|
|
27
|
+
failOnError: true,
|
|
28
|
+
// Routes come from vitakit, so an unlinked page is still generated.
|
|
29
|
+
crawlLinks: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__SLUG__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@10.29.1",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.18"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "nuxt dev",
|
|
11
|
+
"generate": "nuxt generate",
|
|
12
|
+
"preview": "nuxt preview",
|
|
13
|
+
"postinstall": "nuxt prepare",
|
|
14
|
+
"typecheck": "nuxt typecheck",
|
|
15
|
+
"check:static": "vitakit-check",
|
|
16
|
+
"check": "pnpm typecheck && pnpm generate && pnpm check:static",
|
|
17
|
+
"build:cv": "vitakit-build-cv"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@anja.codes/vitakit": "^0.1.0",
|
|
21
|
+
"@nuxt/fonts": "^0.14.0",
|
|
22
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
23
|
+
"nuxt": "^4.5.2",
|
|
24
|
+
"tailwindcss": "^4.3.3",
|
|
25
|
+
"vue": "^3.5.43",
|
|
26
|
+
"vue-router": "^5.3.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^26.6.2",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vue-tsc": "^3.3.11",
|
|
32
|
+
"zod": "^4.6.5"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Placeholder portrait">
|
|
2
|
+
<rect width="128" height="128" fill="#e2e8f0"/>
|
|
3
|
+
<circle cx="64" cy="50" r="23" fill="#94a3b8"/>
|
|
4
|
+
<path d="M20 128c0-27 20-44 44-44s44 17 44 44z" fill="#94a3b8"/>
|
|
5
|
+
<text x="64" y="120" font-family="system-ui, sans-serif" font-size="9" fill="#475569" text-anchor="middle">replace me</text>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 140" width="240" height="140">
|
|
2
|
+
<g stroke="#475569" stroke-width="2" fill="none">
|
|
3
|
+
<path d="M120 130 V100 M120 100 L80 70 M120 100 L160 70 M80 70 L60 40 M80 70 L100 40 M160 70 L150 40 M150 40 L140 15 M150 40 L165 15"/>
|
|
4
|
+
</g>
|
|
5
|
+
<g fill="#1d4ed8">
|
|
6
|
+
<circle cx="120" cy="100" r="4"/><circle cx="80" cy="70" r="4"/><circle cx="160" cy="70" r="4"/>
|
|
7
|
+
<circle cx="60" cy="40" r="4"/><circle cx="100" cy="40" r="4"/><circle cx="150" cy="40" r="4"/>
|
|
8
|
+
<circle cx="140" cy="15" r="4"/><circle cx="165" cy="15" r="4"/>
|
|
9
|
+
</g>
|
|
10
|
+
</svg>
|