A research portfolio for Nick Dorn (PhD candidate, Stanford) built with Jekyll and only GitHub Pages–approved plugins. The homepage is a dark, scroll-driven story that follows blood down the pulmonary arterial tree:
- The heart & proximal arteries — control vs. Fontan sheep (pressure waves damp into continuous cavopulmonary flow).
- Lobar & segmental arteries — a PPAS stenosis narrows, then is virtually treated.
- Interlude: to go deeper, we simplify — a patient model reduces from svMultiPhysics FSI → CFD streamlines → an svZeroDSolver lumped-parameter network, with a qualitative cost meter.
- The vessels we cannot see — a structured tree beyond one outlet adapts under the constant-wall-shear-stress hypothesis (a small model running in the browser).
brew install ruby@3.3 # system Ruby 2.6 is too old
export PATH=/opt/homebrew/opt/ruby@3.3/bin:$PATH
bundle config set --local path vendor/bundle
bundle install
bundle exec jekyll serve # http://127.0.0.1:4000
npm install # dev tools: GSAP, fonts, Puppeteer_config.yml's exclude: list replaces Jekyll's defaults, so it re-lists vendor/,
node_modules/, scripts/, _art/, … — keep it that way when adding entries.
- All homepage copy and pacing live in
_data/story.yml: chapters → beats (key,lenin screen heights,hud,title,body, figures, links), bridge stages (solver, cost label and level), the tree seed, heart rate and the post-op flow multiplier. assets/js/home/scenes.jsmaps each beatkeyto what animates. Removing a beat fromstory.ymlis safe — its tweens are skipped.- After/around the story: About, featured publications (
featured: truein_data/publications.yml) and "Beyond the pulmonary tree" are in_layouts/home.html.
- No pinning, no build step. The tree stage and bridge are CSS
position: sticky; the page is a readable document without JavaScript.assets/js/home/main.js(native ES modules) seeks one paused GSAP timeline from scroll position (story-clock.js). GSAP 3 + ScrollTrigger are self-hosted underassets/vendor/gsap/<version>/(npm run vendorre-copies them). - Procedural tree.
assets/js/home/tree-model.jsis a seeded generator (Murray's-law radii and branch angles, fanning toward an imaging-resolution arc).npm run build:treewrites_includes/story/tree.svg; the browser only hydrates it. To choose a different seed:node scripts/build-tree.mjs --contact-sheet 16 --png→ pick from_art/tree-seeds.png→ settree_seedin_data/story.yml→npm run build:tree.npm run check:treefails on drift. - Chapter 03 model.
assets/js/home/structured-tree.js(Olufsen α/β tree, Poiseuille flow, WSS, constant-WSS adaptation) is unit-tested:npm test.fractal-canvas.jsdraws it. - Fallbacks. No JS / blocked GSAP → static page (5 s watchdog in the
<head>script). Reduced motion (OS setting or footer toggle) → key frames per beat, full tree, no looping animation. The on-stage Pause button stops the heartbeat and particles. Short landscape screens use the static layout. Display preferences: footer → High contrast, Reduce motion (the site is dark only).
The bridge currently uses placeholders: the existing patient streamline render, and centerlines
skeletonized from it (scripts/placeholder_centerlines.py). To use real, camera-aligned exports:
# 1. Save a camera in the ParaView GUI (View ▸ Save Camera…) → my-view.pvcc; preview with --dry-run
/Applications/ParaView-6.1.0.app/Contents/bin/pvbatch scripts/paraview/export_cost_scene.py \
--surface mesh-complete.exterior.vtp --flow-results result_XXXX.vtu \
--centerlines centerlines.vtp --inlet-cap mesh-surfaces/inflow.vtp \
--outlet-caps mesh-surfaces --camera my-view.pvcc --out _art/bridge/pa-01
# optional: --fsi-results <FSI .vtu with Displacement> --zerod-config solver_0d.json
# 2. Web images (AVIF + WebP) and the 0D overlay
python3 scripts/optimize_images.py set --src _art/bridge/pa-01 --key bridge --dst assets/img/story/bridge
cp _art/bridge/pa-01/centerlines.json assets/data/bridge/centerlines.json
node scripts/build-bridge-overlay.mjsoverlay_check.png (ParaView's own tubes on the wall) lets you confirm the projected centerlines.
Raw renders go in _art/ (git-ignored).
scripts/optimize_images.py writes AVIF/WebP derivatives and _data/media.yml;
{% raw %}{% include picture.html key="…" alt="…" sizes="…" %}{% endraw %} renders them.
The sheep figures (assets/img/sheep-*.png, from slides 3–4 of the Fontan supplementary deck) are
split into control/Fontan tiles for the homepage and used whole on the Research and Projects pages.
Replace figures together with their captions; keep control/Fontan labels consistent.
node scripts/snap-story.mjs [--size 390x844] [--reduced] [--beats key:0.5,…]— screenshots of every beat (headless Chrome) into_art/snaps/, with the animation state logged.node scripts/og-gen.mjs— rendersassets/og/template.htmltoassets/img/og-default.png.
_data/story.yml— homepage story (see above)_data/publications.yml—title, authors, journal, year, doi, featured, abstract, tags_data/projects.yml—name, summary, tech, repo, docs, demo, tags, featured, order_data/talks.yml—title, event, date, location, slides, video, abstract_data/media.yml,_data/bridge.yml— generated; do not edit
GitHub Pages builds main from / (root). Generated files (_includes/story/*.svg,
_data/media.yml, optimized images) are committed because Pages runs no custom build.
Update pages/resume.html and pages/cv.html. Older PDFs in assets/docs/ are excluded from the
build until they are refreshed.