@olegkoval/agent-skills 1.60.0 → 1.62.0
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/.github/copilot-instructions.md +2 -0
- package/.github/prompts/carousel-maker.prompt.md +102 -0
- package/.github/prompts/shorts-animator.prompt.md +103 -0
- package/.kiro/steering/carousel-maker.md +101 -0
- package/.kiro/steering/shorts-animator.md +102 -0
- package/.windsurf/rules/carousel-maker.md +100 -0
- package/.windsurf/rules/shorts-animator.md +101 -0
- package/README.md +4 -2
- package/adapters/claude/olko-creative/skills/carousel-maker/SKILL.md +112 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/SKILL.md +113 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +30 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/context-gathering.md +3 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/finding-state.mjs +207 -0
- package/adapters/codex/olko-creative/README.md +2 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/SKILL.md +112 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/SKILL.md +113 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/SKILL.md +112 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/SKILL.md +113 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/adapters/hermes/olko-creative/README.md +2 -0
- package/catalog/skills.json +46 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/skills/carousel-maker/SKILL.md +111 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/plugins/olko-creative/skills/shorts-animator/SKILL.md +112 -0
- package/plugins/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/plugins/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/plugins/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +30 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/context-gathering.md +3 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/finding-state.mjs +207 -0
- package/plugins/olko-github-pr/skills/lekker-review/workflow.js +46 -3
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
- package/scripts/lib/catalog.mjs +2 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Every process, browser and disk write the tool makes, kept out of the domain.
|
|
2
|
+
import { spawnSync, type SpawnSyncOptions } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { H, W } from "../domain/layout.ts";
|
|
8
|
+
import type { Json } from "../domain/types.ts";
|
|
9
|
+
|
|
10
|
+
export function run(cmd: string, args: readonly string[], opts: SpawnSyncOptions = {}): string {
|
|
11
|
+
const r = spawnSync(cmd, args, { encoding: "utf8", maxBuffer: 1 << 26, timeout: 120_000, ...opts });
|
|
12
|
+
if (r.status !== 0) {
|
|
13
|
+
const out = String(r.stderr || r.stdout || r.error?.message || "");
|
|
14
|
+
throw new Error(`${path.basename(cmd)} ${args.slice(0, 2).join(" ")}... failed:\n${out.slice(-1500)}`);
|
|
15
|
+
}
|
|
16
|
+
return String(r.stdout ?? "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const has = (cmd: string): boolean => spawnSync(cmd, ["-version"], { stdio: "ignore" }).status === 0;
|
|
20
|
+
export const readJson = (f: string): unknown => JSON.parse(fs.readFileSync(f, "utf8"));
|
|
21
|
+
export const writeJson = (f: string, v: Json | object): void => fs.writeFileSync(f, JSON.stringify(v, null, 2) + "\n");
|
|
22
|
+
export const mkdir = (...parts: string[]): string => {
|
|
23
|
+
const d = path.join(...parts);
|
|
24
|
+
fs.mkdirSync(d, { recursive: true });
|
|
25
|
+
return d;
|
|
26
|
+
};
|
|
27
|
+
export const writeNew = (f: string, text: string): boolean => {
|
|
28
|
+
if (fs.existsSync(f)) return false;
|
|
29
|
+
fs.writeFileSync(f, text);
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type Chrome = { readonly bin: string; readonly shell: boolean };
|
|
34
|
+
|
|
35
|
+
const SHELL = process.platform === "win32" ? "chrome-headless-shell.exe" : "chrome-headless-shell";
|
|
36
|
+
const SYSTEM: Readonly<Record<string, readonly string[]>> = {
|
|
37
|
+
darwin: ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"],
|
|
38
|
+
linux: ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser"],
|
|
39
|
+
win32: [
|
|
40
|
+
`${process.env["PROGRAMFILES"]}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
41
|
+
`${process.env["PROGRAMFILES(X86)"]}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
42
|
+
`${process.env["PROGRAMFILES(X86)"]}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const dirs = (d: string): readonly string[] =>
|
|
47
|
+
fs.existsSync(d) ? fs.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path.join(d, e.name)) : [];
|
|
48
|
+
|
|
49
|
+
/** The headless shell shorts-animator's doctor (via HyperFrames) downloads, newest first. */
|
|
50
|
+
const cachedShell = (): string | undefined =>
|
|
51
|
+
dirs(path.join(os.homedir(), ".cache/hyperframes/chrome/chrome-headless-shell"))
|
|
52
|
+
.toSorted()
|
|
53
|
+
.reverse()
|
|
54
|
+
.flatMap((v) => dirs(v).map((sub) => path.join(sub, SHELL)))
|
|
55
|
+
.find((f) => fs.existsSync(f));
|
|
56
|
+
|
|
57
|
+
export function findChrome(): Chrome | undefined {
|
|
58
|
+
const env = process.env.CHROME;
|
|
59
|
+
if (env && fs.existsSync(env)) return { bin: env, shell: /headless-shell/.test(env) };
|
|
60
|
+
const sys = (SYSTEM[process.platform] ?? []).find((c) => fs.existsSync(c));
|
|
61
|
+
if (sys) return { bin: sys, shell: false };
|
|
62
|
+
const shell = cachedShell();
|
|
63
|
+
return shell ? { bin: shell, shell: true } : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// One short-lived browser per call: no DevTools socket to manage, and a crash costs one slide.
|
|
67
|
+
const flags = (c: Chrome, height: number): readonly string[] => [
|
|
68
|
+
...(c.shell ? [] : ["--headless"]), "--disable-gpu", "--hide-scrollbars", "--allow-file-access-from-files",
|
|
69
|
+
"--force-device-scale-factor=1", "--no-first-run", "--no-default-browser-check", `--window-size=${W},${height}`,
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export const url = (file: string): string => pathToFileURL(path.resolve(file)).href;
|
|
73
|
+
|
|
74
|
+
export const screenshot = (c: Chrome, html: string, png: string): void =>
|
|
75
|
+
void run(c.bin, [...flags(c, H), `--screenshot=${png}`, url(html)], { stdio: ["ignore", "ignore", "pipe"] });
|
|
76
|
+
|
|
77
|
+
/** The DOM after load, images and fonts included (the virtual time budget waits for them). */
|
|
78
|
+
export const dumpDom = (c: Chrome, html: string, height: number): string =>
|
|
79
|
+
run(c.bin, [...flags(c, height), "--virtual-time-budget=5000", "--dump-dom", url(html)]);
|
|
80
|
+
|
|
81
|
+
export const sheet = (pattern: string, count: number, out: string): boolean => {
|
|
82
|
+
if (!has("ffmpeg")) return false;
|
|
83
|
+
const cols = Math.min(count, 5);
|
|
84
|
+
run("ffmpeg", ["-v", "error", "-y", "-start_number", "1", "-framerate", "1", "-i", pattern,
|
|
85
|
+
"-vf", `scale=360:-2,tile=${cols}x${Math.ceil(count / cols)}:padding=8:color=white`, "-frames:v", "1", out]);
|
|
86
|
+
return true;
|
|
87
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { check, fitFindings, report, type Facts } from "../domain/check.ts";
|
|
4
|
+
import { contexts, esc, section } from "../domain/layout.ts";
|
|
5
|
+
import { artOf, parseBrand, parseDeck } from "../domain/schema.ts";
|
|
6
|
+
import type { Brand, Deck, Result } from "../domain/types.ts";
|
|
7
|
+
|
|
8
|
+
const value = <T>(r: Result<T>): T => {
|
|
9
|
+
if (!r.ok) throw new Error(r.errors.join("; "));
|
|
10
|
+
return r.value;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BRAND: Brand = value(parseBrand({ name: "Slow Hours", handle: "@slowhours" }));
|
|
14
|
+
const RAW = {
|
|
15
|
+
title: "mornings",
|
|
16
|
+
slides: [
|
|
17
|
+
{ kind: "cover", title: "Slow mornings, in 4 steps", body: "A calmer first hour.", art: "window" },
|
|
18
|
+
{ kind: "point", title: "Wake with the light", body: "Open the curtains.", art: "light" },
|
|
19
|
+
{ kind: "point", title: "Coffee, outside", art: "photos/mug.jpg" },
|
|
20
|
+
{ kind: "cta", title: "Save this for tomorrow", follow: "Follow @slowhours" },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
const deck = (patch: object = {}): Deck => value(parseDeck({ ...RAW, ...patch }));
|
|
24
|
+
const facts = (d: Deck, library = ["window", "light"], files = ["photos/mug.jpg"]): Facts => ({ deck: d, library, files });
|
|
25
|
+
const msgs = (f: Facts, level: "error" | "warn"): readonly string[] => check(f).filter((x) => x.level === level).map((x) => `${x.where}: ${x.msg}`);
|
|
26
|
+
|
|
27
|
+
test("parseBrand fills default colours and fonts, and rejects a bad colour", () => {
|
|
28
|
+
assert.equal(BRAND.colors.accent, "#e0783a");
|
|
29
|
+
assert.match(BRAND.fonts.head, /Georgia/);
|
|
30
|
+
assert.equal(parseBrand({ name: "x", colors: { accent: "orange" } }).ok, false);
|
|
31
|
+
assert.equal(parseBrand({}).ok, false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("artOf tells a library drawing from a file from nothing", () => {
|
|
35
|
+
assert.deepEqual(artOf("window"), { kind: "library", name: "window" });
|
|
36
|
+
assert.deepEqual(artOf("photos/mug.jpg"), { kind: "file", file: "photos/mug.jpg" });
|
|
37
|
+
assert.deepEqual(artOf(undefined), { kind: "none" });
|
|
38
|
+
assert.deepEqual(artOf(""), { kind: "none" });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("parseDeck defaults the kind to point and names every slide without a title", () => {
|
|
42
|
+
assert.equal(deck({ slides: [{ title: "a" }] }).slides[0]?.kind, "point");
|
|
43
|
+
const bad = parseDeck({ slides: [{ title: "a" }, {}, { kind: "cta" }] });
|
|
44
|
+
assert.equal(bad.ok, false);
|
|
45
|
+
assert.deepEqual(bad.ok ? [] : bad.errors, ["slides[1] needs a title", "slides[2] needs a title"]);
|
|
46
|
+
assert.equal(parseDeck({ slides: [] }).ok, false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a well-shaped deck with its art present checks clean", () => {
|
|
50
|
+
assert.equal(report(check(facts(deck()))), "check: clean");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("missing library drawings and files are errors", () => {
|
|
54
|
+
const errors = msgs(facts(deck(), ["window"], []), "error");
|
|
55
|
+
assert.ok(errors.some((m) => m.startsWith("slide 2") && m.includes('no drawing "light"')));
|
|
56
|
+
assert.ok(errors.some((m) => m.startsWith("slide 3") && m.includes("no file photos/mug.jpg")));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("an unknown kind is an error; a deck without a cover or cta is warned", () => {
|
|
60
|
+
const d = deck({ slides: [{ kind: "quote", title: "a b" }, { kind: "point", title: "c d" }, { kind: "point", title: "e f" }] });
|
|
61
|
+
assert.ok(msgs(facts(d), "error").some((m) => m.includes('kind "quote"')));
|
|
62
|
+
const warns = msgs(facts(d), "warn");
|
|
63
|
+
assert.ok(warns.some((m) => m.startsWith("slide 1") && m.includes("cover")));
|
|
64
|
+
assert.ok(warns.some((m) => m.startsWith("slide 3") && m.includes("cta")));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("long titles and repeated pictures are warned", () => {
|
|
68
|
+
const d = deck({ slides: [
|
|
69
|
+
{ kind: "cover", title: "one two three four five six seven eight nine", art: "window" },
|
|
70
|
+
{ kind: "point", title: "short", art: "window" },
|
|
71
|
+
{ kind: "cta", title: "save" },
|
|
72
|
+
] });
|
|
73
|
+
const warns = msgs(facts(d), "warn");
|
|
74
|
+
assert.ok(warns.some((m) => m.includes("title is 9 words")));
|
|
75
|
+
assert.ok(warns.some((m) => m.startsWith("slide 2") && m.includes("repeats the picture of slide 1")));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("fitFindings turns the browser's measurements into errors", () => {
|
|
79
|
+
const f = fitFindings([{ slide: 1, overflow: false, missingArt: false }, { slide: 2, overflow: true, missingArt: true }]);
|
|
80
|
+
assert.deepEqual(f.map((x) => x.where), ["slide 2", "slide 2"]);
|
|
81
|
+
assert.ok(f.every((x) => x.level === "error"));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("points are numbered among points only, and the footer counts every slide", () => {
|
|
85
|
+
const d = deck();
|
|
86
|
+
const ctx = contexts(BRAND, d.slides, ["a.jpg", "b.jpg", "c.jpg", undefined]);
|
|
87
|
+
assert.deepEqual(ctx.map((c) => c.point), [0, 1, 2, 2]);
|
|
88
|
+
const html = ctx.map((c, i) => (d.slides[i] ? section(d.slides[i], c) : ""));
|
|
89
|
+
assert.match(html[2] ?? "", /<div class="n">02<\/div>/);
|
|
90
|
+
assert.match(html[0] ?? "", /swipe/);
|
|
91
|
+
assert.match(html[3] ?? "", /4\/4/);
|
|
92
|
+
assert.match(html[3] ?? "", /class="slide cta noart"/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("copy is escaped into the HTML", () => {
|
|
96
|
+
assert.equal(esc(`<b>"x" & y</b>`), "<b>"x" & y</b>");
|
|
97
|
+
const html = section({ kind: "point", title: "<script>", art: { kind: "none" } }, { brand: BRAND, n: 2, total: 3, point: 1 });
|
|
98
|
+
assert.ok(!html.includes("<script>"));
|
|
99
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2023",
|
|
4
|
+
"lib": ["es2023"],
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"moduleResolution": "nodenext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"erasableSyntaxOnly": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"skipLibCheck": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["*.ts", "domain", "edge", "test"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shorts-animator
|
|
3
|
+
description: Use when someone wants a narrated, painted or hand-drawn animated short (TikTok, Reels, YouTube Shorts) from a script or topic and a look described in words. Every frame is drawn in code and GPU-painted, voiced locally, captioned and scored into an MP4; no image models or stock.
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Bash, Read, Write, Edit
|
|
6
|
+
compatibility: Codex, Claude Code, Hermes Agent, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Needs Node 22.18+, ffmpeg and Chrome; the narrator installs Python and its voice model on first use.
|
|
7
|
+
metadata:
|
|
8
|
+
targets: [_source-only]
|
|
9
|
+
author: Oleg Koval
|
|
10
|
+
tags:
|
|
11
|
+
- video
|
|
12
|
+
- animation
|
|
13
|
+
- shorts
|
|
14
|
+
- narration
|
|
15
|
+
- canvas
|
|
16
|
+
---
|
|
17
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
18
|
+
|
|
19
|
+
# Shorts Animator
|
|
20
|
+
|
|
21
|
+
A script and a look in, an animated short out. You write the lines and draw every shot as canvas code; the
|
|
22
|
+
tool paints it on the GPU on twos, narrates it with a local Kokoro voice, captions it, scores it and
|
|
23
|
+
encodes the MP4. Nothing is filmed, generated by an image model, or licensed.
|
|
24
|
+
|
|
25
|
+
`SA` = `node <this skill's folder>/scripts/sa.ts`. Write the full command each time. One folder per short:
|
|
26
|
+
`WORK=./<slug>-short`. First run on a machine: `SA doctor` (checks Node, ffmpeg, Chrome, sets up the voice,
|
|
27
|
+
renders a 3-second test).
|
|
28
|
+
|
|
29
|
+
## 1. Decide, do not interrogate
|
|
30
|
+
|
|
31
|
+
Decide the lines, shots, look, voice and music yourself. Ask one question only if the subject is unclear.
|
|
32
|
+
A pasted script is used word for word. A topic: write it per `references/writing.md`.
|
|
33
|
+
|
|
34
|
+
| they say | look |
|
|
35
|
+
|---|---|
|
|
36
|
+
| oil, painterly, Van Gogh, swirly, "like the faith shorts" | `oil` |
|
|
37
|
+
| watercolour, soft, dreamy, children's book | `watercolor` |
|
|
38
|
+
| crayon, pastel, chalk, for kids | `pastel` |
|
|
39
|
+
| ink, comic, graphic novel, line art | `ink` |
|
|
40
|
+
| flat, storybook, gouache, poster, calm | `gouache` |
|
|
41
|
+
|
|
42
|
+
Custom look: `"look": { "base": "oil", "brush": 16, "palette": {...} }`. Read `references/looks.md` only
|
|
43
|
+
when overriding numbers. Voice `bm_george` (default, scripture and quotes), `am_michael`, `bf_emma`
|
|
44
|
+
(stories), `af_heart` (kids); `speed` 0.85 to 0.95 reads calm. Their recording: `"voice": { "file":
|
|
45
|
+
"assets/voice.m4a" }` with `say` lines matching it in order. `"voice": "none"` for music only. Music
|
|
46
|
+
`mood`: `tender`, `hopeful`, `night`, `wonder`, `playful`, `none`, or a track path. `SA looks` lists all.
|
|
47
|
+
|
|
48
|
+
## 2. Write the plan and the drawings
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
SA new "$WORK" --look oil # short.json + scenes.js + assets/
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`short.json` lines: `{ "say": "...", "scene": "meadow" | ["a", "b"], "move": "push", "hold": 0.8 }`.
|
|
55
|
+
- A list of scenes cuts on the caption chunks. Cut often: a new picture every 1.5 to 3 s.
|
|
56
|
+
- `move`: push, pull, pan-left, pan-right, rise, fall, drift, still. `transition`: cut, fade, dip (rarely).
|
|
57
|
+
- `caption` shows different words; `/` forces a break. `captions.style`: box, plain, none.
|
|
58
|
+
- `format`: 9:16 (default), 16:9, 1:1, 4:5. `twos`: 2 (hand-drawn), 1 (smooth).
|
|
59
|
+
|
|
60
|
+
`scenes.js`: `window.SCENES = { "meadow": (k) => { k.sky({...}); k.person({...}); } }`. Read
|
|
61
|
+
`references/kit.md` before writing scenes. Draw like a painter:
|
|
62
|
+
- Big simple shapes, strong value contrast, one subject. The paint pass adds texture; draw nothing fussy.
|
|
63
|
+
- Keep faces out of the caption band (about 77% down).
|
|
64
|
+
- Every shot different: place, scale, day and night, warm and cool. Match the words.
|
|
65
|
+
- Small motion: `pose: "walk"`, `dx`, rain, glints. `k.t` seconds, `k.p` progress 0 to 1.
|
|
66
|
+
- No helper for it (a boat, an animal)? Write a small function with `k.g`, flat shapes in the palette.
|
|
67
|
+
|
|
68
|
+
## 3. Check, then look (budgeted)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
SA check "$WORK" # no browser, about a second: missing scenes, throwing scenes, unknown kit calls,
|
|
72
|
+
# figures under the captions, line and total length
|
|
73
|
+
SA frames "$WORK" # narrates (cached), one painted frame per shot -> frames/sheet.jpg
|
|
74
|
+
SA still "$WORK" 7 # one shot full size, only when the sheet shows a problem you cannot place
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Fix every `ERROR` from `check` before spending a browser run. Then read `frames/sheet.jpg` once. Fix muddy
|
|
78
|
+
or empty shots, a figure lost in its background (contrast, `rim`), look-alike shots, a red "no scene"
|
|
79
|
+
frame, and every `⚠ the page said` line. **Budget: at most two frames rounds before the first render.**
|
|
80
|
+
Images are the expensive part of this loop; do not reopen a sheet you have already judged.
|
|
81
|
+
|
|
82
|
+
## 4. Render and deliver
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
SA render "$WORK" # about a minute for 40 s -> <title>.mp4, renders/vN.mp4 (+ its short.json), qa/sheet.jpg
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Read `qa/sheet.jpg` only if the render printed a warning or they report a problem. Tell them in two
|
|
89
|
+
lines what it is (look, length, shots) and: "Tell me what to change, one sentence is enough."
|
|
90
|
+
|
|
91
|
+
## 5. One-sentence changes
|
|
92
|
+
|
|
93
|
+
Smallest change, `SA check`, render, show. `SA set "$WORK" voice.speed=0.85 music.mood=night` edits
|
|
94
|
+
short.json (JSON values; plain text otherwise) and refuses an invalid result.
|
|
95
|
+
|
|
96
|
+
| they say | change |
|
|
97
|
+
|---|---|
|
|
98
|
+
| more painterly, softer | `look` or its numbers (brush, swirl, wash) |
|
|
99
|
+
| slower, faster | `voice.speed`, `pause` |
|
|
100
|
+
| a woman's voice, American | `voice.id` |
|
|
101
|
+
| no music, more emotional | `music.mood` |
|
|
102
|
+
| bigger captions, no box | `captions.size` 52, `captions.style` plain |
|
|
103
|
+
| for YouTube | `format` 16:9, redraw shots whose layout breaks |
|
|
104
|
+
| change shot 4 | that scene function only |
|
|
105
|
+
|
|
106
|
+
## When something goes wrong
|
|
107
|
+
|
|
108
|
+
| symptom | fix |
|
|
109
|
+
|---|---|
|
|
110
|
+
| no Chrome | install Chrome, or `SA doctor` fetches a headless one |
|
|
111
|
+
| "painting on the CPU" | works, about 10x slower |
|
|
112
|
+
| narrator setup fails | needs Python 3.10+ or `uv`; or use `"voice": "none"` or their recording |
|
|
113
|
+
| voice timing off with their recording | `say` lines must match its words, in order |
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# The drawing kit
|
|
2
|
+
|
|
3
|
+
Every scene is `(k) => { ... }`. Positions are fractions of the frame (x 0→1 left→right, y 0→1 top→bottom);
|
|
4
|
+
sizes in px are for a 1920-tall frame and scale with it (`k.u`). Draw back to front: sky, far, middle, near.
|
|
5
|
+
|
|
6
|
+
| in `k` | what |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `k.g` | the 2D canvas context (camera already applied): draw anything the helpers don't cover |
|
|
9
|
+
| `k.W`, `k.H`, `k.u` | frame size, and the scale (H/1920) |
|
|
10
|
+
| `k.X(x)`, `k.Y(y)` | fraction → pixels |
|
|
11
|
+
| `k.t`, `k.p` | seconds into the shot (on twos), progress 0→1 |
|
|
12
|
+
| `k.r()` | random, the same on every drawing of the shot |
|
|
13
|
+
| `k.rng(seed)`, `k.noise1(x)` | a seeded random source; smooth 1D noise |
|
|
14
|
+
| `k.pal`, `k.beat` | the short's palette; this shot's beat (scene, line, move) |
|
|
15
|
+
| `k.mix(a, b, t)`, `k.alpha(c, a)` | colour helpers |
|
|
16
|
+
| `IMAGES.name` | an image from `assets/` (a logo, a reference), for `k.g.drawImage` |
|
|
17
|
+
|
|
18
|
+
## Sky and light
|
|
19
|
+
- `k.sky({ stops: [top, …, bottom], y0, y1 })`: a gradient
|
|
20
|
+
- `k.clouds({ y, band, n, size, color, shade, drift, seed })`: small cumulus in a band, drifting
|
|
21
|
+
- `k.sun({ x, y, r, color, glow, glowSize, glowAlpha })`: sun, moon, a bright bulb
|
|
22
|
+
- `k.glow({ x, y, r, color, a })`: any soft light (windows, lanterns, a halo)
|
|
23
|
+
- `k.stars({ n, y1 })`, `k.motes({ n, y0, y1, color })` (fireflies, embers), `k.rain({ n, a, slant })`
|
|
24
|
+
- `k.rays({ x, y, angle, spread, n, a, width })`: god rays through trees or clouds
|
|
25
|
+
- `k.haze({ y, h, color, a })`: air between planes
|
|
26
|
+
|
|
27
|
+
## Land and water
|
|
28
|
+
- `k.ridge({ y, amp, freq, color, top, shape, seed })`: filled ridge: `soft` hills, `peaks` mountains,
|
|
29
|
+
`pines` a conifer line, `bush` a broadleaf line, `rocky`
|
|
30
|
+
- `k.ground({ y, color, far, stripes, stripe, flowers })`: the ground plane, mown stripes, flowers
|
|
31
|
+
- `k.path({ x, y, near, far, color, bend })`: a road to the horizon
|
|
32
|
+
- `k.water({ y0, y1, color, deep, glint, glints })`: lake or sea with moving glints
|
|
33
|
+
- `k.waterfall({ x, w, y0, y1 })`
|
|
34
|
+
- `k.shape({ pts: [[x,y],…], color, smooth })`: any shape: a cliff, a giant tree trunk, an island
|
|
35
|
+
|
|
36
|
+
## Plants
|
|
37
|
+
- `k.tree({ x, y, h, kind, crown, trunk, fruit })`: kind `round` `pine` `autumn` `bare` `palm` `column`
|
|
38
|
+
- `k.trees({ n, y, h, x0, x1, kind, crown })`: a row
|
|
39
|
+
- `k.foliage({ side, x, depth, y0, y1, color, light })`: leaves hanging into frame from a side
|
|
40
|
+
- `k.trunks({ n, color, light, width, x0, x1 })`: inside a forest
|
|
41
|
+
|
|
42
|
+
## Built things
|
|
43
|
+
- `k.house({ x, y, w, h, wall, roof, window, lit, floors, aFrame })`
|
|
44
|
+
- `k.city({ y, h, color, lit, litColor, density, n })`: skyline, windows lit at night
|
|
45
|
+
- `k.pagoda({ x, y, h, color })`, `k.pole({ x, y, h })`, `k.lamp({ x, y, h, light })`
|
|
46
|
+
- `k.block({ x0, y0, x1, y1, color, edge })`: walls, doorways, a lit edge
|
|
47
|
+
- `k.heart({ x, y, s, color })`
|
|
48
|
+
|
|
49
|
+
## People
|
|
50
|
+
`k.person({ x, y, h, pose, facing, hair, skin, hairColor, top, bottom, shoes, robe, rim, silhouette, walk, dx, flip, build })`
|
|
51
|
+
- feet at (x, y), `h` = height as a fraction of the frame (0.25 is a full figure in a landscape)
|
|
52
|
+
- `pose`: `stand` `walk` `cheer` `open` `wave` `point` `reach` `pray` `hug` `sit` `kneel` `hold`
|
|
53
|
+
- `facing`: `front` | `back` (faces are left blank on purpose: it's the style, and it reads at any size)
|
|
54
|
+
- `hair`: `short` `long` `curly` `afro` `bun` `hood` `hat` `cap` `bald`
|
|
55
|
+
- `robe: true`: a long garment (biblical, monks, a dress)
|
|
56
|
+
- `silhouette: "#111"`: one colour (backlit, sunset); `rim: "#9db6f0"`: a lit outline (night)
|
|
57
|
+
- `dx`: moves over the shot (0.05 = 5% of the width); `walk`: steps per second
|
|
58
|
+
- `k.people([{...}, {...}])`: several `person` calls in one
|
|
59
|
+
- Two people walking together read as a relationship; a tiny figure in a huge landscape reads as awe or loneliness.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Looks: from words to numbers
|
|
2
|
+
|
|
3
|
+
A look is a JSON object. Start from a shipped one (`"base": "oil"`) and override.
|
|
4
|
+
|
|
5
|
+
| key | what it does | range |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `mode` | `strokes` (paint/pastel), `wash` (watercolour/ink), `flat` | |
|
|
8
|
+
| `brush` | stroke size in px at 1920 tall | 5 fine … 11 oil … 18 bold |
|
|
9
|
+
| `length` | stroke length ÷ width | 2 dabs … 4.6 hatching |
|
|
10
|
+
| `swirl` | strokes follow a swirl (1) or the shapes (0) | 0 … 1 |
|
|
11
|
+
| `curve` | strokes bend | 0 … 1 |
|
|
12
|
+
| `bristle` | streaks inside a stroke, broken dry ends | 0 … 1 |
|
|
13
|
+
| `impasto` | thick paint catching light | 0 … 1 |
|
|
14
|
+
| `hueJitter` `valueJitter` `satJitter` | colour variation stroke to stroke | 0 … 0.4 |
|
|
15
|
+
| `respect` | big strokes stop at edges (crisper subjects) | 0 … 1 |
|
|
16
|
+
| `boil` | share of strokes redrawn every drawing (the shimmer) | 0 still … 1 lively |
|
|
17
|
+
| `wash` `bleed` | watercolour pooling, blooms, granulation; how far colour runs | 0 … 1 |
|
|
18
|
+
| `outline` `ink` | hand-inked lines, their colour | 0 … 1 |
|
|
19
|
+
| `paper` `paperColor` `grain` | canvas/paper texture, grain | 0 … 1.6 |
|
|
20
|
+
| `posterize` | flat colour bands (screen print, retro) | 0 off, 4 … 8 |
|
|
21
|
+
| `saturation` `contrast` `warmth` | grade | ~0.8 … 1.3 |
|
|
22
|
+
| `vignette` `bloom` | dark corners, glow from lights | 0 … 0.6 |
|
|
23
|
+
| `captions` | `{ style, size, y, box, color, font, case }` | |
|
|
24
|
+
|
|
25
|
+
Words → moves:
|
|
26
|
+
- "thicker / bolder paint" → brush +4, impasto +0.2 · "finer" → brush −3
|
|
27
|
+
- "swirly / Van Gogh" → swirl 0.9, curve 1, hueJitter 0.25 · "calmer" → swirl 0.3, boil 0.2
|
|
28
|
+
- "dreamy / soft" → watercolor, bleed 1 · "crisp" → respect 1, boil 0.2
|
|
29
|
+
- "vintage / old film" → saturation 0.8, warmth 0.5, grain 1, vignette 0.5
|
|
30
|
+
- "comic / graphic novel" → ink, posterize 6 · "screen print / retro poster" → gouache, posterize 5
|
|
31
|
+
- "night is too dark" → contrast 0.95 and lighten the palette, not the look
|
|
32
|
+
- "for kids" → pastel or gouache, saturation 1.1, warm palette, playful music
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Writing the lines
|
|
2
|
+
|
|
3
|
+
- One idea per line, 4 to 12 words. Read aloud it should take 1.5 to 4 seconds.
|
|
4
|
+
- Open on a hook that makes the second line necessary ("You have heard that it was said,").
|
|
5
|
+
- A turn two-thirds in ("But…", "And yet…", "Nobody ever…").
|
|
6
|
+
- Land the last line; give it `hold: 0.8` and a final wide or calm shot.
|
|
7
|
+
- Scripture, quotes and poems: use a public-domain translation (KJV, WEB) or their own words; keep it exact.
|
|
8
|
+
- Never invent a quote and attribute it to a real person.
|
|
9
|
+
- 20 to 45 seconds is the sweet spot for Shorts, Reels and TikTok; 60 s at most.
|