← All notes

Six machines, one file: the hero that is a mosaic

July 30, 20267 min read

The second site I've put live with a video hero is up:

abp-ingenieria.com — ABP Ingeniería & Mantenimiento, machinery rental and a machining shop in Nuevo Laredo. Cranes, articulated boom lifts, scissor lifts, forklifts.

The first one was a clinic with no footage at all, where the whole exercise was generating eight seconds out of a still they already owned. This one is the mirror image. The client had material — 74 photographs of his own machines on real jobs, taken with his own phone. Which creates a question that sounds easy and isn't: which one is the hero?

None of them. Every single shot is a crane doing one specific thing on one specific day, and picking one means the other 73 jobs stop existing. A rental company doesn't sell a job. It sells a fleet.

The hero is a mosaic, not a clip

So the hero is six tiles in a grid, each moving on its own. Every six seconds or so the camera dives into one of them until it fills the frame, holds while you register what the machine is doing, then pulls back to the grid and dives into a different one. Twenty-four seconds, three starring machines, then it starts over.

The shipped desktop hero. Six tiles, three dives, one file — and no seam at the loop point.

Four things about it are load-bearing, and each one is a mistake I'd otherwise have made:

The cycle is twenty-four seconds because every tile's period has to divide it exactly. The starring tiles run on twelve, the fillers on eight; both go into twenty-four cleanly. Give one tile a period that doesn't divide the cycle and all six tiles blink at once when the file loops — a synchronized flinch at the exact moment the whole construction is supposed to be invisible.

Desktop and phone are different mosaics, not one file cropped. 3×2 and 2×3, with different machines in the starring roles.

Side by side: the desktop hero as a three-by-two grid of six landscape clips of cranes, and the phone hero as a two-by-three grid of six vertical clips of different machines
Left, desktop: 3×2 from landscape takes. Right, phone: 2×3 from vertical takes, different machines starring.

A landscape shot of a crane raising a forty-metre tent frame cannot fill a 9:16 screen without becoming an unreadable strip of sky. A vertical hero is built from vertical takes, and the photos that make the best protagonist on a laptop are not the ones that make the best protagonist on a phone. Two mosaics, made separately, chosen separately.

It's one pre-rendered file, not six videos in the DOM. Six simultaneous decodes on a phone isn't a hero, it's a battery complaint. The grid is a rendering decision, not a runtime one.

The zoom comes off a large canvas with the tiles already near their final resolution. Render the mosaic at output size and then scale up for the dive, and the protagonist — the entire payoff of the move — is the blurriest thing on the page at the moment you're asking someone to look at it.

The model thought swaying meant falling

Two generated clips got thrown away, and both failures were the same failure.

The first: a crane lifting the arch of a tent frame. I asked for the suspended load to sway gently, because a load hanging perfectly rigid looks like a photograph. The model obliged by stretching the sling frame by frame until the beam hung well below the hook. It reads as a snapped rigging line. Which is to say it reads as footage of an accident, on the website of a company that rents cranes.

The second: a crane raising a sign onto its post. The model moved the whole post, when the post is anchored in concrete and only the sign goes up.

An image-to-video model has no physics. It has a statistical sense that things in a picture like this one tend to move like this, and rigging is precisely where that instinct is wrong in the most expensive available direction. What actually works:

  • Declare the rigging rigid, explicitly and redundantly. Same length, same tension, same hook-to-load distance; nothing lowers, nothing slackens, nothing stretches. Saying it once is not enough — say it four ways.
  • Name the fixed structures separately. "The post is anchored and does not move." The model does not distinguish load from infrastructure on its own; if it's in frame, it's a candidate for animation.
  • Give the movement to the camera, not to the scene. A slow push-in can never be physically false, and it keeps the shot from feeling dead. Arguing the model into animating nothing just makes it invent movement somewhere you weren't watching.
  • Prefer source photos with nothing suspended. If nothing hangs, nothing can appear to fall.

On models: veo-3.1-lite at $0.05/second is plenty for ambient movement — wind, dust, a drifting camera. When physical coherence is the thing breaking, veo-3.1-fast at $0.10/second actually holds it, and paying double on the two shots that need it is cheaper than a dozen retries on the cheap one. Read the prices from GET /api/v1/videos/models every time: the general model catalog reports pricing: 0 for video models, and that zero means nothing at all.

Making a hero survive at 300 pixels

Which brings me to why this site changed too.

The Projects board has been a grid of screenshots since May. For thirteen of those cards a screenshot is honest. For these two it's a lie by omission: the single thing that makes them different from every other site in the grid is the thing a still image structurally cannot show.

So those two cards play now. But a shipped hero is not a thumbnail, and treating it as one would have cost visitors seven megabytes to look at a page of small pictures:

shipped hero gallery thumbnail
ABP Ingeniería 3.48 MB · 1440×810 375 KB · 576×324
Centro Alternativo OM 3.54 MB · 1920×1080 175 KB · 576×324

Three rules make the rest of it free:

preload="none" and nothing else. The source is declared in the markup and untouched. An IntersectionObserver calls play() two hundred pixels before the card reaches the viewport and pause() when it leaves, so a card you never scroll to costs zero bytes and one you scroll past costs zero decode.

The poster is frame zero of the same loop — eleven to seventeen kilobytes. The card is fully composed before a single video byte is requested, and the handoff from poster to playback has no visible seam, because it's the same frame.

The re-encodes live in this site's own media vault, not the client's. A client who swaps their hero next month doesn't silently change what my portfolio claims I built.

The pause control on each card reads the element's own play and pause events rather than what the code asked for — same rule as the clinic's hero, for the same reason: browsers suspend playback on their own, and a pause icon sitting over a frozen frame is an interface lying to the person trying to use it. Anyone who's asked for reduced motion gets both cards paused, with the button right there.

Where it landed

Two sites, two moving heroes, two opposite problems. The clinic had no footage and one still to build from. The crane company had seventy-four photographs and no single one that could carry a whole screen. One of those gets solved by generating a shot; the other gets solved by admitting there isn't a hero shot and building a hero out of six.

See it: abp-ingenieria.com — and both cards are moving over on the Projects board.