OPUS 5 + HIGGSFIELD ONE-SHOTTED A PLAYABLE ARCADE GAME. NO ENGINE. NO SPRITES.
i wanted to see it: a 1983 laserdisc cabinet - the kind where a cartoon plays and you hit the key on the exact frame - rebuilt where every frame is generated.
this is what came back.
00:00 - a giant robot lands in the middle of a festival crowd, three hearts, timer running
00:06 - "Hold where you are." the beat that asks you to press nothing
00:08 - the scene stops and asks: fight the robot, or run
00:11 - HOLD to transform, lightning crawls up both arms
00:22 - charge your power, SPACE held down, the whole crowd goes blue
00:28 - second choice mid-fight: run, or get ready to battle
00:34 - attack, NICE, combo climbs to 4x
00:38 - PERFECT. the hit lands on frame, sparks everywhere
00:44 - Strike! two keys at once, mash them, 5x
00:52 - Land. 8x and still going
00:56 - a new key unlocks mid-fight, F appears on screen
01:03 - Finish it! mash F, 12x, the robot goes down
01:08 - Victory
full-screen video.
real timing windows.
live HUD - hearts, run timer, combo multiplier, key prompts, unlockable keys.
every frame generated.
not a single sprite hand-drawn.
Dragon's Lair used to mean an animation studio and a quarter every time you died.
this took an afternoon and a prompt.
interactive video is going to be unreal.
follow me + reply "LAIR" and i'll send you the exact prompt β
FABLE 5 + HIGGSFIELD: 40 HOURS PER CLIENT β 35 MINUTES.
to be exact: 35 minutes of my time.
the site still takes days to come together.
I'm just not in them.
so where did the 40 hours actually go?
here's the old breakdown, line by line, and what happened to each one.
β 12 HOURS: DESIGN
wireframes, a visual direction, two more directions because the first got rejected, then the system - type scale, spacing, colour, components.
what replaced it: the system gets generated once from the brief.
the two rejected directions never existed, because a written spec doesn't produce them.
β 16 HOURS: FRONT-END
turning a static file into a working site.
sections, responsive behaviour, forms, CMS fields, motion.
what replaced it: the same work, done against the design tokens instead of a Figma file.
this is where the biggest block of hours went, and it's the one I no longer touch at all.
β 6 HOURS: MEDIA
sourcing, licensing, or shooting.
then editing to length, then re-editing because the crop was wrong.
what replaced it: a batch rendered from the same brief, in parallel with the build.
β 4 HOURS: REVISIONS
two rounds, mostly fixing things that were never specified clearly.
what replaced it: one round, because the spec was written first.
this is a discipline change, not a tool change.
β 2 HOURS: COORDINATION
status calls, chasing assets, updating a client who could have read one line.
what replaced it: nothing.
it just stopped being necessary when there was nobody to coordinate.
WHAT'S LEFT:
β 15 minutes writing the spec
β 20 minutes checking the result against it
that's the entire 35.
the honest read isn't "AI is 68x faster."
it's that 38 of those 40 hours were production and coordination - work that never touched the outcome the client actually paid for.
Follow me + reply "BREAKDOWN" - I'll send the full breakdown to your DMs.
full breakdown in the article
Build a browser game where the entire screen is a cinematic video and the player interacts with it through timed key prompts and branching choices. No 3D engine, no sprites, no game loop rendering characters β the video *is* the game. Everything the player does either lands on a beat inside the clip or picks which clip plays next.
The design problem to solve: video is passive by default. Every element here exists to make the player feel their input mattered inside a scene they cannot actually change. Timing windows, feedback, and the illusion of causality carry the whole experience.
The game runs in a normal browser window, not fullscreen β the canvas is letterboxed inside whatever window it gets, and a fullscreen control is offered but never forced.
---
## 0. Asset contract
Video is the input. Everything else is code.
**Per node, one clip:**
- Format: MP4 (H.264, yuv420p) + WebM (VP9), 1920Γ1080, 30fps, AAC audio.
- Length: 6β20 seconds. Longer nodes lose tension; shorter ones make branching feel twitchy.
- **First and last frame of every clip must be visually continuous with its neighbours.** A choice that cuts to a different camera position breaks the illusion instantly. Author transitions in the edit, not in code.
- Weight: β€ 3.5MB per clip. Poster frame as WebP for each.
- No baked-in UI, no subtitles, no watermarks. Every overlay is DOM.
**Naming:** `node-<slug>.mp4` / `.webm` / `-poster.webp`.
**Beat map, authored per clip:** exact timestamps where a QTE can fire, in seconds from clip start, written against the edit rather than guessed at runtime. A punch lands at 4.2s; the prompt opens at 3.4s and closes at 4.6s.
Scaffold with colour cards of the right duration if clips aren't ready. Every mechanic β timing, lives, combo, key unlocks, branching, quiz β must be fully testable before a single frame of real footage exists.
---
## 1. Design tokens
```
--stage: #08090C /* letterbox area around the video */
--panel: rgba(12,16,32,0.94)
--panel-border: rgba(255,255,255,0.18)
--key-face: rgba(16,18,26,0.90)
--key-edge: rgba(255,255,255,0.78)
--key-active: rgba(255,255,255,0.92) /* pressed keycap fills near-white */
--accent: #7C4DFF /* action buttons, fullscreen, choice highlight */
--accent-glow: rgba(124,77,255,0.55)
--armed: #4ADE80 /* prompt engaged / input registering */
--perfect: #E8D44A /* perfect-window border and text */
--danger: #F0403C /* risky choice, damage, MISS */
--combo: #FF7A1A /* multiplier badge */
--heart: #FF4757
--text: #FFFFFF
--text-2: rgba(255,255,255,0.62)
--radius-panel: 14px
--radius-key: 12px
--radius-pill: 999px
--font-hud: "JetBrains Mono", ui-monospace, monospace
--font-ui: "Inter", system-ui, sans-serif
--prompt-title: 18px / 1.2, weight 600, --font-hud
--prompt-verb: 13px / 1, weight 400, --text-2
--choice: 17px / 1.3, weight 500, --font-ui
--hud: 15px / 1, weight 600, --font-hud, letter-spacing 0.06em
--dur-fast: 120ms
--dur-base: 260ms
--dur-slow: 600ms
--ease: cubic-bezier(0.22, 1, 0.36, 1)
```
Every overlay is a dark translucent panel with `backdrop-filter: blur(14px)` and a 1px border. No drop shadows. Glow only ever means "this is live right now".
---
## 2. Screen layout
The video fills the window, object-fit cover, letterboxed against `--stage`. Four fixed HUD zones sit above it. Nothing else ever appears on screen.
**Top-left β status cluster:**
- A 40px circular `X` (exit), then a pill holding the lives: three `--heart` hearts, 20px, 6px gap, rendered in a pixel/8-bit style.
- On damage the rightmost filled heart hollows out over `--dur-base` and the pill shakes 6px twice over 240ms.
**Top-right β session bar:**
- One pill, `--panel`, containing `TIME 02:24` in `--font-hud`, then three 40px circular buttons: pause, mute, fullscreen. Fullscreen is filled `--accent`.
- **The timer counts up.** It is a run duration, not a threat.
**Below the session bar β combo badge:**
- A `--combo` circle showing the multiplier: `3x`, `5x`, `8x`, `12x`. Appears only above 1x, positioned just under the right end of the session bar.
- On increment it scales from 0.6 with a spring and settles. There is no cap in the observed range β the number simply grows, and that growth is the reward.
**Bottom-left β key cluster:**
- Four keycaps in an inverted T: `β` above, `β β β` below. 72px each, `--key-face`, 2px `--key-edge`, `--radius-key`.
- **Always visible, from the loading screen onward.** It teaches the control scheme with no tutorial.
- On physical keypress the matching cap fills `--key-active` β a near-white flood, not a subtle tint β and scales to 0.94 for 90ms. This mirroring is what connects keyboard to screen.
**Bottom-right β action buttons:**
- Starts as a single 96px circle filled `--accent` with `--accent-glow`, showing a spacebar glyph, with a small `β£` caption beneath it.
- **A second button unlocks later in the run** (see Β§4): an `F` circle appears to its left, same size and treatment, with an `F` caption. The pair stays for the remainder of the run.
- On press, the button inverts: fill goes to `--key-active`, glow drops. Same rule as the keycaps.
---
## 3. Prompts
A prompt is a panel that appears mid-screen, names an action, shows the key or keys, and states how to press.
**Anatomy:**
- `--panel`, `--radius-panel`, 18px padding, min-width 240px, centred horizontally at roughly 36% viewport height.
- Line 1: the instruction in `--prompt-title` β sentence case for calm beats, ALL CAPS with `!` for loud ones.
- Centre: the required input rendered as keycaps, 52px each. Arrow keys show their glyph; Space shows the word `SPACE`; F shows `F`. **Multi-key prompts show both caps separated by a small `+`.**
- Line 3: the verb in `--prompt-verb` β `Tap!`, `Hold`, or `Mash!`.
- A progress hairline runs along the panel's bottom edge, draining across the window. This is the only countdown β no numbers, no ring.
**Observed instruction copy, as a tone reference:**
`Hold where you are.` Β· `HOLD to transform!` Β· `Charge your power` Β· `Lock in your power.` Β· `Bend down` Β· `Charge towards the robot` Β· `Pull` Β· `Attack` Β· `Power Attack` Β· `Combo attack` Β· `Land` Β· `Strike!` Β· `BLAST OFF!` Β· `Power up.` Β· `Finish it!`
Instructions describe what the *character* does, never what the interface wants. Never "press space" β always "Charge your power".
**Input kinds:**
| Kind | Verb | Grading |
|---|---|---|
| `tap` | `Tap!` | keydown within the window |
| `hold` | `Hold` | key held continuously from within the window until `closeAt`; releasing early fails |
| `hold-still` | `Tap!` on a directional key, or nothing | the "do the calm thing" beat |
| `mash` | `Mash!` | N alternating presses across the window; may require **two keys** (e.g. `β` + `β`) |
**Border states:**
- Neutral `--panel-border` β prompt open, no input yet.
- `--armed` (mint) β input registered and currently valid: hold engaged, mash counting.
- `--perfect` (gold) β the press landed inside the tight window; the border and the grade text share the colour.
**Timing:** windows are authored per beat. Grade against `video.currentTime`, never a wall clock.
**The video never stops for a prompt.** Miss it, lose a heart, the scene keeps moving. Pausing to wait for input would kill the tension the design exists to create.
---
## 4. Progressive key unlocks
The control scheme grows during the run. This is the single most under-used idea in the whole format β use it.
- The run starts with arrows + Space. The `F` button does not exist on screen.
- At an authored node (observed around the 02:40 mark, deep into the fight) the `F` button fades in beside Space, and the first prompt requiring it appears shortly after: `Finish it!` / `F` / `Mash!`.
- Once unlocked, the key stays for the rest of the run.
- Unlocks are declared in the node data, never inferred. A key must never appear in the same beat that first demands it β give the player at least one full node to notice it.
---
## 5. Feedback
Four grades, distinct in size, colour and position. This hierarchy is what makes a long fight feel like it has texture.
| Grade | Look | Meaning |
|---|---|---|
| `MISS` | `--danger`, pixel font, centred over the character | window elapsed with no valid input |
| `OK` | small, white, low-contrast, top-centre | valid but loose |
| `NICE!` | `--armed` mint, medium, top-centre | well inside the window |
| `PERFECT!` | `--perfect` gold, large, wide letter-spacing, centred high | inside the tight window |
- Every grade scales from 0.9 to 1.05 and fades over 700ms.
- `OK` is deliberately quiet. Most beats land here, and shouting on every one destroys the value of `PERFECT!`.
- Failure: one heart, a `--danger` vignette pulse at the screen edges for 400ms, combo resets to 1x.
- Success increments the combo. Nothing else about the HUD moves.
---
## 6. Branching choices
At authored decision points the clip holds on a designed still β the edit must land somewhere that reads as a pause, not a freeze.
**Warning:** ~3 seconds before, a `--accent` pill fades in low on screen: `A choice is coming up...`. It stops players from being ambushed mid-beat and resenting the outcome.
**Choice panel:**
- Question centred in `--font-hud`: `A giant robot is attacking!`, `What will you do?`
- Below it, 2β3 full-width option bars, stacked, 12px gap, max-width 760px, 56px tall.
- Each bar: 28px circular icon on the left, label in `--choice`, `β` on the right.
- **The focused option glows**, and colour carries meaning: `--accent` for the standard path, `--danger` for the risky one. Icons differ too β `β` for proceed, `!` for danger β so meaning survives colourblindness.
- Navigate `β β`, confirm `Enter` or `Space`, or click. The arrow caps in the corner light up as focus moves.
- Observed pairs: `Fight the robot` / `Run` β then later `Run` / `Get ready to battle`, with the danger colour swapped onto `Run`. **The same label can be the safe option in one node and the risky one in another.** Tone is per-option, never per-word.
**On confirm:** the panel fades over `--dur-base` while the next clip β already buffered β swaps on the same frame. The player must never see a loading state mid-story.
---
## 7. Quiz nodes
A second node type: answer correctly to proceed.
- Same visual language as choices, but grading is right/wrong rather than narrative.
- Correct: bar flashes `--armed`, combo +1, continue on the success branch.
- Wrong: bar flashes `--danger`, one heart, then either retry with the answer revealed or route to a failure branch, per node config.
- Optional explanation string, shown 2.5s after answering. This is what makes the format usable for teaching, not only for action.
- Never randomise option order on a retry. Players learn positions, and shuffling reads as the game cheating.
---
## 8. Victory and run end
- On the final node, a small `--panel` pill appears centred: `Victory!` β deliberately understated, in the same visual language as everything else. No confetti, no full-screen takeover.
- The scene continues playing past it: the antagonist is down, the crowd reacts, a supporting character enters frame. **Let the video land the emotional beat; the UI stays out of the way.**
- Prompts and the combo badge clear. Hearts, timer and controls remain until the clip ends.
- Then the run summary: elapsed time, grade counts (`PERFECT` / `NICE` / `OK` / `MISS`), best combo, path taken, replay. The path is what makes people replay.
---
## 9. Story graph
The whole experience is one JSON file. No node, prompt, or branch is hardcoded.
```json
{
"meta": {
"title": "Festival",
"startNode": "arrival",
"lives": 3,
"keys": ["ArrowUp", "ArrowLeft", "ArrowDown", "ArrowRight", "Space"],
"comboResetsOnMiss": true
},
"nodes": [
{
"id": "robot-appears",
"type": "scene",
"media": { "src": "/video/node-robot-appears", "poster": "/video/node-robot-appears-poster.webp" },
"duration": 14.2,
"prompts": [
{
"kind": "tap",
"keys": ["ArrowDown"],
"title": "Hold where you are.",
"verb": "Tap!",
"openAt": 3.2, "hitAt": 4.0, "closeAt": 4.8, "perfectWindow": 0.12
},
{
"kind": "hold",
"keys": ["Space"],
"title": "Charge your power",
"verb": "Hold",
"openAt": 7.5, "hitAt": 8.2, "closeAt": 11.0
},
{
"kind": "mash",
"keys": ["ArrowRight", "ArrowUp"],
"title": "Strike!",
"verb": "Mash!",
"openAt": 12.0, "closeAt": 13.6, "requiredPresses": 8
}
],
"choiceWarningAt": 13.8,
"next": "robot-choice"
},
{
"id": "finisher",
"type": "scene",
"media": { "src": "/video/node-finisher" },
"duration": 12.0,
"unlockKeys": ["KeyF"],
"prompts": [
{
"kind": "mash",
"keys": ["KeyF"],
"title": "Finish it!",
"verb": "Mash!",
"openAt": 5.0, "closeAt": 7.4, "requiredPresses": 10
}
],
"next": "victory"
},
{
"id": "robot-choice",
"type": "choice",
"media": { "src": "/video/node-robot-choice" },
"question": "A giant robot is attacking!",
"options": [
{ "label": "Fight the robot", "tone": "accent", "icon": "arrow", "next": "battle" },
{ "label": "Run", "tone": "danger", "icon": "warning", "next": "chase" }
]
},
{
"id": "victory",
"type": "scene",
"media": { "src": "/video/node-victory" },
"banner": "Victory!",
"end": true
}
]
}
```
Rules: a scene node with no `prompts` plays straight through. A prompt whose `closeAt` exceeds the clip duration logs a warning and clamps. A prompt referencing a key that hasn't been unlocked fails validation in dev. Any `next` pointing at a missing node fails loudly in dev and falls back to the start node in production, so a broken branch never dead-ends a player mid-run.
---
## 10. Media handling
This is where the format lives or dies.
- Two `<video>` elements, swapped: one playing, one preloading the next node. On transition swap `z-index` and play β never re-source a single element mid-story.
- **Preload every reachable next node.** At a choice with two options, both are buffered before the choice panel appears. If either isn't ready, hold the warning pill and delay the choice rather than show a spinner.
- `playsInline muted autoplay` for first paint, then unmute on first user gesture. Browsers will not autoplay with sound; the mute button must reflect real state, not intent.
- Poster frames render instantly. The player never sees black between nodes.
- **Loading screen**, exact copy: `Loading your game` as the title, `This can take a few seconds on slower connections. Hang tight.` beneath in `--text-2`, a slow rotating ring above. Honest copy buys more patience than a fake progress bar. The key cluster and action button are **already visible** behind it β the player learns the controls while waiting.
- Below ~2Mbps, drop to a 720p variant. Never degrade to stills; a QTE game without motion has nothing left.
---
## 11. Input
- Keyboard primary: arrows, Space, F, Enter, Esc for pause.
- Every keypress mirrors to its on-screen key immediately, whether or not a prompt is live.
- Touch: on-screen keycaps and action buttons are real buttons. On mobile raise the action buttons to 112px and move the cluster within thumb reach.
- Gamepad: d-pad to arrows, A/X to Space, B/Y to F.
- Process on `keydown`, never `keyup`, for taps. Ignore auto-repeat when grading a tap; count each physical press once when grading a mash.
- Debounce nothing. Input latency is the entire experience.
---
## 12. Performance and state
- React + a small state machine. The story graph is data; the machine handles node β prompt β grade β branch.
- **Grade against `video.currentTime`**, not a separate timer. Dropped frames and buffering desync anything else.
- Persist to `localStorage`: current node, lives, combo, elapsed time, unlocked keys, visited nodes. A refresh mid-run resumes where the player was.
- Total JS under 150KB gzipped. All the weight belongs to video.
- 60fps on the HUD at all times β animate only `transform` and `opacity`.
---
## 13. Accessibility
- Every prompt announces as text; a player who can't see the keycap still hears "hold space".
- `prefers-reduced-motion`: no vignette pulse, no shake, no scale on grade text β colour and text only.
- **Assist mode offered after two consecutive failures**, not buried in a menu: widens every timing window by 60%, lowers mash requirements by half, removes life loss. Players who need it won't pick it in advance.
- Choice tone never depends on colour alone β icon and position carry it too.
- Captions track per clip, toggleable, rendered in DOM so it can be positioned clear of the HUD.
- Contrast: verify panels against the brightest frame of every clip β the explosion frames specifically, not an average.
---
## 14. Build order
Each stage works before the next begins.
1. **Shell** β stage, video element, letterboxing, loading screen with controls already visible, HUD zones.
2. **Story graph** β JSON loader, validation for bad timings, dead branches, unreleased keys.
3. **Playback** β dual-video swap, preloading, posters, mute/pause/fullscreen.
4. **Key mirroring** β always-visible cluster and action buttons reflecting real input.
5. **QTE engine** β prompt rendering, grading against `currentTime`, tap / hold / hold-still / mash, multi-key prompts.
6. **Feedback** β four grade tiers, border states, damage, vignette.
7. **State** β lives, combo, timer, persistence.
8. **Key unlocks** β `unlockKeys`, button entry animation, validation.
9. **Choices** β warning pill, panel, keyboard navigation, both branches preloaded.
10. **Quiz nodes** β grading, explanation, routing.
11. **Real footage swap** β production clips, author every beat map against the actual edit.
12. **Victory and summary** β banner, scene playout, run stats, replay.
13. **Assist mode and accessibility** β widened windows, announcements, captions, reduced motion.
OPUS 5 + HIGGSFIELD ONE-SHOTTED A PLAYABLE ARCADE GAME. NO ENGINE. NO SPRITES.
i wanted to see it: a 1983 laserdisc cabinet - the kind where a cartoon plays and you hit the key on the exact frame - rebuilt where every frame is generated.
this is what came back.
00:00 - a giant robot lands in the middle of a festival crowd, three hearts, timer running
00:06 - "Hold where you are." the beat that asks you to press nothing
00:08 - the scene stops and asks: fight the robot, or run
00:11 - HOLD to transform, lightning crawls up both arms
00:22 - charge your power, SPACE held down, the whole crowd goes blue
00:28 - second choice mid-fight: run, or get ready to battle
00:34 - attack, NICE, combo climbs to 4x
00:38 - PERFECT. the hit lands on frame, sparks everywhere
00:44 - Strike! two keys at once, mash them, 5x
00:52 - Land. 8x and still going
00:56 - a new key unlocks mid-fight, F appears on screen
01:03 - Finish it! mash F, 12x, the robot goes down
01:08 - Victory
full-screen video.
real timing windows.
live HUD - hearts, run timer, combo multiplier, key prompts, unlockable keys.
every frame generated.
not a single sprite hand-drawn.
Dragon's Lair used to mean an animation studio and a quarter every time you died.
this took an afternoon and a prompt.
interactive video is going to be unreal.
follow me + reply "LAIR" and i'll send you the exact prompt β
FABLE 5 + HIGGSFIELD: 14 CLIENTS, $0 ON ADS. THAT'S NOT A FLEX - IT'S A DIAGNOSTIC.
people read "zero ad spend" as bragging.
it isn't. it's a test result, and most agencies would fail it.
here's what it actually measures.
β ADS BUY ATTENTION.
THEY DON'T FIX DEMAND.
if the work doesn't generate its own enquiries, ads don't solve that - they postpone it.
you rent attention, convert some of it, and the moment you stop paying, the pipeline is exactly as empty as it was.
the underlying problem was never reach.
β THE REAL QUESTION IS WHETHER THE OUTPUT IS SHAREABLE
a site that ships in days and looks like it took a month gets posted, screenshotted, and sent to someone's group chat.
one that took six weeks and looks fine gets a polite thank-you and no distribution at all.
that difference is not a marketing problem.
it's a delivery problem wearing a marketing costume.
β SO THE SPEND GOES SOMEWHERE ELSE
$0 on ads and ~$750/mo on the stack.
the money that would have been acquisition budget is production budget instead.
that's the whole trade, and it only works one way: the output has to be good enough to travel on its own.
β WHEN ADS WOULD MAKE SENSE
if the delivery system is proven and the constraint is genuinely awareness, ads are fine.
buying reach for work nobody forwards is the expensive version of hoping.
THE POINT:
β $0 on ads isn't a strategy - it's what happens when the work does the marketing
β if you turned ads off tomorrow, the number of enquiries next month is your actual score
β most people don't want to know that number
run the test before you buy the traffic.
Follow me + reply "DIAGNOSTIC" - I'll send the full breakdown to your DMs.
full breakdown in the article
FABLE 5 + HIGGSFIELD JUST BROKE THE 3 ASSUMPTIONS BEHIND EVERY $35,000 WEBSITE.
none of them were ever a scam.
all three were true five years ago.
that's exactly why they still get priced in.
β ASSUMPTION 1: THE CALENDAR MEASURES THE WORK
six weeks was never six weeks of building.
it's your project sitting in a queue behind three other accounts, waiting for a designer who's booked until Thursday.
the calendar measured availability, not effort.
remove the queue and the same site ships in an afternoon - not because anyone got faster, but because nothing is waiting on anyone.
β ASSUMPTION 2: REVISIONS ARE PART OF THE CRAFT
three rounds get priced in because the agency expects to miss.
it's insurance against a vague brief, sold back to you as attentive service.
write the spec properly - pages, states, breakpoints, what's editable and by whom - and one round covers it.
the rounds were never generosity.
they were the cost of not asking the right questions on day one.
β ASSUMPTION 3: MEDIA HAS A FLOOR
the hero video is the most marked-up line on any web invoice, because it's the one nobody can price against.
a shoot and a crew, or a licensed spot at $150-500 that three other brands already ran.
that floor is gone.
the line didn't get cheaper - it stopped being a line.
THE POINT:
β the calendar was queue time
β the revisions were insurance
β the media markup had nothing underneath it
the $35,000 was never wrong.
it was correct for a delivery system that no longer needs to exist.
Follow me + reply "AUDIT" - I'll send the full breakdown to your DMs.
full breakdown in the article
Build a single-page immersive site where the entire interface is an isometric 3D city. Every building is a real company's headquarters, rendered as a stylised landmark. Visitors drive around with a virtual joystick, click a building, and get a real project brief from that company. Progress is tracked in XP. There is no scroll, no page navigation, and no traditional layout anywhere β the city *is* the product page.
Positioning: this replaces the "logos of companies we work with" strip that every education and careers site has. Instead of a row of grey logos, you walk to the building.
---
## 0. Asset contract
The city model is an input, not something the code generates. Build against this contract and scaffold with a placeholder.
- **Format:** glTF 2.0 binary (`.glb`), Draco-compressed geometry, KTX2/Basis textures.
- **Scale:** 1 unit = 1 metre, Y-up, origin at the central plaza.
- **Budget:** β€ 2.5M triangles, β€ 90MB compressed, textures β€ 2048px.
- **Style:** stylised realism at miniature scale β clean massing, saturated brand materials, tiny trees and cars, water with a flat reflective shader. Reads like an architectural model, not a survey scan.
- **Landmark buildings:** each partner company gets a bespoke structure that references its brand language β a curved timber arena, a matte black monolith, a glass ring. The logo appears as geometry or an emissive sign, never as a flat decal pasted on a box.
- **Required node naming:**
- `HQ_<slug>` β one group per company building, clickable
- `BEACON_<slug>` β anchor point for a holographic light column above an HQ
- `HALO_<slug>` β flat disc under a building, used for the highlight ring
- `ROAD`, `WATER`, `GREEN`, `PROPS` β material groups tinted independently
- `SPAWN` β where the player starts
- **Lighting:** baked lightmaps in a second UV set, midday sun, soft shadows. One realtime directional light only.
**Placeholder:** a grid of coloured boxes with correct node names, so navigation, click targets, XP and card logic can all be built and tested before the real city exists. Swapping the asset must be a single path change.
---
## 1. Design tokens
Lock these. No other colours, fonts, or radii anywhere.
```
--frame-bg: #0B0D10 /* page behind the canvas */
--ui-dark: rgba(16,18,22,0.86)
--ui-light: rgba(255,255,255,0.92)
--ui-border: rgba(255,255,255,0.16)
--ink: #0D0F12
--ink-2: rgba(13,15,18,0.58)
--on-dark: #FFFFFF
--accent: #38E56A /* the beacon green, single accent */
--accent-soft: rgba(56,229,106,0.28)
--xp-gold: #FFC53D
--radius-frame: 16px
--radius-pill: 999px
--radius-card: 20px
--radius-chip: 8px
--font: "Inter", system-ui, sans-serif
--h-card: 21px / 1.25, weight 600, letter-spacing -0.01em
--label: 11px / 1, weight 600, letter-spacing 0.10em, uppercase
--body: 14px / 1.5
--ui: 14px / 1, weight 500
--dur-fast: 160ms
--dur-base: 320ms
--dur-fly: 1100ms
--ease: cubic-bezier(0.22, 1, 0.36, 1)
```
Every control is a dark translucent circle or pill with `backdrop-filter: blur(20px)`. No shadows on UI. The only glow in the entire scene comes from `--accent` on beacons and halos.
---
## 2. Camera and movement
The camera is fixed-angle isometric and never free-looks. The player moves the *view*, not a character.
| Parameter | Value |
|---|---|
| Projection | perspective, FOV 28Β° (long lens, keeps the miniature read) |
| Pitch | 52Β°, locked |
| Bearing | free rotation in 45Β° snapped steps via arrow buttons |
| Height | 180m default, 90m min, 320m max |
| Pan speed | 40 m/s at default height, scaled to height |
| Zoom | discrete steps via +/β buttons, eased over `--dur-base` |
| Bounds | soft clamp at the island edge β camera decelerates, never hard-stops |
| Idle | slow 0.8Β°/s orbit after 12s of no input, cancelled by any interaction |
**Controls:**
- **Virtual joystick, bottom-left**, 96px, always visible on desktop too β it signals "this is drivable" better than any tooltip. Drag to pan, spring back to centre on release.
- **Zoom + / β**, bottom-right, stacked circles.
- **Rotate β βΆ**, bottom-right, rotates bearing in 45Β° snaps.
- **WASD / arrow keys** mirror the joystick. Drag-anywhere-on-canvas also pans.
- No pinch-rotate on touch β only pinch-zoom and drag-pan. Two-finger rotation on a map like this makes people seasick.
---
## 3. Persistent UI
Everything floats over the canvas inside a rounded frame with `--radius-frame`, inset 12px from the viewport.
**Top-left β collapse chevron.** A single 36px circle, `--ui-dark`. Hides all UI for a clean screenshot. Reappears on any input.
**Top-right β XP counter.** Pill, `--ui-light`, containing a `--xp-gold` star glyph and the score: `219 XP`. On gain, the number rolls up and the pill pulses once at 1.04 scale over `--dur-base`. Never animate it more than that β this is a progress signal, not a slot machine.
**Bottom-centre β primary CTA cluster:**
- a 44px circular brand mark button (opens an about overlay)
- a wide `Join Beta` pill in `--ui-dark`, `--on-dark` text
- a 44px circular menu button (opens the nav sheet)
These three sit in one row, always centred, always present.
**Cursor:** a custom grab hand over the canvas, switching to a pointing hand over any `HQ_` node.
---
## 4. Beacons and discovery
This is what makes the city readable instead of pretty.
- Every undiscovered company HQ carries a **holographic light column** β a soft cylinder of `--accent-soft` rising ~60m, with a rotating ring of text at its base reading `JOIN THE WAITLIST` or the company name. The text ring rotates at 8Β°/s and always faces up, wrapping the disc.
- The column pulses gently: opacity 0.25 β 0.4 over 2.6s, sine-eased. All beacons share one clock so the city breathes in unison rather than flickering randomly.
- Hovering an HQ: the `HALO_` disc under it brightens, the building lifts 1.5m over `--dur-fast`, and a small name label fades in above it.
- Once a company's card has been opened, its beacon retires β the column fades out permanently and the halo turns to a thin static ring. **Discovery is visible.** The city visibly empties of beacons as the visitor explores, and that is the progress mechanic.
- Undiscovered count appears nowhere as a number. The beacons *are* the counter.
---
## 5. The project card
Clicking an HQ flies the camera to that building and opens a card.
**Card geometry:** 380px wide, `--ui-light`, `--radius-card`, 20px padding, anchored centre-right on desktop, bottom sheet on mobile. No shadow β a 1px `--ui-border` instead.
**Contents, in order:**
1. **Media block** β 16:10, `--radius-card` minus 8px, holding a short looping clip or still of a person at work. The company logo sits as a small white pill over the top-left corner of the media.
2. **Role label** β `--label`, `--ink-2` β `BUSINESS ANALYST`, `DATA SCIENTIST`.
3. **Project title** β `--h-card`, centred, two lines max: *"Demand Forecasting Model for Nike Zoom Launch"*.
4. **Brief** β `--body`, `--ink-2`, three lines max. One paragraph, no bullets. It must read like a real ticket, not marketing copy.
5. **Tool chips** β a wrapped row of small `--radius-chip` chips, each with a 14px tool icon and name: Cursor, Claude, Pandas, Jupyter, Python, Google Sheets, Canva, Notebook.
6. **CTA** β full-width dark pill, `Join Beta`.
**Behaviour:**
- Card slides up 24px and fades in over `--dur-base`, starting 400ms into the camera flight so the two movements read as one.
- Opening a card awards XP once per company. The counter rolls while the card is still animating in.
- Card closes on Esc, on clicking the canvas, or via a close affordance. The camera stays where it is β it does not fly back. Retreating undoes the visitor's sense of having travelled somewhere.
- Only one card open at a time.
---
## 6. Data model
Everything is driven by `city.json`. No company, project, or tool is hardcoded.
```json
{
"meta": {
"model": "/models/city.glb",
"spawn": { "position": [0, 180, 120], "bearing": 45 },
"xpPerDiscovery": 25
},
"companies": [
{
"slug": "nike",
"name": "Nike",
"node": "HQ_nike",
"logo": "/logos/nike.svg",
"viewpoint": { "position": [140, 110, 210], "bearing": 90, "height": 110 },
"project": {
"role": "Data Scientist",
"title": "Demand Forecasting Model for Nike Zoom Launch",
"brief": "Build a demand forecasting model that predicts weekly sales for the Nike Zoom launch.",
"media": "/media/nike.mp4",
"poster": "/media/nike-poster.webp",
"tools": ["cursor", "pandas", "jupyter", "notebook", "python"]
}
}
],
"tools": {
"cursor": { "label": "Cursor", "icon": "/icons/cursor.svg" },
"pandas": { "label": "Pandas", "icon": "/icons/pandas.svg" }
}
}
```
Rules: a company with no `media` renders the card without the media block, not with a grey placeholder. A tool slug missing from the `tools` map renders as a text-only chip rather than throwing. Every `node` must resolve in the model or that company is skipped with a console warning β never a crash in front of an audience.
**Persistence:** discovered slugs and XP live in `localStorage`. A returning visitor sees the city already partly cleared of beacons, which is the strongest reason to come back.
---
## 7. Motion rules
- Camera flight, card entry and XP roll are one choreographed move on `--ease`. Never let them start at three different times.
- Building hover lift is 1.5m and 160ms β anything bigger makes the city feel rubbery.
- Beacons share a global clock. Never randomise their phase.
- Zoom steps ease; they never snap.
- Joystick returns to centre with a spring, 220ms.
- `prefers-reduced-motion`: camera flights become 200ms cross-fades, beacons stop pulsing (they render at mid-opacity), the idle orbit is disabled, XP updates without rolling.
---
## 8. Performance
- React + React Three Fiber + drei. Zustand for state β UI reads from the store, never from the scene graph.
- Progressive load: the spawn district streams first; the visitor can drive as soon as it's ready. Remaining chunks load by distance from camera.
- Loading screen: full-bleed `--frame-bg`, brand mark, a percentage in `--label`, one line of copy. Fades to scene over 600ms once past 95%.
- Cap `devicePixelRatio` at 2. Instance every repeated prop β trees, cars, street lights, parked vehicles, generic filler blocks.
- Beacons are one merged geometry with a shader-driven pulse, not one mesh per building.
- Frustum culling on; occlusion culling for districts outside the current view.
- Card media preloads only on hover of its building, never on page load.
- Auto quality: sample FPS over a rolling 3s window. Below 45fps drop shadow resolution, then beacon bloom, then pixel ratio to 1.5, then prop LOD. Recover one tier at a time above 58fps sustained 10s. Never oscillate more than once per 10s.
- Target 60fps at 1080p on a 2021 mid-range laptop, 30fps floor on mobile.
---
## 9. Responsive
**Below 900px:**
- Joystick moves to a thumb-reachable position and grows to 112px.
- Zoom and rotate controls stack vertically at the right edge, above the safe area.
- Project card becomes a bottom sheet at 72% height, draggable to full, with the media block on top.
- XP pill shrinks to the number plus star, no "XP" word.
- Camera height range compresses β mobile viewers get a slightly closer default so buildings stay legible.
- The `Join Beta` pill stays permanently visible. It is the only element that must never be scrolled away.
---
## 10. Accessibility and fallback
- **Keyboard:** arrows or WASD pan, `+`/`β` zoom, `Q`/`E` rotate, `Tab` cycles through company buildings in geographic order with the camera flying to each, `Enter` opens the card, `Esc` closes.
- Every card is real DOM text β titles, briefs and tool names must be selectable and readable by a screen reader. Nothing baked into images.
- Focus rings 2px `--accent`, 2px offset, visible against both the light card and the dark canvas.
- Card is focus-trapped while open and announced as a dialog.
- Beacons are decorative and marked `aria-hidden`; discovery state is announced as text when XP changes.
- **No-WebGL fallback:** a directory view β a grid of company cards with the same logos, roles, project titles, briefs and tool chips, in the same visual language. Every piece of content in the 3D experience must be reachable here. It should read as a deliberate second view of the same product, not an apology.
---
## 11. Build order
Each stage works before the next begins.
1. **Shell** β framed canvas, renderer, tone mapping, loading screen, placeholder box city.
2. **Camera and controls** β isometric rig, pan, zoom, snapped rotation, bounds, joystick, keyboard.
3. **Data** β `city.json` loader, company registry, node resolution warnings.
4. **Interaction** β hover lift and halo, click to fly, camera viewpoints.
5. **Project card** β layout, media block, chips, CTA, open/close choreography.
6. **XP and discovery** β counter, roll animation, beacon retirement, `localStorage` persistence.
7. **Beacons** β merged geometry, shader pulse, rotating text rings.
8. **Real asset swap** β production `.glb`, re-author every viewpoint against it.
9. **Performance pass** β chunked loading, instancing, auto quality, media preloading.
10. **Responsive** β bottom sheet, thumb controls, mobile camera range.
11. **Accessibility and fallback** β keyboard tour, focus management, directory view.
HIGGSFIELD + FABLE 5 BUILT A FULL CLIENT WEBSITE. I WAS IN IT FOR 35 MINUTES.
the obvious follow-up is: so why only 14 clients?
if a client costs 35 minutes, 100 clients is 58 hours a month.
that's a part-time job.
it doesn't work, and the reason is the useful part of this whole model.
35 MINUTES IS NOT THE COST.
IT'S THE CEILING.
production scales for free - the system doesn't get tired at client 40.
review doesn't.
every build needs one person who knows what was promised, holding it against what shipped.
that person is the constraint, and there's one of them.
WHERE IT ACTUALLY BREAKS
β at 20 clients, review stops being 20 focused minutes and becomes 20 rushed ones.
you start scanning instead of checking.
β scanning means you approve a build with a missing CMS field.
the client finds it.
now you've spent an hour on a fix that a real review would have caught in a prompt.
β two of those a month and your 35 minutes per client is a fiction.
you're back to a job.
so the number that governs this business isn't revenue or client count.
it's how many reviews you can do at full attention before quality drops - and for me that lands around 14.
THE POINT:
β production cost per client: near zero, flat forever
β review cost per client: 20 minutes, and it does not compress
β your ceiling: total review capacity Γ· 20 minutes
everyone optimises the part that's already free.
the whole game is protecting the twenty minutes that aren't.
Follow me + reply "CEILING" - I'll send the full breakdown to your DMs.
full breakdown in the article
Build a single-page site for a carbon-removal measurement platform. The entire page is full-bleed aerial footage of real industrial facilities, with a single white caption panel anchored at the bottom that changes as the visitor scrolls. No columns, no cards, no hero-plus-features layout. The footage carries the credibility; the interface stays out of its way.
---
## 0. Asset contract
Video is the product here. Build against this and scaffold with placeholders.
**Per section, one clip:**
- Format: MP4 (H.264) + WebM (VP9), 1920Γ1080 minimum, 24fps.
- Length: 8β14 seconds, seamlessly loopable β first and last frame must match.
- Content: slow aerial descent or lateral drift over an industrial site. Shot high with a long lens so the scene reads slightly miniature β tilt-shift feel without a tilt-shift filter.
- Grade: natural daylight, greens intact, no teal-orange push. This must look like documentation, not advertising.
- Weight: β€ 4MB per clip after compression. Poster frame exported as WebP for every clip.
- No on-screen text, no logos, no people identifiable in frame.
**Naming:** `section-<slug>.mp4` / `.webm` / `-poster.webp`. Swapping a clip is a filename change and nothing else.
If footage isn't ready, scaffold with a still poster image per section. Every transition, caption behaviour and scroll mechanic must work identically on stills β the video layer is an upgrade, not a dependency.
---
## 1. Design tokens
Lock these. No other colours, fonts, or radii anywhere.
```
--panel: #FFFFFF
--ink: #0D0F10
--ink-2: rgba(13,15,16,0.52)
--on-video: #FFFFFF
--nav-fill: rgba(255,255,255,0.94)
--nav-border: rgba(13,15,16,0.08)
--cta-fill: #FFFFFF
--dark-section: #0A0B0C
--accent-glow: #7FE3E8 /* used only in the logo build */
--gradient-a: #2F6BFF /* the blue transition */
--gradient-b: #BFD4FF
--radius-nav: 10px
--radius-panel: 4px
--font: "Inter", "Suisse Int'l", system-ui, sans-serif
--h-section: clamp(30px, 3.2vw, 46px) / 1.12, weight 500, letter-spacing -0.02em
--label: 13px / 1.4, weight 450
--nav: 15px / 1, weight 500
--dur-fast: 200ms
--dur-base: 520ms
--dur-slow: 900ms
--ease: cubic-bezier(0.22, 1, 0.36, 1)
```
Everything is near-black on white or white on footage. No mid greys in text, no shadows anywhere, no rounded cards. The only curve in the whole design is the nav pill.
---
## 2. Layout
The page is a fixed viewport. The video fills it edge to edge at all times. Two elements float above it.
**Nav β top, full width, 24px inset:**
- Left: wordmark plus a 32px mark (two overlapping circles, one filled, one outlined, forming a lens shape where they cross). White on footage.
- Right: a single pill container, `--nav-fill`, 1px `--nav-border`, `--radius-nav`, holding three text links (Home / Insights / About) and, separated by a hairline divider, a CTA reading "Talk to us" preceded by a β glyph. The active link sits on a white fill inside the pill; the others are transparent.
- The pill never changes colour with the footage behind it β the fill is opaque enough to hold contrast over anything.
**Caption panel β bottom, anchored left, spanning ~78% of viewport width, 22px from the bottom and left edges:**
- Pure white, `--radius-panel`, 40px padding, height fixed at ~170px so it never jumps between sections.
- Section title in `--h-section`, left-aligned, one or two words ("Any Technology", "Any Scale").
- Bottom-right of the panel: a small right-aligned label in `--label` `--ink-2` β the qualifier ("Fully automated", "Grounded in precision. Built to scale.").
- A single 6px dot sits centred just above the panel's top edge, marking scroll position. It is the only progress indicator on the page.
Nothing else is ever on screen. No scroll arrows, no social icons, no cookie bar competing with the panel.
---
## 3. Section flow
Sections advance on scroll, one viewport at a time. Scroll is captured and snapped β a flick moves exactly one section, never two.
Suggested sequence:
1. **Opening** β widest shot, delivery vehicle entering a facility. Title carries the positioning line. Qualifier: the tagline.
2. **Any Technology** β different facility type. Establishes breadth.
3. **Any Scale** β large industrial plant, tighter framing.
4. **Transition** β the footage dissolves into a moving blue gradient. Caption panel stays; only the background changes. This is the breath between the physical world and the data world.
5. **All your data collection** β dark section. The logo mark rebuilds itself from a grid of small dots, glowing `--accent-glow` as it forms, settling to grey. Qualifier: "Fully automated".
6. **Close** β CTA section, same panel, the "Talk to us" action repeated at scale.
**Transitions between sections:**
- The outgoing clip cross-fades to the incoming one over `--dur-slow`. No cuts, no wipes, no slide-in.
- The caption title swaps with a 12px upward rise and fade over `--dur-base`, starting 150ms into the video cross-fade so the two are felt as one movement.
- The panel itself never moves or resizes. Only its contents change. This is what makes the page feel like one continuous shot rather than a stack of slides.
---
## 4. Video handling
- Every clip: `autoplay muted loop playsinline`, preload metadata only.
- Only the current and next clip are ever in memory. The previous one is unloaded on transition complete.
- Poster frame renders instantly while the clip buffers β the visitor must never see black.
- On `save-data` or a connection slower than 3G, drop to poster images for every section and skip video entirely.
- On mobile, serve a 720p cut of each clip and reframe to portrait β do not letterbox the landscape shot.
- `prefers-reduced-motion`: clips freeze on their poster frames, cross-fades become 200ms opacity swaps, the logo build renders in its final state.
---
## 5. Content model
Driven by `sections.json`. Nothing hardcoded.
```json
{
"brand": { "name": "Cula", "cta": { "label": "Talk to us", "href": "/contact" } },
"nav": [
{ "label": "Home", "href": "/" },
{ "label": "Insights", "href": "/insights" },
{ "label": "About", "href": "/about" }
],
"sections": [
{
"slug": "opening",
"title": "The MRV engine for carbon removal",
"qualifier": "Grounded in precision. Built to scale.",
"media": { "type": "video", "src": "/video/section-opening", "poster": "/video/section-opening-poster.webp" }
},
{
"slug": "any-technology",
"title": "Any Technology",
"qualifier": "",
"media": { "type": "video", "src": "/video/section-tech", "poster": "/video/section-tech-poster.webp" }
},
{
"slug": "data",
"title": "All your data collection",
"qualifier": "Fully automated",
"media": { "type": "canvas", "renderer": "logo-build" }
}
]
}
```
Rules: a section with an empty `qualifier` renders the panel with the title only β no placeholder text, no empty space collapse. A `media.type` of `canvas` swaps the video layer for a generative renderer without changing any panel logic.
---
## 6. The logo build
The one piece of generative work on the page.
- Canvas, full viewport, `--dark-section` background.
- The mark is sampled into a grid of dots at roughly 6px spacing.
- On section entry, dots fade up in a random order over 1.4s, each one glowing `--accent-glow` at 60% opacity as it appears.
- Once the shape completes, the glow drains to neutral grey over 800ms and the dots settle to full opacity.
- After settling, a very slow shimmer passes across the grid every 6 seconds β a 2% opacity wave, barely perceptible. Nothing more.
- Under reduced motion, the grid renders complete and static.
Do not add particles, connecting lines, or mouse interaction. The restraint is the point.
---
## 7. Performance
- React + Tailwind. Lenis for scroll capture and snapping. Framer Motion for caption transitions only.
- Target LCP under 2.0s: the first poster frame is the LCP element, inlined as a low-quality placeholder and swapped when the full WebP loads.
- Total JS under 120KB gzipped. This page has almost no interactivity β it should not ship a heavy runtime.
- Video is the only weight. Budget: 4MB per clip, and never more than two loaded.
- Cap `devicePixelRatio` at 2 for the canvas section.
- Target 60fps during cross-fades on a 2021 mid-range laptop. If frame rate drops below 45 during a transition, shorten `--dur-slow` to 400ms for the remainder of the session rather than dropping frames.
---
## 8. Responsive
**Below 900px:**
- Caption panel spans full width minus 16px margins, height auto, title drops one type step.
- Qualifier moves below the title, left-aligned, rather than sitting bottom-right.
- Nav collapses to the wordmark plus the CTA only β the three links move behind a menu sheet.
- Portrait video cuts per section, not letterboxed landscape.
- Scroll snapping stays. On touch it should feel like flipping cards, not free scrolling.
---
## 9. Accessibility
- Every clip carries a text description of what it shows, associated with the section, so the page is comprehensible without video.
- Section titles are real headings in DOM order β the scroll mechanic must not break document structure.
- Full keyboard navigation: arrow keys and Page Up/Down move between sections, Tab reaches nav and CTA, focus rings visible in `--ink` over white and `--on-video` over footage.
- Autoplaying video is muted and loops without audio, so no media controls are required β but respect `prefers-reduced-motion` per Β§4.
- Contrast: the nav pill and caption panel are opaque, so text contrast never depends on what the footage is doing behind them. Verify against the brightest and darkest frame of every clip.
---
## 10. Build order
1. **Shell** β viewport lock, scroll capture and snapping, section registry.
2. **Media layer** β poster-only version, cross-fade logic, preload/unload.
3. **Caption panel** β fixed geometry, title and qualifier swap, dot indicator.
4. **Nav** β pill, active state, CTA.
5. **Content** β `sections.json` loader, validation warnings for missing media.
6. **Video** β real clips, buffering, connection-aware fallback.
7. **Gradient transition** β the blue breath section.
8. **Logo build** β canvas renderer, dot grid, glow settle.
9. **Responsive** β portrait cuts, panel reflow, menu sheet.
10. **Performance pass** β LCP, JS budget, transition frame rate.
11. **Accessibility audit** β headings, keyboard, contrast against extreme frames, reduced motion.
HERMES AGENT RUNS A $30K/MO AGENCY ON A $5 VPS. HERE'S THE ENTIRE CODE PATH.
every "solo founder" you follow has four contractors on Upwork and a VA hiding in the DMs.
here's what actually holds a one-person agency up.
the bottleneck was never the work.
it was managing the people doing the work.
what replaced the team:
β PERSISTENT MEMORY
one memory across Telegram, Discord, Slack, email and the CLI.
it knows the account, the brand voice, what shipped last week.
nothing gets re-explained, and nothing walks out the door when someone quits.
β SUBAGENTS
isolated workers with their own terminals.
three client jobs run in parallel, zero context bleed.
this is what replaces headcount - not a faster chat window.
β SELF-WRITTEN QA
it checks its own output against a checklist it built from the last 40 jobs.
break something once, add one rule, it never repeats.
β CRON
client reports, backups, morning briefings.
unattended.
the delivery line runs intake β subagents β QA β client comms in your voice before they're awake.
a human sits in one of those four steps.
THE POINT:
β a team gets one person sharper, then that person leaves with the context
β an agent saves the skill and the next client gets it instantly
β every job makes the whole operation permanently smarter
self-hosted, MIT license, no telemetry, $5/mo in servers.
the window where "I manage a team of five" sounds impressive is closing.
the full code path - install, config, the ledger - in the article
Follow me + reply "6:30" - I'll send the setup to your DMs.
Obsidian + Claude just cut a day of decision-making to 90 seconds.
the trick isn't the model.
it's that the vault is built backward - from retrieval instead of capture.
every note app you've tried solved the easy half.
highlight, save, feel productive.
none of them built the structure that brings anything back when it's suddenly relevant six months later.
so you end up with 8,000 saved things and no path to the one that matters.
three properties make the difference: notes as plain markdown you own, wikilinks that mirror how you actually think instead of how you file, and a model with read access to the whole vault.
miss one and it dies quietly.
all three and the answer to a hard decision comes back in 90 seconds - from material you wrote months ago and forgot you had.
the metric at the end is the part worth stealing: not how many notes you have, how many ended up in something you shipped.
HERMES AGENT JUST KILLED THE 5-PERSON AGENCY. $30K/MO, $5 A MONTH.
a client sends a brief at 11pm.
here's what happens while nobody is awake.
β 11:04pm - the brief lands in Telegram.
the agent already knows the account: what shipped last week, the brand voice, the constraints.
nothing gets re-explained, because nothing was ever forgotten.
β 11:06pm - the work fans out.
research on one worker, drafts on another, assets on a third.
all running at once, none of them stepping on each other.
β 2:00am - output gets checked against a checklist the agent wrote itself, from what went wrong on the last 40 jobs.
anything that fails goes back through before a human ever sees it.
β 6:30am - the client gets an update on Telegram, in my voice, before they've made coffee.
β 8:00am - I read what happened.
that's four steps, and a human is in one of them.
the comparison people get wrong: this isn't a faster employee.
an employee at 11pm is asleep, and an employee in month seven is interviewing somewhere else.
the agent is neither.
it costs $5 a month in servers, it's MIT-licensed, and it never hands in notice.
the full delivery line - intake, QA, comms, the code path - in the article
Follow me + reply "NIGHT" - I'll send the setup to your DMs.
FABLE 5 + HIGGSFIELD JUST KILLED THE $35K/MO DESIGN AGENCY.
a staffed agency and a solo operator can bill the exact same $35k a month.
same clients, same deliverables.
the difference is one line: payroll.
here's where an agency's money goes before anyone pays themselves:
β a designer - ~$6k
β a motion artist - ~$6k
β a developer - ~$8k
β a PM plus overhead - ~$5k
that's ~$25k gone.
the agency keeps maybe $10k of its own $35k.
the solo stack that covers all four roles:
β Fable 5 - reads the brief, outputs the design system
β Claude Code - builds every section to the tokens, writes the logic
β Higgsfield - hero video and product shots, no shoot, no license
same site ships.
the payroll just doesn't exist.
software runs ~$750/mo, so a solo operator keeps closer to $34k of the same $35k.
the agency was never selling better work.
it was selling salaries you paid for.
THE POINT:
β staffed agency: $35k revenue β ~$10k kept
β solo operator: $35k revenue β ~$34k kept
β cost of the stack: ~$750/mo, flat whether you run 3 clients or 14
kill the payroll and the same revenue keeps three times the margin.
Follow me + reply "MARGIN" - I'll send the full breakdown to your DMs.
full breakdown in the article
Build a full-screen interactive brand experience where a stylized 3D city model is the entire interface. Visitors fly over the city, pick a topic, and watch the city itself respond β districts light up, routes trace between them, and editorial content slides in beside the view. This is a marketing site for an energy/infrastructure brand, not a dashboard: the tone is cinematic and confident, the copy is short, the city does the explaining.
---
## 0. Asset contract
The city model is an input, not something the code generates. Build against this contract and scaffold with a placeholder until the real asset lands.
- **Format:** glTF 2.0 binary (`.glb`), Draco-compressed geometry, KTX2/Basis textures.
- **Scale:** 1 unit = 1 metre, Y-up, origin at the city centre.
- **Budget:** β€ 2M triangles, β€ 80MB compressed, textures β€ 2048px.
- **Style:** stylized miniature β clean massing, soft edges, physical-model feel rather than photoreal survey data. Buildings simplified, no window-level detail. Water, port, bridges, and green space read as distinct materials.
- **Required node naming:**
- `DISTRICT_<slug>` β a group per district; the app shows, highlights, and frames these
- `ROUTE_<slug>` β spline curves along streets, used to animate the glowing paths
- `POI_<slug>` β empties marking camera targets for each topic
- `WATER`, `TERRAIN`, `BRIDGES` β material groups the app can tint independently
- **Lighting:** baked lightmaps in a second UV set, authored at golden hour β warm low sun, long soft shadows, cool ambient in shadow.
Placeholder fallback: a procedural block city on a grid with correctly named nodes, so camera flights, routes, and topic switching can all be built and tested before the real model exists. Swapping in the production asset must require changing one file path.
---
## 1. Design tokens
Lock these. No additional colours, fonts, or radii anywhere in the build.
```
--page-bg: #2A1F2E /* deep plum, the page around the canvas */
--canvas-edge: #1A1218 /* vignette at the frame edges */
--panel-light: #FFFFFF /* the editorial panel */
--panel-dark: rgba(20,16,22,0.72)
--border-subtle: rgba(255,255,255,0.14)
--text-on-dark: #FFFFFF
--text-on-dark-2: rgba(255,255,255,0.66)
--text-on-light: #1A1218
--text-on-light-2: rgba(26,18,24,0.60)
--accent-route: #4FE0C4 /* the teal that traces through the city */
--accent-warm: #F2B441 /* topic marker, warm counterpoint */
--accent-cool: #6EA8FF /* topic marker */
--accent-violet: #A986FF /* topic marker */
--radius-frame: 18px
--radius-pill: 999px
--radius-card: 12px
--font-display: "Inter Tight", "Neue Haas Grotesk", sans-serif /* headlines, tight, uppercase */
--font-body: "Inter", system-ui, sans-serif
--size-h1: clamp(38px, 4.4vw, 66px) / 0.98, weight 700, letter-spacing -0.02em
--size-body: 15px / 1.6
--size-label: 12px / 1.2, weight 600, letter-spacing 0.08em, uppercase
--dur-fast: 180ms
--dur-base: 360ms
--dur-fly: 1800ms
--ease: cubic-bezier(0.16, 1, 0.3, 1)
```
The 3D canvas sits inside a rounded frame with `--radius-frame`, inset from the viewport edges, floating on `--page-bg`. A soft inner vignette darkens the canvas corners so overlaid text always has contrast.
---
## 2. Screens and flow
The experience runs as three states over the same continuous 3D scene. There is no page navigation and no reload β the camera never cuts.
**STATE 1 β ENTRY**
- The city sits far away, high angle, slowly rotating.
- Centred over it: a small eyebrow line ("Welcome to"), the brand lockup as a large display line, and a single pill button beneath ("Explore the city"). Fine-print legal at the bottom, `--text-on-dark-2`, 11px.
- Everything except the button fades out on click; the camera begins its descent immediately, before any UI has finished animating. Motion starts first β that's what makes it feel like an experience rather than a page.
**STATE 2 β OVERVIEW**
- Camera settles into a low aerial three-quarter view, still drifting.
- Top-left: brand mark, 28px, `--text-on-dark`.
- Left third: the headline in `--size-h1`, uppercase, two or three lines, with a 45-word paragraph beneath in `--text-on-dark-2` and a text link.
- Bottom-centre-right: a `--size-label` prompt ("Select topic") above a row of three pill buttons. Each pill: `--panel-dark`, 1px `--border-subtle`, backdrop blur, a 10px colour dot in its topic accent, and the topic name. Hover raises fill and grows the dot to 14px.
- Bottom-left: a small rounded preview thumbnail (120Γ80) showing a photograph tied to the currently hovered topic, with a "+" affordance. It cross-fades as the visitor moves between pills.
**STATE 3 β TOPIC**
- Choosing a topic does four things at once, all on the same easing curve:
1. the camera flies to that topic's `POI_` viewpoint
2. the relevant `DISTRICT_` groups brighten while everything else drops to 55% brightness
3. `ROUTE_` splines for that topic draw themselves in `--accent-route`, animating from origin to destination
4. a white editorial panel slides in from the right
- The headline block on the left shrinks to a single line and moves up, keeping the topic name visible.
- Switching topics never returns to overview: routes retract, new routes draw, the camera flies directly from one viewpoint to the next.
---
## 3. The editorial panel
Anchored right, 380px wide, full height minus 40px margins, `--panel-light`, `--radius-frame`, no shadow β it reads as a card of paper laid on the scene.
- Header: section label in `--size-label` `--text-on-light-2`, then the topic title in display type, 26px.
- Body: a vertical list of story cards. Each card is a 96px-tall row: a 96Γ96 photograph with `--radius-card` on the left, and on the right a title in uppercase display type at 14px plus a one-line dek in `--text-on-light-2`.
- Hovering a card lifts it 2px and brightens the matching district in the 3D scene behind the panel. This link between panel and city is the whole point β never break it.
- Clicking a card expands it in place to reveal 2β3 short paragraphs and a link out, pushing the other cards down over `--dur-base`.
- The list scrolls independently; the page never scrolls.
- Close via an X top-right or Esc, which returns to STATE 2 with a slow camera pull-back.
---
## 4. Camera
| Parameter | Value |
|---|---|
| Entry framing | high orbit, pitch 62Β°, distance 900m |
| Overview framing | pitch 48Β°, distance 420m, slow drift |
| Topic framing | pitch 40Β°, distance 220m, authored per POI |
| Idle drift | 1.5Β°/s orbit, starts immediately, never stops in STATE 1β2 |
| Flight duration | `--dur-fly`, eased with `--ease` |
| Flight path | quadratic curve with the control point lifted 120m so the camera arcs over the skyline rather than through it |
| FOV | 42Β° β long lens, keeps the miniature feel |
| User control | drag to orbit within Β±35Β° of the authored bearing, no free-look, no zoom |
Constraining the camera is deliberate. A visitor who can fly anywhere will find the ugly angle. Every framing in this build is authored.
---
## 5. Scene and atmosphere
- Golden-hour key light, warm (#FFC98A), low angle, long shadows across the grid.
- Cool ambient (#5A6E96) filling shadow so the city never reads muddy.
- Baked lightmaps, no realtime GI. One realtime directional light only, for the shadow the moving camera needs.
- Water: reflective, slightly animated normal map, tinted toward the plum background so the frame feels unified.
- Post: ACES Filmic tone mapping, subtle bloom on emissive route lines only (threshold 0.85), light vignette. No depth of field on the wide shots β miniatures lose their charm when blurred; a very slight tilt-shift on topic close-ups only.
- Ambient audio, muted by default: soft city hum that ducks under the editorial panel when it opens.
---
## 6. Data model
Everything is driven by `experience.json`. No topic, route, or story is hardcoded.
```json
{
"brand": { "name": "nrg", "model": "/models/city.glb" },
"entry": { "eyebrow": "Welcome to", "title": "the world of nrg", "cta": "Explore the city" },
"overview": {
"headline": "Leading the way to a smarter, brighter future.",
"body": "β¦45 words maxβ¦",
"link": { "label": "Explore how we build a smarter, brighter future", "href": "/about" }
},
"topics": [
{
"slug": "business",
"name": "Business",
"accent": "--accent-cool",
"poi": "POI_business",
"districts": ["DISTRICT_downtown", "DISTRICT_port"],
"routes": ["ROUTE_grid_east"],
"thumb": "/img/topics/business.jpg",
"stories": [
{
"title": "Powering the AI boom",
"dek": "Data centres are rewriting demand curves.",
"image": "/img/stories/ai-boom.jpg",
"body": ["β¦", "β¦"],
"href": "/stories/ai-boom"
}
]
}
]
}
```
Rules: a topic with no `routes` still works (districts brighten, nothing draws). A story with no `body` links straight out instead of expanding. Every `districts` and `poi` value must resolve to a node in the model or the topic is skipped with a console warning β never a crash on stage.
---
## 7. Motion rules
- Camera, district brightening, route drawing, and panel entry all share `--ease` and start on the same frame. Staggering them makes the site feel like software; synchronising them makes it feel like a film.
- Routes draw over 900ms using a dash-offset animation along the spline, with a small bright head that fades behind it.
- Text: headline lines rise 16px and fade in, staggered 60ms per line. Body copy follows after 120ms.
- Panel: slides 40px and fades over `--dur-base`. Cards inside stagger 50ms.
- Buttons scale 0.97 on press, fill lightens on hover. No shadows on any UI element.
- `prefers-reduced-motion`: camera flights become 300ms cross-fades between authored viewpoints, idle drift stops, routes appear fully drawn, text fades without movement.
---
## 8. Performance
- React + React Three Fiber + drei. Zustand for state β UI reads from the store, never from the scene graph.
- Progressive load: the model streams in chunks, city centre first. The entry screen stays up until the first chunk is ready, showing a percentage in `--size-label`, then fades to the scene over 600ms.
- Cap `devicePixelRatio` at 2. Instance every repeated element: street lights, trees, small blocks, vehicles.
- Frustum culling on; occlusion culling for districts outside the current framing.
- Route splines are one merged geometry per topic, not one draw call per segment.
- Auto quality: sample FPS over a rolling 3s window. Below 45fps drop shadow resolution, then bloom, then pixel ratio to 1.5, then district detail. Recover one tier at a time above 58fps for 10s. Never oscillate more than once per 10s.
- Target: 60fps at 1080p on a 2021 mid-range laptop, 30fps floor on mobile.
---
## 9. Responsive and fallback
**Mobile (< 900px):**
- Camera framings re-authored per topic for portrait β do not just letterbox the desktop shot.
- Headline drops two type steps and moves to the bottom third, over a gradient scrim.
- Topic pills become a horizontally scrolling row pinned above the safe area.
- The editorial panel becomes a bottom sheet at 65% height, draggable to full.
- Drag-to-orbit only, no pinch zoom.
**No WebGL / low-end:**
- Serve a rendered still of each authored viewpoint as a full-bleed background, cross-fading on topic change. Every piece of copy, every story card, and the full panel behave identically.
- This must look like a deliberate design, not a degraded one. A visitor on the fallback should not be able to tell they're missing the 3D unless they compare.
---
## 10. Accessibility
- All controls keyboard reachable: Tab through topic pills, Enter to select, Esc to close the panel and return to overview.
- Focus rings 2px in the topic accent colour, 2px offset.
- Every story card is a real link with real text β nothing baked into images. Full alt text on all photography.
- The panel is focus-trapped while open and announced as a dialog.
- Copy contrast checked against both the light panel and the darkest region of the scene behind overlaid text; if a framing puts white text over a bright building, deepen the vignette rather than moving the text.
- `prefers-reduced-motion` fully honoured per Β§7.
---
## 11. Build order
Each stage works before the next begins.
1. **Shell** β page background, rounded canvas frame, renderer, tone mapping, vignette, loading screen.
2. **Placeholder city** β procedural block model with correctly named nodes.
3. **Camera system** β three authored states, flight curves, constrained orbit, idle drift.
4. **State machine** β entry β overview β topic, with all transitions synchronised.
5. **UI layer** β brand mark, headline block, topic pills, hover thumbnail.
6. **Data** β `experience.json` loader, topic registry, validation warnings.
7. **Scene response** β district brightening, route drawing, dimming.
8. **Editorial panel** β cards, expansion, panelβcity hover link.
9. **Real asset swap** β production `.glb`, re-author every framing against it.
10. **Performance pass** β chunked loading, instancing, auto quality.
11. **Responsive + fallback** β portrait framings, bottom sheet, still-image mode.
12. **Accessibility audit** β keyboard, focus, contrast, reduced motion.
State the component tree before writing code. Keep the 3D layer and the UI layer fully decoupled β the UI must be renderable and testable with the canvas switched off.
FABLE 5 + HIGGSFIELD RUN 14 CLIENT BUILDS ON $750/MO. HERE ARE THE 6 THINGS THAT WOULD TRIPLE THAT BILL.
everyone talks about model pricing.
that's not where the money goes.
it goes into repetition - paying an agent to redo work a system should have absorbed once.
here's every place it leaks:
β LEAK 1: REBUILDING THE SCAFFOLD
nav, hero, pricing, footer, contact.
the same skeleton on every client site, generated from scratch every time.
fix: build it once, keep it as a master template, start every project as a copy.
client 10 should cost a fraction of client 2.
β LEAK 2: ONE GIANT SESSION FOR THE WHOLE BUILD
every follow-up re-sends the entire conversation.
by section eight you're paying for section one on every message.
fix: fresh context per section.
feed it the tokens and the spec, ship the section, close it.
β LEAK 3: VAGUE PROMPTS
"make it better" triggers a full regeneration.
so does "something's off."
fix: "the pricing table's third column loses its border below 768px" triggers a patch.
specific costs a fraction and lands first try.
β LEAK 4: TOP MODEL FOR EVERYTHING
scaffolding, file moves, renames, boilerplate - none of that needs the expensive model.
fix: cheap work on a cheap model.
save the good one for the design system and the logic.
β LEAK 5: RE-EXPLAINING THE DESIGN SYSTEM
describing your spacing scale in prose, every prompt, forever.
and it drifts anyway.
fix: tokens in a file.
reference the file.
Fable 5 outputs it once and every build points at it.
β LEAK 6: GENERATING MEDIA INTERACTIVELY
one clip at a time, watching each render, tweaking, re-running.
fix: batch the whole set from one spec while the build runs.
media should never be a session you sit through.
CONNECT HIGGSFIELD (MCP):
add it as a custom connector in Claude Code:
mcp_servers:
higgsfield:
url: "https://t.co/7UyW95GEl4"
one OAuth flow.
Claude generates and pulls clips directly - no exporting by hand.
THE MATH:
β what the stack costs at 3 clients: ~$750/mo
β what it costs at 14 clients: ~$750/mo
β what changes between them: nothing, because the repetition got absorbed
every one of these six turns a variable cost into a fixed one.
that's the whole reason the bill doesn't grow with the client list.
Follow me + reply "BILL" - I'll send the full setup to your DMs.
full breakdown in the article
Rebuild the web app shown in the attached video as closely as possible. It's a dark, cinematic 3D "intent β infrastructure" city-visualization ("Intent to infrastructure with Gemini Cloud Assist") β an AI agent builds a glowing neon city on a dark 3D map that the user flies around, hovers, and inspects while a live agent log streams. Recreate the look, layout, motion, and interactions faithfully.
MATCH THIS EXACTLY:
- 3D scene: a dark near-black city β flat street grid with low dark extruded building blocks fading to a hazy horizon with faint mountains + stars. A central CLUSTER of structures glowing in vivid neon (magenta, electric green, cyan, blue, orange) with heavy bloom. Animated dashed route lines (blue/green/yellow) flowing through the streets. Glowing vertical light-beam beacons (blue), a pulsing green ground ring, floating pins.
- POI hover: a rounded Material pill tooltip with an icon + label fades in (e.g. "Spectators", "Medical Station").
- UI chrome (Material style, rounded/glassy, over the canvas):
Β· top bar: hamburger menu + dim title "Intent to infrastructure with Gemini Cloud Assist"
Β· left vertical tool rail: favorite (heart), location/shield, camera, orbit/recenter, fullscreen + camera-preset toggles "A / B / C"
Β· bottom-center action bar: a rainbow Gemini-style gradient orb, a compare/swap icon, a ">_" terminal/log toggle
Β· bottom-right: settings gear + refresh
Β· right "LOG" panel (slides in on ">_"): tabs PLANNER / RUNNER; table columns TIME Β· AGENT Β· TYPE Β· MESSAGE; rows streaming (PLANNERΒ·TOOL, SIMULATORΒ·RUN, PLANNERΒ·RUN) with colored type tags (red TOOL / blue TOOL / green RUN) + a colored dot per row.
BEHAVIOR TO REPRODUCE:
- Free orbit / pan / zoom camera; smooth camera fly between top-down overview and low horizon angle.
- Camera presets A/B/C: click to smoothly fly (GSAP) between framed viewpoints.
- Hover POI β tooltip pill; toggle the Log panel; new log rows stream in with a subtle highlight.
- A cinematic "build" reveal: structures rise and light up neon cluster-by-cluster, routes draw in, beacons pop.
- Heavy bloom on neon emissives, animated dashed routes, pulsing beacons, slow idle camera drift, smooth eased transitions (no snapping).
STACK: Next.js + TypeScript + Tailwind, React Three Fiber (Three.js) + drei (OrbitControls, GLTF Draco), GSAP for camera, Framer Motion for UI panels, postprocessing (bloom on emissive layer, vignette, faint grain). 60fps, responsive, prefers-reduced-motion fallback.
I'll supply the city as a GLTF β scaffold everything around a placeholder /public/city.glb (or generate a procedural dark grid city with extruded blocks if none), and make the neon cluster, routes, beacons, POIs, camera presets, and streaming Log panel fully working around it. Build the complete app.