Personal portfolio site built with Eleventy.
npm install
npm run serve # Dev server at http://localhost:8080
npm run build # Output to docs/ for GitHub Pages- Math — MathJax 3 via
$inline$and$$block$$delimiters - Syntax highlighting — Prism.js for fenced code blocks
- Images — Place in
src/assets/images/, reference with/assets/images/... - Drafts — Add
draft: trueto frontmatter; the page renders innpm run servebut is excluded fromnpm run buildand from all listings
_resumes/data/master.yaml is the CV — there is no separate CV spec. Every entry in the file appears on the generated CV, in file order, with all non-alt bullets. Nothing is curated in or out downstream, so an entry that doesn't belong on the CV doesn't belong in the file; take something off the CV by deleting it, not by deselecting it. The file also carries the CV's own sections (section order and headings). The CV deliberately has no headline, no summary, and no interests row, and the loader fails the build if master.yaml grows either key or that section — so an unattended regeneration can't switch one back on. Tailored variants set their own; putting one back on the CV means removing the guard in _resumes/_lib/load.js.
That one source renders into:
-
The CV —
npm run gen:resumeswrites_cv.texstraight frommaster.yaml; CI compiles it and ships it as/cv.pdf. Generate it alone withnode _resumes/_generate.js _cv. -
Job-specific PDFs — one spec per application in
_resumes/variants/<name>.yamlselects a subset of the same content: entries and bullets by id, with inline one-off rewordings.npm run gen:resumesrenders every spec to a.texat the_resumes/root;npm run build:resumescompiles them (ornpm run resumesfor both). Variant specs are the only place selection happens.Writing one: select and reword ids that exist, never invent. Keep a
HONESTY NOTESblock recording the JD keywords you deliberately did not claim, so the next run doesn't quietly claim them — a claim that can't survive one follow-up question costs more than a missing keyword. Lead with the strongest quantified bullet, and keep numbers at their original grain ("121 failed attempts, 22 dead-letters", not "high reliability"): surveys of hiring managers put the rejection trigger on genericness, not on AI use, and a number that specific is the one thing a competitor can't fabricate. Before sending, check what a parser sees —pdftotext -raw foo.pdf -for reading order andpdffonts foo.pdffor glyphs that won't extract. Workday prefills work history from the upload and mis-maps often, so verify its dates by hand.
Privacy: job-specific variants (both the variants/*.yaml specs and their generated .tex) are gitignored — they reveal where you're applying, and this repo is public. Only the underscore-prefixed shared files are tracked (_cv.tex, _preamble.tex, _heading.tex, data/*.yaml). Keep your own backup of the variant specs; git does not have them.
- The homepage timeline —
src/_data/resume.jscomputes the timeline at site build time frommaster.yaml+_resumes/data/website.yaml, so the website can never drift from the resumes.
Bullet text is canonical prose with a tiny markup, escaped per target (LaTeX/HTML):
| Canonical | LaTeX | HTML |
|---|---|---|
**bold** / *italic* |
\textbf / \emph |
<strong> / <em> |
[text](url) |
\href{url}{text} |
<a href="url"> |
% & # $ _ (literal) |
escaped | escaped |
– — ~ × λ “ ” |
-- --- $\sim$ … |
literal |
Rules: bullets always use >- block scalars (never wrap a line mid-word — YAML folding inserts a space); dates are quoted "YYYY-MM" or present; raw backslashes are a validation error (use a per-bullet tex:/html: override for anything the markup can't express). Bullets are normalized at render time to the house style — terminal punctuation always present, and a plain all-lowercase opening word is capitalized (mixed-case openers like iOS/gRPC are left alone; tex:/html: overrides bypass normalization). Markup goldens: npm run test:resumes.
Variant specs also take optional top-level keys and entry fields:
headline:— one professional line rendered under the name in the heading (small caps).summary:— a short Summary section rendered first, before Skills.blurb:(on a role entry, as a per-variant override or inmaster.yaml) — one italic line of company/role context between the role heading and its bullets.pageBreak: true(on a section, in a variant or inmaster.yaml) — start that section on a fresh page. Rejected on the first section, which would leave page 1 empty.
master.yaml also carries a publications: collection — papers he is an author on. Each entry has title, authors (canonical text, so **Tyler Flar** bolds his own name), venue, date ("YYYY-MM"), and optionally location, url with a urlLabel, and a note saying which part of the work was his — a co-authored paper does not say that on its own. The CV renders each as one citation line (\resumePublication, no two-column heading) with the note as its single bullet; the homepage renders the whole collection as a Publications section, and website.yaml may add a project link per id to the write-up the paper came out of. Variants select them like any other entry, under a kind: publication section. Only his own work goes in bullets anywhere in the file: group projects tell the team's story on the website, but a CV bullet has to survive a look at the commit history.
The sections: block in master.yaml is the house format reference: section order is Skills → Professional Experience → Projects → Publications → Leadership & Volunteering → Education, and tailored variants repeat that order with trimmed entry lists. A variant may add an interests section, which the CV does not carry; it belongs at the top, ahead of Skills. Every collection in master.yaml must be rendered by some section — the loader rejects a collection no section covers, so dead content can't accumulate.
_resumes/data/interests.yaml holds the short "what I'm into" row. It has two rendered shapes, one per surface — the website always shows it; a résumé shows it only where its spec declares an interests section, which the CV's does not:
- Website — emoji bubbles in the hero card, under a hairline rule and an
Interestslabel that sets them apart from the bio and the link pills. - Résumé — one flush-left line near the top (
\resumeInterests): a small-caps label, then the picks separated by middots, with a hanging indent so a wrapped second line aligns under the picks rather than the label. Deliberately not a\section— it's a personal aside near the top, and a ruled heading would give it the same weight as Professional Experience. Text only; emoji don't survive pdflatex, so the LaTeX renderer drops them.
The file has two halves, and the split is the whole design:
bank:— the vocabulary you may pick from, modelled on dating-app interest badges (Bumble ships ~200 across a dozen categories; Tinder "Passions" shows 3–5). Broad labels a stranger reads at a glance: Cooking, not "sous-vide short ribs". Categories exist to keep the bank browsable and are never displayed.selected:— the ids that actually show, in display order. Aim for 6–8; the loader refuses more than 10, rejects duplicates, and rejects any id the bank doesn't define (with the full bank in the error). Empty the list to drop the row from both surfaces.
An interests section is available to variant specs too, optionally narrowed with its own items: [cooking, gym].
selected: is maintained by a Tasque interests sync run (data/work-templates/interests-sync/), which reads what he's actually been doing from his Tasque memories, rewrites the list, regenerates _cv.tex, and commits. The CV carries no interests section, so that regeneration is a no-op for the PDF — the sync's visible effect is the website hero row. npm run test:resumes is that run's contract — bank ids well-formed, every pick resolvable, every label LaTeX-safe, every emoji actually an emoji.
Length is not fixed. There is no page limit in the builder and none in the specs: a résumé runs as long as its content earns, and no longer. One page is a convention rather than a rule — the best test of it (ResumeGo, 482 recruiters over 7,712 resumes) found two-page resumes preferred 2.3× overall and 1.4× even for entry-level roles, so deleting real evidence to hit one page trades away the thing being measured. What the tools give you is measurement, not a ceiling — the generator prints an approximate word count per variant (generated foo.tex (~480 words)) and the build prints the real page count from the pdflatex log (-> output/foo.pdf (2 pages)). The one outcome to avoid is the 1.2-page document whose second page holds nothing but Education; go tight on one page or genuinely fill two. Two levers make a deliberate multi-page résumé read well instead of merely spilling:
- Pages after the first carry a
Tyler Flar — Page Ncontinuation header, so a page that gets separated still identifies itself. Page 1 suppresses it (the full name block is already there). pageBreak: trueputs the break where you chose it, so a later page opens on a clean section boundary instead of a half-finished entry or a stray bullet carried over from the page before.
A page that is mostly white space is the one thing to avoid: either cut back to fill what you have, or add the substance that justifies it.
The generator refuses to overwrite a hand-written .tex (no AUTO-GENERATED header) unless run with --force; such files stay frozen at the _resumes/ root and still compile. --check exits non-zero if any committed .tex is stale relative to its spec.
A variant spec is a snapshot of one application, and the master library moves on without it: when content a spec selected has since been removed from master.yaml, a full npm run gen:resumes reports that spec as skipped (stale spec) and carries on, so old snapshots never block the CV or the interests sync. Its committed .tex still builds. Asking for that variant by name still fails loudly, and --check counts it as stale.
markdown-it-mathjax3is pinned to 4.x. v5.2.0 is unusable here: itsdeasync-based renderer exhausts the JS heap (build OOMs at 2 GB; the 4.x build finishes in under a second).npm auditis clean on 4.x, so there is no security pressure to retry until upstream fixes it.