Humanized-Computerized Portfolio
Rendering Modes
My handwriting — a custom font I created, using my favorite writing implements: A uniball Signo 207 gel pen, and for bold/headers a Fine Point Sharpie. I wrote the alphabet out (353 characters for cases, punctuation, symbols, accenting), uploaded it to Calligraphr, and modified each character's spacing. Then exported to an .otf file.
Full-resolution standard image.
Source Code Pro — a typewriter-style typeface. Readable, accessible, and expected. The initial default setting.
The photo as sampled. For the project pages, there is no change to any images, they remain full resolution and legible. For the homepage selfie only, it is dropped to ~100px to create a stepping stone for future transitions.
Hex — each letter becomes its UTF-8 bytes in hexadecimal: the format a computer actually is actually saving on disk.
The photo as stored. JPEG cuts an image into 8×8 blocks, converts each to the frequency domain, and drops detail by quantizing the coefficients — this mode imitates with plain block-averaging.
Binary — the letters as processed. Every character cut to the raw eight bits a processor moves through memory, shown as space-separated 8-bit bytes.
The photo as processed. A decoded image in memory is a flat array of bytes — three per pixel, eight bits a channel, the same eight bits that spell one character in binary text. This drops the colour and seven of every eight of them to reach pure black and white, then fakes the missing grays with an Atkinson dither — the trick fax machines, early Macs, and e-ink displays actually use.
Signal — the words as transmitted. The same bits leaving the machine as a slew-stepped voltage on a wire (up for 1, down for 0), traced with block characters.
The photo as transmitted. Analog TV, VGA, and slow-scan radio send a picture one horizontal line at a time, as a voltage that rises and falls with brightness. An oscilloscope on that wire shows exactly this: one luminance trace per line, stacked.
Custom cursor
At the human-est notch the OS cursor is replaced too, with my literal hand. Hover over a cell below to use it:
Accessibility
On first page visit, the slider is set to 3: The typeface is Source Code Pro, and is legible and familiar. All images are clear and visible (only the home selfie is slightly pixelated for stylistic effect).
IF the user desires, they can then experiment with the slider, but it is not necessary. If they wander to an unreadable notch, the slider pulsates as a visual cue of how to escape back to the human zone.
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 machine-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-encoded] span. Each word is handed to the notch's encoder — binary uppercases it and renders it as space-separated 8-bit bytes; hex emits its real UTF-8 bytes; signal renders those bytes' bits as a block-character waveform (▂▃▅▆). 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.
Computerized Images
At the three computer-side notches every project image — and the GitHub and LinkedIn icons — is swapped for a rendering of itself that matches the text encoding at that notch. A small build-time pipeline (scripts/computerize.mjs, using sharp only to decode and re-encode) writes three variants per image as static files. Together they trace a picture down its own stack, from stored file to raw samples to signal on a wire — each a real thing computers do, drawn in exaggerated form and described notch by notch in the showcase above.
Pipeline:
The variants are generated at build time, never in the browser. scripts/computerize.mjs collects the image paths from every project's MDX frontmatter plus the two brand icons, and for each one sharp decodes it to a small raw pixel buffer (longest edge 400px). Three pure functions in scripts/computerize-lib.mjs turn that buffer into the renderings above — sharp re-encodes hex and binary as PNG, signal is emitted as an SVG string. Output files are named by a hash of the source bytes and written to public/_computerized/, so an unchanged image is skipped on the next run and orphaned variants are pruned. The run also writes lib/computerized-manifest.json, a map from each original src to its three variant paths.
At runtime computerizedSrc(src, notch) reads that manifest: notches 0–2 return the matching variant, every other notch returns the original untouched. ProjectsGrid, ProjectHero, and Nav each resolve their image through it against the same HumanizerContext notch that drives the text, so image and prose change together. Because a grid thumbnail and its detail-page hero resolve to the exact same file at a given notch, the existing thumbnail→hero FLIP animation stays one continuous image across the navigation. The variants ship as static files committed to the repo — CI regenerates them on every build and commits any changes alongside the projects screenshot, so adding a project image needs no extra step.
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
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