DEV Community

Dhardingsea Developer
Dhardingsea Developer

Posted on

I hand-built four things my tooling already shipped. The command I never ran was `catalog`.

A build log about agent skills, HTML-to-MP4 video, and the specific cost of not reading the manual — with measured numbers."

The setup

The extension is Dota Companion — a Dota 2
stats popup that also prices your Steam inventory. I needed intro videos: a 30 and a 60 for
YouTube, shorts for TikTok.

The stack is HyperFrames (Apache 2.0) — HTML and
CSS in, MP4 out, no bundler. You write a composition as a plain HTML file, declare timing with
data-* attributes, register one paused GSAP timeline, and a headless Chrome + FFmpeg pipeline
renders it. It's genuinely good for agent work because the artifact is a file an agent can read
and edit, not a React tree it has to reason about.

I also keep a personal "skill" file for it — a markdown doc the agent loads that carries facts
about my environment, as opposed to facts about the framework. The framework has its own docs.
Mine holds things like "this install flag hangs in a non-TTY shell."

Round one was bad in an instructive way

First pass: screenshots on a gradient, an animated outline ring around whatever the narration
was naming, a caption bar, a slow zoom.

Two problems, both visible in the first render:

The ring sat on the text. At video scale a 3px border plus a glow lands on the glyphs of
the thing it's pointing at. The highlight was actively making the subject harder to read.

It looked like a slideshow because it was one — a flat rectangle pasted on a gradient,
with a Ken Burns zoom.

Round two: the fixes that actually worked

Focus became a hole, not a ring. Instead of drawing on the subject, dim everything
except it:

.spot {
  position: absolute;          /* sized to the measured target rect */
  border-radius: 10px;
  box-shadow: 0 0 0 4000px rgba(4, 7, 14, 0.58);
  outline: 1.5px solid rgba(126, 198, 232, 0.85);
  outline-offset: 3px;
}
Enter fullscreen mode Exit fullscreen mode

The scrim is the shadow this box casts outward, clipped by the device frame. The lit region is
negative space, so it is geometrically incapable of covering the text it points at. This is
the Apple/Samsung how-to grammar — the frame goes quiet, the subject stays lit.

Coordinates got measured, not eyeballed. The screenshot script records
getBoundingClientRect() for every control the video will point at, normalised to the image:

rec.targets[key] = {
  cx: (box.x + box.w / 2) / vp.width,
  cy: (box.y + box.h / 2) / vp.height,
  w: box.w / vp.width,
  h: box.h / vp.height,
};
Enter fullscreen mode Exit fullscreen mode

Reading coordinates off a rendered PNG by eye puts the highlight next to the button, and
nothing downstream can detect that.

Depth became three planes. Ambient gradient → the same screenshot blown up and blurred
behind the device (colour spill) → the crisp shot in a window with a chrome bar and a real
shadow. Light behaving like light is what reads as modern; a drop-shadow alone reads flat at
video scale.

Seams got a rulebook. One direction for the whole film, every cut landing mid-motion on
both sides with mirrored power4 eases, and exactly one reserved "arrival" vector spent on
the payoff beat.

Before / after, same product, same screenshots:

The numbers that changed how I plan

Three measurements did more for my throughput than any visual change.

Render cost is dominated by one CSS property. check reports
browserGpuMode probe → software — no GPU, every frame rasterised on CPU:

Composition Wall-clock
Flat planes, no full-frame filters ~3–4× realtime (63.9s film → 3m17s)
One blurred backdrop plane + backdrop-filter ~10–23× realtime (64.2s film → 21m39s)

Same film, same length, ~6× apart. A four-film set cost 43 minutes of wall-clock. That's not a
detail, that's the shape of your afternoon.

Script length is arithmetic. Across 33 TTS segments — 338 words, 131.5 seconds — the rate
was 2.57 words/sec. So a 60-second film is about 144 words. I wrote my first 60s script by
feel; it measured 92 seconds. 53% over, and a full regeneration of every segment.

Three lint errors fire on every first build. gsap_exit_missing_hard_kill (a fade ending
on a clip boundary needs a matching tl.set or a seek restores stale visibility — it fired 7×),
duplicate_media_discovery_risk (two <img> sharing a src; the documented failure mode is a
blank render), and text_occluded (a caption inside a clip paints under any overlay declared
after the clips — DOM order decides).

And one no linter catches: exit opacity has to land exactly on the cut. Fading at 55% of
the exit travel left ~0.14s of empty stage between every scene — four black frames per seam,
passing every automated check.

I wrote all of this into the skill file. Felt good. Version 1.3.0.

Then I ran one command I'd never run

Later, unrelated, someone asked whether I could use stock footage or animated characters. So I
went looking at what the tooling could actually reach:

$ npx hyperframes catalog --json
358 items    # 145 blocks, 213 components
Enter fullscreen mode Exit fullscreen mode

Three hundred and fifty-eight prebuilt, installable motion components. hyperframes add <name>
drops one into your project as a local sub-composition with a paste-ready snippet.

Here is what was in there:

What I hand-built What already existed
A GSAP proxy tween counting a number up apple-money-count — counts up, flashes green, bursts money icons with sound
CSS device chrome with three fake traffic lights device-frame-stage, or macos-tahoe-liquid-glass — a real 3D laptop with a screen slot
A blurred backdrop plane for fake depth camera-dolly-zoom with focal length solved per frame; rack-focus with real aperture bokeh
Hand-timed emphasis on headlines char-slam-explode, caption-particle-burst, confetti, vfx-shatter

Four for four. Better versions of all of them. Sitting behind a command I never ran because I
went straight from "read the authoring docs" to "write the composition."

The honest kicker: the before/after above was won entirely by hand. Not one frame of it
uses the registry. I found the registry after those videos shipped. The visible improvement
came from craft; the invisible waste came from not shopping first.

What I actually changed

The skill file gained a section that now runs before any authoring:

Shop the registry BEFORE hand-authoring motion. 358 items. catalog --json first — the
bare form times out per-item. Skipping this is the single largest waste measured in this
skill's history.

Plus a routing table for the things I'd been guessing at: humanoid presenters route to an
avatar-video path (which needs an interactive OAuth login — a human action, not something an
agent can complete headlessly); a 3D rigged character is flatly not viable on a 2-core,
no-GPU box when a CSS blur alone already costs 20× realtime; stock footage is legally clean
from Pexels but works as texture, not actors; and extracted game assets are a hard no.

And I cut it down while adding that. The file went 264 → 236 lines, because a comparison table
re-arguing a decision I'd already made, and a ## Requirements section holding two commands,
were both bloat.

The transferable bit

The lesson isn't "read the docs." I read the docs. I read the authoring model, the data
attributes, the determinism rules — and then implemented, from those primitives, four
components that shipped in the box.

The docs told me how to build. Nothing prompted me to ask what already exists. Those
are different questions and I only asked the first one.

If you're pointing an agent at an unfamiliar framework, the highest-leverage line you can put
in its instructions isn't a code sample. It's: inventory the catalog before you write
anything.

Top comments (0)