@pixerate/editor 0.223.7 → 0.223.9

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.
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
20
20
  }
21
21
  export interface SkillEval {
22
22
  prompt: string;
23
- fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture;
23
+ fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture | CanvasEvalFixture;
24
24
  checks: SkillEvalCheck[];
25
25
  solution: SkillEvalToolCall[];
26
26
  }
@@ -54,6 +54,8 @@ export interface VectorEvalFixture {
54
54
  id: string;
55
55
  params?: Record<string, unknown>;
56
56
  }>;
57
+ /** Also register the colour tools (`check_color_contrast`, `check_color_palette`, ...). */
58
+ colorTools?: boolean;
57
59
  }
58
60
  export interface PaintEvalFixture {
59
61
  editor: 'paint';
@@ -107,6 +109,13 @@ export interface EffectEvalFixture {
107
109
  /** Tool calls that prepare the session, run in order. */
108
110
  setup?: SkillEvalToolCall[];
109
111
  }
112
+ export interface CanvasEvalFixture {
113
+ editor: 'canvas';
114
+ /** The starting nodes (React Flow style: `id`, `type`, `position`, `width`, `height`, `data`, `parentId`). */
115
+ nodes: Array<Record<string, unknown>>;
116
+ /** Tool calls that prepare the canvas (components, instances), run in order. */
117
+ setup?: SkillEvalToolCall[];
118
+ }
110
119
  /** One eval file of a skill. */
111
120
  export interface SkillEvalFile {
112
121
  skill: string;
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
20
20
  }
21
21
  export interface SkillEval {
22
22
  prompt: string;
23
- fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture;
23
+ fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture | CanvasEvalFixture;
24
24
  checks: SkillEvalCheck[];
25
25
  solution: SkillEvalToolCall[];
26
26
  }
@@ -54,6 +54,8 @@ export interface VectorEvalFixture {
54
54
  id: string;
55
55
  params?: Record<string, unknown>;
56
56
  }>;
57
+ /** Also register the colour tools (`check_color_contrast`, `check_color_palette`, ...). */
58
+ colorTools?: boolean;
57
59
  }
