From 76a6587815d67fd87cfe1f35773e0d92e25aae5b Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Mon, 10 Aug 2026 23:40:01 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20track=20CV/r=C3=A9sum=C3=A9=20pipeline?= =?UTF-8?q?=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit yaml-source/ was fully gitignored, so the YAML that drives both the CV and the résumé had no version history and no backup — it existed only on one machine. Track the pipeline *source* (data/, templates/, build.py, Makefile) and keep only the artifacts (build/, output/, editor backups) local, matching the existing intent that rendered PDFs ship via static/. This is also a prerequisite for the site build: the vita page currently hand-copies education, publications, presentations, and experience out of these files, and has drifted from them in ~15 places. Generating it needs the data present in the repo. Includes the .claude/settings.local.json ignore rule already staged in the working tree. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SUGesXiMmACsLBTGG1xuEU --- .gitignore | 15 +- yaml-source/Makefile | 26 +++ yaml-source/README.md | 114 +++++++++++++ yaml-source/build.py | 146 ++++++++++++++++ yaml-source/data/affiliations.yml | 12 ++ yaml-source/data/education.yml | 33 ++++ yaml-source/data/experience.yml | 127 ++++++++++++++ yaml-source/data/grants.yml | 16 ++ yaml-source/data/personal.yml | 29 ++++ yaml-source/data/presentations.yml | 27 +++ yaml-source/data/projects.yml | 29 ++++ yaml-source/data/publications.yml | 64 +++++++ yaml-source/data/skills.yml | 56 +++++++ yaml-source/templates/cv.tex.j2 | 194 ++++++++++++++++++++++ yaml-source/templates/resume.tex.j2 | 114 +++++++++++++ yaml-source/templates/resume_ats.tex.j2 | 134 +++++++++++++++ yaml-source/templates/shared/commands.tex | 39 +++++ yaml-source/templates/shared/preamble.tex | 54 ++++++ 18 files changed, 1225 insertions(+), 4 deletions(-) create mode 100644 yaml-source/Makefile create mode 100644 yaml-source/README.md create mode 100644 yaml-source/build.py create mode 100644 yaml-source/data/affiliations.yml create mode 100644 yaml-source/data/education.yml create mode 100644 yaml-source/data/experience.yml create mode 100644 yaml-source/data/grants.yml create mode 100644 yaml-source/data/personal.yml create mode 100644 yaml-source/data/presentations.yml create mode 100644 yaml-source/data/projects.yml create mode 100644 yaml-source/data/publications.yml create mode 100644 yaml-source/data/skills.yml create mode 100644 yaml-source/templates/cv.tex.j2 create mode 100644 yaml-source/templates/resume.tex.j2 create mode 100644 yaml-source/templates/resume_ats.tex.j2 create mode 100644 yaml-source/templates/shared/commands.tex create mode 100644 yaml-source/templates/shared/preamble.tex diff --git a/.gitignore b/.gitignore index cf1f7c2..ad8bd6e 100644 --- a/.gitignore +++ b/.gitignore @@ -97,9 +97,13 @@ content/drafts/ checklist.md # CV/résumé build pipeline (YAML → Jinja → xelatex). The canonical PDFs -# live under static/ and ship with the site; the pipeline itself is -# kept locally for regeneration but not version-controlled here. -yaml-source/ +# live under static/ and ship with the site. The pipeline *source* is +# tracked: data/ is the single source of truth for both documents (and is +# read by the site build to render the vita page), and templates/ + +# build.py are what regenerate them. Only artifacts stay local. +yaml-source/build/ +yaml-source/output/ +yaml-source/data/*~ # Handover bundle (archived locally for reference; not part of the site). levineuwirth_handover.zip @@ -147,4 +151,7 @@ content/photography/**/*.raf content/photography/**/*.orf content/photography/**/*.tif content/photography/**/*.tiff -content/photography/**/*.psd \ No newline at end of file +content/photography/**/*.psd +# Claude Code local settings — machine-specific, and this repo mirrors +# to a public GitHub remote. +.claude/settings.local.json diff --git a/yaml-source/Makefile b/yaml-source/Makefile new file mode 100644 index 0000000..da1acf7 --- /dev/null +++ b/yaml-source/Makefile @@ -0,0 +1,26 @@ +.PHONY: all all_all cv resume resume_ats ats clean install + +# Default: build the two website-facing PDFs (cv + 1-page résumé). +# Use `make all_all` to also build the ATS-optimized résumé variant. +all: + @python3 build.py all + +all_all: + @python3 build.py all_all + +cv: + @python3 build.py cv + +resume: + @python3 build.py resume + +# ATS-optimized résumé variant. See templates/resume_ats.tex.j2. +resume_ats ats: + @python3 build.py resume_ats + +clean: + @rm -rf build output + @echo "cleaned." + +install: + @pip install --user pyyaml jinja2 diff --git a/yaml-source/README.md b/yaml-source/README.md new file mode 100644 index 0000000..4a4dbb9 --- /dev/null +++ b/yaml-source/README.md @@ -0,0 +1,114 @@ +# Levi Neuwirth — CV and Résumé + +Single-source pipeline producing two PDFs from a shared YAML content store: + +- `output/cv.pdf` — multi-page academic CV. +- `output/resume.pdf` — one-page industry résumé. + +## How it works + +Content lives in `data/*.yml`, one file per logical section. Each entry has +`cv_visible` and `resume_visible` flags that control whether it appears in +the CV, the résumé, or both. The two Jinja2 templates in `templates/` render +into LaTeX, which `xelatex` compiles. + +## Building + +```bash +make all # build both PDFs +make cv # build CV only +make resume # build résumé only +make clean # remove build/ and output/ +make install # pip install pyyaml + jinja2 (user install) +``` + +Requires: Python 3.9+, `xelatex` (TeX Live, MacTeX, or equivalent), Source +Serif Pro and Source Sans Pro fonts. On most Linux distributions these fonts +are in the `texlive-fonts-extra` or equivalent package; on macOS they're +available through Homebrew (`brew install --cask font-source-serif-pro +font-source-sans-pro`) or directly from Adobe. + +## Maintenance patterns + +**Add a new publication:** edit `data/publications.yml`, append an entry, +set `cv_visible` and `resume_visible` as appropriate. Rebuild. + +**Update a role:** edit `data/experience.yml`. The `cv_order` and +`resume_order` fields let you reorder per document. + +**Change the ordering on the résumé without changing the CV:** adjust +`resume_order` in `experience.yml`. The CV uses `cv_order` independently. + +**Move a project from "in progress" to "published":** move its entry from +`projects.yml` into `publications.yml`. + +**Hide something temporarily:** flip its visibility flag to `false`. The +entry stays in source, preserving history. + +## Page-count notes + +- **Résumé is a clean 1 page.** It holds five Experience entries (MARS V, + Shu Lab, NeuroAI, xAI, contracting), 3 projects, education, and header. + The fit depends on two deliberate choices: xAI is held to a single line + (scope line dropped, metrics bullets removed — see below), and the + résumé-only spacing in `resume.tex.j2` (`\titlespacing`, `itemize` + spacing, `\parskip`) is tuned so the last education note lands on page 1. + Adding a multi-line Experience or Projects entry will spill onto a second + page; when that happens, either trim content or nudge `\parskip` down + (0.1em is the current value; page 1 still reads with air at that level). +- **xAI is intentionally de-emphasized.** It sits fourth in Experience and + is one line that keeps only the `grok-code-fast-1` credential. The old + scope line and the two metrics bullets (tool-execution / API-usage + percentages, hallucination-rate reduction) were removed on purpose to + keep both documents' centre of gravity on research; do not re-add them + without a reason. +- **CV at 4 pages is normal** for this level of output. Page 4 is sparse + (Languages, Skills, Research Interests); tightening `\titlespacing` and + `\parskip` in `preamble.tex` can collapse to 3 pages but tends to + reappear at 4 as soon as new content is added. + +## Design notes + +- Body: Source Serif Pro. Headers: Source Sans Pro. Fully black and white. +- Templates use LaTeX-safe Jinja delimiters: `%% ... %%` for blocks, + `(( ... ))` for variables, `%# ... #%` for comments. This avoids + collisions with LaTeX's own `%` and `{}`. +- `\entryheader` in `templates/shared/commands.tex` is the unified + organization/role/date/location block used by both templates. +- The MARS V and Shu Lab entries have `cv_section: research` set in + `experience.yml`, which routes them into the CV's "Research Experience" + section while they still sit under plain "Experience" on the résumé. + Other routing flags can be added similarly. +- **Ordering is per-axis.** The résumé sorts Experience by `resume_order` + (research-first: MARS V, Shu Lab, NeuroAI, then xAI and contracting). + The CV templates render in **file order** within each section — the + `cv_order` field is documentation of intent, not a sort key, so keep the + YAML file order and `cv_order` consistent when reordering. + +## File layout + +``` +cv/ +├── data/ # YAML content (source of truth) +│ ├── affiliations.yml +│ ├── education.yml +│ ├── experience.yml +│ ├── grants.yml +│ ├── personal.yml +│ ├── presentations.yml +│ ├── projects.yml +│ ├── publications.yml +│ └── skills.yml +├── templates/ +│ ├── cv.tex.j2 +│ ├── resume.tex.j2 +│ └── shared/ +│ ├── commands.tex +│ └── preamble.tex +├── build.py # renders + compiles +├── Makefile +├── build/ # intermediate (.aux, .log, rendered .tex) +└── output/ + ├── cv.pdf + └── resume.pdf +``` diff --git a/yaml-source/build.py b/yaml-source/build.py new file mode 100644 index 0000000..d62614a --- /dev/null +++ b/yaml-source/build.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +Build script for CV and résumé. + +Loads YAML data files, renders Jinja2 LaTeX templates with a LaTeX-safe +delimiter configuration, and compiles both documents via xelatex. + +Usage: + python build.py cv # build CV only + python build.py resume # build website résumé only + python build.py resume_ats # build ATS-optimized résumé only + python build.py all # build cv + resume (default; not ats) + python build.py all_all # build cv + resume + resume_ats +""" + +import sys +import subprocess +import shutil +from pathlib import Path +from datetime import date + +import yaml +from jinja2 import Environment, FileSystemLoader + + +ROOT = Path(__file__).parent +DATA_DIR = ROOT / "data" +TEMPLATE_DIR = ROOT / "templates" +OUTPUT_DIR = ROOT / "output" +BUILD_DIR = ROOT / "build" + + +def load_all_data(): + """Load every .yml file in data/ and merge into a single dict.""" + data = {} + for yml_file in sorted(DATA_DIR.glob("*.yml")): + with open(yml_file) as f: + loaded = yaml.safe_load(f) + if not loaded: + continue + stem = yml_file.stem + if stem == "personal": + # personal.yml's contents become the `personal` key + data["personal"] = loaded + elif stem == "skills": + # skills.yml has multiple top-level keys — merge all directly + data.update(loaded) + else: + # everything else merges directly (education.yml has key + # 'education', experience.yml has key 'experience', etc.) + data.update(loaded) + data["build_date"] = date.today().strftime("%B %Y") + return data + + +def make_env(): + """Jinja2 environment configured for LaTeX-safe delimiters.""" + return Environment( + loader=FileSystemLoader(str(TEMPLATE_DIR)), + block_start_string="((*", + block_end_string="*))", + variable_start_string="(((", + variable_end_string=")))", + comment_start_string="((#", + comment_end_string="#))", + trim_blocks=True, + lstrip_blocks=True, + autoescape=False, + ) + + +def render(template_name, data): + env = make_env() + template = env.get_template(template_name) + return template.render(**data) + + +def compile_tex(tex_path, output_name): + """Run xelatex twice (for cross-references) and move the PDF to output/.""" + BUILD_DIR.mkdir(exist_ok=True) + OUTPUT_DIR.mkdir(exist_ok=True) + + # Copy shared templates into build dir so \input paths resolve cleanly + build_template_dir = BUILD_DIR / "templates" + if build_template_dir.exists(): + shutil.rmtree(build_template_dir) + shutil.copytree(TEMPLATE_DIR, build_template_dir) + + # Write .tex into build dir + tex_in_build = BUILD_DIR / f"{output_name}.tex" + tex_in_build.write_text(tex_path.read_text()) + + # Compile (twice, as is tradition) + for _ in range(2): + result = subprocess.run( + ["xelatex", "-interaction=nonstopmode", "-halt-on-error", + f"-output-directory={BUILD_DIR}", str(tex_in_build)], + cwd=ROOT, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(f"ERROR compiling {output_name}.tex:") + print(result.stdout[-2000:]) + sys.exit(1) + + # Move PDF to output/ + pdf_src = BUILD_DIR / f"{output_name}.pdf" + pdf_dst = OUTPUT_DIR / f"{output_name}.pdf" + shutil.copy(pdf_src, pdf_dst) + print(f" → {pdf_dst}") + + +def build_one(name): + """Build a single document by name ('cv' or 'resume').""" + print(f"Building {name}...") + data = load_all_data() + tex_source = render(f"{name}.tex.j2", data) + + # Write rendered .tex into build dir + BUILD_DIR.mkdir(exist_ok=True) + tex_path = BUILD_DIR / f"{name}.tex" + tex_path.write_text(tex_source) + + compile_tex(tex_path, name) + + +def main(): + target = sys.argv[1] if len(sys.argv) > 1 else "all" + if target in ("cv", "resume", "resume_ats"): + build_one(target) + elif target == "all": + build_one("cv") + build_one("resume") + elif target == "all_all": + build_one("cv") + build_one("resume") + build_one("resume_ats") + else: + print(f"Unknown target: {target}") + print("Usage: python build.py [cv|resume|resume_ats|all|all_all]") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/yaml-source/data/affiliations.yml b/yaml-source/data/affiliations.yml new file mode 100644 index 0000000..4d9d3b9 --- /dev/null +++ b/yaml-source/data/affiliations.yml @@ -0,0 +1,12 @@ +affiliations: + - name: "NSF Award \\#2148451" + full_title: "A Learning Ecosystem for Teaching High School Students Machine Learning Concepts and Data Science Skills in Healthcare and Medicine" + role: "Undergraduate mentor / research affiliate" + start: October 2023 + end: May 2025 + # The award title and role line above already state what the project is + # and what the role was; this line carries only the two non-obvious + # qualifications a reader can't infer from them. + description: "Not an author on the study's research outputs; independent of the Shu laboratory research." + cv_visible: true + resume_visible: false diff --git a/yaml-source/data/education.yml b/yaml-source/data/education.yml new file mode 100644 index 0000000..2af4cf0 --- /dev/null +++ b/yaml-source/data/education.yml @@ -0,0 +1,33 @@ +# Education entries, most recent first. +education: + - institution: Technical University of Denmark (DTU) + location: Kongens Lyngby, Denmark + degree: MSc in Computer Science and Engineering + start: September 2026 + end: expected 2028 + notes_cv: "Intended path toward PhD upon completion." + notes_resume: null + cv_visible: true + resume_visible: true + + - institution: Brown University + location: Providence, RI + degree: "Sc.B., Mathematics and Computer Science" + start: August 2022 + end: May 2026 + gpa: "3.8/4.0" + notes_cv: "Primary undergraduate advisor: D. Ellis Hershkowitz (Computer Science). Primary research supervisor: Liqi Shu, MD (Neurology, Warren Alpert Medical School)." + # Résumé keeps the UTRA credential only; DIS lives on the CV as its own entry. + notes_resume: "Brown Undergraduate Teaching and Research Award (UTRA), individual recipient, Summer 2024." + cv_visible: true + resume_visible: true + + - institution: DIS Copenhagen / Københavns Universitet + location: Copenhagen, Denmark + degree: Semester abroad + start: Fall 2024 + end: null + notes_cv: null + notes_resume: null + cv_visible: true + resume_visible: false # folded into Brown entry on résumé diff --git a/yaml-source/data/experience.yml b/yaml-source/data/experience.yml new file mode 100644 index 0000000..67eaeec --- /dev/null +++ b/yaml-source/data/experience.yml @@ -0,0 +1,127 @@ +# Experience entries. `resume_order` drives the résumé's Experience sort +# (research-first: MARS V, the two Shu Lab stints, contracting, then NeuroAI). +# `cv_order` documents intent; the CV template renders in file order within +# each section, so keep file order and cv_order consistent. The two axes let +# us order independently per audience without duplicating content. + +experience: + # MARS V heads both documents. On the CV it is routed into "Research + # Experience" via cv_section and sits first because it appears first among + # research entries in this file (the CV template preserves file order). + - organization: Cambridge AI Safety Hub + role: MARS V Fellow + location: Cambridge, United Kingdom + start: July 2026 + end: October 2026 + cv_section: research + cv_order: 1 + resume_order: 1 + cv_visible: true + resume_visible: true + bullets: + - "Selected for MARS V, a competitive part-time AI-safety research fellowship, working under James Petrie (Future of Life Institute)." + - "Research on zero-knowledge proofs for large language models, toward cryptographic verification of claims about model training, inference, and deployment." + + # TOGGLED OFF on both documents as of August 2026 — retained, not deleted. + # The entry is accurate and stays in the file so it can be switched back on + # by flipping the two visibility flags; the orders below are kept live so it + # lands back in the right slot (last among industry entries) if it is. + # Held to one line when shown: the grok-code-fast-1 scope is the dated, + # externally verifiable part, and the metrics bullets are omitted to keep + # both documents' centre of gravity on research. + - organization: xAI + location: Remote + start: May 2025 + end: August 2025 + cv_order: 5 + resume_order: 5 + cv_visible: false + resume_visible: false + scope: null + bullets: + - "Contributed to the training of \\texttt{grok-code-fast-1}, xAI's agentic coding model. Built LLM integrations into autonomous agent frameworks and resolved 50+ agentic tool-execution failures." + cv_summary: null + + # Contract work renders inside Industry Experience rather than its own + # section, and sits above NeuroAI because it is the more active of the two. + # `contract_work: false` keeps it out of the (now conditional) Selected + # Contract Work section while leaving that machinery available. + - organization: "Independent Research Contractor" + role: "Frontier AI labs" + contract_work: false + location: null + start: "2025" + end: "Present" + cv_order: 6 + resume_order: 4 + cv_visible: true + resume_visible: true + bullets: + - "Code and mathematics contributions across agentic workflow design, task and capability evaluation, reasoning assessment, model safety, and red-teaming." + + # Held to one line: the role is stated plainly without framing the venture's + # scope or direction. + - organization: NeuroAI LLC + role: + location: neuroai.health + location_url: https://neuroai.health + start: March 2026 + end: Present + cv_order: 7 + resume_order: 5 + cv_visible: true + resume_visible: true + bullets: + - "Early-stage clinical-AI venture. Leading model development and deployment infrastructure." + + # The Shu Lab is the anchor research block, split at the degree boundary: + # the undergraduate stint carries the body of work, and the affiliate stint + # below carries only what is still open. Splitting rather than retitling + # keeps "Undergraduate Researcher" accurate to the years it was true. + # Both live under "Research Experience" on the CV (a separate section from + # Industry Experience) and under "Experience" on the résumé. The template + # handles the section-header difference. + - organization: Shu Laboratory, Brown University Department of Neurology + role: Undergraduate Researcher and Technical Lead + location: Providence, RI + start: October 2023 + end: May 2026 + cv_section: research # forces this into "Research Experience" on CV + cv_order: 2 + resume_order: 2 + cv_visible: true + resume_visible: true + # Preamble paragraph only rendered on CV; résumé jumps straight to bullets. + cv_preamble: "Research with Dr.~Liqi Shu spanning clinical machine learning, movement-disorder kinematic analysis, and claims-based outcome prediction. Two distinct research threads:" + bullets: + - "Technical lead on \\textbf{NeuroPose}, a 3D pose-estimation and kinematic-analysis system for neurological-recovery research. Built end-to-end pipeline: Python/TensorFlow deep-learning inference, MATLAB statistical post-processing, Rust backend with HTML/JS frontends. System supports four externally-funded sub-projects; clinical-implications manuscript in preparation." + - "Co-lead developer on an \\textbf{order-invariant ICD-10-CM embedding model} for 30-day readmission (AUC 0.75 vs 0.66 CCI baseline) and postdischarge mortality prediction (AUC 0.86 vs 0.78), Deep Sets architecture trained on 113M+ adult hospitalizations from the HCUP Nationwide Readmissions Database. Preprint under review at \\textit{JAMIA}; public calculator deployed at \\href{https://levineuwirth.github.io/icd_embeddings/}{levineuwirth.github.io}." + + # Post-graduation continuation, deliberately bulletless: the work is fully + # described in the block above, so this entry's only job is to show the + # collaboration did not stop at the degree. Winding down through 2026. + # (The templates skip the itemize when `bullets` is absent.) + - organization: Shu Laboratory, Brown University Department of Neurology + role: Research Affiliate + location: Providence, RI + start: May 2026 + end: Present + cv_section: research + cv_order: 3 + resume_order: 3 + cv_visible: true + resume_visible: true + + # CCV / PQC block — research experience on CV only. Not on résumé because + # the PQC work appears there under Projects instead. + - organization: Brown University Center for Computation and Visualization (CCV) + role: Independent Researcher + location: Providence, RI + start: March 2025 + end: July 2026 + cv_section: research + cv_order: 4 + cv_visible: true + resume_visible: false + bullets: + - "Micro-architectural study of SIMD contributions to lattice-based post-quantum cryptography (ML-KEM / Kyber) on x86-64 AVX2, conducted on Brown's OSCAR HPC cluster with staff support. Phase 1 released as a technical report (see Publications)." diff --git a/yaml-source/data/grants.yml b/yaml-source/data/grants.yml new file mode 100644 index 0000000..838fb8b --- /dev/null +++ b/yaml-source/data/grants.yml @@ -0,0 +1,16 @@ +grants: + - name: MARS V Fellowship, Cambridge AI Safety Hub + kind: competitive selection + year: 2026 + # Fellowship description, dates, and mentor live on the MARS V experience + # entry; this note carries only the funding terms. + note: "Compute, travel, and accommodation provided." + cv_visible: true + resume_visible: false + + - name: Brown Undergraduate Teaching and Research Award (UTRA) + kind: individual recipient + year: Summer 2024 + note: "First funding awarded for NeuroPose; the work has since attracted three additional project-wide grants through the Shu laboratory." + cv_visible: true + resume_visible: false # folded into Brown education entry on résumé diff --git a/yaml-source/data/personal.yml b/yaml-source/data/personal.yml new file mode 100644 index 0000000..e4d7977 --- /dev/null +++ b/yaml-source/data/personal.yml @@ -0,0 +1,29 @@ +# Personal contact info and header links +name: Levi Neuwirth +email: ln@levineuwirth.org +phone: "+1 (845) 332-0898" +website: levineuwirth.org +website_url: https://levineuwirth.org + +# Links shown in header, in order. Set 'resume_visible' / 'cv_visible' to control appearance. +links: + - label: ORCID + href: https://orcid.org/0009-0002-0162-3587 + display: "0009-0002-0162-3587" + cv_visible: true + resume_visible: false + - label: Forgejo + href: https://git.levineuwirth.org/neuwirth + display: git.levineuwirth.org/neuwirth + cv_visible: true + resume_visible: true + - label: GitHub + href: https://github.com/levineuwirth + display: github.com/levineuwirth + cv_visible: true + resume_visible: true + - label: LinkedIn + href: https://linkedin.com/in/levineuwirth + display: linkedin.com/in/levineuwirth + cv_visible: false + resume_visible: true diff --git a/yaml-source/data/presentations.yml b/yaml-source/data/presentations.yml new file mode 100644 index 0000000..10bab81 --- /dev/null +++ b/yaml-source/data/presentations.yml @@ -0,0 +1,27 @@ +presentations: + - authors: "Shu L, Neuwirth L†, Wang X†, Zheng H†" + title: "Order-Invariant ICD-10-CM Embedding for Readmission and Mortality Prediction: Toward Multimodal Generative Patient Models" + venue: "IEEE/ACM Conference on Connected Health: Applications, Systems and Engineering Technologies (CHASE)" + year: 2026 + month: August + status: "Presented" + cv_visible: true + resume_visible: false + + - authors: "Ma J, Arms S, Kaneira L, Lall M, Chen K, Cabral W, Man D, Neuwirth L, Shu L" + title: "Early Detection of Neurological Disorders through Video-Captured Kinematic Analysis" + venue: "Summer UTRA Symposium, Brown University / Rhode Island Hospital Neurology Department" + kind: "Poster presentation" + year: 2025 + month: August + cv_visible: true + resume_visible: false + + - authors: "Neuwirth L, Dasher AS" + title: "Proving What a Datacenter Did: Verified Inference Between Adversaries" + venue: "MARS V, Cambridge AI Safety Hub" + year: 2026 + month: July + status: "Accepted" + cv_visible: true + resume_visible: false diff --git a/yaml-source/data/projects.yml b/yaml-source/data/projects.yml new file mode 100644 index 0000000..841cda5 --- /dev/null +++ b/yaml-source/data/projects.yml @@ -0,0 +1,29 @@ +# Projects shown on CV under "Systems and Engineering Projects" and on +# résumé under "Projects". The PQC entry is research-grade and also +# appears in Publications on the CV; on the résumé it's the strongest +# project and leads the list. + +projects: + - name: Post-Quantum Cryptography on x86 AVX2 + start: March 2025 + end: Present + description: "Micro-architectural study of SIMD contributions to ML-KEM / Kyber on Intel AVX2, conducted on Brown's OSCAR HPC cluster. Hand-written AVX2 assembly achieves 35--56$\\times$ speedup over compiler-optimized C for core NTT arithmetic; 5.4--7.1$\\times$ end-to-end KEM speedup. Full statistical analysis (Mann-Whitney U, Cliff's $\\delta$, bootstrapped CIs). Technical report and reproducible artifact public." + cv_visible: false # appears under Publications instead on CV + resume_visible: true + resume_order: 1 + + - name: Weenix + start: January 2025 + end: August 2025 + description: "Full Unix-like kernel in 10{,}000 lines of C: virtual memory, VFS, system calls, threading, device drivers and interrupt handlers, and file systems with custom linker support for running userspace x86-64 ELF binaries. Brown CS 169." + cv_visible: true + resume_visible: true + resume_order: 2 + + - name: Networking Stack from Scratch + start: October 2024 + end: July 2025 + description: "TCP/IP, RIP, UDP, and DNS implementations in Go, supporting file transmission of up to 1~GB across networks of up to 8 virtual machines." + cv_visible: true + resume_visible: true + resume_order: 3 diff --git a/yaml-source/data/publications.yml b/yaml-source/data/publications.yml new file mode 100644 index 0000000..6c6b49b --- /dev/null +++ b/yaml-source/data/publications.yml @@ -0,0 +1,64 @@ +# Publications and preprints. Equal-contribution undergrad authors marked +# with equal_contrib: true; the template renders the appropriate footnote. + +publications: + - authors: "Shu L, Neuwirth L†, Wang X†, Zheng H†" + title: "Beyond Comorbidity Indices: An Order-Invariant ICD-10-CM Embedding for Readmission and Mortality Prediction" + venue: "Under review at \\textit{Journal of the American Medical Informatics Association} (JAMIA)" + year: 2026 + links: + - label: Preprint + href: https://levineuwirth.org/essays/beyond-comorbidity-indices/ + - label: Code + href: https://git.levineuwirth.org/neuwirth/beyond_comorbidity_indices + - label: Calculator + href: https://levineuwirth.github.io/icd_embeddings/ + equal_contrib_note: "†~Equal contribution among undergraduate authors." + cv_visible: true + resume_visible: false # surfaced instead via the Shu Lab bullet on résumé + + - authors: "Neuwirth L" + title: "Branch-Tube Persistence and Static Coverage in Tree-Ball Geometry" + venue: "Preprint" + year: 2026 + month: July + links: + - label: Preprint + href: https://levineuwirth.org/essays/branch-based-local-capture-in-tree-balls/ + cv_visible: true + resume_visible: false # math preprint; engineering résumé surfaces other work + + - authors: "Neuwirth L" + title: "The Annealed Critical Window for Growing-Radius Domination in Random Regular Graphs" + venue: "Preprint" + year: 2026 + month: July + links: + - label: Preprint + href: https://levineuwirth.org/essays/near-critical-growing-radius-domination.html + cv_visible: true + resume_visible: false # math preprint; engineering résumé surfaces other work + + - authors: "Neuwirth L" + title: "Where Does SIMD Help Post-Quantum Cryptography? A Micro-Architectural Study of ML-KEM on x86 AVX2" + venue: "Technical report, Brown University Department of Computer Science" + year: 2026 + month: April + links: + - label: Report + href: https://levineuwirth.org/essays/where-does-simd-help-post-quantum-cryptography/ + - label: Artifact + href: https://git.levineuwirth.org/neuwirth/where-simd-helps + cv_visible: true + resume_visible: false # surfaced instead via the PQC project on résumé + + - authors: "NeuroPose clinical-implications manuscript" + title: null + venue: "In preparation. Shu laboratory, Brown University Department of Neurology." + year: "2026--2027" + links: + - label: Artifact + href: https://git.levineuwirth.org/neuwirth/neuropose + target: "Target venue TBD" + cv_visible: true + resume_visible: false # "in prep" doesn't belong on a résumé diff --git a/yaml-source/data/skills.yml b/yaml-source/data/skills.yml new file mode 100644 index 0000000..c16464e --- /dev/null +++ b/yaml-source/data/skills.yml @@ -0,0 +1,56 @@ +skills: + programming: + label: Programming + items: + - "Assembly (x86-64, inline SIMD)" + - C + - C++ + - "C\\#" + - Go + - Haskell + - Java + - JavaScript + - Python + - Rust + cv_visible: true + resume_visible: true + + ml_ai: + label: ML and AI + items: + - PyTorch + - TensorFlow + - NumPy + - Pandas + - OpenCV + cv_visible: true + resume_visible: true + + tools: + label: Tools and platforms + items: + - Git + - LaTeX + - Linux (Arch, Debian, Gentoo) + - make + - SLURM + - perf + - Docker + cv_visible: true + resume_visible: true + +# Human languages — separate section on CV, folded into Skills on résumé. +languages: + - name: English + level: native + - name: Spanish + level: C1 + - name: German + level: B1 + - name: Danish + level: A2 + - name: French + level: A1 + +# Research interests line — CV only; absent from résumé. +research_interests: "AI safety; graph theory, number theory, and theoretical computer science; computer systems and high-performance computing; artificial intelligence and machine learning; reinforcement learning; deep learning; cryptography; computer vision; computer architecture; security; clinical and biomedical ML" diff --git a/yaml-source/templates/cv.tex.j2 b/yaml-source/templates/cv.tex.j2 new file mode 100644 index 0000000..57fd522 --- /dev/null +++ b/yaml-source/templates/cv.tex.j2 @@ -0,0 +1,194 @@ +\documentclass[11pt]{article} + +\input{templates/shared/preamble.tex} + +\geometry{ + letterpaper, + top=0.9in, + bottom=0.9in, + left=1.0in, + right=1.0in +} + +\input{templates/shared/commands.tex} + +\begin{document} + +% HEADER +\begin{center} + {\headerfont\LARGE\bfseries (((personal.name)))}\\[0.3em] + {\headerfont\small\color{metagray}CURRICULUM VITAE \textperiodcentered\ Last updated (((build_date)))}\\[0.8em] + {\small + \href{mailto:(((personal.email)))}{(((personal.email)))} + \ \textperiodcentered\ + (((personal.phone))) + \ \textperiodcentered\ + \href{(((personal.website_url)))}{(((personal.website)))} + ((* for link in personal.links if link.cv_visible *)) + \ \textperiodcentered\ + \href{(((link.href)))}{(((link.label))): (((link.display)))} + ((* endfor *)) + } +\end{center} + +\vspace{0.4em} + +% EDUCATION +\section*{Education} + +((* for edu in education if edu.cv_visible *)) +\entryheader{(((edu.institution)))}{}{(((edu.start)))((* if edu.end *)) -- (((edu.end)))((* endif *))}{(((edu.location)))} +\textit{(((edu.degree)))}((* if edu.gpa *)). GPA (((edu.gpa))).((* else *)).((* endif *)) +((* if edu.notes_cv *)) +\\[0.2em] {\small (((edu.notes_cv)))} +((* endif *)) +\par\vspace{0.5em} +((* endfor *)) + +% RESEARCH EXPERIENCE +\section*{Research Experience} + +((* for exp in experience if exp.cv_visible and exp.cv_section == 'research' *)) +\entryheader{(((exp.organization)))}{(((exp.role or '')))}{(((exp.start)))((* if exp.end *)) -- (((exp.end)))((* endif *))}{(((exp.location or '')))} +((* if exp.cv_preamble *)) +(((exp.cv_preamble))) +\par\vspace{0.3em} +((* endif *)) +((* if exp.bullets *)) +\begin{itemize} +((* for bullet in exp.bullets *)) + \item (((bullet))) +((* endfor *)) +\end{itemize} +((* endif *)) +\vspace{0.3em} +((* endfor *)) + +% PUBLICATIONS AND PREPRINTS +\section*{Publications and Preprints} + +{\small Equal-contribution undergraduate authors are marked with \textdagger.}\par\vspace{0.4em} + +((* for pub in publications if pub.cv_visible *)) +\noindent +((* if pub.title *)) +\textbf{(((pub.authors))).} \textit{(((pub.title))).} (((pub.venue))).% +((* if pub.links *)) +((* for link in pub.links *)) +\ \href{(((link.href)))}{[(((link.label)))]}% +((* endfor *)) +((* endif *)) +\ (((pub.year)))((* if pub.month *)), (((pub.month)))((* endif *)). +((* else *)) +\textbf{(((pub.authors))).} (((pub.venue)))((* if pub.target *)) (((pub.target)))((* endif *)), (((pub.year))). +((* endif *)) +\par\vspace{0.5em} +((* endfor *)) + +% PRESENTATIONS +\section*{Presentations} + +((* for pres in presentations if pres.cv_visible *)) +\noindent +\textbf{(((pres.authors))).} \textit{(((pres.title))).} +((* if pres.kind *)) (((pres.kind))),((* endif *)) +\ (((pres.venue))),((* if pres.month *)) (((pres.month))) (((pres.year))).((* else *)) (((pres.year))).((* endif *)) +((* if pres.status *)) (((pres.status))).((* endif *)) +\par\vspace{0.5em} +((* endfor *)) + +% GRANTS AND AWARDS +\section*{Grants and Awards} + +((* for grant in grants if grant.cv_visible *)) +\noindent +\textbf{(((grant.name)))}((* if grant.kind *)), (((grant.kind)))((* endif *)).((* if grant.year *)) (((grant.year))).((* endif *)) +((* if grant.note *)) +\\[0.15em] {\small (((grant.note)))} +((* endif *)) +\par\vspace{0.4em} +((* endfor *)) + +% RESEARCH AFFILIATIONS +\section*{Research Affiliations} + +((* for aff in affiliations if aff.cv_visible *)) +\entryheader{(((aff.name))) --- (((aff.full_title)))}{(((aff.role)))}{(((aff.start)))((* if aff.end *)) -- (((aff.end)))((* endif *))}{} +{\small (((aff.description)))} +\par\vspace{0.4em} +((* endfor *)) + +% SYSTEMS AND ENGINEERING PROJECTS +\section*{Systems and Engineering Projects} + +((* for proj in projects if proj.cv_visible *)) +\noindent +\textbf{(((proj.name)))} {\small\color{metagray}\textit{(((proj.start)))((* if proj.end *)) -- (((proj.end)))((* endif *))}}. (((proj.description))) +\par\vspace{0.4em} +((* endfor *)) + +% INDUSTRY EXPERIENCE +\section*{Industry Experience} + +((* for exp in experience if exp.cv_visible and exp.cv_section != 'research' and not exp.contract_work *)) +\entryheader{(((exp.organization)))}{(((exp.role or '')))}{(((exp.start)))((* if exp.end *)) -- (((exp.end)))((* endif *))}{(((exp.location or '')))} +((* if exp.scope *)) +(((exp.scope))) +\par\vspace{0.2em} +((* endif *)) +((* if not exp.bullets *)) +((* elif exp.bullets|length == 1 *)) +(((exp.bullets[0]))) +\par +((* else *)) +\begin{itemize} +((* for bullet in exp.bullets *)) + \item (((bullet))) +((* endfor *)) +\end{itemize} +((* endif *)) +\vspace{0.3em} +((* endfor *)) + +% SELECTED CONTRACT WORK +% Conditional: contract entries normally render inside Industry Experience. +% This section appears only if an entry opts in with `contract_work: true`, +% so the heading never renders empty. +((* set contract_entries = experience | selectattr('cv_visible') | selectattr('contract_work', 'defined') | selectattr('contract_work') | list *)) +((* if contract_entries *)) +\section*{Selected Contract Work} + +((* for exp in contract_entries *)) +\noindent +\textbf{(((exp.organization))) --- (((exp.role)))}. {\small\color{metagray}(((exp.start)))((* if exp.end *)) -- (((exp.end)))((* endif *))}. +\par\vspace{0.2em} +((* for bullet in exp.bullets *)) +(((bullet))) +((* endfor *)) +((* if exp.cv_disclaimer *)) +\par\vspace{0.2em} +{\small\textit{(((exp.cv_disclaimer)))}} +((* endif *)) +((* endfor *)) +((* endif *)) + +% LANGUAGES +\section*{Languages} + +((* for lang in languages *)) +(((lang.name))) (((lang.level)))((* if not loop.last *)) \textperiodcentered\ ((* else *)).((* endif *)) +((* endfor *)) + +% TECHNICAL SKILLS +\section*{Technical Skills} + +((* for key, group in skills.items() if group.cv_visible *)) +\noindent\textbf{(((group.label))):} (((group['items'] | join(', ')))).\par\vspace{0.2em} +((* endfor *)) + +% RESEARCH INTERESTS +\section*{Research Interests} + +(((research_interests))) + +\end{document} diff --git a/yaml-source/templates/resume.tex.j2 b/yaml-source/templates/resume.tex.j2 new file mode 100644 index 0000000..21087bf --- /dev/null +++ b/yaml-source/templates/resume.tex.j2 @@ -0,0 +1,114 @@ +\documentclass[10pt]{extarticle} + +\input{templates/shared/preamble.tex} + +% 10.5pt body via extarticle + renewcommand approach. +% extarticle supports 8pt-20pt in 0.5pt increments; we set via class option +% above to 10pt then bump with \fontsize locally. Cleaner: just use 11pt +% class option with compensating \linespread. Simplest approach below: +\renewcommand{\normalsize}{\fontsize{10.5}{12.5}\selectfont} +\normalsize + +\geometry{ + letterpaper, + top=0.5in, + bottom=0.5in, + left=0.7in, + right=0.7in +} + +\input{templates/shared/commands.tex} + +% Ragged-right to prevent awkward word-spacing from full justification. +\RaggedRight + +% Résumé spacing: tightened for one-page fit. +\titlespacing*{\section}{0pt}{0.4em}{0.15em} +\setlist[itemize]{ + leftmargin=1.2em, + topsep=0.1em, + itemsep=0.08em, + parsep=0pt, + label={\small\textbullet} +} +\setlength{\parskip}{0.1em} + +% Looser entryheader: date/location on its own line below the org/role line. +% This prevents awkward wrap for long org names like Shu Laboratory. +\renewcommand{\entryheader}[4]{% + \noindent + \textbf{#1}\ifx&\else~---~\textit{#2}\fi% + \par + \vspace{-0.15em}% + {\small\color{metagray}#3\ifx&\else~\textperiodcentered~#4\fi}% + \par\vspace{0.15em}% +} + +\begin{document} + +% HEADER +\begin{center} + {\headerfont\Large\bfseries (((personal.name)))}\\[0.25em] + {\small + \href{mailto:(((personal.email)))}{(((personal.email)))} + \ \textperiodcentered\ + (((personal.phone))) + \ \textperiodcentered\ + \href{(((personal.website_url)))}{(((personal.website)))} + ((* for link in personal.links if link.resume_visible *)) + \ \textperiodcentered\ + \href{(((link.href)))}{(((link.label)))} + ((* endfor *)) + } +\end{center} + +\vspace{0.1em} + +% EXPERIENCE (ordered by resume_order) +\section*{Experience} + +((* for exp in (experience | selectattr('resume_visible') | sort(attribute='resume_order') | list) *)) +\entryheader{(((exp.organization)))}{(((exp.role or '')))}{(((exp.start)))((* if exp.end *)) -- (((exp.end)))((* endif *))}{(((exp.location or '')))} +((* if exp.scope *)) +(((exp.scope))) +\par\vspace{0.1em} +((* endif *)) +((* if not exp.bullets *)) +((* elif exp.bullets|length == 1 *)) +(((exp.bullets[0]))) +\par +((* else *)) +\begin{itemize} +((* for bullet in exp.bullets *)) + \item (((bullet))) +((* endfor *)) +\end{itemize} +((* endif *)) +\vspace{0.1em} +((* endfor *)) + +% PROJECTS +\section*{Projects} + +((* for proj in (projects | selectattr('resume_visible') | sort(attribute='resume_order') | list) *)) +\noindent\textbf{(((proj.name)))} {\small\color{metagray}\textit{(((proj.start)))((* if proj.end *)) -- (((proj.end)))((* endif *))}}. (((proj.description))) +\par\vspace{0.1em} +((* endfor *)) + +% EDUCATION +\section*{Education} + +((* for edu in education if edu.resume_visible *)) +\entryheader{(((edu.institution)))}{(((edu.degree)))}{(((edu.start)))((* if edu.end *)) -- (((edu.end)))((* endif *))}{(((edu.location)))} +((* if edu.gpa or edu.notes_resume *)) +{\small ((* if edu.gpa *))GPA (((edu.gpa))).((* endif *))((* if edu.notes_resume *)) (((edu.notes_resume)))((* endif *))} +((* endif *)) +\vspace{0.1em} +((* endfor *)) + +% Skills are intentionally omitted from this résumé — the projects and +% experience entries above prove the stack more credibly than a list can. +% A keyword-dense ATS variant can be rendered from the same YAMLs via a +% separate template when needed. + +\end{document} diff --git a/yaml-source/templates/resume_ats.tex.j2 b/yaml-source/templates/resume_ats.tex.j2 new file mode 100644 index 0000000..455cf6b --- /dev/null +++ b/yaml-source/templates/resume_ats.tex.j2 @@ -0,0 +1,134 @@ +%% ATS-optimized résumé variant. +%% +%% Design priorities, in order: +%% 1. Parses cleanly through ATS pipelines — single column, conventional +%% section headings ("Work Experience", "Technical Skills", etc.), +%% no images or boxes, no fancy typography hacks. +%% 2. Keyword density — restores the Technical Skills section and opens +%% with a professional summary so keyword matchers have plenty to +%% grep over. +%% 3. Readable for a human recruiter skimming the output of whatever +%% ATS the company uses. +%% +%% Page budget is unconstrained; ATS pipelines do not penalize 2 pages. +%% The website-facing résumé (resume.tex.j2) is the 1-page artifact. + +\documentclass[11pt]{article} + +\input{templates/shared/preamble.tex} + +\geometry{ + letterpaper, + top=1in, + bottom=1in, + left=1in, + right=1in +} + +\input{templates/shared/commands.tex} + +% Ragged-right reads more naturally than full justification in short lines. +\RaggedRight + +% Conventional section spacing — not tight, not loose. ATS parsers are +% sensitive to unusual vertical rhythm; stick close to article defaults. +\titlespacing*{\section}{0pt}{0.8em}{0.3em} +\setlist[itemize]{ + leftmargin=1.2em, + topsep=0.25em, + itemsep=0.2em, + parsep=0pt, + label={\small\textbullet} +} +\setlength{\parskip}{0.3em} + +% Entryheader for ATS: organization/role on one line, date/location on +% the next. Plain black text (no metagray) because some ATS grayscale +% conversions mangle low-contrast text. +\renewcommand{\entryheader}[4]{% + \noindent + \textbf{#1}\ifx&\else~---~\textit{#2}\fi% + \par + \vspace{-0.15em}% + {\small #3\ifx&\else~\textperiodcentered~#4\fi}% + \par\vspace{0.2em}% +} + +\begin{document} + +%% HEADER +\begin{center} + {\headerfont\Large\bfseries (((personal.name)))}\\[0.25em] + {\small + \href{mailto:(((personal.email)))}{(((personal.email)))} + \ \textperiodcentered\ + (((personal.phone))) + \ \textperiodcentered\ + \href{(((personal.website_url)))}{(((personal.website)))} + ((* for link in personal.links if link.resume_visible *)) + \ \textperiodcentered\ + \href{(((link.href)))}{(((link.label)))} + ((* endfor *)) + } +\end{center} + +\vspace{0.3em} + +%% SUMMARY +\section*{Summary} + +Computer science and mathematics researcher and engineer building autonomous AI agents, clinical machine-learning systems, and low-level cryptographic software. Summer 2025 intern at xAI contributing to training of \texttt{grok-code-fast-1}. Co-founder of NeuroAI LLC (clinical AI tooling). Undergraduate research at Brown University Department of Neurology; preprint under review at \textit{JAMA Network Open}. Independent research in post-quantum cryptography and SIMD micro-architecture, supported by Brown's HPC staff. Proficient in Python, Rust, Go, C, Assembly (x86-64 AVX2), and modern ML frameworks (PyTorch, TensorFlow). + +%% WORK EXPERIENCE +\section*{Work Experience} + +((* for exp in (experience | selectattr('resume_visible') | sort(attribute='resume_order') | list) *)) +\entryheader{(((exp.organization)))}{(((exp.role)))}{(((exp.start)))((* if exp.end *)) -- (((exp.end)))((* endif *))}{(((exp.location or '')))} +((* if exp.scope *)) +(((exp.scope))) +\par\vspace{0.1em} +((* endif *)) +((* if exp.bullets|length == 1 *)) +(((exp.bullets[0]))) +\par +((* else *)) +\begin{itemize} +((* for bullet in exp.bullets *)) + \item (((bullet))) +((* endfor *)) +\end{itemize} +((* endif *)) +\vspace{0.3em} +((* endfor *)) + +%% PROJECTS +\section*{Projects} + +((* for proj in (projects | selectattr('resume_visible') | sort(attribute='resume_order') | list) *)) +\noindent\textbf{(((proj.name)))} \textit{(((proj.start)))((* if proj.end *)) -- (((proj.end)))((* endif *))}. (((proj.description))) +\par\vspace{0.3em} +((* endfor *)) + +%% EDUCATION +\section*{Education} + +((* for edu in education if edu.resume_visible *)) +\entryheader{(((edu.institution)))}{(((edu.degree)))}{(((edu.start)))((* if edu.end *)) -- (((edu.end)))((* endif *))}{(((edu.location)))} +((* if edu.gpa or edu.notes_resume *)) +{\small ((* if edu.gpa *))GPA (((edu.gpa))).((* endif *))((* if edu.notes_resume *)) (((edu.notes_resume)))((* endif *))} +((* endif *)) +\vspace{0.2em} +((* endfor *)) + +%% TECHNICAL SKILLS — restored for ATS keyword matching. +\section*{Technical Skills} + +((* for key, group in skills.items() if group.resume_visible *)) +\noindent\textbf{(((group.label))):} (((group['items'] | join(', ')))).\par\vspace{0.15em} +((* endfor *)) +\noindent\textbf{Human Languages:} +((* for lang in languages *)) +(((lang.name))) (((lang.level)))((* if not loop.last *)), ((* else *)).((* endif *)) +((* endfor *)) + +\end{document} diff --git a/yaml-source/templates/shared/commands.tex b/yaml-source/templates/shared/commands.tex new file mode 100644 index 0000000..7e85448 --- /dev/null +++ b/yaml-source/templates/shared/commands.tex @@ -0,0 +1,39 @@ +% Custom macros and section formatting. + +% \sectionheader — unified section header style, sans-serif, uppercase tracking, +% thin rule beneath. Used by both templates for consistency. +\titleformat{\section} + {\headerfont\large\bfseries\MakeUppercase} + {}{0pt} + {} + [\vspace{-0.4em}\rule{\linewidth}{0.4pt}] + +\titlespacing*{\section}{0pt}{1.2em}{0.6em} + +% \subsectionheader — for sub-entries (e.g., NeuroPose within Shu Lab). +\titleformat{\subsection} + {\headerfont\normalsize\bfseries} + {}{0pt} + {} + +\titlespacing*{\subsection}{0pt}{0.6em}{0.2em} + +% \entryheader — a standard role/institution block. Two lines: +% Line 1: \textbf{Organization} — \textit{Role} +% Line 2: \small dates · location +\newcommand{\entryheader}[4]{% + \noindent\textbf{#1}% + \ifx&\else~---~\textit{#2}\fi\\[0.1em] + {\small\color{metagray}#3\ifx&\else~\textperiodcentered~#4\fi}% + \par\vspace{0.2em}% +} + +% \pubentry — a publication/presentation line. +\newcommand{\pubentry}[3]{% + \noindent #1. \textit{#2}. {\small\color{metagray}#3}% + \par\vspace{0.35em}% +} + +% \headerblock — document-top name and contact. Rendered differently by the +% two templates, so the actual layout is defined there; this is just a +% placeholder hook if we want to factor it out later. diff --git a/yaml-source/templates/shared/preamble.tex b/yaml-source/templates/shared/preamble.tex new file mode 100644 index 0000000..7656677 --- /dev/null +++ b/yaml-source/templates/shared/preamble.tex @@ -0,0 +1,54 @@ +% Shared preamble: packages, fonts, page geometry. +% Kept intentionally minimal — every package here is mainstream and stable. + +\usepackage{fontspec} +\usepackage{geometry} +\usepackage{titlesec} +\usepackage{enumitem} +\usepackage{hyperref} +\usepackage{xcolor} +\usepackage{ragged2e} +\usepackage{fancyhdr} + +% Fonts: Source Serif/Sans if available, fall back to TeX Gyre (Termes/Heros). +% TeX Gyre ships with every TeX Live install, so this compiles anywhere. +% To force Source fonts on your own machine, just ensure they're installed +% system-wide or via TeX Live's fontspec search path. +\IfFontExistsTF{Source Serif Pro}{ + \setmainfont[Ligatures=TeX, Numbers={Lining,Proportional}]{Source Serif Pro} +}{ + \setmainfont[Ligatures=TeX, Numbers={Lining,Proportional}]{TeX Gyre Termes} +} + +\IfFontExistsTF{Source Sans Pro}{ + \newfontfamily\headerfont[Ligatures=TeX, Numbers={Lining,Proportional}]{Source Sans Pro} +}{ + \newfontfamily\headerfont[Ligatures=TeX, Numbers={Lining,Proportional}]{TeX Gyre Heros} +} + +% Black-and-white color palette. `linkcolor` stays black for print-conservative +% output; the URL is still clickable in PDF readers. +\definecolor{headerblack}{gray}{0.0} +\definecolor{rulegray}{gray}{0.15} +\definecolor{metagray}{gray}{0.35} + +\hypersetup{ + colorlinks=true, + linkcolor=black, + urlcolor=black, + citecolor=black, + pdfborder={0 0 0} +} + +% No paragraph indent, small inter-paragraph space. +\setlength{\parindent}{0pt} +\setlength{\parskip}{0.35em} + +% Tight list defaults — itemize blocks should feel controlled, not sprawling. +\setlist[itemize]{ + leftmargin=1.2em, + topsep=0.2em, + itemsep=0.25em, + parsep=0pt, + label={\small\textbullet} +}