← Creative Lab
Personal R&D — 2026

VIOLET Studio

A six-lane editor that researches a brand once and applies it to every generation

VIOLET Studio researches a brand once, then lets it shape every still, clip, music bed and voiceover generated inside a six-lane editor. The proof is an NBA spec spot, cut and exported frame for frame.

40
Generation Endpoints
6
Track Types, One Timeline
0
API Keys In The Browser
The hard part

The same brand profile that shaped the prompt also grades the finished image — a vision model scores palette and style, and a failing verdict's suggested fix folds straight back into the next prompt.

Client
Personal R&D
Role
Creator / Product Engineer Editor Architecture / Brand Systems
Timeline
2026
The proof, firstan NBA spec spot, cut and exported in the studio
00:00.00

27.5 seconds, six lanes, one export. A self-initiated spec spot for the NBA — not affiliated, not commissioned; the players are synthetic. Every still and clip was generated inside the timeline with the researched NBA profile leaning on the prompts; the mark on screen is the league's real file, not a drawing of it; the six voiceover pieces, the bed and the end card sit on their own lanes; the file was rendered by the studio's own composer. It is the file every export number on this page was measured on.

1920×1080 · 30 fps
18 clips · text, logo, video, voiceover, music, SFX
turn the sound on — the mix is the point

the problem

Generated brand work drifts, and it's made one asset at a time somewhere other than where the spot is cut.

the outcome

The brand is researched once and shapes everything generated inside the editor, where the cut is made.

the proof

The exported file is that cut, frame for frame — 27.533 s to the millisecond, six of six voice pieces at zero lag.

The VIOLET Studio editor with the NBA spec spot open: preview above, six timeline lanes below with real waveforms, the gallery of generated media and the project settings on the left
The file, and where it was made. The same project, open in the editor: eighteen clips across six lanes — text, logo, video, voiceover, music, SFX — with real waveforms on every audio lane, the league's mark on the GFX lane, and the researched profile one panel to the left. The playhead is on the jump shot at 13.47 s — the moment the hero's poster is taken from.
Why a spin-off at alltwo problems the parent couldn't solve

The first was shape. VIOLET's thesis — research a brand, then hold every generation to it — is an editorial idea, but it lived in a generator. Brand consistency is something you judge across a sequence, and there was no sequence: you generated, downloaded, and assembled the work somewhere else, where the brand profile no longer existed.

The second was keys. VIOLET's front end reached the model providers directly, so the API keys compiled into the JavaScript bundle — which is why it can never be hosted anywhere public. Studio's first milestone was a hard architectural rule, and it holds today: no key is ever sent to or read from the browser. Generation goes through a server-side proxy, and the brand research doesn't use a key at all.

The walk, brief to exporttwelve steps · one packet · the last five are what August couldn't show

VIOLET's original argument was a system that holds an opinion about its own output. Studio finishes it twice over: the same brand profile that shaped the prompt is used to grade the result, and the timeline the result lands on is exported by a local pass that places every clip at its timestamp and in-point — so the file is the cut. Press play, or step through it; every box is a real module and every number in it was measured.

stage 1the loop — brand → research → compose → generate → audit → back
a brand name or a pasted client brief “NBA” research local CLI · web search · 17 s palette · rivals · the logo compose influence dial 0 → 1 prompt + negative prompt generate server proxy · 40 endpoints image · video · music · VO audit vision model · same profile palette + style · a fix timeline 6 lanes · edges on frames cut · trim · slip · ⌘-scrub export one local ffmpeg pass @ timestamp + in-point ↓ no API key reaches the browser — research needs none at all, generation goes through a server proxy pass fail → apply fix & regenerate
stage 2the cut, exported — timeline → plan → one ffmpeg pass → the file, measured
the cut 6 lanes · 18 clips toFrame · healRowGaps planClips work area · in-points /api/export one local ffmpeg pass black base, exact length video trim @ in-point · freeze audio trim · pad · delay · mix overlays text + real logo · windows export.mp4 27.533 s · 1080p · 30 fps exact to the millisecond receipts · measured against the timeline duration exact · 6/6 VO pieces at 0 ms lag · music correlation 0.94–1.00 · video SSIM 0.86–0.99 at the true in-point · black where the timeline is empty
step 1/12 A brand name is the whole input.
every value from the fix log
Turn the influence diala real researched brand — the composition runs in your browser