58
60
  export interface PaintEvalFixture {
59
61
  editor: 'paint';
@@ -107,6 +109,13 @@ export interface EffectEvalFixture {
107
109
  /** Tool calls that prepare the session, run in order. */
108
110
  setup?: SkillEvalToolCall[];
109
111
  }
112
+ export interface CanvasEvalFixture {
113
+ editor: 'canvas';
114
+ /** The starting nodes (React Flow style: `id`, `type`, `position`, `width`, `height`, `data`, `parentId`). */
115
+ nodes: Array<Record<string, unknown>>;
116
+ /** Tool calls that prepare the canvas (components, instances), run in order. */
117
+ setup?: SkillEvalToolCall[];
118
+ }
110
119
  /** One eval file of a skill. */
111
120
  export interface SkillEvalFile {
112
121
  skill: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.223.7",
3
+ "version": "0.223.9",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
package/skills/README.md CHANGED
@@ -65,6 +65,10 @@ request like "clean up this export" finds it.
65
65
  | [`pixerate-animation-logo-reveal`](pixerate-animation-logo-reveal/SKILL.md) | Animation (`pixerate-animation-editor`) | A short reveal built from the logo's parts, landing on the beat and ending on the held, finished logo. |
66
66
  | [`pixerate-generation-budgeted-batch`](pixerate-generation-budgeted-batch/SKILL.md) | Generation (`pixerate-generation`) | The right model for the format, plan and estimate before spending, drafts cheap and finals once, safe retries, variations from the history. |
67
67
  | [`pixerate-effect-look-development`](pixerate-effect-look-development/SKILL.md) | Effects (`pixerate-effect-authoring`) | Tune a look in a few moves, save it as a preset, animate with eased keyframes, seamless loops. |
68
+ | [`pixerate-accessibility-audit`](pixerate-accessibility-audit/SKILL.md) | Cross-domain (`pixerate-color`) | Text and non-text contrast, colour-blind-safe meaning, both modes, smallest fixes and a report, in any editor. |
69
+ | [`pixerate-brand-application`](pixerate-brand-application/SKILL.md) | Cross-domain (`pixerate-color`) | Brand roles mapped to exact palette values, near-misses snapped, readable text on brand colours, derivatives and house-style checks. |
70
+ | [`pixerate-canvas-design-system-components`](pixerate-canvas-design-system-components/SKILL.md) | Canvas (`pixerate-canvas`) | Main components and linked instances, component sets with named variant properties, overrides kept linked. |
71
+ | [`pixerate-canvas-layout`](pixerate-canvas-layout/SKILL.md) | Canvas (`pixerate-canvas`) | Align, distribute, order and lock with the arrange commands, changing only the intended axis. |
68
72
  | [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Video (`pixerate-video-editor`) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. Owns the dialogue tools. |
69
73
  | [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Video (`pixerate-video-editor`) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. Owns the music tools. |
70
74
 
@@ -128,12 +132,13 @@ same change.
128
132
  `packages/core/tests/agent-skill-evals.test.ts`. An eval has a `prompt` (the task, in the
129
133
  user's words), a `fixture` (the starting document: `{ "editor": "spreadsheet", "rows" }`,
130
134
  `{ "editor": "image", "width", "height", "background", "shapes" }` or `{ "editor":
131
- "vector", "width", "height", "setup" }` with commands that draw the starting art,
135
+ "vector", "width", "height", "setup", "colorTools" }` with commands that draw the starting art,
132
136
  `{ "editor": "paint", "width", "height", "setup" }` `{ "editor": "video", "setup" }` or `{ "editor":
133
137
  "chart", "setup" }` `{ "editor": "diagram", "setup" }` with tool calls,
134
138
  `{ "editor": "rich-text", "html" }`,
135
139
  `{ "editor": "animation", "setup" }`, `{ "editor": "generation", "models", "prices", "history" }`
136
- (a fake host that records each generation) or `{ "editor": "effect", "effectId", "setup" }`), `checks` (a read-only
140
+ (a fake host that records each generation) `{ "editor": "effect", "effectId", "setup" }` or `{ "editor": "canvas",
141
+ "nodes", "setup" }` (a headless canvas with the component, arrange and document tools)), `checks` (a read-only
137
142
  tool, or `run_command` with a read-only command such as `vector.list`, its `args`, what its result must match in `expect`, and `why`) and a `solution` (the
138
143
  tool calls an expert would make). The test proves each eval: some check fails on the fixture,
139
144
  and all pass after the solution. A model harness runs the same file with the model in place
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: pixerate-accessibility-audit
3
+ description: The accessibility audit workflow across Pixerate editors - checking text contrast (WCAG AA 4.5:1, large text 3:1), non-text contrast for icons, borders and controls (3:1), colour-only meaning and palettes under colour-vision deficiencies, light and dark modes, type readability, and fixing what fails with the smallest change, then reporting what was checked and changed. Use when asked to audit, check or fix accessibility, contrast, legibility, readability or colour-blind safety of a design, chart, icon, document, UI or brand palette, or to meet WCAG, or when check_color_contrast, check_color_palette or check_dual_color_contrast are 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
+ # Auditing accessibility
10
+
11
+ An accessibility audit finds what some readers cannot see or tell apart, fixes it with the
12
+ smallest change that keeps the design's intent, and reports both. Measure, never eyeball:
13
+ every text and background pair gets a contrast ratio, every set of colours that carries meaning
14
+ gets a colour-vision check, and every fix is measured again.
15
+
16
+ This skill is the method. `pixerate-color` has the colour tools; read colours from the design
17
+ with its editor's tools (`vector.list` in the vector editor, `get_chart` and
18
+ `check_chart_colors` for charts, `measure_region` for an image).
19
+
20
+ ## Goal and quality bar
21
+
22
+ - Normal text at least 4.5:1 against its background, large text (24 px, or 18.5 px bold) at
23
+ least 3:1 (`check_color_contrast`, with `large: true` for large text).
24
+ - Icons, borders, focus rings, chart lines and control outlines at least 3:1 against what they
25
+ sit on.
26
+ - Colours that carry meaning (status, series, categories) distinct under protanopia,
27
+ deuteranopia and tritanopia (`check_color_palette` `ok: true`), and not the only cue (add a
28
+ label, shape or pattern where meaning matters).
29
+ - Both light and dark modes pass when the design has them (`check_dual_color_contrast`).
30
+ - Fixes change as little as possible: the text colour rather than the brand background, a
31
+ darker shade of the same hue rather than a new hue.
32
+ - A report lists each pair checked, its ratio before and after, and what changed.
33
+
34
+ ## Inputs to confirm
35
+
36
+ - **The standard:** WCAG 2 AA (the default) or AAA.
37
+ - **What may change:** usually text and icon colours; brand backgrounds and logos only with
38
+ approval.
39
+ - **Modes:** light, dark or both.
40
+
41
+ ## Working method
42
+
43
+ 1. **Inventory.** Read every foreground and background pair from the design's editor: text on
44
+ surfaces, icons on cards, lines on plots, status colours.
45
+ **Check:** each pair has a role (normal text, large text, graphic, decorative).
46
+ 2. **Measure.** `check_color_contrast` for each pair (`large: true` for large text);
47
+ `check_color_palette` for sets of meaningful colours; `check_dual_color_contrast` for dual
48
+ colours.
49
+ **Check:** a list of failures with their ratios.
50
+ 3. **Fix the smallest thing.** Use the `suggestion` the contrast check returns (a lightness that
51
+ passes), or a darker or lighter shade of the same hue; for palettes, replace the clashing
52
+ colours with ones that differ in lightness as well as hue (blue and orange rather than red and
53
+ green). Apply with the editor's own tools (`vector.setStyle`, `update_chart_series`).
54
+ **Check:** measure the pair again; it passes.
55
+ 4. **Meaning without colour.** Where a palette carries meaning, make sure a label, icon or shape
56
+ carries it too.
57
+ 5. **Report** pairs, ratios before and after, and anything left that needs a decision (a brand
58
+ colour that cannot pass with any text colour).
59
+
60
+ ## Decision table
61
+
62
+ | Element | Requirement | Tool |
63
+ | --- | --- | --- |
64
+ | Body text, labels | 4.5:1 | `check_color_contrast` |
65
+ | Large or bold headings | 3:1 | `check_color_contrast` with `large: true` |
66
+ | Icons, borders, focus rings, chart lines | 3:1 | `check_color_contrast` |
67
+ | Status or series colours | Distinct under CVD | `check_color_palette` |
68
+ | Light and dark themes | Both pass | `check_dual_color_contrast` |
69
+ | Decorative graphics, disabled controls, logos | No requirement | Note them as exempt |
70
+
71
+ ## Recipes
72
+
73
+ **Fix a button label.** `check_color_contrast` `{ foreground: label, background: button }` →
74
+ use its suggested lightness or a dark shade of the button's hue → apply → check again.
75
+
76
+ **Colour-blind-safe status colours.** `check_color_palette` with the three colours → replace with
77
+ a set that varies in lightness (`#0072b2`, `#e69f00`, `#000000`) → check again; keep the labels.
78
+
79
+ **A faint icon.** `check_color_contrast` icon stroke against the card; darken the stroke until it
80
+ reaches 3:1.
81
+
82
+ ## Pitfalls
83
+
84
+ - **Eyeballing contrast.** Screens and eyes vary; measure every pair.
85
+ - **Changing the brand colour** to fix text; change the text first.
86
+ - **Large-text allowance on small text.** 3:1 applies only to large or bold-large text.
87
+ - **Translucent colours** are measured composited over their real backdrop; pass the opaque
88
+ backdrop.
89
+ - **Red and green only** for meaning fails the most common colour-vision deficiency.
90
+ - **Fixing one mode** and breaking the other in a dual theme.
91
+
92
+ ## Tools used
93
+
94
+ - `check_color_contrast`: text and graphic contrast, with a suggested passing lightness
95
+ - `check_color_palette`: distinctness under colour-vision deficiencies
96
+ - `check_dual_color_contrast`, `derive_dual_color`: light and dark modes
97
+ - `inspect_color`: parse and convert a colour
98
+ - `blend_colors`: shades and tints of a hue
@@ -0,0 +1,97 @@
1
+ {
2
+ "prompt": "Our 'Buy now' button fails accessibility review: the label is hard to read on the button. Fix the label colour so it meets WCAG AA for normal text, keeping the button's brand blue.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 200,
6
+ "height": 80,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "rect",
13
+ "x": 20,
14
+ "y": 20,
15
+ "width": 160,
16
+ "height": 40,
17
+ "name": "Button",
18
+ "fill": "#4da3ff",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "rect",
26
+ "x": 60,
27
+ "y": 32,
28
+ "width": 80,
29
+ "height": 16,
30
+ "name": "Label",
31
+ "fill": "#ffffff",
32
+ "stroke": null
33
+ }
34
+ }
35
+ ]
36
+ },
37
+ "checks": [
38
+ {
39
+ "why": "label on button at least 4.5:1",
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); }; return cr(v.find((n) => n.name === 'Label').fill, v.find((n) => n.name === 'Button').fill) >= 4.5; })()"
48
+ }
49
+ }
50
+ }
51
+ },
52
+ {
53
+ "why": "the button keeps its blue",
54
+ "tool": "run_command",
55
+ "args": {
56
+ "id": "vector.list"
57
+ },
58
+ "expect": {
59
+ "result": {
60
+ "nodes": {
61
+ "$some": {
62
+ "name": "Button",
63
+ "fill": "#4da3ff"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ],
70
+ "solution": [
71
+ {
72
+ "tool": "check_color_contrast",
73
+ "args": {
74
+ "foreground": "#ffffff",
75
+ "background": "#4da3ff"
76
+ }
77
+ },
78
+ {
79
+ "tool": "run_command",
80
+ "args": {
81
+ "id": "vector.setStyle",
82
+ "params": {
83
+ "ids": [
84
+ {
85
+ "$read": "run_command",
86
+ "args": {
87
+ "id": "vector.list"
88
+ },
89
+ "path": "result.nodes.1.id"
90
+ }
91
+ ],
92
+ "fill": "#0b1f33"
93
+ }
94
+ }
95
+ }
96
+ ]
97
+ }
@@ -0,0 +1,160 @@
1
+ {
2
+ "prompt": "These three status dots (OK, warning, error) must be told apart by colour-blind users too. Fix their colours.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 120,
6
+ "height": 40,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "ellipse",
13
+ "x": 10,
14
+ "y": 10,
15
+ "width": 20,
16
+ "height": 20,
17
+ "name": "OK",
18
+ "fill": "#2ca02c",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "ellipse",
26
+ "x": 50,
27
+ "y": 10,
28
+ "width": 20,
29
+ "height": 20,
30
+ "name": "Warning",
31
+ "fill": "#bcbd22",
32
+ "stroke": null
33
+ }
34
+ },
35
+ {
36
+ "id": "vector.addShape",
37
+ "params": {
38
+ "kind": "ellipse",
39
+ "x": 90,
40
+ "y": 10,
41
+ "width": 20,
42
+ "height": 20,
43
+ "name": "Error",
44
+ "fill": "#d62728",
45
+ "stroke": null
46
+ }
47
+ }
48
+ ]
49
+ },
50
+ "checks": [
51
+ {
52
+ "why": "the three colours pass the colour-vision check",
53
+ "tool": "check_color_palette",
54
+ "args": {
55
+ "colors": {
56
+ "$read": "run_command",
57
+ "args": {
58
+ "id": "vector.list"
59
+ },
60
+ "path": "result.nodes.*.fill"
61
+ }
62
+ },
63
+ "expect": {
64
+ "ok": true
65
+ }
66
+ },
67
+ {
68
+ "why": "still three dots",
69
+ "tool": "run_command",
70
+ "args": {
71
+ "id": "vector.list"
72
+ },
73
+ "expect": {
74
+ "result": {
75
+ "nodes": {
76
+ "$length": 3
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ],
82
+ "solution": [
83
+ {
84
+ "tool": "check_color_palette",
85
+ "args": {
86
+ "colors": [
87
+ "#2ca02c",
88
+ "#bcbd22",
89
+ "#d62728"
90
+ ]
91
+ }
92
+ },
93
+ {
94
+ "tool": "run_command",
95
+ "args": {
96
+ "id": "vector.setStyle",
97
+ "params": {
98
+ "ids": [
99
+ {
100
+ "$read": "run_command",
101
+ "args": {
102
+ "id": "vector.list"
103
+ },
104
+ "path": "result.nodes.0.id"
105
+ }
106
+ ],
107
+ "fill": "#0072b2"
108
+ }
109
+ }
110
+ },
111
+ {
112
+ "tool": "run_command",
113
+ "args": {
114
+ "id": "vector.setStyle",
115
+ "params": {
116
+ "ids": [
117
+ {
118
+ "$read": "run_command",
119
+ "args": {
120
+ "id": "vector.list"
121
+ },
122
+ "path": "result.nodes.1.id"
123
+ }
124
+ ],
125
+ "fill": "#e69f00"
126
+ }
127
+ }
128
+ },
129
+ {
130
+ "tool": "run_command",
131
+ "args": {
132
+ "id": "vector.setStyle",
133
+ "params": {
134
+ "ids": [
135
+ {
136
+ "$read": "run_command",
137
+ "args": {
138
+ "id": "vector.list"
139
+ },
140
+ "path": "result.nodes.2.id"
141
+ }
142
+ ],
143
+ "fill": "#000000"
144
+ }
145
+ }
146
+ },
147
+ {
148
+ "tool": "check_color_palette",
149
+ "args": {
150
+ "colors": {
151
+ "$read": "run_command",
152
+ "args": {
153
+ "id": "vector.list"
154
+ },
155
+ "path": "result.nodes.*.fill"
156
+ }
157
+ }
158
+ }
159
+ ]
160
+ }
@@ -0,0 +1,109 @@
1
+ {
2
+ "prompt": "The outline icon on this card is too faint. Make sure it meets the WCAG contrast for non-text graphics (3:1) against the card.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 120,
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": 120,
16
+ "height": 120,
17
+ "name": "Card",
18
+ "fill": "#ffffff",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "ellipse",
26
+ "x": 40,
27
+ "y": 40,
28
+ "width": 40,
29
+ "height": 40,
30
+ "name": "Icon",
31
+ "fill": null,
32
+ "stroke": "#c8c8c8",
33
+ "strokeWidth": 2
34
+ }
35
+ }
36
+ ]
37
+ },
38
+ "checks": [
39
+ {
40
+ "why": "the icon stroke against the card is at least 3:1",
41
+ "tool": "check_color_contrast",
42
+ "args": {
43
+ "foreground": {
44
+ "$read": "run_command",
45
+ "args": {
46
+ "id": "vector.list"
47
+ },
48
+ "path": "result.nodes.1.stroke"
49
+ },
50
+ "background": {
51
+ "$read": "run_command",
52
+ "args": {
53
+ "id": "vector.list"
54
+ },
55
+ "path": "result.nodes.0.fill"
56
+ }
57
+ },
58
+ "expect": {
59
+ "ratio": {
60
+ "$min": 3
61
+ }
62
+ }
63
+ },
64
+ {
65
+ "why": "the card is unchanged",
66
+ "tool": "run_command",
67
+ "args": {
68
+ "id": "vector.list"
69
+ },
70
+ "expect": {
71
+ "result": {
72
+ "nodes": {
73
+ "$some": {
74
+ "name": "Card",
75
+ "fill": "#ffffff"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ],
82
+ "solution": [
83
+ {
84
+ "tool": "check_color_contrast",
85
+ "args": {
86
+ "foreground": "#c8c8c8",
87
+ "background": "#ffffff"
88
+ }
89
+ },
90
+ {
91
+ "tool": "run_command",
92
+ "args": {
93
+ "id": "vector.setStyle",
94
+ "params": {
95
+ "ids": [
96
+ {
97
+ "$read": "run_command",
98
+ "args": {
99
+ "id": "vector.list"
100
+ },
101
+ "path": "result.nodes.1.id"
102
+ }
103
+ ],
104
+ "stroke": "#767676"
105
+ }
106
+ }
107
+ }
108
+ ]
109
+ }
@@ -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