CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Academic personal website for Moritz Seebacher (PhD student, ifo Institute / LMU Munich). Hosted on GitHub Pages at https://moritzseebacher.github.io. Repository: github.com/moritzseebacher/moritzseebacher.github.io. Research focus: education & labor economics, using LinkedIn data to study skills, social networks, and career trajectories.

Private material — STRICT

This repository is public. It is served at https://moritzseebacher.github.io, and its full history is readable by anyone, including files deleted in a later commit.

Nothing describing Moritz’s preferences may be committed here. Not in a doc, not in a comment, not in a variable name, not in a test fixture, not in a .pyc. Specifically:

Where it goes instead: F:\Academic Website\job_market_2026\, which is deliberately outside this repository. Preference material is local only and known to Claude — it is not tracked by git anywhere, not in this repo and not in another one.

The job market tooling is untracked on purpose. .claude/commands/jm-scan.md, .claude/scripts/jm_*.py, .claude/job-market-*.md and __pycache__/ live on disk so the /jm-scan command still works, but they are in .gitignore and must stay there. Do not git add -f them. Do not “restore” them on the grounds that they look like missing code.

Before committing anything, check. If a change touches the job market workflow, confirm git status shows no job-market path staged. When in doubt, ask rather than commit.

Tech Stack

Build & Preview

python .claude/scripts/site_check.py   # Pre-push validation (no Ruby needed; see fallback below)
bundle exec jekyll serve               # Local dev server (http://localhost:4000)
bundle exec jekyll build               # Build static site to _site/

Always run site_check.py before pushing. It needs only Python and catches the breakages that actually occur when editing this site by hand: nav anchors that no longer resolve, links to files that do not exist, orphan PDFs left in the repo root, unbalanced <details>/<div> tags, blank lines that break kramdown’s tight-list rule, and paper action rows whose PDF link is in the wrong position. Exit code 1 means do not push.

Finding a Python interpreter — ALWAYS check the fallback

python is not on PATH on every machine this repo is edited from, and on the remote ifo server it is not on PATH at all. Do not conclude that Python is unavailable and skip site_check.py — the checker is the only pre-push validation there is.

  1. Baseline (local computer): python, python3, or py on PATH.
  2. Backup (remote ifo server), always check this if step 1 fails: Z:\PromotionProject\git_moritz\python_environment\jobtitles_env\python.exe — a project venv on the ifo network drive, verified working on 4 Sep 2026 (Python 3.10.19). It runs site_check.py and cv_audit.py fine; they use only the standard library. cv_audit.py additionally shells out to pdftotext, which must be on PATH; without it the audit blocks rather than passing unverified.

Same order applies to cv_audit.py and any other script in .claude/scripts/.

A Gemfile is committed for local preview via the github-pages gem, which pins the same dependency versions GitHub Pages uses server-side. Ruby is not installed on the current machine, so the Gemfile has not been exercised — bundle install will need running once before jekyll serve works. Deployment does not depend on it: Pages builds with its own dependency set and ignores the repo’s Gemfile.

File Structure

moseeb98.github.io/
├── _config.yml                          # Jekyll config, author profile, theme settings
├── _data/navigation.yml                 # Top nav bar entries (all anchor links to index.md)
├── _includes/footer/custom.html         # Mobile nav JS fix (moves nav items to hidden-links)
├── assets/css/main.scss                 # Custom SCSS overrides (avatar sizing, mobile layout)
├── index.md                             # Main page — all content sections live here
├── Seebacher-Moritz_2023_7_ret_pass_format.jpg    # Profile photo (served from root)
├── Seebacher_Career_Effects_Online_Social_Networks.pdf  # Job market paper (stable filename)
├── Multidimensional_Skills_LinkedIn_IZA_DP17896.pdf     # Working paper PDF (stable filename)
└── CV_Academic_Moritz_Seebacher_MM_YY_English.pdf  # CV (built from LaTeX, served from root)

Architecture

Single-page academic site — all content lives on the index page:

Content Conventions

Publication / paper entries

Each entry is a Markdown bullet:

