Tudinh Duong

Colophon

This site is Astro on Netlify: four dependencies, no client framework, every page served as static files. It uses system fonts only, in two colours — a warm paper and an ink — with one accent for links and focus.

The images come from external sources: photographs from Air boards, the collecting archive from Are.na channels. A scheduled job checks those sources each morning and rebuilds only when something has changed.

The method

Designed and built in the browser, in sessions with Claude (Anthropic's Opus and Fable models). Nothing is mocked up elsewhere: each direction is prototyped in code, used for a while, then kept or reversed. The commit history is the record — each message records the decision behind it.

The collaboration

The model did the high-volume work — reading, searching, drafting, recovering old builds — and I made the calls and fixed what it got wrong. Most of it was content: getting the words and images into the site from documents and shared libraries, worked as a back-and-forth, not a single prompt.

The writing mostly started in other documents. The bio, the credentials and the client and agency lists came out of a press pack; the talk summaries came from the presentations.

The photographs came from Air, Made by ON's shared asset store of 531 boards. Working over the Air API and an MCP server, the model searched the whole workspace for frames with me in them; we cut a few hundred candidates down to the set here — stages, audiences and the aftermath, no posed portraits — and it filed those into their own boards and tagged each with its photographer, so every image carries a credit.

The Are.na channels behind the archive got the same treatment: the model read every public channel, looked at samples of what each one actually held, and proposed the renames and merges. I signed the list off and it ran the reshuffle through Are.na's API in one pass — the kind of block-by-block work that never gets done by hand. It ran more than once: first to flatten the names, then to fold the near-duplicate channels together and put the whole public collection on the site, grouped into families, in place of the handful it used to show.

This page was put together the same way. The model read back through the roughly two hundred commits of the rebuild, found the decisions worth a plate, and worked out which commit each state lived at, then checked those builds out and photographed them (the rig above). I decided which to keep and how to frame them; it did the reading. That was the split throughout: the model covers the volume, the decisions stay mine.

Directions

Design decisions from the rebuild, most of it over about a week in July 2026, grouped by area. Each state below is a real build that shipped, re-run and photographed; the moving images are screen recordings of those builds.

Homepage & composition

Homepage photo wall

A row of portraits, then a rotating band sourced from Air, now a fixed wall of seven credited prints.

The band started as a full-bleed row between the bio and the recognition list — press portraits and talk photos in a folder, pulled in by a glob at build, with a credit under each. The source then moved to Air: a build-time loader reads the tudinh.com boards, so adding a photo means dropping it on a board, and each frame’s photographer comes from the board metadata. To fill the width, the band rotated through the images.

The rotation was dropped for a fixed wall: seven images chosen by name, each at its own aspect ratio, each credited below, arranged around one larger photo of the speaker. A rotating strip makes the photos feel interchangeable; a fixed, credited wall treats each as a specific photographer’s work. Air remains the source, served through Astro’s own image pipeline, with the checked-in files as a fallback so a build without credentials still works.

The homepage on 5 July 2026, a full-bleed band of speaker portraits running between the bio and recognition sections.
the portrait band
hanging from Air, turning
The current homepage, seven photographs hung as a wall at their own ratios, each photographer named beneath their frame.
the wall

The pinned name

The pinned name went from a paper band to a floating halo to a plain paper chip, and stopped shifting between pages.

Once the hero scrolls off, a compact copy of the name pins to the top, above the current section label. The first version drew a solid paper band across the width, but that band cut across the grid and photo band behind it, which are meant to run unbroken; its lower edge also dissolved the content scrolling under it with a gradient. The name was then floated over the content, kept legible over images with a paper-coloured text-shadow. That shadow is a soft glow — and, like the gradient fade before it, out of place on a site with otherwise hard edges.

The final version is a paper chip: the name sits on a background of the page’s own paper colour — invisible over paper, opaque only where it crosses an image. No glow, and it reuses the same technique as the filter pills and the collecting name. One bug remained: on the homepage the name was baseline-aligned to the taller serif heading, which dropped it about 6px below its position on other pages, so it jumped when navigating in and out. Top-aligning the masthead fixed it.

The homepage on 6 July 2026 scrolled into the dispatch section, the name pinned at the top of the page on its paper band.
pinned, on its band
The homepage later that day, the name floating free over the grid with a soft halo still around it.
floating, with a halo
the paper chip

The section nav

The masthead carried a section nav; it was dropped for the labels that already pin in the reading rail.

The masthead first carried a nav: a row of section links — Collecting, Dispatch, Bio, Talks, Contact — that lit the current one as you scrolled, and let you jump between them.

But the reading layout already names every section, in the label beside it in the left rail, and that label pins under the masthead as you read past it — so the page was announcing the current section twice. The nav was dropped and the pinned labels kept. One label travels down each section and marks where you are, so orientation comes from the structure already on the page rather than a separate list to keep in step with it. It also took a strip of chrome off the top of every page.

The homepage on 5 July 2026, the masthead carrying a nav — Collecting, Dispatch, Bio, Talks, Contact — with the current section, Collecting, lit as you scroll.
a row of section links
The current homepage scrolled into the Bio section, with no nav — the section's own label pins in the left rail and names where you are, below the floating name.
the pinned label names it

Quotes and talk decks

Quotes were made to scale with their images; then each featured talk got its own deck of slides.

This covers two changes. First, quotes: a pulled quote was showing its source annotation instead of the quoted text, so the priority was reversed — the quote’s own text shows first, and the source link falls back to the URL in the clip’s description when the quote has none. Quotes also scale with the size of their image.

Second, the talks. A featured talk used to be a single still and a title; each now has its own deck — one slide per point, arranged around the main image. The arrangement is computed from the main image’s aspect ratio, so a 16:9 title card and a near-square engraving get different layouts. Each slide animates in slightly offset as it scrolls into view, with a different delay each time, and stays static for reduced-motion or no-JS. In progress: four talks have decks so far; the rest are still single images.

The homepage on 10 July 2026, featured talks standing as titles and stills, no decks yet.
the talks, before decks
The current homepage, a featured talk with its deck of slides scattered around the anchor plate.
a talk opens its deck

The dispatch feature

The homepage's dispatch feature gained a lead image — a read-on doorway into the full edition — over a numbered index.

On the homepage the newest dispatch runs as a feature: the edition number, its title, and the issue’s own opening lines — the writing itself, not a summary pulled from elsewhere. At first the feature was all text, closing on a ‘Continue reading’ link, with the previous editions listed by date.

It then gained a lead image drawn from the edition, at first with a plain ‘Continue reading’ link beneath it. The image soon took that job over: on a pointer it dims and a pill follows the cursor to name where it goes, so the whole feature becomes one doorway into the full piece rather than a block of text ending in a link. The editions below became a numbered index — newest first, the latest few readable in place and the rest behind the subscribe.

The homepage on 6 July 2026, the dispatch feature as text — edition 19 'Hallmark', its opening lines, and a 'Continue reading' link — above a date-keyed list of previous editions.
text, then a link
The homepage later on 6 July 2026, the dispatch feature now carrying a lead image from the edition with a plain 'Continue reading' link beneath it — the image not yet the way in.
a lead image, read-on below
a read-on doorway

Page transitions

Navigating gained a morph — a shared cell or the feature title carries across, instead of a hard page load.

Moving between pages used to be a plain load: the new page replaced the old one outright. Same-origin navigations now use the browser’s native cross-document View Transitions — the page cross-fades, and where two pages share an element, that element morphs across instead of blinking out and back.

Two pairs share an element. The homepage’s collecting strip is the literal head of the /collecting grid, so clicking a cell lifts it into its place there while the rest of the grid fills in. The dispatch feature is the head of its edition, so its title carries over into the reading page. It runs on the browser’s own @view-transition, not a client router — a real navigation, with the URL and scripts behaving normally, and a browser without support just loads the page. The point is to tie the homepage to the pages it opens into, so they read as one surface rather than two separate loads.

a cell lifts into the grid
the title carries across

The archive

The archive viewer

A lightbox at first, rebuilt as a full-page view, now paging through the archive.

The first version was a lightbox: clicking a cell opened the block in a native dialog over a dimmed grid, with edge chevrons, arrow-key paging, and both neighbours preloaded. The interaction worked, but the chrome — the scrim, the chevron buttons, the icons — appeared nowhere else on the site and looked borrowed.

The rebuild kept the full-page takeover and dropped that chrome. The item sits on open paper with its provenance set below like a museum label; Close, Prev and Next are text, not icons. Paging animates as a page-turn — the current image slides off, the next slides in, and on touch it follows the finger. It reads as part of the site rather than a modal on top of it.

The collecting archive on 5 July 2026, an item floating in an are.na-style overlay above the dimmed grid.
the lightbox
The viewer later the same day, redesigned as a full plate page with the item resting on open paper.
a plate page, not a lightbox
the plate turns

Homepage grid links

The homepage grid became a link into the archive — first the thumbnails, then the whole sheet.

The homepage sample grid first had its own viewer — clicking a cell opened an eight-item dialog in place. That meant two viewers to maintain, and browsing stopped at the sample. Now each cell links directly to that item in /collecting (prefetched on hover), so there is one viewer and one URL scheme, and the first click opens the full archive rather than a preview. The homepage dialog, the end-of-sample handoff, and the rotation fallback were all removed once the links replaced them.

The grid was then made to work as a single link target: an “Explore Collecting” hint follows the cursor, and the per-cell hover-dim is limited to the archive so the homepage doesn’t imitate a behaviour it doesn’t have. At first only the thumbnails were links, so the gaps between them did nothing; now every cell is a full link, with another link behind the grid covering the gaps, so a click anywhere opens the collection.

That cursor hint needs a pointer. Phones have none, so on touch the Collecting section becomes a different thing: a full-bleed band of the newest images drifting sideways, with an “Open the collection” pill — a live, tappable way in rather than a hover doorway a finger can’t trigger. The hover-only script is gated to pointer devices, so it never attaches on touch. Still in progress: it isn’t yet decided how much more the homepage and archive should share.

The homepage on 5 July 2026, the collecting section a fixed grid of eight cells, each in its own box.
cells
The current homepage, the collecting cells opened into a single tall row that leads into the archive.
doorways
on touch, a moving band

Loading placeholders

Loading placeholders that match each picture's real shape and size.

While an image loads, its cell shows a placeholder in the palest ink. The first placeholders were plain rectangles filling the cell, so the layout shifted when the real image arrived at its own aspect ratio. They were changed to match the image’s shape — sized to the exact box it will occupy — so nothing moves on load. Then they were corrected for size: Are.na’s CDN doesn’t upscale, so a small original renders smaller than the cell, but the placeholder had been sized to the cell. It is now based on the original’s real pixel dimensions — the size the image will actually render at — rather than the maximum size the CDN advertises.

The collecting archive on 9 July 2026 mid-load, every cell a flat rectangular wash of pale ink.
flat washes
the wash takes the shape

The archive filters

The collecting filters went from a stacked nav in a fixed left column to paper-pill dropdowns in the corner.

The archive’s filters began as a stacked nav in a fixed left column: a Type list — Everything, Images, Videos, Text, Links — set vertically under the name, with a board dropdown above it. It worked, but it spent a whole column and restated the grid’s structure down the side.

The filters then moved to two dropdowns in the top-right corner, which freed the grid to run full-width. A live readout moved up with them, showing how many items match and the date of the one in view. They took the site’s paper-pill treatment last, the same chip the name and section labels wear: at rest each pill sits as a quiet label on the paper, filling in only when it is open.

The collecting archive on 6 July 2026, its filters a stacked Type list — Everything, Images, Videos, Text, Links — in a fixed left column beside the grid.
a fixed left column
The collecting archive later on 6 July 2026, its filters now two dropdowns in the top-right corner (All collections, All media) with the grid full-width beneath — before the pills took their paper rest-state treatment.
moved to the corner
The current collecting archive, its filters two paper-pill dropdowns in the top-right corner (All collections, All media), the grid running full-width beneath.
paper pills

Reorganising the archive

Twenty-two channels became thirteen — grouped, plain-named, the whole public set now on the site; a record of save-dates kept the stream steady.

The archive mirrors a set of Are.na channels that needed sorting out: twenty-two of them, named like References: Motion and Creative Spark: Release Notes, some overlapping, one mislabelled entirely. Are.na has no bulk operations. Moving a block means opening it, connecting it to the new channel, removing it from the old one — one block at a time — and each move resets the block’s saved date, which is the date the archive sorts by. Renaming a channel breaks its URL. Cleaning this up by hand meant hundreds of clicks and a shuffled stream, so it never got done.

The first step was to make moving cheap. Before anything moved, every block’s original date was written into a record in the repo, and the site now reads dates from that record instead of the API. The config switched from channel slugs to Are.na’s numeric ids, which don’t change on rename. After that, any block could move anywhere and any channel could take a new name without the site noticing.

The decisions came from reading the channels. An audit showed that one channel’s blocks already all existed in another — a deletion, not a merge — and that Release Notes actually held website references. Samples from each channel — thumbnails, titles, passages — showed what each collection actually was: Writtings, mostly photographed quotes and sayings, became Words. Every rename, merge and move went into one manifest — ten renames, two merges, four deletions, twelve moves — which ran as a single pass through Are.na’s MCP server, each operation checked as it went. One block refused: stuck half-processed on Are.na’s side, it rejects every new connection, so it stayed where it was.

By hand, that’s a job that gets started and abandoned. Run from the manifest it took a few minutes — and it ran again. A second pass folded the near-duplicate channels together: a brand-identity set and an editorial one, both really graphic design, and a tools channel beside an AI one. The AI channel’s handful of references to other people’s collections were lifted into a private board first, so the merge lost nothing.

The same pass opened the archive up. Where the site used to show a chosen handful of boards, it now shows the whole public collection — thirteen channels, grouped into families so a longer list still reads at a glance. Showing everything meant the build began brushing Are.na’s anonymous rate limit; rather than drop a board, the loader now waits the window out and asks again.

The record stays useful after the sort: it’s the same file the image captions land in next, and those captions are what the archive’s search will be built on.

The collecting board filter's channel list before the reorganisation — thirteen channels in monospace on paper, several carrying taxonomy prefixes (References: Motion, Creative Spark: Release Notes, References: Newsletter), each with its block count.
thirteen boards, prefixed names
The same list after the reorganisation — thirteen channels in monospace on paper, plain nouns bucketed under five family labels (Visual references, Graphics & design, Image & place, Words, Web & tools), each channel with its block count.
thirteen boards, grouped

Theme & build

Daily rebuild check

A daily job checks the content sources and rebuilds only when something has changed.

The content comes from external sources — photos on Air boards, blocks in Are.na channels — so the site could rebuild on a timer. But most days nothing has changed, and rebuilding anyway wastes build minutes and redeploys an identical site. Instead, each deploy publishes /build-state.json: a fingerprint per source recording the exact upstream state that build used. A scheduled GitHub Action runs each morning, compares that file against the live APIs, and triggers the Netlify build only when something differs. An unreachable source is treated as unchanged — skipped and retried the next day — so a temporary API failure can’t cause a rebuild against missing data. If the file is absent (the first deploy), it builds to create it. The current /build-state.json is shown below.

The site's build-state ledger, live JSON in monospace on paper — each Air board and Are.na channel with the freshness fingerprint this build consumed.
the ledger it checks

Dark mode

Dark mode follows the system by default, with a manual override in the footer.

The 2023 site had a manual light/dark toggle, then dropped it for a single fixed theme. The rebuild handles theming differently: there is no toggle by default — the theme follows the visitor’s system setting, with a light/dark control in the footer as an override. A chosen theme is saved and applied before the first paint, so the page doesn’t flash the wrong background. The remaining hardcoded colours were moved into tokens at the same time, so switching themes changes every colour with none left behind.

lights on
The same footer with the lights off, the paper turned to a warm near-black, the ink flipped to off-white.
lights off