Skip to content

Commit 469c17b

Browse files
authored
feat: editorial CV pipeline — real HTML/PDF rendering, Lighthouse 100 (#3)
## Why PDFs were broken (the CI workflow deliberately forced a fallback Python script that emitted a 1-page text-only PDF stub) and the HTML had none of the metadata/semantics needed for decent Lighthouse scores. This rebuild swaps the whole pipeline for a Python + Jinja2 + Markdown + WeasyPrint setup with an editorial visual treatment. ## What changes **Pipeline (replaces forced-fallback build):** - `scripts/build.py` — single entrypoint, reads `src/cv-*.de.md` (YAML front-matter + Markdown), renders through Jinja2 templates, writes `public/cv-*.de.html` and matching PDFs via WeasyPrint, plus `public/index.html` landing page. - `templates/base.html.j2` + `cv.html.j2` + `index.html.j2` own the full ` <head>`: title, description, canonical, OG profile tags, Twitter card, JSON-LD `schema.org/Person`, favicon, font preloads. - Drops the `__missing_pandoc__` / `__missing_weasyprint__` overrides; deletes `scripts/render_html.py` and `scripts/render_pdf.py`. - Drops committed `public/*` — it's now gitignored and built fresh in CI on every push/PR. **Visual (editorial / serif / typographic):** - Self-hosted variable fonts: Source Serif 4 (body) + Inter (UI), both OFL, ~50 KB each. - Warm off-white page `#fbfaf6`, high-contrast text (>16:1), deep wine accent `#7d2d3a` (>5.7:1). - Section labels in small-caps Inter; bullet markers in accent color; hairline rules between sections; no boxes/cards. - Print stylesheet: A4 portrait, 18mm margins, page numbers via `@page` footer, `break-inside: avoid` on roles, link URLs revealed on print. **Accessibility / SEO / best-practices wins:** - Skip-link, semantic `<main>` / `<header>` / `<footer>`, single `<h1>` per page, descriptive `aria-label` on PDF links, `aria-current` on active variant, `rel=me` on profile links. - `<title>`, `<meta name=description>`, `theme-color`, `link rel=canonical`, `link rel=icon` favicon.svg, `link rel=alternate type=application/pdf`. - Open Graph profile tags + JSON-LD `Person` for richer LinkedIn/Twitter/Google previews. **CI:** - New `lighthouse` job runs [`treosh/lighthouse-ci-action@v12`](https://github.com/treosh/lighthouse-ci-action) on every PR with the built `public/` as fixture. - `.lighthouserc.json` asserts **a11y / BP / SEO at 1.0** and performance at 0.95 (will fail the PR check if any drops). - Pages deploy is unchanged otherwise. ## Local Lighthouse results (desktop preset) Verified on all three pages before opening this PR: | Page | Performance | A11y | Best Practices | SEO | |---|---|---|---|---| | `index.html` | 100 | 100 | 100 | 100 | | `cv-executive.de.html` | 100 | 100 | 100 | 100 | | `cv-technical.de.html` | 100 | 100 | 100 | 100 | Compare to the previous numbers (a11y 87, SEO 80, best-practices 96). ## PDFs Both PDFs now render through real WeasyPrint, not the 1-page text stub. ~36 KB each, 2 pages, with running headers, page numbers, and the same editorial typography as the screen version. ## How to verify locally ```bash sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libcairo2 libgdk-pixbuf-2.0-0 make install build serve # http://localhost:8000 make lighthouse # writes lighthouse-*.html per page ``` ## Notes / trade-offs - Repo grew by ~200 KB of self-hosted fonts (4 woff2 files). In exchange we drop external CDN requests, get full CSP/best-practices score, and avoid Google Fonts privacy footprint. - `public/` is no longer in git history going forward (already removed in this PR; old commits keep their copies — fine).
2 parents 05f61f9 + 205876b commit 469c17b

25 files changed

Lines changed: 920 additions & 482 deletions

.github/workflows/build.yml

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,84 @@ permissions:
1111
pages: write
1212
id-token: write
1313

14+
concurrency:
15+
group: pages-${{ github.ref }}
16+
cancel-in-progress: false
17+
1418
jobs:
1519
build:
1620
runs-on: ubuntu-latest
1721
steps:
1822
- name: Checkout
1923
uses: actions/checkout@v4
2024

21-
- name: Setup Pages
22-
if: github.ref == 'refs/heads/main'
23-
uses: actions/configure-pages@v5
25+
- name: Setup Python
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: "3.13"
29+
cache: pip
2430

25-
- name: Install dependencies
31+
- name: Install WeasyPrint system libraries
2632
run: |
2733
sudo apt-get update
28-
sudo apt-get install -y pandoc weasyprint make
34+
sudo apt-get install -y --no-install-recommends \
35+
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
36+
libcairo2 libgdk-pixbuf-2.0-0
37+
38+
- name: Install Python deps
39+
run: |
40+
python -m pip install --upgrade pip
41+
pip install -r requirements.txt
2942
3043
- name: Build
31-
run: PANDOC=__missing_pandoc__ WEASYPRINT=__missing_weasyprint__ make build
44+
run: python scripts/build.py
3245

33-
- name: Verify committed public artifacts
34-
run: git diff --exit-code public/
46+
- name: Setup Pages
47+
if: github.ref == 'refs/heads/main'
48+
uses: actions/configure-pages@v5
3549

3650
- name: Upload Pages artifact
3751
if: github.ref == 'refs/heads/main'
3852
uses: actions/upload-pages-artifact@v3
3953
with:
4054
path: public/
4155

56+
- name: Upload public/ as workflow artifact (PRs)
57+
if: github.event_name == 'pull_request'
58+
uses: actions/upload-artifact@v4
59+
with:
60+
name: public
61+
path: public/
62+
retention-days: 7
63+
64+
lighthouse:
65+
if: github.event_name == 'pull_request'
66+
needs: build
67+
runs-on: ubuntu-latest
68+
steps:
69+
- name: Checkout
70+
uses: actions/checkout@v4
71+
72+
- name: Download built site
73+
uses: actions/download-artifact@v4
74+
with:
75+
name: public
76+
path: public
77+
78+
- name: Setup Node
79+
uses: actions/setup-node@v4
80+
with:
81+
node-version: "20"
82+
83+
- name: Run Lighthouse CI
84+
uses: treosh/lighthouse-ci-action@v12
85+
with:
86+
# URLs and staticDistDir live in .lighthouserc.json so the local
87+
# `npx lhci autorun` workflow stays in sync with CI.
88+
uploadArtifacts: true
89+
temporaryPublicStorage: true
90+
configPath: ./.lighthouserc.json
91+
4292
deploy:
4393
if: github.ref == 'refs/heads/main'
4494
needs: build

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,16 @@
33
*.bak
44
dist/
55
src/*private*
6+
7+
# Build artifacts (regenerated in CI)
8+
public/
9+
10+
# Local Python build env
11+
.venv/
12+
.venv-build/
13+
__pycache__/
14+
*.pyc
15+
16+
# Lighthouse local reports
17+
lighthouse-*.html
18+
.lighthouseci/

.lighthouserc.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"ci": {
3+
"collect": {
4+
"staticDistDir": "./public",
5+
"url": [
6+
"http://localhost/index.html",
7+
"http://localhost/cv-executive.de.html",
8+
"http://localhost/cv-technical.de.html"
9+
],
10+
"settings": {
11+
"preset": "desktop",
12+
"onlyCategories": ["performance", "accessibility", "best-practices", "seo"]
13+
}
14+
},
15+
"assert": {
16+
"assertions": {
17+
"categories:performance": ["error", { "minScore": 0.95 }],
18+
"categories:accessibility": ["error", { "minScore": 1 }],
19+
"categories:best-practices": ["error", { "minScore": 1 }],
20+
"categories:seo": ["error", { "minScore": 1 }]
21+
}
22+
}
23+
}
24+
}

Makefile

Lines changed: 38 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,40 @@
1-
PANDOC ?= pandoc
2-
WEASYPRINT ?= weasyprint
3-
CSS := assets/style.css
4-
PUBLIC := public
5-
SRC := src
6-
7-
EXEC_MD := $(SRC)/cv-executive.de.md
8-
TECH_MD := $(SRC)/cv-technical.de.md
9-
10-
EXEC_HTML := $(PUBLIC)/cv-executive.de.html
11-
TECH_HTML := $(PUBLIC)/cv-technical.de.html
12-
EXEC_PDF := $(PUBLIC)/cv-executive.de.pdf
13-
TECH_PDF := $(PUBLIC)/cv-technical.de.pdf
14-
INDEX_HTML := $(PUBLIC)/index.html
15-
16-
.PHONY: build html pdf index check clean
17-
18-
build: html pdf index
19-
html: $(EXEC_HTML) $(TECH_HTML)
20-
pdf: $(EXEC_PDF) $(TECH_PDF)
21-
index: $(INDEX_HTML)
22-
23-
$(PUBLIC):
24-
mkdir -p $(PUBLIC)
25-
26-
$(EXEC_HTML): $(EXEC_MD) $(CSS) | $(PUBLIC)
27-
@if command -v $(PANDOC) >/dev/null 2>&1; then \
28-
$(PANDOC) $< -f markdown -t html5 --standalone --metadata title="Executive CV" --metadata lang="de" --css=../$(CSS) -o $@; \
29-
else \
30-
python3 scripts/render_html.py $< $@ ../$(CSS); \
31-
fi
32-
33-
$(TECH_HTML): $(TECH_MD) $(CSS) | $(PUBLIC)
34-
@if command -v $(PANDOC) >/dev/null 2>&1; then \
35-
$(PANDOC) $< -f markdown -t html5 --standalone --metadata title="Technical CV" --metadata lang="de" --css=../$(CSS) -o $@; \
36-
else \
37-
python3 scripts/render_html.py $< $@ ../$(CSS); \
38-
fi
39-
40-
$(EXEC_PDF): $(EXEC_HTML) $(CSS)
41-
@if command -v $(WEASYPRINT) >/dev/null 2>&1; then \
42-
$(WEASYPRINT) $< $@; \
43-
else \
44-
python3 scripts/render_pdf.py $< $@; \
45-
fi
46-
47-
$(TECH_PDF): $(TECH_HTML) $(CSS)
48-
@if command -v $(WEASYPRINT) >/dev/null 2>&1; then \
49-
$(WEASYPRINT) $< $@; \
50-
else \
51-
python3 scripts/render_pdf.py $< $@; \
52-
fi
53-
54-
$(INDEX_HTML): $(EXEC_HTML)
55-
cp $(EXEC_HTML) $(INDEX_HTML)
56-
57-
check: build
58-
git diff --exit-code public/
1+
PYTHON ?= python3
2+
VENV ?= .venv
3+
4+
.PHONY: help build serve clean install lighthouse
5+
6+
help:
7+
@echo "make install - create venv and install dependencies"
8+
@echo "make build - build public/ from src/ (HTML + PDF)"
9+
@echo "make serve - build and serve public/ on http://localhost:8000"
10+
@echo "make lighthouse - run a local Lighthouse audit (needs npx)"
11+
@echo "make clean - remove public/ and venv"
12+
13+
$(VENV)/bin/python:
14+
$(PYTHON) -m venv $(VENV) || (which uv && uv venv $(VENV))
15+
$(VENV)/bin/python -m pip install -U pip || $(VENV)/bin/python -m ensurepip --upgrade
16+
$(VENV)/bin/python -m pip install -r requirements.txt
17+
18+
install: $(VENV)/bin/python
19+
20+
build: install
21+
$(VENV)/bin/python scripts/build.py
22+
23+
serve: build
24+
@echo "Serving public/ on http://localhost:8000"
25+
$(PYTHON) -m http.server 8000 -d public
26+
27+
lighthouse: build
28+
@echo "Running Lighthouse on built public/"
29+
npx --yes lighthouse "file://$(CURDIR)/public/index.html" \
30+
--quiet --chrome-flags="--headless" \
31+
--output html --output-path ./lighthouse-index.html
32+
npx --yes lighthouse "file://$(CURDIR)/public/cv-executive.de.html" \
33+
--quiet --chrome-flags="--headless" \
34+
--output html --output-path ./lighthouse-executive.html
35+
npx --yes lighthouse "file://$(CURDIR)/public/cv-technical.de.html" \
36+
--quiet --chrome-flags="--headless" \
37+
--output html --output-path ./lighthouse-technical.html
5938

6039
clean:
61-
rm -f $(EXEC_HTML) $(TECH_HTML) $(EXEC_PDF) $(TECH_PDF) $(INDEX_HTML)
40+
rm -rf public $(VENV) lighthouse-*.html

README.md

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,65 @@
22

33
Source-controlled CV workflow for Sebastian Mendel.
44

5-
Published version:
5+
Published version: <https://cybottm.github.io/cv/>
66

7-
<https://cybottm.github.io/cv/>
8-
9-
## Files
7+
## Layout
108

119
| Path | Purpose |
1210
|---|---|
13-
| `src/cv-executive.de.md` | Short public executive CV |
14-
| `src/cv-technical.de.md` | More detailed public technical CV |
15-
| `public/index.html` | GitHub Pages entry point |
16-
| `public/cv-executive.de.html` | Generated executive HTML CV |
17-
| `public/cv-executive.de.pdf` | Generated executive PDF CV |
18-
| `public/cv-technical.de.html` | Generated technical HTML CV |
19-
| `public/cv-technical.de.pdf` | Generated technical PDF CV |
20-
| `assets/style.css` | Shared print-friendly stylesheet |
21-
| `Makefile` | Local build commands |
22-
23-
## Build
11+
| `src/cv-executive.de.md` | Source: short executive CV (Markdown + YAML front-matter) |
12+
| `src/cv-technical.de.md` | Source: longer technical CV |
13+
| `templates/base.html.j2` | Jinja2 base template (`<head>`, OG, JSON-LD, etc.) |
14+
| `templates/cv.html.j2` | CV page template |
15+
| `templates/index.html.j2` | Landing page template |
16+
| `assets/style.css` | Editorial CSS (screen + print) |
17+
| `assets/fonts/*.woff2` | Self-hosted Source Serif 4 + Inter (variable, OFL) |
18+
| `assets/favicon.svg` | Monogram favicon |
19+
| `scripts/build.py` | The whole build: MD → HTML → PDF + index |
20+
| `requirements.txt` | Python deps (pinned) |
21+
| `Makefile` | Convenience targets (`build`, `serve`, `lighthouse`, `clean`) |
22+
| `.github/workflows/build.yml` | GitHub Actions: build, Lighthouse-CI on PRs, Pages deploy |
23+
| `public/` | **Build artifact** — generated, not committed |
24+
25+
## Build locally
26+
27+
You need Python 3.13 and the WeasyPrint runtime libs (Pango, Cairo, HarfBuzz, gdk-pixbuf).
2428

25-
Requirements:
29+
```bash
30+
# Debian/Ubuntu
31+
sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libcairo2 libgdk-pixbuf-2.0-0
2632

27-
- `make`
28-
- `pandoc`
29-
- `weasyprint`
33+
make install # creates .venv/ and installs requirements.txt
34+
make build # writes public/cv-*.de.html + .pdf and public/index.html
35+
make serve # builds, then serves public/ on http://localhost:8000
36+
```
3037

31-
Build all generated files:
38+
Skip PDF rendering during a fast iteration:
3239

3340
```bash
34-
make build
41+
.venv/bin/python scripts/build.py --no-pdf
3542
```
3643

37-
Check that generated files are up to date:
44+
## Local Lighthouse audit
3845

3946
```bash
40-
make check
47+
make lighthouse # writes lighthouse-*.html for each page
4148
```
4249

43-
Remove generated HTML/PDF files:
50+
## Editing content
4451

45-
```bash
46-
make clean
47-
```
52+
Each `src/cv-*.de.md` has a YAML front-matter block: `variant`, `short_label`,
53+
`lang`, `title`, `description`, `updated`, `og_locale`. The body is plain Markdown.
54+
The `<h1>` and tagline are rendered from `templates/cv.html.j2`, not from the MD,
55+
so the MD body should start at `## Profil`.
4856

49-
## Source of truth
57+
## CI / publishing
5058

51-
The Markdown files in `src/` are the source of truth. Files in `public/` are generated artifacts for GitHub Pages and downloadable HTML/PDF versions.
59+
- Every push and PR builds the site.
60+
- PRs additionally run [`treosh/lighthouse-ci-action`](https://github.com/treosh/lighthouse-ci-action) and post results.
61+
- Pushes to `main` deploy to GitHub Pages.
5262

53-
## Publishing
63+
## License
5464

55-
GitHub Pages serves the generated files from `public/`. The default published CV is `public/index.html`.
65+
CV content © Sebastian Mendel. Source code (templates, scripts, CSS) under the
66+
repository's license; bundled fonts (Source Serif 4, Inter) under SIL OFL.

assets/favicon.svg

Lines changed: 7 additions & 0 deletions
Loading
50.6 KB
Binary file not shown.

assets/fonts/Inter-Variable.woff2

47.1 KB
Binary file not shown.
50.3 KB
Binary file not shown.
49.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)