Skip to main content
ShirePath Solutions

Note 07, build log

Eight Days After Launch, I Redrew the Whole Site as a Map

I shipped a redesign, then replaced it a week later with a site drawn like a storybook map of my work. How the prototypes went, how the map is generated, and how it reaches visitors without JavaScript.

On September 23 I shipped a redesign of this site called Hearth & Lantern. Warm colors, a lamp theme for night, a day theme for daylight, and a toggle in the header to switch between them. It was finished and it worked. Eight days later I started over.

Why redo something that just shipped

Hearth & Lantern looked like a nice website, not like the work of someone who builds things carefully, and that is the one thing this site has to say. A client or a hiring manager sees the site before anything else I have made, so it should be a portfolio piece itself, not a frame around one.

I wrote down what I wanted before touching code. Craft first: a professional builder who leads the job and brings in a crew of specialists when a craft calls for one. The Shire as the setting, since that is where the name comes from. One theme, no light and dark toggle. And keep everything underneath: the Lambda deploy, the MDX notes, and the SES contact form.

Prototypes, side by side

Each direction got its own branch in its own git worktree, so I could compare real pages instead of mockups. Each built only the home page and one case study: enough to judge, cheap enough to throw away.

  • Blueprint to Built used React Three Fiber: you scroll through a wireframe hillside on drafting blue, and it gets painted into daylight as you go.
  • The Plotter was generative SVG: a land survey that a pen plotter draws while you watch, seeded from the date, so it is different land every day.
  • The Surveyed Path took the Plotter plainer, with copper ink and simpler navigation.
  • Two more Plotter variants used the brand colors. Brand Sheet drew everything in green and tan on cream. Tinted Survey plotted in pen, then washed watercolor colors for meadow, arable, woodland, and water in under the lines, like a hand-tinted estate map.

Tinted Survey was the one, but contour lines read as a survey, not a story. I asked for the elevation map to become a medieval story map in the Tolkien manner: hills drawn as little humps, woods as clumps of trees, a river under a bridge, and a road between named places. That became the Story Map. The rule for it is still restraint: no parchment, no runes, no fake old spelling, and only one monster, a wyrm I let sleep past the edge of the map, where the things I have not built yet are.

Every prototype scored 94 to 98 for performance and 100 for accessibility on Lighthouse mobile. Speed did not pick the winner, so I could choose on feel.

How the map works

The map is a pure function, generateStoryMap(seed, places, layout) in src/lib/plot/storymap.ts, so the same seed draws the same map on the server and in the browser. The seed is today's date in Central time, hashed into a small seeded random number generator, so the country changes once a day. The "Draw a new country" button appends a counter:

const seed = redraws && redraws.day === day ? `${day}.${redraws.n}` : day;

Today's map is seed 2026-10-02, the first redraw is 2026-10-02.1, and the map's key prints the seed you are looking at.

The places are the portfolio. src/lib/map-places.ts reads them from src/data/portfolio.ts. Shipped work stands lived in, with a lit window and chimney smoke. Work on the bench is a bare timber frame with a ladder. Shelved work that was actually built is only its dashed footings, and an idea that never got built gets nothing, because nothing was ever there. The road runs past all of it up to the barn from the logo, on the highest hill. The site has always tried to be honest about what shipped and what did not, and now that honesty is geography.

It is plain SVG with no drawing library. A small plotter reveals each stroke with stroke-dashoffset at a constant pen speed, then watercolor washes painted on a low-resolution canvas bloom beneath the linework. With reduced motion on, the map arrives drawn and tinted.

The map is laid out for the crop actually on screen, with places and names steering around the panels laid over it. A phone 360 px or wider shows three to five places; I checked that on 120 seeds at three phone widths. A 320 px phone shows only two on about one seed in twelve. Generative work means testing many seeds, not one screenshot.

The empty frame

Because the map is laid out in the browser, the prototype showed an empty frame to anyone without JavaScript, and to everyone else until scripts ran.

The fix is a poster. At build, src/lib/plot/poster.ts draws the build day's map at a desktop crop and a phone crop as two standalone SVG files. The page shows the poster first, then swaps in today's live map once it is laid out. Without JavaScript, the poster is the map.

Getting the URL right took a few tries. One version put the date in the file name and drew any day but the build day on its first request, so a page cached from an older build could still get its map. On Lambda, that on-demand render tries to write Next's cache to a read-only filesystem. The next drew every poster per request and marked it immutable, which meant any made-up date drew a map and kept it in memory, and two deploys on one day reused a URL that promised never to change.

Now both posters are prerendered at build under hashed names, /map/<wide|narrow>-<date>-<hash>.svg, where the hash is of the drawing itself. Any other name is a 404 that draws nothing. They go out with:

Cache-Control: public, max-age=31536000, immutable

and a CloudFront /map/* behavior holds them for a year. Old names stay at the edge after a deploy, so a page cached from the previous build still finds its own poster. The build day is fixed once in next.config.ts as MAP_SEED, so every page and both posters agree on which day they drew.

Keeping it out of the first paint

The first full build of the home page scored 81 for mobile performance, because generating the map is the heaviest script on the page. The live map now waits for an idle moment after load, the poster is fetched at low priority with its names set in Georgia, so the map's own lettering face only downloads when the live map draws, and the home page details one system drawing instead of two. That took it to 96.

Sheets and drawings

Every other route is a numbered drawing sheet with a title block in the corner, and the drawing index in the header is the navigation. Each shipped project gets its own sheet, numbered 02.1, 02.2, and on, with a system drawing: its parts, what moves between them, and the parcels that run on one platform. The prototype drew those from portfolio copy. Now I read each project's repo: deploy config, infrastructure, schema, auth, email, scheduled jobs, and the one flow that matters most. Only what is there gets drawn. Each drawing lists the files it was read from in basis, and the build fails if a drawing links to a part that does not exist or a shipped project has none.

Reading the code corrected the record. One platform's specification listed Stripe, and there is no Stripe in its code. One idea's page listed an API integration among its results, and there was no code yet, so it is now listed as an idea. That is the part of this redesign I would keep even if I threw the look away again.

What it cost

Redesigning a week after a launch was a choice, and not a free one. A week of Hearth & Lantern work went in the bin, and /studio and /industries now answer with permanent redirects that search engines have to relearn. A site that draws itself is also more code to own: a generator, a plotter, a tint, a poster route, and a CloudFront behavior, all for one picture.

I think it was worth it, because the picture is the point. It shows the work, tells the truth about which work is finished, and is built the way I build everything else: pieces I understand, checked against the real thing.

Relentlessly curious. Unreasonably willing to build.

  1. Off Vercel, Onto AWS: What a Small Next.js Site Actually Costs to Run

    I moved this site off Vercel onto AWS: Next.js on Lambda behind CloudFront, defined in Terraform. Here is how it is wired and what it really costs to run.

    5 min read

  2. One API, Two Apps: Building the Biblical Battle Plans iOS App

    Version two of Biblical Battle Plans moved the game rules behind one tested API, and that is what made a native SwiftUI app possible. Here is what it bought me, what was hard, and where the app stands.

    7 min read

© 2026 ShirePath Solutions LLC. Built by Conner Contreras and crew in Columbia, Tennessee.