Skip to content
Shipcraft

Work — case study

Animation, not video

Passwerk Club — a members' driving club on the world's great roads.

A guided driving club wanted a site that felt like the week itself. Seven days from wireframe to live, and one line of client direction rewrote the middle of it. Everything below actually happened, including the finished work it deleted.

7things shipped
24 hourslongest gap between them
1 weekwireframe to live

What shipped, and when

Seven deliveries, seven days

One a day, and one task in progress at a time.

  1. Day 1: Wireframes: every section, and what each mechanism does
  2. Day 2: The layout built on the client's own content
  3. Day 3: First pass at the scroll sequence — animation, not video
  4. Day 4: Animation and layout finished
  5. Day 5: Fixes and tweaks, down to 375 px
  6. Day 6: Out of the design tool: the generated runtime read, and refused
  7. Day 7: Rewritten on Next.js, deployed, domain configured

The instruction that set the build

The centre of this site is a full-screen sequence: five chapters of a driving week, one dissolving into the next as you scroll. Your car arrives at the hotel. A pass at dawn. A hairpin climb. A puncture and the recovery vehicle. A lap of a circuit.

We had it running on video. Five clips, cut and colour-matched, scrubbed by scroll position.

The client's direction came back in one line: animation, not video, for now.

That is a small sentence with a real bill attached, and it was right. Video on a landing page is weight every visitor pays for, it cannot follow a theme, and it cannot be retimed without going back to whoever shot it. So the sequence was rebuilt out of motion the browser produces itself: the section pins, the chapters crossfade against scroll, each frame drifts while it holds, letterbox bars close in, and a counter and a progress rail tell you where in the week you are.

The sequence carries no video at all now, and it reads the same.

05 / 0581.5%
The sequence as it ships: five chapters, the fifth arriving over the fourth, position read straight off the scroll.

Day 1 — Wireframes before pictures

Nothing the client saw on the first day had a photograph, a typeface or a colour in it.

The first delivery was the structure: every section of the page, in order, and what each one does. The hero. The pinned sequence and how many chapters it holds. The route panels. The grid of what the week includes. The booking form and what it collects. Boxes, labels, and the behaviour written next to the thing that behaves.

That included the sequence itself, because it is the part that is genuinely hard to imagine from a static image: what pins, what crossfades against what, where the chapter counter and the progress rail sit, when the letterbox bars close, and what happens on a phone where all of it has to survive a 375 px column.

This ordering is not politeness. A mock-up invites a conversation about taste, and taste is the cheapest thing in the room to change. A wireframe forces the conversation that actually costs money: is this the right sequence, does this section earn its place, and does this mechanism do what you thought it did.

You cannot approve a mechanism you have only seen decorated.

heropinned sequenceroute panelsincluded gridbooking form
Day one. No words, no art, and every mechanism named.

Day 2 — Their words, our structure

With the structure agreed, the client sent the content, and the layout was built on it the same day.

We do not write copy for clients, on this project or any other. The people who run these weeks know what a service car can actually do for you at the side of a road in Romania, and we do not. Our job was to make the structure hold their words, and to tell them where the structure was going to fight them — a heading with room for four words, a card that reads badly under twelve.

That boundary is worth being explicit about, because it is where a lot of agency work quietly goes wrong. A studio that fills in the client's words is a studio inventing the business. Route names, what is and isn't included, what the guide car does — all of it is theirs, and all of it stayed exactly as they wrote it.

The one thing that was ours to solve was consistency. Five chapters, three route panels and a hero, and a driving club is not credible if the car changes between them. So a single reference car was locked and every frame across the site was regenerated against it: same car in the hotel forecourt, on the pass, on the climb, at the roadside, on the circuit. Getting there took fourteen rounds of correcting what came back — bystanders removed from the arrival so the car moves under its own power, a chase car recoloured, a shot that reversed made to drive forward, a lap that had wandered into a desert put back into the forest it belongs in, a van replaced with a flatbed transporter.

The six illustrations in the grid came back as photographs on the first pass — six competing pictures fighting the type. They were redrawn as flat two-tone screenprints in the site's own two colours, so the grid reads as one object instead of six.

