@olegkoval/agent-skills 1.59.3 → 1.61.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/self-critique.prompt.md +167 -43
- 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 +5 -3
- 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-reflection/skills/self-critique/SKILL.md +167 -43
- package/adapters/claude/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/adapters/claude/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -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/cursor/olko-reflection/skills/self-critique/SKILL.md +167 -43
- package/adapters/cursor/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/adapters/cursor/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -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/grok/olko-reflection/skills/self-critique/SKILL.md +167 -43
- package/adapters/grok/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/adapters/grok/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
- package/adapters/hermes/olko-creative/README.md +2 -0
- package/catalog/skills.json +47 -1
- package/docs/superpowers/plans/2026-10-27-self-critique-v2.md +306 -0
- package/docs/superpowers/specs/2026-10-27-self-critique-v2-spec.md +104 -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-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-reflection/skills/self-critique/SKILL.md +167 -43
- package/plugins/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/plugins/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
- 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,268 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* THE PAINT PASS: a flat drawing in, a painted frame out.
|
|
3
|
+
*
|
|
4
|
+
* Every scene is first drawn flat (kit.js): clean shapes, no texture. This
|
|
5
|
+
* pass repaints it on the GPU in the short's look. "strokes" lays three
|
|
6
|
+
* layers of brush strokes (big, medium, small), each stroke one colour picked
|
|
7
|
+
* from the drawing at its centre, turned along the shapes and a slow swirl,
|
|
8
|
+
* with bristle streaks and a little impasto light. Big strokes stop at edges
|
|
9
|
+
* like a careful painter; the small ones finish the edges ragged. "wash" is
|
|
10
|
+
* watercolour: soft bleed, pigment pooled at the edges, granulation, paper.
|
|
11
|
+
* "flat" keeps the shapes and only adds the paper, grain and grade. Any mode
|
|
12
|
+
* can add ink lines and posterize.
|
|
13
|
+
*
|
|
14
|
+
* Every number lives in the look (looks/*.json, references/looks.md), so a
|
|
15
|
+
* style described in words becomes a look file, and the same scene can be
|
|
16
|
+
* painted any way.
|
|
17
|
+
*
|
|
18
|
+
* Frame by frame: `tick` is the drawing number. With boil > 0, that share of
|
|
19
|
+
* strokes is laid again on every drawing, so the paint shimmers the way
|
|
20
|
+
* hand-made animation does.
|
|
21
|
+
*/
|
|
22
|
+
(function () {
|
|
23
|
+
const VS = `#version 300 es
|
|
24
|
+
in vec2 p; void main(){ gl_Position = vec4(p, 0., 1.); }`;
|
|
25
|
+
|
|
26
|
+
const FS = `#version 300 es
|
|
27
|
+
precision highp float;
|
|
28
|
+
uniform sampler2D uImg;
|
|
29
|
+
uniform vec2 uRes;
|
|
30
|
+
uniform float uS; // px scale: frame height / 1920
|
|
31
|
+
uniform float uTick;
|
|
32
|
+
uniform float uMode; // 0 strokes, 1 wash, 2 flat
|
|
33
|
+
uniform float uBrush, uLen, uLayers, uSwirl, uFlow, uBristle, uImpasto, uCurve;
|
|
34
|
+
uniform float uHueJ, uValJ, uSatJ, uRespect, uBoil;
|
|
35
|
+
uniform float uPaper, uGrain, uWash, uOutline, uPoster, uSat, uContrast, uWarm, uVig, uBloom, uBleed;
|
|
36
|
+
uniform vec3 uInk, uPaperCol;
|
|
37
|
+
out vec4 o;
|
|
38
|
+
|
|
39
|
+
vec3 hash32(vec2 p){ vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yxz + 33.33); return fract((p3.xxy + p3.yzz) * p3.zyx); }
|
|
40
|
+
float hash12(vec2 p){ vec3 p3 = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }
|
|
41
|
+
float vnoise(vec2 p){ vec2 i = floor(p), f = fract(p); vec2 u = f*f*(3. - 2.*f);
|
|
42
|
+
return mix(mix(hash12(i), hash12(i + vec2(1,0)), u.x), mix(hash12(i + vec2(0,1)), hash12(i + vec2(1,1)), u.x), u.y); }
|
|
43
|
+
float fbm(vec2 p){ float s = 0., a = .5; for (int i = 0; i < 4; i++){ s += a*vnoise(p); p = p*2.03 + 7.1; a *= .5; } return s; }
|
|
44
|
+
|
|
45
|
+
vec3 img(vec2 px, float lod){ return textureLod(uImg, px / uRes, lod).rgb; }
|
|
46
|
+
float lum(vec3 c){ return dot(c, vec3(.299, .587, .114)); }
|
|
47
|
+
|
|
48
|
+
vec3 hueShift(vec3 c, float a){
|
|
49
|
+
const mat3 toYIQ = mat3(.299, .596, .211, .587, -.274, -.523, .114, -.322, .312);
|
|
50
|
+
const mat3 toRGB = mat3(1., 1., 1., .956, -.272, -1.106, .621, -.647, 1.703);
|
|
51
|
+
vec3 y = toYIQ * c; float h = atan(y.z, y.y) + a, ch = length(y.yz);
|
|
52
|
+
return toRGB * vec3(y.x, ch*cos(h), ch*sin(h));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
vec2 flowDir(vec2 c){
|
|
56
|
+
float e = 7.*uS;
|
|
57
|
+
float gx = lum(img(c + vec2(e, 0.), 3.)) - lum(img(c - vec2(e, 0.), 3.));
|
|
58
|
+
float gy = lum(img(c + vec2(0., e), 3.)) - lum(img(c - vec2(0., e), 3.));
|
|
59
|
+
float ang = fbm(c / (uFlow*uS) + vec2(uTick*.013*uBoil, 0.)) * 6.2832 * 1.7;
|
|
60
|
+
vec2 nd = vec2(cos(ang), sin(ang));
|
|
61
|
+
vec2 g = vec2(gx, gy);
|
|
62
|
+
float gl = length(g);
|
|
63
|
+
if (gl < 1e-4) return nd;
|
|
64
|
+
vec2 ed = vec2(-g.y, g.x) / gl;
|
|
65
|
+
if (dot(ed, nd) < 0.) ed = -ed;
|
|
66
|
+
float w = clamp(gl*9., 0., 1.) * (1. - .75*uSwirl);
|
|
67
|
+
return normalize(mix(nd, ed, w));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// One layer of strokes of a given size. rgb + coverage.
|
|
71
|
+
vec4 strokes(vec2 px, float size, float lseed, float respect){
|
|
72
|
+
vec2 cell = floor(px / size);
|
|
73
|
+
float bestZ = -1.;
|
|
74
|
+
vec4 res = vec4(0.);
|
|
75
|
+
vec3 here = img(px, 0.);
|
|
76
|
+
for (int j = -2; j <= 2; j++) for (int i = -2; i <= 2; i++){
|
|
77
|
+
vec2 c = cell + vec2(float(i), float(j));
|
|
78
|
+
float boil = hash12(c + lseed*3.1) < uBoil ? uTick : 0.;
|
|
79
|
+
vec3 h = hash32(c*1.37 + lseed + boil*.618);
|
|
80
|
+
vec2 ctr = (c + .1 + .8*h.xy) * size;
|
|
81
|
+
vec2 d = px - ctr;
|
|
82
|
+
float W = size*(.34 + .22*h.z);
|
|
83
|
+
float Lh = size*uLen*.5*(.7 + .6*h.y);
|
|
84
|
+
if (dot(d, d) > (Lh + W)*(Lh + W)) continue;
|
|
85
|
+
float z = hash12(c + lseed + 19.7 + boil);
|
|
86
|
+
if (z < bestZ) continue;
|
|
87
|
+
vec2 dir = flowDir(ctr);
|
|
88
|
+
float u = dot(d, dir), v = dot(d, vec2(-dir.y, dir.x));
|
|
89
|
+
v -= (h.x - .5) * uCurve * u*u / Lh;
|
|
90
|
+
float un = u / Lh;
|
|
91
|
+
if (abs(un) > 1.) continue;
|
|
92
|
+
float taper = sqrt(max(0., 1. - pow(abs(un), 2.4)));
|
|
93
|
+
float wv = W*taper*(.82 + .36*vnoise(vec2(u/(6.*uS), h.z*40.)));
|
|
94
|
+
float edge = abs(v) - wv;
|
|
95
|
+
if (edge > 0.) continue;
|
|
96
|
+
vec3 col = img(ctr, .6);
|
|
97
|
+
// a careful painter stops a big stroke at an edge
|
|
98
|
+
float a = clamp(-edge / (1.3*uS), 0., 1.);
|
|
99
|
+
a *= 1. - respect*smoothstep(.10, .28, distance(here, col));
|
|
100
|
+
if (a <= .02) continue;
|
|
101
|
+
float br = vnoise(vec2(v/(1.7*uS) + h.x*91., un*.7 + h.y*13.));
|
|
102
|
+
a *= mix(1., step(.38, br), smoothstep(.55, 1., abs(un))*uBristle);
|
|
103
|
+
if (a <= .02) continue;
|
|
104
|
+
// colour of this stroke: the drawing's, nudged
|
|
105
|
+
col = hueShift(col, (h.x - .5)*uHueJ);
|
|
106
|
+
col *= 1. + (h.y - .5)*uValJ;
|
|
107
|
+
float l = lum(col); col = mix(vec3(l), col, 1. + (h.z - .5)*uSatJ);
|
|
108
|
+
// bristle streaks along the stroke, and the ridge catching light
|
|
109
|
+
col *= 1. + (br - .5)*uBristle*.32;
|
|
110
|
+
float side = v / max(wv, 1e-3);
|
|
111
|
+
float lit = dot(vec2(-dir.y, dir.x), normalize(vec2(-.6, -.8)));
|
|
112
|
+
col *= 1. + uImpasto*(.16*side*lit + .08*(1. - side*side) - .05);
|
|
113
|
+
bestZ = z;
|
|
114
|
+
res = vec4(col, a);
|
|
115
|
+
}
|
|
116
|
+
return res;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void main(){
|
|
120
|
+
vec2 px = vec2(gl_FragCoord.x, uRes.y - gl_FragCoord.y);
|
|
121
|
+
vec3 col;
|
|
122
|
+
if (uMode < .5){
|
|
123
|
+
col = img(px + (vec2(fbm(px/(9.*uS)), fbm(px/(9.*uS) + 5.)) - .5)*4.*uS, 1.5);
|
|
124
|
+
float b = uBrush*uS;
|
|
125
|
+
for (int k = 0; k < 3; k++){
|
|
126
|
+
float fk = float(k);
|
|
127
|
+
if (fk >= uLayers) break;
|
|
128
|
+
float size = b * (fk == 0. ? 2.3 : fk == 1. ? 1.35 : .78);
|
|
129
|
+
float respect = uRespect * (fk == 0. ? 1. : fk == 1. ? .8 : .35);
|
|
130
|
+
vec4 s = strokes(px, size, fk*17.31 + 3., respect);
|
|
131
|
+
col = mix(col, s.rgb, s.a);
|
|
132
|
+
}
|
|
133
|
+
} else if (uMode < 1.5){
|
|
134
|
+
// the wash moves a little each drawing (boil), and bleeds past the line
|
|
135
|
+
float bt = floor(uTick*uBoil + .5)*.37;
|
|
136
|
+
vec2 q = px + (vec2(fbm(px/(16.*uS) + bt), fbm(px/(16.*uS) + 9. + bt)) - .5)*uBleed*22.*uS;
|
|
137
|
+
col = img(q, 1.4 + uBleed);
|
|
138
|
+
// pigment pools at the edge of each wash
|
|
139
|
+
float wet = length(img(q, 0.) - img(q, 3.5));
|
|
140
|
+
col = mix(col, col*col*1.12, uWash*.9*smoothstep(.02, .16, wet));
|
|
141
|
+
// blooms: the wash dried unevenly
|
|
142
|
+
col *= 1. + uWash*.3*(fbm(px/(95.*uS) + 3.) - .5);
|
|
143
|
+
col = mix(col, col*mix(vec3(1.), vec3(.92, .95, 1.05), fbm(px/(60.*uS))), uWash*.6);
|
|
144
|
+
// granulation in the tooth of the paper
|
|
145
|
+
col *= 1. - uWash*.24*(fbm(px/(2.2*uS)) - .5);
|
|
146
|
+
// the paper showing through where the brush ran dry
|
|
147
|
+
float dry = smoothstep(.62, .82, fbm(px/(22.*uS) + 7. + bt));
|
|
148
|
+
col = mix(col, uPaperCol, uWash*(.08 + .3*dry*smoothstep(.0, .12, wet)));
|
|
149
|
+
col = mix(col, uPaperCol, .12*uWash);
|
|
150
|
+
} else {
|
|
151
|
+
col = img(px, 0.);
|
|
152
|
+
}
|
|
153
|
+
if (uOutline > 0.){
|
|
154
|
+
float e = 1.4*uS;
|
|
155
|
+
float gx = lum(img(px + vec2(e,0.), .5)) - lum(img(px - vec2(e,0.), .5));
|
|
156
|
+
float gy = lum(img(px + vec2(0.,e), .5)) - lum(img(px - vec2(0.,e), .5));
|
|
157
|
+
float wob = .7 + .6*vnoise(px/(30.*uS) + uTick*.37);
|
|
158
|
+
float line = smoothstep(.06, .2, length(vec2(gx, gy))*wob);
|
|
159
|
+
col = mix(col, uInk, line*uOutline);
|
|
160
|
+
}
|
|
161
|
+
if (uPoster > 1.) col = floor(col*uPoster + .5) / uPoster;
|
|
162
|
+
// ground: canvas weave or paper tooth, then grain
|
|
163
|
+
float weave = sin(px.x*3.1416/(2.6*uS)) * sin(px.y*3.1416/(2.6*uS));
|
|
164
|
+
float tooth = fbm(px/(3.*uS));
|
|
165
|
+
col *= 1. + uPaper*(.05*weave + .09*(tooth - .5));
|
|
166
|
+
col += (hash12(px + fract(uTick*.137)*911.) - .5) * uGrain * .07;
|
|
167
|
+
// grade
|
|
168
|
+
col += uBloom * max(img(px, 5.) - .55, 0.) * 1.4;
|
|
169
|
+
float l = lum(col);
|
|
170
|
+
col = mix(vec3(l), col, uSat);
|
|
171
|
+
col = (col - .5)*uContrast + .5;
|
|
172
|
+
col += vec3(.035, .01, -.03)*uWarm;
|
|
173
|
+
vec2 uv = px / uRes - .5;
|
|
174
|
+
col *= 1. - uVig*dot(uv*vec2(1.2, .9), uv*vec2(1.2, .9))*1.3;
|
|
175
|
+
o = vec4(clamp(col, 0., 1.), 1.);
|
|
176
|
+
}`;
|
|
177
|
+
|
|
178
|
+
const DEFAULTS: PaintSettings = {
|
|
179
|
+
mode: "strokes", brush: 9, length: 2.6, layers: 3, swirl: 0.55, flow: 160, curve: 0.5,
|
|
180
|
+
bristle: 0.6, impasto: 0.5, hueJitter: 0.12, valueJitter: 0.14, satJitter: 0.25, respect: 0.85,
|
|
181
|
+
boil: 0.5, paper: 0.6, grain: 0.5, wash: 0, bleed: 0, outline: 0, ink: "#1a1612",
|
|
182
|
+
paperColor: "#f3eee2", posterize: 0, saturation: 1.12, contrast: 1.06, warmth: 0.2,
|
|
183
|
+
vignette: 0.35, bloom: 0.25,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const MODES = new Map<string, number>([["strokes", 0], ["wash", 1], ["flat", 2]]);
|
|
187
|
+
|
|
188
|
+
// shader uniform name -> the look setting that feeds it
|
|
189
|
+
const NUMBER_UNIFORMS: [string, PaintNumberKey][] = [
|
|
190
|
+
["uBrush", "brush"], ["uLen", "length"], ["uLayers", "layers"], ["uSwirl", "swirl"],
|
|
191
|
+
["uFlow", "flow"], ["uCurve", "curve"], ["uBristle", "bristle"], ["uImpasto", "impasto"],
|
|
192
|
+
["uHueJ", "hueJitter"], ["uValJ", "valueJitter"], ["uSatJ", "satJitter"],
|
|
193
|
+
["uRespect", "respect"], ["uBoil", "boil"], ["uPaper", "paper"], ["uGrain", "grain"],
|
|
194
|
+
["uWash", "wash"], ["uBleed", "bleed"], ["uOutline", "outline"], ["uPoster", "posterize"],
|
|
195
|
+
["uSat", "saturation"], ["uContrast", "contrast"], ["uWarm", "warmth"],
|
|
196
|
+
["uVig", "vignette"], ["uBloom", "bloom"],
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
function hex(c: string): number[] {
|
|
200
|
+
const n = parseInt(String(c).replace("#", ""), 16);
|
|
201
|
+
return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function compile(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {
|
|
205
|
+
const s = gl.createShader(type);
|
|
206
|
+
if (!s) throw new Error("WebGL2 could not create a shader");
|
|
207
|
+
gl.shaderSource(s, src);
|
|
208
|
+
gl.compileShader(s);
|
|
209
|
+
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s) ?? "shader compile failed");
|
|
210
|
+
return s;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function link(gl: WebGL2RenderingContext): WebGLProgram {
|
|
214
|
+
const prog = gl.createProgram();
|
|
215
|
+
if (!prog) throw new Error("WebGL2 could not create a program");
|
|
216
|
+
gl.attachShader(prog, compile(gl, gl.VERTEX_SHADER, VS));
|
|
217
|
+
gl.attachShader(prog, compile(gl, gl.FRAGMENT_SHADER, FS));
|
|
218
|
+
gl.linkProgram(prog);
|
|
219
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(prog) ?? "program link failed");
|
|
220
|
+
return prog;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function makeTexture(gl: WebGL2RenderingContext): WebGLTexture {
|
|
224
|
+
const tex = gl.createTexture();
|
|
225
|
+
if (!tex) throw new Error("WebGL2 could not create a texture");
|
|
226
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
227
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
228
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
229
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
230
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
231
|
+
return tex;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function Painter(canvas: HTMLCanvasElement): Painter {
|
|
235
|
+
const gl = canvas.getContext("webgl2", { preserveDrawingBuffer: true, antialias: false });
|
|
236
|
+
if (!gl) throw new Error("WebGL2 is not available");
|
|
237
|
+
const prog = link(gl);
|
|
238
|
+
gl.useProgram(prog);
|
|
239
|
+
const buf = gl.createBuffer();
|
|
240
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
241
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
|
|
242
|
+
const loc = gl.getAttribLocation(prog, "p");
|
|
243
|
+
gl.enableVertexAttribArray(loc);
|
|
244
|
+
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
|
|
245
|
+
const tex = makeTexture(gl);
|
|
246
|
+
const U = (n: string) => gl.getUniformLocation(prog, n);
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
paint(flat, look, tick) {
|
|
250
|
+
const L = { ...DEFAULTS, ...look };
|
|
251
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
252
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
253
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, flat);
|
|
254
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
255
|
+
gl.uniform2f(U("uRes"), canvas.width, canvas.height);
|
|
256
|
+
gl.uniform1f(U("uS"), canvas.height / 1920);
|
|
257
|
+
gl.uniform1f(U("uTick"), tick);
|
|
258
|
+
gl.uniform1f(U("uMode"), MODES.get(L.mode) ?? 0);
|
|
259
|
+
for (const [name, key] of NUMBER_UNIFORMS) gl.uniform1f(U(name), L[key]);
|
|
260
|
+
gl.uniform3fv(U("uInk"), hex(L.ink));
|
|
261
|
+
gl.uniform3fv(U("uPaperCol"), hex(L.paperColor));
|
|
262
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
window.ShortsPaint = { Painter, DEFAULTS };
|
|
268
|
+
})();
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* THE STAGE: which shot is on screen at a frame, the camera, the paint pass
|
|
3
|
+
* and the caption. window.SHORT (the timeline the CLI builds) and
|
|
4
|
+
* window.SCENES (the project's scenes.js) are loaded before this.
|
|
5
|
+
*
|
|
6
|
+
* window.drawing(d) → paints drawing d (held `twos` frames) and returns it as a JPEG data URL
|
|
7
|
+
* window.still(i, at) → shot i at progress `at` (0..1), for contact sheets
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
const S = window.SHORT;
|
|
11
|
+
const { W, H, fps, twos } = S;
|
|
12
|
+
|
|
13
|
+
function context2d(canvas: HTMLCanvasElement): CanvasRenderingContext2D {
|
|
14
|
+
const g = canvas.getContext("2d");
|
|
15
|
+
if (!g) throw new Error("2D canvas is not available");
|
|
16
|
+
return g;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function layer(): { canvas: HTMLCanvasElement; g: CanvasRenderingContext2D } {
|
|
20
|
+
const canvas = document.createElement("canvas");
|
|
21
|
+
canvas.width = W; canvas.height = H;
|
|
22
|
+
return { canvas, g: context2d(canvas) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { canvas: flat, g: fg } = layer();
|
|
26
|
+
const { canvas: under, g: ug } = layer();
|
|
27
|
+
const glc = document.createElement("canvas");
|
|
28
|
+
glc.width = W; glc.height = H;
|
|
29
|
+
const painter = window.ShortsPaint.Painter(glc);
|
|
30
|
+
const out = document.getElementById("out");
|
|
31
|
+
if (!(out instanceof HTMLCanvasElement)) throw new Error('no <canvas id="out"> on the page');
|
|
32
|
+
out.width = W; out.height = H;
|
|
33
|
+
const og = context2d(out);
|
|
34
|
+
|
|
35
|
+
const ease = (x: number) => x * x * (3 - 2 * x);
|
|
36
|
+
|
|
37
|
+
/** Scale and offset of the camera at progress eased e, strength a. */
|
|
38
|
+
type CameraPose = (a: number, e: number, p: number) => [number, number, number];
|
|
39
|
+
const CAMERA_MOVES = new Map<string, CameraPose>([
|
|
40
|
+
["push", (a, e) => [1 + 0.08 * a * e, 0, 0]],
|
|
41
|
+
["pull", (a, e) => [1 + 0.08 * a * (1 - e), 0, 0]],
|
|
42
|
+
["pan-left", (a, e) => [1.12, W * 0.05 * a * (1 - 2 * e), 0]],
|
|
43
|
+
["pan-right", (a, e) => [1.12, -W * 0.05 * a * (1 - 2 * e), 0]],
|
|
44
|
+
["rise", (a, e) => [1.1, 0, H * 0.04 * a * (1 - 2 * e)]],
|
|
45
|
+
["fall", (a, e) => [1.1, 0, -H * 0.04 * a * (1 - 2 * e)]],
|
|
46
|
+
["drift", (a, _e, p) => [1.05, W * 0.015 * a * Math.sin(p * 2.2), H * 0.008 * a * Math.cos(p * 1.7)]],
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/** The camera, as a transform about the frame centre. Moves are slow on purpose. */
|
|
50
|
+
function camera(g: CanvasRenderingContext2D, move: string | undefined, p: number, amount: number | undefined): void {
|
|
51
|
+
const a = amount ?? 1;
|
|
52
|
+
const e = ease(Math.min(1, Math.max(0, p)));
|
|
53
|
+
const [s, x, y] = (move === undefined ? undefined : CAMERA_MOVES.get(move))?.(a, e, p) ?? [1, 0, 0];
|
|
54
|
+
g.setTransform(s, 0, 0, s, W / 2 - (W / 2) * s + x, H / 2 - (H / 2) * s + y);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function drawMissingScene(g: CanvasRenderingContext2D, name: string): void {
|
|
58
|
+
g.fillStyle = "#c33"; g.fillRect(0, 0, W, H);
|
|
59
|
+
g.fillStyle = "#fff"; g.font = `${60 * H / 1920}px sans-serif`;
|
|
60
|
+
g.fillText(`no scene "${name}"`, 40, H / 2);
|
|
61
|
+
console.error(`no scene "${name}" in scenes.js`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function drawBeat(g: CanvasRenderingContext2D, i: number, t: number): void {
|
|
65
|
+
const b = S.beats[i];
|
|
66
|
+
const fn = window.SCENES[b.scene];
|
|
67
|
+
g.save();
|
|
68
|
+
g.setTransform(1, 0, 0, 1, 0, 0);
|
|
69
|
+
g.fillStyle = "#000";
|
|
70
|
+
g.fillRect(0, 0, W, H);
|
|
71
|
+
camera(g, b.move, t / b.dur, b.moveAmount);
|
|
72
|
+
if (!fn) drawMissingScene(g, b.scene);
|
|
73
|
+
else {
|
|
74
|
+
const k = window.ShortsKit.make(g, W, H, t, Math.min(1, t / b.dur), window.ShortsKit.hashStr(b.scene + (b.seed ?? "")), S.look.palette, b);
|
|
75
|
+
try { fn(k); } catch (e) { console.error(`scene "${b.scene}": ${e instanceof Error ? e.message : String(e)}`); }
|
|
76
|
+
}
|
|
77
|
+
g.restore();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function beatAt(time: number): number {
|
|
81
|
+
let i = 0;
|
|
82
|
+
while (i < S.beats.length - 1 && time >= S.beats[i + 1].start) i++;
|
|
83
|
+
return i;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function captionText(text: string, C: Captions): string {
|
|
87
|
+
return C.case === "upper" ? text.toUpperCase() : C.case === "lower" ? text.toLowerCase() : text;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function caption(text: string): void {
|
|
91
|
+
const C = S.captions || {};
|
|
92
|
+
if (!text || C.style === "none") return;
|
|
93
|
+
const size = (C.size ?? 44) * (H / 1920) * (W > H ? 1.25 : 1);
|
|
94
|
+
og.font = `${C.weight ?? 500} ${size}px ${C.font ?? '"Helvetica Neue", Helvetica, Arial, sans-serif'}`;
|
|
95
|
+
og.textAlign = "center";
|
|
96
|
+
og.textBaseline = "middle";
|
|
97
|
+
const lines = wrap(captionText(text, C), W * (C.width ?? 0.78), og);
|
|
98
|
+
const lh = size * 1.3;
|
|
99
|
+
const y0 = H * (C.y ?? 0.775) - ((lines.length - 1) * lh) / 2;
|
|
100
|
+
lines.forEach((ln, j) => {
|
|
101
|
+
const y = y0 + j * lh;
|
|
102
|
+
if ((C.style ?? "box") === "box") {
|
|
103
|
+
const w = og.measureText(ln).width + size * 0.7;
|
|
104
|
+
og.fillStyle = C.box ?? "rgba(20,20,20,0.62)";
|
|
105
|
+
og.fillRect(W / 2 - w / 2, y - lh / 2 + size * 0.04, w, lh - size * 0.08);
|
|
106
|
+
} else {
|
|
107
|
+
og.shadowColor = "rgba(0,0,0,0.75)";
|
|
108
|
+
og.shadowBlur = size * 0.35;
|
|
109
|
+
}
|
|
110
|
+
og.fillStyle = C.color ?? "#ffffff";
|
|
111
|
+
og.fillText(ln, W / 2, y + size * 0.04);
|
|
112
|
+
og.shadowBlur = 0;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function wrap(text: string, max: number, g: CanvasRenderingContext2D): string[] {
|
|
117
|
+
const words = text.split(/\s+/), lines: string[] = [];
|
|
118
|
+
let cur = "";
|
|
119
|
+
for (const w of words) {
|
|
120
|
+
const next = cur ? cur + " " + w : w;
|
|
121
|
+
if (g.measureText(next).width > max && cur) { lines.push(cur); cur = w; } else cur = next;
|
|
122
|
+
}
|
|
123
|
+
if (cur) lines.push(cur);
|
|
124
|
+
return lines;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The words on screen at a time: the caption track (chunks of the line, timed to the voice). */
|
|
128
|
+
function captionFor(time: number, b: Beat): string {
|
|
129
|
+
if (S.track) {
|
|
130
|
+
for (const c of S.track) if (time >= c.at && time < c.end) return c.text;
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
return b.caption ?? b.say ?? "";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** How long the soft cut into this shot lasts: "fade" or "dip", else none. */
|
|
137
|
+
function fadeFor(b: Beat): number {
|
|
138
|
+
return b.transition === "fade" ? (b.fadeDur ?? 0.5) : b.transition === "dip" ? (b.fadeDur ?? 0.7) : 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// a soft cut: the end of the previous shot fades under this one ("fade"), or through black ("dip")
|
|
142
|
+
function softCut(i: number, b: Beat, t: number, fade: number): void {
|
|
143
|
+
const prev = S.beats[i - 1];
|
|
144
|
+
fg.save();
|
|
145
|
+
if (b.transition === "dip") {
|
|
146
|
+
fg.globalAlpha = 1 - t / fade;
|
|
147
|
+
fg.fillStyle = "#000";
|
|
148
|
+
fg.fillRect(0, 0, W, H);
|
|
149
|
+
} else {
|
|
150
|
+
drawBeat(ug, i - 1, prev.dur);
|
|
151
|
+
fg.globalAlpha = 1 - t / fade;
|
|
152
|
+
fg.drawImage(under, 0, 0);
|
|
153
|
+
}
|
|
154
|
+
fg.restore();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function render(time: number, tick: number): void {
|
|
158
|
+
const i = beatAt(time);
|
|
159
|
+
const b = S.beats[i];
|
|
160
|
+
const t = time - b.start;
|
|
161
|
+
drawBeat(fg, i, t);
|
|
162
|
+
const fade = fadeFor(b);
|
|
163
|
+
if (fade && i > 0 && t < fade) softCut(i, b, t, fade);
|
|
164
|
+
painter.paint(flat, S.look, tick);
|
|
165
|
+
og.drawImage(glc, 0, 0);
|
|
166
|
+
caption(captionFor(time, b));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
window.drawing = (d) => {
|
|
170
|
+
const time = (d * twos) / fps + 0.0001;
|
|
171
|
+
render(time, d);
|
|
172
|
+
return out.toDataURL("image/jpeg", 0.93);
|
|
173
|
+
};
|
|
174
|
+
window.still = (i, at) => {
|
|
175
|
+
const b = S.beats[i];
|
|
176
|
+
const time = b.start + b.dur * at;
|
|
177
|
+
render(time, Math.round((time * fps) / twos));
|
|
178
|
+
return out.toDataURL("image/jpeg", 0.9);
|
|
179
|
+
};
|
|
180
|
+
window.ready = true;
|
|
181
|
+
})();
|