byForjaTools · Mockups

Mockups

The proof asset that rides with every Loom

One image per prospect, rendered from HTML, attached to the outreach email and shown on screen during the Loom. It exists for one reason: to prove AL looked at their business before asking for anything. Canva was retired on 09/08/2026, the render takes 3.2 seconds.

What it is, and what it is not

It is
A proof
Left panel: what their surface does today, factual, sourced, dated. Right panel: the month laid out with their own colours. It shows work already done, for free, before any ask.
It is not
A brochure
The moment it lists benefits and an offer, it becomes a sales sheet and gets thrown away like every other one. The offer belongs in a second asset, sent once the prospect replies.

The four hard rules

Rule 1
One surface, everywhere
If the left panel names a website defect, the right panel cannot propose a content system. The prospect reads it as "you talk about my site and you sell me Instagram". The Loom script must sit on that same surface too. This cost two rebuilds on 10/08/2026.
Rule 2
No measurable promise
No view counts, no growth multiples, no engagement percentages. One client served and no transferable data. Promise what is handed over, never what an audience will do.
Rule 3
Nothing internal on the frame
No Artist or Pilot archetype, no price, no market multiplier, no margin, no former client name. The prospect sees this asset.
Rule 4
Read in three seconds
It plays behind a voice in a 40 second video. Anything that has to be read has failed. When a block gets tight, remove a line rather than shrink the type.

The build

Everything lives in DD/byForja/Templates-Lancement/loom-mockups/ on AL's disk. Fonts are base64 baked into the master, so the render never touches the network and comes out byte identical on any machine.

master.template.html      the layout. Edit here, every prospect inherits.
build-master.js           bakes the woff2 fonts in. Run after editing the template.
render.js                 splices a config into the master and screenshots it.
configs/<slug>.js         one file per prospect. Everything specific lives here.
configs/archive/          superseded configs, kept.
out/                      the PNGs.

node build-master.js master.template.html fonts loom-mockup-master.html
node render.js configs/buckleather.js      one prospect
node render.js                             every config in the folder

The config fields

FieldRequiredWhat it does
company founder craft cityyesHeader and title. founder is never guessed: if no name appears on their own pages, open on the business name instead.
scopenoThe pill on the right. Says which surface the proposal covers. Defaults to "social media". Set it whenever the offer is not purely social.
observed.eyebrownoLabel above the left panel. Defaults to "What the site does today". Set it when the left panel observes something other than the website, so the panel and the scope pill agree.
observed.sourcenoWhat was actually opened, printed in the "Observed on" line. Defaults to the site. Same reason: cite the surface being judged.
observed.headline observed.points[]yesOne factual sentence, then 2 to 4 short observations. Never sneering: the problem is never their craft, it is the plumbing around it.
evidence.items[]yesMax 3 tiles in the browser mock. {type:"broken"} draws an empty frame, {type:"note",text} draws a label.
palette[]yes5 entries, derived from their real site. ink is "light" or "dark" and drives the text colour on the tile.
cells[]yesExactly 9. Roughly 4 Reels, 4 Photos, 1 Carousel, across about 6 recurring categories.
deliverables[]noThe "what you get" rail. Max 4 lines, under about 21 characters each. Facts of delivery only, never results. Absent field means no rail and no layout shift.
promise footNoteyesThe closing line and its small print. No invented numbers.

File naming

The prospect sees the attachment name in their inbox, so it carries the brand. render.js derives it from company: casing preserved, accents folded.

Buckleather-content-plan-byForja.png
My-Sweet-Sweet-World-content-plan-byForja.png
Mlles-Gateaux-content-plan-byForja.png

A copy is still written as out/<slug>.png, because the Mockup column in the prospects DB and other scripts point at it. Superseded versions keep a suffix: buckleather-v1-website.png, buckleather-v2-content.png. Never overwrite a version AL has seen.

Traps already paid for

The blue film on the palette

The .glow layer is position:absolute; inset:0 and paints byForja blue over everything below it in the stacking order. The grid cells escape it because they are positioned; the palette chips were not, so every chip rendered with a 24 percent blue film and showed colours that were not the prospect's, one row under a line promising them their own palette. Found 09/08/2026 on all 11 mockups. Any new element that carries a client colour needs position:relative.

The arrow that is not there

Outfit is subsetted to a unicode range that includes U+2191 and U+2193 but not U+2192. A literal right arrow silently falls back to a system font. Arrows are drawn in CSS.

Verify by looking, not by rendering

A render that exits 0 proves nothing. Open the PNG and check: fonts loaded and not fallback, palette chips exact against the config, founder name spelled right, nothing clipped, no internal vocabulary. Two of the three defects above survived a successful render.

Where it sits in the chain

Step 04 of the acquisition staircase, A.2 on the architecture page. It comes after qualification and pricing, before the Loom script, and the script must be written on the same surface the mockup proposes. Doctrine lives in Grammar 02 on Notion; this page is the operating manual.