One reference car, locked, and every frame on the site regenerated against it.

Days 3 and 4 — Built, not filmed

The sequence went from five clips to one scroll handler, in a first pass and then a finished one.

The section pins for the length of the whole week. Each chapter's opacity is driven directly off scroll position, so two are always crossfading rather than cutting. Each frame drifts slowly while it holds, which is most of what made the video feel cinematic in the first place. Letterbox bars grow to their full height as the sequence takes over the viewport, then retract. A tick per chapter, a counter and a progress rail all read from the same number.

All of that is one function of arithmetic on a throttled scroll listener. It ships as a few kilobytes rather than a few megabytes, it follows the theme because it is made of the same tokens as everything else, and it can be retimed by changing a number rather than by booking a shoot.

A film is something you watch. This had to be something you drive.

Day 5 — Fixes, including one that was ours

Day five was the unglamorous one: tweaks, spacing, and the page walked end to end in a browser rather than read in an editor.

Hero, chapter one, chapter four. The crossfade, the chapter counter, the progress rail, the active tick. Chips select. Submit changes its label and does not navigate. No console errors, at desktop width and at 375 px.

One suspicion turned out to be our own mistake. The navigation looked broken at 375 px in a screenshot, and wasn't — we had read a 2× image as though it were 1×. Measuring the positions settled it, and the one real change we did make was a narrow-width rule for below about 330 px, where the wordmark and the links genuinely would collide.

Two caveats went on the record rather than into a backlog nobody reads: the imagery was still hot-linked to the tool that generated it and had to be pulled local before launch, and the booking form has nothing behind it yet.

Day 6 — The runtime we did not ship

Getting the design out of the design tool produced two files: the page, and a second one that turned out to be a runtime — an interpreter for the template language the tool writes in, with its own loops, conditionals, bindings and hover states, built on React.

We could have vendored it. The page would have worked that afternoon.

Instead we read what it actually did and ported the meaning. Every loop expanded into real markup. Every inline style lifted into a class. Every scripted hover became a real hover rule, and the accent colour it passed around became a custom property. What came out was three plain files — markup, stylesheet, one script — with no dependencies and no build step.

A marketing page does not need an interpreter. Shipping one because it arrived in the box is how a static site quietly becomes an application.

The interpreter that came in the box, and the three files that replaced it.

Day 7 — Rewritten, deployed, live

The last day was the move onto Next.js, and the brief for it was unusually strict: change nothing a visitor can see.

Fonts came off the font CDN and into the framework's own loader, which removed the last render-blocking third-party request. The markup split into components, every data attribute kept intact because the scroll code selects on them. The repeated content became typed arrays instead of hand-repeated markup. The choreography moved into one client component and one effect, its arithmetic carried across untouched.

Three things had to change for React, and only three. The page is queried inside the effect rather than at module scope. The load listener was dropped, because load has usually already fired by the time a page hydrates. And a cleanup was added that cancels the pending frame, removes the listeners and disconnects both observers — without it, React's development double-invoke leaves a second set of observers attached, which is a bug that exists only in development and only if you never look.

Then it was proved rather than assumed. At 82% through the sequence the page reported chapter 05 of 05, the rail at 81.5%, two layers crossfading at 0.78 and 1.0, the letterbox bars at their full 46 px and tick five active. Twelve reveal elements still staggered across 300 ms. Then the real production server, not the development one, because that is what actually runs.

Deployed, domain configured, live at the end of the seventh day.

The week after — Presenting the route pages

Each route on the landing page needed a page of its own, and this time the content arrived finished. The client had the itineraries written: six days, where you sleep, where you eat, what you would never have found on your own. Our job was not to research a driving week. It was to present one.

So the structure came first again — what a route page is, as a shape, before anything was poured into it. A week laid out day by day, the stays, the eating, the things worth booking ahead, and a map.

Their content had things in it that a layout can very easily bury. A limitation stated plainly, because there is no five-star chain hotel in that corridor and they say so. Distances given as planning figures rather than false precision. A season stated conservatively, because the weather above two thousand metres does not care what the map says. A design that quietly loses those makes honest copy look like marketing, so each of them got a place in the structure rather than a footnote at the bottom.

