@generativereality/content-studio 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ # The pick: showing work so a person can choose
2
+
3
+ The pick is where most studio work has failed: not because the work was bad, but because the
4
+ person could not choose between options. They couldn't open the page where they were, they couldn't
5
+ tell the samples apart, or they were asked to assemble a combination. A review page is part of the
6
+ product.
7
+
8
+ ## One best, then a few real alternatives
9
+
10
+ - **Lead with your recommendation, finished.** The first thing on the page is the version you would
11
+ post, complete (title, body, image, first comment, everything), ready to approve as it is.
12
+ - **Then two or three alternatives**, each one also complete, and each different in **angle or
13
+ format**, not in wording. One line under each says how it differs and when it would be the better
14
+ choice.
15
+ - **Never a parts menu.** A page of 19 titles, 5 bodies and 4 images asked one person to compose about
16
+ 380 combinations. They answered by asking whether it was a bug. If the titles matter, show the best
17
+ title on the recommended version and list three alternative titles beneath it, no more.
18
+ - **Keep the variations, out of the way.** All the hooks and angles you tried can go at the bottom
19
+ under a plain heading, for anyone who wants to look. The decision must not depend on reading them.
20
+
21
+ ## Make it reviewable where the person is
22
+
23
+ - **It opens on a phone.** Use the review mechanism the project has (a card with a review page, a
24
+ shared document, an artifact link). A local file path opens nowhere but on the machine that has it.
25
+ Put the full clickable link in the message.
26
+ - **Size:** each image under about 1.5 MB, and the whole page under about 6 MB, or phones drop it. Use
27
+ stills and short clips. Never embed a full-resolution master.
28
+ - **Label every sample** with what it is, the tool or model that made it, what it cost and how long it
29
+ took. Unlabelled samples cannot be judged: a reviewer who couldn't tell which voice was which in a
30
+ concatenated sampler could review none of them.
31
+ - **Side by side** for anything compared (two cuts, two thumbnails, before and after). **One player per
32
+ voice or clip**, never one long file with all the takes.
33
+ - **Show it as it will be seen:** a feed image at feed size, a vertical clip in a phone frame, a title
34
+ as it will look in the feed, with the first line of the body under it.
35
+ - **Numbered lists render as lists**, and verdicts and notes stay separate from the work itself.
36
+
37
+ ## Reviewing generated media: board, shoot, review
38
+
39
+ - **Board before shoot.** No paid clip before the board is approved (`generation.md`).
40
+ - **Re-render the review after every shoot**, including a small one, before reporting. A take nobody
41
+ has watched isn't a deliverable.
42
+ - **One review doc for the production, not one per round.** Retire superseded material out of it and
43
+ leave a short ledger of what went and why, so nothing is re-shot by accident; with two docs, the
44
+ person reads whichever they opened last.
45
+ - **Say what is wrong with each take in the doc, beside it.** The faults found are the review's output;
46
+ the clips are the evidence.
47
+ - **Import the prompts from the generation script; never retype them.** Guard the script's run loop
48
+ behind `main()` so the review builder can import its job table. A doc that claimed to extract its
49
+ prompts while carrying a hand-pasted copy drifted.
50
+ - Keep the markdown body as the source and let a build step embed the clips. Stage web-sized copies
51
+ somewhere persistent beside the takes, never `/tmp` (one staging folder was wiped mid-project).
52
+ Transcode small first (about 854 px wide, CRF 27).
53
+ - With a markdown renderer, wrap a `<video>` in a `<figure>` on **one line**: `<video>` isn't a CommonMark
54
+ block tag and `<figure>` is, and a newline inside the figure leaks the rest as escaped text.
55
+ - With `review-md` (`tools.md`): media needs `--allow-html` and `media-src 'self' file:` in the CSP, or
56
+ the players look normal and refuse every clip. Confirm in the tab by loading each element
57
+ (`x.load()`, then `x.error`), not by the player being visible. Heading ids carry no section number, so
58
+ list them before linking (`[...document.querySelectorAll("h2")].map(h=>h.id)`), and add `?v=N` to
59
+ reload a re-rendered file the browser has cached.
60
+ - **Verify the render, not its exit code:** count the missing-clip markers the builder writes (it
61
+ should write a visible one, never a gap), count `<video>` tags, grep for `&lt;figure` leaks, then open
62
+ it. **Prove an embed is current by hashing it,** not by mtime and not by opening a heavy page (evals
63
+ timed out on a 13 MB render): decode every `data:` payload, hash it, and match it to the current files;
64
+ one that matches nothing is stale. When the change under review is to the sound or grade, also measure
65
+ the embedded clip, because a current file can still be the cut before the fix.
66
+
67
+ ## The question
68
+
69
+ One question, one line: *"Post the recommended version?"*. Offer up to three one-tap answers that are
70
+ real answers (*"Post it as is"*, *"Use alternative B"*, *"Shorter"*). Always leave a free answer, and
71
+ always a way to say it is wrong, which is not one of the buttons.
72
+
73
+ ## After the answer
74
+
75
+ - Write the pick or the no, with the person's reason in their own words, into the lessons file.
76
+ - A reason like "sounds like AI" or "not good enough" is new material, not a request to polish: ask
77
+ what is off and what they would say instead (`voice.md` §critique).
78
+ - A preference is one person's taste, and a fault they saw or heard (choppy motion, a wrong accent, a
79
+ face that changed) rules a tool out for everyone. Record which kind it was.
80
+ - If they pick, make that version well. Don't ask again about what they have already decided.
@@ -0,0 +1,135 @@
1
+ # Search: pages that rank and are worth landing on
2
+
3
+ A page meant to be found has two jobs, and it fails if it only does one. It has to answer the query
4
+ well enough that a search engine or an AI assistant puts it in front of someone, and it has to be
5
+ worth that person's time once they arrive. Both jobs fail the same way: writing what we want to say
6
+ about our product, in our own words for it, on a query nobody types.
7
+
8
+ ## 1. The query is measured, never guessed
9
+
10
+ Choose the target query from measured volume (Google Keyword Planner, Search Console, Bing Webmaster
11
+ Tools), and write the measurement down with its date the same day, in a numbered file in the
12
+ project's research folder, because scratch files vanish. Keyword Planner without ad spend gives buckets
13
+ (10–100, 100–1K, 1K–10K, 10K–100K, 100K–1M): precise enough to choose with, too coarse to forecast with.
14
+ What has changed decisions:
15
+
16
+ - **People search the job, not the container.** An AI-prefixed phrasing ("ai expense tracker app")
17
+ has measured two to four volume buckets below its plain twin ("expense tracker app"), and in other
18
+ languages the AI-prefixed form often returns nothing. The AI belongs in the answer, not in the title.
19
+ - **People search the label and the product, not the idea.** A bank-statement label or a product name
20
+ carries thousands a month; an idea like "private budgeting" carries almost nothing. A page about an
21
+ idea needs a product or label word in its title.
22
+ - **A brand nobody searches can't carry a comparison page.** Check the brand's own volume first.
23
+ - **Volume isn't intent.** Read the results before choosing: a query whose top ten are all about fees
24
+ can't be won by a page about something else with the same words.
25
+
26
+ ## 2. Read the results page before writing
27
+
28
+ Open the query in a real browser and write down what is there:
29
+
30
+ - **Who owns the top three, and whether any small independent site ranks at all.** That second point
31
+ is the winnability test, and it is usually kinder than it looks.
32
+ - **What takes the click before the results.** Where an AI overview answers completely, a page that
33
+ only summarises loses, but **being cited is a way in that ranking isn't**: overviews often cite
34
+ small sites. Note the narrowing question the overview asks at the end; each split it offers is a
35
+ page.
36
+ - **People Also Ask, word for word.** They are the best outline for the page's sections. Put FAQ
37
+ schema only on questions the page visibly answers.
38
+ - **Freshness.** Results dated this week mean a maintained query; undated results mean an open one.
39
+ - **Intent.** "Personal expense tracker" turned out to be a spreadsheet-template query.
40
+ - **What the ranking pages do well:** a method section with numbers in it, a spec block with
41
+ identical fields per item, jump links, an FAQ, a visible date checked, named experts where real.
42
+ The best openings in one read of thirteen ranking pages were first-person failure stories, usable
43
+ only when the failure is real.
44
+ - **What they all leave out.** The gaps are where the page comes from.
45
+
46
+ ## 3. The bar: what does this page have that nobody else's does?
47
+
48
+ A page about a job **teaches the job in full**: the job in the heading, what makes it annoying, how to
49
+ do it yourself step by step, the named operators people search for in the detail actually verified,
50
+ then the offer to let the product do it. The call to action is gated; the page isn't. Someone who never
51
+ downloads anything should be able to finish the job with the page alone, and that is what makes it
52
+ worth ranking.
53
+
54
+ **Before writing, name the one thing on this page that neither the ranking pages nor the company's
55
+ own help pages carry.** If you can't name it, don't write the page yet. Sourcing every sentence from
56
+ the operator's own help pages is a reliable way to make a page that adds nothing to those pages. Kinds
57
+ of novelty that have worked:
58
+
59
+ - **A real run:** we did the job and wrote down what happened, including what went wrong.
60
+ - **A synthesis nobody has done:** the same questions asked of every option, in one table.
61
+ - **What the company doesn't say**, found by reading its pages against each other.
62
+ - **The consequence people act against:** a regulator's sentence that changes what to do.
63
+ - **Something made from the reader's own kind of data:** a real household's numbers (anonymised,
64
+ presented as "one household", never identifying anyone).
65
+
66
+ A real run on people's own data is presented as **one household's, never as "ours"**, with every
67
+ identifying detail left out (whose profile, children, names, places). A run on the author's own account
68
+ told in the first person is different, because it concerns nobody else. A paraphrase of the company's
69
+ help pages may sit on the page as the reference half, short and saying whose rules they are, after the
70
+ novel part and never as the reason the page exists.
71
+
72
+ Three tests before publishing:
73
+ 1. **Delete the product paragraph.** Is the page still worth reading?
74
+ 2. **Can someone actually follow it?** Real steps with the real friction: which button, which export,
75
+ what goes wrong the first time.
76
+ 3. **Does it answer the awkward question too**, honestly, even when the honest answer sends the reader
77
+ elsewhere?
78
+
79
+ ## 4. Facts from the primary page, quoted and dated
80
+
81
+ - The vendor's, operator's or regulator's own page is the only acceptable source; directories and
82
+ review sites are often wrong or stale (three of sixteen listings checked in one directory were).
83
+ - Quote the sentence and keep its address and the date read beside the draft; a footnote lets a
84
+ reader check it.
85
+ - "Not stated on their site" is a finding, and isn't the same as "no".
86
+ - **Never invent a number for illustration.** If it wasn't measured, write the sentence without it.
87
+ - A page whose facts age shows the date they were checked, where the reader sees it.
88
+
89
+ ## 5. The opening is for the reader
90
+
91
+ The first lines say who the page is for and answer the question, then date the facts. It is not a
92
+ note about our research. Headings are plain and say the thing.
93
+
94
+ ## 6. Comparison pages
95
+
96
+ A title like *"X or Y?"* tells the reader they are choosing. When the two don't do the same job, say
97
+ so: frame it as "who does what" (tables like "a month with both", not "a month with each"), with no row
98
+ leaning our way and at least two rows where we answer "no". Record per comparison whether the other
99
+ product competes, and let a test pin the page's framing to it. A comparison with no real difference in
100
+ it is a pitch with a new heading.
101
+
102
+ ## 7. Writing for AI assistants as well
103
+
104
+ - In a study of generative engines (GEO, KDD 2024), adding quotations, statistics and cited sources
105
+ raised a page's visibility in AI answers the most, roughly 30 to 40%. An authoritative tone didn't
106
+ help, and keyword stuffing did worse than nothing. Answer the question in the first two sentences;
107
+ write self-contained, citable claims, each number with its source.
108
+ - Assistants search in long specific queries (about 12 words on average for one of them, 2026) and lean
109
+ on *best*, *review*, *vs*, *alternative* and the year. A title should contain the words such a query
110
+ would: on an "X alternative" query they cited small pages with *alternative* in the title and missed
111
+ pages titled "X or Y?".
112
+ - Answer each sub-question in its own paragraph or table row, because assistants lift the piece that
113
+ answers their part.
114
+ - Carry a `dateModified` (Article schema) from the date the facts were checked.
115
+ - Where assistants look matters more than markup: ChatGPT search, Copilot and DuckDuckGo use Bing's
116
+ index; one major assistant most likely uses Brave's, which has no submission console and finds pages
117
+ through links from sites it already indexes (forums, code hosts, directories); Perplexity has its own.
118
+ Ping Bing (IndexNow) after every release that adds or changes pages; a 403 on the first run after a new
119
+ key is key validation, so run it again. `llms.txt` exists but no major assistant has committed to
120
+ reading it.
121
+ - **Describe the product exactly**, because an assistant repeats it word for word. Never claim more
122
+ privacy or capability than is true.
123
+ - Search engines accept AI-assisted pages that add value and treat mass pages without it as spam.
124
+
125
+ ## 8. After it is live
126
+
127
+ Check the page serves (grep for a phrase only the new build has), is in the sitemap and isn't
128
+ blocked by robots.txt. A page waiting for approval must not ride along on someone else's release, so
129
+ keep it on its own branch. Search Console and Bing Webmaster Tools are the feedback, not opinion:
130
+ impressions with no clicks means a title or answer problem; neither means not indexed or not
131
+ competitive. Search Console data lags about four days and covers Google only. Bing Webmaster Tools'
132
+ AI Performance report shows Copilot citations and the grounding queries behind them, the nearest thing
133
+ to a list of what assistants search. Which crawlers actually read a page is in the CDN's request logs
134
+ by user agent (OAI-SearchBot and ChatGPT-User, ClaudeBot, PerplexityBot, DuckAssistBot). Expect nothing
135
+ for weeks on a young domain: the first pages are an investment in a corpus, not a test of one page.
@@ -0,0 +1,112 @@
1
+ # Stills: thumbnails, covers, carousels, lead images, collages
2
+
3
+ **Code sets the words, AI makes pictures and decoration, and no generative model touches a real photo
4
+ it isn't explicitly restoring.** That rule decides most of this file. Every model asked to "arrange" or
5
+ "restore" photos redrew faces a little. Every current model spells a headline correctly, so the
6
+ reason to set type in code isn't spelling: one word can change without re-rolling the picture, and
7
+ the brand stays exact.
8
+
9
+ ## The idea comes from the subject
10
+
11
+ Seven image models answered one thumbnail brief with the same layout (condensed headline left, phone
12
+ in hand right). The only thumbnail that looked designed was built from the product's own output: a
13
+ transcript with its filler words struck out. **The sameness is the slop.** Start from something real
14
+ and specific to the subject: its own output, its own screen, a real photo, a real number. Write
15
+ the idea down before opening any tool.
16
+
17
+ ## Recipe A: layout in code (the default)
18
+
19
+ 1. One `brand.css` with tokens and fonts. Bundle OFL fonts locally, with no font CDN at render. **Never
20
+ name a font family `Serif` or `Sans`**: unquoted, those are CSS generic keywords and Chrome silently
21
+ draws Times. Give custom families unique names.
22
+ 2. One HTML file per frame, sized exactly to the output.
23
+ 3. Render with headless Chrome: `scripts/render-frames.sh <out-dir> <w> <h> <frame.html>…`. It uses the
24
+ installed Chrome or Chromium, at 0.1 to 1 s a frame, and runs the same on Windows.
25
+ 4. Gate: contrast and anti-slop (`npx impeccable detect <folder>` caught a real 3.2:1 grey), then
26
+ **open every PNG and look**. Faults found only by looking: a title on a busy patch, a note behind a
27
+ print, a cropped footer, a caption box over the screenshot, an accent dot vanishing on an
28
+ accent-coloured slide.
29
+ 5. Measure margins in code rather than by eye; uneven ones (71/61/61/52 px) read as sloppy.
30
+
31
+ Without a browser (a server, a Worker), use Satori with resvg (MPL-2.0, fine to bundle). It needs static
32
+ TTF fonts, an explicit width on every text box (or text won't wrap), and footers pinned with absolute
33
+ positioning.
34
+
35
+ ## Recipe B: a generated background under typeset text
36
+
37
+ Ask the model for the picture only, and end the prompt with *"No printed matter, no writing, no labels,
38
+ no logos"*. Local models write gibberish on any paper or sign in the scene without it. Then use the
39
+ image as the CSS background in recipe A. See `generation.md` for models.
40
+
41
+ ## Carousels
42
+
43
+ - Slide 1 carries the hook; each later slide is one item with its proof; the last slide is the next
44
+ step. A list that overflows the screen gets saved.
45
+ - **Model-drawn carousels:** make slide 1, then pass it as the reference image for slides 2 to 5 with
46
+ *"use the attached slide 1 as the exact design system"*, in parallel. Ask for 2K, because 1K returns
47
+ 928 × 1152, below Instagram's 1080 × 1350. One run of five cost $0.17 and took about 30 s (Nov 2026).
48
+ The output is a flat bitmap, so a typo fix is a re-roll and the re-roll will differ. Prefer recipe A
49
+ for anything with text that may change.
50
+
51
+ ## Lead images of a real product
52
+
53
+ Use a real screenshot or a real recording frame, framed and captioned in code. Never mock up a screen,
54
+ and never let a model "improve" one. If the screenshot shows a stale or wrong state (another brand's
55
+ name in the header, a debug banner), get a new one; don't paint over it. Blur real people's names
56
+ in it. Test the image at the size the feed shows it (often about 400 px wide on a phone).
57
+
58
+ ## Collage (the "paper reel")
59
+
60
+ A layout of 3 to 8 pictures on a generated paper, as a still and as a 5 to 10 s film. People find it
61
+ very shareable.
62
+ 1. **Paper,** one per output size. Prompt: *"decorations only along the outer 12% of the frame; the
63
+ whole centre plain, evenly lit; top-down; no photos, screens, text, people or hands"*. A hosted model
64
+ at 2K costs about $0.05 and takes about 30 s; a local one is free but slower, with a less clean
65
+ centre (put the title on a card). **The paper's mood is the choice**, and the layout follows it.
66
+ 2. **Spec:** theme (notebook, desk, cork, scrapbook, album, journal), layout (scatter for many photos,
67
+ grid for calm, hero with satellites, timeline for dates), motion (drop, fade or slide in; camera
68
+ drift, push or pan).
69
+ 3. **Layout in code:** take a decoration mask from the paper; detect faces (YuNet, MIT) so no crop cuts
70
+ one; seed and score placements against decoration and overlap; put notes where they cover nothing
71
+ and the title in the least decorated spot. Make the still from the end state, not the last frame
72
+ of a camera move. Step the film frame by frame into ffmpeg.
73
+ 4. **Accept only if** no face is cut, the title and prints overlap decoration by less than about 15%,
74
+ you have looked at the still, and the film decodes.
75
+ 5. A person's own photos are never altered and never sent to a hosted model without their yes. Make
76
+ the paper locally or reuse one.
77
+
78
+ Faults seen: a still taken from a zoomed last frame crops the margins; strings or arrows drawn across
79
+ interface text make it unreadable; the hero print got buried under the others until grid cells fixed
80
+ it. Feeding all the photos into an image model to "arrange" them redrew every face.
81
+
82
+ ## Thumbnails
83
+
84
+ Note the conventions of the outliers in the niche (colour, amount of text, face or no face,
85
+ composition) and decide on purpose. The title and the thumbnail are one promise, so don't repeat the
86
+ title in the image. Text readable at phone size. Make two or three that differ in idea, not in colour,
87
+ and let YouTube's Test & Compare decide, not taste.
88
+
89
+ ## Covers
90
+
91
+ A cover makes someone stop and feel the claim. An illustration that carries the argument beats a data
92
+ chart; charts belong in the body as evidence. For a personal album or keepsake, the person's untouched
93
+ photo with a well-set title is usually the one to use. A painted version made from it is a separate,
94
+ labelled option.
95
+
96
+ ## Restoring an old photo (a person's own, at their request)
97
+
98
+ - **Most faithful:** a top hosted editor with *"Do not change any face, expression or pose"* (Nano
99
+ Banana Pro, about $0.15 at 2K, Nov 2026). **Best value:** Nano Banana 2.1 at 2K, about $0.05 (it slightly
100
+ enlarged a child's eyes). **Best free:** a local FLUX 3 Klein edit, then Real-ESRGAN.
101
+ - **Always compare original and result side by side at the faces** before accepting.
102
+ - **Upscale only after cleaning:** Real-ESRGAN on a raw scan sharpens the scratches as detail.
103
+ - **Colourising invents facts** (eye and hair colour). Offer it as a separate, labelled version, never in
104
+ place of the original.
105
+ - Don't use: GPT Image (reframed the photo and redrew two new children), Kontext's restore app
106
+ (colourised unasked, with modern faces), GFPGAN and CodeFormer (kept every scratch and put blue eyes
107
+ into a black-and-white photo; CodeFormer is also non-commercial), OpenCV inpainting (faces to mush).
108
+
109
+ ## Fixing a bad generated detail
110
+
111
+ Remove the object rather than asking for a repair. A backwards hand was fixed by *"lying on the
112
+ surface, no hands"*. Asking a model to repair the hand kept it backwards and re-laid out the whole image.
@@ -0,0 +1,26 @@
1
+ # Tool skills this one routes to, and where it overrides them
2
+
3
+ content-studio holds the process and the craft. For a tool's own mechanics, read that tool's skill
4
+ when it is installed. Where a tool skill's defaults conflict with the rules here, **this skill wins**,
5
+ because its rules protect the person (cost, consent, nothing going out). Those conflicts are listed
6
+ below.
7
+
8
+ | Tool skill | Use it for | Overrides from content-studio |
9
+ |---|---|---|
10
+ | `hyperframes` (+ `hyperframes-cli`, `hyperframes-registry`) | HTML-to-video: the render contract, motion craft, captions, the registry of ready-made blocks (transitions, titles, charts, maps). Search the registry before hand-building any named visual. | Telemetry off (`npx hyperframes telemetry disable` or `HYPERFRAMES_NO_TELEMETRY=1`); `init --skip-skills` so it doesn't write skills into the person's global folder; **never** its `publish`, `cloud`, `billing` or `feedback` commands, and no cloud render, without the person's yes; render only after the person picked (the pick in `review.md` replaces its own approval step) |
11
+ | `media-use` | sourcing media for a HyperFrames video: catalog music and images, logos (never redrawn), cutouts, colour treatments, transcripts | **Always show what a run cost** (it says never to); local and free first where quality is equal, not only when asked; no HeyGen-hosted generation of a real person |
12
+ | `review-md` | rendering a markdown review page to one HTML file, or an artifact | media pages need `--allow-html` and `media-src 'self' file:` in the CSP, or `<video>`/`<audio>` are silently refused; verify a media page by decoding each embedded file, because an automation browser may load no audio |
13
+ | `browser-automation` | reading a place's rules and outliers in the person's signed-in browser; preparing a post in its form for the person to send; screen-recording a web app | read-only unless preparing a draft; never press post, send or submit |
14
+ | A personal voice skill or voice notes (whatever the person keeps) | their voice, their banned words, their lessons | it overrides `voice.md` wherever they differ |
15
+ | A strategy or growth skill (positioning, channels, bets) | what to say, to whom, where and why now | content-studio never overrides its decisions; it raises doubts in the brief |
16
+
17
+ ## Scripts in this skill
18
+
19
+ The `.sh` scripts need bash: on Windows, run them from Git Bash (which Claude Code on Windows uses).
20
+ `spend.mjs` needs only Node.
21
+
22
+ | Script | What it does |
23
+ |---|---|
24
+ | `scripts/voice-sweep.sh <file>` | the mechanical machine-prose sweep: em dashes against paragraphs, short fragments, banned words, negative-parallel shapes, drumbeat lines. Exit 0 always; read the hits |
25
+ | `scripts/render-frames.sh <out-dir> <w> <h> <frame.html>…` | renders HTML frames to PNG with the installed Chrome/Chromium headless (macOS, Linux, Windows Git Bash) |
26
+ | `scripts/spend.mjs add|total` | the per-piece spend ledger (`spend.jsonl` in the piece's folder) |
@@ -0,0 +1,155 @@
1
+ # Video: demos, explainers, reels, launch clips, memory films
2
+
3
+ Video prefers pieces that combine real footage, stills and animation over any single one. A narrated
4
+ slideshow is the least exciting format there is, and to many people it reads like the phone's own
5
+ automatic "memories" feature.
6
+
7
+ ## The pipeline
8
+
9
+ 1. **Script and shots first,** as one timeline file (JSON): shots, text, captions, focus points,
10
+ music, end line. The agent's job is writing the timeline; any renderer can consume it.
11
+ 2. **Voice (if any) and its transcript** for word timings (`audio.md`).
12
+ 3. **Render:** for HTML-based motion, use **HyperFrames** (HTML + GSAP rendered to MP4 through Chrome
13
+ and ffmpeg, Apache-2.0); read its own skills for the render contract (`tools.md`). For a
14
+ zero-dependency route, use Pillow + ffmpeg. Remotion is the fastest renderer, but it is free only
15
+ for up to three employees and doesn't normalise loudness. The renderers look nearly identical on the
16
+ same timeline. HyperFrames' text came out slightly clearer.
17
+ 4. **Check:** `hyperframes check` (it caught captions too short to read, an end card too short for its
18
+ credits, overlaps, type under 28 px and low contrast), a contact sheet of frames to look at, then
19
+ the encode checks below.
20
+
21
+ A timeline shape that has worked well for photo-and-clip films:
22
+
23
+ ```json
24
+ {"width":1080,"height":1920,"fps":30,"style":"calm|cut",
25
+ "shots":[
26
+ {"photo":"<file>","seconds":3.2,"motion":"push|pull|pan-left|pan-right|none","focus":[0.5,0.4],"caption":"…","transition":"cut|dissolve"},
27
+ {"clip":"<file>","from":1.2,"seconds":2.5,"sound":true,"caption":"…"},
28
+ {"card":{"title":"Day 2 · Saturday","lines":["…"]},"seconds":2.0}],
29
+ "music":"<file>","music_db":-10,"end_line":"…"}
30
+ ```
31
+
32
+ Calm style pushes 1.00→1.09 with dissolves; cut style punches in 1.04→1.18 with hard cuts. A text that
33
+ needs longer than its shot's seconds is a warning to fix, not a render to ship.
34
+
35
+ ## Cut craft
36
+
37
+ - **Open on the most specific moment,** never a title card.
38
+ - **A real person's recorded voice sets the order** (a memo, an interview, a founder talking): edit
39
+ to the order of the telling, not to the timeline, and use the word timestamps to put each shot on
40
+ the word that names it.
41
+ - **Cut on the words.** Hard cuts on the narrator's or speaker's words, using word timestamps.
42
+ - **Punch in** toward the face or the subject (1.04 to 1.18).
43
+ - **Real sound from real clips:** the loudest 2.5 s window of a clip, one to three times a film, ducked
44
+ under any voice. Never use a performance's music.
45
+ - **One label per run of shots** (a day and place on a dark pill). Per-shot labels stack up and become
46
+ unreadable in fast cuts.
47
+ - **Index and stats cards** (*"Day 2 · Saturday · harbour, market"*, counts computed from the data),
48
+ and an **itinerary map** where the route matters: the camera follows the route, solid where there
49
+ are pictures, dashed and captioned where not.
50
+ - **For what wasn't filmed:** an index card, a cut scene done properly in motion design, or generated
51
+ stills in one style. Crude drawn shapes look terrible.
52
+ - **End on a line that lands,** or a face, or a line addressed to whoever the film is for.
53
+ - **Lengths:** social 35 to 45 s, product 45 to 55 s, scrapbook 30 to 40 s, calm slideshow 50 to 70 s.
54
+ - **Offer different styles** (social, product, scrapbook, calm) from the same material, and say which
55
+ you'd pick and why. The person's favourite changes with the material.
56
+
57
+ ## Showing a real product
58
+
59
+ Every frame is the real product. Record the real web app with Playwright and a Chrome DevTools
60
+ screencast (3× device pixel ratio, about 45 fps, recorded in real time), or record a native app with the
61
+ OS screen recorder, then compose the result with punch-ins and captions. Best: write the demo screen as
62
+ a page whose state is a pure `render(t)`, so the same page can be recorded live and seeked frame by
63
+ frame by the renderer. Caption pills must not cover the rows you are showing during a punch-in.
64
+
65
+ Recording a native app on macOS (four takes once captured the wrong thing without one error):
66
+ - **Use `screencapture -v`, not ffmpeg `-f avfoundation`.** avfoundation records whoever is on the
67
+ console whatever user it runs as, its device numbers differ per session (one session's "screen" was
68
+ the other's camera), and its feed froze while the file kept growing.
69
+ - **`screencapture -v` writes the file only when `-V <secs>` runs out; an interrupt saves nothing.**
70
+ Record in short parts (`-V 60`) in a loop and stop by letting the part in flight finish.
71
+ - **Prove the picture moved:** hash the menu-bar clock region of each part's first and last frame. A
72
+ frozen capture otherwise looks like a quiet screen.
73
+ - **Address footage by wall-clock time,** and have whatever drives the app write a UTC event log, so a
74
+ cut is written against events and every on-screen line can be checked against what happened.
75
+ - Background tabs and covered windows aren't on camera: lay out the windows before the take, with the
76
+ driven browser tab the one showing.
77
+ - **Compose screen recordings in a renderer with sub-pixel transforms,** not ffmpeg `zoompan`, which
78
+ moves in whole-pixel jumps (still for 6 or 7 frames, then a jump): judder on interface text. Master
79
+ at 4K, crop as clipping boxes, push in with a CSS transform, set captions as real text. Remotion
80
+ writes full-range `yuvj420p` / bt470bg; convert to tv-range bt709 for deliverables. A zoomed-out
81
+ recording at 720p leaves interface text at about 7 px.
82
+
83
+ ## Captions
84
+
85
+ - **Caption text comes from the script; timing comes from the transcript,** zipped word by word by
86
+ position. Whisper writes "That's spinach" for "That spinach?" and misspells names, so its text is never
87
+ the caption.
88
+ - **Reading time** at least 0.5 s + 0.3 s a word; type at least 28 px at 1080 wide; contrast at least
89
+ 3:1. White on light images measured 1.4 to 2.3:1, so give them a soft dark backing.
90
+ - Bold word-by-word captions for social cuts; phrase captions for calm films. 17 to 20 characters a
91
+ second is a comfortable pace.
92
+ - **Over a voice, a caption is on screen exactly while its line is spoken.** A separate reading-speed
93
+ floor (15 characters a second) made every voiced cut drag and made a faster voice change nothing. A
94
+ text-only cut of the same film takes the voiced cut's timings.
95
+ - Translated captions with no voice: spread the timing by word length.
96
+
97
+ ## Crops
98
+
99
+ - 9:16 from landscape: set a focus point per shot (faces and saliency), or the subject is lost. Set it
100
+ on the subject of the line being said: a portrait crop once cut someone out of their own goodbye.
101
+ - A group wider than the slice is shown whole over a blurred, darkened copy of itself. Never letterbox.
102
+
103
+ ## Motion that survives a seek
104
+
105
+ HyperFrames renders by seeking. GSAP suppresses `call` and `onUpdate` on a seek, so counters are
106
+ stacked numbers switched with zero-length sets, and flicker is precomputed from a seed. Move with
107
+ transforms, not `top` (which snaps to pixels); use `svgOrigin` in SVG; never overlap tweens on one
108
+ element; start overlays hidden.
109
+
110
+ ## HDR phone clips
111
+
112
+ iPhone clips are HDR (HLG), and some renderers then promote the whole film to HDR at a fraction of
113
+ real-time speed. Tone-map to SDR BT.709 first (`avconvert --preset Preset1920x1080` on a Mac in about
114
+ 2 s a clip; ffmpeg `zscale` + `tonemap` elsewhere, which needs zimg), then render as SDR.
115
+
116
+ ## Encode and verify
117
+
118
+ - Master: x264 CRF 18 at 1080p. Chat copy: H.264 720p at most about 15.5 MB. HEVC 1080p is about 10 to 16 MB.- Always `-movflags +faststart`, or a browser downloads the whole file before playing a frame. A `-c
119
+ copy` concat doesn't inherit it, so name it again, and check the atom order (`moov` before `mdat`)
120
+ rather than trusting the flag was passed. Ask what each intermediate is for: a clean master that
121
+ existed only as a pre-burn concat had no faststart and no home.
122
+ - Loudness: −14 LUFS for social, −16 to −18 for a quiet film, true peak −1.5 dB (`audio.md`).
123
+ - **Verify by decoding, not by exit code:** `ffmpeg -v error -i out.mp4 -f null -` prints nothing when
124
+ the file is sound; a killed render leaves a file of the right size that is truncated. Run a backgrounded
125
+ build under `python3 -u`, or its log stays empty and reads as a hang. Transcribe the finished mix once
126
+ more.
127
+ - **Count frames; never read `Duration:`** (it reported 7.62 s and 7.63 s for parts a whole frame
128
+ apart). Count with `ffmpeg -v error -i f.mp4 -map 0:v -f null - -progress /dev/stdout`. Three causes
129
+ it hides: `-t` before `-i` cuts at a packet boundary (after the filter it is exact); a video-only part
130
+ rounds differently from one with audio, so build every part with the same command, silent track
131
+ included; and `-shortest` with `-c copy` drops frames. Use `-shortest` only for a truly unbounded
132
+ input (a looping bed, `anullsrc`).
133
+ - Some ffmpeg builds (Homebrew 9.x, Remotion's bundled one) have no `drawtext` and no subtitles filter.
134
+ Text comes from the HTML layer or Pillow, never from ffmpeg. An app-bundled ffmpeg that has `drawtext`
135
+ may lack a fontconfig configuration (pass `fontfile=`), and with a variable font a newline inside one
136
+ `textfile` draws a missing-glyph box: one `drawtext` per line.
137
+ - **The poster frame is a deliverable,** with its own build step. Take it from the captioned master,
138
+ and derive the timestamp from the subtitle cue whose line it should carry, so retiming the line moves
139
+ the poster. Then check the page uses it: a site may mount its own local still, so re-uploading changes
140
+ nothing a visitor sees (read the page's `poster=` attribute).
141
+
142
+ ## Publishing the file
143
+
144
+ - **Serve a draft film at a stable URL** and overwrite it; mint a dated copy only when a published embed
145
+ must not change underneath it. Check the CDN's caching before building around it: an overwrite may
146
+ be live at once.
147
+ - **Verify an upload by hash, and know when the ETag can't answer.** For a single-part S3 or R2 upload the
148
+ ETag is the MD5; above the multipart threshold (about 8 MB for `aws s3 cp`) it ends in `-<parts>` and
149
+ never matches. Download and hash instead; don't recompute a multipart ETag, which depends on a
150
+ configurable chunk size. Four perfect uploads once reported as failures this way, and a check that
151
+ cries wolf on the normal case gets ignored on the real one.
152
+ - **"Is this new?"** When someone reports a fault in a cut they approved before, fetch the published
153
+ version and compare before reasoning about recent edits. Validate the comparison first: a correlation
154
+ at a guessed offset read 0.02 ("different files") for identical audio. Compare windowed RMS, and scan
155
+ for the alignment if you correlate.
@@ -0,0 +1,115 @@
1
+ # Voice: the person's words, and prose that doesn't read as machine-made
2
+
3
+ Readers now assume low-effort text is machine-made and trust it less. Merriam-Webster's word of 2025
4
+ was *slop*. In a 2025 survey of 3,000 US adults, trust in a piece fell by nearly half when people
5
+ believed AI made it, whether or not it did (Raptive). Some places ban generated text outright (Hacker
6
+ News). So voice is the first craft problem, not the last polish.
7
+
8
+ ## 1. The person's material first
9
+
10
+ Before the first draft of anything published under someone's name:
11
+
12
+ 1. **Ask for their material, not the copy.** One question with no draft attached, built from what you
13
+ found: *"What did you think when you saw the first real sign-up?"*, *"Which of these three threads
14
+ would you actually reply to, and what would you say?"*. Never *"what should the post say?"*. Say a
15
+ spoken answer is welcome and that a minute is enough.
16
+ 2. **Build from their answer.** Keep their phrases, their order of telling and their opinion. Cut and
17
+ arrange; don't paraphrase into smoother words. On the review page, mark which lines are theirs.
18
+ 3. **Look for their written voice.** Their published posts, a voice notes file, their lessons from
19
+ earlier picks, and any voice skill they keep for themselves. Read those before writing, and follow
20
+ them over this file wherever they differ.
21
+ 4. **Name the sources** of the person's material beside the draft (which answer, which memo, which
22
+ earlier post), so the next version can go back to them.
23
+
24
+ A piece the person didn't shape isn't ready for review, however well it scores.
25
+
26
+ ## 2. The critique is new material {#critique}
27
+
28
+ When the person says it sounds like AI, misses the point, or asks how this is good, **do not reword
29
+ the same draft**. Answer their question honestly (including *"it isn't, and here is why"*), ask what is
30
+ off and what they would say instead, and rebuild from their reply. After a piece has been sent back
31
+ once, offer two or three one-line directions or ask for their dictation; don't write another full
32
+ draft on your own.
33
+
34
+ ## 3. The tells
35
+
36
+ Machine drafts perform emotion instead of expressing thought: they build suspense, stage reveals and
37
+ punch up clauses, where a person talks through a thought and lets the content be the drama. Each of
38
+ these tells has killed a real draft. Rewrite the sentence; don't just swap a word.
39
+
40
+ 1. **Dramatic fragments and one-line paragraphs.** *"That's not what happened."* *"Free. Local. Yours."*
41
+ Carry the beat inside a full sentence.
42
+ 2. **Meta-narration.** *"Here's the part that…"*, *"Here is the trap"*, *"Let me explain"*, *"The part
43
+ nobody puts in these posts"*. Do the thing instead of announcing it.
44
+ 3. **Negative parallelism.** *"Not X. Not Y. Z."*, *"It's not about X, it's about Y"*, and the
45
+ two-sentence version *"X was not the total. It was the spread."* Say what the thing is.
46
+ 4. **Rule of three.** Triads for rhythm read as machine rhythm. Use two items, four, or a sentence.
47
+ 5. **Zinger endings.** Paragraphs ending on a quotable punch line. End with the thought completed.
48
+ 6. **Rhetorical question answered at once.** *"Is it faster? No."* Make it a statement. Real questions
49
+ to the reader are fine.
50
+ 7. **Em-dash punch-ups.** One dash per paragraph is the tell. Commas, parentheses and colons do the work.
51
+ When you remove a dash, re-read the sentence, because removing the pause can change the meaning
52
+ (*"the ones that were not — in a browser on your own machine"* became a sentence about something
53
+ else). Split the sentence or use a full stop. A dash doing comma or colon work isn't the tell; dashes
54
+ added for emphasis, about one a paragraph, are.
55
+ 8. **Uniform rhythm.** Every paragraph two to four sentences of the same length. Vary it the way speech
56
+ varies.
57
+ 9. **Escalation.** Everything world-changing. Plain understatement carries the extraordinary better.
58
+ 10. **AI vocabulary.** pivotal, delve, tapestry, testament, vibrant, boasts, foster, leverage (as
59
+ filler), landscape, intricate, meticulous, showcase, garner, seamless, unlock, supercharge,
60
+ game-changer, "in today's fast-paced world", "the reality is", and the sincerity tic ("to be
61
+ honest").
62
+ 11. **Copula avoidance.** *"serves as"*, *"functions as"*, *"marks"*, *"represents"* where *is* would do.
63
+ 12. **Elegant variation.** Rotating synonyms to avoid repeating a word. People repeat their words.
64
+ 13. **Trailing -ing clauses** (*"…, highlighting the importance of"*), emoji bullets, a bold lead-in
65
+ on every line, a summary at the end.
66
+
67
+ What reads as a person: long sentences that carry a whole thought, in speech order, connected with
68
+ *and*, *so*, *but*, *because*; concrete names, numbers and what actually happened; the writer's own
69
+ uncertainty said plainly; a dry aside; plain descriptive headings; an invitation instead of a call to
70
+ action; numbers stated once, precisely, without ceremony.
71
+
72
+ **Where this applies:** everything published under the person's name, and correspondence sent as them
73
+ too (a support request, a reply to a partner), where one staged sentence is a large share of a short
74
+ message and the register is plainer still. Release notes count: they drift into feature-marketing
75
+ voice, every item a small triumph with a beat at the end. Channels change the register (tighter on a
76
+ professional network, more instructional in a course), never the list of tells.
77
+
78
+ ## 4. The sweep, and why it isn't the check
79
+
80
+ Run `scripts/voice-sweep.sh <file>` over every text before review. It counts em dashes against
81
+ paragraphs, lists short fragments, and finds the banned phrases and the commonest negative-parallel
82
+ shapes. It is a net with known holes. Pages have gone out passing every grep while carrying two tells
83
+ in one sentence. A launch post sent back as "way too AI generated" came up almost clean: what gave it
84
+ away was its shape (a slogan argued with in the title, tidy labelled sections, a bullet list explaining
85
+ itself), which no grep sees. So, after the sweep:
86
+
87
+ - **Read it aloud as the person.** Would they say this sentence on a call? Rewrite any sentence you
88
+ can picture on a conference slide.
89
+ - **Count the shapes no grep finds:** how many paragraphs end on a punchy clause (more than one or two
90
+ means the prose is performing), and whether three paragraphs in a row have the same shape.
91
+ - **Three parallel full-length sentences** starting the same way (*"The … you meant to …"* three times)
92
+ are a fragment drumbeat even though each is long.
93
+ - **Negative parallelism split by a full stop** (*"X was not the total. It was the spread."*) and the
94
+ bare *"What it is missing is not A. It is B, C and D"* slip past comma patterns; the sweep has a line
95
+ for the first shape, and reading catches the rest.
96
+
97
+ The fragment list is candidates, not verdicts: brand names with a dot and abbreviations come back as
98
+ false positives, and a short fragment is sometimes right (a deliberate two-word line in an approved
99
+ hero). On `.svelte`, `.ts` or `.html` files the sweep skips comments, so a dash in a code comment doesn't
100
+ count as one in the copy.
101
+
102
+ ## 5. A document is not a log of itself
103
+
104
+ A post, page or guide reads as if written this morning by one author who knew everything in it. No
105
+ *"updated on…"*, *"this section used to say…"*, no editor's notes. A date that says how old a fact
106
+ is ("prices as read on 3 November") is a property of the fact and stays. A date that says when the
107
+ text was edited goes.
108
+
109
+ This holds for any document meant to be read through, internal plans and review packs included, not
110
+ only published prose. Most self-history wraps a warning a first-time reader needs, because they would
111
+ make the same wrong inference: **keep the correction, drop the autobiography.** Re-aim it from the
112
+ document's past to the reader (*"What this does not mean: X is not solved"*, not *"this section said X
113
+ was solved for a few hours"*). The one exception is an agent-facing operations file, which may name a
114
+ wrong version that escaped into other docs or habits, because someone may still hold it; if nobody does,
115
+ it is autobiography there too.