Single-page resume for Jake Hoffman — the "engineering log" v2. Plain HTML, CSS, and a touch of vanilla JS. No framework, no build step.
Live at https://jakethehoffer.github.io/website/
python -m http.server 8000Open http://localhost:8000/. You can also just double-click
index.html — it works file:// too.
Project content (cards on the site + entries on the resume) lives in
projects.yml. To add or edit a project, change that one file and run:
python scripts/build-site.pyThis calls three generators in order:
scripts/generate-cards.py— rewrites the<div class="projects">block ofindex.htmlfromprojects.yml.scripts/build-resume.py— buildsresume-source.docxfrom scratch out of two tracked text files:resume-static.yml(contact header, summary, education, experience) +projects.yml(the PERSONAL PROJECTS section, byresume_priority+ cap). No binary template; the docx is fully reproducible from text.scripts/refresh-meta.py— refresheslast-committimestamps for projects withauto_meta: trueand stamps the footerlast_deployed.
After running build-site.py, regenerate the PDF:
"C:/Program Files/LibreOffice/program/soffice.exe" --headless --convert-to pdf --outdir . resume-source.docx
mv resume-source.pdf resume.pdfCommit index.html, the YAML you changed, and resume.pdf together.
resume-source.docx is gitignored — it's a derived artifact, not a
source.
- Projects on the resume: edit
projects.yml(resume:block +resume_priority). See "Resume curation" below. - Everything else (contact line, summary, education, experience):
edit
resume-static.yml. It's plain, diffable text; the formatting (Garamond, margins, bullet/date layout) lives inbuild-resume.py.
Each entry:
| field | description |
|---|---|
key |
short id; matches data-meta="<key>.last_commit" on the page |
name |
displayed name in the card's <h3> |
status |
active, shipped, or archived (controls the pill colour) |
url |
external link; null = name renders without an <a> wrapper |
private |
true → name renders as a private pill, not a (404-ing) link |
case_study |
optional on-page anchor (e.g. "#case-study") for a [ read case study ↓ ] CTA |
resume_priority |
int; higher = more important (drives resume curation, below) |
meta_key |
data-meta sentinel key (usually <key>.last_commit) |
auto_meta |
true → refresh-meta.py auto-updates the timestamp |
hardcoded_date |
fallback string when auto_meta: false (e.g. "mar 2024") |
what |
1-2 sentence lede (HTML allowed for entities) |
body |
80-120 word description (HTML allowed) |
metrics |
terse stats line (HTML entities pre-encoded) |
chips |
tech-stack chips line |
sample |
optional {label, html} for a code-block example |
media |
optional {src, alt, width, height} for an image |
cta |
optional {label, url} for an external CTA button |
resume |
optional {role, bullets} for the resume; null/omit to skip |
Project order in the rendered page matches order in projects.yml.
The website shows every project. The resume shows only the top
RESUME_MAX_PROJECTS (in scripts/build-resume.py, currently 4) of
the projects that have a resume: block, ranked by resume_priority
(highest first) and displayed in projects.yml order.
This means adding a project never forces manual cuts to
EXPERIENCE/EDUCATION to keep the resume on one page — a new project
simply competes for the capped slots. To feature a new project on the
resume, give it a resume: block and a resume_priority higher than
whichever project it should displace. (Because the docx is now built
from scratch from text, the old PARAGRAPHS_TO_DROP trim-list — ~75
lines of page-fit hacks — is gone entirely.)
A GitHub Action at .github/workflows/refresh-meta.yml runs
refresh-meta.py every Monday at 12:00 UTC (and on manual
gh workflow run). It commits any changes back to main automatically.
The action reads META_REFRESH_TOKEN (a fine-grained PAT) from secrets.
To set it up:
- Create a fine-grained PAT at https://github.com/settings/personal-access-tokens/new with Repository access → All repositories and Repository permissions → Metadata: Read. "All repositories" is the right scope so new projects are picked up automatically.
- Add it to the website repo at
https://github.com/jakethehoffer/website/settings/secrets/actions
as
META_REFRESH_TOKEN.
Without the secret the workflow stays inert — the default
GITHUB_TOKEN can only read the current repo, so every project with
auto_meta: true logs [skip].
resume-source.docx is a derived artifact (gitignored). It's built
from scratch by scripts/build-resume.py out of resume-static.yml +
projects.yml, then LibreOffice converts it to resume.pdf. The full
sequence is documented above in "Adding or editing projects". The
committed resume.pdf is the only public-facing binary; the docx can
always be regenerated from the tracked text sources.
Drop the repo contents on any static host. GitHub Pages: push the repo,
then in Settings → Pages, source = main branch / root.
projects.yml— single source of truth for featured projects (site + resume).resume-static.yml— tracked source for the resume's static sections (contact, summary, education, experience).index.html— semantic single-page markup; projects block generated fromprojects.yml.styles.css— all-mono design system, dark default + parchment light.script.js— boot animation, mobile nav, theme toggle, IntersectionObserver reveal.scripts/build-site.py— orchestrator (runs the three generators).scripts/generate-cards.py— renders the projects block ofindex.html.scripts/build-resume.py— buildsresume-source.docxfrom scratch out ofresume-static.yml+projects.yml.scripts/refresh-meta.py— refreshes last-commit timestamps.resume.pdf— downloadable PDF (the committed published artifact).docs/superpowers/— design specs and implementation plans.