@shopify/klint 0.0.4

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.
@@ -0,0 +1,1146 @@
1
+ // src/plugins/Color.tsx
2
+ var Color = class {
3
+ constructor() {
4
+ // context: KlintContexts;
5
+ /**
6
+ * Array of predefined colors in the Klint color palette
7
+ */
8
+ this.colors = [
9
+ "#E84D37",
10
+ // coral
11
+ "#7F4C2F",
12
+ // brown
13
+ "#EDBC2F",
14
+ // mustard
15
+ "#BF3034",
16
+ // crimson
17
+ "#18599D",
18
+ // navy
19
+ "#45A7C6",
20
+ // sky
21
+ "#8CB151",
22
+ // olive
23
+ "#252120",
24
+ // charcoal
25
+ "#ECA088",
26
+ // peach
27
+ "#C9B1B8",
28
+ // rose
29
+ "#8F3064",
30
+ // plum
31
+ "#7B8870",
32
+ // sage
33
+ "#C0C180",
34
+ // drab
35
+ "#4B423D",
36
+ // taupe
37
+ "#1A2A65",
38
+ // midnight
39
+ "#EAA550",
40
+ // golden
41
+ "#F17B04",
42
+ // orange
43
+ "#404757"
44
+ // slate
45
+ ];
46
+ }
47
+ // /**
48
+ // * Creates a new Color instance
49
+ // * @param ctx - The Klint context
50
+ // */
51
+ // constructor(ctx: KlintContexts) {
52
+ // this.context = ctx;
53
+ // }
54
+ get coral() {
55
+ return this.colors[0];
56
+ }
57
+ get brown() {
58
+ return this.colors[1];
59
+ }
60
+ get mustard() {
61
+ return this.colors[2];
62
+ }
63
+ get crimson() {
64
+ return this.colors[3];
65
+ }
66
+ get navy() {
67
+ return this.colors[4];
68
+ }
69
+ get sky() {
70
+ return this.colors[5];
71
+ }
72
+ get olive() {
73
+ return this.colors[6];
74
+ }
75
+ get charcoal() {
76
+ return this.colors[7];
77
+ }
78
+ get peach() {
79
+ return this.colors[8];
80
+ }
81
+ get rose() {
82
+ return this.colors[9];
83
+ }
84
+ get plum() {
85
+ return this.colors[10];
86
+ }
87
+ get sage() {
88
+ return this.colors[11];
89
+ }
90
+ get drab() {
91
+ return this.colors[12];
92
+ }
93
+ get taupe() {
94
+ return this.colors[13];
95
+ }
96
+ get midnight() {
97
+ return this.colors[14];
98
+ }
99
+ get golden() {
100
+ return this.colors[15];
101
+ }
102
+ get orange() {
103
+ return this.colors[16];
104
+ }
105
+ get slate() {
106
+ return this.colors[17];
107
+ }
108
+ /**
109
+ * Ensures a color string has a # prefix
110
+ * @param color - Color string in hex format (with or without #)
111
+ * @returns Hex color string with # prefix
112
+ */
113
+ hex(color) {
114
+ return color.startsWith("#") ? color : `#${color}`;
115
+ }
116
+ /**
117
+ * Creates an RGB color string
118
+ * @param r - Red component (0-255)
119
+ * @param g - Green component (0-255)
120
+ * @param b - Blue component (0-255)
121
+ * @returns RGB color string
122
+ */
123
+ rgb(r, g, b) {
124
+ return `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
125
+ }
126
+ /**
127
+ * Creates an RGBA color string
128
+ * @param r - Red component (0-255)
129
+ * @param g - Green component (0-255)
130
+ * @param b - Blue component (0-255)
131
+ * @param alpha - Alpha/opacity value (0-1)
132
+ * @returns RGBA color string
133
+ */
134
+ rgba(r, g, b, alpha) {
135
+ return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(
136
+ b
137
+ )}, ${alpha})`;
138
+ }
139
+ /**
140
+ * Creates a grayscale color
141
+ * @param value - Gray value (0-255)
142
+ * @param alpha - Optional alpha/opacity value (0-1)
143
+ * @returns RGB or RGBA grayscale color string
144
+ */
145
+ gray(value, alpha) {
146
+ return alpha !== void 0 ? `rgba(${Math.round(value)}, ${Math.round(value)}, ${Math.round(
147
+ value
148
+ )}, ${alpha})` : `rgb(${Math.round(value)}, ${Math.round(value)}, ${Math.round(value)})`;
149
+ }
150
+ /**
151
+ * Creates an HSL color string
152
+ * @param h - Hue (0-360)
153
+ * @param s - Saturation percentage (0-100)
154
+ * @param l - Lightness percentage (0-100)
155
+ * @returns HSL color string
156
+ */
157
+ hsl(h, s, l) {
158
+ return `hsl(${h % 360}, ${Math.max(0, s)}%, ${Math.max(0, l)}%)`;
159
+ }
160
+ /**
161
+ * Creates an HSLA color string
162
+ * @param h - Hue (0-360)
163
+ * @param s - Saturation percentage (0-100)
164
+ * @param l - Lightness percentage (0-100)
165
+ * @param alpha - Alpha/opacity value (0-1)
166
+ * @returns HSLA color string
167
+ */
168
+ hsla(h, s, l, alpha) {
169
+ return `hsla(${h % 360}, ${Math.max(0, s)}%, ${Math.max(0, l)}%, ${alpha})`;
170
+ }
171
+ /**
172
+ * Creates an LCH color string
173
+ * @param l - Lightness percentage (0-100)
174
+ * @param c - Chroma value
175
+ * @param h - Hue (0-360)
176
+ * @returns LCH color string
177
+ */
178
+ lch(l, c, h) {
179
+ return `lch(${l}% ${c} ${h})`;
180
+ }
181
+ /**
182
+ * Creates an LCH color string with alpha
183
+ * @param l - Lightness percentage (0-100)
184
+ * @param c - Chroma value
185
+ * @param h - Hue (0-360)
186
+ * @param alpha - Alpha/opacity value (0-1)
187
+ * @returns LCH color string with alpha
188
+ */
189
+ lcha(l, c, h, alpha) {
190
+ return `lch(${l}% ${c} ${h} / ${alpha})`;
191
+ }
192
+ /**
193
+ * Creates a LAB color string
194
+ * @param l - Lightness percentage (0-100)
195
+ * @param a - A-axis value (green to red)
196
+ * @param b - B-axis value (blue to yellow)
197
+ * @returns LAB color string
198
+ */
199
+ lab(l, a, b) {
200
+ return `lab(${l}% ${a} ${b})`;
201
+ }
202
+ /**
203
+ * Creates a LAB color string with alpha
204
+ * @param l - Lightness percentage (0-100)
205
+ * @param a - A-axis value (green to red)
206
+ * @param b - B-axis value (blue to yellow)
207
+ * @param alpha - Alpha/opacity value (0-1)
208
+ * @returns LAB color string with alpha
209
+ */
210
+ laba(l, a, b, alpha) {
211
+ return `lab(${l}% ${a} ${b} / ${alpha})`;
212
+ }
213
+ /**
214
+ * Creates an OKLCH color string
215
+ * @param l - Lightness value (0-1)
216
+ * @param c - Chroma value
217
+ * @param h - Hue (0-360)
218
+ * @returns OKLCH color string
219
+ */
220
+ oklch(l, c, h) {
221
+ return `oklch(${l} ${c} ${h})`;
222
+ }
223
+ /**
224
+ * Creates an OKLCH color string with alpha
225
+ * @param l - Lightness value (0-1)
226
+ * @param c - Chroma value
227
+ * @param h - Hue (0-360)
228
+ * @param alpha - Alpha/opacity value (0-1)
229
+ * @returns OKLCH color string with alpha
230
+ */
231
+ oklcha(l, c, h, alpha) {
232
+ return `oklch(${l} ${c} ${h} / ${alpha})`;
233
+ }
234
+ /**
235
+ * Creates an OKLAB color string
236
+ * @param l - Lightness value (0-1)
237
+ * @param a - A-axis value (green to red)
238
+ * @param b - B-axis value (blue to yellow)
239
+ * @returns OKLAB color string
240
+ */
241
+ oklab(l, a, b) {
242
+ return `oklab(${l} ${a} ${b})`;
243
+ }
244
+ /**
245
+ * Creates an OKLAB color string with alpha
246
+ * @param l - Lightness value (0-1)
247
+ * @param a - A-axis value (green to red)
248
+ * @param b - B-axis value (blue to yellow)
249
+ * @param alpha - Alpha/opacity value (0-1)
250
+ * @returns OKLAB color string with alpha
251
+ */
252
+ oklaba(l, a, b, alpha) {
253
+ return `oklab(${l} ${a} ${b} / ${alpha})`;
254
+ }
255
+ /**
256
+ * Blends two colors using CSS color-mix
257
+ * @param colorA - First color
258
+ * @param colorB - Second color
259
+ * @param factor - Blend factor (0-1) where 0 is colorA and 1 is colorB
260
+ * @param colorMode - Color space to blend in (e.g., "oklch", "hsl")
261
+ * @returns Blended color string
262
+ */
263
+ blendColors(colorA, colorB, factor, colorMode = "oklch") {
264
+ const t = Math.max(0, Math.min(1, factor)) * 100;
265
+ return `color-mix(in ${colorMode}, ${colorA}, ${colorB} ${t}%)`;
266
+ }
267
+ /**
268
+ * Creates a palette of colors based on a single base color
269
+ * @param baseColor - The base color to create palette from
270
+ * @param steps - Number of steps in each direction (lighter/darker)
271
+ * @returns Array of color strings forming a palette
272
+ */
273
+ createPalette(baseColor, steps = 9) {
274
+ const palette = [];
275
+ for (let i = 1; i < steps; i++) {
276
+ const factor = i / steps;
277
+ palette.unshift(this.blendColors(baseColor, "#ffffff", factor, "oklch"));
278
+ }
279
+ palette.push(baseColor);
280
+ for (let i = 1; i < steps; i++) {
281
+ const factor = i / steps;
282
+ palette.push(this.blendColors(baseColor, "#000000", factor, "oklch"));
283
+ }
284
+ return palette;
285
+ }
286
+ /**
287
+ * Creates a complementary color (opposite on the color wheel)
288
+ * @param color - Base color
289
+ * @returns Complementary color string
290
+ */
291
+ complementary(color) {
292
+ return this.blendColors(color, "hsl(180deg 100% 50%)", 1, "hsl");
293
+ }
294
+ /**
295
+ * Creates analogous colors (adjacent on the color wheel)
296
+ * @param color - Base color
297
+ * @param angle - Angle of separation in degrees
298
+ * @returns Tuple of two analogous color strings
299
+ */
300
+ analogous(color, angle = 30) {
301
+ return [
302
+ this.blendColors(color, `hsl(${-angle}deg 100% 50%)`, 1, "hsl"),
303
+ this.blendColors(color, `hsl(${angle}deg 100% 50%)`, 1, "hsl")
304
+ ];
305
+ }
306
+ /**
307
+ * Creates a triadic color scheme (three colors evenly spaced on the color wheel)
308
+ * @param color - Base color
309
+ * @returns Tuple of two additional colors to form a triadic scheme
310
+ */
311
+ triadic(color) {
312
+ return [
313
+ this.blendColors(color, "hsl(120deg 100% 50%)", 1, "hsl"),
314
+ this.blendColors(color, "hsl(240deg 100% 50%)", 1, "hsl")
315
+ ];
316
+ }
317
+ /**
318
+ * Increases the saturation of a color
319
+ * @param color - Base color
320
+ * @param amount - Amount to saturate (percentage)
321
+ * @returns Saturated color string
322
+ */
323
+ saturate(color, amount) {
324
+ return this.blendColors(
325
+ color,
326
+ "hsl(0deg 100% 50% / 0%)",
327
+ amount / 100,
328
+ "hsl"
329
+ );
330
+ }
331
+ /**
332
+ * Lightens a color by mixing with white
333
+ * @param color - Base color
334
+ * @param amount - Amount to lighten (percentage)
335
+ * @returns Lightened color string
336
+ */
337
+ lighten(color, amount) {
338
+ return this.blendColors(color, "white", amount / 100, "hsl");
339
+ }
340
+ /**
341
+ * Darkens a color by mixing with black
342
+ * @param color - Base color
343
+ * @param amount - Amount to darken (percentage)
344
+ * @returns Darkened color string
345
+ */
346
+ darken(color, amount) {
347
+ return this.blendColors(color, "black", amount / 100, "hsl");
348
+ }
349
+ };
350
+ var Color_default = Color;
351
+
352
+ // src/plugins/Easing.tsx
353
+ var Easing = class {
354
+ constructor(ctx) {
355
+ this.normalize = (val) => {
356
+ return val * 0.5 + 0.5;
357
+ };
358
+ this.expand = (val) => {
359
+ return val * 2 - 1;
360
+ };
361
+ this.inout = (val, power = 2) => {
362
+ const m = val - 1;
363
+ const t = val * 2;
364
+ if (t < 1) {
365
+ return val * Math.pow(t, power - 1);
366
+ }
367
+ return power % 2 === 0 ? 1 - Math.pow(m, power) * Math.pow(2, power - 1) : 1 + Math.pow(m, power) * Math.pow(2, power - 1);
368
+ };
369
+ this.in = (val, power = 2) => {
370
+ return Math.pow(val, power);
371
+ };
372
+ this.out = (val, power = 2) => {
373
+ const m = val - 1;
374
+ return power % 2 === 0 ? 1 - Math.pow(m, power) : 1 + Math.pow(m, power);
375
+ };
376
+ this.overshootIn = (val) => {
377
+ const k = 1.70158;
378
+ return val * val * (val * (k + 1) - k);
379
+ };
380
+ this.overshootOut = (val) => {
381
+ const m = val - 1;
382
+ const k = 1.70158;
383
+ return 1 + m * m * (m * (k + 1) + k);
384
+ };
385
+ this.overshootInOut = (val) => {
386
+ const m = val - 1;
387
+ const t = val * 2;
388
+ const k = 1.70158 * 1.525;
389
+ if (val < 0.5)
390
+ return val * t * (t * (k + 1) - k);
391
+ return 1 + 2 * m * m * (2 * m * (k + 1) + k);
392
+ };
393
+ this.bounceOut = (val) => {
394
+ const r = 1 / 2.75;
395
+ const k1 = r;
396
+ const k2 = 2 * r;
397
+ const k3 = 1.5 * r;
398
+ const k4 = 2.5 * r;
399
+ const k5 = 2.25 * r;
400
+ const k6 = 2.625 * r;
401
+ const k0 = 7.5625;
402
+ let t;
403
+ if (val < k1) {
404
+ return k0 * val * val;
405
+ } else if (val < k2) {
406
+ t = val - k3;
407
+ return k0 * t * t + 0.75;
408
+ } else if (val < k4) {
409
+ t = val - k5;
410
+ return k0 * t * t + 0.9375;
411
+ }
412
+ t = val - k6;
413
+ return k0 * t * t + 0.984375;
414
+ };
415
+ this.bounceIn = (val) => {
416
+ return 1 - this.bounceOut(1 - val);
417
+ };
418
+ this.bounceInOut = (val) => {
419
+ const t = val * 2;
420
+ if (t < 1)
421
+ return 0.5 - 0.5 * this.bounceOut(1 - t);
422
+ return 0.5 + 0.5 * this.bounceOut(t - 1);
423
+ };
424
+ this.elasticIn = (val) => {
425
+ const m = val - 1;
426
+ return -Math.pow(2, 10 * m) * Math.sin((m * 40 - 3) * Math.PI / 6);
427
+ };
428
+ this.elasticOut = (val) => {
429
+ return 1 + Math.pow(2, 10 * -val) * Math.sin((-val * 40 - 3) * Math.PI / 6);
430
+ };
431
+ this.elasticInOut = (val) => {
432
+ const s = 2 * val - 1;
433
+ const k = (80 * s - 9) * Math.PI / 18;
434
+ if (s < 0)
435
+ return -0.5 * Math.pow(2, 10 * s) * Math.sin(k);
436
+ return 1 + 0.5 * Math.pow(2, -10 * s) * Math.sin(k);
437
+ };
438
+ this.smoothstep = (val, x0 = 0, x1 = 1) => {
439
+ let p = (val - x0) / (x1 - x0);
440
+ p = p < 0 ? 0 : p > 1 ? 1 : p;
441
+ return p * p * (3 - 2 * p);
442
+ };
443
+ this.log = () => {
444
+ console.log(this);
445
+ };
446
+ this.context = ctx;
447
+ }
448
+ };
449
+ var Easing_default = Easing;
450
+
451
+ // src/plugins/SVGfont.tsx
452
+ var SVGfont = class {
453
+ constructor(context) {
454
+ this.context = context;
455
+ this.font = "";
456
+ this.SCALE = 1;
457
+ this.targetXHeight = 256;
458
+ this.metrics = {
459
+ fontFamily: "",
460
+ fontWeight: "",
461
+ unitsPerEm: 0,
462
+ ascent: 0,
463
+ descent: 0,
464
+ xHeight: 0,
465
+ capHeight: 0
466
+ };
467
+ this.glyphs = /* @__PURE__ */ new Map();
468
+ this.context = context;
469
+ }
470
+ parse(font, desiredXHeight = 256) {
471
+ const parser = new DOMParser();
472
+ const doc = parser.parseFromString(font, "text/xml");
473
+ const fontFace = doc.querySelector("font-face");
474
+ if (fontFace) {
475
+ this.metrics.fontFamily = fontFace.getAttribute("font-family") || "";
476
+ this.metrics.fontWeight = fontFace.getAttribute("font-weight") || "";
477
+ this.metrics.unitsPerEm = parseInt(
478
+ fontFace.getAttribute("units-per-em") || "0"
479
+ );
480
+ this.metrics.ascent = parseInt(fontFace.getAttribute("ascent") || "0");
481
+ this.metrics.descent = parseInt(fontFace.getAttribute("descent") || "0");
482
+ this.metrics.xHeight = parseInt(fontFace.getAttribute("x-height") || "0");
483
+ this.metrics.capHeight = parseInt(
484
+ fontFace.getAttribute("cap-height") || "0"
485
+ );
486
+ }
487
+ this.targetXHeight = desiredXHeight;
488
+ this.SCALE = this.targetXHeight / this.metrics.xHeight;
489
+ const glyphElements = doc.querySelectorAll("glyph");
490
+ const basicCharRegex = /^[\w]$/;
491
+ const commonGlyphNames = /* @__PURE__ */ new Set([
492
+ "space",
493
+ "period",
494
+ "comma",
495
+ "hyphen",
496
+ "exclam",
497
+ "question",
498
+ "slash",
499
+ "less",
500
+ "greater",
501
+ "parenleft",
502
+ "parenright",
503
+ "braceleft",
504
+ "braceright",
505
+ "bracketleft",
506
+ "bracketright"
507
+ ]);
508
+ glyphElements.forEach((glyph) => {
509
+ const character = glyph.getAttribute("glyph-name");
510
+ const unicode = glyph.getAttribute("unicode");
511
+ if (!character || !unicode || !basicCharRegex.test(character) && !commonGlyphNames.has(character) && character.length > 1)
512
+ return;
513
+ const horizAdvX = parseInt(glyph.getAttribute("horiz-adv-x") || "0");
514
+ let pathData = glyph.getAttribute("d");
515
+ if (pathData) {
516
+ pathData = pathData.replace(
517
+ /[-+]?\d*\.?\d+/g,
518
+ (match) => (parseFloat(match) * this.SCALE).toString()
519
+ );
520
+ }
521
+ const glyphData = {
522
+ name: character,
523
+ unicode,
524
+ horizAdvX: horizAdvX * this.SCALE,
525
+ d: pathData || void 0
526
+ };
527
+ this.glyphs.set(unicode, glyphData);
528
+ });
529
+ if (!this.glyphs.has(" ")) {
530
+ this.glyphs.set(" ", {
531
+ name: "space",
532
+ unicode: " ",
533
+ horizAdvX: this.metrics.unitsPerEm * this.SCALE * 0.25,
534
+ // typical space width
535
+ d: void 0
536
+ });
537
+ }
538
+ }
539
+ toJSON() {
540
+ return {
541
+ metrics: this.metrics,
542
+ glyphs: Array.from(this.glyphs.entries()).reduce((obj, [key, value]) => {
543
+ obj[key] = value;
544
+ return obj;
545
+ }, {})
546
+ };
547
+ }
548
+ getPoints(text, options) {
549
+ const {
550
+ factor,
551
+ align = "left",
552
+ center = "alphabetic",
553
+ letterSpacing = 0
554
+ } = options;
555
+ const points = [];
556
+ const svgns = "http://www.w3.org/2000/svg";
557
+ const svg = document.createElementNS(svgns, "svg");
558
+ const path = document.createElementNS(svgns, "path");
559
+ svg.appendChild(path);
560
+ let totalWidth = 0;
561
+ const chars = text.split("");
562
+ chars.forEach((char, i) => {
563
+ const glyph = this.glyphs.get(char);
564
+ if (glyph) {
565
+ totalWidth += glyph.horizAdvX;
566
+ if (i < chars.length - 1)
567
+ totalWidth += letterSpacing;
568
+ }
569
+ });
570
+ let currentX = 0;
571
+ if (align === "center") {
572
+ currentX = -totalWidth / 2;
573
+ } else if (align === "right") {
574
+ currentX = -totalWidth;
575
+ }
576
+ console.log(this.SCALE);
577
+ let yOffset = 0;
578
+ switch (center) {
579
+ case "middle":
580
+ yOffset = -((this.metrics.ascent - this.metrics.descent) / 2) * this.SCALE * 0.5;
581
+ break;
582
+ case "top":
583
+ yOffset = this.metrics.ascent * this.SCALE;
584
+ break;
585
+ case "alphabetic":
586
+ default:
587
+ yOffset = 0;
588
+ break;
589
+ }
590
+ for (const char of chars) {
591
+ const glyph = this.glyphs.get(char);
592
+ if (!glyph)
593
+ continue;
594
+ if (!glyph.d) {
595
+ currentX += glyph.horizAdvX + letterSpacing;
596
+ continue;
597
+ }
598
+ path.setAttribute("d", glyph.d);
599
+ const subpaths = glyph.d.split(/(?=[Mm])/);
600
+ const glyphContours = [];
601
+ for (const subpath of subpaths) {
602
+ if (!subpath.trim())
603
+ continue;
604
+ path.setAttribute("d", subpath);
605
+ const pathLength = path.getTotalLength();
606
+ const numPoints = Math.max(10, Math.floor(pathLength * factor));
607
+ const contourPoints = [];
608
+ const step = pathLength / (numPoints - 1);
609
+ for (let i = 0; i < numPoints; i++) {
610
+ const point = path.getPointAtLength(i * step);
611
+ contourPoints.push({
612
+ x: point.x + currentX,
613
+ y: -(point.y + yOffset)
614
+ // Apply vertical offset
615
+ });
616
+ }
617
+ if (subpath.toLowerCase().includes("z")) {
618
+ contourPoints.push({ ...contourPoints[0] });
619
+ }
620
+ glyphContours.push(contourPoints);
621
+ }
622
+ glyphContours.sort((a, b) => b.length - a.length);
623
+ for (let i = 1; i < glyphContours.length; i++) {
624
+ glyphContours[i].reverse();
625
+ }
626
+ points.push(glyphContours);
627
+ currentX += glyph.horizAdvX + letterSpacing;
628
+ }
629
+ svg.remove();
630
+ return points;
631
+ }
632
+ flatten(points, displacement) {
633
+ return points.flatMap(
634
+ (glyph) => glyph.flatMap(
635
+ (contour, contourIndex) => contour.map((point) => {
636
+ if (displacement) {
637
+ return displacement({
638
+ point,
639
+ position: point,
640
+ groupIndex: contourIndex,
641
+ letterSpacing: 0
642
+ });
643
+ }
644
+ return point;
645
+ })
646
+ )
647
+ );
648
+ }
649
+ draw(points, displacement) {
650
+ for (const glyph of points) {
651
+ this.context.beginShape();
652
+ glyph.forEach((contour, contourIndex) => {
653
+ if (contourIndex !== 0) {
654
+ this.context.beginContour();
655
+ }
656
+ contour.forEach((point) => {
657
+ if (displacement) {
658
+ const { x, y } = displacement({
659
+ point,
660
+ position: point,
661
+ groupIndex: contourIndex,
662
+ letterSpacing: 0
663
+ // You can add letter spacing logic here
664
+ });
665
+ this.context.vertex(x, y);
666
+ } else {
667
+ this.context.vertex(point.x, point.y);
668
+ }
669
+ });
670
+ if (contourIndex !== 0) {
671
+ this.context.endContour();
672
+ }
673
+ });
674
+ this.context.endShape(true);
675
+ }
676
+ }
677
+ };
678
+ var SVGfont_default = SVGfont;
679
+
680
+ // src/plugins/State.tsx
681
+ var State = class {
682
+ constructor() {
683
+ this.store = /* @__PURE__ */ new Map();
684
+ }
685
+ set(key, value, callback) {
686
+ this.store.set(key, value);
687
+ callback?.(key, value);
688
+ }
689
+ get(key, callback) {
690
+ const value = this.store.get(key);
691
+ callback?.(key, value);
692
+ return value;
693
+ }
694
+ has(key) {
695
+ return this.store.has(key);
696
+ }
697
+ delete(key, callback) {
698
+ this.store.delete(key);
699
+ callback?.(key);
700
+ }
701
+ log() {
702
+ return this.store;
703
+ }
704
+ };
705
+ var State_default = State;
706
+
707
+ // src/plugins/Vector.tsx
708
+ var Vector = class {
709
+ /**
710
+ * Creates a new Vector
711
+ * @param x - X-coordinate (default: 0)
712
+ * @param y - Y-coordinate (default: 0)
713
+ */
714
+ constructor(x = 0, y = 0) {
715
+ this.x = x;
716
+ this.y = y;
717
+ }
718
+ /**
719
+ * Adds another vector to this vector
720
+ * @param v - Vector to add
721
+ * @returns This vector after addition
722
+ */
723
+ add(v) {
724
+ this.x += v.x;
725
+ this.y += v.y;
726
+ return this;
727
+ }
728
+ /**
729
+ * Subtracts another vector from this vector
730
+ * @param v - Vector to subtract
731
+ * @returns This vector after subtraction
732
+ */
733
+ sub(v) {
734
+ this.x -= v.x;
735
+ this.y -= v.y;
736
+ return this;
737
+ }
738
+ /**
739
+ * Multiplies this vector by a scalar
740
+ * @param n - Scalar to multiply by
741
+ * @returns This vector after multiplication
742
+ */
743
+ mult(n) {
744
+ this.x *= n;
745
+ this.y *= n;
746
+ return this;
747
+ }
748
+ /**
749
+ * Divides this vector by a scalar
750
+ * @param n - Scalar to divide by
751
+ * @returns This vector after division
752
+ */
753
+ div(n) {
754
+ this.x /= n;
755
+ this.y /= n;
756
+ return this;
757
+ }
758
+ // to do : project, perp, slerp
759
+ /**
760
+ * Rotates this vector by an angle
761
+ * @param angle - Angle in radians
762
+ * @returns This vector after rotation
763
+ */
764
+ rotate(angle) {
765
+ const cos = Math.cos(angle);
766
+ const sin = Math.sin(angle);
767
+ const x = this.x * cos - this.y * sin;
768
+ const y = this.x * sin + this.y * cos;
769
+ this.x = x;
770
+ this.y = y;
771
+ return this;
772
+ }
773
+ /**
774
+ * Calculates the magnitude (length) of this vector
775
+ * @returns The magnitude of the vector
776
+ */
777
+ mag() {
778
+ return Math.sqrt(this.x * this.x + this.y * this.y);
779
+ }
780
+ /**
781
+ * Alias for mag() - calculates the length of this vector
782
+ * @returns The length of the vector
783
+ */
784
+ length() {
785
+ return this.mag();
786
+ }
787
+ /**
788
+ * Calculates the dot product of this vector with another vector
789
+ * @param v - The other vector
790
+ * @returns The dot product
791
+ */
792
+ dot(v) {
793
+ return this.x * v.x + this.y * v.y;
794
+ }
795
+ /**
796
+ * Calculates the distance between this vector and another vector
797
+ * @param v - The other vector
798
+ * @returns The distance between the vectors
799
+ */
800
+ dist(v) {
801
+ return Math.hypot(this.x - v.x, this.y - v.y);
802
+ }
803
+ /**
804
+ * Calculates the angle of this vector
805
+ * @returns The angle in radians
806
+ */
807
+ angle() {
808
+ return Math.atan2(-this.x, -this.y) + Math.PI;
809
+ }
810
+ /**
811
+ * Creates a copy of this vector
812
+ * @returns A new Vector with the same coordinates
813
+ */
814
+ copy() {
815
+ return new Vector(this.x, this.y);
816
+ }
817
+ /**
818
+ * Normalizes this vector (sets its magnitude to 1)
819
+ * @returns This vector after normalization
820
+ */
821
+ normalize() {
822
+ const m = this.mag();
823
+ return m !== 0 ? this.div(m) : this;
824
+ }
825
+ /**
826
+ * Sets the coordinates of this vector
827
+ * @param x - New X-coordinate
828
+ * @param y - New Y-coordinate
829
+ * @returns This vector after setting coordinates
830
+ */
831
+ set(x, y) {
832
+ this.x = x;
833
+ this.y = y;
834
+ return this;
835
+ }
836
+ /**
837
+ * Creates a new vector at a specified angle and distance from a center point
838
+ * @param center - The center point vector
839
+ * @param a - The angle in radians
840
+ * @param r - The radius (distance from center)
841
+ * @returns A new Vector at the calculated position
842
+ */
843
+ static fromAngle(center, a, r) {
844
+ const x = Math.cos(a) * r + center.x;
845
+ const y = Math.sin(a) * r + center.y;
846
+ return new Vector(x, y);
847
+ }
848
+ };
849
+ var Vector_default = Vector;
850
+
851
+ // src/plugins/Time.tsx
852
+ var Time = class {
853
+ constructor(ctx) {
854
+ this.timelines = /* @__PURE__ */ new Map();
855
+ this.currentTimeline = "default";
856
+ this.DEFAULT_DURATION = 8 * 60;
857
+ this.staggers = [];
858
+ this.context = ctx;
859
+ this.timelines.set("default", {
860
+ progress: 0,
861
+ duration: this.DEFAULT_DURATION
862
+ });
863
+ }
864
+ timeline(key) {
865
+ if (!this.timelines.has(key)) {
866
+ this.timelines.set(key, { progress: 0, duration: this.DEFAULT_DURATION });
867
+ }
868
+ this.currentTimeline = key;
869
+ return this;
870
+ }
871
+ use(progress) {
872
+ const timeline = this.timelines.get(this.currentTimeline);
873
+ if (timeline.duration <= 0) {
874
+ timeline.progress = 0;
875
+ return this;
876
+ }
877
+ timeline.progress = timeline.duration === 1 ? Math.min(progress, 1) : progress / timeline.duration % 1;
878
+ return this;
879
+ }
880
+ for(duration) {
881
+ const timeline = this.timelines.get(this.currentTimeline);
882
+ timeline.duration = duration;
883
+ return this;
884
+ }
885
+ stagger(num, offset = 0, callback) {
886
+ const timeline = this.timelines.get(this.currentTimeline);
887
+ const totalduration = this.context.remap(
888
+ timeline.progress,
889
+ 0,
890
+ 1,
891
+ 0,
892
+ 1 + offset
893
+ );
894
+ for (let i = 0; i < num; i++) {
895
+ const id = 1 - i / (num - 1);
896
+ const progress = this.context.constrain(
897
+ totalduration - id * offset,
898
+ 0,
899
+ 1
900
+ );
901
+ if (!callback) {
902
+ if (this.staggers[i]) {
903
+ this.staggers[i].progress = progress;
904
+ } else {
905
+ this.staggers[i] = { progress, id };
906
+ }
907
+ } else {
908
+ callback?.(progress, id, num);
909
+ }
910
+ }
911
+ return callback ? this : this.staggers;
912
+ }
913
+ between(from = 0, to = 1, callback) {
914
+ const timeline = this.timelines.get(this.currentTimeline);
915
+ const localProgress = this.context.remap(
916
+ timeline.progress,
917
+ Math.max(0, from),
918
+ Math.min(1, to),
919
+ 0,
920
+ 1
921
+ );
922
+ callback(localProgress);
923
+ return this;
924
+ }
925
+ progress() {
926
+ return this.timelines.get(this.currentTimeline)?.progress || 0;
927
+ }
928
+ };
929
+ var Time_default = Time;
930
+
931
+ // src/plugins/Text.tsx
932
+ var Text = class {
933
+ constructor(ctx) {
934
+ this.findTextSize = (text, dist, estimate, direction = "x") => {
935
+ let low = 1;
936
+ let high = estimate || this.context.__textSize * 2;
937
+ let lastValidSize = low;
938
+ for (let i = 0; i < 16; i++) {
939
+ const mid = Math.floor((low + high) / 2);
940
+ this.context.__textSize = mid;
941
+ const bounds = this.textBounds(text);
942
+ const size = direction === "x" ? bounds.width : bounds.height;
943
+ if (size === dist) {
944
+ return mid;
945
+ } else if (size < dist) {
946
+ lastValidSize = mid;
947
+ low = mid + 1;
948
+ } else {
949
+ high = mid - 1;
950
+ }
951
+ }
952
+ return lastValidSize;
953
+ };
954
+ this.getTextMetrics = (text) => {
955
+ const ctx = this.context;
956
+ const metrics = ctx.measureText(text);
957
+ return {
958
+ width: metrics.width,
959
+ height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent,
960
+ baseline: metrics.actualBoundingBoxAscent
961
+ };
962
+ };
963
+ this.splitTo = (text, kind, options = {}) => {
964
+ const ctx = this.context;
965
+ const {
966
+ maxWidth = 0,
967
+ lineSpacing = 0,
968
+ letterSpacing = 0,
969
+ wordSpacing = 0
970
+ } = options;
971
+ ctx.computeFont();
972
+ if (maxWidth < this.textBounds(" ").width * 2 && maxWidth !== 0) {
973
+ return [];
974
+ }
975
+ const lines = text.split("\n");
976
+ const lineHeights = lines.map((line) => this.getTextMetrics(line).height);
977
+ const totalHeight = lineHeights.reduce((sum, height) => sum + height, 0) + (lines.length - 1) * (lineSpacing || 0);
978
+ let y = ctx.textBaseline === "middle" ? -totalHeight / 2 : 0;
979
+ return lines.flatMap((lineText, lineIndex) => {
980
+ const words = lineText.split(" ");
981
+ const currentLine = {
982
+ text: "",
983
+ width: 0,
984
+ letters: []
985
+ };
986
+ const totalWidth = this.getTextMetrics(lineText).width;
987
+ let startX = 0;
988
+ switch (ctx.textAlign) {
989
+ case "center":
990
+ startX = -totalWidth / 2;
991
+ break;
992
+ case "right":
993
+ startX = -totalWidth;
994
+ break;
995
+ }
996
+ let x = startX;
997
+ let letterIndex = 0;
998
+ let wordIndex = 0;
999
+ const lineLetters = words.flatMap((word) => {
1000
+ const letters = [];
1001
+ for (const char of word) {
1002
+ const charMetrics = this.getTextMetrics(char);
1003
+ x += charMetrics.width / 2;
1004
+ const letterData = {
1005
+ char,
1006
+ x,
1007
+ y: y + (ctx.textBaseline === "middle" ? lineHeights[lineIndex] / 2 : 0),
1008
+ metrics: charMetrics,
1009
+ ...kind === "all" && {
1010
+ letterIndex,
1011
+ wordIndex,
1012
+ lineIndex
1013
+ }
1014
+ };
1015
+ letters.push(letterData);
1016
+ currentLine.text += char;
1017
+ x += charMetrics.width / 2 + letterSpacing;
1018
+ letterIndex++;
1019
+ }
1020
+ if (wordIndex < words.length - 1) {
1021
+ const spaceMetrics = this.getTextMetrics(" ");
1022
+ x += spaceMetrics.width + wordSpacing;
1023
+ letters.push({
1024
+ char: " ",
1025
+ x,
1026
+ y,
1027
+ metrics: spaceMetrics,
1028
+ ...kind === "all" && {
1029
+ letterIndex,
1030
+ wordIndex,
1031
+ lineIndex
1032
+ }
1033
+ });
1034
+ currentLine.text += " ";
1035
+ letterIndex++;
1036
+ }
1037
+ wordIndex++;
1038
+ return letters;
1039
+ });
1040
+ const lineHeight = lineHeights[lineIndex];
1041
+ y += lineHeight + lineSpacing;
1042
+ return lineLetters;
1043
+ });
1044
+ };
1045
+ this.circularText = (text, radius = 100, fill = "fill", offset = 0, arc = Math.PI * 2) => {
1046
+ const totalAngle = Math.min(Math.max(arc, 0), Math.PI * 2);
1047
+ if (fill === "fill") {
1048
+ const chars = text.split("");
1049
+ const anglePerChar = totalAngle / (chars.length + 1);
1050
+ chars.forEach((char, i) => {
1051
+ const angle = anglePerChar * i + offset;
1052
+ const x = Math.cos(angle) * radius;
1053
+ const y = Math.sin(angle) * radius;
1054
+ this.context.push();
1055
+ this.context.textAlign = "center";
1056
+ this.context.translate(x, y);
1057
+ this.context.rotate(angle + Math.PI / 2);
1058
+ this.context.text(char, 0, 0);
1059
+ this.context.pop();
1060
+ });
1061
+ } else if (fill === "kerned") {
1062
+ let currentAngle = offset;
1063
+ text.split("").forEach((char) => {
1064
+ const charWidth = this.textBounds(char).width;
1065
+ currentAngle += charWidth / radius * 0.5;
1066
+ const angle = currentAngle;
1067
+ const x = Math.cos(angle) * radius;
1068
+ const y = Math.sin(angle) * radius;
1069
+ this.context.push();
1070
+ this.context.textAlign = "center";
1071
+ this.context.translate(x, y);
1072
+ this.context.rotate(angle + Math.PI / 2);
1073
+ this.context.text(char, 0, 0);
1074
+ this.context.pop();
1075
+ currentAngle += charWidth / radius * 0.5;
1076
+ });
1077
+ } else if (fill === "words") {
1078
+ const words = text.split(" ");
1079
+ const wordMetrics = words.map((word) => ({
1080
+ word,
1081
+ width: this.textBounds(word).width
1082
+ }));
1083
+ const spaceCount = words.length;
1084
+ const totalWordWidth = wordMetrics.reduce((sum, m) => sum + m.width, 0);
1085
+ const spaceAngle = (totalAngle - totalWordWidth / radius) / spaceCount;
1086
+ let currentAngle = offset;
1087
+ wordMetrics.forEach(({ word }, i) => {
1088
+ word.split("").forEach((char) => {
1089
+ const charWidth = this.textBounds(char).width;
1090
+ currentAngle += charWidth / radius * 0.5;
1091
+ const x = Math.cos(currentAngle) * radius;
1092
+ const y = Math.sin(currentAngle) * radius;
1093
+ this.context.push();
1094
+ this.context.textAlign = "center";
1095
+ this.context.translate(x, y);
1096
+ this.context.rotate(currentAngle + Math.PI / 2);
1097
+ this.context.text(char, 0, 0);
1098
+ this.context.pop();
1099
+ currentAngle += charWidth / radius * 0.5;
1100
+ });
1101
+ if (i < words.length - 1) {
1102
+ currentAngle += spaceAngle;
1103
+ }
1104
+ });
1105
+ }
1106
+ };
1107
+ this.textBounds = (text) => {
1108
+ if (this.context.font !== this.context.__computedTextFont) {
1109
+ this.context.font = this.context.__computedTextFont;
1110
+ }
1111
+ const metrics = this.context.measureText(text);
1112
+ return {
1113
+ x: metrics.actualBoundingBoxLeft * -1,
1114
+ y: metrics.actualBoundingBoxAscent * -1,
1115
+ width: metrics.width,
1116
+ height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
1117
+ };
1118
+ };
1119
+ this.log = () => {
1120
+ console.log(this.context);
1121
+ };
1122
+ this.context = ctx;
1123
+ }
1124
+ };
1125
+ var Text_default = Text;
1126
+
1127
+ // src/plugins/Thing.tsx
1128
+ var Thing = class {
1129
+ constructor(ctx) {
1130
+ this.context = ctx;
1131
+ }
1132
+ log() {
1133
+ console.log(this.context);
1134
+ }
1135
+ };
1136
+ var Thing_default = Thing;
1137
+ export {
1138
+ Color_default as Color,
1139
+ Easing_default as Easing,
1140
+ SVGfont_default as SVGfont,
1141
+ State_default as State,
1142
+ Text_default as Text,
1143
+ Thing_default as Thing,
1144
+ Time_default as Time,
1145
+ Vector_default as Vector
1146
+ };