@shopify/klint 0.0.91 → 0.0.92

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/dist/index.js CHANGED
@@ -88,7 +88,9 @@ function Klint({
88
88
  const containerRef = useRef(null);
89
89
  const contextRef = useRef(null);
90
90
  const intersectionObserverRef = useRef(null);
91
- const resizeObserverRef = useRef(null);
91
+ const resizeCallbackRef = useRef(
92
+ null
93
+ );
92
94
  const [isVisible, setIsVisible] = useState(true);
93
95
  const __options = {
94
96
  ...DEFAULT_OPTIONS,
@@ -130,10 +132,11 @@ function Klint({
130
132
  }
131
133
  updateCanvasSize();
132
134
  if (__options.ignoreResize !== "true") {
133
- resizeObserverRef.current = new ResizeObserver(() => {
135
+ const handleResize = () => {
134
136
  updateCanvasSize(context2.__isReadyToDraw);
135
- });
136
- resizeObserverRef.current.observe(container);
137
+ };
138
+ window.addEventListener("resize", handleResize);
139
+ resizeCallbackRef.current = handleResize;
137
140
  }
138
141
  intersectionObserverRef.current = new IntersectionObserver(
139
142
  (entries) => {
@@ -195,12 +198,13 @@ function Klint({
195
198
  }
196
199
  };
197
200
  initializeKlint();
198
- const frameId = animationFrameId.current;
199
201
  return () => {
200
- resizeObserverRef.current?.disconnect();
202
+ if (resizeCallbackRef.current) {
203
+ window.removeEventListener("resize", resizeCallbackRef.current);
204
+ }
201
205
  intersectionObserverRef.current?.disconnect();
202
- if (frameId) {
203
- cancelAnimationFrame(frameId);
206
+ if (animationFrameId.current) {
207
+ cancelAnimationFrame(animationFrameId.current);
204
208
  }
205
209
  };
206
210
  }, []);
@@ -232,6 +236,911 @@ function Klint({
232
236
  // src/useKlint.tsx
233
237
  import { useRef as useRef2, useCallback as useCallback2, useEffect as useEffect2, useMemo } from "react";
234
238
 
239
+ // src/elements/Color.tsx
240
+ var Color = class {
241
+ constructor() {
242
+ // context: KlintContexts;
243
+ /**
244
+ * Array of predefined colors in the Klint color palette
245
+ */
246
+ this.colors = [
247
+ "#E84D37",
248
+ // coral
249
+ "#7F4C2F",
250
+ // brown
251
+ "#EDBC2F",
252
+ // mustard
253
+ "#BF3034",
254
+ // crimson
255
+ "#18599D",
256
+ // navy
257
+ "#45A7C6",
258
+ // sky
259
+ "#8CB151",
260
+ // olive
261
+ "#252120",
262
+ // charcoal
263
+ "#ECA088",
264
+ // peach
265
+ "#C9B1B8",
266
+ // rose
267
+ "#8F3064",
268
+ // plum
269
+ "#7B8870",
270
+ // sage
271
+ "#C0C180",
272
+ // drab
273
+ "#4B423D",
274
+ // taupe
275
+ "#1A2A65",
276
+ // midnight
277
+ "#EAA550",
278
+ // golden
279
+ "#F17B04",
280
+ // orange
281
+ "#404757"
282
+ // slate
283
+ ];
284
+ }
285
+ // /**
286
+ // * Creates a new Color instance
287
+ // * @param ctx - The Klint context
288
+ // */
289
+ // constructor(ctx: KlintContexts) {
290
+ // this.context = ctx;
291
+ // }
292
+ get coral() {
293
+ return this.colors[0];
294
+ }
295
+ get brown() {
296
+ return this.colors[1];
297
+ }
298
+ get mustard() {
299
+ return this.colors[2];
300
+ }
301
+ get crimson() {
302
+ return this.colors[3];
303
+ }
304
+ get navy() {
305
+ return this.colors[4];
306
+ }
307
+ get sky() {
308
+ return this.colors[5];
309
+ }
310
+ get olive() {
311
+ return this.colors[6];
312
+ }
313
+ get charcoal() {
314
+ return this.colors[7];
315
+ }
316
+ get peach() {
317
+ return this.colors[8];
318
+ }
319
+ get rose() {
320
+ return this.colors[9];
321
+ }
322
+ get plum() {
323
+ return this.colors[10];
324
+ }
325
+ get sage() {
326
+ return this.colors[11];
327
+ }
328
+ get drab() {
329
+ return this.colors[12];
330
+ }
331
+ get taupe() {
332
+ return this.colors[13];
333
+ }
334
+ get midnight() {
335
+ return this.colors[14];
336
+ }
337
+ get golden() {
338
+ return this.colors[15];
339
+ }
340
+ get orange() {
341
+ return this.colors[16];
342
+ }
343
+ get slate() {
344
+ return this.colors[17];
345
+ }
346
+ /**
347
+ * Ensures a color string has a # prefix
348
+ * @param color - Color string in hex format (with or without #)
349
+ * @returns Hex color string with # prefix
350
+ */
351
+ hex(color) {
352
+ return color.startsWith("#") ? color : `#${color}`;
353
+ }
354
+ /**
355
+ * Creates an RGB color string
356
+ * @param r - Red component (0-255)
357
+ * @param g - Green component (0-255)
358
+ * @param b - Blue component (0-255)
359
+ * @returns RGB color string
360
+ */
361
+ rgb(r, g, b) {
362
+ return `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
363
+ }
364
+ /**
365
+ * Creates an RGBA color string
366
+ * @param r - Red component (0-255)
367
+ * @param g - Green component (0-255)
368
+ * @param b - Blue component (0-255)
369
+ * @param alpha - Alpha/opacity value (0-1)
370
+ * @returns RGBA color string
371
+ */
372
+ rgba(r, g, b, alpha) {
373
+ return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(
374
+ b
375
+ )}, ${alpha})`;
376
+ }
377
+ /**
378
+ * Creates a grayscale color
379
+ * @param value - Gray value (0-255)
380
+ * @param alpha - Optional alpha/opacity value (0-1)
381
+ * @returns RGB or RGBA grayscale color string
382
+ */
383
+ gray(value, alpha) {
384
+ return alpha !== void 0 ? `rgba(${Math.round(value)}, ${Math.round(value)}, ${Math.round(
385
+ value
386
+ )}, ${alpha})` : `rgb(${Math.round(value)}, ${Math.round(value)}, ${Math.round(value)})`;
387
+ }
388
+ /**
389
+ * Creates an HSL color string
390
+ * @param h - Hue (0-360)
391
+ * @param s - Saturation percentage (0-100)
392
+ * @param l - Lightness percentage (0-100)
393
+ * @returns HSL color string
394
+ */
395
+ hsl(h, s, l) {
396
+ return `hsl(${h % 360}, ${Math.max(0, s)}%, ${Math.max(0, l)}%)`;
397
+ }
398
+ /**
399
+ * Creates an HSLA color string
400
+ * @param h - Hue (0-360)
401
+ * @param s - Saturation percentage (0-100)
402
+ * @param l - Lightness percentage (0-100)
403
+ * @param alpha - Alpha/opacity value (0-1)
404
+ * @returns HSLA color string
405
+ */
406
+ hsla(h, s, l, alpha) {
407
+ return `hsla(${h % 360}, ${Math.max(0, s)}%, ${Math.max(0, l)}%, ${alpha})`;
408
+ }
409
+ /**
410
+ * Creates an LCH color string
411
+ * @param l - Lightness percentage (0-100)
412
+ * @param c - Chroma value
413
+ * @param h - Hue (0-360)
414
+ * @returns LCH color string
415
+ */
416
+ lch(l, c, h) {
417
+ return `lch(${l}% ${c} ${h})`;
418
+ }
419
+ /**
420
+ * Creates an LCH color string with alpha
421
+ * @param l - Lightness percentage (0-100)
422
+ * @param c - Chroma value
423
+ * @param h - Hue (0-360)
424
+ * @param alpha - Alpha/opacity value (0-1)
425
+ * @returns LCH color string with alpha
426
+ */
427
+ lcha(l, c, h, alpha) {
428
+ return `lch(${l}% ${c} ${h} / ${alpha})`;
429
+ }
430
+ /**
431
+ * Creates a LAB color string
432
+ * @param l - Lightness percentage (0-100)
433
+ * @param a - A-axis value (green to red)
434
+ * @param b - B-axis value (blue to yellow)
435
+ * @returns LAB color string
436
+ */
437
+ lab(l, a, b) {
438
+ return `lab(${l}% ${a} ${b})`;
439
+ }
440
+ /**
441
+ * Creates a LAB color string with alpha
442
+ * @param l - Lightness percentage (0-100)
443
+ * @param a - A-axis value (green to red)
444
+ * @param b - B-axis value (blue to yellow)
445
+ * @param alpha - Alpha/opacity value (0-1)
446
+ * @returns LAB color string with alpha
447
+ */
448
+ laba(l, a, b, alpha) {
449
+ return `lab(${l}% ${a} ${b} / ${alpha})`;
450
+ }
451
+ /**
452
+ * Creates an OKLCH color string
453
+ * @param l - Lightness value (0-1)
454
+ * @param c - Chroma value
455
+ * @param h - Hue (0-360)
456
+ * @returns OKLCH color string
457
+ */
458
+ oklch(l, c, h) {
459
+ return `oklch(${l} ${c} ${h})`;
460
+ }
461
+ /**
462
+ * Creates an OKLCH color string with alpha
463
+ * @param l - Lightness value (0-1)
464
+ * @param c - Chroma value
465
+ * @param h - Hue (0-360)
466
+ * @param alpha - Alpha/opacity value (0-1)
467
+ * @returns OKLCH color string with alpha
468
+ */
469
+ oklcha(l, c, h, alpha) {
470
+ return `oklch(${l} ${c} ${h} / ${alpha})`;
471
+ }
472
+ /**
473
+ * Creates an OKLAB color string
474
+ * @param l - Lightness value (0-1)
475
+ * @param a - A-axis value (green to red)
476
+ * @param b - B-axis value (blue to yellow)
477
+ * @returns OKLAB color string
478
+ */
479
+ oklab(l, a, b) {
480
+ return `oklab(${l} ${a} ${b})`;
481
+ }
482
+ /**
483
+ * Creates an OKLAB color string with alpha
484
+ * @param l - Lightness value (0-1)
485
+ * @param a - A-axis value (green to red)
486
+ * @param b - B-axis value (blue to yellow)
487
+ * @param alpha - Alpha/opacity value (0-1)
488
+ * @returns OKLAB color string with alpha
489
+ */
490
+ oklaba(l, a, b, alpha) {
491
+ return `oklab(${l} ${a} ${b} / ${alpha})`;
492
+ }
493
+ /**
494
+ * Blends two colors using CSS color-mix
495
+ * @param colorA - First color
496
+ * @param colorB - Second color
497
+ * @param factor - Blend factor (0-1) where 0 is colorA and 1 is colorB
498
+ * @param colorMode - Color space to blend in (e.g., "oklch", "hsl")
499
+ * @returns Blended color string
500
+ */
501
+ blendColors(colorA, colorB, factor, colorMode = "oklch") {
502
+ const t = Math.max(0, Math.min(1, factor)) * 100;
503
+ return `color-mix(in ${colorMode}, ${colorA}, ${colorB} ${t}%)`;
504
+ }
505
+ /**
506
+ * Creates a palette of colors based on a single base color
507
+ * @param baseColor - The base color to create palette from
508
+ * @param steps - Number of steps in each direction (lighter/darker)
509
+ * @returns Array of color strings forming a palette
510
+ */
511
+ createPalette(baseColor, steps = 9) {
512
+ const palette = [];
513
+ for (let i = 1; i < steps; i++) {
514
+ const factor = i / steps;
515
+ palette.unshift(this.blendColors(baseColor, "#ffffff", factor, "oklch"));
516
+ }
517
+ palette.push(baseColor);
518
+ for (let i = 1; i < steps; i++) {
519
+ const factor = i / steps;
520
+ palette.push(this.blendColors(baseColor, "#000000", factor, "oklch"));
521
+ }
522
+ return palette;
523
+ }
524
+ /**
525
+ * Creates a complementary color (opposite on the color wheel)
526
+ * @param color - Base color
527
+ * @returns Complementary color string
528
+ */
529
+ complementary(color) {
530
+ return this.blendColors(color, "hsl(180deg 100% 50%)", 1, "hsl");
531
+ }
532
+ /**
533
+ * Creates analogous colors (adjacent on the color wheel)
534
+ * @param color - Base color
535
+ * @param angle - Angle of separation in degrees
536
+ * @returns Tuple of two analogous color strings
537
+ */
538
+ analogous(color, angle = 30) {
539
+ return [
540
+ this.blendColors(color, `hsl(${-angle}deg 100% 50%)`, 1, "hsl"),
541
+ this.blendColors(color, `hsl(${angle}deg 100% 50%)`, 1, "hsl")
542
+ ];
543
+ }
544
+ /**
545
+ * Creates a triadic color scheme (three colors evenly spaced on the color wheel)
546
+ * @param color - Base color
547
+ * @returns Tuple of two additional colors to form a triadic scheme
548
+ */
549
+ triadic(color) {
550
+ return [
551
+ this.blendColors(color, "hsl(120deg 100% 50%)", 1, "hsl"),
552
+ this.blendColors(color, "hsl(240deg 100% 50%)", 1, "hsl")
553
+ ];
554
+ }
555
+ /**
556
+ * Increases the saturation of a color
557
+ * @param color - Base color
558
+ * @param amount - Amount to saturate (percentage)
559
+ * @returns Saturated color string
560
+ */
561
+ saturate(color, amount) {
562
+ return this.blendColors(
563
+ color,
564
+ "hsl(0deg 100% 50% / 0%)",
565
+ amount / 100,
566
+ "hsl"
567
+ );
568
+ }
569
+ /**
570
+ * Lightens a color by mixing with white
571
+ * @param color - Base color
572
+ * @param amount - Amount to lighten (percentage)
573
+ * @returns Lightened color string
574
+ */
575
+ lighten(color, amount) {
576
+ return this.blendColors(color, "white", amount / 100, "hsl");
577
+ }
578
+ /**
579
+ * Darkens a color by mixing with black
580
+ * @param color - Base color
581
+ * @param amount - Amount to darken (percentage)
582
+ * @returns Darkened color string
583
+ */
584
+ darken(color, amount) {
585
+ return this.blendColors(color, "black", amount / 100, "hsl");
586
+ }
587
+ };
588
+ var Color_default = Color;
589
+
590
+ // src/elements/Easing.tsx
591
+ var Easing = class {
592
+ constructor(ctx) {
593
+ this.normalize = (val) => {
594
+ return val * 0.5 + 0.5;
595
+ };
596
+ this.expand = (val) => {
597
+ return val * 2 - 1;
598
+ };
599
+ this.inout = (val, power = 2) => {
600
+ const m = val - 1;
601
+ const t = val * 2;
602
+ if (t < 1) {
603
+ return val * Math.pow(t, power - 1);
604
+ }
605
+ return power % 2 === 0 ? 1 - Math.pow(m, power) * Math.pow(2, power - 1) : 1 + Math.pow(m, power) * Math.pow(2, power - 1);
606
+ };
607
+ this.in = (val, power = 2) => {
608
+ return Math.pow(val, power);
609
+ };
610
+ this.out = (val, power = 2) => {
611
+ const m = val - 1;
612
+ return power % 2 === 0 ? 1 - Math.pow(m, power) : 1 + Math.pow(m, power);
613
+ };
614
+ this.overshootIn = (val) => {
615
+ const k = 1.70158;
616
+ return val * val * (val * (k + 1) - k);
617
+ };
618
+ this.overshootOut = (val) => {
619
+ const m = val - 1;
620
+ const k = 1.70158;
621
+ return 1 + m * m * (m * (k + 1) + k);
622
+ };
623
+ this.overshootInOut = (val) => {
624
+ const m = val - 1;
625
+ const t = val * 2;
626
+ const k = 1.70158 * 1.525;
627
+ if (val < 0.5) return val * t * (t * (k + 1) - k);
628
+ return 1 + 2 * m * m * (2 * m * (k + 1) + k);
629
+ };
630
+ this.bounceOut = (val) => {
631
+ const r = 1 / 2.75;
632
+ const k1 = r;
633
+ const k2 = 2 * r;
634
+ const k3 = 1.5 * r;
635
+ const k4 = 2.5 * r;
636
+ const k5 = 2.25 * r;
637
+ const k6 = 2.625 * r;
638
+ const k0 = 7.5625;
639
+ let t;
640
+ if (val < k1) {
641
+ return k0 * val * val;
642
+ } else if (val < k2) {
643
+ t = val - k3;
644
+ return k0 * t * t + 0.75;
645
+ } else if (val < k4) {
646
+ t = val - k5;
647
+ return k0 * t * t + 0.9375;
648
+ }
649
+ t = val - k6;
650
+ return k0 * t * t + 0.984375;
651
+ };
652
+ this.bounceIn = (val) => {
653
+ return 1 - this.bounceOut(1 - val);
654
+ };
655
+ this.bounceInOut = (val) => {
656
+ const t = val * 2;
657
+ if (t < 1) return 0.5 - 0.5 * this.bounceOut(1 - t);
658
+ return 0.5 + 0.5 * this.bounceOut(t - 1);
659
+ };
660
+ this.elasticIn = (val) => {
661
+ const m = val - 1;
662
+ return -Math.pow(2, 10 * m) * Math.sin((m * 40 - 3) * Math.PI / 6);
663
+ };
664
+ this.elasticOut = (val) => {
665
+ return 1 + Math.pow(2, 10 * -val) * Math.sin((-val * 40 - 3) * Math.PI / 6);
666
+ };
667
+ this.elasticInOut = (val) => {
668
+ const s = 2 * val - 1;
669
+ const k = (80 * s - 9) * Math.PI / 18;
670
+ if (s < 0) return -0.5 * Math.pow(2, 10 * s) * Math.sin(k);
671
+ return 1 + 0.5 * Math.pow(2, -10 * s) * Math.sin(k);
672
+ };
673
+ this.smoothstep = (val, x0 = 0, x1 = 1) => {
674
+ let p = (val - x0) / (x1 - x0);
675
+ p = p < 0 ? 0 : p > 1 ? 1 : p;
676
+ return p * p * (3 - 2 * p);
677
+ };
678
+ this.log = () => {
679
+ console.log(this);
680
+ };
681
+ this.context = ctx;
682
+ }
683
+ };
684
+ var Easing_default = Easing;
685
+
686
+ // src/elements/State.tsx
687
+ var State = class {
688
+ constructor() {
689
+ this.store = /* @__PURE__ */ new Map();
690
+ }
691
+ set(key, value, callback) {
692
+ this.store.set(key, value);
693
+ callback?.(key, value);
694
+ }
695
+ get(key, callback) {
696
+ const value = this.store.get(key);
697
+ callback?.(key, value);
698
+ return value;
699
+ }
700
+ has(key) {
701
+ return this.store.has(key);
702
+ }
703
+ delete(key, callback) {
704
+ this.store.delete(key);
705
+ callback?.(key);
706
+ }
707
+ log() {
708
+ return this.store;
709
+ }
710
+ };
711
+ var State_default = State;
712
+
713
+ // src/elements/Vector.tsx
714
+ var Vector = class _Vector {
715
+ /**
716
+ * Creates a new Vector
717
+ * @param x - X-coordinate (default: 0)
718
+ * @param y - Y-coordinate (default: 0)
719
+ */
720
+ constructor(x = 0, y = 0) {
721
+ this.x = x;
722
+ this.y = y;
723
+ }
724
+ /**
725
+ * Adds another vector to this vector
726
+ * @param v - Vector to add
727
+ * @returns This vector after addition
728
+ */
729
+ add(v) {
730
+ this.x += v.x;
731
+ this.y += v.y;
732
+ return this;
733
+ }
734
+ /**
735
+ * Subtracts another vector from this vector
736
+ * @param v - Vector to subtract
737
+ * @returns This vector after subtraction
738
+ */
739
+ sub(v) {
740
+ this.x -= v.x;
741
+ this.y -= v.y;
742
+ return this;
743
+ }
744
+ /**
745
+ * Multiplies this vector by a scalar
746
+ * @param n - Scalar to multiply by
747
+ * @returns This vector after multiplication
748
+ */
749
+ mult(n) {
750
+ this.x *= n;
751
+ this.y *= n;
752
+ return this;
753
+ }
754
+ /**
755
+ * Divides this vector by a scalar
756
+ * @param n - Scalar to divide by
757
+ * @returns This vector after division
758
+ */
759
+ div(n) {
760
+ this.x /= n;
761
+ this.y /= n;
762
+ return this;
763
+ }
764
+ // to do : project, perp, slerp
765
+ /**
766
+ * Rotates this vector by an angle
767
+ * @param angle - Angle in radians
768
+ * @returns This vector after rotation
769
+ */
770
+ rotate(angle) {
771
+ const cos = Math.cos(angle);
772
+ const sin = Math.sin(angle);
773
+ const x = this.x * cos - this.y * sin;
774
+ const y = this.x * sin + this.y * cos;
775
+ this.x = x;
776
+ this.y = y;
777
+ return this;
778
+ }
779
+ /**
780
+ * Calculates the magnitude (length) of this vector
781
+ * @returns The magnitude of the vector
782
+ */
783
+ mag() {
784
+ return Math.sqrt(this.x * this.x + this.y * this.y);
785
+ }
786
+ /**
787
+ * Alias for mag() - calculates the length of this vector
788
+ * @returns The length of the vector
789
+ */
790
+ length() {
791
+ return this.mag();
792
+ }
793
+ /**
794
+ * Calculates the dot product of this vector with another vector
795
+ * @param v - The other vector
796
+ * @returns The dot product
797
+ */
798
+ dot(v) {
799
+ return this.x * v.x + this.y * v.y;
800
+ }
801
+ /**
802
+ * Calculates the distance between this vector and another vector
803
+ * @param v - The other vector
804
+ * @returns The distance between the vectors
805
+ */
806
+ dist(v) {
807
+ return Math.hypot(this.x - v.x, this.y - v.y);
808
+ }
809
+ /**
810
+ * Calculates the angle of this vector
811
+ * @returns The angle in radians
812
+ */
813
+ angle() {
814
+ return Math.atan2(-this.x, -this.y) + Math.PI;
815
+ }
816
+ /**
817
+ * Creates a copy of this vector
818
+ * @returns A new Vector with the same coordinates
819
+ */
820
+ copy() {
821
+ return new _Vector(this.x, this.y);
822
+ }
823
+ /**
824
+ * Normalizes this vector (sets its magnitude to 1)
825
+ * @returns This vector after normalization
826
+ */
827
+ normalize() {
828
+ const m = this.mag();
829
+ return m !== 0 ? this.div(m) : this;
830
+ }
831
+ /**
832
+ * Sets the coordinates of this vector
833
+ * @param x - New X-coordinate
834
+ * @param y - New Y-coordinate
835
+ * @returns This vector after setting coordinates
836
+ */
837
+ set(x, y) {
838
+ this.x = x;
839
+ this.y = y;
840
+ return this;
841
+ }
842
+ /**
843
+ * Creates a new vector at a specified angle and distance from a center point
844
+ * @param center - The center point vector
845
+ * @param a - The angle in radians
846
+ * @param r - The radius (distance from center)
847
+ * @returns A new Vector at the calculated position
848
+ */
849
+ static fromAngle(center, a, r) {
850
+ const x = Math.cos(a) * r + center.x;
851
+ const y = Math.sin(a) * r + center.y;
852
+ return new _Vector(x, y);
853
+ }
854
+ };
855
+ var Vector_default = Vector;
856
+
857
+ // src/elements/Time.tsx
858
+ var Time = class {
859
+ constructor(ctx) {
860
+ this.timelines = /* @__PURE__ */ new Map();
861
+ this.currentTimeline = "default";
862
+ this.DEFAULT_DURATION = 8 * 60;
863
+ this.staggers = [];
864
+ this.context = ctx;
865
+ this.timelines.set("default", {
866
+ progress: 0,
867
+ duration: this.DEFAULT_DURATION
868
+ });
869
+ }
870
+ timeline(key) {
871
+ if (!this.timelines.has(key)) {
872
+ this.timelines.set(key, { progress: 0, duration: this.DEFAULT_DURATION });
873
+ }
874
+ this.currentTimeline = key;
875
+ return this;
876
+ }
877
+ use(progress) {
878
+ const timeline = this.timelines.get(this.currentTimeline);
879
+ if (timeline.duration <= 0) {
880
+ timeline.progress = 0;
881
+ return this;
882
+ }
883
+ timeline.progress = timeline.duration === 1 ? Math.min(progress, 1) : progress / timeline.duration % 1;
884
+ return this;
885
+ }
886
+ for(duration) {
887
+ const timeline = this.timelines.get(this.currentTimeline);
888
+ timeline.duration = duration;
889
+ return this;
890
+ }
891
+ stagger(num, offset = 0, callback) {
892
+ const timeline = this.timelines.get(this.currentTimeline);
893
+ const totalduration = this.context.remap(
894
+ timeline.progress,
895
+ 0,
896
+ 1,
897
+ 0,
898
+ 1 + offset
899
+ );
900
+ for (let i = 0; i < num; i++) {
901
+ const id = 1 - i / (num - 1);
902
+ const progress = this.context.constrain(
903
+ totalduration - id * offset,
904
+ 0,
905
+ 1
906
+ );
907
+ if (!callback) {
908
+ if (this.staggers[i]) {
909
+ this.staggers[i].progress = progress;
910
+ } else {
911
+ this.staggers[i] = { progress, id };
912
+ }
913
+ } else {
914
+ callback?.(progress, id, num);
915
+ }
916
+ }
917
+ return callback ? this : this.staggers;
918
+ }
919
+ between(from = 0, to = 1, callback) {
920
+ const timeline = this.timelines.get(this.currentTimeline);
921
+ const localProgress = this.context.remap(
922
+ timeline.progress,
923
+ Math.max(0, from),
924
+ Math.min(1, to),
925
+ 0,
926
+ 1
927
+ );
928
+ callback(localProgress);
929
+ return this;
930
+ }
931
+ progress() {
932
+ return this.timelines.get(this.currentTimeline)?.progress || 0;
933
+ }
934
+ };
935
+ var Time_default = Time;
936
+
937
+ // src/elements/Text.tsx
938
+ var Text = class {
939
+ constructor(ctx) {
940
+ this.findTextSize = (text, dist, estimate, direction = "x") => {
941
+ let low = 1;
942
+ let high = estimate || this.context.__textSize * 2;
943
+ let lastValidSize = low;
944
+ for (let i = 0; i < 16; i++) {
945
+ const mid = Math.floor((low + high) / 2);
946
+ this.context.__textSize = mid;
947
+ const bounds = this.textBounds(text);
948
+ const size = direction === "x" ? bounds.width : bounds.height;
949
+ if (size === dist) {
950
+ return mid;
951
+ } else if (size < dist) {
952
+ lastValidSize = mid;
953
+ low = mid + 1;
954
+ } else {
955
+ high = mid - 1;
956
+ }
957
+ }
958
+ return lastValidSize;
959
+ };
960
+ this.getTextMetrics = (text) => {
961
+ const ctx = this.context;
962
+ const metrics = ctx.measureText(text);
963
+ return {
964
+ width: metrics.width,
965
+ height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent,
966
+ baseline: metrics.actualBoundingBoxAscent
967
+ };
968
+ };
969
+ this.splitTo = (text, kind, options = {}) => {
970
+ const ctx = this.context;
971
+ const {
972
+ maxWidth = 0,
973
+ lineSpacing = 0,
974
+ letterSpacing = 0,
975
+ wordSpacing = 0
976
+ } = options;
977
+ ctx.computeFont();
978
+ if (maxWidth < this.textBounds(" ").width * 2 && maxWidth !== 0) {
979
+ return [];
980
+ }
981
+ const lines = text.split("\n");
982
+ const lineHeights = lines.map((line) => this.getTextMetrics(line).height);
983
+ const totalHeight = lineHeights.reduce((sum, height) => sum + height, 0) + (lines.length - 1) * (lineSpacing || 0);
984
+ let y = ctx.textBaseline === "middle" ? -totalHeight / 2 : 0;
985
+ return lines.flatMap((lineText, lineIndex) => {
986
+ const words = lineText.split(" ");
987
+ const currentLine = {
988
+ text: "",
989
+ width: 0,
990
+ letters: []
991
+ };
992
+ const totalWidth = this.getTextMetrics(lineText).width;
993
+ let startX = 0;
994
+ switch (ctx.textAlign) {
995
+ case "center":
996
+ startX = -totalWidth / 2;
997
+ break;
998
+ case "right":
999
+ startX = -totalWidth;
1000
+ break;
1001
+ }
1002
+ let x = startX;
1003
+ let letterIndex = 0;
1004
+ let wordIndex = 0;
1005
+ const lineLetters = words.flatMap((word) => {
1006
+ const letters = [];
1007
+ for (const char of word) {
1008
+ const charMetrics = this.getTextMetrics(char);
1009
+ x += charMetrics.width / 2;
1010
+ const letterData = {
1011
+ char,
1012
+ x,
1013
+ y: y + (ctx.textBaseline === "middle" ? lineHeights[lineIndex] / 2 : 0),
1014
+ metrics: charMetrics,
1015
+ ...kind === "all" && {
1016
+ letterIndex,
1017
+ wordIndex,
1018
+ lineIndex
1019
+ }
1020
+ };
1021
+ letters.push(letterData);
1022
+ currentLine.text += char;
1023
+ x += charMetrics.width / 2 + letterSpacing;
1024
+ letterIndex++;
1025
+ }
1026
+ if (wordIndex < words.length - 1) {
1027
+ const spaceMetrics = this.getTextMetrics(" ");
1028
+ x += spaceMetrics.width + wordSpacing;
1029
+ letters.push({
1030
+ char: " ",
1031
+ x,
1032
+ y,
1033
+ metrics: spaceMetrics,
1034
+ ...kind === "all" && {
1035
+ letterIndex,
1036
+ wordIndex,
1037
+ lineIndex
1038
+ }
1039
+ });
1040
+ currentLine.text += " ";
1041
+ letterIndex++;
1042
+ }
1043
+ wordIndex++;
1044
+ return letters;
1045
+ });
1046
+ const lineHeight = lineHeights[lineIndex];
1047
+ y += lineHeight + lineSpacing;
1048
+ return lineLetters;
1049
+ });
1050
+ };
1051
+ this.circularText = (text, radius = 100, fill = "fill", offset = 0, arc = Math.PI * 2) => {
1052
+ const totalAngle = Math.min(Math.max(arc, 0), Math.PI * 2);
1053
+ if (fill === "fill") {
1054
+ const chars = text.split("");
1055
+ const anglePerChar = totalAngle / (chars.length + 1);
1056
+ chars.forEach((char, i) => {
1057
+ const angle = anglePerChar * i + offset;
1058
+ const x = Math.cos(angle) * radius;
1059
+ const y = Math.sin(angle) * radius;
1060
+ this.context.push();
1061
+ this.context.textAlign = "center";
1062
+ this.context.translate(x, y);
1063
+ this.context.rotate(angle + Math.PI / 2);
1064
+ this.context.text(char, 0, 0);
1065
+ this.context.pop();
1066
+ });
1067
+ } else if (fill === "kerned") {
1068
+ let currentAngle = offset;
1069
+ text.split("").forEach((char) => {
1070
+ const charWidth = this.textBounds(char).width;
1071
+ currentAngle += charWidth / radius * 0.5;
1072
+ const angle = currentAngle;
1073
+ const x = Math.cos(angle) * radius;
1074
+ const y = Math.sin(angle) * radius;
1075
+ this.context.push();
1076
+ this.context.textAlign = "center";
1077
+ this.context.translate(x, y);
1078
+ this.context.rotate(angle + Math.PI / 2);
1079
+ this.context.text(char, 0, 0);
1080
+ this.context.pop();
1081
+ currentAngle += charWidth / radius * 0.5;
1082
+ });
1083
+ } else if (fill === "words") {
1084
+ const words = text.split(" ");
1085
+ const wordMetrics = words.map((word) => ({
1086
+ word,
1087
+ width: this.textBounds(word).width
1088
+ }));
1089
+ const spaceCount = words.length;
1090
+ const totalWordWidth = wordMetrics.reduce((sum, m) => sum + m.width, 0);
1091
+ const spaceAngle = (totalAngle - totalWordWidth / radius) / spaceCount;
1092
+ let currentAngle = offset;
1093
+ wordMetrics.forEach(({ word }, i) => {
1094
+ word.split("").forEach((char) => {
1095
+ const charWidth = this.textBounds(char).width;
1096
+ currentAngle += charWidth / radius * 0.5;
1097
+ const x = Math.cos(currentAngle) * radius;
1098
+ const y = Math.sin(currentAngle) * radius;
1099
+ this.context.push();
1100
+ this.context.textAlign = "center";
1101
+ this.context.translate(x, y);
1102
+ this.context.rotate(currentAngle + Math.PI / 2);
1103
+ this.context.text(char, 0, 0);
1104
+ this.context.pop();
1105
+ currentAngle += charWidth / radius * 0.5;
1106
+ });
1107
+ if (i < words.length - 1) {
1108
+ currentAngle += spaceAngle;
1109
+ }
1110
+ });
1111
+ }
1112
+ };
1113
+ this.textBounds = (text) => {
1114
+ if (this.context.font !== this.context.__computedTextFont) {
1115
+ this.context.font = this.context.__computedTextFont;
1116
+ }
1117
+ const metrics = this.context.measureText(text);
1118
+ return {
1119
+ x: metrics.actualBoundingBoxLeft * -1,
1120
+ y: metrics.actualBoundingBoxAscent * -1,
1121
+ width: metrics.width,
1122
+ height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
1123
+ };
1124
+ };
1125
+ this.log = () => {
1126
+ console.log(this.context);
1127
+ };
1128
+ this.context = ctx;
1129
+ }
1130
+ };
1131
+ var Text_default = Text;
1132
+
1133
+ // src/elements/Thing.tsx
1134
+ var Thing = class {
1135
+ constructor(ctx) {
1136
+ this.context = ctx;
1137
+ }
1138
+ log() {
1139
+ console.log(this.context);
1140
+ }
1141
+ };
1142
+ var Thing_default = Thing;
1143
+
235
1144
  // src/KlintFunctions.tsx
236
1145
  var KlintCoreFunctions = {
237
1146
  saveCanvas: (ctx) => () => {
@@ -311,6 +1220,10 @@ var KlintCoreFunctions = {
311
1220
  vertical: "top"
312
1221
  };
313
1222
  if (!options?.ignoreFunctions) {
1223
+ context.Color = ctx.Color;
1224
+ context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
1225
+ context.Easing = ctx.Easing;
1226
+ context.Text = ctx.Text;
314
1227
  Object.entries(KlintFunctions).forEach(([name, fn]) => {
315
1228
  context[name] = fn(context);
316
1229
  });
@@ -759,7 +1672,10 @@ function useKlint() {
759
1672
  const contextRef = useRef2(null);
760
1673
  const mouseRef = useRef2(null);
761
1674
  const scrollRef = useRef2(null);
762
- const useImage = () => {
1675
+ const useDev = () => {
1676
+ return;
1677
+ };
1678
+ const KlintImage = () => {
763
1679
  const imagesRef = useRef2(/* @__PURE__ */ new Map());
764
1680
  const loadImage = useCallback2(
765
1681
  async (key, url) => {
@@ -817,7 +1733,7 @@ function useKlint() {
817
1733
  clearImages: useCallback2(() => imagesRef.current.clear(), [])
818
1734
  };
819
1735
  };
820
- const useMouse = () => {
1736
+ const KlintMouse = () => {
821
1737
  if (!mouseRef.current) {
822
1738
  mouseRef.current = { ...DEFAULT_MOUSE_STATE };
823
1739
  }
@@ -892,7 +1808,7 @@ function useKlint() {
892
1808
  onMouseUp: (callback) => mouseUpCallbackRef.current = callback
893
1809
  };
894
1810
  };
895
- const useScroll = () => {
1811
+ const KlintScroll = () => {
896
1812
  if (!scrollRef.current) {
897
1813
  scrollRef.current = { ...DEFAULT_SCROLL_STATE };
898
1814
  }
@@ -921,7 +1837,7 @@ function useKlint() {
921
1837
  onScroll: (callback) => scrollCallbackRef.current = callback
922
1838
  };
923
1839
  };
924
- const useWindow = () => {
1840
+ const KlintWindow = () => {
925
1841
  const resizeCallbackRef = useRef2(
926
1842
  null
927
1843
  );
@@ -988,6 +1904,13 @@ function useKlint() {
988
1904
  context.__offscreens = /* @__PURE__ */ new Map();
989
1905
  context.__isPlaying = true;
990
1906
  context.__currentContext = context;
1907
+ context.Color = new Color_default();
1908
+ context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
1909
+ context.Easing = new Easing_default(context);
1910
+ context.State = new State_default();
1911
+ context.Time = new Time_default(context);
1912
+ context.Text = new Text_default(context);
1913
+ context.Thing = new Thing_default(context);
991
1914
  Object.entries(KlintCoreFunctions).forEach(([name, fn]) => {
992
1915
  context[name] = fn(context);
993
1916
  });
@@ -1023,11 +1946,12 @@ function useKlint() {
1023
1946
  context: contextRef.current,
1024
1947
  initCoreContext
1025
1948
  },
1026
- useMouse,
1027
- useScroll,
1028
- useWindow,
1029
- useImage,
1030
- togglePlay
1949
+ KlintMouse,
1950
+ KlintScroll,
1951
+ KlintWindow,
1952
+ KlintImage,
1953
+ togglePlay,
1954
+ useDev
1031
1955
  };
1032
1956
  }
1033
1957
  var useProps = (props) => {