The RESHAPE Lab website. Static HTML generated from a handful of CSV files by
one Python script, served from GitHub Pages. No framework, no database, no
build toolchain — python scripts/build_site.py and you are done.
python scripts/build_site.py # normal build (hits DBLP + Unpaywall)
python scripts/build_site.py --offline # rebuild from cache, no network
Everything under "generated files" below is committed. That is deliberate: GitHub Pages serves the repo as-is, so the HTML has to be in it, and it means crawlers and link previews see real content instead of an empty shell.
- Add a row to
data/members.csv. - Drop a square photo at
assets/people/<slug>.jpg. - Run the build.
They get a card on people.html and their own page at people/<slug>.html
with their publications, projects, and current position.
| Column | Notes |
|---|---|
slug |
URL and filename key. Leave blank to derive it from the name. |
name |
As you want it displayed, accents and all. |
role |
Free text: PhD student, Co-director, Visiting researcher. |
group |
Which section they appear under: faculty, postdoc, phd, ms, undergrad, collaborator, alumni. |
order |
Optional integer for manual ordering inside a group. |
dblp_pid |
The part after /pid/ in their DBLP URL, e.g. 70/3474. |
aliases |
Semicolon-separated name variants DBLP uses. |
affiliation, started, ended, now, topic |
Shown on their page. |
homepage, scholar, github |
Optional links. |
pub_from, pub_to |
Optional year window — use it so an alumnus's later papers don't land in the lab list. |
Changing someone's role is one cell. Moving them to alumni is two: set
group to alumni and fill in ended and now.
Members without a dblp_pid still get credit: the script matches author names
from other members' records, folding accents, so Marco Aurelio Gerosa in DBLP
matches Marco Aurélio Gerosa in the CSV. Add an aliases entry for anything
folding cannot catch.
- Add a row to
data/projects.csv. - Optionally write
data/projects/<slug>.mdfor the long description. - Run the build.
The row alone produces a full project page. The Markdown file only supplies the
prose under "About". Funding columns (funder, program, award_no,
award_url, amount, role) do double duty: they render on the project page
and build funding.html, grouped by funder with subtotals. There is no
separate funding file to keep in sync.
members takes semicolon-separated member slugs. Anyone who co-authored one of
the project's papers is added automatically, so you only list people whose
involvement the papers do not already show.
Nothing to do. The monthly job re-reads every member's DBLP record and picks it up.
Alumni are filtered. Their DBLP records hold a whole career, and harvesting all of it would fill the list with work the lab had no part in. One rule decides:
a paper counts if at least one author was in the lab when it appeared.
Current members are always in the lab. An alumnus is in the lab for years inside
[started, ended + 1] from members.csv.
| Situation | Result |
|---|---|
| A current member is an author | kept |
| An alumnus, and the year is inside their window | kept |
| Every author had left, or had not yet arrived | dropped |
| Dates unknown, another lab member is a co-author | kept |
| Dates unknown, they are there alone | dropped |
Both bounds matter, and started matters most for visiting scholars. Someone
who spent 2022 here has decades of publications either side of that visit; a rule
that only checked the departure year would let every earlier one through.
Years are enough — no months — and either bound helps on its own. A missing
started leaves the window open at the bottom, a missing ended open at the top,
and the build warns you which alumni are dated on one side only.
dropped_pubs.md is regenerated every build and lists everything the rule
removed, grouped by person, with each DBLP key. Nothing disappears quietly. To
rescue one, put its key in data/include_pubs.txt. Two things you may want to add by hand, in data/pub_tags.csv:
award— DBLP does not record awards.projects— semicolon-separated project slugs, which puts the paper on those project pages and shows project chips under the paper.
To find a paper's DBLP key, look in pub_index.csv (regenerated every build,
listing every harvested paper) or read it off the DBLP record URL.
To hide a paper — a member's work from outside the lab, or a duplicate record —
add its key to data/exclude_pubs.txt.
The build picks the first of these that exists:
publications/<dblp-key-with-dashes>.pdf— self-hosted manuscript- Unpaywall's best open-access location, found via the paper's DOI
- An arXiv preprint, matched from DBLP's CoRR entries by title
missing_pdfs.md lists every paper with none of the three, and gives the exact
filename to use for each. That file is your to-do list.
Add a row to data/news.csv. Put project slugs in the projects column and the
item also appears under "News and presentations" on those project pages. For
anything longer than the one-line summary, write data/news/<slug>.md.
An optional image column takes a path relative to the site root (e.g.
assets/news/20260601-launch.jpg). When set, it shows as a small thumbnail on
every list the item appears on (home, news.html, project pages) and as a
full-width hero image at the top of the item's own page. Leave it blank for a
text-only item — nothing breaks either way.
news.html shows only the 5 most recent items by default, with a "View more"
button that reveals the rest. All items are present in the page's HTML either
way, so nothing is hidden from search engines — the button just toggles
visibility client-side.
data/members.csv ──┬─→ dblp.org/pid/<pid>.xml ──→ dedupe by DBLP key
│ │
│ Unpaywall (by DOI) ──────────┤
│ arXiv (CoRR titles) ─────────┤
│ publications/*.pdf ──────────┤
│ ↓
├────────────────────────→ publications.html (?member= filter)
├────────────────────────→ people.html, people/<slug>.html
│ ↑
data/pub_tags.csv ─┴──── project + award tags ───────────┤
│
data/projects.csv ──┬──→ projects.html, projects/<slug>.html
└──→ funding.html (grouped, subtotalled)
data/news.csv ──────────→ news.html + each project's news section
Attribution is exact rather than guessed: because each paper is harvested from a specific member's DBLP record, we know whose it is without matching names. Name folding is the fallback for members who have no DBLP record of their own.
publications.html renders every paper server-side with a
data-members="slug slug" attribute, then a small script filters on the client.
Clicking a name on people.html goes to that person's own page, which lists
their papers as static HTML — so it works with JavaScript off and search engines
index it. publications.html?member=<slug> deep-links the filtered view.
index.html, people.html, publications.html, projects.html,
funding.html, news.html, people/*.html, projects/*.html, sitemap.xml,
robots.txt, pub_index.csv, missing_pdfs.md, cache/*.
Edits here are overwritten on the next build. Change data/, assets/style.css,
or the templates in scripts/build_site.py instead.
Lives in RESHAPELab/RESHAPELab.github.io — the organization site repo, so it
serves from the domain root rather than a /reponame/ subpath.
During the transition the workflow deploys from the static-site branch
(DEPLOY_BRANCH at the top of .github/workflows/build.yml), leaving main
and its Vercel deployment alone. After cutover, set it to main.
Set Settings → Pages → Source to GitHub Actions, not "Deploy from a
branch". .github/workflows/build.yml builds, commits the regenerated files, and
deploys in a single run.
This matters: commits pushed by a workflow using GITHUB_TOKEN do not trigger a
Pages build. With branch deploys, the monthly DBLP refresh would commit new HTML
that never went live. Deploying from inside the same run removes that dependency.
The workflow runs on every push to main, on the 1st of each month (with
--refresh-dblp), and on demand from the Actions tab. If the commit step fails
with a permissions error, set Settings → Actions → General → Workflow
permissions to Read and write.
Because this is an organization site, its custom domain is inherited by every
other Pages site in the org: any RESHAPELab repo with Pages enabled is served at
www.reshapelab.site/<repo>/. Keep Pages switched off on repos you do not want
published there — including the archived reshapelab Vue repo.
Full walkthrough, including the DNS cutover from Vercel: WALKTHROUGH.md.
Enter www.reshapelab.site under Settings → Pages → Custom domain, then tick
Enforce HTTPS. At your DNS provider:
| Type | Name | Value |
|---|---|---|
| CNAME | www |
reshapelab.github.io |
| A | @ |
185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153 |
| AAAA | @ |
2606:50c0:8000::153, 2606:50c0:8001::153, 2606:50c0:8002::153, 2606:50c0:8003::153 |
The apex records are what make reshapelab.site redirect to the www version.
DNS can take up to 24 hours; the HTTPS certificate is issued after both names
resolve. Verify with dig www.reshapelab.site +noall +answer.
Without a custom domain the site serves from https://reshapelab.github.io/.
Every internal link is relative, so it works on either URL — but set SITE_URL
so sitemap.xml carries the right host:
SITE_URL=https://reshapelab.github.io python scripts/build_site.py
| Flag | Effect |
|---|---|
--offline |
Build from cache/ only. No network. |
--refresh-dblp |
Ignore the DBLP cache and refetch every record. |
--no-oa |
Skip the Unpaywall check (much faster). |
--refresh-oa |
Re-query every DOI against Unpaywall. |
--deep |
Also search the arXiv API by title for unmatched papers (~3 s each). |
Editing CSVs in Excel is safe: it saves as Mac Roman or cp1252 rather than UTF-8, and every reader here detects that and decodes it correctly, warning you to re-save. Files this project writes are always UTF-8.
Every build ends with a warning list — missing photos, unknown member slugs in
projects.csv, DBLP keys in pub_tags.csv that match no paper. Read it; it is
usually a typo report.
The home page opens with a full-bleed masthead: the mark at 136px, the wordmark
as the h1, the expanded lab name, then the claim and a viewport-wide
contribution strip whose density tracks papers per year. The mark animates once
on load — wall, then the community cells, then the newcomer arriving through the
doorway — and holds still under prefers-reduced-motion.
Palette and type are inherited from igor.pro.br so the two sites read as
related: paper #fafbfc, ink #1b1f24, GitHub contribution greens, with
--ink-green: #1d3329 for the logo outline. Space Grotesk for display, Source
Serif 4 for body, JetBrains Mono for metadata. Logo assets are in assets/:
full colour, reversed for dark backgrounds, single-colour via currentColor,
and a solid-fill favicon that survives 16 px.
- Fill in
dblp_pidfor every member who has a DBLP record - Add photos to
assets/people/ - Point the
CNAMEat the right domain and enable Pages onmain - Set
UNPAYWALL_EMAILinscripts/build_site.pyif it should not be Igor's - Run
python scripts/build_site.pyonce locally and read the warnings
Step-by-step walkthrough: WALKTHROUGH.md. In short:
scripts/migrate_from_vue.py imports the previous site's content. Clone the old
repo next to this one and run it once:
git clone https://github.com/RESHAPELab/reshapelab ../reshapelab-old
python scripts/migrate_from_vue.py ../reshapelab-old
python scripts/build_site.py
It reads public/members.json, funding.json, research_areas.json,
posts.json, and images/people/, and writes data/members.csv,
data/people/*.md, data/research.csv, data/projects.csv, data/news.csv,
data/news/*.md, and downscaled square photos into assets/people/.
It never overwrites silently: existing files are copied to *.pre-migration
first, rows already in members.csv or projects.csv are merged rather than
replaced (projects match on award number, so existing slugs and URLs survive),
and every judgement call is printed at the end for review. Delete the script
once you are happy with the result.