@pixerate/editor 0.223.6 → 0.223.8

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.
Files changed (23) hide show
  1. package/dist/tools/skill-evals.d.cts +23 -1
  2. package/dist/tools/skill-evals.d.ts +23 -1
  3. package/package.json +1 -1
  4. package/skills/README.md +8 -3
  5. package/skills/pixerate-accessibility-audit/SKILL.md +98 -0
  6. package/skills/pixerate-accessibility-audit/evals/button-label-contrast.json +97 -0
  7. package/skills/pixerate-accessibility-audit/evals/distinct-for-cvd.json +160 -0
  8. package/skills/pixerate-accessibility-audit/evals/icon-non-text-contrast.json +109 -0
  9. package/skills/pixerate-brand-application/SKILL.md +99 -0
  10. package/skills/pixerate-brand-application/evals/apply-palette.json +148 -0
  11. package/skills/pixerate-brand-application/evals/readable-on-brand.json +104 -0
  12. package/skills/pixerate-brand-application/evals/snap-to-palette.json +148 -0
  13. package/skills/pixerate-color/SKILL.md +8 -0
  14. package/skills/pixerate-effect-authoring/SKILL.md +7 -0
  15. package/skills/pixerate-effect-look-development/SKILL.md +96 -0
  16. package/skills/pixerate-effect-look-development/evals/fade-in.json +55 -0
  17. package/skills/pixerate-effect-look-development/evals/seamless-loop.json +66 -0
  18. package/skills/pixerate-effect-look-development/evals/subtle-preset.json +49 -0
  19. package/skills/pixerate-generation/SKILL.md +7 -0
  20. package/skills/pixerate-generation-budgeted-batch/SKILL.md +99 -0
  21. package/skills/pixerate-generation-budgeted-batch/evals/drafts-within-budget.json +101 -0
  22. package/skills/pixerate-generation-budgeted-batch/evals/right-model-for-format.json +124 -0
  23. package/skills/pixerate-generation-budgeted-batch/evals/vary-earlier.json +101 -0
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: pixerate-brand-application
3
+ description: The brand application workflow across Pixerate editors - applying a brand palette, type and tokens to a design, chart or document, mapping each element to its brand role (background, primary, accent, text), snapping near-miss colours to the palette, keeping text readable on brand colours, deriving tints and dark-mode variants without leaving the brand, and running the house-style checks. Use when asked to brand, rebrand, apply a brand, style guide, palette, theme or design tokens, make something on-brand, snap colours to a palette, or check brand consistency, or when check_color_contrast, check_color_palette, blend_colors or a check_*_style tool is available. Load with pixerate-color, which lists the colour tools, and the skill for the editor that holds the design.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-color
7
+ ---
8
+
9
+ # Applying a brand
10
+
11
+ A brand is a small set of decisions: a palette with roles (background, primary, accent, text),
12
+ type, and rules for using them. Applying it means mapping every element of a design to a role
13
+ and giving it that role's value exactly, not something close. Colours outside the palette are
14
+ either snapped to the nearest brand colour or derived from one deliberately (a tint, a dark-mode
15
+ variant), and every text pairing still meets contrast.
16
+
17
+ This skill is the method. `pixerate-color` has the colour tools; the design's editor applies the
18
+ values (`vector.setStyle`, `update_chart_series`), design tokens live in `pixerate-tokens`, type
19
+ in `pixerate-typography`, and a host's house style in `check_*_style` tools.
20
+
21
+ ## Goal and quality bar
22
+
23
+ - Every colour in the design is a brand colour, or a documented derivative of one (a tint, a
24
+ shade, a dark-mode pair).
25
+ - Each element has the colour of its role: backgrounds, primary surfaces, accents, text.
26
+ - Text on every brand surface meets WCAG AA (see `pixerate-accessibility-audit`).
27
+ - Values are exact (`#1d3557`, not `#1f3759`), referenced from tokens where the editor supports
28
+ them.
29
+ - Layout, geometry and content are unchanged by the rebrand.
30
+ - House-style checks pass where the host provides them.
31
+
32
+ ## Inputs to confirm
33
+
34
+ - **The palette with roles**, or a tokens set (`pixerate-tokens`), and the type.
35
+ - **Which elements take which role** when it is not obvious.
36
+ - **What to do when a pairing cannot meet contrast** (another palette colour, larger text, or an
37
+ approved derivative).
38
+
39
+ ## Working method
40
+
41
+ 1. **Read the design** with its editor: every element's fills, strokes and text colours.
42
+ **Check:** each element has a role.
43
+ 2. **Map roles to brand values.** Write the mapping (background → `#f1faee`, panel → primary,
44
+ badge → accent) before changing anything.
45
+ 3. **Apply exactly**, in one batch where the editor allows it (`run_commands` of
46
+ `vector.setStyle`, one `update_chart` call).
47
+ **Check:** reading the design again shows only brand values, and geometry unchanged.
48
+ 4. **Snap near-misses.** For colours close to the palette, pick the nearest brand colour by
49
+ perceptual distance (OKLab): `inspect_color` gives each colour's OKLCH for comparison.
50
+ 5. **Readable text.** `check_color_contrast` for each text-on-surface pair; when white fails on a
51
+ light brand colour, use the darkest brand colour for the text.
52
+ **Check:** every text pair passes AA.
53
+ 6. **Derivatives.** Tints and shades with `blend_colors` in OKLab from a brand colour; dark-mode
54
+ pairs with `derive_dual_color`. Name and report them.
55
+ 7. **House style.** Run the host's `check_*_style` tools (for example `check_chart_style`) and fix
56
+ what they report.
57
+
58
+ ## Decision table
59
+
60
+ | Situation | Approach |
61
+ | --- | --- |
62
+ | A colour close to a brand colour | Snap to the nearest by OKLab distance |
63
+ | A needed colour not in the palette | Tint or shade a brand colour (`blend_colors`), and say so |
64
+ | White text fails on a light brand colour | The darkest brand colour for text |
65
+ | No palette colour passes on a surface | Report it; propose larger text or another surface colour |
66
+ | A dark mode is needed | `derive_dual_color` from each brand colour |
67
+ | Charts | Series colours from the palette in its order, checked with `check_chart_colors` |
68
+ | Tokens available | Apply token references, not raw values |
69
+
70
+ ## Recipes
71
+
72
+ **Apply a palette.** Read element ids → one `run_commands` with a `vector.setStyle` per element
73
+ and its brand `fill` → read again to confirm values and bounds.
74
+
75
+ **Snap to the palette.** `inspect_color` each off-brand colour and each brand colour; pick the
76
+ smallest OKLab distance; apply.
77
+
78
+ **Readable badge text.** `check_color_contrast` white and the darkest brand colour on the badge;
79
+ apply the one that passes.
80
+
81
+ ## Pitfalls
82
+
83
+ - **Close is not on-brand.** `#1f3759` beside `#1d3557` looks the same and fails every audit.
84
+ - **Inventing colours** to fix contrast leaves the brand; use a palette colour or an approved
85
+ derivative.
86
+ - **Some pairings can never pass**: no text colour reaches 4.5:1 on a mid-tone red or blue.
87
+ Report it instead of breaking the brand silently.
88
+ - **Rebranding moves things**: change only colours and type, not geometry or content.
89
+ - **Accent overuse**: accents mark what matters; most of a design is background and primary.
90
+ - **Hue-only palettes** for charts fail colour-blind readers; check them.
91
+
92
+ ## Tools used
93
+
94
+ - `inspect_color`: parse colours and compare them in OKLCH
95
+ - `check_color_contrast`: text on brand surfaces
96
+ - `check_color_palette`: brand colours used for meaning, under colour-vision deficiencies
97
+ - `blend_colors`: tints and shades of brand colours
98
+ - `derive_dual_color`, `check_dual_color_contrast`: dark-mode variants
99
+ - `suggest_color_harmony`: supporting colours when the palette has gaps
@@ -0,0 +1,148 @@
1
+ {
2
+ "prompt": "Apply our brand palette to this layout: the background is #f1faee, the big panel is primary #1d3557, and the small badge is accent #e63946.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 200,
6
+ "height": 120,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "rect",
13
+ "x": 0,
14
+ "y": 0,
15
+ "width": 200,
16
+ "height": 120,
17
+ "name": "Background",
18
+ "fill": "#ffffff",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "rect",
26
+ "x": 20,
27
+ "y": 20,
28
+ "width": 160,
29
+ "height": 80,
30
+ "name": "Panel",
31
+ "fill": "#334455",
32
+ "stroke": null
33
+ }
34
+ },
35
+ {
36
+ "id": "vector.addShape",
37
+ "params": {
38
+ "kind": "ellipse",
39
+ "x": 150,
40
+ "y": 10,
41
+ "width": 30,
42
+ "height": 30,
43
+ "name": "Badge",
44
+ "fill": "#ff6600",
45
+ "stroke": null
46
+ }
47
+ }
48
+ ]
49
+ },
50
+ "checks": [
51
+ {
52
+ "why": "each part in its brand colour",
53
+ "tool": "run_command",
54
+ "args": {
55
+ "id": "vector.list"
56
+ },
57
+ "expect": {
58
+ "result": {
59
+ "nodes": {
60
+ "$expr": "(() => { const f = (n) => (v.find((x) => x.name === n).fill || '').toLowerCase(); return f('Background') === '#f1faee' && f('Panel') === '#1d3557' && f('Badge') === '#e63946'; })()"
61
+ }
62
+ }
63
+ }
64
+ },
65
+ {
66
+ "why": "nothing moved",
67
+ "tool": "run_command",
68
+ "args": {
69
+ "id": "vector.list"
70
+ },
71
+ "expect": {
72
+ "result": {
73
+ "nodes": {
74
+ "$some": {
75
+ "name": "Badge",
76
+ "bounds": {
77
+ "x": 150,
78
+ "y": 10,
79
+ "width": 30,
80
+ "height": 30
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ ],
88
+ "solution": [
89
+ {
90
+ "tool": "run_command",
91
+ "args": {
92
+ "id": "vector.list"
93
+ }
94
+ },
95
+ {
96
+ "tool": "run_commands",
97
+ "args": {
98
+ "steps": [
99
+ {
100
+ "id": "vector.setStyle",
101
+ "params": {
102
+ "ids": [
103
+ {
104
+ "$read": "run_command",
105
+ "args": {
106
+ "id": "vector.list"
107
+ },
108
+ "path": "result.nodes.0.id"
109
+ }
110
+ ],
111
+ "fill": "#f1faee"
112
+ }
113
+ },
114
+ {
115
+ "id": "vector.setStyle",
116
+ "params": {
117
+ "ids": [
118
+ {
119
+ "$read": "run_command",
120
+ "args": {
121
+ "id": "vector.list"
122
+ },
123
+ "path": "result.nodes.1.id"
124
+ }
125
+ ],
126
+ "fill": "#1d3557"
127
+ }
128
+ },
129
+ {
130
+ "id": "vector.setStyle",
131
+ "params": {
132
+ "ids": [
133
+ {
134
+ "$read": "run_command",
135
+ "args": {
136
+ "id": "vector.list"
137
+ },
138
+ "path": "result.nodes.2.id"
139
+ }
140
+ ],
141
+ "fill": "#e63946"
142
+ }
143
+ }
144
+ ]
145
+ }
146
+ }
147
+ ]
148
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "prompt": "The white text on our light-blue badge fails contrast. Fix it using a colour from our palette (#1d3557, #457b9d, #a8dadc, #f1faee, #e63946), keeping the badge light blue.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 120,
6
+ "height": 60,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "rect",
13
+ "x": 10,
14
+ "y": 10,
15
+ "width": 100,
16
+ "height": 40,
17
+ "name": "Badge",
18
+ "fill": "#a8dadc",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "rect",
26
+ "x": 30,
27
+ "y": 24,
28
+ "width": 60,
29
+ "height": 12,
30
+ "name": "Text",
31
+ "fill": "#ffffff",
32
+ "stroke": null
33
+ }
34
+ }
35
+ ]
36
+ },
37
+ "checks": [
38
+ {
39
+ "why": "the text uses a palette colour that reads on the badge at 4.5:1 or more",
40
+ "tool": "run_command",
41
+ "args": {
42
+ "id": "vector.list"
43
+ },
44
+ "expect": {
45
+ "result": {
46
+ "nodes": {
47
+ "$expr": "(() => { const L = (h) => { const c = [1, 3, 5].map((i) => parseInt(h.replace('#', '').padEnd(6, '0').slice(i - 1, i + 1), 16) / 255).map((x) => (x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4)); return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; }; const cr = (a, b) => { const [x, y] = [L(a), L(b)].sort((p, q) => q - p); return (x + 0.05) / (y + 0.05); }; const t = v.find((n) => n.name === 'Text').fill.toLowerCase(); return ['#1d3557', '#457b9d', '#a8dadc', '#f1faee', '#e63946'].includes(t) && cr(t, v.find((n) => n.name === 'Badge').fill) >= 4.5; })()"
48
+ }
49
+ }
50
+ }
51
+ },
52
+ {
53
+ "why": "the badge stays light blue",
54
+ "tool": "run_command",
55
+ "args": {
56
+ "id": "vector.list"
57
+ },
58
+ "expect": {
59
+ "result": {
60
+ "nodes": {
61
+ "$some": {
62
+ "name": "Badge",
63
+ "fill": "#a8dadc"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ],
70
+ "solution": [
71
+ {
72
+ "tool": "check_color_contrast",
73
+ "args": {
74
+ "foreground": "#ffffff",
75
+ "background": "#a8dadc"
76
+ }
77
+ },
78
+ {
79
+ "tool": "check_color_contrast",
80
+ "args": {
81
+ "foreground": "#1d3557",
82
+ "background": "#a8dadc"
83
+ }
84
+ },
85
+ {
86
+ "tool": "run_command",
87
+ "args": {
88
+ "id": "vector.setStyle",
89
+ "params": {
90
+ "ids": [
91
+ {
92
+ "$read": "run_command",
93
+ "args": {
94
+ "id": "vector.list"
95
+ },
96
+ "path": "result.nodes.1.id"
97
+ }
98
+ ],
99
+ "fill": "#1d3557"
100
+ }
101
+ }
102
+ }
103
+ ]
104
+ }
@@ -0,0 +1,148 @@
1
+ {
2
+ "prompt": "These colours are almost our brand colours but not exact. Snap each shape to the nearest colour in the palette: #1d3557, #457b9d, #a8dadc, #e63946.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 160,
6
+ "height": 40,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "rect",
13
+ "x": 0,
14
+ "y": 0,
15
+ "width": 40,
16
+ "height": 40,
17
+ "name": "A",
18
+ "fill": "#1f3759",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "rect",
26
+ "x": 40,
27
+ "y": 0,
28
+ "width": 40,
29
+ "height": 40,
30
+ "name": "B",
31
+ "fill": "#4a7fa0",
32
+ "stroke": null
33
+ }
34
+ },
35
+ {
36
+ "id": "vector.addShape",
37
+ "params": {
38
+ "kind": "rect",
39
+ "x": 80,
40
+ "y": 0,
41
+ "width": 40,
42
+ "height": 40,
43
+ "name": "C",
44
+ "fill": "#aadcde",
45
+ "stroke": null
46
+ }
47
+ },
48
+ {
49
+ "id": "vector.addShape",
50
+ "params": {
51
+ "kind": "rect",
52
+ "x": 120,
53
+ "y": 0,
54
+ "width": 40,
55
+ "height": 40,
56
+ "name": "D",
57
+ "fill": "#e43b48",
58
+ "stroke": null
59
+ }
60
+ }
61
+ ]
62
+ },
63
+ "checks": [
64
+ {
65
+ "why": "each shape in its nearest brand colour",
66
+ "tool": "run_command",
67
+ "args": {
68
+ "id": "vector.list"
69
+ },
70
+ "expect": {
71
+ "result": {
72
+ "nodes": {
73
+ "$expr": "v.map((n) => (n.fill || '').toLowerCase()).join() === '#1d3557,#457b9d,#a8dadc,#e63946'"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ],
79
+ "solution": [
80
+ {
81
+ "tool": "run_commands",
82
+ "args": {
83
+ "steps": [
84
+ {
85
+ "id": "vector.setStyle",
86
+ "params": {
87
+ "ids": [
88
+ {
89
+ "$read": "run_command",
90
+ "args": {
91
+ "id": "vector.list"
92
+ },
93
+ "path": "result.nodes.0.id"
94
+ }
95
+ ],
96
+ "fill": "#1d3557"
97
+ }
98
+ },
99
+ {
100
+ "id": "vector.setStyle",
101
+ "params": {
102
+ "ids": [
103
+ {
104
+ "$read": "run_command",
105
+ "args": {
106
+ "id": "vector.list"
107
+ },
108
+ "path": "result.nodes.1.id"
109
+ }
110
+ ],
111
+ "fill": "#457b9d"
112
+ }
113
+ },
114
+ {
115
+ "id": "vector.setStyle",
116
+ "params": {
117
+ "ids": [
118
+ {
119
+ "$read": "run_command",
120
+ "args": {
121
+ "id": "vector.list"
122
+ },
123
+ "path": "result.nodes.2.id"
124
+ }
125
+ ],
126
+ "fill": "#a8dadc"
127
+ }
128
+ },
129
+ {
130
+ "id": "vector.setStyle",
131
+ "params": {
132
+ "ids": [
133
+ {
134
+ "$read": "run_command",
135
+ "args": {
136
+ "id": "vector.list"
137
+ },
138
+ "path": "result.nodes.3.id"
139
+ }
140
+ ],
141
+ "fill": "#e63946"
142
+ }
143
+ }
144
+ ]
145
+ }
146
+ }
147
+ ]
148
+ }
@@ -7,6 +7,14 @@ description: How to inspect colours, check and optimize WCAG contrast and access
7
7
 
8
8
  The colour module provides DOM-free colour tools for inspection, WCAG accessibility validation, colour-vision deficiency simulation, palette audits, colour harmony suggestions, and perceptual blending.
9
9
 
10
+ ## Workflow skills
11
+
12
+ For these jobs, load the workflow skill too: it has the method, the checks between steps and
13
+ the pitfalls, and uses the tools below.
14
+
15
+ - `pixerate-accessibility-audit`: contrast, colour-blind-safe meaning, both modes, fixes and a report.
16
+ - `pixerate-brand-application`: brand roles, exact palette values, readable text, derivatives.
17
+
10
18
  ## Working method
11
19
 
12
20
  1. **Inspect and convert.** Run `inspect_color` to parse any hex, `rgb()`, `hsl()`, `oklch()`, or CSS named colour into standard formats (hex, rgb, hsl, oklch) and relative luminance, and to check if it fits within the sRGB gamut.
@@ -17,6 +17,13 @@ preview frames.
17
17
 
18
18
  For general error reporting and undo conventions, see `pixerate-agent-tools`.
19
19
 
20
+ ## Workflow skills
21
+
22
+ For these jobs, load the workflow skill too: it has the method, the checks between steps and
23
+ the pitfalls, and uses the tools below.
24
+
25
+ - `pixerate-effect-look-development`: tune a look, save presets, eased keyframes, seamless loops.
26
+
20
27
  ## Working method
21
28
 
22
29
  1. **Inspect first.** Run `effect_authoring_describe` to inspect the effect name, kind, source
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: pixerate-effect-look-development
3
+ description: The look development workflow for Pixerate effects - reading an effect's controls, finding the look with a few deliberate value changes, saving looks as named presets, animating controls with keyframes and easing, setting duration and looping for seamless cycles, previewing at key moments, and editing shader source only when controls cannot get there. Use when asked to design, tune, tweak or animate an effect, shader, filter or procedural look, make a preset or variant of a look, make an effect fade in or out, pulse or loop, or when effect_authoring_describe, effect_authoring_set_values, effect_authoring_set_keyframe or effect_authoring_save_preset are available. Load with pixerate-effect-authoring, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-effect-authoring
7
+ ---
8
+
9
+ # Developing a look
10
+
11
+ An effect is a set of controls over a shader or procedure. A look is a set of values for those
12
+ controls, kept as a named preset; an animated look adds keyframed tracks over a duration. Find
13
+ the look with a few deliberate moves (start from a preset, change one control at a time, look),
14
+ save it, and animate only what should change over time.
15
+
16
+ This skill is the method. `pixerate-effect-authoring` has the full tool reference.
17
+
18
+ ## Goal and quality bar
19
+
20
+ - Every value is within its control's range (`effect_authoring_describe` lists `min`, `max`,
21
+ `default`).
22
+ - The look is saved as a preset with a clear name, so it can be applied again.
23
+ - Animation uses as few keyframes as the motion needs, with easing (`ease-out` to arrive,
24
+ `ease-in-out` for a pulse); duration fits the use.
25
+ - A loop is seamless: `loop` on, and the animated values match at the start and the end.
26
+ - `issues` is empty and `previewError` is null.
27
+
28
+ ## Inputs to confirm
29
+
30
+ - **The look** in words (subtle, strong, warm, glitchy) or a reference.
31
+ - **Static or animated**, and the duration and looping for animation.
32
+ - **Whether to keep** the result as a preset, and its name.
33
+
34
+ ## Working method
35
+
36
+ 1. **Read.** `effect_authoring_describe`: controls with ranges and defaults, current values,
37
+ presets, tracks, duration, loop, issues.
38
+ **Check:** you know which control does what.
39
+ 2. **Start close.** `effect_authoring_apply_preset` when a preset is near the look;
40
+ `effect_authoring_reset_values` to start clean.
41
+ 3. **Tune.** `effect_authoring_set_values` with only the controls you change, a few at a time;
42
+ `effect_authoring_view` to look.
43
+ **Check:** `values` in `effect_authoring_describe` are what you set; no `issues`.
44
+ 4. **Save.** `effect_authoring_save_preset` with a descriptive `name` ("Subtle", "Warm glow").
45
+ **Check:** the name is in `presets`.
46
+ 5. **Animate.** `effect_authoring_set_timing` for `duration` and `loop`; then
47
+ `effect_authoring_set_keyframe` per control at the times that matter, with `easing` into the
48
+ next keyframe. For a loop, the last keyframe's value equals the first's.
49
+ **Check:** `tracks` list the control with the keyframe count you meant.
50
+ 6. **Preview** with `effect_authoring_seek` to key times and `effect_authoring_view`.
51
+ 7. **Source last.** `effect_authoring_get_source` / `effect_authoring_set_source` only when no
52
+ control can produce the look; read the returned issues and fix them.
53
+
54
+ ## Decision table
55
+
56
+ | Want | Approach |
57
+ | --- | --- |
58
+ | A softer or stronger version of a look | Scale the main control (strength, amount); save as a preset |
59
+ | A starting point | `effect_authoring_apply_preset` |
60
+ | Back to defaults | `effect_authoring_reset_values` |
61
+ | Fade in | Keyframes from the neutral value to the look, `ease-out` |
62
+ | Pulse or breathe | Three keyframes (rest, peak, rest), `ease-in-out`, loop on |
63
+ | A one-off change at a moment | Two keyframes around the moment |
64
+ | Remove animation from a control | `effect_authoring_clear_track` |
65
+ | Something no control does | Edit the source |
66
+
67
+ ## Recipes
68
+
69
+ **Quarter-strength preset.** `effect_authoring_set_values` `{ values: { amount: 0.25 } }` →
70
+ `effect_authoring_save_preset` `{ name: "Subtle" }`.
71
+
72
+ **Fade in over 1.5 s in a 2 s effect.** `effect_authoring_set_timing` `{ duration: 2000 }` →
73
+ `effect_authoring_set_keyframe` `{ control: "amount", time: 0, value: 0 }` → `{ control:
74
+ "amount", time: 1500, value: 1, easing: "ease-out" }`.
75
+
76
+ **Seamless 3 s pulse.** `effect_authoring_set_timing` `{ duration: 3000, loop: true }` → keyframes
77
+ at 0 (0), 1500 (1, `ease-in-out`) and 3000 (0, `ease-in-out`).
78
+
79
+ ## Pitfalls
80
+
81
+ - **Values out of range** are clamped or refused; read the control's `min` and `max`.
82
+ - **Many keyframes** where three would do make motion lumpy and hard to edit.
83
+ - **Linear easing** on organic motion looks mechanical.
84
+ - **Loops that jump**: the end value must equal the start value.
85
+ - **Overwriting a preset** by saving under an existing name; choose a new name unless replacing
86
+ is intended.
87
+ - **Editing source first**: controls are safer and keep presets working.
88
+
89
+ ## Tools used
90
+
91
+ - `effect_authoring_describe`, `effect_authoring_view`, `effect_authoring_seek`: read, look and preview
92
+ - `effect_authoring_set_values`, `effect_authoring_reset_values`, `effect_authoring_apply_preset`: find the look
93
+ - `effect_authoring_save_preset`: keep the look
94
+ - `effect_authoring_set_timing`, `effect_authoring_set_keyframe`, `effect_authoring_remove_keyframe`, `effect_authoring_clear_track`: animate
95
+ - `effect_authoring_get_source`, `effect_authoring_set_source`: edit the shader when needed
96
+ - `effect_authoring_undo`, `effect_authoring_redo`: take back a step
@@ -0,0 +1,55 @@
1
+ {
2
+ "prompt": "Animate the effect fading in: no effect at the start, full strength by 1.5 seconds, in a 2-second effect.",
3
+ "fixture": {
4
+ "editor": "effect",
5
+ "effectId": "fake.invert"
6
+ },
7
+ "checks": [
8
+ {
9
+ "why": "a 2-second effect",
10
+ "tool": "effect_authoring_describe",
11
+ "expect": {
12
+ "duration": 2000
13
+ }
14
+ },
15
+ {
16
+ "why": "the amount is keyframed (at least a start and an end)",
17
+ "tool": "effect_authoring_describe",
18
+ "expect": {
19
+ "tracks": {
20
+ "$some": {
21
+ "control": "amount",
22
+ "keyframes": {
23
+ "$min": 2
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ],
30
+ "solution": [
31
+ {
32
+ "tool": "effect_authoring_set_timing",
33
+ "args": {
34
+ "duration": 2000
35
+ }
36
+ },
37
+ {
38
+ "tool": "effect_authoring_set_keyframe",
39
+ "args": {
40
+ "control": "amount",
41
+ "time": 0,
42
+ "value": 0
43
+ }
44
+ },
45
+ {
46
+ "tool": "effect_authoring_set_keyframe",
47
+ "args": {
48
+ "control": "amount",
49
+ "time": 1500,
50
+ "value": 1,
51
+ "easing": "ease-out"
52
+ }
53
+ }
54
+ ]
55
+ }