@pantoken/swatches 0.1.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/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # @pantoken/swatches
2
+
3
+ Export Instructure colours as designer swatch palettes — files a designer imports directly into
4
+ their tool — plus a viewable SVG specimen sheet. These are terminal outputs, not an interchange
5
+ format: for token interchange use `@pantoken/dtcg` or `@pantoken/figma`.
6
+
7
+ | Format | Tools |
8
+ | ---------------------------------------- | ------------------------------------------------------------------ |
9
+ | **ASE** (`toAse`) | Photoshop, Illustrator, InDesign (and Affinity, which imports ASE) |
10
+ | **`.gpl`** (`toGpl`) | GIMP, Inkscape, Krita, Blender, and Aseprite |
11
+ | **`.sketchpalette`** (`toSketchPalette`) | Sketch (Sketch Palettes plugin) and Lunacy |
12
+ | **`.svg`** (`toSvg`) | A grouped specimen sheet for a README, docs page, PR, or Figma |
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ npm i @pantoken/swatches
18
+ ```
19
+
20
+ Also available as `pantoken/swatches`.
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import { swatches, toAse, toGpl, toSketchPalette, toSvg } from "@pantoken/swatches";
26
+
27
+ writeFileSync("instructure.ase", toAse(swatches));
28
+ writeFileSync("instructure.gpl", toGpl(swatches));
29
+ writeFileSync("instructure.sketchpalette", JSON.stringify(toSketchPalette(swatches)));
30
+ writeFileSync("instructure.svg", toSvg(swatches));
31
+ ```
32
+
33
+ Or via the CLI:
34
+
35
+ ```sh
36
+ pantoken generate swatches --format ase --out instructure.ase
37
+ pantoken generate swatches --format gpl --out instructure.gpl
38
+ pantoken generate swatches --format sketch --out instructure.sketchpalette
39
+ pantoken generate swatches --format svg --out instructure.svg
40
+ ```
41
+
42
+ Swatches are colours only — a flat reduction of the token set. Build the list from any IR with
43
+ `toSwatches(tokens, mode)`.
44
+
45
+ ## API
46
+
47
+ - **`swatches: Swatch[]`** — the `rebrand` colour swatches (the default palette).
48
+ - **`toSwatches(tokens, mode?): Swatch[]`** — reduce a token IR to a flat list of colour swatches:
49
+ resolve references, pick a `mode` (`"light"` default or `"dark"`), and keep only tokens whose value
50
+ is a hex colour. Names drop the `--instui-` prefix.
51
+ - **`toAse(swatches): Uint8Array`** — encode swatches as Adobe Swatch Exchange (ASE) bytes.
52
+ - **`toGpl(swatches, options?): string`** — encode swatches as a GIMP `.gpl` palette string.
53
+ `options.name` sets the palette name (default `"Instructure"`).
54
+ - **`toSketchPalette(swatches): SketchPalette`** — encode swatches as a Sketch palette object
55
+ (serialize with `JSON.stringify`).
56
+ - **`toSvg(swatches, options?): string`** — render a grouped SVG specimen sheet. Swatches are grouped
57
+ by name minus the leaf segment; `options.title` sets the heading and `options.columns` the chips
58
+ per row (default `6`).
59
+ - **`hexToRgb(hex): Rgb | undefined`** — parse a hex colour to 0–255 channels, or `undefined`.
60
+ - **`Swatch`, `Rgb`, `Mode`, `ToGplOptions`, `SketchPalette`, `ToSvgOptions`** — the model and option
61
+ types.
62
+
63
+ ## Related
64
+
65
+ - Pairs with `@pantoken/tokens` for the token IR to reduce.
66
+ - For token interchange (not terminal palettes), see `@pantoken/dtcg` and `@pantoken/figma`.
67
+
68
+ ## License
69
+
70
+ MIT
@@ -0,0 +1,177 @@
1
+ import { Token } from "@pantoken/model";
2
+
3
+ //#region src/model.d.ts
4
+ /** A single palette entry. */
5
+ interface Swatch {
6
+ name: string;
7
+ hex: string;
8
+ }
9
+ /** RGB channels, 0–255. */
10
+ interface Rgb {
11
+ r: number;
12
+ g: number;
13
+ b: number;
14
+ }
15
+ /**
16
+ * Parse `#rgb`/`#rrggbb`/`#rrggbbaa` to 0–255 channels, or `undefined` if not a hex colour.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { hexToRgb } from "@pantoken/swatches";
21
+ *
22
+ * hexToRgb("#ff8800"); // { r: 255, g: 136, b: 0 }
23
+ * hexToRgb("nope"); // undefined
24
+ * ```
25
+ */
26
+ declare function hexToRgb(hex: string): Rgb | undefined;
27
+ /** The colour mode to resolve when flattening `light-dark()` values. */
28
+ type Mode = "light" | "dark";
29
+ /**
30
+ * Reduce a token IR to a flat list of colour swatches: resolve references, pick a mode, keep only
31
+ * tokens whose value is a hex colour (icons and non-colour tokens are dropped).
32
+ *
33
+ * @param tokens - The IR (e.g. from `@pantoken/tokens`).
34
+ * @param mode - Which colour mode to resolve (default `"light"`).
35
+ * @returns The swatch list, named by token (without the `--instui-` prefix).
36
+ *
37
+ * @example Reduce the token IR to light-mode swatches
38
+ * ```ts
39
+ * import { toSwatches } from "@pantoken/swatches";
40
+ * import { tokens } from "@pantoken/tokens";
41
+ *
42
+ * const swatches = toSwatches(tokens); // [{ name: "color-background-brand", hex: "#…" }, …]
43
+ * ```
44
+ *
45
+ * @example Dark mode
46
+ * ```ts
47
+ * import { toSwatches } from "@pantoken/swatches";
48
+ * import { byTheme } from "@pantoken/tokens";
49
+ *
50
+ * const swatches = toSwatches(byTheme("canvas"), "dark");
51
+ * ```
52
+ */
53
+ declare function toSwatches(tokens: readonly Token[], mode?: Mode): Swatch[];
54
+ //#endregion
55
+ //#region src/ase.d.ts
56
+ /**
57
+ * Encode swatches as ASE bytes. Non-hex swatches are skipped.
58
+ *
59
+ * @param swatches - The palette.
60
+ * @returns The ASE file as bytes.
61
+ *
62
+ * @example Write an Adobe .ase palette
63
+ * ```ts
64
+ * import { writeFileSync } from "node:fs";
65
+ * import { swatches, toAse } from "@pantoken/swatches";
66
+ *
67
+ * writeFileSync("instructure.ase", toAse(swatches));
68
+ * ```
69
+ */
70
+ declare function toAse(swatches: readonly Swatch[]): Uint8Array;
71
+ //#endregion
72
+ //#region src/gpl.d.ts
73
+ /** Options for {@link toGpl}. */
74
+ interface ToGplOptions {
75
+ /** The palette name (default `"Instructure"`). */
76
+ name?: string;
77
+ }
78
+ /**
79
+ * Encode swatches as a GIMP `.gpl` palette string.
80
+ *
81
+ * @param swatches - The palette.
82
+ * @param options - {@link ToGplOptions}.
83
+ *
84
+ * @example Write a GIMP .gpl palette
85
+ * ```ts
86
+ * import { writeFileSync } from "node:fs";
87
+ * import { swatches, toGpl } from "@pantoken/swatches";
88
+ *
89
+ * writeFileSync("instructure.gpl", toGpl(swatches));
90
+ * ```
91
+ *
92
+ * @example With a custom palette name
93
+ * ```ts
94
+ * import { swatches, toGpl } from "@pantoken/swatches";
95
+ *
96
+ * const gpl = toGpl(swatches, { name: "Instructure Rebrand" });
97
+ * ```
98
+ */
99
+ declare function toGpl(swatches: readonly Swatch[], options?: ToGplOptions): string;
100
+ //#endregion
101
+ //#region src/sketch.d.ts
102
+ /** A `.sketchpalette` document. */
103
+ interface SketchPalette {
104
+ compatibleVersion: string;
105
+ pluginVersion: string;
106
+ colors: {
107
+ name: string;
108
+ red: number;
109
+ green: number;
110
+ blue: number;
111
+ alpha: number;
112
+ }[];
113
+ }
114
+ /**
115
+ * Encode swatches as a Sketch palette object (serialize with `JSON.stringify`).
116
+ *
117
+ * @param swatches - The palette.
118
+ *
119
+ * @example Write a Sketch .sketchpalette
120
+ * ```ts
121
+ * import { writeFileSync } from "node:fs";
122
+ * import { swatches, toSketchPalette } from "@pantoken/swatches";
123
+ *
124
+ * writeFileSync("instructure.sketchpalette", JSON.stringify(toSketchPalette(swatches)));
125
+ * ```
126
+ */
127
+ declare function toSketchPalette(swatches: readonly Swatch[]): SketchPalette;
128
+ //#endregion
129
+ //#region src/svg.d.ts
130
+ /** Options for {@link toSvg}. */
131
+ interface ToSvgOptions {
132
+ /** The sheet title (default `"Instructure color tokens"`). */
133
+ title?: string;
134
+ /** Chips per row within a section (default `6`). */
135
+ columns?: number;
136
+ }
137
+ /**
138
+ * Render swatches as a grouped SVG specimen sheet.
139
+ *
140
+ * @param swatches - The palette (e.g. from `toSwatches`).
141
+ * @param options - {@link ToSvgOptions}.
142
+ * @returns The SVG document as a string.
143
+ *
144
+ * @example Render a specimen sheet for a README
145
+ * ```ts
146
+ * import { writeFileSync } from "node:fs";
147
+ * import { swatches, toSvg } from "@pantoken/swatches";
148
+ *
149
+ * writeFileSync("palette.svg", toSvg(swatches));
150
+ * ```
151
+ *
152
+ * @example Specimen only the core palette, with a title and wider grid
153
+ * ```ts
154
+ * import { swatches, toSvg } from "@pantoken/swatches";
155
+ *
156
+ * const core = swatches.filter((s) => !s.name.startsWith("primitive-"));
157
+ * const svg = toSvg(core, { title: "Instructure core colors", columns: 8 });
158
+ * ```
159
+ */
160
+ declare function toSvg(swatches: readonly Swatch[], options?: ToSvgOptions): string;
161
+ //#endregion
162
+ //#region src/index.d.ts
163
+ /**
164
+ * The `rebrand` colour swatches (the default palette).
165
+ *
166
+ * @example Encode the ready-made palette to any format
167
+ * ```ts
168
+ * import { writeFileSync } from "node:fs";
169
+ * import { swatches, toAse, toGpl } from "@pantoken/swatches";
170
+ *
171
+ * writeFileSync("instructure.ase", toAse(swatches));
172
+ * writeFileSync("instructure.gpl", toGpl(swatches));
173
+ * ```
174
+ */
175
+ declare const swatches: Swatch[];
176
+ //#endregion
177
+ export { type Mode, type Rgb, type SketchPalette, type Swatch, type ToGplOptions, type ToSvgOptions, hexToRgb, swatches, toAse, toGpl, toSketchPalette, toSvg, toSwatches };
package/dist/index.mjs ADDED
@@ -0,0 +1,340 @@
1
+ import { tokens } from "@pantoken/tokens";
2
+ import { makeResolver, parseHexColor } from "@pantoken/utils";
3
+ //#region src/model.ts
4
+ /**
5
+ * The shared swatch model and colour helpers. A {@link Swatch} is just a name + hex colour — the
6
+ * flat reduction of the token set that every designer swatch format consumes.
7
+ *
8
+ * @module
9
+ */
10
+ /**
11
+ * Parse `#rgb`/`#rrggbb`/`#rrggbbaa` to 0–255 channels, or `undefined` if not a hex colour.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { hexToRgb } from "@pantoken/swatches";
16
+ *
17
+ * hexToRgb("#ff8800"); // { r: 255, g: 136, b: 0 }
18
+ * hexToRgb("nope"); // undefined
19
+ * ```
20
+ */
21
+ function hexToRgb(hex) {
22
+ const c = parseHexColor(hex);
23
+ return c ? {
24
+ r: c.r,
25
+ g: c.g,
26
+ b: c.b
27
+ } : void 0;
28
+ }
29
+ /**
30
+ * Reduce a token IR to a flat list of colour swatches: resolve references, pick a mode, keep only
31
+ * tokens whose value is a hex colour (icons and non-colour tokens are dropped).
32
+ *
33
+ * @param tokens - The IR (e.g. from `@pantoken/tokens`).
34
+ * @param mode - Which colour mode to resolve (default `"light"`).
35
+ * @returns The swatch list, named by token (without the `--instui-` prefix).
36
+ *
37
+ * @example Reduce the token IR to light-mode swatches
38
+ * ```ts
39
+ * import { toSwatches } from "@pantoken/swatches";
40
+ * import { tokens } from "@pantoken/tokens";
41
+ *
42
+ * const swatches = toSwatches(tokens); // [{ name: "color-background-brand", hex: "#…" }, …]
43
+ * ```
44
+ *
45
+ * @example Dark mode
46
+ * ```ts
47
+ * import { toSwatches } from "@pantoken/swatches";
48
+ * import { byTheme } from "@pantoken/tokens";
49
+ *
50
+ * const swatches = toSwatches(byTheme("canvas"), "dark");
51
+ * ```
52
+ */
53
+ function toSwatches(tokens, mode = "light") {
54
+ const resolve = makeResolver(tokens, { mode });
55
+ const out = [];
56
+ for (const token of tokens) {
57
+ if (token.meta?.kind === "icon") continue;
58
+ const hex = resolve(token.value).trim();
59
+ if (hexToRgb(hex)) out.push({
60
+ name: token.name.replace(/^--instui-/, ""),
61
+ hex
62
+ });
63
+ }
64
+ return out;
65
+ }
66
+ //#endregion
67
+ //#region src/ase.ts
68
+ /**
69
+ * Encode swatches as an Adobe Swatch Exchange (ASE) file — the palette format Photoshop,
70
+ * Illustrator, and InDesign import. Binary layout (big-endian):
71
+ *
72
+ * ```
73
+ * "ASEF" u16 major=1 u16 minor=0 u32 blockCount
74
+ * per colour block:
75
+ * u16 type=0x0001 u32 dataLength
76
+ * u16 nameLen(incl. NUL) UTF-16BE name + NUL "RGB " f32 r,g,b (0–1) u16 colorType=2
77
+ * ```
78
+ *
79
+ * @module
80
+ */
81
+ /**
82
+ * Encode swatches as ASE bytes. Non-hex swatches are skipped.
83
+ *
84
+ * @param swatches - The palette.
85
+ * @returns The ASE file as bytes.
86
+ *
87
+ * @example Write an Adobe .ase palette
88
+ * ```ts
89
+ * import { writeFileSync } from "node:fs";
90
+ * import { swatches, toAse } from "@pantoken/swatches";
91
+ *
92
+ * writeFileSync("instructure.ase", toAse(swatches));
93
+ * ```
94
+ */
95
+ function toAse(swatches) {
96
+ const entries = swatches.map((s) => ({
97
+ name: s.name,
98
+ rgb: hexToRgb(s.hex)
99
+ })).filter((e) => Boolean(e.rgb));
100
+ const header = Buffer.alloc(12);
101
+ header.write("ASEF", 0, "ascii");
102
+ header.writeUInt16BE(1, 4);
103
+ header.writeUInt16BE(0, 6);
104
+ header.writeUInt32BE(entries.length, 8);
105
+ const chunks = [header];
106
+ for (const { name, rgb } of entries) {
107
+ const nameLen = name.length + 1;
108
+ const nameBuf = Buffer.alloc(nameLen * 2);
109
+ for (let i = 0; i < name.length; i++) nameBuf.writeUInt16BE(name.charCodeAt(i), i * 2);
110
+ const color = Buffer.alloc(12);
111
+ color.writeFloatBE(rgb.r / 255, 0);
112
+ color.writeFloatBE(rgb.g / 255, 4);
113
+ color.writeFloatBE(rgb.b / 255, 8);
114
+ const nameLenBuf = Buffer.alloc(2);
115
+ nameLenBuf.writeUInt16BE(nameLen, 0);
116
+ const colorType = Buffer.alloc(2);
117
+ colorType.writeUInt16BE(2, 0);
118
+ const data = Buffer.concat([
119
+ nameLenBuf,
120
+ nameBuf,
121
+ Buffer.from("RGB ", "ascii"),
122
+ color,
123
+ colorType
124
+ ]);
125
+ const blockHeader = Buffer.alloc(6);
126
+ blockHeader.writeUInt16BE(1, 0);
127
+ blockHeader.writeUInt32BE(data.length, 2);
128
+ chunks.push(blockHeader, data);
129
+ }
130
+ return new Uint8Array(Buffer.concat(chunks));
131
+ }
132
+ //#endregion
133
+ //#region src/gpl.ts
134
+ /**
135
+ * Encode swatches as a GIMP palette (`.gpl`) — imported by GIMP, Inkscape, Krita, Blender, and
136
+ * Aseprite. Plain text: a header, then `R G B name` lines (0–255).
137
+ *
138
+ * @module
139
+ */
140
+ /**
141
+ * Encode swatches as a GIMP `.gpl` palette string.
142
+ *
143
+ * @param swatches - The palette.
144
+ * @param options - {@link ToGplOptions}.
145
+ *
146
+ * @example Write a GIMP .gpl palette
147
+ * ```ts
148
+ * import { writeFileSync } from "node:fs";
149
+ * import { swatches, toGpl } from "@pantoken/swatches";
150
+ *
151
+ * writeFileSync("instructure.gpl", toGpl(swatches));
152
+ * ```
153
+ *
154
+ * @example With a custom palette name
155
+ * ```ts
156
+ * import { swatches, toGpl } from "@pantoken/swatches";
157
+ *
158
+ * const gpl = toGpl(swatches, { name: "Instructure Rebrand" });
159
+ * ```
160
+ */
161
+ function toGpl(swatches, options = {}) {
162
+ const lines = [
163
+ "GIMP Palette",
164
+ `Name: ${options.name ?? "Instructure"}`,
165
+ "Columns: 0",
166
+ "#"
167
+ ];
168
+ for (const swatch of swatches) {
169
+ const rgb = hexToRgb(swatch.hex);
170
+ if (!rgb) continue;
171
+ const col = (n) => String(n).padStart(3, " ");
172
+ lines.push(`${col(rgb.r)} ${col(rgb.g)} ${col(rgb.b)}\t${swatch.name}`);
173
+ }
174
+ return `${lines.join("\n")}\n`;
175
+ }
176
+ //#endregion
177
+ //#region src/sketch.ts
178
+ /**
179
+ * Encode swatches as a Sketch `.sketchpalette` document (the Sketch Palettes plugin format). JSON
180
+ * with colours as 0–1 RGBA.
181
+ *
182
+ * @module
183
+ */
184
+ /**
185
+ * Encode swatches as a Sketch palette object (serialize with `JSON.stringify`).
186
+ *
187
+ * @param swatches - The palette.
188
+ *
189
+ * @example Write a Sketch .sketchpalette
190
+ * ```ts
191
+ * import { writeFileSync } from "node:fs";
192
+ * import { swatches, toSketchPalette } from "@pantoken/swatches";
193
+ *
194
+ * writeFileSync("instructure.sketchpalette", JSON.stringify(toSketchPalette(swatches)));
195
+ * ```
196
+ */
197
+ function toSketchPalette(swatches) {
198
+ const colors = [];
199
+ for (const swatch of swatches) {
200
+ const rgb = hexToRgb(swatch.hex);
201
+ if (!rgb) continue;
202
+ colors.push({
203
+ name: swatch.name,
204
+ red: rgb.r / 255,
205
+ green: rgb.g / 255,
206
+ blue: rgb.b / 255,
207
+ alpha: 1
208
+ });
209
+ }
210
+ return {
211
+ compatibleVersion: "2.0",
212
+ pluginVersion: "2.22",
213
+ colors
214
+ };
215
+ }
216
+ //#endregion
217
+ //#region src/svg.ts
218
+ const MARGIN = 32;
219
+ const GROUP_HEAD_H = 26;
220
+ const GROUP_GAP = 26;
221
+ const CELL_W = 150;
222
+ const SWATCH_H = 46;
223
+ const CELL_H = 78;
224
+ const GAP_X = 14;
225
+ const GAP_Y = 16;
226
+ const MAX_LABEL = 22;
227
+ /** Escape the five XML text characters. */
228
+ function esc(text) {
229
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
230
+ }
231
+ /** The category a swatch belongs to: first two name segments, or three for a primitive family. */
232
+ function categoryOf(name) {
233
+ const parts = name.split("-");
234
+ const depth = parts[0] === "primitive" ? 3 : 2;
235
+ return parts.slice(0, depth).join("-");
236
+ }
237
+ /** Group swatches by category, preserving first-appearance order. */
238
+ function group(swatches) {
239
+ const groups = /* @__PURE__ */ new Map();
240
+ for (const swatch of swatches) {
241
+ const key = categoryOf(swatch.name);
242
+ const rest = swatch.name.length > key.length ? swatch.name.slice(key.length + 1) : swatch.name;
243
+ const label = rest.length > MAX_LABEL ? `${rest.slice(0, MAX_LABEL - 1)}…` : rest;
244
+ (groups.get(key) ?? groups.set(key, []).get(key)).push({
245
+ label,
246
+ swatch
247
+ });
248
+ }
249
+ return [...groups].map(([name, items]) => ({
250
+ name,
251
+ items
252
+ }));
253
+ }
254
+ /**
255
+ * Render swatches as a grouped SVG specimen sheet.
256
+ *
257
+ * @param swatches - The palette (e.g. from `toSwatches`).
258
+ * @param options - {@link ToSvgOptions}.
259
+ * @returns The SVG document as a string.
260
+ *
261
+ * @example Render a specimen sheet for a README
262
+ * ```ts
263
+ * import { writeFileSync } from "node:fs";
264
+ * import { swatches, toSvg } from "@pantoken/swatches";
265
+ *
266
+ * writeFileSync("palette.svg", toSvg(swatches));
267
+ * ```
268
+ *
269
+ * @example Specimen only the core palette, with a title and wider grid
270
+ * ```ts
271
+ * import { swatches, toSvg } from "@pantoken/swatches";
272
+ *
273
+ * const core = swatches.filter((s) => !s.name.startsWith("primitive-"));
274
+ * const svg = toSvg(core, { title: "Instructure core colors", columns: 8 });
275
+ * ```
276
+ */
277
+ function toSvg(swatches, options = {}) {
278
+ const title = options.title ?? "Instructure color tokens";
279
+ const columns = Math.max(1, options.columns ?? 6);
280
+ const width = MARGIN * 2 + columns * CELL_W + (columns - 1) * GAP_X;
281
+ const groups = group(swatches);
282
+ const body = [];
283
+ let y = 78;
284
+ for (const g of groups) {
285
+ body.push(`<text x="${MARGIN}" y="${y}" class="group">${esc(g.name)}</text>`);
286
+ const top = y + GROUP_HEAD_H;
287
+ const rows = Math.ceil(g.items.length / columns);
288
+ g.items.forEach(({ label, swatch }, i) => {
289
+ const x = MARGIN + i % columns * 164;
290
+ const cy = top + Math.floor(i / columns) * 94;
291
+ body.push(`<g transform="translate(${x},${cy})">`, `<rect width="${CELL_W}" height="${SWATCH_H}" rx="6" fill="${esc(swatch.hex)}" class="chip"/>`, `<text x="0" y="62" class="leaf">${esc(label)}</text>`, `<text x="0" y="75" class="hex">${esc(swatch.hex.toUpperCase())}</text>`, `</g>`);
292
+ });
293
+ y = top + rows * CELL_H + (rows - 1) * GAP_Y + GROUP_GAP;
294
+ }
295
+ const height = y + MARGIN - GROUP_GAP;
296
+ return [
297
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" font-family="system-ui, sans-serif">`,
298
+ `<style>`,
299
+ `.bg { fill: #ffffff; }`,
300
+ `.title { fill: #111827; font-size: 20px; font-weight: 700; }`,
301
+ `.group { fill: #374151; font-size: 14px; font-weight: 600; }`,
302
+ `.chip { stroke: rgba(0,0,0,0.15); stroke-width: 1; }`,
303
+ `.leaf { fill: #374151; font-size: 11px; }`,
304
+ `.hex { fill: #9ca3af; font-size: 10px; font-family: ui-monospace, monospace; }`,
305
+ `</style>`,
306
+ `<rect class="bg" width="${width}" height="${height}"/>`,
307
+ `<text x="${MARGIN}" y="52" class="title">${esc(title)}</text>`,
308
+ ...body,
309
+ `</svg>`,
310
+ ``
311
+ ].join("\n");
312
+ }
313
+ //#endregion
314
+ //#region src/index.ts
315
+ /**
316
+ * `@pantoken/swatches` — export Instructure colours as designer swatch palettes.
317
+ *
318
+ * Swatches are a flat reduction of the tokens to named colours, delivered *directly to a designer*
319
+ * to import into their tool. This is not an interchange format (use `@pantoken/dtcg` or
320
+ * `@pantoken/figma` for that) — it's a terminal palette file: Adobe ASE, GIMP `.gpl`, or Sketch
321
+ * `.sketchpalette`.
322
+ *
323
+ * @module
324
+ * @experimental
325
+ */
326
+ /**
327
+ * The `rebrand` colour swatches (the default palette).
328
+ *
329
+ * @example Encode the ready-made palette to any format
330
+ * ```ts
331
+ * import { writeFileSync } from "node:fs";
332
+ * import { swatches, toAse, toGpl } from "@pantoken/swatches";
333
+ *
334
+ * writeFileSync("instructure.ase", toAse(swatches));
335
+ * writeFileSync("instructure.gpl", toGpl(swatches));
336
+ * ```
337
+ */
338
+ const swatches = toSwatches(tokens);
339
+ //#endregion
340
+ export { hexToRgb, swatches, toAse, toGpl, toSketchPalette, toSvg, toSwatches };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@pantoken/swatches",
3
+ "version": "0.1.0",
4
+ "description": "Export Instructure colours as designer swatch palettes: Adobe ASE, GIMP .gpl, and Sketch .sketchpalette.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "exports": {
11
+ ".": "./dist/index.mjs",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "dependencies": {
18
+ "@pantoken/model": "0.1.0",
19
+ "@pantoken/tokens": "0.1.0",
20
+ "@pantoken/utils": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^24.13.3",
24
+ "typescript": "^6.0.3",
25
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.4",
26
+ "vite-plus": "0.2.4"
27
+ },
28
+ "pantoken": {
29
+ "key": "swatches",
30
+ "kind": "subpath"
31
+ },
32
+ "scripts": {
33
+ "build": "vp pack",
34
+ "dev": "vp pack --watch",
35
+ "test": "vp test",
36
+ "check": "vp check"
37
+ }
38
+ }