Humanized Portfolio

In the age of AI authoring, I wanted to handmake my portfolio website. The viewers can adjust the slider at the top of the page to their viewing preference.

Rendering Modes

Notch 0 — Computer:

8-bit binary. Each word is written in two lines to preserve the visual blocking of the site. A selectionchange listener snaps selection boundaries to whole word spans so partial row selection is not possible to preserve the order by not allowing partial selections.

Notch 1 — Mostly Computer:

Font switches to Courier Prime.

Notch 2 — Neutral:

I created a font out of my own handwriting with Calligraphr, using my favorite writing implements: A uniball Signo 207 gel pen, and for bold/headers a Fine Point Sharpie. Because I'm writing these words I wanted to physically write each of these letters too. I wrote the alphabet out (353 characters for cases, punctuation, symbols, accenting), scanned it in, and modified each character's spacing if needed. Then exported to an .otf file.

Notch 3 — Mostly Human:

[]

Notch 4 — Human:

An SVG <filter> using feTurbulence and feBlend is injected into the DOM and applied via CSS filter: url(#hw-texture) to all text elements. The fractal noise layer simulates ink variation on paper.

Architecture

The slider state lives in a HumanizerContext provided at the layout level. A HumanizerContent wrapper component reads from context and applies the appropriate CSS classes and font variables.

Font switching is handled entirely through CSS custom properties — the active font variable is set on the container and inherited by all children, so no per-element logic is needed.

The binary encoding effect runs in a useEffect tied to the notch value. A TreeWalker traverses every text node in the container, skipping nodes already inside a [data-binary] span. Each word is uppercased, split at the midpoint, and each half encoded as space-separated 8-bit binary rendered as a two-row inline <span>. Original text nodes are moved into hidden display:none wrappers rather than removed, so React can reconcile the virtual DOM without losing content. A MutationObserver re-applies the effect if React updates the subtree after initial application.

The ink texture at notch 4 is an SVG <filter> using feTurbulence and feBlend injected into the DOM and applied via filter: url(#hw-texture) on all text elements. It is rendered only when that notch is active.

Stack

Framework:

Next.js 16 with App Router and Turbopack: Server components keep the initial render fast. Turbopack cuts dev rebuild time significantly over Webpack.

Language:

TypeScript: MDX metadata, React context, and DOM manipulation code all pass data between each other. Types make those contracts explicit so a change in one place doesn't silently break another.

Content:

MDX: Project pages are authored in MDX, which allows metadata exports and React component embedding alongside markdown. The metadata drives the project list; the content renders directly in the detail page.

Styling:

Tailwind CSS v4: Utility classes handle layout and spacing. Custom CSS properties allow the active font to be swapped globally from a single context value.

State:

React Context API: Propagates the slider notch to all content components without prop drilling.

localStorage: Persists the slider preference across sessions.

APIs:

DOM TreeWalker: Efficiently traverses only text nodes for binary encoding.

MutationObserver: Re-applies binary mode if React updates the subtree.

Selection API: Snaps selections to whole word spans so partial row selection is not possible.

Deployment:

Vercel: Native Next.js support with zero config and automatic static generation per route.

site status

Checking…

production@30e2ae0Merge pull request #1 from luka-sherman/development

Dedication

To my high school self who bought this domain, to my college and postgrad selves who kept paying for it, it just took me 12 years to put something here.

Next Project