luckiest-co 1.0.39 → 1.0.41
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/ad-creative/CHANGELOG.md +7 -0
- package/skills/ad-creative/SKILL.md +23 -4
- package/skills/ad-creative/references/generative-tools.md +6 -30
- package/skills/ads/CHANGELOG.md +9 -0
- package/skills/ads/SKILL.md +19 -9
- package/skills/ads/references/reading-google-ads-data.md +121 -0
- package/skills/ai-seo/CHANGELOG.md +11 -0
- package/skills/ai-seo/SKILL.md +21 -22
- package/skills/ai-seo/references/content-types.md +11 -0
- package/skills/ai-seo/references/linkedin-ai-citations.md +122 -0
- package/skills/ai-seo/references/platform-ranking-factors.md +32 -19
- package/skills/ai-seo/references/positioning-and-consensus.md +102 -0
- package/skills/coder/CHANGELOG.md +23 -0
- package/skills/coder/SKILL.md +16 -4
- package/skills/coder-brainstorming/CHANGELOG.md +9 -0
- package/skills/coder-brainstorming/SKILL.md +5 -3
- package/skills/coder-debugging/CHANGELOG.md +11 -0
- package/skills/coder-debugging/SKILL.md +15 -3
- package/skills/coder-executing-plans/CHANGELOG.md +9 -0
- package/skills/coder-executing-plans/SKILL.md +5 -3
- package/skills/coder-finishing-a-branch/CHANGELOG.md +9 -0
- package/skills/coder-finishing-a-branch/SKILL.md +7 -3
- package/skills/coder-guard/CHANGELOG.md +7 -0
- package/skills/coder-guard/SKILL.md +6 -6
- package/skills/coder-receiving-code-review/CHANGELOG.md +9 -0
- package/skills/coder-receiving-code-review/SKILL.md +5 -3
- package/skills/coder-shipping/CHANGELOG.md +9 -0
- package/skills/coder-shipping/SKILL.md +4 -4
- package/skills/coder-subagent-driven-development/CHANGELOG.md +9 -0
- package/skills/coder-subagent-driven-development/SKILL.md +5 -3
- package/skills/coder-writing-plans/CHANGELOG.md +9 -0
- package/skills/coder-writing-plans/SKILL.md +5 -3
- package/skills/cold-email/CHANGELOG.md +7 -0
- package/skills/cold-email/SKILL.md +15 -8
- package/skills/cold-email/references/follow-up-sequences.md +4 -4
- package/skills/cold-email/references/frameworks.md +5 -5
- package/skills/cold-email/references/personalization.md +10 -10
- package/skills/competitor-profiling/CHANGELOG.md +8 -0
- package/skills/competitor-profiling/SKILL.md +34 -9
- package/skills/competitor-profiling/references/tool-reference.md +141 -5
- package/skills/competitors/CHANGELOG.md +8 -0
- package/skills/competitors/SKILL.md +32 -5
- package/skills/competitors/references/content-architecture.md +6 -0
- package/skills/competitors/references/templates.md +3 -0
- package/skills/copy-editing/CHANGELOG.md +10 -0
- package/skills/copy-editing/SKILL.md +41 -9
- package/skills/copy-editing/references/checklist.md +11 -0
- package/skills/copywriting/CHANGELOG.md +11 -0
- package/skills/copywriting/SKILL.md +32 -4
- package/skills/copywriting/references/copy-frameworks.md +2 -3
- package/skills/copywriting/references/natural-transitions.md +4 -20
- package/skills/customer-research/CHANGELOG.md +8 -0
- package/skills/customer-research/SKILL.md +6 -3
- package/skills/directory-submissions/CHANGELOG.md +9 -0
- package/skills/directory-submissions/SKILL.md +21 -10
- package/skills/emails/CHANGELOG.md +8 -0
- package/skills/emails/SKILL.md +22 -3
- package/skills/emails/references/sequence-templates.md +3 -3
- package/skills/image/CHANGELOG.md +8 -0
- package/skills/image/SKILL.md +4 -4
- package/skills/image/references/ai-image-prompting.md +1 -1
- package/skills/marketing-plan/CHANGELOG.md +8 -0
- package/skills/marketing-plan/SKILL.md +8 -4
- package/skills/marketing-plan/references/market-sizing.md +166 -0
- package/skills/marketing-plan/references/methodology.md +2 -0
- package/skills/model-router/CHANGELOG.md +9 -0
- package/skills/model-router/SKILL.md +3 -3
- package/skills/model-router/references/weave-routing-notes.md +1 -0
- package/skills/model-router/scripts/route.py +3 -1
- package/skills/prospecting/CHANGELOG.md +7 -0
- package/skills/prospecting/SKILL.md +3 -3
- package/skills/prospecting/references/saas-prospecting.md +1 -1
- package/skills/public-relations/CHANGELOG.md +10 -0
- package/skills/public-relations/SKILL.md +16 -4
- package/skills/public-relations/references/crisis-communications.md +111 -0
- package/skills/public-relations/references/newsjacking.md +2 -0
- package/skills/public-relations/references/statement-templates.md +143 -0
- package/skills/sales-enablement/CHANGELOG.md +8 -0
- package/skills/sales-enablement/SKILL.md +72 -4
- package/skills/sales-enablement/references/win-loss-analysis.md +72 -0
- package/skills/skill-audit/ATTRIBUTION.md +5 -0
- package/skills/skill-audit/CHANGELOG.md +19 -0
- package/skills/skill-audit/SKILL.md +121 -0
- package/skills/skill-audit-security/ATTRIBUTION.md +14 -0
- package/skills/skill-audit-security/CHANGELOG.md +14 -0
- package/skills/skill-audit-security/LICENSE-APACHE-2.0 +190 -0
- package/skills/skill-audit-security/SKILL.md +99 -0
- package/skills/skill-audit-security/references/rule-ids.md +28 -0
- package/skills/skill-audit-security/references/source-review.md +58 -0
- package/skills/sms/CHANGELOG.md +8 -0
- package/skills/sms/SKILL.md +18 -4
- package/skills/sms/references/whatsapp.md +258 -0
- package/skills/social/CHANGELOG.md +11 -0
- package/skills/social/SKILL.md +31 -7
- package/skills/social/references/listening.md +2 -0
- package/skills/social/references/platforms.md +15 -9
- package/skills/social/references/post-templates.md +9 -9
- package/skills/social/references/short-form-video.md +5 -5
- package/skills/social/references/x-algorithm.md +69 -0
- package/skills/trends/CHANGELOG.md +3 -0
- package/skills/trends/SKILL.md +14 -4
- package/skills/video/CHANGELOG.md +8 -0
- package/skills/video/SKILL.md +11 -25
- package/skills/video-studio/ATTRIBUTION.md +3 -0
- package/skills/video-studio/CHANGELOG.md +25 -0
- package/skills/video-studio/LICENSE-motionmaxxing +202 -0
- package/skills/video-studio/SKILL.md +115 -25
- package/skills/video-studio/references/audio.md +15 -0
- package/skills/video-studio/references/interview.md +75 -0
- package/skills/video-studio/references/step-2-plan.md +39 -7
- package/skills/video-studio/references/storyboard.md +3 -1
- package/skills/video-studio/references/tones.md +5 -5
- package/skills/video-studio/scripts/pyproject.toml +4 -2
- package/skills/video-studio/scripts/qa.mjs +85 -1
- package/skills/video-studio/scripts/qa.test.mjs +37 -1
- package/skills/video-studio/scripts/render-scenes.mjs +1 -0
- package/skills/video-studio/scripts/storyboard.schema.json +6 -1
- package/skills/video-studio/scripts/uv.lock +8 -5
- package/skills/video-studio-art-director/ATTRIBUTION.md +3 -0
- package/skills/video-studio-art-director/CHANGELOG.md +7 -0
- package/skills/video-studio-art-director/LICENSE-motionmaxxing +202 -0
- package/skills/video-studio-art-director/SKILL.md +42 -7
- package/skills/video-studio-composition/ATTRIBUTION.md +3 -0
- package/skills/video-studio-composition/CHANGELOG.md +7 -0
- package/skills/video-studio-composition/LICENSE-motionmaxxing +202 -0
- package/skills/video-studio-composition/SKILL.md +17 -4
- package/skills/video-studio-director/ATTRIBUTION.md +3 -0
- package/skills/video-studio-director/CHANGELOG.md +15 -0
- package/skills/video-studio-director/LICENSE-motionmaxxing +202 -0
- package/skills/video-studio-director/SKILL.md +105 -10
- package/skills/video-studio-motion/ATTRIBUTION.md +3 -0
- package/skills/video-studio-motion/CHANGELOG.md +8 -0
- package/skills/video-studio-motion/LICENSE-motionmaxxing +202 -0
- package/skills/video-studio-motion/SKILL.md +98 -5
- package/skills/video-studio-motion/references/kinetic-type.md +89 -0
- package/skills/video-studio-motion/references/ui-demo.md +59 -0
- package/skills/video-studio-review/CHANGELOG.md +11 -0
- package/skills/video-studio-review/SKILL.md +31 -5
- package/skills/video-studio-review/editor/index.html +21 -3
- package/skills/video-studio-review/references/apply-feedback.md +19 -0
- package/skills/video-studio-review/scripts/build-reel.mjs +35 -10
- package/skills/video-studio-review/scripts/build-reel.test.mjs +15 -0
- package/skills/video-studio-script/ATTRIBUTION.md +6 -0
- package/skills/video-studio-script/CHANGELOG.md +12 -0
- package/skills/video-studio-script/LICENSE-motionmaxxing +202 -0
- package/skills/video-studio-script/SKILL.md +41 -10
- package/skills/video-studio-showreel/CHANGELOG.md +21 -0
- package/skills/video-studio-showreel/SKILL.md +231 -0
- package/skills/video-studio-showreel/references/motion-design-pack-notes.md +28 -0
- package/skills/video-studio-showreel/references/zingage-breakdown.md +47 -0
- package/skills/video-studio-showreel/scripts/build.py +10 -0
- package/skills/video-studio-showreel/templates/showreel.html +264 -0
- package/skills/video-watcher/CHANGELOG.md +6 -0
- package/skills/video-watcher/SKILL.md +3 -3
- package/skills/video-watcher/scripts/setup.py +9 -7
- package/skills/trends/agents/openai.yaml +0 -8
- package/skills/trends/scripts/build-skill.sh +0 -39
- package/skills/trends/scripts/compare.sh +0 -61
- package/skills/trends/scripts/evaluate_search_quality.py +0 -573
- package/skills/trends/scripts/test-v1-vs-v2.sh +0 -220
- package/skills/trends/scripts/test_device_auth.py +0 -128
- package/skills/trends/scripts/verify_v3.py +0 -195
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Context interview
|
|
2
|
+
|
|
3
|
+
Every new run starts here, before Step 1. Five minutes of questions saves a
|
|
4
|
+
storyboard nobody wanted. The answers go to `<output-dir>/brief.md`, and the
|
|
5
|
+
director, the script, and the plan read that file instead of guessing.
|
|
6
|
+
|
|
7
|
+
## 1. Infer first
|
|
8
|
+
|
|
9
|
+
Read the request, then skim the repo the way Step 1 does (README, landing page,
|
|
10
|
+
`package.json`, an existing `plan.md` or `brief.md`). Fill in every field below
|
|
11
|
+
that they already answer. A field counts as answered only when the request or the
|
|
12
|
+
repo states one value for it, not when you could guess it. When sources disagree
|
|
13
|
+
(the README and `package.json` describe different products) or name several
|
|
14
|
+
candidates (two audiences), the field is open: ask, with those candidates as the
|
|
15
|
+
options.
|
|
16
|
+
|
|
17
|
+
## 2. Ask what is left
|
|
18
|
+
|
|
19
|
+
Use AskUserQuestion. At most 4 questions per call and at most 2 calls. Skip every
|
|
20
|
+
question the request or repo already answers. Start the first call with one
|
|
21
|
+
line naming what you inferred and from where, for example: "From your README: it
|
|
22
|
+
is for freelancers, and the message is 'get paid on time'. A few questions on the
|
|
23
|
+
rest."
|
|
24
|
+
|
|
25
|
+
Each question offers 2 to 4 options drawn from the repo (the user can always pick
|
|
26
|
+
"Other" and type). Put your best guess first and mark it "(Recommended)".
|
|
27
|
+
|
|
28
|
+
| Call | Field | Question | Example options |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| 1 | Audience | Who is this video for? | Two or three audiences from the README or site |
|
|
31
|
+
| 1 | Message | What is the one thing they should remember? | Two or three lines from the product's own copy |
|
|
32
|
+
| 1 | Where | Where will it be posted? | "X and LinkedIn, 16:9, 20s", "TikTok and Reels, 9:16, 30s", "YouTube or website, 16:9, 60-90s" |
|
|
33
|
+
| 1 | Type | What kind of video? | "Motion graphics (HyperFrames)", "AI-generated video", "A mix of both" |
|
|
34
|
+
| 2 | Why now | What should viewers do after watching? | "Sign up", "Join the waitlist", "Watch the launch", with the real URL |
|
|
35
|
+
| 2 | References | Any video or style you want it to feel like? | "No reference", "I'll paste a link" |
|
|
36
|
+
| 2 | Must show | Anything that has to be in it? | "Our logo and the dashboard", "A real customer number", "Nothing specific" |
|
|
37
|
+
| 2 | Voice | Voiceover or no voiceover? | "No voice, music and text only", "AI voice (Kokoro)", "I'll record it" |
|
|
38
|
+
|
|
39
|
+
If call 1 answers everything that matters (the user typed a full brief in
|
|
40
|
+
"Other"), skip call 2. Never ask a third time: anything still open is inferred
|
|
41
|
+
and marked as such.
|
|
42
|
+
|
|
43
|
+
"A mix of both" means some scenes are `ai` and the rest are motion, demo, or
|
|
44
|
+
logo scenes. AI scenes cost credits, so the cost check comes later, before any
|
|
45
|
+
generation (SKILL.md Step 3).
|
|
46
|
+
|
|
47
|
+
## 3. Write brief.md
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
# Brief: <product>
|
|
51
|
+
|
|
52
|
+
- Audience: <who>
|
|
53
|
+
- Message: <one line, no "and">
|
|
54
|
+
- Where: <platforms>, <format>, <length>s
|
|
55
|
+
- Type: <motion graphics | AI-generated | mix>
|
|
56
|
+
- Call to action: <action>, <url>
|
|
57
|
+
- References: <links or none>
|
|
58
|
+
- Must show: <assets or none>
|
|
59
|
+
- Voice: <none | Kokoro | user recording>
|
|
60
|
+
|
|
61
|
+
Inferred, not asked: <field>: <value> (from <source>), ...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Mark every value you did not hear from the user in the "Inferred" line (write
|
|
65
|
+
"none" when everything was asked), so the user can spot a wrong guess. A URL no
|
|
66
|
+
file states is written as `<url> [confirm]`. In chat, summarize the brief in four
|
|
67
|
+
lines or fewer and give its path, then go to Step 1.
|
|
68
|
+
|
|
69
|
+
## With --yes
|
|
70
|
+
|
|
71
|
+
Ask nothing. Infer every field from the request and the repo, write `brief.md`
|
|
72
|
+
with every field listed under "Inferred", show it, and continue. Defaults when
|
|
73
|
+
nothing says otherwise: Where is "X and LinkedIn, 16:9" at the middle of the
|
|
74
|
+
mode's default length (20s for `launch`), Type is motion graphics, Voice is none,
|
|
75
|
+
References and Must show are none, and a missing URL is `[confirm]`.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Step 2: Write the video plan
|
|
2
2
|
|
|
3
|
-
Write `<output-dir>/plan.md`. One focused page.
|
|
3
|
+
Write `<output-dir>/plan.md`. One focused page. Start from `brief.md` (the
|
|
4
|
+
context interview): its audience, message, platform, length, call to action, and
|
|
5
|
+
type are settled. Do not re-ask or change them; flag a conflict to the user instead. This is the creative north star for the entire video.
|
|
4
6
|
|
|
5
7
|
The plan should specify what the video must communicate and what project material must be used. It should not prescribe low-level Hyperframes implementation details. Hyperframes will decide the concrete composition structure, animation mechanics, and render workflow from the brief in Step 3.
|
|
6
8
|
|
|
@@ -80,11 +82,14 @@ approved (`step-4-deliver.md`, "Other formats (last)").
|
|
|
80
82
|
|
|
81
83
|
### Scene 1 — [name] — [duration]s
|
|
82
84
|
[What's on screen. What text appears. What product material must be referenced.]
|
|
85
|
+
Belief: [the viewer ... ; one belief, no "and"; importance 1, 2 or 3]
|
|
86
|
+
Picture only: [what the picture shows with the text covered; "a card on a flat colour" is not an answer]
|
|
83
87
|
Sequential/interaction: [yes — describe what appears one by one or what interaction is simulated, e.g. "3 stat cards arrive one by one" or "cursor clicks the match button"; or none]
|
|
84
88
|
Audio intent: [what the sound should do emotionally]
|
|
85
89
|
Audio-coupled idea: [typed text, beat-aligned reveal, counter ticks, card-by-card sequence, simulated tap/swipe/type — or none]
|
|
86
90
|
Music: [mood, or "none" only if disabled / missing / intentionally silent]
|
|
87
91
|
Transition mood: [clean / hard / dramatic / soft / chaotic] → Scene 2
|
|
92
|
+
Handoff: [what carries into Scene 2, or "cut on motion"; Scene 2 opens already moving]
|
|
88
93
|
|
|
89
94
|
### Scene 2 — [name] — [duration]s
|
|
90
95
|
[...]
|
|
@@ -180,7 +185,7 @@ These are among the most effective moments in a video — they make the product
|
|
|
180
185
|
If a scene has either of these, commit to it explicitly in the scene description:
|
|
181
186
|
- Good: "3 horse profiles slide in one by one, each with a card sound"
|
|
182
187
|
- Good: "Simulate a right-swipe on Thunder's profile card"
|
|
183
|
-
- Good: "The
|
|
188
|
+
- Good: "The query types into the product's own search field with keyboard sounds" (type only into a real input field, never a headline)
|
|
184
189
|
- Avoid: leaving it vague and hoping Hyperframes adds rhythm on its own
|
|
185
190
|
|
|
186
191
|
Hyperframes can implement both patterns well — but only if the plan specifies what's appearing, in what order, and that interaction is being simulated. The plan is the contract.
|
|
@@ -251,15 +256,42 @@ If SFX are enabled, note likely sound opportunities but leave exact filenames to
|
|
|
251
256
|
|
|
252
257
|
## Transition vocabulary
|
|
253
258
|
|
|
254
|
-
Match transitions to tone:
|
|
259
|
+
Match transitions to tone. Never crossfade whole scenes: the `crossfade` gate fails
|
|
260
|
+
an opacity tween on a full-frame layer. Cut on motion instead: scene A speeds up
|
|
261
|
+
over its last 3 to 8 frames, scene B opens already moving. For a softer join, use
|
|
262
|
+
a blur-matched cut (A blurs out to about 18px, B starts at about 80px and resolves
|
|
263
|
+
in about 8 frames). Fades are for single objects, not scenes. Handoff recipes are
|
|
264
|
+
in `luckiest-video-studio-motion`.
|
|
255
265
|
|
|
256
266
|
| Tone | Preferred transitions |
|
|
257
267
|
|---|---|
|
|
258
|
-
| `default` |
|
|
259
|
-
| `polished` |
|
|
268
|
+
| `default` | Cut on motion, clean wipe |
|
|
269
|
+
| `polished` | Blur-matched cut, slide |
|
|
260
270
|
| `yc-parody` | Hard cut, minimal |
|
|
261
271
|
| `chaotic` | Hard cut, flash, zoom |
|
|
262
|
-
| `deadpan` |
|
|
263
|
-
| `cinematic` | Dramatic wipe,
|
|
272
|
+
| `deadpan` | Hard cut after a long hold |
|
|
273
|
+
| `cinematic` | Dramatic wipe, blur-matched cut |
|
|
264
274
|
| `app-store` | Slide, smooth wipe |
|
|
265
275
|
|
|
276
|
+
## Storyboard stills
|
|
277
|
+
|
|
278
|
+
Every scene gets one still, `storyboard/<id>.png` at the master size, before the
|
|
279
|
+
storyboard review. The still shows the hold frame: what the viewer sees when the
|
|
280
|
+
scene settles. Cheap and local only, never a generated image or clip.
|
|
281
|
+
|
|
282
|
+
| Kind | Still |
|
|
283
|
+
|---|---|
|
|
284
|
+
| `motion`, `ai` | A static HTML frame, `storyboard/frames/<id>.html`: the brand background, the scene's main text in the brand fonts, and the real assets it will show, placed where the hold frame puts them. For `ai`, add a one-line caption of what the shot shows. |
|
|
285
|
+
| `demo` | A frame grab of the footage at the hold point: `ffmpeg -ss <t> -i <file> -frames:v 1 storyboard/<id>.png`. No footage yet: an HTML frame naming what will be recorded. |
|
|
286
|
+
| `logo` | The logo file centered on the brand background, as an HTML frame. |
|
|
287
|
+
|
|
288
|
+
Capture each HTML frame with headless Chrome (no install):
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --hide-scrollbars \
|
|
292
|
+
--window-size=<width>,<height> --screenshot=<abs-run-dir>/storyboard/<id>.png "file://<abs-run-dir>/storyboard/frames/<id>.html"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Look at each still before the review. A scene whose `still` field names another
|
|
296
|
+
file uses that one instead. When a review edit changes a line or a layout,
|
|
297
|
+
redraw only that scene's still.
|
|
@@ -36,7 +36,9 @@ this file and re-renders only what it touched. Schema:
|
|
|
36
36
|
| `loudness` | Integrated loudness of `final.mp4` in LUFS, default `-14` (the streaming norm), two-pass with a -1.2 dBTP ceiling. `false` skips it. |
|
|
37
37
|
| `sfx` | Optional `{ "file": "audio/sfx.wav", "volume": 1 }` from `scripts/sfx-cues.mjs`, mixed under the joined video at 0 s. Cue times are global, so re-run the mixer after a scene's length changes. |
|
|
38
38
|
| `scenes[].id` | Lowercase and hyphens. Stable: edits refer to scenes by id. |
|
|
39
|
-
| `scenes[].kind` | `motion` (HyperFrames scene), `logo` (HyperFrames logo recipe), `demo` (footage). |
|
|
39
|
+
| `scenes[].kind` | `motion` (HyperFrames scene), `logo` (HyperFrames logo recipe), `demo` (footage), `ai` (AI-generated shot, storyboard stage only: it becomes `demo` once its clip is generated and approved). |
|
|
40
|
+
| `scenes[].still` | Optional. The scene's storyboard still, default `storyboard/<id>.png`. The storyboard review shows it. |
|
|
41
|
+
| `scenes[].prompt` | For `ai`, the generation prompt from `luckiest-video-studio-recreate`. Kept after the scene becomes `demo`, so the shot can be regenerated. |
|
|
40
42
|
| `scenes[].composition` | For motion and logo, the sub-composition under `composition/`. Default `compositions/<id>.html`. |
|
|
41
43
|
| `scenes[].source`, `sourceStart` | For demo, the footage file and where to start in it. |
|
|
42
44
|
| `scenes[].focus` | For demo, `{ "x": 0.5, "y": 0.5 }` as fractions of the source frame: the point kept in view when the footage is cropped to a different shape (a 16:9 screen recording in a vertical cut). Set it on the cursor, the face, or the UI the line talks about. |
|
|
@@ -31,7 +31,7 @@ Horse Tinder.
|
|
|
31
31
|
Find your perfect stablemate.
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
**Transitions:**
|
|
34
|
+
**Transitions:** Cut on motion or clean slide.
|
|
35
35
|
|
|
36
36
|
**When to use:** Most absurd consumer apps. Projects that have personality without trying too hard.
|
|
37
37
|
|
|
@@ -57,7 +57,7 @@ upon completion.
|
|
|
57
57
|
|
|
58
58
|
**Outro style:** Product name. Tagline. Silence.
|
|
59
59
|
|
|
60
|
-
**Transitions:**
|
|
60
|
+
**Transitions:** Blur-matched cut (out to 18px, in from 80px, about 8 frames).
|
|
61
61
|
|
|
62
62
|
**When to use:** Projects that aren't jokes. Products that want to feel premium. Anytime the user says "clean" or "elegant."
|
|
63
63
|
|
|
@@ -92,7 +92,7 @@ The ride-hailing app for ride-hailing assets.
|
|
|
92
92
|
taxifortaxis.com
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
**Transitions:** Hard cut
|
|
95
|
+
**Transitions:** Hard cut, landing mid-move.
|
|
96
96
|
|
|
97
97
|
**When to use:** Any recursive or absurd concept that benefits from being played straight. "Psychologists for Chatbots", "Taxi for Taxis", "Briefcase for Baby."
|
|
98
98
|
|
|
@@ -157,7 +157,7 @@ Now I fear birds, weather, and gravity.
|
|
|
157
157
|
Fish Flight School.
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
**Transitions:**
|
|
160
|
+
**Transitions:** Long hold, then a hard cut. A held frame keeps one living thing (a slow drift or a caret).
|
|
161
161
|
|
|
162
162
|
**When to use:** Projects that have a quote as the strongest thing on the site. "Psychologists for Chatbots" testimonial-forward approach. Anything where restraint makes the joke land harder.
|
|
163
163
|
|
|
@@ -192,7 +192,7 @@ FISH FLIGHT SCHOOL
|
|
|
192
192
|
The sky was never the limit.
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
**Transitions:** Dramatic wipe or
|
|
195
|
+
**Transitions:** Dramatic wipe, or a blur-matched cut with scale (scene enters at 0.95, reaches 1.0).
|
|
196
196
|
|
|
197
197
|
**When to use:** Anything with natural epic quality. "Fish Flight School" maps perfectly. Anything involving scale, nature, or grand claims.
|
|
198
198
|
|
|
@@ -5,10 +5,12 @@ description = "Development tooling for the /brag skill."
|
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"librosa>=0.10.2",
|
|
8
|
-
"numpy>=
|
|
9
|
-
"scipy>=1.
|
|
8
|
+
"numpy>=2.4.6",
|
|
9
|
+
"scipy>=1.17.1",
|
|
10
10
|
"soundfile>=0.12",
|
|
11
11
|
]
|
|
12
12
|
|
|
13
13
|
[tool.uv]
|
|
14
14
|
package = false
|
|
15
|
+
# urllib3 comes in through librosa's download helpers; 2.8.0 fixes GHSA-8988-9cw3-xx77, GHSA-gh4c-6fx4-qh6g, GHSA-vxq7-64xx-v4gw.
|
|
16
|
+
constraint-dependencies = ["urllib3>=2.8.0"]
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
// node qa.mjs face <run-dir> [--max 18] cards covering the speaker's face (scene faceRect)
|
|
15
15
|
// node qa.mjs avsync <video> [--expect 1.5,4.2] [--duration 20] audio present, length right, hits within one frame
|
|
16
16
|
// node qa.mjs loop <video> [--max 6] last frame must match the first, so the piece loops
|
|
17
|
+
// node qa.mjs endhold <video> [--max 1.4] [--share 0.25] end card held too long, final shot too long
|
|
18
|
+
// node qa.mjs blankframes <video> [--run 3] runs of empty swatch frames
|
|
19
|
+
// node qa.mjs hud <composition-dir> counters, timecodes, BPM/FPS, greetings, "label · $value", tracked caps
|
|
17
20
|
// node qa.mjs facts <run-dir> on-screen numbers not in storyboard facts, em dashes on screen
|
|
18
21
|
// node qa.mjs safe <run-dir> [--max 0.004] [--stills] text or logo edges in the margins each format's platforms cover
|
|
19
22
|
// node qa.mjs judge <new.mp4> <old.mp4> [--out dir] [--cmd "claude -p --model sonnet"] [--verdicts A,B]
|
|
@@ -292,6 +295,80 @@ export function loopCheck(video, { max = 6 } = {}) {
|
|
|
292
295
|
return { ok: diff <= max, diff: Number(diff.toFixed(2)), frames: frames.length };
|
|
293
296
|
}
|
|
294
297
|
|
|
298
|
+
// ---------- end hold and final shot ----------
|
|
299
|
+
|
|
300
|
+
// The picture must not sit unchanged at the end for more than `max` s, and the last shot
|
|
301
|
+
// (last hard cut to the end) must be at most `share` of the film. A film with no cuts skips the share check.
|
|
302
|
+
export function endHold(video, { max = 1.4, share = 0.25, still = 0.35 } = {}) {
|
|
303
|
+
const { fps } = probe(video), frames = grayFrames(video, 96, 54);
|
|
304
|
+
let n = 0;
|
|
305
|
+
for (let i = frames.length - 1; i > 0 && meanDiff(frames[i], frames[i - 1]) < still; i--) n++;
|
|
306
|
+
const duration = frames.length / fps, hold = Number((n / fps).toFixed(2));
|
|
307
|
+
const cuts = cutTimes(grayFrames(video), fps), last = cuts.at(-1);
|
|
308
|
+
const finalShare = last == null ? null : Number(((duration - last) / duration).toFixed(3));
|
|
309
|
+
return { ok: hold <= max && (finalShare == null || finalShare <= share), hold, finalShare,
|
|
310
|
+
note: hold < 0.6 ? "ends mid-motion (fine if intended)" : undefined };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---------- flat frames ----------
|
|
314
|
+
|
|
315
|
+
// A flat frame is an empty swatch: luma std under 1.5 and range under 8 at 96x54. Fails on a run longer than `run` frames.
|
|
316
|
+
export function blankRuns(video, { run = 3, std = 1.5, range = 8 } = {}) {
|
|
317
|
+
const { fps } = probe(video), runs = [];
|
|
318
|
+
let start = -1;
|
|
319
|
+
const frames = grayFrames(video, 96, 54);
|
|
320
|
+
frames.forEach((f, i) => {
|
|
321
|
+
let lo = 255, hi = 0, sum = 0;
|
|
322
|
+
for (const v of f) { lo = Math.min(lo, v); hi = Math.max(hi, v); sum += v; }
|
|
323
|
+
const mean = sum / f.length;
|
|
324
|
+
let sq = 0; for (const v of f) sq += (v - mean) ** 2;
|
|
325
|
+
const flat = Math.sqrt(sq / f.length) < std && hi - lo < range;
|
|
326
|
+
if (flat && start < 0) start = i;
|
|
327
|
+
if ((!flat || i === frames.length - 1) && start >= 0) {
|
|
328
|
+
const end = flat ? i + 1 : i;
|
|
329
|
+
if (end - start > run) runs.push({ start: Number((start / fps).toFixed(2)), frames: end - start });
|
|
330
|
+
start = -1;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
return { ok: runs.length === 0, runs };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---------- HUD and page chrome ----------
|
|
337
|
+
|
|
338
|
+
// Drop elements marked data-ui (real product UI) before scanning: a clock or a "1/3" inside the product is fine.
|
|
339
|
+
export function stripUi(html) {
|
|
340
|
+
let out = "", depth = 0, last = 0;
|
|
341
|
+
for (const m of html.matchAll(/<(\/?)([a-z][\w-]*)\b[^>]*?(\/?)>/gi)) {
|
|
342
|
+
const [tag, close, , selfClose] = m, opens = !close && !selfClose && !/^(br|img|input|meta|link|hr|source|path|circle|rect|line|polyline|polygon|use)$/i.test(m[2]);
|
|
343
|
+
if (depth === 0) out += html.slice(last, m.index);
|
|
344
|
+
if (depth > 0) { if (close) depth--; else if (opens) depth++; }
|
|
345
|
+
else if (!close && /\bdata-ui\b/.test(tag)) { if (opens) depth = 1; }
|
|
346
|
+
else out += tag;
|
|
347
|
+
last = m.index + tag.length;
|
|
348
|
+
}
|
|
349
|
+
return depth === 0 ? out + html.slice(last) : out;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const CHROME = [
|
|
353
|
+
["counter", /\b0\d\s*(?:\/\s*(?:\d{1,2}|[A-Z]{2,})|[-\u2013]\s*[A-Z]{2,})\b|\b\d{1,2}\s*\/\s*[A-Z]{2,}\b/g],
|
|
354
|
+
["timecode", /\b\d{1,2}:\d{2}(?::\d{2})?(?:[:.]\d{2})?\b/g],
|
|
355
|
+
["metadata", /\b\d+(?:\.\d+)?\s?(?:BPM|FPS|fps)\b/g],
|
|
356
|
+
["greeting", /\bGood (?:morning|afternoon|evening)\b/gi],
|
|
357
|
+
["label-value", /[A-Za-z]+\s*[\u00b7\u2022]\s*[$\u20ac\u00a3]\s?\d/g],
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
// Page chrome and HUD text that reads as a deck or an AI default. Text inside [data-ui] is exempt.
|
|
361
|
+
// ponytail: tracked caps is a warning only, the card packs use it for kickers and source lines; promote it if packs change.
|
|
362
|
+
export function scanHud(html) {
|
|
363
|
+
const src = stripUi(html), text = visibleText(src), found = [];
|
|
364
|
+
for (const [rule, re] of CHROME) for (const m of text.matchAll(re)) found.push({ rule, token: m[0].trim() });
|
|
365
|
+
for (const m of src.matchAll(/([^{}<>]+)\{([^{}]*)\}/g)) {
|
|
366
|
+
const body = m[2], ls = body.match(/letter-spacing\s*:\s*([\d.]+)(em|px)/);
|
|
367
|
+
if (/text-transform\s*:\s*uppercase/.test(body) && ls && (ls[2] === "em" ? +ls[1] >= 0.05 : +ls[1] >= 2)) found.push({ rule: "tracked-caps", warn: true, token: m[1].trim().split("\n").pop().trim() });
|
|
368
|
+
}
|
|
369
|
+
return found;
|
|
370
|
+
}
|
|
371
|
+
|
|
295
372
|
// ---------- facts ----------
|
|
296
373
|
|
|
297
374
|
const ENTITIES = { amp: "&", nbsp: " ", mdash: "\u2014", ndash: "\u2013", lt: "<", gt: ">", quot: '"', "#39": "'", "#8212": "\u2014" };
|
|
@@ -447,7 +524,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
|
|
|
447
524
|
if (gate === "judge" && rest[0] && !rest[0].startsWith("--")) rest.unshift("--old", rest.shift());
|
|
448
525
|
const opt = (n, d) => { const i = rest.indexOf(n); return i >= 0 ? rest[i + 1] : d; };
|
|
449
526
|
const num = (n, d) => Number(opt(n, d));
|
|
450
|
-
if (!gate || !target) { console.error("Usage: qa.mjs <tokens|crossfade|motion|legibility|pops|deadframes|presence|beatsync|beatgrid|face|avsync|loop|facts|safe|judge> <path> [options]"); process.exit(1); }
|
|
527
|
+
if (!gate || !target) { console.error("Usage: qa.mjs <tokens|crossfade|motion|legibility|pops|deadframes|presence|beatsync|beatgrid|face|avsync|loop|endhold|blankframes|hud|facts|safe|judge> <path> [options]"); process.exit(1); }
|
|
451
528
|
let result;
|
|
452
529
|
if (gate === "tokens") {
|
|
453
530
|
const tokens = opt("--tokens") ? JSON.parse(readFileSync(opt("--tokens"), "utf8")) : DEFAULT_TOKENS;
|
|
@@ -482,6 +559,13 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
|
|
|
482
559
|
const expect = (opt("--expect", "") || "").split(",").filter(Boolean).map(Number);
|
|
483
560
|
result = avSync(target, { expect, duration: opt("--duration") != null ? num("--duration") : undefined });
|
|
484
561
|
} else if (gate === "loop") result = loopCheck(target, { max: num("--max", 6) });
|
|
562
|
+
else if (gate === "endhold") result = endHold(target, { max: num("--max", 1.4), share: num("--share", 0.25) });
|
|
563
|
+
else if (gate === "blankframes") result = blankRuns(target, { run: num("--run", 3) });
|
|
564
|
+
else if (gate === "hud") {
|
|
565
|
+
const files = statSync(target).isDirectory() ? htmlFiles(target) : [target];
|
|
566
|
+
const found = files.flatMap((f) => scanHud(readFileSync(f, "utf8")).map((v) => ({ file: f, ...v })));
|
|
567
|
+
result = { ok: found.every((v) => v.warn), found };
|
|
568
|
+
}
|
|
485
569
|
else if (gate === "safe") result = safeAreas(target, { max: num("--max", 0.004), stills: rest.includes("--stills") });
|
|
486
570
|
else if (gate === "facts") {
|
|
487
571
|
const sb = JSON.parse(readFileSync(join(target, "storyboard.json"), "utf8"));
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
scanTokens, scanCrossfades, scanMotion, legibility, findPops, deadFrames, presence,
|
|
11
11
|
beatSync, findAnchor, cutTimes, beatGrid, grayFrames, probe,
|
|
12
12
|
faceCover, pairVerdict, parseWinner, audioOnsets, avSync, visibleText, scanFacts, loopCheck,
|
|
13
|
-
fullFrameSelectors, safeAreas,
|
|
13
|
+
fullFrameSelectors, safeAreas, endHold, blankRuns, scanHud,
|
|
14
14
|
} from "./qa.mjs";
|
|
15
15
|
|
|
16
16
|
const dir = mkdtempSync(join(tmpdir(), "qa-gates-"));
|
|
@@ -209,6 +209,42 @@ test("loop: last frame matching the first passes, a different end frame fails",
|
|
|
209
209
|
assert.equal(cli("loop", ends).status, 1);
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
test("endhold: a 2 s static end card fails, a short resolved hold and a one-shot film pass", () => {
|
|
213
|
+
const long = video("endhold-long.mp4", [["testsrc", 2], ["0x202020", 2]]);
|
|
214
|
+
const r = endHold(long);
|
|
215
|
+
assert.equal(r.ok, false);
|
|
216
|
+
assert.ok(r.hold >= 1.9 && r.finalShare > 0.25, JSON.stringify(r));
|
|
217
|
+
assert.equal(cli("endhold", long).status, 1);
|
|
218
|
+
const short = video("endhold-short.mp4", [["testsrc", 3.5], ["0x202020", 0.8]]);
|
|
219
|
+
assert.equal(endHold(short).ok, true, JSON.stringify(endHold(short)));
|
|
220
|
+
const oneShot = video("endhold-oneshot.mp4", [["testsrc", 3]]);
|
|
221
|
+
assert.deepEqual([endHold(oneShot).ok, endHold(oneShot).finalShare], [true, null]);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("blankframes: half a second of empty swatch fails, a 3-frame dip passes", () => {
|
|
225
|
+
const empty = video("flat-empty.mp4", [["testsrc", 1], ["0x335577", 0.5], ["testsrc", 1]]);
|
|
226
|
+
const r = blankRuns(empty);
|
|
227
|
+
assert.equal(r.ok, false);
|
|
228
|
+
assert.equal(r.runs[0].frames, 15);
|
|
229
|
+
assert.equal(cli("blankframes", empty).status, 1);
|
|
230
|
+
const dip = video("flat-dip.mp4", [["testsrc", 1], ["0x335577", 0.1], ["testsrc", 1]]);
|
|
231
|
+
assert.equal(blankRuns(dip).ok, true, JSON.stringify(blankRuns(dip)));
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("hud: HUD counters, timecodes, BPM, greetings and label-value fail; product UI and plain copy pass", () => {
|
|
235
|
+
const bad = `<p>02 / SPEAK</p><p>01 - THE WAIT</p><p>00:14</p><p>120 BPM</p><p>Good morning, Sam</p><p>Revenue · $4,200</p>`;
|
|
236
|
+
assert.deepEqual(scanHud(bad).map((v) => v.rule), ["counter", "counter", "timecode", "metadata", "greeting", "label-value"]);
|
|
237
|
+
const good = `<h1>Rent the name before you buy it</h1><p>3 / 10 founders</p><div data-ui class="phone"><span>9:41</span><p>Good morning</p></div>`;
|
|
238
|
+
assert.deepEqual(scanHud(good), []);
|
|
239
|
+
const caps = scanHud(`<style>.k{text-transform:uppercase;letter-spacing:0.12em}</style><p class="k">source</p>`);
|
|
240
|
+
assert.deepEqual(caps.map((v) => [v.rule, v.warn]), [["tracked-caps", true]]);
|
|
241
|
+
const comp = join(dir, "chrome-comp"); mkdirSync(comp, { recursive: true });
|
|
242
|
+
writeFileSync(join(comp, "a.html"), bad);
|
|
243
|
+
assert.equal(cli("hud", comp).status, 1);
|
|
244
|
+
writeFileSync(join(comp, "a.html"), good);
|
|
245
|
+
assert.equal(cli("hud", comp).status, 0);
|
|
246
|
+
});
|
|
247
|
+
|
|
212
248
|
test("crossfade: a full-frame layer is found at the composition's own size, not only 1920x1080", () => {
|
|
213
249
|
const html = `<div data-composition-id="m" data-width="1080" data-height="1920"></div><style>.bg{width:1080px;height:1920px}.v{width:var(--w);height:var(--h)}.wide{width:1920px;height:1080px}</style>`;
|
|
214
250
|
assert.deepEqual([...fullFrameSelectors(html)], [".bg", ".v"]);
|
|
@@ -34,6 +34,7 @@ const ids = new Set();
|
|
|
34
34
|
for (const s of sb.scenes) {
|
|
35
35
|
if (ids.has(s.id)) fail(`duplicate scene id "${s.id}"`);
|
|
36
36
|
ids.add(s.id);
|
|
37
|
+
if (s.kind === "ai") fail(`scene ${s.id}: "ai" scene has no clip yet. Generate it, then set kind "demo" and source to the clip`);
|
|
37
38
|
if (!["motion", "logo", "demo"].includes(s.kind)) fail(`scene ${s.id}: unknown kind "${s.kind}"`);
|
|
38
39
|
if (!(s.duration > 0)) fail(`scene ${s.id}: duration must be > 0`);
|
|
39
40
|
}
|
|
@@ -55,13 +55,18 @@
|
|
|
55
55
|
"required": ["id", "kind", "duration"],
|
|
56
56
|
"properties": {
|
|
57
57
|
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
|
|
58
|
-
"kind": { "enum": ["motion", "logo", "demo"] },
|
|
58
|
+
"kind": { "enum": ["motion", "logo", "demo", "ai"], "description": "ai: AI-generated shot, storyboard stage only; set to demo with source once its clip exists" },
|
|
59
59
|
"duration": { "type": "number", "exclusiveMinimum": 0 },
|
|
60
60
|
"line": { "type": "string", "description": "What this scene says or shows, one line, used in TIMING.md" },
|
|
61
|
+
"belief": { "type": "string", "description": "The belief this scene creates: 'the viewer ...'. One change in the viewer, no 'and'. Written by the director" },
|
|
62
|
+
"importance": { "enum": [1, 2, 3], "description": "1 support, 2 build, 3 the point. At most a quarter of scenes are 3" },
|
|
63
|
+
"pictureOnly": { "type": "string", "description": "What the picture shows with the text covered. 'A flat colour', 'a card on a flat colour' and 'a block of text' are rejected answers" },
|
|
61
64
|
"composition": { "type": "string", "description": "motion and logo: sub-composition path relative to composition/, default compositions/<id>.html" },
|
|
62
65
|
"recipe": { "type": "string", "description": "logo: recipe name from references/logo-recipes.md" },
|
|
63
66
|
"source": { "type": "string", "description": "demo: footage path relative to the run folder" },
|
|
64
67
|
"sourceStart": { "type": "number", "minimum": 0, "default": 0 },
|
|
68
|
+
"still": { "type": "string", "description": "Storyboard hold-frame still relative to the run folder, default storyboard/<id>.png" },
|
|
69
|
+
"prompt": { "type": "string", "description": "ai: the generation prompt, kept after the scene becomes demo" },
|
|
65
70
|
"hold": { "type": "number", "minimum": 0, "description": "Seconds into the scene where render-scenes --stills captures the hold frame. Default 0.5s before the cut." },
|
|
66
71
|
"focus": { "type": "object", "required": ["x", "y"], "properties": { "x": { "type": "number", "minimum": 0, "maximum": 1 }, "y": { "type": "number", "minimum": 0, "maximum": 1 } }, "description": "demo: crop center as fractions of the source frame when its shape differs from the output, default { x: 0.5, y: 0.5 }" },
|
|
67
72
|
"faceRect": { "type": "array", "items": { "type": "number", "minimum": 0, "maximum": 1 }, "minItems": 4, "maxItems": 4, "description": "demo: speaker's face as [x, y, w, h] fractions of the frame, checked by the face gate" },
|
|
@@ -6,6 +6,9 @@ resolution-markers = [
|
|
|
6
6
|
"python_full_version < '3.13'",
|
|
7
7
|
]
|
|
8
8
|
|
|
9
|
+
[manifest]
|
|
10
|
+
constraints = [{ name = "urllib3", specifier = ">=2.8.0" }]
|
|
11
|
+
|
|
9
12
|
[[package]]
|
|
10
13
|
name = "audioop-lts"
|
|
11
14
|
version = "0.2.2"
|
|
@@ -89,8 +92,8 @@ dependencies = [
|
|
|
89
92
|
[package.metadata]
|
|
90
93
|
requires-dist = [
|
|
91
94
|
{ name = "librosa", specifier = ">=0.10.2" },
|
|
92
|
-
{ name = "numpy", specifier = ">=
|
|
93
|
-
{ name = "scipy", specifier = ">=1.
|
|
95
|
+
{ name = "numpy", specifier = ">=2.4.6" },
|
|
96
|
+
{ name = "scipy", specifier = ">=1.17.1" },
|
|
94
97
|
{ name = "soundfile", specifier = ">=0.12" },
|
|
95
98
|
]
|
|
96
99
|
|
|
@@ -811,9 +814,9 @@ wheels = [
|
|
|
811
814
|
|
|
812
815
|
[[package]]
|
|
813
816
|
name = "urllib3"
|
|
814
|
-
version = "2.
|
|
817
|
+
version = "2.8.0"
|
|
815
818
|
source = { registry = "https://pypi.org/simple" }
|
|
816
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
819
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
|
|
817
820
|
wheels = [
|
|
818
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
821
|
+
{ url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
|
|
819
822
|
]
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
| **"AI finally designs like me"** by The Design Guy (YouTube, 2026-09-27, https://www.youtube.com/watch?v=E3Lgtf4xjeM) | Video, all rights reserved | Ideas only: DESIGN.md as a file of decisions already made, rules as choice plus reason plus example, locked/guided/open freedom levels, mining repeated review corrections for rules, pointing to existing assets instead of describing them, and checking that the agent read the file. No text or footage is copied | n/a |
|
|
10
10
|
| **prompt-images** by Replicate (`replicate/skills`, https://github.com/replicate/skills/tree/main/skills/prompt-images) | Apache-2.0, Copyright 2025 Replicate, Inc. | Adapted into `references/shot-prompting.md`: natural-language shot descriptions, naming subjects, camera, film, lighting and composition vocabulary, quoted text, editing and consistency rules, guidance and negative-prompt pitfalls. Rewritten and reordered, not copied verbatim | `LICENSE-replicate` |
|
|
11
11
|
| **"How to make AI images look less like AI"** by Pixova (https://www.pixova.io/blog/how-to-make-ai-images-look-less-like-ai) | Article, all rights reserved | Ideas only, in section 3b: directional light with hard shadows, film stock and grain, skin texture, off-center candid framing, specific environments, banned prompt words, lower CFG. No text is copied | n/a |
|
|
12
|
+
| **motionmaxxing** by Tejas Makwana (`Tejashmakwana/motionmaxxing`, FinalMotion, finalmotion.video), commit 8c8ec0f2 | Apache-2.0 | World per act, one system per film, the hero frame, and the additions to the Banned list. Rewritten in our own words; no code, runtime, media, studies or `library/` rows are copied | `LICENSE-motionmaxxing` |
|
|
12
13
|
|
|
13
14
|
## Changes in this edition
|
|
14
15
|
|
|
@@ -23,3 +24,5 @@
|
|
|
23
24
|
- Added `art-direction.json` as a file the studio and other sub-skills read.
|
|
24
25
|
- Mapped studio tone presets to tone cells.
|
|
25
26
|
- Added style-frame approval at studio Step 2b and `--review` with Approve or Block.
|
|
27
|
+
|
|
28
|
+
motionmaxxing NOTICE: "motionmaxxing: looksmaxxing for motion graphics. Made by Tejas Makwana (x.com/makwanatejas170), github.com/Tejashmakwana/motionmaxxing. Licensed under the Apache License, Version 2.0." Changes: ideas and measured numbers restated for HyperFrames and GSAP runs; its GSAP runtime, scripts, showcase media and precedent library are not included.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog — luckiest-video-studio-art-director
|
|
2
2
|
|
|
3
|
+
## 1.6.0 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
- World per act (ground, light, depth, texture, sound) and a named hero frame in the look lock; accent colour carries one meaning.
|
|
6
|
+
- Never-ship list: page chrome and HUD, deck layouts, generic web-component UI, the name's first association, invented proof, house-style defaults, cards on empty grounds. Review gains a never-ship column.
|
|
7
|
+
- One clock and one blur grammar per film in the motion language and `art-direction.json`.
|
|
8
|
+
- From Tejas Makwana's motionmaxxing (Apache-2.0), in our own words.
|
|
9
|
+
|
|
3
10
|
## 1.5.0 — 2026-10-08
|
|
4
11
|
- "Do not repeat earlier runs": reads earlier `art-direction.json` files in the
|
|
5
12
|
workspace and bans their signature transition, wow device, texture, and concept,
|