Personal academic website. Loosely inspired by https://jonbarron.info/.
The site is plain HTML and CSS with no JavaScript. build.py (Python 3.11+, standard library only) assembles it from src/ into dist/, and dist/ is what gets deployed.
python3 build.py # build once into dist/
python3 build.py --serve # build, serve at http://localhost:8000, rebuild + reload the browser on save
python3 build.py --watch # rebuild on save, without a server
Optional: brew install webp so photos are resized and converted to WebP during the build. Without it, the build warns and uses the original images.
| To change… | Edit |
|---|---|
| Publications | src/data/publications.bib |
| Publication thumbnails | src/static/assets/photos/pubpics/<bibkey>.jpeg |
| Projects, teaching, press, talks | src/data/projects.toml, teaching.toml, press.toml, talks.toml |
| Name, contact links, headshot | src/partials/header.html |
| Bio paragraph | src/partials/bio.html |
| Reviewing line | src/partials/service.html |
| Section order on the home page | src/pages/index.html |
<head> (title, fonts, CSS) |
src/partials/head.html |
| Styles (text sizes, phone layout) | src/static/css/style.css |
| Font files (IBM Plex Sans) | src/static/fonts/ibm-plex-sans/ |
| CV | src/static/assets/documents/vita.pdf |
| Site name, base path, nav, image sizes | src/site.toml |
Anything in src/static/ is copied into dist/ at the same path.
Add an entry to src/data/publications.bib and a thumbnail named after its key (pubpics/<key>.jpeg). The list sorts newest year first and keeps file order within a year.
@inproceedings{beveridge2027example,
title = {Paper Title}, % required; HTML like <i>…</i> is allowed
author = {Beveridge, Matthew and Nayar, Shree K.}, % required
year = {2027}, % required
booktitle = {CVPR}, % or journal = {…}
note = {Oral}, % optional, shown in red
url = {https://arxiv.org/abs/…}, % [paper] link
url_text = {dataset page}, % optional label instead of "paper"
url_project = {https://…}, % [project page]
url_code = {https://…}, % [code]
url_press = {https://…}, % [press]
abstract = {One-sentence summary.} % [tl;dr] toggle
}
Patents show number, and @mastersthesis entries show type (school).
Copy an existing block in the matching TOML file. Each file lists its required fields at the top.
Talks are listed in src/data/talks.toml but aren’t on the home page yet. To show them, add this to src/pages/index.html where the section should appear:
<section>
<h2 id="Talks">Recent Talks</h2>
</section>
Create src/pages/notes.html. It builds to dist/notes/index.html, served at /notes/.
---
title: Notes | Matthew Beveridge
description: Assorted notes.
---
<h2>Notes</h2>
<p>…</p>
The default layout (src/layouts/default.html) adds the <head>, nav, and footer. Pages in subfolders work too: src/pages/projects/foo.html builds to /projects/foo/.
To link it from the nav, add an entry in src/site.toml. The nav appears once there are two or more entries.
[[nav]]
title = "notes"
href = "notes/"
| Syntax | Meaning |
|———————–|————————————————————————-|
| | Insert `src/partials/name.html` |
| | A variable from the page’s front matter or src/site.toml (HTML-escaped) |
| | The site's base path. Start local links with it: `css/style.css` |
|, | The page's URL, and the build date |
|, ,, , | Lists rendered from src/data/, usable on any page |
| ``, `<h1 id="project-notes">Project notes</h1>
Personal academic website, built as a static site by a small Python script. See README.md for the file map, editing tasks, and deploy steps. This file records the design rules to keep.
dist/ must contain no <script>. The only script is the auto-reload snippet, and build.py --serve adds it to responses in memory without writing it to disk.build.py: no pip packages, Node, or frameworks. The one external tool is cwebp, and it is optional: without it the build warns and uses the original images.,, and the named data blocks. Don’t add loops, conditionals, or a template engine.build.py is one file of small functions in pipeline order: helpers → publications → TOML lists → pages → images → build → dev server.BuildError) for:
Only warn for a missing thumbnail, abstract, or image, or a placeholder URL. Pages are rendered in memory first, so a failed build never replaces dist/.
html.escape. BibTeX values, partials, and pages are trusted HTML (bib titles use <i> and —).src/site.toml: site variables, the [webp] folder-to-width map, and the [[nav]] list (hidden while it has one entry or fewer)src/layouts/default.html: wraps every page; the page body goes where `` issrc/pages/: foo.html builds to dist/foo/index.html; index.html stays index.htmlsrc/partials/: head, header, bio, service, footersrc/data/:
publications.bib: sorted by year, newest first, with file order kept within a year; the thumbnail is pubpics/<key>.jpegprojects.toml, teaching.toml, press.tomltalks.toml: rendered by ``, not currently on the home pagesrc/static/: copied into dist/ at the same pathsTo add a new data list:
src/data/.render_<name>(site) function in build.py.DATA_BLOCKS.python3 build.py # must exit 0; only the known warnings
grep -r "<script" dist/ # must print nothing
python3 build.py --serve # eyeball http://localhost:8000
Keep the visual design unchanged unless asked:
src/static/fonts/ (no Google Fonts or other third-party requests)var(--small); don’t add one-off font-size values:root knobs (--section-gap, --heading-gap, --entry-gap, --indent); indent section content with .indented, not per-element paddingmax-width: 600px block at the end of style.css for phones; check changes at 390px wide toosimple branch.main until the server is live. Until then, GitHub Pages serves main at mattbeveridge.com, which still uses the old root index.html.CNAME as part of the cutover.` | Used in layouts: the nav links, and the page body |
The build stops with a clear message on mistakes such as:
Missing thumbnails and placeholder links only produce warnings.
Every push runs .github/workflows/build.yml. It builds the site and uploads dist/ as a downloadable artifact.
Automatic deploy. On pushes to main, the workflow also rsyncs dist/ to the server, once these repository secrets are set:
| Secret | Example |
|---|---|
DEPLOY_HOST |
beveridge@server.cs.columbia.edu |
DEPLOY_PATH |
public_html |
DEPLOY_SSH_KEY |
a private key authorized on the server |
Manual deploy:
python3 build.py && rsync -av --delete dist/ user@host:public_html/
If the site is served from a subfolder (for example https://…/~beveridge/), set base = "/~beveridge/" in src/site.toml.