Nathan Bennett's portfolio: selected work and one chronological timeline with Career, Education, and Project rails. Built with Svelte 5, SvelteKit, TypeScript, and Tailwind CSS 4.
V1's Svelte 3 source remains at 99c5560 and on the v1 branch. V2 includes 31 original portfolio items plus 14 chapters from the user-selected prototype (career, education, and Zombiehood). Dates and historical claims still need editorial confirmation.
Use Node.js 22.12+ and npm with the committed lockfile. Vercel's runtime is explicitly Node 22.
npm ci
npm run devOpen the printed URL, normally http://localhost:5173.
npm run check # TypeScript and Svelte, including content scripts
npm test # Content preservation, schema, timeline and Markdown safety
npm run content:check # Item structure, local references and Markdown links
npm run content:audit # Asset sizes, duplicate hashes and unreferenced files
npm run build # Validate and prerender the production app
npm run preview # Serve that production build
npm run format # Consistent formatting across source and content
npm run format:check # Check formatting without edits
npm run audit # Dependency security auditNo database or API is required. SvelteKit imports the item files and asset URLs at build time. The homepage is prerendered; search, animated track filtering, archive disclosure, and media expansion run locally in the browser. Gameplay animations sit alongside screenshots in Notes & media and autoplay only while visible. Offscreen animations and hidden tabs stop rendering; reduced-motion visitors get a still image with a Play control.
content/items/game-project-adder/
entry.json # Metadata + Markdown body in one record
assets/
cover.png
animation.gif
screenshots/01.jpg
screenshots/02.jpg
extras/01.png # Preserved historical media, not in the public gallery
The common schema is content/schema.ts. src/lib/content.ts validates and imports the records. PortfolioItem.svelte is the reusable presentation component; Company and Education use the same item model without requiring media. Vite imports generate fingerprinted asset URLs, so moving an item folder and its record does not require hand-maintaining public URLs.
npm run item:new -- --kind project --slug my-project --title "My project"This creates a draft with a screenshots folder. Edit its JSON, add media, and set draft to false when ready. Drafts validate but do not appear in the app. See content editing rules.
content/profile.json contains biography, skills, contact links, and the resume reference. content/asset-migrations.json maps historical asset URLs to item folders. Existing deep-link paths remain metadata until a future routing pass. The old contact form is not part of this MVP.
brew install optipng gifsicle jpeg-turbo
# Python 3 with Pillow is required for independent decoded-image verification.
npm run assets:optimizeThe TypeScript command runs OptiPNG, Gifsicle and jpegtran. It accepts only smaller outputs with identical decoded RGBA pixels, frame durations, loop count, ICC profile, and EXIF. It never uses JPEG re-encoding or lossy GIF options. Failures keep the original. Detailed results are written to .reports/lossless-optimization.json.
- The catalog's Markdown renders through an explicit renderer: raw HTML is escaped, unsafe link schemes are rejected, and images resolve only to local item assets.
- Unknown dates remain unknown; year-only and month-only dates keep their precision. Imported reference dates carry provenance and
datesNeedReview. - “Earlier chapters” previews one and a half older entries with a fading edge. Continuing to scroll past its prompt reveals the full archive; the prompt also supports keyboard activation. Close archive returns to the preview. Search includes all history directly.
- The scoped
cookieoverride patches SvelteKit's transitive dependency; revisit it on framework upgrades. - Superseded aggregate content, loose descriptions, duplicate item assets, and Paint.NET source files have been removed. Historical source remains available in Git and on
v1.
The header theme selector supports System (default), Light, and Dark. Explicit choices persist locally; System tracks OS appearance changes. Company logos and studio artwork are stored within their owning items; source URLs are recorded in content/image-sources.json. Logo artwork retains its original colors on a suitable light or dark surface. The hero uses Dead Traveler’s official studio background. Oswald is self-hosted with its SIL Open Font License in static/fonts/.
npm run assets:modernize uses gif2webp (Homebrew webp) and Python/Pillow to generate lossless animated WebP variants. A variant is selected only when smaller and every decoded frame, duration, loop count, ICC profile and EXIF match. Original GIFs remain in their item folders; content/animation-optimization.json records verification and hashes.
Work (/) contains the featured cards and timeline. About (/about) contains a short introduction and the skills catalog. Blog (/blog) lists published posts, with individual prerendered pages. The first post is “What’s done is done”; its copy lives in content/blog/whats-done-is-done.json and is included in the AI-readable text. Shared navigation highlights the current page. The About skills combine the original catalog and the supplied résumé, with local Devicon brand icons and Feather symbols for broader disciplines. Technology chips link to official homepages or documentation. Icon sources and licenses live in static/icons/skills/; résumé provenance is recorded in content/skill-sources.json. Page navigation uses a short View Transition when supported, respecting reduced motion.
/llms.txt provides a compact portfolio index; /llms-full.txt includes the published descriptions, dates, skills, and external links. Both are generated from the content catalog during each build, exclude draft entries and source-only concepts, and require no JavaScript. Canonical links point to https://nlb.dev. This is a reading aid, not a crawler permission or training policy.
Posts live in content/blog/*.json and use one shared /blog/[slug] page. Set draft: true to show a post only on the dev deployment (or a local dev server); set it to false to publish it everywhere. The server-only catalog filters the index, direct routes, prerender entries, and AI-readable endpoints together. Draft pages use noindex, nofollow; this is a public preview, not confidential storage.
VERCEL_GIT_COMMIT_REF is authoritative on Vercel. Unknown production builds exclude drafts. To reproduce a deployment locally, use BLOG_BUILD_BRANCH=main npm run build or BLOG_BUILD_BRANCH=dev npm run build. A local dev server shows drafts unless a branch variable explicitly excludes them. Draft content is never imported into client modules; production does not emit a draft HTML page or route data. AI-readable links use the matching main/dev hostname.
/looking-for is the shareable partnership page, with copy in content/looking-for.json. Migration draft statistics and count methodology are recorded in content/research/zombiehood-migration.json; they describe named commits, not live progress.
Zombiehood’s wider cover is copied from the existing trailer asset Marketing/Trailers/images/wide-logo_16x9_1440x810.png in the Dead Traveler Drive folder. The previous cover remains in the item’s assets for reference.