This is the thesis, running. The profile on the left was produced by the app's own research route against the NBA, for the spec spot above, and the composition on the right is a port of lib/brand.ts. The dial is not a fade — it is a ladder. Each threshold folds one more layer of the brand into the prompt, in the order the source uses.

researched profile — …

brand influence0.60
composed prompt — what the model receives

          
negative prompt — what it must avoid

          

Three details in there are the difference between a demo and a tool. The palette is emitted as colour language plus hex — royal blue (#1D428A) — because a diffusion model treats a bare hex code as close to noise while the colour name actually steers it. The brand's don't-list is routed to the negative prompt with its imperatives stripped: writing “avoid crowd shots” into a positive prompt makes crowd shots more likely, and leaving the word “avoid” in a negative prompt negates the negation. And a rival's mark is excluded at every active level, not just the high ones — it used to open at 0.5, which is how a swoosh once ended up in a demo.

Four things worth reading the code foreach one a decision, with the number that proved it

01 · researchNo API key — and a four-second answer when it can't.

Brand research shells out to the local Claude Code CLI in headless mode with web search. It authenticates with a developer's own login, so there is no key to leak and nothing to host. A real NBA run streams each search into the panel and returns in 17 s.

Two measured failures shaped it. A spawned claude -p reads stdin until EOF and the child never closed it, so every run hung to the 240 s timeout — 3.7 s once stdin was ignored. And an expired login made the CLI retry for over three minutes; a one-turn preflight with retries off now answers in 4.0 s with a plain “login expired” and the fix. Profiles are cached per brand on disk.

The brand panel with the NBA profile: four palette swatches, the real NBA mark, a line for guidelines, the researched photography style and the influence selector on Style
The panel, after research. Palette as swatches, the real mark, a drop zone for the brand book, the photography style in the brand's own words, and the influence selector — Off, Palette, Style, Full.

02 · the markThe real logo, not a drawn one.

A diffusion model draws an approximation of a mark — two stripes where there are three. So logos are sourced as files, in tiers: Wikidata's logo property on Wikimedia Commons first, then the brand's own site (header SVG, inline marks, touch icon), then web search only if both come back empty. Every candidate is downloaded and rejected unless it has real transparency and at least 200 px to work with.

The earlier version asked a model to hunt for the URL. Reproduced, it spent all 12 of its turns, tried the shell twice, and returned nothing in 32 s. The tiered finder returns the NBA's primary mark in 3–4 s, adidas and Aesop in 1–3 s.

The logo finder dialog: five vector NBA candidates on checkerboards, the first from Wikimedia Commons via Wikidata, the rest from nba.com including two team crests
Five vectors, on checkerboards. The top candidate is the league's primary mark from Commons via Wikidata; the rest came off nba.com, team crests included. Clearing a mark for distributed work is still the human's call — this is the same material you'd put in a pitch board.

03 · the editorAn editor that thinks in frames.

Every edit lands on a frame. A gap narrower than one frame is healed, because nobody could have placed it; neighbours give way on a move exactly as they do on a drag, and a clip is never shorter than 250 ms. The keys are an editor's keys, and ⌘-scrubbing the ruler plays 110 ms bursts of the actual audio.

The rule was earned. Ed's playhead at 17.97 s sat on a one-frame flash between two clips that touched exactly — no gap in the data. The preview turned milliseconds into frames with Math.floor, separately for each clip's start and length, and floating point had shaved a hair off one of them. “Frame 539 belonged to nobody.” Try it: at 30 fps, one second is not thirty frames — not quite.

⌘K cut at playhead⌥[⌥] trim edge to playhead [] move in/out to playheadY slip ⌘+drag ruler: hear it⇧␣ play from In
a cut at1000ms ÷ (1000 / 30) =29.999999999999996frames
Math.floor29one frame belongs to nobody
toFrame (round)30neighbours share the boundary
frame 27clip A ends · clip B beginsframe 38

Of the 301 frame-exact boundaries in a 30-second timeline, — come out a hair under the integer in floating point. Computed just now, in this browser. toFrame() rounds through the frame index and a clip's length is end − start in frames, so no Math.floor remains in the preview.

The timeline mid-slip: a dashed ghost of the whole 5.9-second clip drawn behind the trimmed clip being slid, over the voiceover lane's waveforms
Slip, with the whole media ghosted behind the clip. The dashed bar is everything the clip could show; the solid one is the window it does. The in-point that window sets is what the export honours.

04 · the fileThe export is the cut.

The remote compose endpoint the app inherited places whole files at timestamps: no in-point, so every cut, trim and slip exported from the start of its media, and no say over a leading gap, six voice pieces from one file, or when the bed comes in. It couldn't be made exact, so the export moved home.

One ffmpeg pass on this machine: a black base of the exact length, each clip trimmed at its in-point and laid at its timestamp, held on its last frame if it outlives its media; audio trimmed, padded, delayed and mixed; text and the real logo burned in for exactly their windows. The remote compose stays as the fallback on a machine without ffmpeg.

verified on the NBA cutmeasured
duration27.533 s · exact
voiceover pieces at zero lag6 / 6
music continuous · correlation0.94 – 1.00
video vs source at the true in-point · SSIM0.86 – 0.99
where the timeline is emptyblack · exactly
The export dialog after a render: length 34.9 s, frame 1920 by 1080 at 30 fps, whole timeline, 8 picture clips, 2 sound clips, 12 overlays, and the finished Welcome tour video playing inline
The export dialog, after a render. Length, frame, range, picture and sound clip counts, overlays — then the finished file, playable in place. This one is the Welcome tour below: 8 picture clips, 2 sound clips, 12 overlays, 34.9 s.
Revise, by regionthe promise can't rest on the model

Circle a part of a still or a frame, say what it becomes; only that part changes.

An inpainting model does not honour a mask the way the sentence implies — it resamples the whole frame, and one squashed everything to its 32-pixel grid on the way through. So every revision is composited back through the mask at native size: outside the circle stays pixel-identical because the pixels are the originals. Video merges through a finite matte; a frame can be revised and reanimated; 9 revise-only endpoints sit behind one dialog. Hold to compare.

The revise dialog: a lasso drawn around one player in a generated still, the model set to FLUX Fill Pro, and the instruction 'swap the jersey for a classic white home kit, keep the pose'
Lasso one player, type the change. “Swap the jersey for a classic white home kit, keep the pose.” Everyone outside the circle is untouched — not nearly, exactly.
The studio demonstrates itselfa shipped project that opens on first launch

The first thing a fresh install shows is a 35-second spot the studio made about the studio: six clips and two stills generated in-app, a music bed and a voiceover generated on the audio lanes, twelve overlays and the VIOLET mark on the GFX lane — cut on the same timeline, rendered by the same export, and pinned in the project menu with a reset. It opens once, and never hijacks an install that already has work in it.

00:00.00

Welcome to VIOLET Studio. “A video studio where stills, motion, voice and music are generated right inside the timeline. Describe a brand, and VIOLET learns how it looks — its palette, its type, and what it never does — then folds that into every shot.” The voice is generated; the captions were hand-timed to its speech runs; the logo swaps to a dark variant over bright stills and crossfades in 250 ms.

34.946 s
8 picture clips · 2 sound clips · 12 overlays
VIOLET's own brand — nothing to clear

Paper trailfour more frames from the running app
The generate panel beside the preview: model options built from the model's schema — safety checker, frames, seed, duration, resolution, camera lock, aspect ratio — and an estimated cost of $0.18 above the Generate button
The form builds itself. Every model wants different parameters, so the form is rendered from each endpoint's own OpenAPI schema — zero UI code per model — and prices the run before it starts. Verified overrides beat the published schema where a provider advertises options its live API rejects.
A frame from an adidas session: a neon alley scene regenerated after the brand audit asked for the brand's golden yellow and crimson red in the lighting
The audit, caught mid-loop. From an adidas session (19 Aug): this frame's prompt carries the compliance verdict's own words — “Introduce the brand's golden yellow and crimson red into the lighting or sign elements” — because “apply fix & regenerate” folds the audit's suggestion back into the next prompt.
The backup and restore dialog: export and import buttons, then a list of snapshots of the NBA project, one roughly every minute of change, each with a Restore button
Nothing lost. A snapshot is written to disk every 60 seconds while something changes. The rule was learned the hard way: a demo project once existed in exactly one place, and that place was a browser profile.
A dialog at boot: two projects are missing from this browser, NBA with 18 clips and 72 media, and The morning brew with 5 clips and 8 media, each with a Restore button
Offered back at boot. A project the browser has lost but the disk still holds is put back exactly as it was at its last snapshot — or not, and the older snapshots stay where they are.
Built under a milestone gateand a discipline borrowed from elsewhere in the Lab

Work proceeded in order, and a milestone was not done until its acceptance checks passed — the same SPEC discipline as QUARRY and DETOUR. The criteria are the interesting part, because they are all observable: “a 17s edit shows a ~17s ruler”; “pick a brand → generate 3 stills → visibly consistent styling”; “a stranger can npm i && npm run dev with no keys and see a working timeline”.

Every fix is logged with what broke, why, what changed, and the measurement that verified it — 84 entries at the last count. One milestone was reworded mid-flight and signed off: brand research moved from “an OpenAI key server-side” to the keyless CLI, and the doc records that it changed and why. A late discovery got its own line too: routing generation through a provider's prepaid credits turned out to be impossible as designed, because its API bills a separate wallet from its subscription — blocked, written down, then built with the constraint understood rather than discovered twice.

// the same profile that shaped the prompt grades the result
auditMedia(image, brand) → { paletteScore, styleScore, verdict, issues, fix }

// runs automatically, but only when a brand actually shaped the generation,
// so unbranded work costs nothing extra. Failures are swallowed on purpose:
// an audit is commentary, and must never mark a good generation as broken.

What genuinely works

The editor. Six lanes, frame-exact cut / trim / slip / move, magnetic gap-healing, audible scrubbing, real waveforms, undo throughout, and a preview that renders the in-point it exports.

The brand loop. Research → the real logo → influence dial → generation → vision audit → apply-fix, all inside one window.

The export. A local, measured composer; the file is the cut. Snapshots every minute, recovery at boot, portable archives.

The key posture. No key is ever sent to or read from the browser; the client is proxy-only.

The honest edge

It runs locally, by design. Brand research needs the Claude CLI on the machine and generation needs a provider key in server env. This is a creative tool on a workstation, not a hosted product.

The audit grades images only. Video frames are not extracted for grading.

Logo fades are not burned into exports. The preview crossfades; the export cuts hard.

164 TypeScript files32,823 lines15 API routes 72 commits7 working days

Next.jsReactzustandTanStack QueryRemotion IndexedDBWeb Audionative ffmpegfal.aiHiggsfield Claude Code CLIWikidataschema-driven UIvision auditserver-proxied keys
Scope

Project Elements

Editor ArchitectureMulti-Track TimelineBrand Research AutomationPrompt CompositionVision-Model QASchema-Driven UIServer-Proxied KeysFrame-Exact Export

Have a Project in Mind?

Animation direction, VFX and creative technology. Message me on LinkedIn or get in touch.