- [Paper Title](https://url-to-paper) (with Co-Author Name)  
  <small><strong>Venue Name</strong>, Volume X (Year), pages.</small>

Abstracts

Every entry at working-paper stage or beyond (publications, working papers) carries a collapsible abstract. Non-refereed policy papers do not. The job market paper is the exception in the other direction: its abstract is expanded by default (<details ... open>), but it collapses like the rest.

What goes public, and at which stage (Moritz, revised 16 Sep 2026). A work-in-progress project is listed once Moritz has decided it will become a paper and wants the collaboration visible — as a title-only entry with coauthors and no action row (the promotions project with Dorn and Woessmann is the example). An abstract is added once the coauthors have agreed on one (the alumni project). Projects still at the idea, outline, or grant-review stage (misreporting, AI and labor) stay off the site even though the research statement names them: the statement goes to a committee, the site is public, and they are deliberately not the same list. Work-in-progress entries never link to a PDF. Whatever is listed here is listed in the CV in the same words, and cv_audit.py (rule R37) checks every title on both sides.

Status notes. An entry that has a draft but no public PDF carries <span class="paper-note">Draft available upon request</span> directly after the abstract toggle (alumni project, 16 Sep 2026). The same words go into the CV’s outlet slot, and rule R37 fails if one side has the tag and the other does not. Drop the tag the day the PDF goes up.

Every abstract sits inside a <div class="paper-actions"> row, whether or not the entry has a PDF. The <details> holds only its <summary>; the abstract text is a sibling <span class="abstract-text">:

- [Paper Title](https://url) (with Co-Author)  
  <small><strong>Venue</strong>, details.</small>
  <div class="paper-actions"><details class="abstract"><summary>Abstract</summary></details><a class="paper-pdf" href="/File_Name.pdf">PDF</a><span class="abstract-text">Full abstract text…</span></div>

Hosted paper PDFs

Publications and working papers with an ungated PDF in the repo root carry a PDF button in the action row, directly after the abstract toggle (see above for the markup).

Links are distinguished by colour only — the theme’s hover underline is switched off in assets/css/main.scss under --- LINKS ---.

Section headings

Use ## Title {#anchor-id} so navigation links (/#anchor-id) resolve correctly.

Static files

CV PDF, job market paper PDF, and profile photo are served from the repo root (no subdirectory).

Job market paper update workflow

Unlike the CV, the JMP PDF keeps a stable filenameSeebacher_Career_Effects_Online_Social_Networks.pdf — so that links shared on the job market never break. New drafts are dated in the paper itself, not in the filename.

When a new draft arrives (every step, every time — updating the CV is part of this workflow, not a separate task):

  1. Overwrite Seebacher_Career_Effects_Online_Social_Networks.pdf with the new PDF, keeping the filename unchanged. Do not add a dated copy to the repo root.
  2. Diff the new title page against index.md (pdftotext -f 1 -l 1 <pdf> -) — the title in the ## Job Market Paper heading and the abstract-text span must match the PDF verbatim. When the abstract changed, take the new text from the paper’s main.tex, not from pdftotext, to avoid line-break artifacts.
  3. Update the CV in the same pass: carry any title or abstract change into tex/cv.tex in the application package, then rebuild and re-sync the website copy with .\build.ps1 web (see CV update workflow below). Rules R37 (title) and R38 (abstract, verbatim) in .claude/scripts/cv_audit.py enforce both.
  4. Consistency gate — refuse to commit or push while anything is inconsistent. Run site_check.py and cv_audit.py; any failure means fix it first and re-run. A stale abstract on the site or in the CV is a blocker, not a follow-up.
  5. Commit and push — the new PDF goes live at the same URL.

CV update workflow

The CV is built from LaTeX, not Word (changed 10 September 2026). The source is tex/cv.tex in the application package, which lives outside this repository with the rest of the job market material. One source produces two PDFs:

Build Output Carries the referees?
.\build.ps1 core pdf\Seebacher_CV.pdf Yes — sent with applications and to the letter writers
.\build.ps1 web the dated CV_Academic_*.pdf in this repo root Yes — the same PDF under the dated name

Since 16 September 2026 the two copies are the same document. All four letter writers agreed to be listed publicly, so the References section (names, affiliations, email addresses) is on the website CV. The page itself lists no referees (Moritz, same day): the CV is the one place, so index.md gets no References section and no referee mailto: link. Until then the website build defined \publicCV, which dropped the block; that switch is gone. build.ps1 web refuses to copy a CV that does not carry all four referees.

Never hand-edit the PDF in this repo root. It is overwritten by the next build.ps1 web. Edit tex/cv.tex and rebuild.

When updating the CV:

  1. Edit tex/cv.tex in the application package.
  2. Run .\build.ps1 core (the copy that goes to committees and letter writers) and .\build.ps1 web (the public copy). The web command copies the public PDF here under a filename dated to the current month, deletes the superseded one, and relinks index.md if the month rolled over.
  3. Run site_check.py and cv_audit.py. cv_audit.py is the gate that matters: it reads the published PDF and fails if its References section is missing or short of four referees, if index.md links a referee address, if the Fields line, a paper title or the draft-status tag disagrees with index.md, or if an abstract is not verbatim in both.
  4. Commit and push — the new PDF goes live automatically.

The Word documents in the repo root are superseded and are kept only as history. They are git-ignored and no longer feed anything; cv_audit.py warns while they are still there.

Responsive Layout (CSS)

Desktop (≥1024px): Sidebar avatar is circular, 85% of sidebar width, portrait crop (aspect-ratio: 5/6, object-position: 50% 15%).

Mobile (≤800px): Two-column CSS grid — photo fills the left column, name/bio and contact links stack in the right column. Avatar is rectangular (no border-radius, object-fit: contain). The button replaces the hamburger icon; the footer JS moves all nav items to the hidden-links dropdown.

Deployment

Push to main branch → GitHub Pages builds and deploys automatically. No CI/CD config needed.

Repository: https://github.com/moritzseebacher/moritzseebacher.github.io Live site: https://moritzseebacher.github.io