CLAUDE.md
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this repo is
Joey Schulz’s personal academic website, built from the Academic Pages Jekyll template (itself a fork of Minimal Mistakes). The remote is wizrads/wizrads.github.io; GitHub Pages builds and serves it at https://joeyschulz.com (apex A records to GitHub’s IPs, www CNAME to wizrads.github.io, which 301s to the apex). Enforce HTTPS is on.
The custom domain is set both in the repository’s Pages settings and in the root CNAME file — keep the two in sync; deleting CNAME can drop the domain on the next deploy.
Pages is configured as “Deploy from a branch”: master, / (root), built by GitHub’s own pages build and deployment workflow.
Workflow
Commit and push straight to master. No feature branches, no pull requests — single-author personal site, and master is the deploy branch, so a PR only delays the change going live.
bundle exec jekyll build --strict_front_matter- commit,
git push origin master - the Pages build takes about a minute; then verify against https://joeyschulz.com
gh is installed at /opt/homebrew/bin/gh and authenticated as wizrads, but that directory is not always on PATH — prefix with export PATH="/opt/homebrew/bin:$PATH". To watch a deploy: gh api repos/wizrads/wizrads.github.io/pages/builds/latest --jq '{status,commit}'.
Commands
bundle install # Ruby deps (delete Gemfile.lock first if it errors; the lock is gitignored)
bundle exec jekyll serve -l -H localhost # dev server with livereload at localhost:4000
bundle exec jekyll build --strict_front_matter # what CI would run; use this to check a change builds
npm install && npm run build:js # rebuild assets/js/main.min.js
docker compose up # containerized alternative (_config.yml + _config_docker.yml, port 4000)
Editing _config.yml requires restarting Jekyll; Markdown/HTML changes hot-reload.
Always preview with jekyll serve, never by serving _site/ with another static server. url is https://joeyschulz.com, so a plain jekyll build bakes absolute production URLs into every asset link — the page then silently loads the live CSS and JS and your local changes appear to do nothing. jekyll serve rewrites url to the local address for the duration.
JavaScript is committed pre-built. main.min.js is jquery + plugins/jquery.greedy-navigation.js + _main.js + theme.js uglified together, and it is the only script the site loads. After editing any of those sources, run npm run build:js and commit the regenerated bundle.
There is no test suite. CI is .github/workflows/jekyll-build.yml, but it triggers on branch main while this repo uses master, so it never actually runs. Verify builds locally.
Content
All template placeholder content is gone. _config.yml, _pages/about.md, _pages/cv.md, and the _publications (17), _talks (28), _teaching (2), and _portfolio (4) collections hold real content generated from Joey’s CV. _posts/ is empty and there is no blog.
Dates are year-accurate only. The CV supplies publication and meeting years but not days, so every generated file uses YYYY-01-01. Never present those days as real.
Attribution matters to Joey and is not inferable from author order. He is first author on the AVATAR 2.0 and 3D-printed device papers but asked that both be described as collaborations he contributed to rather than led; the _portfolio entries and the homepage’s Background paragraph say so explicitly. Ask before characterizing his role on anything new.
Source material lives outside the repo: his CV (~/Downloads/01-Schulz_CV_AI_update.docx as of Sep 2026) and his Google Scholar profile. When adding publications or talks in bulk, copy the front-matter shape of an existing file rather than using the markdown_generator/ notebooks, which expect their own TSV/BibTeX inputs.
Paper figures
images/publications/ holds one figure per paper, embedded near the bottom of the publication’s Markdown body and also set as header.teaser in front matter. Only 3 of 17 are done (the three Frontiers papers, which are CC BY). The rest are blocked on two things: the paywalled journals do not make figures retrievable, and which figure counts as the main one is Joey’s editorial call, not a guess to make. Ask him for the files.
Hidden pages
/cv/ is deliberately hidden but not deleted: its nav entry is commented out in _data/navigation.yml and it carries sitemap: false. Note that flag needs two cooperating pieces — jekyll-sitemap reads it for sitemap.xml, and _pages/sitemap.md was patched to honour it as well, because that human-readable sitemap is linked from the footer of every page and would otherwise keep the page one click away. Use the same pattern for anything else to be hidden.
/cv-json/ (_pages/cv-json.md + _data/cv.json) is unlinked, still in sitemap.xml, and only partly populated: scripts/cv_markdown_to_json.py demands a rigid Degree, Institution, YYYY bullet format that the real CV does not fit, so its Education and Work sections come out empty. Either fix the script or delete the page — do not contort cv.md to satisfy the parser.
Known gaps: no ORCID iD in the sidebar, no files/cv.pdf for the download link, real talk dates outstanding.
Architecture
Content is data, layout is theme. Each content type is a Jekyll collection whose YAML front matter drives several rendered surfaces at once:
_publications/,_talks/,_teaching/,_portfolio/— collections declared in_config.ymlwithpermalink: /:collection/:path/; filenames are date-prefixed (YYYY-MM-DD-slug.md)._pages/— standalone pages, each declaring its ownpermalink. The listing pages (publications.html,talks.html,teaching.html,portfolio.html) iterate the collections.- One talk file feeds the talks list, its own page, the CV’s Talks section, and
talkmap.html. Changing a front-matter key ripples across all of them. _pages/cv.md’s Publications / Talks / Teaching sections are Liquid loops over the collections, so adding a file to_publicationsupdates the CV automatically.
Rendering chain: _layouts/ (default, single, archive, archive-taxonomy, talk, cv-layout, splash, plus compress.html, which minifies output) compose _includes/ partials — head/, masthead.html, sidebar.html, author-profile.html, archive-single*.html, seo.html, footer/, scripts.html, toc/.
Site-wide data:
_config.yml— identity, theauthor:sidebar block,publication_category(currentlymanuscriptsandpreprints), analytics/comments providers, andsite_theme.urlmust stay equal to the custom domain, since canonical tags, Open Graph URLs, the feed, and every absolute asset link are built from it._data/navigation.yml— top menu._data/authors.yml— multi-author bylines._data/ui-text.yml— UI strings.- The sidebar avatar is
images/profile.jpg, referenced byauthor.avataras a bare filename.
Theming: site_theme is default; each theme has light and dark variants in _sass/theme/, wired up by _sass/_themes.scss. The masthead toggle switches light/dark at runtime (assets/js/theme.js). Prefer changing theme variables over hardcoding colors.
Generators (manual): markdown_generator/ turns TSV/CSV/BibTeX/ORCID data into collection files. talkmap.py / talkmap.ipynb geocode _talks/ locations for /talkmap.html; the scrape_talks.yml workflow runs the notebook on pushes touching _talks/ and commits the result back.
_site/ and .sass-cache/ are build output and gitignored.
Local changes to theme files — do not lose these in an upstream merge
Several template files were patched to fix visible bugs. They look like innocuous theme code and are easy to revert by accident.
_sass/theme/_default_{light,dark}.scss,_sass/layout/_base.scss,plugins/jquery.greedy-navigation.js—$masthead-height(65px) and$masthead-height-narrow(58px) must equal the masthead’s measured height, becausebodyreserves those values and the greedy-nav JS measures the real thing and corrects any mismatch after first paint, which shows up as the whole page jumping. The JS now only writes that padding when it disagrees by more than 2px. If you change the theme, the nav items, or the masthead’s font/padding, re-measure and update both constants (document.querySelector('.masthead').getBoundingClientRect().heightat a wide and a narrow viewport).$masthead-height-narrowis declared with!defaultin_sass/_themes.scssso other themes still compile._includes/masthead.html— the nav’s overflow button ships withclass="hidden"(plus anaria-labelit never had). Upstream rendered it visible and let the JS hide it, which flashed a hamburger on every page load._includes/author-profile.html— the avatar<img>must not carryclass="author__avatar"; that class belongs to its wrapper<div>. Upstream put it on both, so the wrapper’sdisplay: table-cell; width: 36pxlanded on the image and collapsed it to 0×0 below the$largebreakpoint — which reads as “the photo doesn’t load”. Note the other branch of that sameif/else, used whenavataris a full URL, never had the bug._sass/include/_utilities.scss—.author__urls i/.social-icons ireserve a1.25embox. Font Awesome and Academicons are loaded withrel="preload"and only become stylesheets after first paint, so without this the icons have no width and every sidebar row’s text shifts ~10px sideways once they land._sass/layout/_footer.scss+_includes/footer.html— the footer is one compact flex row (~46px, was ~93px). The@include clearfixwas removed from that rule deliberately: its::before/::afterpseudo-elements become flex items and force the copyright onto its own line. Navigation is a plain full page load. Hover-prefetching (instant.page) was added and then deliberately reverted at Joey’s request — do not reintroduce it or a client-side router without asking.
The staggered intro fade-in (masthead 0.15s, #main 0.35s, footer 0.45s) is upstream behaviour and is kept. It was briefly deleted on the theory that it was what made navigation feel like a reload; that was wrong. The fade is what makes the upstream demo look smooth, and the computed animations here now match https://academicpages.github.io exactly. What actually looked broken was three real bugs firing underneath it — the masthead padding jump, the flashing nav button, and the shifting sidebar icons, all fixed above. Do not remove the fade to “speed up” navigation.
Upstream template leftovers
.github/workflows/bad-pr.yml and close-tests.yml police the upstream template’s issues and PRs — the first auto-closes any PR whose body is empty or contains “by deleting this comment block”, which is a phrase in this repo’s own .github/PULL_REQUEST_TEMPLATE.md; the second auto-closes issues mentioning “test”. Both are irrelevant to a personal fork and safe to delete. README.md, _pages/terms.md, and the PR templates are likewise upstream docs.
