A personal hub that turns one CV data file into three views: an interactive tool cloud, an academic CV, and a printable Markdown résumé.
Everything on the site is rendered from a single data/cv.json. Edit that one file and
the hub, the academic CV page, and the generated CV.*.md all update — nothing is
duplicated. It runs entirely in the browser; no tracking, no external CDNs, fonts served
from this origin.
🔗 Live: https://rijdho.github.io/
Available in English, German and Spanish (auto-detected, switchable) — interface and content both, including every long description.
-
The hub (
index.html) — the 17 tools and experiments as a cloud of ovals grouped by topic (drag, filter, click to open the live app), plus a tabbed section for the rest of the CV: writing, publications, talks, experience, education, engagements, training.
-
The academic CV (
cv.html) — the same data laid out as a formal, print-ready CV. The Print / PDF button produces a clean PDF straight from the browser.
- The Markdown CV (
CV.en.md,CV.de.md,CV.es.md) — generated bybuild_cv.py, renders natively on GitHub and converts to PDF with pandoc.
data/cv.json the single source of truth (you edit this)
│
├──► index.html reads it at load → the hub
├──► cv.html reads it at load → the academic CV
└──► build_cv.py → CV.en.md · CV.de.md · CV.es.md
The two HTML pages fetch('./data/cv.json') at load and render from it, so a change to
cv.json is live immediately. CV.*.md is a build artifact — the GitHub Action
regenerates it on every push, and you can also run the build locally.
Everything lives in data/cv.json. To add something, drop a new object into the right
array; the hub and CV pick it up on next load, and build_cv.py folds it into the Markdown
CVs. Translated text fields are { "en": …, "es": …, "de": … } — a missing de falls back
to English, but fill all three: EN/DE/ES is the floor, and every field currently carries them.
That includes the individual skills[].items, which are objects, not plain strings.
A new tool (a bubble in the cloud): add to experiments. topic sets its colour/cluster
and must be one of metadata, assessment, infra, story.
{
"title": "My New Tool",
"year": "2026", "status": "Active",
"url": "https://example.rijdho.org/",
"tags": ["React", "FAIR"],
"topic": "metadata",
"desc": { "en": "What it does.", "es": "Qué hace.", "de": "" }
}A new publication: add to the right group under publications
(peerReviewed, policyPapers, guides, datasets, workingPapers):
{ "authors": "Hartley Belmar, R.", "year": "2026", "title": "Paper title",
"venue": "Journal", "url": "https://doi.org/…" }A new talk: add to presentations —
{ "type": "…", "title": "…", "event": "…", "date": "…" }.
A new essay/column: add to portfolio — same shape as a tool minus
status/tags/topic.
A new degree: add to education. degree is a translated field, not a plain string, and
its Spanish should be the wording registered in ORCID rather than a translation made up here:
{ "degree": { "en": "Master in Sciences", "es": "Magíster en Ciencias",
"de": "Master in Naturwissenschaften" },
"inst": "Universidad de La Frontera, Chile", "year": "2013" }The header block is personal. degrees is the short post-nominal list printed under the
name (["MSc", "MSc", "Dr."] → MSc · MSc · Dr.) and is deliberately separate from the full
names in education. "emailIsAlias": true appends (alias) next to the address on the CV
page and in the Markdown, so the address is not mistaken for a primary institutional one.
Linking two related projects in the cloud: give each an "id" and list the other in
"related": ["<id>"] — the two bubbles then attract and touch. Add "platform": "github"
to draw a dashed bubble (an open build published on GitHub Pages, vs. a solid bubble for a
tool hosted on your own domain). This is how each hosted tool is paired with its open
GitHub twin.
A DOI: add "doi": "10.5281/zenodo.…" to any entry and its detail panel shows a DOI
link under the title.
Pointing at a sibling that is not itself a bubble: add "twin": { "title": …, "url": … }.
It renders as a ↔ link in the panel and on the CV, so a description that names another tool
("the open twin of X") is actually reachable without adding X to the cloud.
Then: commit and push. The live site updates from the JSON immediately; the Action regenerates the Markdown CVs. To preview before pushing, run it locally (below).
python3 -m http.server 8000 # fetch() needs http://, not file://
# open http://localhost:8000/
python3 build_cv.py # regenerate CV.en/de/es.md from data/cv.json
npm i puppeteer # tooling only; or set CHROME_PATH to an existing Chrome
node docs/screenshots.mjs docs http://localhost:8000 # regenerate the images aboveThe screenshots are generated from the running site, never hand-cropped, so they can be refreshed after any change instead of quietly ageing. The script fixes the random seed and emulates reduced motion, so the cloud settles into the same arrangement each run and a regenerated image diffs meaningfully. If you change what a shot shows, update its alt text — the alt text states the numbers on screen.
GitHub Pages via Actions (.github/workflows/deploy.yml) — uploads the folder verbatim
and regenerates the Markdown CVs. The user-site repo publishes at the domain root.
- The site is a curated, self-reported view of one person's work; it is not a verified record. Publication and metric claims trace to the linked sources, not to this page.
- Author names in the publication list follow how each publisher set them, so the same person appears in several forms. That is the record, not a normalisation error.
- A few older entries link to a publisher homepage rather than the item itself, where no stable record URL exists.
- Fonts are Inter (self-hosted woff2, see
fonts/).
MIT (LICENSE). The bundled Inter typeface is under the SIL Open Font License 1.1
(fonts/LICENSE.txt).

