Chronological log of decisions and their tradeoffs.
Rebuilt the site on Next.js 14 App Router.
For: Server components, static export, and built-in image optimisation are well suited to a portfolio. File-based routing keeps things simple.
Against: App Router conventions add mental overhead compared to a basic static site generator. Some third-party packages still have incomplete App Router support — ran into this later.
The snowfall animation lives at its own route rather than embedded in the portfolio.
For: Full-screen immersive experience. Can be shared or linked to as a standalone piece.
Against: Requires a back-link so users aren't stranded. The dark background couldn't be set globally — it bled into other pages on navigation because the framework doesn't unload page-level styles on client-side transitions. Required a more deliberate scoping approach.
Added page view tracking via a deferred script so it loads after the page is interactive.
For: Understand which content gets traffic without slowing down the page.
Against: Blocked by most ad-blockers, so data is always incomplete. Adds a third-party dependency to every page load.
Built a login-protected section using Google SSO. Removed in a later pass.
For: Lets you protect private content without building custom auth.
Against: Hit a production build error that required a patch. Auth infrastructure is significant overhead for a mostly-public portfolio. The protected content was eventually removed, making auth pointless.
Conclusion: Auth is not worth the complexity here. If private sections are needed in future, a simple password-protected page or a separate deployment is a better fit.
Writings are a typed list of titles, dates, and links — not a full CMS or markdown pipeline.
For: Zero infrastructure. External articles and internal posts coexist in the same list. Trivial to add a new entry.
Against: Each internal post still needs a hand-written page file. No support for drafts or scheduled publishing. Doesn't scale gracefully past roughly ten posts.
Individual blog posts are written directly as page files. No markdown, no headless CMS.
For: Full control over layout and styling per post. No build pipeline complexity.
Against: Each new post requires creating a file and writing code rather than prose. Not a good authoring experience as volume grows.
Why not a CMS: A CMS was evaluated and fully reverted due to unstable APIs at the time. For infrequent, high-effort posts, a code file is acceptable for now.
All manually managed content — writings, paintings, software, recent items — lives in one typed file.
For: One place to update anything. The type system enforces that every piece of content has the required fields.
Against: The file grows with content volume. Adding or removing a section requires code changes — this isn't something a non-technical collaborator could do.
Comic images are read from disk at build time, sorted by file modification time.
For: Adding new comics requires no code changes at all — just drop a file into a folder. Order is controlled by when you drop the file, regardless of filename.
Against: Only works at build time on the server — can't be done in the browser. Modification time resets when files are copied or a repo is re-cloned, which can scramble ordering unexpectedly.
The portfolio page is server-rendered. Only the lightbox, waitlist form, and contact form are client-side.
For: No JavaScript sent to the browser for the page itself — fast load, good for search engines. Server rendering can read from the filesystem at build time.
Against: Any interactivity has to be pushed into a separate isolated component. Easy to accidentally break by adding browser-only code to the wrong file.
One reusable grid handles three different interaction patterns: standalone images, a shared navigable gallery, and multi-panel comics where clicking a panel opens that sequence.
For: Lightbox behaviour, keyboard navigation, watermarking, and grid layout are written once and shared across all image sections.
Against: The multi-panel mode relies on each grid item carrying extra metadata — the contract isn't obvious from looking at the component. Multiple modes mean multiple code paths to maintain.
The recently feed is populated explicitly rather than auto-aggregated from all content.
For: Full editorial control. An ongoing practice series with many images can appear as a single entry rather than flooding the feed.
Against: Easy to forget to add an entry when publishing. Some content is registered in two places — once in its own section, once in the recent feed.
Why not auto-aggregation: Ongoing series don't belong in a feed as individual items. The noise outweighed the convenience.
One font loaded, with a system fallback. A second decorative font was removed.
For: One fewer network request. Consistent visual rhythm across the page.
Against: Less typographic contrast between headings and body text.
A text watermark is rendered on all image thumbnails and the lightbox using a blend mode that inverts against the background.
For: Automatically visible on both white and dark backgrounds without choosing a specific colour.
Against: Can look unusual on mid-tone or colourful backgrounds. Can be removed with basic image editing — not a true technical barrier.
Why not adversarial image poisoning: Tools like Glaze apply imperceptible perturbations that confuse AI style models at the pixel level. This must be done offline before images are uploaded. The web watermark is a supplementary layer, not a replacement. Glaze is recommended for high-value originals.
Known AI training crawlers are explicitly blocked from image directories. Images are also flagged to prevent indexing by search engines.
For: Major AI labs publicly commit to honouring these declarations. It is the most practical protection available without a server or CDN.
Against: Declarations are not enforceable. Rogue scrapers ignore them. The list of known crawlers requires ongoing maintenance as new ones appear.