Then the map, which is the one part of the page we made rather than laid out. Every waypoint carries the real latitude and longitude from their itinerary and is projected into the drawing, so the line on the page is the shape of the route. No tile provider, no API key, no third-party script, nothing fetched at runtime. The trade is stated rather than hidden: no pan, no zoom, no country outline. It is a roadbook diagram with a scale bar, not an atlas pretending otherwise.

By the third route the components had been rewritten to take a route as a prop, so each page is four lines of its own. The first route was converted and re-verified before either new one was written, which is the only reason that refactor can be trusted.

2 145 m50 km
Real coordinates, projected. The line is the shape of the road, not a decoration of it.

The words arrived finished. The pictures did not.

Generated imagery is fine as mood on a landing page. It is not fine on a page that plans a specific week in a specific place — and unlike the words, the pictures were ours to find.

Hotel marketing photography and travel-blog images are not ours to republish. So everything came from Wikimedia Commons under Creative Commons or public-domain terms: 24 files, each fetched with its author, licence and source page, downloaded rather than hot-linked, then resized and re-encoded — 15 MB of originals down to 5.9 MB. Every gallery carries a credit strip naming each photographer and licence, both linked. That is what the licences require, and it is the honest way to show whose pictures these are.

Then we looked at them. All 24, rendered as a single contact sheet, in one pass. Four things came out of that one look that no filename would ever have shown: a portrait where a citadel was expected, a winter ski slope in a week sold as a summer one, a 19th-century engraving standing in for a photograph, and two alt texts describing more than their pictures actually contained.

One of those mattered for more than accuracy. The file was a portrait of a person in traditional dress — the wrong subject, and not someone to put on a commercial page without their say-so.

Every outbound link in their itinerary was fetched and its title checked before it went into the data. That killed several plausible-looking domains that no longer resolve, and it is thirty seconds of work per link against a page full of dead ends.

One sheet, one look, four errors. None of them visible in a filename.

An overrun, named as one

Not everything here went to plan, and the failure was ours.

Asked for a narrow change, we came back with eight fully drawn directions for something adjacent that nobody had requested. The underlying research was worth keeping. The eight directions were not asked for, and they went into the log as an overrun the same day rather than being presented as a gift.

A subscription is a promise about attention: one task in progress, and it is the one that was asked for. Spending a day on something better is still spending a day on something else.

Work nobody asked for is not a bonus. It is a cost somebody else pays.

In progress — Taking a booking

The form still does not take a booking. It never has, and that was written down on day five rather than quietly implied otherwise.

That is the current task. It is one task, it is in the queue like everything else, and it will ship the way the rest of this did: the mechanism agreed first — what is collected, what is confirmed, what happens when a week is full — then built, then stepped through in a browser before anybody is told it works.

We would rather write that sentence than show you a form that posts into nothing.

What the week bought

Nobody hires us to port a scroll handler. Here is what came out of it:

  • Seven deliveries in seven days, one a day, one task in progress at a time — wireframe on Monday, live on its own domain by the end of the week.
  • The first thing the client approved was behaviour, not decoration — every mechanism named in a wireframe before a picture existed.
  • Every word on the site is the client's. We do not write your copy, and we said so before it could become an assumption.
  • One line of client direction deleted five finished video clips and was acted on the same day, because it was right.
  • A generated runtime was read and refused. The site ships plain files and no interpreter.
  • The framework rewrite changed nothing a visitor can see, and was proved against the real production server rather than the development one.
  • The route content arrived finished and was presented, not rewritten — structure, map and galleries around the client's own itineraries.
  • Twenty-four photographs were licensed, credited and pulled local, and four wrong ones were caught by looking at them.
  • An overrun was named as an overrun in the log, not presented as value.

The deliverable is a site that deploys. The value is that everything on it is either the client's own words or a mechanism they signed off before it was built.

Sign off the mechanism, not the mock-up.

See pricingBook a fit call