@shopify/klint 0.0.98 → 0.3.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/bin/create-editor +423 -0
- package/bin/create-sandbox +232 -0
- package/bin/klint +146 -0
- package/dist/Klint-CsVzll4n.d.cts +1410 -0
- package/dist/Klint-CsVzll4n.d.ts +1410 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/index.cjs +2369 -317
- package/dist/index.d.cts +50 -674
- package/dist/index.d.ts +50 -674
- package/dist/index.js +2359 -316
- package/dist/plugins/index.cjs +2339 -995
- package/dist/plugins/index.d.cts +560 -0
- package/dist/plugins/index.d.ts +485 -656
- package/dist/plugins/index.js +2333 -984
- package/package.json +17 -19
- package/dist/elements/index.cjs +0 -946
- package/dist/elements/index.d.cts +0 -658
- package/dist/elements/index.d.ts +0 -658
- package/dist/elements/index.js +0 -913
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-3RG5ZIWI.js";
|
|
4
|
+
|
|
1
5
|
// src/Klint.tsx
|
|
2
6
|
import React, { useRef, useEffect, useState, useCallback } from "react";
|
|
3
7
|
var DEFAULT_FPS = 60;
|
|
@@ -37,11 +41,18 @@ var CONFIG_PROPS = [
|
|
|
37
41
|
"__textWeight",
|
|
38
42
|
"__textStyle",
|
|
39
43
|
"__textSize",
|
|
44
|
+
"__textLeading",
|
|
40
45
|
"__textAlignment",
|
|
46
|
+
"__fillRule",
|
|
41
47
|
"__isPlaying"
|
|
42
48
|
];
|
|
43
|
-
function useAnimate(contextRef, draw, isVisible) {
|
|
49
|
+
function useAnimate(contextRef, draw, isVisible, enablePerformanceTracking = false) {
|
|
44
50
|
const animationFrameId = useRef(0);
|
|
51
|
+
const frameTimeHistoryRef = useRef([]);
|
|
52
|
+
const frameStartTimeRef = useRef(0);
|
|
53
|
+
const frameCountRef = useRef(0);
|
|
54
|
+
const lastFpsUpdateRef = useRef(0);
|
|
55
|
+
const droppedFramesRef = useRef(0);
|
|
45
56
|
const animate = useCallback(
|
|
46
57
|
(timestamp = 0) => {
|
|
47
58
|
if (!contextRef.current || !isVisible) return;
|
|
@@ -55,22 +66,55 @@ function useAnimate(contextRef, draw, isVisible) {
|
|
|
55
66
|
if (!context.__lastTargetTime) {
|
|
56
67
|
context.__lastTargetTime = now;
|
|
57
68
|
context.__lastRealTime = now;
|
|
69
|
+
frameStartTimeRef.current = now;
|
|
70
|
+
lastFpsUpdateRef.current = now;
|
|
58
71
|
}
|
|
59
72
|
const sinceLast = now - context.__lastTargetTime;
|
|
60
73
|
const epsilon = 5;
|
|
61
74
|
if (sinceLast >= target - epsilon) {
|
|
75
|
+
const frameStart = enablePerformanceTracking && context.__performance ? performance.now() : 0;
|
|
62
76
|
context.deltaTime = now - context.__lastRealTime;
|
|
63
77
|
draw(context);
|
|
64
78
|
if (context.time > 1e7) context.time = 0;
|
|
65
79
|
if (context.frame > 1e7) context.frame = 0;
|
|
66
|
-
context.time += context.deltaTime /
|
|
80
|
+
context.time += context.deltaTime / 1e3;
|
|
67
81
|
context.frame++;
|
|
68
82
|
context.__lastTargetTime = now;
|
|
69
83
|
context.__lastRealTime = now;
|
|
84
|
+
if (enablePerformanceTracking && context.__performance) {
|
|
85
|
+
const frameTime = performance.now() - frameStart;
|
|
86
|
+
const targetFrameTime = 1e3 / context.fps;
|
|
87
|
+
frameTimeHistoryRef.current.push(frameTime);
|
|
88
|
+
if (frameTimeHistoryRef.current.length > 60) {
|
|
89
|
+
frameTimeHistoryRef.current.shift();
|
|
90
|
+
}
|
|
91
|
+
const avgFrameTime = frameTimeHistoryRef.current.reduce((a, b) => a + b, 0) / frameTimeHistoryRef.current.length;
|
|
92
|
+
context.__performance.frameTime = frameTime;
|
|
93
|
+
context.__performance.averageFrameTime = avgFrameTime;
|
|
94
|
+
context.__performance.minFrameTime = Math.min(
|
|
95
|
+
...frameTimeHistoryRef.current
|
|
96
|
+
);
|
|
97
|
+
context.__performance.maxFrameTime = Math.max(
|
|
98
|
+
...frameTimeHistoryRef.current
|
|
99
|
+
);
|
|
100
|
+
if (frameTime > targetFrameTime * 1.1) {
|
|
101
|
+
droppedFramesRef.current++;
|
|
102
|
+
}
|
|
103
|
+
context.__performance.droppedFrames = droppedFramesRef.current;
|
|
104
|
+
frameCountRef.current++;
|
|
105
|
+
if (now - lastFpsUpdateRef.current >= 1e3) {
|
|
106
|
+
context.__performance.fps = frameCountRef.current;
|
|
107
|
+
frameCountRef.current = 0;
|
|
108
|
+
lastFpsUpdateRef.current = now;
|
|
109
|
+
if (typeof performance !== "undefined" && performance.memory) {
|
|
110
|
+
context.__performance.memoryUsage = performance.memory.usedJSHeapSize / 1048576;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
70
114
|
}
|
|
71
115
|
animationFrameId.current = requestAnimationFrame(animate);
|
|
72
116
|
},
|
|
73
|
-
[draw, isVisible, contextRef]
|
|
117
|
+
[draw, isVisible, contextRef, enablePerformanceTracking]
|
|
74
118
|
);
|
|
75
119
|
return {
|
|
76
120
|
animate,
|
|
@@ -83,7 +127,8 @@ function Klint({
|
|
|
83
127
|
draw,
|
|
84
128
|
options = {},
|
|
85
129
|
preload,
|
|
86
|
-
onVisible
|
|
130
|
+
onVisible,
|
|
131
|
+
enablePerformanceTracking = false
|
|
87
132
|
}) {
|
|
88
133
|
const canvasRef = useRef(null);
|
|
89
134
|
const containerRef = useRef(null);
|
|
@@ -93,13 +138,36 @@ function Klint({
|
|
|
93
138
|
null
|
|
94
139
|
);
|
|
95
140
|
const [isVisible, setIsVisible] = useState(true);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
143
|
+
import.meta.hot.dispose(() => {
|
|
144
|
+
console.log("[Klint] Component unmounting due to HMR");
|
|
145
|
+
if (contextRef.current) {
|
|
146
|
+
contextRef.current.__isPlaying = false;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (typeof module !== "undefined" && module.hot) {
|
|
151
|
+
module.hot.dispose(() => {
|
|
152
|
+
console.log("[Klint] Component unmounting due to Webpack HMR");
|
|
153
|
+
if (contextRef.current) {
|
|
154
|
+
contextRef.current.__isPlaying = false;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}, []);
|
|
96
159
|
const __options = {
|
|
97
160
|
...DEFAULT_OPTIONS,
|
|
98
161
|
...options
|
|
99
162
|
};
|
|
100
163
|
const [toStaticImage, setStaticImage] = useState(null);
|
|
101
164
|
const initContext = context?.initCoreContext;
|
|
102
|
-
const { animate, animationFrameId } = useAnimate(
|
|
165
|
+
const { animate, animationFrameId } = useAnimate(
|
|
166
|
+
contextRef,
|
|
167
|
+
draw,
|
|
168
|
+
isVisible,
|
|
169
|
+
enablePerformanceTracking
|
|
170
|
+
);
|
|
103
171
|
const updateCanvasSize = (shouldRedraw = false) => {
|
|
104
172
|
if (!containerRef.current || !contextRef.current || !canvasRef.current)
|
|
105
173
|
return;
|
|
@@ -129,6 +197,16 @@ function Klint({
|
|
|
129
197
|
const context2 = contextRef.current;
|
|
130
198
|
if (!context2) return;
|
|
131
199
|
context2.__dpr = dpr;
|
|
200
|
+
if (enablePerformanceTracking) {
|
|
201
|
+
context2.__performance = {
|
|
202
|
+
fps: 0,
|
|
203
|
+
frameTime: 0,
|
|
204
|
+
averageFrameTime: 0,
|
|
205
|
+
minFrameTime: Infinity,
|
|
206
|
+
maxFrameTime: 0,
|
|
207
|
+
droppedFrames: 0
|
|
208
|
+
};
|
|
209
|
+
}
|
|
132
210
|
if (__options.fps && __options.fps !== context2.fps) {
|
|
133
211
|
context2.fps = __options.fps;
|
|
134
212
|
}
|
|
@@ -591,7 +669,8 @@ var Color_default = Color;
|
|
|
591
669
|
|
|
592
670
|
// src/elements/Easing.tsx
|
|
593
671
|
var Easing = class {
|
|
594
|
-
constructor(
|
|
672
|
+
constructor() {
|
|
673
|
+
this.zeroOut = (val) => Object.is(val, -0) || Math.abs(val) < 1e-12 ? 0 : val;
|
|
595
674
|
this.normalize = (val) => {
|
|
596
675
|
return val * 0.5 + 0.5;
|
|
597
676
|
};
|
|
@@ -615,19 +694,19 @@ var Easing = class {
|
|
|
615
694
|
};
|
|
616
695
|
this.overshootIn = (val) => {
|
|
617
696
|
const k = 1.70158;
|
|
618
|
-
return val * val * (val * (k + 1) - k);
|
|
697
|
+
return this.zeroOut(val * val * (val * (k + 1) - k));
|
|
619
698
|
};
|
|
620
699
|
this.overshootOut = (val) => {
|
|
621
700
|
const m = val - 1;
|
|
622
701
|
const k = 1.70158;
|
|
623
|
-
return 1 + m * m * (m * (k + 1) + k);
|
|
702
|
+
return this.zeroOut(1 + m * m * (m * (k + 1) + k));
|
|
624
703
|
};
|
|
625
704
|
this.overshootInOut = (val) => {
|
|
626
705
|
const m = val - 1;
|
|
627
706
|
const t = val * 2;
|
|
628
707
|
const k = 1.70158 * 1.525;
|
|
629
|
-
if (val < 0.5) return val * t * (t * (k + 1) - k);
|
|
630
|
-
return 1 + 2 * m * m * (2 * m * (k + 1) + k);
|
|
708
|
+
if (val < 0.5) return this.zeroOut(val * t * (t * (k + 1) - k));
|
|
709
|
+
return this.zeroOut(1 + 2 * m * m * (2 * m * (k + 1) + k));
|
|
631
710
|
};
|
|
632
711
|
this.bounceOut = (val) => {
|
|
633
712
|
const r = 1 / 2.75;
|
|
@@ -680,48 +759,22 @@ var Easing = class {
|
|
|
680
759
|
this.log = () => {
|
|
681
760
|
console.log(this);
|
|
682
761
|
};
|
|
683
|
-
this.context = ctx;
|
|
684
762
|
}
|
|
685
763
|
};
|
|
686
764
|
var Easing_default = Easing;
|
|
687
765
|
|
|
688
|
-
// src/elements/State.tsx
|
|
689
|
-
var State = class {
|
|
690
|
-
constructor() {
|
|
691
|
-
this.store = /* @__PURE__ */ new Map();
|
|
692
|
-
}
|
|
693
|
-
set(key, value, callback) {
|
|
694
|
-
this.store.set(key, value);
|
|
695
|
-
callback?.(key, value);
|
|
696
|
-
}
|
|
697
|
-
get(key, callback) {
|
|
698
|
-
const value = this.store.get(key);
|
|
699
|
-
callback?.(key, value);
|
|
700
|
-
return value;
|
|
701
|
-
}
|
|
702
|
-
has(key) {
|
|
703
|
-
return this.store.has(key);
|
|
704
|
-
}
|
|
705
|
-
delete(key, callback) {
|
|
706
|
-
this.store.delete(key);
|
|
707
|
-
callback?.(key);
|
|
708
|
-
}
|
|
709
|
-
log() {
|
|
710
|
-
return this.store;
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
|
-
var State_default = State;
|
|
714
|
-
|
|
715
766
|
// src/elements/Vector.tsx
|
|
716
767
|
var Vector = class _Vector {
|
|
717
768
|
/**
|
|
718
769
|
* Creates a new Vector
|
|
719
770
|
* @param x - X-coordinate (default: 0)
|
|
720
771
|
* @param y - Y-coordinate (default: 0)
|
|
772
|
+
* @param z - Z-coordinate (default: 0)
|
|
721
773
|
*/
|
|
722
|
-
constructor(x = 0, y = 0) {
|
|
774
|
+
constructor(x = 0, y = 0, z = 0) {
|
|
723
775
|
this.x = x;
|
|
724
776
|
this.y = y;
|
|
777
|
+
this.z = z;
|
|
725
778
|
}
|
|
726
779
|
/**
|
|
727
780
|
* Adds another vector to this vector
|
|
@@ -731,6 +784,7 @@ var Vector = class _Vector {
|
|
|
731
784
|
add(v) {
|
|
732
785
|
this.x += v.x;
|
|
733
786
|
this.y += v.y;
|
|
787
|
+
this.z += v.z;
|
|
734
788
|
return this;
|
|
735
789
|
}
|
|
736
790
|
/**
|
|
@@ -741,6 +795,7 @@ var Vector = class _Vector {
|
|
|
741
795
|
sub(v) {
|
|
742
796
|
this.x -= v.x;
|
|
743
797
|
this.y -= v.y;
|
|
798
|
+
this.z -= v.z;
|
|
744
799
|
return this;
|
|
745
800
|
}
|
|
746
801
|
/**
|
|
@@ -751,6 +806,7 @@ var Vector = class _Vector {
|
|
|
751
806
|
mult(n) {
|
|
752
807
|
this.x *= n;
|
|
753
808
|
this.y *= n;
|
|
809
|
+
this.z *= n;
|
|
754
810
|
return this;
|
|
755
811
|
}
|
|
756
812
|
/**
|
|
@@ -761,11 +817,11 @@ var Vector = class _Vector {
|
|
|
761
817
|
div(n) {
|
|
762
818
|
this.x /= n;
|
|
763
819
|
this.y /= n;
|
|
820
|
+
this.z /= n;
|
|
764
821
|
return this;
|
|
765
822
|
}
|
|
766
|
-
// to do : project, perp, slerp
|
|
767
823
|
/**
|
|
768
|
-
* Rotates this vector by an angle
|
|
824
|
+
* Rotates this vector by an angle (around z-axis for 2D rotation)
|
|
769
825
|
* @param angle - Angle in radians
|
|
770
826
|
* @returns This vector after rotation
|
|
771
827
|
*/
|
|
@@ -783,7 +839,7 @@ var Vector = class _Vector {
|
|
|
783
839
|
* @returns The magnitude of the vector
|
|
784
840
|
*/
|
|
785
841
|
mag() {
|
|
786
|
-
return Math.sqrt(this.x * this.x + this.y * this.y);
|
|
842
|
+
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
|
|
787
843
|
}
|
|
788
844
|
/**
|
|
789
845
|
* Alias for mag() - calculates the length of this vector
|
|
@@ -798,7 +854,7 @@ var Vector = class _Vector {
|
|
|
798
854
|
* @returns The dot product
|
|
799
855
|
*/
|
|
800
856
|
dot(v) {
|
|
801
|
-
return this.x * v.x + this.y * v.y;
|
|
857
|
+
return this.x * v.x + this.y * v.y + this.z * v.z;
|
|
802
858
|
}
|
|
803
859
|
/**
|
|
804
860
|
* Calculates the distance between this vector and another vector
|
|
@@ -806,21 +862,21 @@ var Vector = class _Vector {
|
|
|
806
862
|
* @returns The distance between the vectors
|
|
807
863
|
*/
|
|
808
864
|
dist(v) {
|
|
809
|
-
return Math.hypot(this.x - v.x, this.y - v.y);
|
|
865
|
+
return Math.hypot(this.x - v.x, this.y - v.y, this.z - v.z);
|
|
810
866
|
}
|
|
811
867
|
/**
|
|
812
|
-
* Calculates the angle of this vector
|
|
868
|
+
* Calculates the angle of this vector (in 2D, ignoring z)
|
|
813
869
|
* @returns The angle in radians
|
|
814
870
|
*/
|
|
815
871
|
angle() {
|
|
816
|
-
return Math.atan2(
|
|
872
|
+
return Math.atan2(this.y, this.x);
|
|
817
873
|
}
|
|
818
874
|
/**
|
|
819
875
|
* Creates a copy of this vector
|
|
820
876
|
* @returns A new Vector with the same coordinates
|
|
821
877
|
*/
|
|
822
878
|
copy() {
|
|
823
|
-
return new _Vector(this.x, this.y);
|
|
879
|
+
return new _Vector(this.x, this.y, this.z);
|
|
824
880
|
}
|
|
825
881
|
/**
|
|
826
882
|
* Normalizes this vector (sets its magnitude to 1)
|
|
@@ -834,107 +890,128 @@ var Vector = class _Vector {
|
|
|
834
890
|
* Sets the coordinates of this vector
|
|
835
891
|
* @param x - New X-coordinate
|
|
836
892
|
* @param y - New Y-coordinate
|
|
893
|
+
* @param z - New Z-coordinate (default: 0)
|
|
837
894
|
* @returns This vector after setting coordinates
|
|
838
895
|
*/
|
|
839
|
-
set(x, y) {
|
|
896
|
+
set(x, y, z = 0) {
|
|
840
897
|
this.x = x;
|
|
841
898
|
this.y = y;
|
|
899
|
+
this.z = z;
|
|
842
900
|
return this;
|
|
843
901
|
}
|
|
844
902
|
/**
|
|
845
|
-
*
|
|
846
|
-
* @param
|
|
847
|
-
* @
|
|
848
|
-
* @param r - The radius (distance from center)
|
|
849
|
-
* @returns A new Vector at the calculated position
|
|
903
|
+
* Calculates the cross product of this vector with another vector
|
|
904
|
+
* @param v - The other vector
|
|
905
|
+
* @returns A new Vector representing the cross product
|
|
850
906
|
*/
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
var Vector_default = Vector;
|
|
858
|
-
|
|
859
|
-
// src/elements/Time.tsx
|
|
860
|
-
var Time = class {
|
|
861
|
-
constructor(ctx) {
|
|
862
|
-
this.timelines = /* @__PURE__ */ new Map();
|
|
863
|
-
this.currentTimeline = "default";
|
|
864
|
-
this.DEFAULT_DURATION = 8 * 60;
|
|
865
|
-
this.staggers = [];
|
|
866
|
-
this.context = ctx;
|
|
867
|
-
this.timelines.set("default", {
|
|
868
|
-
progress: 0,
|
|
869
|
-
duration: this.DEFAULT_DURATION
|
|
870
|
-
});
|
|
907
|
+
cross(v) {
|
|
908
|
+
return new _Vector(
|
|
909
|
+
this.y * v.z - this.z * v.y,
|
|
910
|
+
this.z * v.x - this.x * v.z,
|
|
911
|
+
this.x * v.y - this.y * v.x
|
|
912
|
+
);
|
|
871
913
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
914
|
+
/**
|
|
915
|
+
* Calculates this vector's position relative to another vector
|
|
916
|
+
* @param v - The reference vector
|
|
917
|
+
* @returns This vector after making it relative to v
|
|
918
|
+
*/
|
|
919
|
+
relativeTo(v) {
|
|
920
|
+
this.x -= v.x;
|
|
921
|
+
this.y -= v.y;
|
|
922
|
+
this.z -= v.z;
|
|
877
923
|
return this;
|
|
878
924
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
925
|
+
/**
|
|
926
|
+
* Makes this vector point towards a target vector
|
|
927
|
+
* @param target - The target vector to look at
|
|
928
|
+
* @returns This vector after pointing towards target
|
|
929
|
+
*/
|
|
930
|
+
lookAt(target) {
|
|
931
|
+
const direction = new _Vector(
|
|
932
|
+
target.x - this.x,
|
|
933
|
+
target.y - this.y,
|
|
934
|
+
target.z - this.z
|
|
935
|
+
);
|
|
936
|
+
direction.normalize();
|
|
937
|
+
this.x = direction.x;
|
|
938
|
+
this.y = direction.y;
|
|
939
|
+
this.z = direction.z;
|
|
886
940
|
return this;
|
|
887
941
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
942
|
+
/**
|
|
943
|
+
* Converts this vector from world space to screen space
|
|
944
|
+
* @param width - Screen width
|
|
945
|
+
* @param height - Screen height
|
|
946
|
+
* @returns This vector after screen space conversion
|
|
947
|
+
*/
|
|
948
|
+
toScreen(width, height) {
|
|
949
|
+
this.x = (this.x + 1) * width / 2;
|
|
950
|
+
this.y = (1 - this.y) * height / 2;
|
|
891
951
|
return this;
|
|
892
952
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
953
|
+
/**
|
|
954
|
+
* Spherical linear interpolation between this vector and another vector
|
|
955
|
+
* @param v - The target vector
|
|
956
|
+
* @param amt - Interpolation amount (0-1)
|
|
957
|
+
* @returns This vector after interpolation
|
|
958
|
+
*/
|
|
959
|
+
slerp(v, amt) {
|
|
960
|
+
if (amt === 0) {
|
|
961
|
+
return this;
|
|
962
|
+
}
|
|
963
|
+
if (amt === 1) {
|
|
964
|
+
return this.set(v.x, v.y, v.z);
|
|
965
|
+
}
|
|
966
|
+
const selfMag = this.mag();
|
|
967
|
+
const vMag = v.mag();
|
|
968
|
+
const magmag = selfMag * vMag;
|
|
969
|
+
if (magmag === 0) {
|
|
970
|
+
this.mult(1 - amt).add(new _Vector(v.x * amt, v.y * amt, v.z * amt));
|
|
971
|
+
return this;
|
|
972
|
+
}
|
|
973
|
+
const axis = this.cross(v);
|
|
974
|
+
const axisMag = axis.mag();
|
|
975
|
+
const theta = Math.atan2(axisMag, this.dot(v));
|
|
976
|
+
if (axisMag > 0) {
|
|
977
|
+
axis.x /= axisMag;
|
|
978
|
+
axis.y /= axisMag;
|
|
979
|
+
axis.z /= axisMag;
|
|
980
|
+
} else if (theta < Math.PI * 0.5) {
|
|
981
|
+
this.mult(1 - amt).add(new _Vector(v.x * amt, v.y * amt, v.z * amt));
|
|
982
|
+
return this;
|
|
983
|
+
} else {
|
|
984
|
+
if (this.z === 0 && v.z === 0) {
|
|
985
|
+
axis.set(0, 0, 1);
|
|
986
|
+
} else if (this.x !== 0) {
|
|
987
|
+
axis.set(this.y, -this.x, 0).normalize();
|
|
915
988
|
} else {
|
|
916
|
-
|
|
989
|
+
axis.set(1, 0, 0);
|
|
917
990
|
}
|
|
918
991
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
Math.min(1, to),
|
|
927
|
-
0,
|
|
928
|
-
1
|
|
929
|
-
);
|
|
930
|
-
callback(localProgress);
|
|
992
|
+
const ey = axis.cross(this);
|
|
993
|
+
const lerpedMagFactor = 1 - amt + amt * vMag / selfMag;
|
|
994
|
+
const cosMultiplier = lerpedMagFactor * Math.cos(amt * theta);
|
|
995
|
+
const sinMultiplier = lerpedMagFactor * Math.sin(amt * theta);
|
|
996
|
+
this.x = this.x * cosMultiplier + ey.x * sinMultiplier;
|
|
997
|
+
this.y = this.y * cosMultiplier + ey.y * sinMultiplier;
|
|
998
|
+
this.z = this.z * cosMultiplier + ey.z * sinMultiplier;
|
|
931
999
|
return this;
|
|
932
1000
|
}
|
|
933
|
-
|
|
934
|
-
|
|
1001
|
+
/**
|
|
1002
|
+
* Creates a new vector at a specified angle and distance from a center point
|
|
1003
|
+
* @param center - The center point vector
|
|
1004
|
+
* @param a - The angle in radians
|
|
1005
|
+
* @param r - The radius (distance from center)
|
|
1006
|
+
* @returns A new Vector at the calculated position
|
|
1007
|
+
*/
|
|
1008
|
+
static fromAngle(center, a, r) {
|
|
1009
|
+
const x = Math.cos(a) * r + center.x;
|
|
1010
|
+
const y = Math.sin(a) * r + center.y;
|
|
1011
|
+
return new _Vector(x, y, center.z);
|
|
935
1012
|
}
|
|
936
1013
|
};
|
|
937
|
-
var
|
|
1014
|
+
var Vector_default = Vector;
|
|
938
1015
|
|
|
939
1016
|
// src/elements/Text.tsx
|
|
940
1017
|
var Text = class {
|
|
@@ -1143,138 +1220,1512 @@ var Thing = class {
|
|
|
1143
1220
|
};
|
|
1144
1221
|
var Thing_default = Thing;
|
|
1145
1222
|
|
|
1146
|
-
// src/
|
|
1147
|
-
var
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1173
|
-
|
|
1223
|
+
// src/elements/Grid.tsx
|
|
1224
|
+
var Grid = class {
|
|
1225
|
+
/**
|
|
1226
|
+
* Creates a new Grid instance
|
|
1227
|
+
* @param ctx - The Klint context
|
|
1228
|
+
*/
|
|
1229
|
+
constructor(ctx) {
|
|
1230
|
+
this.context = ctx;
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Create a rectangular grid of points
|
|
1234
|
+
* @param x - X position of the grid
|
|
1235
|
+
* @param y - Y position of the grid
|
|
1236
|
+
* @param width - Width of the grid
|
|
1237
|
+
* @param height - Height of the grid
|
|
1238
|
+
* @param countX - Number of points horizontally
|
|
1239
|
+
* @param countY - Number of points vertically
|
|
1240
|
+
* @param options - Grid options
|
|
1241
|
+
* @returns Array of grid points
|
|
1242
|
+
*/
|
|
1243
|
+
rect(x, y, width, height, countX, countY, options) {
|
|
1244
|
+
const origin = options?.origin || "corner";
|
|
1245
|
+
const points = [];
|
|
1246
|
+
let startX = x;
|
|
1247
|
+
let startY = y;
|
|
1248
|
+
if (origin === "center") {
|
|
1249
|
+
startX = x - width / 2;
|
|
1250
|
+
startY = y - height / 2;
|
|
1174
1251
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1252
|
+
const spacingX = countX > 1 ? width / (countX - 1) : 0;
|
|
1253
|
+
const spacingY = countY > 1 ? height / (countY - 1) : 0;
|
|
1254
|
+
for (let j = 0; j < countY; j++) {
|
|
1255
|
+
for (let i = 0; i < countX; i++) {
|
|
1256
|
+
const pointX = startX + i * spacingX;
|
|
1257
|
+
const pointY = startY + j * spacingY;
|
|
1258
|
+
const id = j * countX + i;
|
|
1259
|
+
points.push({
|
|
1260
|
+
x: pointX,
|
|
1261
|
+
y: pointY,
|
|
1262
|
+
i,
|
|
1263
|
+
j,
|
|
1264
|
+
id
|
|
1265
|
+
});
|
|
1182
1266
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
context.Color = ctx.Color;
|
|
1226
|
-
context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
|
|
1227
|
-
context.Easing = ctx.Easing;
|
|
1228
|
-
context.Text = ctx.Text;
|
|
1229
|
-
Object.entries(KlintFunctions).forEach(([name, fn]) => {
|
|
1230
|
-
context[name] = fn(context);
|
|
1267
|
+
}
|
|
1268
|
+
return points;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Create a radial grid of points
|
|
1272
|
+
* @param x - Center X position
|
|
1273
|
+
* @param y - Center Y position
|
|
1274
|
+
* @param radius - Maximum radius
|
|
1275
|
+
* @param count - Number of points per ring
|
|
1276
|
+
* @param ringCount - Number of rings
|
|
1277
|
+
* @param ringSpace - Space between rings
|
|
1278
|
+
* @param options - Grid options
|
|
1279
|
+
* @returns Array of grid points
|
|
1280
|
+
*/
|
|
1281
|
+
radial(x, y, radius, count, ringCount, ringSpace, options) {
|
|
1282
|
+
const perStepCount = options?.perStepCount || 0;
|
|
1283
|
+
const points = [];
|
|
1284
|
+
let id = 0;
|
|
1285
|
+
for (let ring = 0; ring < ringCount; ring++) {
|
|
1286
|
+
const ringRadius = ring * ringSpace;
|
|
1287
|
+
if (ringRadius > radius) break;
|
|
1288
|
+
const ringPointCount = count + perStepCount * ring;
|
|
1289
|
+
for (let i = 0; i < ringPointCount; i++) {
|
|
1290
|
+
const angle = Math.PI * 2 * i / ringPointCount;
|
|
1291
|
+
const pointX = x + Math.cos(angle) * ringRadius;
|
|
1292
|
+
const pointY = y + Math.sin(angle) * ringRadius;
|
|
1293
|
+
points.push({
|
|
1294
|
+
x: pointX,
|
|
1295
|
+
y: pointY,
|
|
1296
|
+
i,
|
|
1297
|
+
j: ring,
|
|
1298
|
+
id: id++
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (ringCount > 0 && points.length === 0) {
|
|
1303
|
+
points.push({
|
|
1304
|
+
x,
|
|
1305
|
+
y,
|
|
1306
|
+
i: 0,
|
|
1307
|
+
j: 0,
|
|
1308
|
+
id: 0
|
|
1231
1309
|
});
|
|
1232
1310
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1311
|
+
return points;
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Create a hexagonal grid of points
|
|
1315
|
+
* @param x - X position of the grid
|
|
1316
|
+
* @param y - Y position of the grid
|
|
1317
|
+
* @param width - Width of the grid area
|
|
1318
|
+
* @param height - Height of the grid area
|
|
1319
|
+
* @param size - Size of each hexagon
|
|
1320
|
+
* @param options - Grid options
|
|
1321
|
+
* @returns Array of grid points
|
|
1322
|
+
*/
|
|
1323
|
+
hex(x, y, width, height, size, options) {
|
|
1324
|
+
const origin = options?.origin || "corner";
|
|
1325
|
+
const pointy = options?.pointy !== false;
|
|
1326
|
+
const points = [];
|
|
1327
|
+
let startX = x;
|
|
1328
|
+
let startY = y;
|
|
1329
|
+
if (origin === "center") {
|
|
1330
|
+
startX = x - width / 2;
|
|
1331
|
+
startY = y - height / 2;
|
|
1332
|
+
}
|
|
1333
|
+
const hexWidth = pointy ? Math.sqrt(3) * size : 2 * size;
|
|
1334
|
+
const hexHeight = pointy ? 2 * size : Math.sqrt(3) * size;
|
|
1335
|
+
const cols = Math.ceil(width / hexWidth) + 1;
|
|
1336
|
+
const rows = Math.ceil(height / (hexHeight * 0.75)) + 1;
|
|
1337
|
+
let id = 0;
|
|
1338
|
+
for (let j = 0; j < rows; j++) {
|
|
1339
|
+
for (let i = 0; i < cols; i++) {
|
|
1340
|
+
let pointX;
|
|
1341
|
+
let pointY;
|
|
1342
|
+
if (pointy) {
|
|
1343
|
+
pointX = startX + i * hexWidth;
|
|
1344
|
+
pointY = startY + j * hexHeight * 0.75;
|
|
1345
|
+
if (j % 2 === 1) {
|
|
1346
|
+
pointX += hexWidth / 2;
|
|
1347
|
+
}
|
|
1348
|
+
} else {
|
|
1349
|
+
pointX = startX + i * hexWidth * 0.75;
|
|
1350
|
+
pointY = startY + j * hexHeight;
|
|
1351
|
+
if (i % 2 === 1) {
|
|
1352
|
+
pointY += hexHeight / 2;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (pointX <= startX + width && pointY <= startY + height) {
|
|
1356
|
+
points.push({
|
|
1357
|
+
x: pointX,
|
|
1358
|
+
y: pointY,
|
|
1359
|
+
i,
|
|
1360
|
+
j,
|
|
1361
|
+
id: id++
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1237
1364
|
}
|
|
1238
1365
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1366
|
+
return points;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Create a triangular grid of points
|
|
1370
|
+
* @param x - X position of the grid
|
|
1371
|
+
* @param y - Y position of the grid
|
|
1372
|
+
* @param width - Width of the grid
|
|
1373
|
+
* @param height - Height of the grid
|
|
1374
|
+
* @param size - Size of each triangle
|
|
1375
|
+
* @param options - Grid options
|
|
1376
|
+
* @returns Array of grid points
|
|
1377
|
+
*/
|
|
1378
|
+
triangle(x, y, width, height, size, options) {
|
|
1379
|
+
const origin = options?.origin || "corner";
|
|
1380
|
+
const points = [];
|
|
1381
|
+
let startX = x;
|
|
1382
|
+
let startY = y;
|
|
1383
|
+
if (origin === "center") {
|
|
1384
|
+
startX = x - width / 2;
|
|
1385
|
+
startY = y - height / 2;
|
|
1241
1386
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1387
|
+
const triHeight = Math.sqrt(3) / 2 * size;
|
|
1388
|
+
const cols = Math.ceil(width / (size / 2)) + 1;
|
|
1389
|
+
const rows = Math.ceil(height / triHeight) + 1;
|
|
1390
|
+
let id = 0;
|
|
1391
|
+
for (let j = 0; j < rows; j++) {
|
|
1392
|
+
for (let i = 0; i < cols; i++) {
|
|
1393
|
+
const pointX = startX + i * (size / 2);
|
|
1394
|
+
const pointY = startY + j * triHeight;
|
|
1395
|
+
if (pointX <= startX + width && pointY <= startY + height) {
|
|
1396
|
+
points.push({
|
|
1397
|
+
x: pointX,
|
|
1398
|
+
y: pointY,
|
|
1399
|
+
i,
|
|
1400
|
+
j,
|
|
1401
|
+
id: id++
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1248
1405
|
}
|
|
1249
|
-
|
|
1250
|
-
return context;
|
|
1251
|
-
},
|
|
1252
|
-
getOffscreen: (ctx) => (id) => {
|
|
1253
|
-
const offscreen = ctx.__offscreens.get(id);
|
|
1254
|
-
if (!offscreen)
|
|
1255
|
-
throw new Error(`No offscreen context found with id: ${id}`);
|
|
1256
|
-
return offscreen;
|
|
1406
|
+
return points;
|
|
1257
1407
|
}
|
|
1258
1408
|
};
|
|
1259
|
-
var
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1409
|
+
var Grid_default = Grid;
|
|
1410
|
+
|
|
1411
|
+
// src/elements/Strip.tsx
|
|
1412
|
+
var Strip = class {
|
|
1413
|
+
/**
|
|
1414
|
+
* Creates a new Strip instance
|
|
1415
|
+
* @param ctx - The Klint context
|
|
1416
|
+
*/
|
|
1417
|
+
constructor(ctx) {
|
|
1418
|
+
this.context = ctx;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Create a strip of triangles from points
|
|
1422
|
+
* Points are connected in a zigzag pattern:
|
|
1423
|
+
* 0 - 2 - 4 ...
|
|
1424
|
+
* | / | / |
|
|
1425
|
+
* 1 - 3 - 5 ...
|
|
1426
|
+
*
|
|
1427
|
+
* @param points - Array of points (must be even number for complete triangles)
|
|
1428
|
+
* @param draw - Optional callback to customize each triangle's appearance
|
|
1429
|
+
*/
|
|
1430
|
+
triangles(points, draw) {
|
|
1431
|
+
if (points.length < 3) return;
|
|
1432
|
+
const numTriangles = Math.floor((points.length - 2) * 2);
|
|
1433
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
1434
|
+
const baseIndex = Math.floor(i / 2) * 2;
|
|
1435
|
+
const isEven = i % 2 === 0;
|
|
1436
|
+
let p1;
|
|
1437
|
+
let p2;
|
|
1438
|
+
let p3;
|
|
1439
|
+
if (isEven) {
|
|
1440
|
+
p1 = points[baseIndex];
|
|
1441
|
+
p2 = points[baseIndex + 1];
|
|
1442
|
+
p3 = points[baseIndex + 2];
|
|
1443
|
+
} else {
|
|
1444
|
+
p1 = points[baseIndex + 1];
|
|
1445
|
+
p2 = points[baseIndex + 3];
|
|
1446
|
+
p3 = points[baseIndex + 2];
|
|
1447
|
+
}
|
|
1448
|
+
if (!p1 || !p2 || !p3) continue;
|
|
1449
|
+
const center = {
|
|
1450
|
+
x: (p1.x + p2.x + p3.x) / 3,
|
|
1451
|
+
y: (p1.y + p2.y + p3.y) / 3
|
|
1452
|
+
};
|
|
1453
|
+
const triangle = {
|
|
1454
|
+
id: i,
|
|
1455
|
+
center,
|
|
1456
|
+
points: [p1, p2, p3]
|
|
1457
|
+
};
|
|
1458
|
+
let fillColor;
|
|
1459
|
+
if (draw) {
|
|
1460
|
+
const result = draw(triangle);
|
|
1461
|
+
if (typeof result === "string") {
|
|
1462
|
+
fillColor = result;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
this.context.beginPath();
|
|
1466
|
+
this.context.moveTo(p1.x, p1.y);
|
|
1467
|
+
this.context.lineTo(p2.x, p2.y);
|
|
1468
|
+
this.context.lineTo(p3.x, p3.y);
|
|
1469
|
+
this.context.closePath();
|
|
1470
|
+
if (fillColor) {
|
|
1471
|
+
const prevFill = this.context.fillStyle;
|
|
1472
|
+
this.context.fillStyle = fillColor;
|
|
1473
|
+
this.context.fill();
|
|
1474
|
+
this.context.fillStyle = prevFill;
|
|
1475
|
+
} else if (this.context.checkTransparency("fill")) {
|
|
1476
|
+
this.context.fill();
|
|
1477
|
+
}
|
|
1478
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1479
|
+
this.context.stroke();
|
|
1480
|
+
}
|
|
1272
1481
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Create a strip of quads from points
|
|
1485
|
+
* Points are connected in a grid pattern:
|
|
1486
|
+
* 0 - 2 - 4 ...
|
|
1487
|
+
* | | |
|
|
1488
|
+
* 1 - 3 - 5 ...
|
|
1489
|
+
*
|
|
1490
|
+
* @param points - Array of points (must be even number for complete quads)
|
|
1491
|
+
* @param draw - Optional callback to customize each quad's appearance
|
|
1492
|
+
*/
|
|
1493
|
+
quads(points, draw) {
|
|
1494
|
+
if (points.length < 4) return;
|
|
1495
|
+
const numQuads = Math.floor((points.length - 2) / 2);
|
|
1496
|
+
for (let i = 0; i < numQuads; i++) {
|
|
1497
|
+
const baseIndex = i * 2;
|
|
1498
|
+
const p1 = points[baseIndex];
|
|
1499
|
+
const p2 = points[baseIndex + 1];
|
|
1500
|
+
const p3 = points[baseIndex + 3];
|
|
1501
|
+
const p4 = points[baseIndex + 2];
|
|
1502
|
+
if (!p1 || !p2 || !p3 || !p4) continue;
|
|
1503
|
+
const center = {
|
|
1504
|
+
x: (p1.x + p2.x + p3.x + p4.x) / 4,
|
|
1505
|
+
y: (p1.y + p2.y + p3.y + p4.y) / 4
|
|
1506
|
+
};
|
|
1507
|
+
const quad = {
|
|
1508
|
+
id: i,
|
|
1509
|
+
center,
|
|
1510
|
+
points: [p1, p2, p3, p4]
|
|
1511
|
+
};
|
|
1512
|
+
let fillColor;
|
|
1513
|
+
if (draw) {
|
|
1514
|
+
const result = draw(quad);
|
|
1515
|
+
if (typeof result === "string") {
|
|
1516
|
+
fillColor = result;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
this.context.beginPath();
|
|
1520
|
+
this.context.moveTo(p1.x, p1.y);
|
|
1521
|
+
this.context.lineTo(p2.x, p2.y);
|
|
1522
|
+
this.context.lineTo(p3.x, p3.y);
|
|
1523
|
+
this.context.lineTo(p4.x, p4.y);
|
|
1524
|
+
this.context.closePath();
|
|
1525
|
+
if (fillColor) {
|
|
1526
|
+
const prevFill = this.context.fillStyle;
|
|
1527
|
+
this.context.fillStyle = fillColor;
|
|
1528
|
+
this.context.fill();
|
|
1529
|
+
this.context.fillStyle = prevFill;
|
|
1530
|
+
} else if (this.context.checkTransparency("fill")) {
|
|
1531
|
+
this.context.fill();
|
|
1532
|
+
}
|
|
1533
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1534
|
+
this.context.stroke();
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Create a single hull shape from points
|
|
1540
|
+
* Points are connected following the winding order:
|
|
1541
|
+
* 0 - 2 - 4 - ... n-1
|
|
1542
|
+
* | |
|
|
1543
|
+
* 1 - 3 - 5 - ... n
|
|
1544
|
+
*
|
|
1545
|
+
* Final order: 0 - 2 - 4 ... n-1, n, ... 5 - 3 - 1
|
|
1546
|
+
*
|
|
1547
|
+
* @param points - Array of points
|
|
1548
|
+
* @param draw - Optional callback to add elements along the hull
|
|
1549
|
+
*/
|
|
1550
|
+
hull(points, draw) {
|
|
1551
|
+
if (points.length < 2) return;
|
|
1552
|
+
const hullPath = [];
|
|
1553
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
1554
|
+
hullPath.push(points[i]);
|
|
1555
|
+
}
|
|
1556
|
+
for (let i = points.length - 1 - points.length % 2; i >= 1; i -= 2) {
|
|
1557
|
+
hullPath.push(points[i]);
|
|
1558
|
+
}
|
|
1559
|
+
this.context.beginPath();
|
|
1560
|
+
this.context.moveTo(hullPath[0].x, hullPath[0].y);
|
|
1561
|
+
for (let i = 1; i < hullPath.length; i++) {
|
|
1562
|
+
this.context.lineTo(hullPath[i].x, hullPath[i].y);
|
|
1563
|
+
}
|
|
1564
|
+
this.context.closePath();
|
|
1565
|
+
if (this.context.checkTransparency("fill")) {
|
|
1566
|
+
this.context.fill();
|
|
1567
|
+
}
|
|
1568
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1569
|
+
this.context.stroke();
|
|
1570
|
+
}
|
|
1571
|
+
if (draw) {
|
|
1572
|
+
const numPairs = Math.floor(points.length / 2);
|
|
1573
|
+
for (let i = 0; i < numPairs; i++) {
|
|
1574
|
+
const topPoint = points[i * 2];
|
|
1575
|
+
const bottomPoint = points[i * 2 + 1];
|
|
1576
|
+
if (topPoint && bottomPoint) {
|
|
1577
|
+
const center = {
|
|
1578
|
+
x: (topPoint.x + bottomPoint.x) / 2,
|
|
1579
|
+
y: (topPoint.y + bottomPoint.y) / 2
|
|
1580
|
+
};
|
|
1581
|
+
const hull = {
|
|
1582
|
+
id: i,
|
|
1583
|
+
center
|
|
1584
|
+
};
|
|
1585
|
+
draw(hull);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Create a ribbon/tape effect from points
|
|
1592
|
+
* Similar to hull but with configurable width
|
|
1593
|
+
*
|
|
1594
|
+
* @param points - Array of center points
|
|
1595
|
+
* @param width - Width of the ribbon
|
|
1596
|
+
* @param draw - Optional callback
|
|
1597
|
+
*/
|
|
1598
|
+
ribbon(points, width, draw) {
|
|
1599
|
+
if (points.length < 2) return;
|
|
1600
|
+
const offsetPoints = [];
|
|
1601
|
+
for (let i = 0; i < points.length; i++) {
|
|
1602
|
+
const curr = points[i];
|
|
1603
|
+
const prev = points[i - 1] || curr;
|
|
1604
|
+
const next = points[i + 1] || curr;
|
|
1605
|
+
const dx = next.x - prev.x;
|
|
1606
|
+
const dy = next.y - prev.y;
|
|
1607
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
1608
|
+
const perpX = -dy / len * (width / 2);
|
|
1609
|
+
const perpY = dx / len * (width / 2);
|
|
1610
|
+
offsetPoints.push({
|
|
1611
|
+
top: { x: curr.x + perpX, y: curr.y + perpY },
|
|
1612
|
+
bottom: { x: curr.x - perpX, y: curr.y - perpY }
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
this.context.beginPath();
|
|
1616
|
+
this.context.moveTo(offsetPoints[0].top.x, offsetPoints[0].top.y);
|
|
1617
|
+
for (let i = 1; i < offsetPoints.length; i++) {
|
|
1618
|
+
this.context.lineTo(offsetPoints[i].top.x, offsetPoints[i].top.y);
|
|
1619
|
+
}
|
|
1620
|
+
for (let i = offsetPoints.length - 1; i >= 0; i--) {
|
|
1621
|
+
this.context.lineTo(offsetPoints[i].bottom.x, offsetPoints[i].bottom.y);
|
|
1622
|
+
}
|
|
1623
|
+
this.context.closePath();
|
|
1624
|
+
if (this.context.checkTransparency("fill")) {
|
|
1625
|
+
this.context.fill();
|
|
1626
|
+
}
|
|
1627
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1628
|
+
this.context.stroke();
|
|
1629
|
+
}
|
|
1630
|
+
if (draw) {
|
|
1631
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
1632
|
+
const center = {
|
|
1633
|
+
x: (points[i].x + points[i + 1].x) / 2,
|
|
1634
|
+
y: (points[i].y + points[i + 1].y) / 2
|
|
1635
|
+
};
|
|
1636
|
+
const result = draw({ id: i, center });
|
|
1637
|
+
if (typeof result === "string") {
|
|
1638
|
+
this.context.beginPath();
|
|
1639
|
+
this.context.moveTo(offsetPoints[i].top.x, offsetPoints[i].top.y);
|
|
1640
|
+
this.context.lineTo(offsetPoints[i + 1].top.x, offsetPoints[i + 1].top.y);
|
|
1641
|
+
this.context.lineTo(offsetPoints[i + 1].bottom.x, offsetPoints[i + 1].bottom.y);
|
|
1642
|
+
this.context.lineTo(offsetPoints[i].bottom.x, offsetPoints[i].bottom.y);
|
|
1643
|
+
this.context.closePath();
|
|
1644
|
+
const prevFill = this.context.fillStyle;
|
|
1645
|
+
this.context.fillStyle = result;
|
|
1646
|
+
this.context.fill();
|
|
1647
|
+
this.context.fillStyle = prevFill;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
var Strip_default = Strip;
|
|
1654
|
+
|
|
1655
|
+
// src/elements/Noise.tsx
|
|
1656
|
+
var Noise = class {
|
|
1657
|
+
/**
|
|
1658
|
+
* Creates a new Noise instance
|
|
1659
|
+
* @param ctx - The Klint context
|
|
1660
|
+
*/
|
|
1661
|
+
constructor(ctx) {
|
|
1662
|
+
this.perm = [];
|
|
1663
|
+
this.permMod12 = [];
|
|
1664
|
+
this.grad3 = [
|
|
1665
|
+
[1, 1, 0],
|
|
1666
|
+
[-1, 1, 0],
|
|
1667
|
+
[1, -1, 0],
|
|
1668
|
+
[-1, -1, 0],
|
|
1669
|
+
[1, 0, 1],
|
|
1670
|
+
[-1, 0, 1],
|
|
1671
|
+
[1, 0, -1],
|
|
1672
|
+
[-1, 0, -1],
|
|
1673
|
+
[0, 1, 1],
|
|
1674
|
+
[0, -1, 1],
|
|
1675
|
+
[0, 1, -1],
|
|
1676
|
+
[0, -1, -1]
|
|
1677
|
+
];
|
|
1678
|
+
this.grad4 = [
|
|
1679
|
+
[0, 1, 1, 1],
|
|
1680
|
+
[0, 1, 1, -1],
|
|
1681
|
+
[0, 1, -1, 1],
|
|
1682
|
+
[0, 1, -1, -1],
|
|
1683
|
+
[0, -1, 1, 1],
|
|
1684
|
+
[0, -1, 1, -1],
|
|
1685
|
+
[0, -1, -1, 1],
|
|
1686
|
+
[0, -1, -1, -1],
|
|
1687
|
+
[1, 0, 1, 1],
|
|
1688
|
+
[1, 0, 1, -1],
|
|
1689
|
+
[1, 0, -1, 1],
|
|
1690
|
+
[1, 0, -1, -1],
|
|
1691
|
+
[-1, 0, 1, 1],
|
|
1692
|
+
[-1, 0, 1, -1],
|
|
1693
|
+
[-1, 0, -1, 1],
|
|
1694
|
+
[-1, 0, -1, -1],
|
|
1695
|
+
[1, 1, 0, 1],
|
|
1696
|
+
[1, 1, 0, -1],
|
|
1697
|
+
[1, -1, 0, 1],
|
|
1698
|
+
[1, -1, 0, -1],
|
|
1699
|
+
[-1, 1, 0, 1],
|
|
1700
|
+
[-1, 1, 0, -1],
|
|
1701
|
+
[-1, -1, 0, 1],
|
|
1702
|
+
[-1, -1, 0, -1],
|
|
1703
|
+
[1, 1, 1, 0],
|
|
1704
|
+
[1, 1, -1, 0],
|
|
1705
|
+
[1, -1, 1, 0],
|
|
1706
|
+
[1, -1, -1, 0],
|
|
1707
|
+
[-1, 1, 1, 0],
|
|
1708
|
+
[-1, 1, -1, 0],
|
|
1709
|
+
[-1, -1, 1, 0],
|
|
1710
|
+
[-1, -1, -1, 0]
|
|
1711
|
+
];
|
|
1712
|
+
this.currentSeed = Math.random();
|
|
1713
|
+
this.F2 = 0.5 * (Math.sqrt(3) - 1);
|
|
1714
|
+
this.G2 = (3 - Math.sqrt(3)) / 6;
|
|
1715
|
+
this.F3 = 1 / 3;
|
|
1716
|
+
this.G3 = 1 / 6;
|
|
1717
|
+
this.F4 = (Math.sqrt(5) - 1) / 4;
|
|
1718
|
+
this.G4 = (5 - Math.sqrt(5)) / 20;
|
|
1719
|
+
this.context = ctx;
|
|
1720
|
+
this.buildPermutationTable();
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Build permutation table for noise generation
|
|
1724
|
+
*/
|
|
1725
|
+
buildPermutationTable() {
|
|
1726
|
+
const p = [];
|
|
1727
|
+
for (let i = 0; i < 256; i++) {
|
|
1728
|
+
p[i] = i;
|
|
1729
|
+
}
|
|
1730
|
+
let n = 256;
|
|
1731
|
+
while (n > 0) {
|
|
1732
|
+
const index = Math.floor(this.random() * n--);
|
|
1733
|
+
const temp = p[n];
|
|
1734
|
+
p[n] = p[index];
|
|
1735
|
+
p[index] = temp;
|
|
1736
|
+
}
|
|
1737
|
+
this.perm = [];
|
|
1738
|
+
this.permMod12 = [];
|
|
1739
|
+
for (let i = 0; i < 512; i++) {
|
|
1740
|
+
this.perm[i] = p[i & 255];
|
|
1741
|
+
this.permMod12[i] = this.perm[i] % 12;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Seeded random number generator
|
|
1746
|
+
*/
|
|
1747
|
+
random() {
|
|
1748
|
+
const x = Math.sin(this.currentSeed++) * 1e4;
|
|
1749
|
+
return x - Math.floor(x);
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Set seed for noise generation
|
|
1753
|
+
* @param seed - Seed value for reproducible noise
|
|
1754
|
+
*/
|
|
1755
|
+
seed(seed) {
|
|
1756
|
+
this.currentSeed = seed !== void 0 ? seed : Math.random() * 1e4;
|
|
1757
|
+
this.buildPermutationTable();
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Fade function for Perlin noise
|
|
1761
|
+
*/
|
|
1762
|
+
fade(t) {
|
|
1763
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Linear interpolation
|
|
1767
|
+
*/
|
|
1768
|
+
lerp(t, a, b) {
|
|
1769
|
+
return a + t * (b - a);
|
|
1770
|
+
}
|
|
1771
|
+
perlin(x, y, z, w) {
|
|
1772
|
+
if (y === void 0) {
|
|
1773
|
+
const xi = Math.floor(x) & 255;
|
|
1774
|
+
const xf = x - Math.floor(x);
|
|
1775
|
+
const u = this.fade(xf);
|
|
1776
|
+
const a = this.perm[xi];
|
|
1777
|
+
const b = this.perm[xi + 1];
|
|
1778
|
+
const grad1 = (hash, x2) => (hash & 1) === 0 ? x2 : -x2;
|
|
1779
|
+
return this.lerp(u, grad1(a, xf), grad1(b, xf - 1));
|
|
1780
|
+
} else if (z === void 0) {
|
|
1781
|
+
const xi = Math.floor(x) & 255;
|
|
1782
|
+
const yi = Math.floor(y) & 255;
|
|
1783
|
+
const xf = x - Math.floor(x);
|
|
1784
|
+
const yf = y - Math.floor(y);
|
|
1785
|
+
const u = this.fade(xf);
|
|
1786
|
+
const v = this.fade(yf);
|
|
1787
|
+
const aa = this.perm[this.perm[xi] + yi];
|
|
1788
|
+
const ab = this.perm[this.perm[xi] + yi + 1];
|
|
1789
|
+
const ba = this.perm[this.perm[xi + 1] + yi];
|
|
1790
|
+
const bb = this.perm[this.perm[xi + 1] + yi + 1];
|
|
1791
|
+
const grad2 = (hash, x3, y2) => {
|
|
1792
|
+
const h = hash & 3;
|
|
1793
|
+
const u2 = h < 2 ? x3 : y2;
|
|
1794
|
+
const v2 = h < 2 ? y2 : x3;
|
|
1795
|
+
return ((h & 1) === 0 ? u2 : -u2) + ((h & 2) === 0 ? v2 : -v2);
|
|
1796
|
+
};
|
|
1797
|
+
const x1 = this.lerp(u, grad2(aa, xf, yf), grad2(ba, xf - 1, yf));
|
|
1798
|
+
const x2 = this.lerp(u, grad2(ab, xf, yf - 1), grad2(bb, xf - 1, yf - 1));
|
|
1799
|
+
return this.lerp(v, x1, x2);
|
|
1800
|
+
} else if (w === void 0) {
|
|
1801
|
+
const xi = Math.floor(x) & 255;
|
|
1802
|
+
const yi = Math.floor(y) & 255;
|
|
1803
|
+
const zi = Math.floor(z) & 255;
|
|
1804
|
+
const xf = x - Math.floor(x);
|
|
1805
|
+
const yf = y - Math.floor(y);
|
|
1806
|
+
const zf = z - Math.floor(z);
|
|
1807
|
+
const u = this.fade(xf);
|
|
1808
|
+
const v = this.fade(yf);
|
|
1809
|
+
const w2 = this.fade(zf);
|
|
1810
|
+
const aaa = this.perm[this.perm[this.perm[xi] + yi] + zi];
|
|
1811
|
+
const aba = this.perm[this.perm[this.perm[xi] + yi + 1] + zi];
|
|
1812
|
+
const aab = this.perm[this.perm[this.perm[xi] + yi] + zi + 1];
|
|
1813
|
+
const abb = this.perm[this.perm[this.perm[xi] + yi + 1] + zi + 1];
|
|
1814
|
+
const baa = this.perm[this.perm[this.perm[xi + 1] + yi] + zi];
|
|
1815
|
+
const bba = this.perm[this.perm[this.perm[xi + 1] + yi + 1] + zi];
|
|
1816
|
+
const bab = this.perm[this.perm[this.perm[xi + 1] + yi] + zi + 1];
|
|
1817
|
+
const bbb = this.perm[this.perm[this.perm[xi + 1] + yi + 1] + zi + 1];
|
|
1818
|
+
const grad3 = (hash, x2, y2, z2) => {
|
|
1819
|
+
const h = hash & 15;
|
|
1820
|
+
const u2 = h < 8 ? x2 : y2;
|
|
1821
|
+
const v2 = h < 4 ? y2 : h === 12 || h === 14 ? x2 : z2;
|
|
1822
|
+
return ((h & 1) === 0 ? u2 : -u2) + ((h & 2) === 0 ? v2 : -v2);
|
|
1823
|
+
};
|
|
1824
|
+
const x1 = this.lerp(
|
|
1825
|
+
w2,
|
|
1826
|
+
this.lerp(
|
|
1827
|
+
v,
|
|
1828
|
+
this.lerp(u, grad3(aaa, xf, yf, zf), grad3(baa, xf - 1, yf, zf)),
|
|
1829
|
+
this.lerp(u, grad3(aba, xf, yf - 1, zf), grad3(bba, xf - 1, yf - 1, zf))
|
|
1830
|
+
),
|
|
1831
|
+
this.lerp(
|
|
1832
|
+
v,
|
|
1833
|
+
this.lerp(u, grad3(aab, xf, yf, zf - 1), grad3(bab, xf - 1, yf, zf - 1)),
|
|
1834
|
+
this.lerp(u, grad3(abb, xf, yf - 1, zf - 1), grad3(bbb, xf - 1, yf - 1, zf - 1))
|
|
1835
|
+
)
|
|
1836
|
+
);
|
|
1837
|
+
return x1;
|
|
1838
|
+
} else {
|
|
1839
|
+
return this.perlin(x, y, z) * 0.5 + this.perlin(x + w, y + w, z + w) * 0.5;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
simplex(x, y, z, w) {
|
|
1843
|
+
if (y === void 0) {
|
|
1844
|
+
return this.perlin(x);
|
|
1845
|
+
} else if (z === void 0) {
|
|
1846
|
+
let n0 = 0, n1 = 0, n2 = 0;
|
|
1847
|
+
const s = (x + y) * this.F2;
|
|
1848
|
+
const i = Math.floor(x + s);
|
|
1849
|
+
const j = Math.floor(y + s);
|
|
1850
|
+
const t = (i + j) * this.G2;
|
|
1851
|
+
const X0 = i - t;
|
|
1852
|
+
const Y0 = j - t;
|
|
1853
|
+
const x0 = x - X0;
|
|
1854
|
+
const y0 = y - Y0;
|
|
1855
|
+
let i1, j1;
|
|
1856
|
+
if (x0 > y0) {
|
|
1857
|
+
i1 = 1;
|
|
1858
|
+
j1 = 0;
|
|
1859
|
+
} else {
|
|
1860
|
+
i1 = 0;
|
|
1861
|
+
j1 = 1;
|
|
1862
|
+
}
|
|
1863
|
+
const x1 = x0 - i1 + this.G2;
|
|
1864
|
+
const y1 = y0 - j1 + this.G2;
|
|
1865
|
+
const x2 = x0 - 1 + 2 * this.G2;
|
|
1866
|
+
const y2 = y0 - 1 + 2 * this.G2;
|
|
1867
|
+
const ii = i & 255;
|
|
1868
|
+
const jj = j & 255;
|
|
1869
|
+
const gi0 = this.permMod12[ii + this.perm[jj]];
|
|
1870
|
+
const gi1 = this.permMod12[ii + i1 + this.perm[jj + j1]];
|
|
1871
|
+
const gi2 = this.permMod12[ii + 1 + this.perm[jj + 1]];
|
|
1872
|
+
let t0 = 0.5 - x0 * x0 - y0 * y0;
|
|
1873
|
+
if (t0 < 0) {
|
|
1874
|
+
n0 = 0;
|
|
1875
|
+
} else {
|
|
1876
|
+
t0 *= t0;
|
|
1877
|
+
n0 = t0 * t0 * this.dot2(this.grad3[gi0], x0, y0);
|
|
1878
|
+
}
|
|
1879
|
+
let t1 = 0.5 - x1 * x1 - y1 * y1;
|
|
1880
|
+
if (t1 < 0) {
|
|
1881
|
+
n1 = 0;
|
|
1882
|
+
} else {
|
|
1883
|
+
t1 *= t1;
|
|
1884
|
+
n1 = t1 * t1 * this.dot2(this.grad3[gi1], x1, y1);
|
|
1885
|
+
}
|
|
1886
|
+
let t2 = 0.5 - x2 * x2 - y2 * y2;
|
|
1887
|
+
if (t2 < 0) {
|
|
1888
|
+
n2 = 0;
|
|
1889
|
+
} else {
|
|
1890
|
+
t2 *= t2;
|
|
1891
|
+
n2 = t2 * t2 * this.dot2(this.grad3[gi2], x2, y2);
|
|
1892
|
+
}
|
|
1893
|
+
return 70 * (n0 + n1 + n2);
|
|
1894
|
+
} else if (w === void 0) {
|
|
1895
|
+
let n0 = 0, n1 = 0, n2 = 0, n3 = 0;
|
|
1896
|
+
const s = (x + y + z) * this.F3;
|
|
1897
|
+
const i = Math.floor(x + s);
|
|
1898
|
+
const j = Math.floor(y + s);
|
|
1899
|
+
const k = Math.floor(z + s);
|
|
1900
|
+
const t = (i + j + k) * this.G3;
|
|
1901
|
+
const X0 = i - t;
|
|
1902
|
+
const Y0 = j - t;
|
|
1903
|
+
const Z0 = k - t;
|
|
1904
|
+
const x0 = x - X0;
|
|
1905
|
+
const y0 = y - Y0;
|
|
1906
|
+
const z0 = z - Z0;
|
|
1907
|
+
let i1, j1, k1;
|
|
1908
|
+
let i2, j2, k2;
|
|
1909
|
+
if (x0 >= y0) {
|
|
1910
|
+
if (y0 >= z0) {
|
|
1911
|
+
i1 = 1;
|
|
1912
|
+
j1 = 0;
|
|
1913
|
+
k1 = 0;
|
|
1914
|
+
i2 = 1;
|
|
1915
|
+
j2 = 1;
|
|
1916
|
+
k2 = 0;
|
|
1917
|
+
} else if (x0 >= z0) {
|
|
1918
|
+
i1 = 1;
|
|
1919
|
+
j1 = 0;
|
|
1920
|
+
k1 = 0;
|
|
1921
|
+
i2 = 1;
|
|
1922
|
+
j2 = 0;
|
|
1923
|
+
k2 = 1;
|
|
1924
|
+
} else {
|
|
1925
|
+
i1 = 0;
|
|
1926
|
+
j1 = 0;
|
|
1927
|
+
k1 = 1;
|
|
1928
|
+
i2 = 1;
|
|
1929
|
+
j2 = 0;
|
|
1930
|
+
k2 = 1;
|
|
1931
|
+
}
|
|
1932
|
+
} else {
|
|
1933
|
+
if (y0 < z0) {
|
|
1934
|
+
i1 = 0;
|
|
1935
|
+
j1 = 0;
|
|
1936
|
+
k1 = 1;
|
|
1937
|
+
i2 = 0;
|
|
1938
|
+
j2 = 1;
|
|
1939
|
+
k2 = 1;
|
|
1940
|
+
} else if (x0 < z0) {
|
|
1941
|
+
i1 = 0;
|
|
1942
|
+
j1 = 1;
|
|
1943
|
+
k1 = 0;
|
|
1944
|
+
i2 = 0;
|
|
1945
|
+
j2 = 1;
|
|
1946
|
+
k2 = 1;
|
|
1947
|
+
} else {
|
|
1948
|
+
i1 = 0;
|
|
1949
|
+
j1 = 1;
|
|
1950
|
+
k1 = 0;
|
|
1951
|
+
i2 = 1;
|
|
1952
|
+
j2 = 1;
|
|
1953
|
+
k2 = 0;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
const x1 = x0 - i1 + this.G3;
|
|
1957
|
+
const y1 = y0 - j1 + this.G3;
|
|
1958
|
+
const z1 = z0 - k1 + this.G3;
|
|
1959
|
+
const x2 = x0 - i2 + 2 * this.G3;
|
|
1960
|
+
const y2 = y0 - j2 + 2 * this.G3;
|
|
1961
|
+
const z2 = z0 - k2 + 2 * this.G3;
|
|
1962
|
+
const x3 = x0 - 1 + 3 * this.G3;
|
|
1963
|
+
const y3 = y0 - 1 + 3 * this.G3;
|
|
1964
|
+
const z3 = z0 - 1 + 3 * this.G3;
|
|
1965
|
+
const ii = i & 255;
|
|
1966
|
+
const jj = j & 255;
|
|
1967
|
+
const kk = k & 255;
|
|
1968
|
+
const gi0 = this.permMod12[ii + this.perm[jj + this.perm[kk]]];
|
|
1969
|
+
const gi1 = this.permMod12[ii + i1 + this.perm[jj + j1 + this.perm[kk + k1]]];
|
|
1970
|
+
const gi2 = this.permMod12[ii + i2 + this.perm[jj + j2 + this.perm[kk + k2]]];
|
|
1971
|
+
const gi3 = this.permMod12[ii + 1 + this.perm[jj + 1 + this.perm[kk + 1]]];
|
|
1972
|
+
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
|
|
1973
|
+
if (t0 < 0) {
|
|
1974
|
+
n0 = 0;
|
|
1975
|
+
} else {
|
|
1976
|
+
t0 *= t0;
|
|
1977
|
+
n0 = t0 * t0 * this.dot3(this.grad3[gi0], x0, y0, z0);
|
|
1978
|
+
}
|
|
1979
|
+
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
|
|
1980
|
+
if (t1 < 0) {
|
|
1981
|
+
n1 = 0;
|
|
1982
|
+
} else {
|
|
1983
|
+
t1 *= t1;
|
|
1984
|
+
n1 = t1 * t1 * this.dot3(this.grad3[gi1], x1, y1, z1);
|
|
1985
|
+
}
|
|
1986
|
+
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
|
|
1987
|
+
if (t2 < 0) {
|
|
1988
|
+
n2 = 0;
|
|
1989
|
+
} else {
|
|
1990
|
+
t2 *= t2;
|
|
1991
|
+
n2 = t2 * t2 * this.dot3(this.grad3[gi2], x2, y2, z2);
|
|
1992
|
+
}
|
|
1993
|
+
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
|
|
1994
|
+
if (t3 < 0) {
|
|
1995
|
+
n3 = 0;
|
|
1996
|
+
} else {
|
|
1997
|
+
t3 *= t3;
|
|
1998
|
+
n3 = t3 * t3 * this.dot3(this.grad3[gi3], x3, y3, z3);
|
|
1999
|
+
}
|
|
2000
|
+
return 32 * (n0 + n1 + n2 + n3);
|
|
2001
|
+
} else {
|
|
2002
|
+
return this.simplex(x, y, z) * 0.5 + this.simplex(x + w, y + w, z + w) * 0.5;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Dot product for 2D
|
|
2007
|
+
*/
|
|
2008
|
+
dot2(g, x, y) {
|
|
2009
|
+
return g[0] * x + g[1] * y;
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Dot product for 3D
|
|
2013
|
+
*/
|
|
2014
|
+
dot3(g, x, y, z) {
|
|
2015
|
+
return g[0] * x + g[1] * y + g[2] * z;
|
|
2016
|
+
}
|
|
2017
|
+
hash(x, y, z, w) {
|
|
2018
|
+
let n = 0;
|
|
2019
|
+
if (y === void 0) {
|
|
2020
|
+
n = Math.sin(x * 12.9898 + this.currentSeed) * 43758.5453;
|
|
2021
|
+
} else if (z === void 0) {
|
|
2022
|
+
n = Math.sin(x * 12.9898 + y * 78.233 + this.currentSeed) * 43758.5453;
|
|
2023
|
+
} else if (w === void 0) {
|
|
2024
|
+
n = Math.sin(x * 12.9898 + y * 78.233 + z * 37.719 + this.currentSeed) * 43758.5453;
|
|
2025
|
+
} else {
|
|
2026
|
+
n = Math.sin(x * 12.9898 + y * 78.233 + z * 37.719 + w * 59.1337 + this.currentSeed) * 43758.5453;
|
|
2027
|
+
}
|
|
2028
|
+
return n - Math.floor(n);
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Fractal Brownian Motion (fBm) noise
|
|
2032
|
+
* Supports options object for amplitude/frequency/lacunarity/gain/octaves.
|
|
2033
|
+
*/
|
|
2034
|
+
fbm(x, y, z, options) {
|
|
2035
|
+
let yVal = void 0;
|
|
2036
|
+
let zVal = void 0;
|
|
2037
|
+
let opts = typeof y === "object" ? y : typeof z === "object" ? z : {};
|
|
2038
|
+
if (typeof y === "number") yVal = y;
|
|
2039
|
+
if (typeof z === "number") zVal = z;
|
|
2040
|
+
if (options) opts = { ...opts, ...options };
|
|
2041
|
+
const octaves = opts.octaves ?? 4;
|
|
2042
|
+
if (octaves <= 0) return 0;
|
|
2043
|
+
let amplitude = opts.amplitude ?? 1;
|
|
2044
|
+
let frequency = opts.frequency ?? 1;
|
|
2045
|
+
const lacunarity = opts.lacunarity ?? 2;
|
|
2046
|
+
const gain = opts.gain ?? 0.5;
|
|
2047
|
+
let value = 0;
|
|
2048
|
+
let maxValue = 0;
|
|
2049
|
+
for (let i = 0; i < octaves; i++) {
|
|
2050
|
+
if (yVal === void 0) {
|
|
2051
|
+
value += amplitude * this.perlin(x * frequency);
|
|
2052
|
+
} else if (zVal === void 0) {
|
|
2053
|
+
value += amplitude * this.perlin(x * frequency, yVal * frequency);
|
|
2054
|
+
} else {
|
|
2055
|
+
value += amplitude * this.perlin(x * frequency, yVal * frequency, zVal * frequency);
|
|
2056
|
+
}
|
|
2057
|
+
maxValue += amplitude;
|
|
2058
|
+
amplitude *= gain;
|
|
2059
|
+
frequency *= lacunarity;
|
|
2060
|
+
}
|
|
2061
|
+
return maxValue === 0 ? 0 : value / maxValue;
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Turbulence noise (absolute value of noise)
|
|
2065
|
+
* Supports options object for octaves.
|
|
2066
|
+
*/
|
|
2067
|
+
turbulence(x, y, z, options) {
|
|
2068
|
+
let yVal = void 0;
|
|
2069
|
+
let zVal = void 0;
|
|
2070
|
+
let opts = typeof y === "object" ? y : typeof z === "object" ? z : {};
|
|
2071
|
+
if (typeof y === "number") yVal = y;
|
|
2072
|
+
if (typeof z === "number") zVal = z;
|
|
2073
|
+
if (options) opts = { ...opts, ...options };
|
|
2074
|
+
const octaves = opts.octaves ?? 4;
|
|
2075
|
+
if (octaves <= 0) return 0;
|
|
2076
|
+
let value = 0;
|
|
2077
|
+
let amplitude = 1;
|
|
2078
|
+
let frequency = 1;
|
|
2079
|
+
let maxValue = 0;
|
|
2080
|
+
for (let i = 0; i < octaves; i++) {
|
|
2081
|
+
let noise = 0;
|
|
2082
|
+
if (yVal === void 0) {
|
|
2083
|
+
noise = this.perlin(x * frequency);
|
|
2084
|
+
} else if (zVal === void 0) {
|
|
2085
|
+
noise = this.perlin(x * frequency, yVal * frequency);
|
|
2086
|
+
} else {
|
|
2087
|
+
noise = this.perlin(x * frequency, yVal * frequency, zVal * frequency);
|
|
2088
|
+
}
|
|
2089
|
+
value += amplitude * Math.abs(noise);
|
|
2090
|
+
maxValue += amplitude;
|
|
2091
|
+
amplitude *= 0.5;
|
|
2092
|
+
frequency *= 2;
|
|
2093
|
+
}
|
|
2094
|
+
return maxValue === 0 ? 0 : value / maxValue;
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Ridged multifractal noise (simple implementation)
|
|
2098
|
+
*/
|
|
2099
|
+
ridge(x, y, options) {
|
|
2100
|
+
let yVal = void 0;
|
|
2101
|
+
let opts = typeof y === "object" ? y : {};
|
|
2102
|
+
if (typeof y === "number") yVal = y;
|
|
2103
|
+
if (options) opts = { ...opts, ...options };
|
|
2104
|
+
const octaves = opts.octaves ?? 4;
|
|
2105
|
+
if (octaves <= 0) return 0;
|
|
2106
|
+
let amplitude = opts.amplitude ?? 1;
|
|
2107
|
+
let frequency = opts.frequency ?? 1;
|
|
2108
|
+
const lacunarity = opts.lacunarity ?? 2;
|
|
2109
|
+
const gain = opts.gain ?? 0.5;
|
|
2110
|
+
let value = 0;
|
|
2111
|
+
let weight = 1;
|
|
2112
|
+
let maxValue = 0;
|
|
2113
|
+
for (let i = 0; i < octaves; i++) {
|
|
2114
|
+
const n = yVal === void 0 ? this.perlin(x * frequency) : this.perlin(x * frequency, yVal * frequency);
|
|
2115
|
+
let signal = 1 - Math.abs(n);
|
|
2116
|
+
signal *= signal;
|
|
2117
|
+
signal *= weight;
|
|
2118
|
+
weight = signal * 2;
|
|
2119
|
+
weight = Math.min(Math.max(weight, 0), 1);
|
|
2120
|
+
value += signal * amplitude;
|
|
2121
|
+
maxValue += amplitude;
|
|
2122
|
+
amplitude *= gain;
|
|
2123
|
+
frequency *= lacunarity;
|
|
2124
|
+
}
|
|
2125
|
+
return maxValue === 0 ? 0 : value / maxValue;
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Cellular / Worley noise (2D simple implementation)
|
|
2129
|
+
*/
|
|
2130
|
+
cellular(x, y, options) {
|
|
2131
|
+
let yVal = void 0;
|
|
2132
|
+
let opts = typeof y === "object" ? y : {};
|
|
2133
|
+
if (typeof y === "number") yVal = y;
|
|
2134
|
+
if (options) opts = { ...opts, ...options };
|
|
2135
|
+
if (yVal === void 0) yVal = 0;
|
|
2136
|
+
const xi = Math.floor(x);
|
|
2137
|
+
const yi = Math.floor(yVal);
|
|
2138
|
+
const distance = opts.distance ?? "euclidean";
|
|
2139
|
+
let minDist = Infinity;
|
|
2140
|
+
for (let j = -1; j <= 1; j++) {
|
|
2141
|
+
for (let i = -1; i <= 1; i++) {
|
|
2142
|
+
const fx = i + this.hash(xi + i, yi + j);
|
|
2143
|
+
const fy = j + this.hash(yi + j, xi + i);
|
|
2144
|
+
const dx = fx + xi - x;
|
|
2145
|
+
const dy = fy + yi - yVal;
|
|
2146
|
+
const dist = distance === "manhattan" ? Math.abs(dx) + Math.abs(dy) : Math.sqrt(dx * dx + dy * dy);
|
|
2147
|
+
if (dist < minDist) minDist = dist;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
const maxDist = Math.SQRT2;
|
|
2151
|
+
const normalized = 1 - Math.min(minDist / maxDist, 1);
|
|
2152
|
+
return normalized;
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
var Noise_default = Noise;
|
|
2156
|
+
|
|
2157
|
+
// src/elements/Hotspot.tsx
|
|
2158
|
+
var Hotspot = class {
|
|
2159
|
+
/**
|
|
2160
|
+
* Creates a new Hotspot instance
|
|
2161
|
+
* @param ctx - The Klint context
|
|
2162
|
+
*/
|
|
2163
|
+
constructor(ctx) {
|
|
2164
|
+
this.context = ctx;
|
|
2165
|
+
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Check if point is inside a circle
|
|
2168
|
+
* @param point - Point to check (usually mouse position)
|
|
2169
|
+
* @param x - Circle center X
|
|
2170
|
+
* @param y - Circle center Y
|
|
2171
|
+
* @param radius - Circle radius
|
|
2172
|
+
* @returns True if point is inside the circle
|
|
2173
|
+
*/
|
|
2174
|
+
circle(point, x, y, radius) {
|
|
2175
|
+
const dx = point.x - x;
|
|
2176
|
+
const dy = point.y - y;
|
|
2177
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2178
|
+
return distance <= radius;
|
|
2179
|
+
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Check if point is inside a rectangle
|
|
2182
|
+
* @param point - Point to check (usually mouse position)
|
|
2183
|
+
* @param x - Rectangle X position
|
|
2184
|
+
* @param y - Rectangle Y position
|
|
2185
|
+
* @param width - Rectangle width
|
|
2186
|
+
* @param height - Rectangle height
|
|
2187
|
+
* @returns True if point is inside the rectangle
|
|
2188
|
+
*/
|
|
2189
|
+
rect(point, x, y, width, height) {
|
|
2190
|
+
const origin = this.context.__rectangleOrigin || "corner";
|
|
2191
|
+
let left, top;
|
|
2192
|
+
if (origin === "center") {
|
|
2193
|
+
left = x - width / 2;
|
|
2194
|
+
top = y - height / 2;
|
|
2195
|
+
} else {
|
|
2196
|
+
left = x;
|
|
2197
|
+
top = y;
|
|
2198
|
+
}
|
|
2199
|
+
const right = left + width;
|
|
2200
|
+
const bottom = top + height;
|
|
2201
|
+
return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Check if point is inside an ellipse
|
|
2205
|
+
* @param point - Point to check
|
|
2206
|
+
* @param x - Ellipse center X
|
|
2207
|
+
* @param y - Ellipse center Y
|
|
2208
|
+
* @param radiusX - Horizontal radius
|
|
2209
|
+
* @param radiusY - Vertical radius
|
|
2210
|
+
* @param rotation - Rotation angle in radians
|
|
2211
|
+
* @returns True if point is inside the ellipse
|
|
2212
|
+
*/
|
|
2213
|
+
ellipse(point, x, y, radiusX, radiusY, rotation = 0) {
|
|
2214
|
+
const cos = Math.cos(-rotation);
|
|
2215
|
+
const sin = Math.sin(-rotation);
|
|
2216
|
+
const dx = point.x - x;
|
|
2217
|
+
const dy = point.y - y;
|
|
2218
|
+
const localX = dx * cos - dy * sin;
|
|
2219
|
+
const localY = dx * sin + dy * cos;
|
|
2220
|
+
return localX * localX / (radiusX * radiusX) + localY * localY / (radiusY * radiusY) <= 1;
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Check if point is inside a polygon
|
|
2224
|
+
* @param point - Point to check
|
|
2225
|
+
* @param vertices - Array of polygon vertices
|
|
2226
|
+
* @returns True if point is inside the polygon
|
|
2227
|
+
*/
|
|
2228
|
+
polygon(point, vertices) {
|
|
2229
|
+
let inside = false;
|
|
2230
|
+
const n = vertices.length;
|
|
2231
|
+
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
2232
|
+
const xi = vertices[i].x, yi = vertices[i].y;
|
|
2233
|
+
const xj = vertices[j].x, yj = vertices[j].y;
|
|
2234
|
+
const intersect = yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi;
|
|
2235
|
+
if (intersect) inside = !inside;
|
|
2236
|
+
}
|
|
2237
|
+
return inside;
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Check if point is inside a Path2D
|
|
2241
|
+
* @param point - Point to check
|
|
2242
|
+
* @param path - Path2D object
|
|
2243
|
+
* @returns True if point is inside the path
|
|
2244
|
+
*/
|
|
2245
|
+
path(point, path) {
|
|
2246
|
+
return this.context.isPointInPath(path, point.x, point.y);
|
|
2247
|
+
}
|
|
2248
|
+
};
|
|
2249
|
+
var Hotspot_default = Hotspot;
|
|
2250
|
+
|
|
2251
|
+
// src/elements/Performance.tsx
|
|
2252
|
+
var Performance = class {
|
|
2253
|
+
constructor(ctx) {
|
|
2254
|
+
this.textMetricsCache = /* @__PURE__ */ new Map();
|
|
2255
|
+
this.frameTimeHistory = [];
|
|
2256
|
+
this.MAX_HISTORY = 60;
|
|
2257
|
+
this.context = ctx;
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Batch multiple canvas operations together for better performance
|
|
2261
|
+
*/
|
|
2262
|
+
batchDraw(drawFn) {
|
|
2263
|
+
this.context.save();
|
|
2264
|
+
this.context.beginPath();
|
|
2265
|
+
try {
|
|
2266
|
+
drawFn();
|
|
2267
|
+
} finally {
|
|
2268
|
+
this.context.restore();
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Optimize drawing by using offscreen canvas for static elements
|
|
2273
|
+
*/
|
|
2274
|
+
useOffscreenCache(id, width, height, renderFn) {
|
|
2275
|
+
let offscreen = this.context.__offscreens.get(id);
|
|
2276
|
+
if (!offscreen || offscreen instanceof HTMLImageElement) {
|
|
2277
|
+
offscreen = this.context.createOffscreen(id, width, height);
|
|
2278
|
+
if (offscreen && !(offscreen instanceof HTMLImageElement)) {
|
|
2279
|
+
renderFn(offscreen);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (offscreen) {
|
|
2283
|
+
if (offscreen instanceof HTMLImageElement) {
|
|
2284
|
+
this.context.image(offscreen, 0, 0);
|
|
2285
|
+
} else {
|
|
2286
|
+
this.context.image(offscreen.canvas, 0, 0);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Throttle expensive operations to run less frequently
|
|
2292
|
+
*/
|
|
2293
|
+
throttleFrame(interval, fn) {
|
|
2294
|
+
const cacheKey = `__throttle_${fn.toString().slice(0, 50)}`;
|
|
2295
|
+
const cached = this.context[cacheKey];
|
|
2296
|
+
if (!cached || this.context.frame % interval === 0) {
|
|
2297
|
+
const result = fn();
|
|
2298
|
+
this.context[cacheKey] = { value: result, frame: this.context.frame };
|
|
2299
|
+
return result;
|
|
2300
|
+
}
|
|
2301
|
+
return cached.value;
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Detect potential memory leaks by tracking object creation
|
|
2305
|
+
*/
|
|
2306
|
+
useLeakDetection() {
|
|
2307
|
+
const objectCounts = /* @__PURE__ */ new Map();
|
|
2308
|
+
let lastCheckFrame = 0;
|
|
2309
|
+
return {
|
|
2310
|
+
track: (type) => {
|
|
2311
|
+
const count = objectCounts.get(type) || 0;
|
|
2312
|
+
objectCounts.set(type, count + 1);
|
|
2313
|
+
},
|
|
2314
|
+
check: () => {
|
|
2315
|
+
if (this.context.frame - lastCheckFrame < 60) return;
|
|
2316
|
+
lastCheckFrame = this.context.frame;
|
|
2317
|
+
const warnings = [];
|
|
2318
|
+
objectCounts.forEach((count, type) => {
|
|
2319
|
+
if (count > 1e4) {
|
|
2320
|
+
warnings.push(
|
|
2321
|
+
`Potential memory leak: ${type} has ${count} instances`
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
if (warnings.length > 0) {
|
|
2326
|
+
console.warn("[Klint] Memory leak warnings:", warnings);
|
|
2327
|
+
}
|
|
2328
|
+
objectCounts.clear();
|
|
2329
|
+
}
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Optimize text rendering by caching text measurements
|
|
2334
|
+
*/
|
|
2335
|
+
getCachedTextMetrics(text, font) {
|
|
2336
|
+
const cacheKey = `${font}:${text}`;
|
|
2337
|
+
if (!this.textMetricsCache.has(cacheKey)) {
|
|
2338
|
+
this.context.save();
|
|
2339
|
+
this.context.font = font;
|
|
2340
|
+
const metrics = this.context.measureText(text);
|
|
2341
|
+
this.context.restore();
|
|
2342
|
+
this.textMetricsCache.set(cacheKey, metrics);
|
|
2343
|
+
if (this.textMetricsCache.size > 1e3) {
|
|
2344
|
+
const firstKey = this.textMetricsCache.keys().next().value;
|
|
2345
|
+
if (firstKey) this.textMetricsCache.delete(firstKey);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return this.textMetricsCache.get(cacheKey);
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Clear all performance caches
|
|
2352
|
+
*/
|
|
2353
|
+
clearCaches() {
|
|
2354
|
+
this.textMetricsCache.clear();
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* Get current performance metrics
|
|
2358
|
+
*/
|
|
2359
|
+
getMetrics() {
|
|
2360
|
+
return this.context.__performance || null;
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Render performance widget on canvas
|
|
2364
|
+
*/
|
|
2365
|
+
render(options = {}) {
|
|
2366
|
+
const metrics = this.getMetrics();
|
|
2367
|
+
if (!metrics) return;
|
|
2368
|
+
const {
|
|
2369
|
+
x = 10,
|
|
2370
|
+
y = 10,
|
|
2371
|
+
width = 200,
|
|
2372
|
+
height = 120,
|
|
2373
|
+
backgroundColor = "rgba(0, 0, 0, 0.8)",
|
|
2374
|
+
textColor = "#ffffff",
|
|
2375
|
+
accentColor = "#4ecdc4",
|
|
2376
|
+
showGraph = true,
|
|
2377
|
+
graphHeight = 40,
|
|
2378
|
+
fontSize = 12,
|
|
2379
|
+
showMemory = true
|
|
2380
|
+
} = options;
|
|
2381
|
+
if (showGraph) {
|
|
2382
|
+
this.frameTimeHistory.push(metrics.frameTime);
|
|
2383
|
+
if (this.frameTimeHistory.length > this.MAX_HISTORY) {
|
|
2384
|
+
this.frameTimeHistory.shift();
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
this.context.save();
|
|
2388
|
+
this.context.fillStyle = backgroundColor;
|
|
2389
|
+
this.context.fillRect(x, y, width, height);
|
|
2390
|
+
this.context.strokeStyle = accentColor;
|
|
2391
|
+
this.context.lineWidth = 1;
|
|
2392
|
+
this.context.strokeRect(x, y, width, height);
|
|
2393
|
+
this.context.fillStyle = textColor;
|
|
2394
|
+
this.context.font = `${fontSize}px monospace`;
|
|
2395
|
+
this.context.textAlign = "left";
|
|
2396
|
+
this.context.textBaseline = "top";
|
|
2397
|
+
let currentY = y + 8;
|
|
2398
|
+
const fpsColor = metrics.fps >= 55 ? "#4ecdc4" : metrics.fps >= 30 ? "#ffd93d" : "#ff6b6b";
|
|
2399
|
+
this.context.fillStyle = fpsColor;
|
|
2400
|
+
this.context.fillText(`FPS: ${metrics.fps}`, x + 8, currentY);
|
|
2401
|
+
currentY += fontSize + 4;
|
|
2402
|
+
this.context.fillStyle = textColor;
|
|
2403
|
+
this.context.fillText(`Frame: ${metrics.frameTime.toFixed(2)}ms`, x + 8, currentY);
|
|
2404
|
+
currentY += fontSize + 4;
|
|
2405
|
+
this.context.fillText(`Avg: ${metrics.averageFrameTime.toFixed(2)}ms`, x + 8, currentY);
|
|
2406
|
+
currentY += fontSize + 4;
|
|
2407
|
+
this.context.fillText(
|
|
2408
|
+
`Min: ${metrics.minFrameTime.toFixed(2)}ms / Max: ${metrics.maxFrameTime.toFixed(2)}ms`,
|
|
2409
|
+
x + 8,
|
|
2410
|
+
currentY
|
|
2411
|
+
);
|
|
2412
|
+
currentY += fontSize + 4;
|
|
2413
|
+
if (metrics.droppedFrames > 0) {
|
|
2414
|
+
this.context.fillStyle = "#ff6b6b";
|
|
2415
|
+
this.context.fillText(`Dropped: ${metrics.droppedFrames}`, x + 8, currentY);
|
|
2416
|
+
currentY += fontSize + 4;
|
|
2417
|
+
}
|
|
2418
|
+
if (showMemory && metrics.memoryUsage !== void 0) {
|
|
2419
|
+
this.context.fillStyle = textColor;
|
|
2420
|
+
this.context.fillText(`Memory: ${metrics.memoryUsage.toFixed(2)}MB`, x + 8, currentY);
|
|
2421
|
+
currentY += fontSize + 4;
|
|
2422
|
+
}
|
|
2423
|
+
if (showGraph && this.frameTimeHistory.length > 1) {
|
|
2424
|
+
const graphX = x + 8;
|
|
2425
|
+
const graphY = currentY + 4;
|
|
2426
|
+
const graphWidth = width - 16;
|
|
2427
|
+
const targetFrameTime = 1e3 / this.context.fps;
|
|
2428
|
+
this.context.fillStyle = "rgba(255, 255, 255, 0.1)";
|
|
2429
|
+
this.context.fillRect(graphX, graphY, graphWidth, graphHeight);
|
|
2430
|
+
const targetY = graphY + graphHeight - targetFrameTime / (targetFrameTime * 2) * graphHeight;
|
|
2431
|
+
this.context.strokeStyle = "rgba(255, 255, 255, 0.3)";
|
|
2432
|
+
this.context.lineWidth = 1;
|
|
2433
|
+
this.context.beginPath();
|
|
2434
|
+
this.context.moveTo(graphX, targetY);
|
|
2435
|
+
this.context.lineTo(graphX + graphWidth, targetY);
|
|
2436
|
+
this.context.stroke();
|
|
2437
|
+
this.context.strokeStyle = accentColor;
|
|
2438
|
+
this.context.lineWidth = 2;
|
|
2439
|
+
this.context.beginPath();
|
|
2440
|
+
const maxFrameTime = Math.max(...this.frameTimeHistory, targetFrameTime * 2);
|
|
2441
|
+
const stepX = graphWidth / (this.frameTimeHistory.length - 1);
|
|
2442
|
+
this.frameTimeHistory.forEach((frameTime, index) => {
|
|
2443
|
+
const normalizedTime = Math.min(frameTime / maxFrameTime, 1);
|
|
2444
|
+
const pointY = graphY + graphHeight - normalizedTime * graphHeight;
|
|
2445
|
+
const pointX = graphX + index * stepX;
|
|
2446
|
+
if (index === 0) {
|
|
2447
|
+
this.context.moveTo(pointX, pointY);
|
|
2448
|
+
} else {
|
|
2449
|
+
this.context.lineTo(pointX, pointY);
|
|
2450
|
+
}
|
|
2451
|
+
});
|
|
2452
|
+
this.context.stroke();
|
|
2453
|
+
}
|
|
2454
|
+
this.context.restore();
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* Alias for render() - shows performance widget
|
|
2458
|
+
*/
|
|
2459
|
+
show(options) {
|
|
2460
|
+
this.render(options);
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
var Performance_default = Performance;
|
|
2464
|
+
|
|
2465
|
+
// src/elements/SSR.tsx
|
|
2466
|
+
var SSR = class {
|
|
2467
|
+
constructor(ctx) {
|
|
2468
|
+
this.context = ctx || null;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Render a Klint sketch to a static image (base64 data URL)
|
|
2472
|
+
*/
|
|
2473
|
+
async renderToImage(draw, options) {
|
|
2474
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
2475
|
+
return this.renderInBrowser(draw, options);
|
|
2476
|
+
}
|
|
2477
|
+
throw new Error(
|
|
2478
|
+
"Server-side rendering requires 'canvas' package. Install it with: npm install canvas\nAlternatively, use SSR.generateImageUrl() to generate images via an API endpoint."
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Generate a static image URL for a Klint sketch
|
|
2483
|
+
*/
|
|
2484
|
+
async generateImageUrl(draw, options) {
|
|
2485
|
+
if (typeof window === "undefined") {
|
|
2486
|
+
throw new Error(
|
|
2487
|
+
"generateImageUrl() requires a browser environment. For server-side rendering, use renderToImage() with the 'canvas' package."
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
return this.renderInBrowser(draw, options);
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Check if Klint can run in the current environment
|
|
2494
|
+
*/
|
|
2495
|
+
canRender() {
|
|
2496
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
2497
|
+
return true;
|
|
2498
|
+
}
|
|
2499
|
+
try {
|
|
2500
|
+
__require("canvas");
|
|
2501
|
+
return true;
|
|
2502
|
+
} catch {
|
|
2503
|
+
return false;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
/**
|
|
2507
|
+
* Render in browser environment (client-side)
|
|
2508
|
+
*/
|
|
2509
|
+
async renderInBrowser(draw, options) {
|
|
2510
|
+
return new Promise((resolve, reject) => {
|
|
2511
|
+
try {
|
|
2512
|
+
const canvas = document.createElement("canvas");
|
|
2513
|
+
const dpr = options.dpr || (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1);
|
|
2514
|
+
canvas.width = options.width * dpr;
|
|
2515
|
+
canvas.height = options.height * dpr;
|
|
2516
|
+
const ctx = canvas.getContext("2d");
|
|
2517
|
+
if (!ctx) {
|
|
2518
|
+
reject(new Error("Failed to get canvas context"));
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
const klintContext = this.createMinimalContext(
|
|
2522
|
+
ctx,
|
|
2523
|
+
canvas,
|
|
2524
|
+
options.width,
|
|
2525
|
+
options.height,
|
|
2526
|
+
dpr
|
|
2527
|
+
);
|
|
2528
|
+
draw(klintContext);
|
|
2529
|
+
const mimeType = options.format === "jpeg" ? "image/jpeg" : options.format === "webp" ? "image/webp" : "image/png";
|
|
2530
|
+
const quality = options.quality !== void 0 ? options.quality : 0.85;
|
|
2531
|
+
const dataUrl = canvas.toDataURL(mimeType, quality);
|
|
2532
|
+
resolve(dataUrl);
|
|
2533
|
+
} catch (error) {
|
|
2534
|
+
reject(error);
|
|
2535
|
+
}
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Create a minimal KlintContext for server rendering
|
|
2540
|
+
* This provides basic functionality without full Klint features
|
|
2541
|
+
*/
|
|
2542
|
+
createMinimalContext(ctx, canvas, width, height, dpr) {
|
|
2543
|
+
return {
|
|
2544
|
+
canvas,
|
|
2545
|
+
width: width * dpr,
|
|
2546
|
+
height: height * dpr,
|
|
2547
|
+
// Basic canvas methods
|
|
2548
|
+
fillRect: ctx.fillRect.bind(ctx),
|
|
2549
|
+
strokeRect: ctx.strokeRect.bind(ctx),
|
|
2550
|
+
clearRect: ctx.clearRect.bind(ctx),
|
|
2551
|
+
beginPath: ctx.beginPath.bind(ctx),
|
|
2552
|
+
moveTo: ctx.moveTo.bind(ctx),
|
|
2553
|
+
lineTo: ctx.lineTo.bind(ctx),
|
|
2554
|
+
arc: ctx.arc.bind(ctx),
|
|
2555
|
+
fill: ctx.fill.bind(ctx),
|
|
2556
|
+
stroke: ctx.stroke.bind(ctx),
|
|
2557
|
+
save: ctx.save.bind(ctx),
|
|
2558
|
+
restore: ctx.restore.bind(ctx),
|
|
2559
|
+
translate: ctx.translate.bind(ctx),
|
|
2560
|
+
rotate: ctx.rotate.bind(ctx),
|
|
2561
|
+
scale: ctx.scale.bind(ctx),
|
|
2562
|
+
// Basic properties
|
|
2563
|
+
fillStyle: ctx.fillStyle,
|
|
2564
|
+
strokeStyle: ctx.strokeStyle,
|
|
2565
|
+
lineWidth: ctx.lineWidth,
|
|
2566
|
+
// Minimal Klint-like API
|
|
2567
|
+
background: (color) => {
|
|
2568
|
+
ctx.fillStyle = color || "#000";
|
|
2569
|
+
ctx.fillRect(0, 0, width * dpr, height * dpr);
|
|
2570
|
+
},
|
|
2571
|
+
fillColor: (color) => {
|
|
2572
|
+
ctx.fillStyle = color;
|
|
2573
|
+
},
|
|
2574
|
+
strokeColor: (color) => {
|
|
2575
|
+
ctx.strokeStyle = color;
|
|
2576
|
+
},
|
|
2577
|
+
circle: (x, y, radius) => {
|
|
2578
|
+
ctx.beginPath();
|
|
2579
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
2580
|
+
ctx.fill();
|
|
2581
|
+
},
|
|
2582
|
+
rectangle: (x, y, w, h) => {
|
|
2583
|
+
ctx.fillRect(x, y, w, h || w);
|
|
2584
|
+
},
|
|
2585
|
+
// Time properties (static for server rendering)
|
|
2586
|
+
frame: 0,
|
|
2587
|
+
time: 0,
|
|
2588
|
+
deltaTime: 0,
|
|
2589
|
+
fps: 60
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
var SSR_default = SSR;
|
|
2594
|
+
|
|
2595
|
+
// src/KlintFunctions.tsx
|
|
2596
|
+
var KlintCoreFunctions = {
|
|
2597
|
+
saveCanvas: (ctx) => () => {
|
|
2598
|
+
const link = document.createElement("a");
|
|
2599
|
+
link.download = "canvas.png";
|
|
2600
|
+
link.href = ctx.canvas.toDataURL();
|
|
2601
|
+
link.click();
|
|
2602
|
+
},
|
|
2603
|
+
fullscreen: (ctx) => () => {
|
|
2604
|
+
ctx.canvas.requestFullscreen?.();
|
|
2605
|
+
},
|
|
2606
|
+
play: (ctx) => () => {
|
|
2607
|
+
if (!ctx.__isPlaying) ctx.__isPlaying = true;
|
|
2608
|
+
},
|
|
2609
|
+
pause: (ctx) => () => {
|
|
2610
|
+
if (ctx.__isPlaying) ctx.__isPlaying = false;
|
|
2611
|
+
},
|
|
2612
|
+
// to do
|
|
2613
|
+
redraw: () => () => {
|
|
2614
|
+
},
|
|
2615
|
+
extend: (ctx) => (name, data, enforceReplace = false) => {
|
|
2616
|
+
if (name in ctx && !enforceReplace) return;
|
|
2617
|
+
ctx[name] = data;
|
|
2618
|
+
},
|
|
2619
|
+
passImage: () => (element) => {
|
|
2620
|
+
if (!element.complete) {
|
|
2621
|
+
console.warn("Image passed to passImage() is not fully loaded");
|
|
2622
|
+
return null;
|
|
2623
|
+
}
|
|
2624
|
+
return element;
|
|
2625
|
+
},
|
|
2626
|
+
passImages: () => (elements) => {
|
|
2627
|
+
return elements.map((element) => {
|
|
2628
|
+
if (!element.complete) {
|
|
2629
|
+
console.warn("Image passed to passImages() is not fully loaded");
|
|
2630
|
+
return null;
|
|
2631
|
+
}
|
|
2632
|
+
return element;
|
|
2633
|
+
});
|
|
2634
|
+
},
|
|
2635
|
+
saveConfig: (ctx) => (from) => {
|
|
2636
|
+
return Object.fromEntries(
|
|
2637
|
+
CONFIG_PROPS.map((key) => [
|
|
2638
|
+
key,
|
|
2639
|
+
from?.[key] ?? ctx[key]
|
|
2640
|
+
])
|
|
2641
|
+
);
|
|
2642
|
+
},
|
|
2643
|
+
restoreConfig: (ctx) => (config) => {
|
|
2644
|
+
Object.assign(ctx, config);
|
|
2645
|
+
},
|
|
2646
|
+
describe: (ctx) => (description) => {
|
|
2647
|
+
ctx.__description = description;
|
|
2648
|
+
},
|
|
2649
|
+
createOffscreen: (ctx) => (id, width, height, options, callback) => {
|
|
2650
|
+
const offscreen = document.createElement("canvas");
|
|
2651
|
+
offscreen.width = width * ctx.__dpr;
|
|
2652
|
+
offscreen.height = height * ctx.__dpr;
|
|
2653
|
+
const context = offscreen.getContext("2d", {
|
|
2654
|
+
alpha: options?.alpha ?? true,
|
|
2655
|
+
willReadFrequently: options?.willreadfrequently ?? false
|
|
2656
|
+
});
|
|
2657
|
+
if (!context) throw new Error("Failed to create offscreen context");
|
|
2658
|
+
context.__dpr = ctx.__dpr;
|
|
2659
|
+
context.width = width * ctx.__dpr;
|
|
2660
|
+
context.height = height * ctx.__dpr;
|
|
2661
|
+
context.__isMainContext = false;
|
|
2662
|
+
context.__imageOrigin = "corner";
|
|
2663
|
+
context.__rectangleOrigin = "corner";
|
|
2664
|
+
context.__canvasOrigin = "corner";
|
|
2665
|
+
context.__textFont = "sans-serif";
|
|
2666
|
+
context.__textWeight = "normal";
|
|
2667
|
+
context.__textStyle = "normal";
|
|
2668
|
+
context.__textSize = 120;
|
|
2669
|
+
context.__textLeading = void 0;
|
|
2670
|
+
context.__textAlignment = {
|
|
2671
|
+
horizontal: "left",
|
|
2672
|
+
vertical: "top"
|
|
2673
|
+
};
|
|
2674
|
+
context.__fillRule = "nonzero";
|
|
2675
|
+
if (!options?.ignoreFunctions) {
|
|
2676
|
+
context.Color = ctx.Color;
|
|
2677
|
+
context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
|
|
2678
|
+
context.Easing = ctx.Easing;
|
|
2679
|
+
context.Text = ctx.Text;
|
|
2680
|
+
Object.entries(KlintFunctions).forEach(([name, fn]) => {
|
|
2681
|
+
context[name] = fn(context);
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
if (options?.origin) {
|
|
2685
|
+
context.__canvasOrigin = options.origin;
|
|
2686
|
+
if (options.origin === "center") {
|
|
2687
|
+
context.translate(context.width * 0.5, context.height * 0.5);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
if (callback) {
|
|
2691
|
+
callback(context);
|
|
2692
|
+
}
|
|
2693
|
+
if (options?.static === "true") {
|
|
2694
|
+
const base64 = offscreen.toDataURL();
|
|
2695
|
+
const img = new Image();
|
|
2696
|
+
img.src = base64;
|
|
2697
|
+
ctx.__offscreens.set(id, img);
|
|
2698
|
+
return img;
|
|
2699
|
+
}
|
|
2700
|
+
ctx.__offscreens.set(id, context);
|
|
2701
|
+
return context;
|
|
2702
|
+
},
|
|
2703
|
+
getOffscreen: (ctx) => (id) => {
|
|
2704
|
+
const offscreen = ctx.__offscreens.get(id);
|
|
2705
|
+
if (!offscreen)
|
|
2706
|
+
throw new Error(`No offscreen context found with id: ${id}`);
|
|
2707
|
+
return offscreen;
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
var KlintFunctions = {
|
|
2711
|
+
extend: (ctx) => (name, data, enforceReplace = false) => {
|
|
2712
|
+
if (name in ctx && !enforceReplace) return;
|
|
2713
|
+
ctx[name] = data;
|
|
2714
|
+
},
|
|
2715
|
+
background: (ctx) => (color) => {
|
|
2716
|
+
ctx.resetTransform();
|
|
2717
|
+
ctx.push();
|
|
2718
|
+
if (color && color !== "transparent") {
|
|
2719
|
+
ctx.fillStyle = color;
|
|
2720
|
+
ctx.fillRect(0, 0, ctx.width, ctx.height);
|
|
2721
|
+
} else {
|
|
2722
|
+
ctx.clearRect(0, 0, ctx.width, ctx.height);
|
|
2723
|
+
}
|
|
2724
|
+
ctx.pop();
|
|
2725
|
+
if (ctx.__canvasOrigin === "center")
|
|
2726
|
+
ctx.translate(ctx.width * 0.5, ctx.height * 0.5);
|
|
2727
|
+
},
|
|
2728
|
+
reset: (ctx) => () => {
|
|
1278
2729
|
ctx.clearRect(0, 0, ctx.width, ctx.height);
|
|
1279
2730
|
ctx.resetTransform();
|
|
1280
2731
|
},
|
|
@@ -1322,9 +2773,12 @@ var KlintFunctions = {
|
|
|
1322
2773
|
return true;
|
|
1323
2774
|
},
|
|
1324
2775
|
drawIfVisible: (ctx) => () => {
|
|
1325
|
-
if (ctx.checkTransparency("fill")) ctx.fill();
|
|
2776
|
+
if (ctx.checkTransparency("fill")) ctx.fill(ctx.__fillRule || "nonzero");
|
|
1326
2777
|
if (ctx.checkTransparency("stroke")) ctx.stroke();
|
|
1327
2778
|
},
|
|
2779
|
+
fillRule: (ctx) => (rule) => {
|
|
2780
|
+
ctx.__fillRule = rule;
|
|
2781
|
+
},
|
|
1328
2782
|
line: (ctx) => (x1, y1, x2, y2) => {
|
|
1329
2783
|
if (!ctx.checkTransparency("stroke")) return;
|
|
1330
2784
|
ctx.beginPath();
|
|
@@ -1386,17 +2840,29 @@ var KlintFunctions = {
|
|
|
1386
2840
|
ctx.__currentContours = [];
|
|
1387
2841
|
},
|
|
1388
2842
|
beginContour: (ctx) => () => {
|
|
1389
|
-
if (!ctx.__startedShape) return;
|
|
1390
|
-
if (ctx.__startedContour && ctx.__currentContour?.length) {
|
|
1391
|
-
ctx.__currentContours?.push([...ctx.__currentContour]);
|
|
1392
|
-
}
|
|
2843
|
+
if (!ctx.__startedShape || ctx.__startedContour) return;
|
|
1393
2844
|
ctx.__startedContour = true;
|
|
1394
2845
|
ctx.__currentContour = [];
|
|
1395
2846
|
},
|
|
1396
2847
|
vertex: (ctx) => (x, y) => {
|
|
1397
2848
|
if (!ctx.__startedShape) return;
|
|
1398
2849
|
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
1399
|
-
points?.push(
|
|
2850
|
+
points?.push({ type: "line", x, y });
|
|
2851
|
+
},
|
|
2852
|
+
bezierVertex: (ctx) => (cp1x, cp1y, cp2x, cp2y, x, y) => {
|
|
2853
|
+
if (!ctx.__startedShape) return;
|
|
2854
|
+
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
2855
|
+
points?.push({ type: "bezier", cp1x, cp1y, cp2x, cp2y, x, y });
|
|
2856
|
+
},
|
|
2857
|
+
quadraticVertex: (ctx) => (cpx, cpy, x, y) => {
|
|
2858
|
+
if (!ctx.__startedShape) return;
|
|
2859
|
+
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
2860
|
+
points?.push({ type: "quadratic", cpx, cpy, x, y });
|
|
2861
|
+
},
|
|
2862
|
+
arcVertex: (ctx) => (x1, y1, x2, y2, radius) => {
|
|
2863
|
+
if (!ctx.__startedShape) return;
|
|
2864
|
+
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
2865
|
+
points?.push({ type: "arc", x1, y1, x2, y2, radius });
|
|
1400
2866
|
},
|
|
1401
2867
|
endContour: (ctx) => (forceRevert = true) => {
|
|
1402
2868
|
if (!ctx.__startedContour || !ctx.__currentContour?.length) return;
|
|
@@ -1414,17 +2880,38 @@ var KlintFunctions = {
|
|
|
1414
2880
|
const points = ctx.__currentShape;
|
|
1415
2881
|
if (!points?.length) return;
|
|
1416
2882
|
const drawPath = (points2, close2 = false) => {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
2883
|
+
if (points2.length === 0) return;
|
|
2884
|
+
const firstPoint = points2[0];
|
|
2885
|
+
const startX = firstPoint.type === "line" ? firstPoint.x : firstPoint.type === "bezier" ? firstPoint.x : firstPoint.type === "quadratic" ? firstPoint.x : firstPoint.x2;
|
|
2886
|
+
const startY = firstPoint.type === "line" ? firstPoint.y : firstPoint.type === "bezier" ? firstPoint.y : firstPoint.type === "quadratic" ? firstPoint.y : firstPoint.y2;
|
|
2887
|
+
ctx.moveTo(startX, startY);
|
|
2888
|
+
for (let i = 0; i < points2.length; i++) {
|
|
2889
|
+
const point = points2[i];
|
|
2890
|
+
switch (point.type) {
|
|
2891
|
+
case "line":
|
|
2892
|
+
if (i > 0) ctx.lineTo(point.x, point.y);
|
|
2893
|
+
break;
|
|
2894
|
+
case "bezier":
|
|
2895
|
+
ctx.bezierCurveTo(
|
|
2896
|
+
point.cp1x,
|
|
2897
|
+
point.cp1y,
|
|
2898
|
+
point.cp2x,
|
|
2899
|
+
point.cp2y,
|
|
2900
|
+
point.x,
|
|
2901
|
+
point.y
|
|
2902
|
+
);
|
|
2903
|
+
break;
|
|
2904
|
+
case "quadratic":
|
|
2905
|
+
ctx.quadraticCurveTo(point.cpx, point.cpy, point.x, point.y);
|
|
2906
|
+
break;
|
|
2907
|
+
case "arc":
|
|
2908
|
+
ctx.arcTo(point.x1, point.y1, point.x2, point.y2, point.radius);
|
|
2909
|
+
break;
|
|
1426
2910
|
}
|
|
1427
2911
|
}
|
|
2912
|
+
if (close2 && points2.length > 1) {
|
|
2913
|
+
ctx.closePath();
|
|
2914
|
+
}
|
|
1428
2915
|
};
|
|
1429
2916
|
ctx.beginPath();
|
|
1430
2917
|
drawPath(points, close);
|
|
@@ -1448,6 +2935,9 @@ var KlintFunctions = {
|
|
|
1448
2935
|
addColorStop: () => (gradient, offset = 0, color = "#000") => {
|
|
1449
2936
|
return gradient.addColorStop(offset, color);
|
|
1450
2937
|
},
|
|
2938
|
+
PI: () => Math.PI,
|
|
2939
|
+
TWO_PI: () => Math.PI * 2,
|
|
2940
|
+
TAU: () => Math.PI * 2,
|
|
1451
2941
|
constrain: () => (val, floor, ceil) => {
|
|
1452
2942
|
return Math.max(floor, Math.min(val, ceil));
|
|
1453
2943
|
},
|
|
@@ -1485,6 +2975,14 @@ var KlintFunctions = {
|
|
|
1485
2975
|
const t = (n - A) / (B - A);
|
|
1486
2976
|
return ctx.lerp(C, D, t, bounded);
|
|
1487
2977
|
},
|
|
2978
|
+
bezierLerp: () => (a, b, c, d, t) => {
|
|
2979
|
+
const u = 1 - t;
|
|
2980
|
+
return u * u * u * a + 3 * u * u * t * b + 3 * u * t * t * c + t * t * t * d;
|
|
2981
|
+
},
|
|
2982
|
+
bezierTangent: () => (a, b, c, d, t) => {
|
|
2983
|
+
const u = 1 - t;
|
|
2984
|
+
return 3 * d * t * t - 3 * c * t * t + 6 * c * u * t - 6 * b * u * t + 3 * b * u * u - 3 * a * u * u;
|
|
2985
|
+
},
|
|
1488
2986
|
textFont: (ctx) => (font) => {
|
|
1489
2987
|
ctx.__textFont = font;
|
|
1490
2988
|
},
|
|
@@ -1520,7 +3018,8 @@ var KlintFunctions = {
|
|
|
1520
3018
|
ctx.__textAlignment.vertical = vertical ?? ctx.__textAlignment.vertical;
|
|
1521
3019
|
},
|
|
1522
3020
|
textLeading: (ctx) => (spacing) => {
|
|
1523
|
-
ctx.
|
|
3021
|
+
ctx.__textLeading = spacing;
|
|
3022
|
+
return ctx.__textLeading;
|
|
1524
3023
|
},
|
|
1525
3024
|
computeFont: (ctx) => () => {
|
|
1526
3025
|
ctx.computeTextStyle();
|
|
@@ -1539,10 +3038,110 @@ var KlintFunctions = {
|
|
|
1539
3038
|
if (ctx.textBaseline !== ctx.__textAlignment.vertical) {
|
|
1540
3039
|
ctx.textBaseline = ctx.__textAlignment.vertical;
|
|
1541
3040
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
ctx.
|
|
3041
|
+
const textString = String(text);
|
|
3042
|
+
if (textString.includes("\n")) {
|
|
3043
|
+
const lines = textString.split("\n");
|
|
3044
|
+
const firstLineMetrics = ctx.measureText(lines[0] || "M");
|
|
3045
|
+
const defaultLineHeight = firstLineMetrics.actualBoundingBoxAscent + firstLineMetrics.actualBoundingBoxDescent;
|
|
3046
|
+
const lineHeight = ctx.__textLeading ?? ctx.__textSize * 1.2;
|
|
3047
|
+
const totalHeight = lines.length * lineHeight - (lineHeight - defaultLineHeight);
|
|
3048
|
+
let startY = y;
|
|
3049
|
+
if (ctx.__textAlignment.vertical === "middle") {
|
|
3050
|
+
startY = y - totalHeight / 2 + defaultLineHeight / 2;
|
|
3051
|
+
} else if (ctx.__textAlignment.vertical === "bottom") {
|
|
3052
|
+
startY = y - totalHeight + defaultLineHeight;
|
|
3053
|
+
} else if (ctx.__textAlignment.vertical === "top") {
|
|
3054
|
+
startY = y + defaultLineHeight / 2;
|
|
3055
|
+
}
|
|
3056
|
+
lines.forEach((line, index) => {
|
|
3057
|
+
const lineY = startY + index * lineHeight;
|
|
3058
|
+
if (ctx.checkTransparency("fill"))
|
|
3059
|
+
ctx.fillText(line, x, lineY, maxWidth);
|
|
3060
|
+
if (ctx.checkTransparency("stroke"))
|
|
3061
|
+
ctx.strokeText(line, x, lineY, maxWidth);
|
|
3062
|
+
});
|
|
3063
|
+
} else {
|
|
3064
|
+
if (ctx.checkTransparency("fill"))
|
|
3065
|
+
ctx.fillText(textString, x, y, maxWidth);
|
|
3066
|
+
if (ctx.checkTransparency("stroke"))
|
|
3067
|
+
ctx.strokeText(textString, x, y, maxWidth);
|
|
3068
|
+
}
|
|
3069
|
+
},
|
|
3070
|
+
paragraph: (ctx) => (text, x, y, width, options) => {
|
|
3071
|
+
if (text === void 0) return;
|
|
3072
|
+
ctx.computeFont();
|
|
3073
|
+
const textString = String(text);
|
|
3074
|
+
const justification = options?.justification || "left";
|
|
3075
|
+
const overflow = options?.overflow || 0;
|
|
3076
|
+
const breakMode = options?.break || "words";
|
|
3077
|
+
const originalAlign = ctx.textAlign;
|
|
3078
|
+
const originalBaseline = ctx.textBaseline;
|
|
3079
|
+
if (justification === "center") {
|
|
3080
|
+
ctx.textAlign = "center";
|
|
3081
|
+
} else if (justification === "right") {
|
|
3082
|
+
ctx.textAlign = "right";
|
|
3083
|
+
} else {
|
|
3084
|
+
ctx.textAlign = "left";
|
|
3085
|
+
}
|
|
3086
|
+
ctx.textBaseline = "top";
|
|
3087
|
+
const tokens = breakMode === "letters" ? textString.split("") : textString.split(/\s+/);
|
|
3088
|
+
const lines = [];
|
|
3089
|
+
let currentLine = "";
|
|
3090
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3091
|
+
const token = tokens[i];
|
|
3092
|
+
const testLine = currentLine ? breakMode === "letters" ? currentLine + token : currentLine + " " + token : token;
|
|
3093
|
+
const metrics = ctx.measureText(testLine);
|
|
3094
|
+
if (metrics.width > width && currentLine !== "") {
|
|
3095
|
+
lines.push(currentLine);
|
|
3096
|
+
currentLine = token;
|
|
3097
|
+
} else {
|
|
3098
|
+
currentLine = testLine;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (currentLine) {
|
|
3102
|
+
lines.push(currentLine);
|
|
3103
|
+
}
|
|
3104
|
+
const lineHeight = ctx.__textLeading ?? ctx.__textSize * 1.2;
|
|
3105
|
+
let linesToDraw = lines;
|
|
3106
|
+
if (overflow > 0 && lines.length * lineHeight > overflow) {
|
|
3107
|
+
const maxLines = Math.floor(overflow / lineHeight);
|
|
3108
|
+
linesToDraw = lines.slice(0, maxLines);
|
|
3109
|
+
}
|
|
3110
|
+
linesToDraw.forEach((line, index) => {
|
|
3111
|
+
const lineY = y + index * lineHeight;
|
|
3112
|
+
let lineX = x;
|
|
3113
|
+
if (justification === "center") {
|
|
3114
|
+
lineX = x + width / 2;
|
|
3115
|
+
} else if (justification === "right") {
|
|
3116
|
+
lineX = x + width;
|
|
3117
|
+
} else if (justification === "justified" && index < linesToDraw.length - 1) {
|
|
3118
|
+
const words = line.split(/\s+/);
|
|
3119
|
+
if (words.length > 1) {
|
|
3120
|
+
const textWidth = ctx.measureText(words.join("")).width;
|
|
3121
|
+
const totalSpaceWidth = width - textWidth;
|
|
3122
|
+
const spaceWidth = totalSpaceWidth / (words.length - 1);
|
|
3123
|
+
let currentX = x;
|
|
3124
|
+
words.forEach((word, wordIndex) => {
|
|
3125
|
+
if (ctx.checkTransparency("fill")) {
|
|
3126
|
+
ctx.fillText(word, currentX, lineY);
|
|
3127
|
+
}
|
|
3128
|
+
if (ctx.checkTransparency("stroke")) {
|
|
3129
|
+
ctx.strokeText(word, currentX, lineY);
|
|
3130
|
+
}
|
|
3131
|
+
currentX += ctx.measureText(word).width + spaceWidth;
|
|
3132
|
+
});
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
if (ctx.checkTransparency("fill")) {
|
|
3137
|
+
ctx.fillText(line, lineX, lineY);
|
|
3138
|
+
}
|
|
3139
|
+
if (ctx.checkTransparency("stroke")) {
|
|
3140
|
+
ctx.strokeText(line, lineX, lineY);
|
|
3141
|
+
}
|
|
3142
|
+
});
|
|
3143
|
+
ctx.textAlign = originalAlign;
|
|
3144
|
+
ctx.textBaseline = originalBaseline;
|
|
1546
3145
|
},
|
|
1547
3146
|
// DO NOT use putImageData for images you can draw : https://www.measurethat.net/Benchmarks/Show/9510/0/putimagedata-vs-drawimage
|
|
1548
3147
|
image: (ctx) => (image, x, y, arg3, arg4, arg5, arg6, arg7, arg8) => {
|
|
@@ -1592,11 +3191,8 @@ var KlintFunctions = {
|
|
|
1592
3191
|
return ctx.getImageData(0, 0, ctx.width, ctx.height);
|
|
1593
3192
|
},
|
|
1594
3193
|
updatePixels: (ctx) => (pixels) => {
|
|
1595
|
-
const
|
|
1596
|
-
|
|
1597
|
-
ctx.width,
|
|
1598
|
-
ctx.height
|
|
1599
|
-
);
|
|
3194
|
+
const pixelArray = pixels instanceof Uint8ClampedArray ? new Uint8ClampedArray(pixels) : new Uint8ClampedArray(pixels);
|
|
3195
|
+
const imageData = new ImageData(pixelArray, ctx.width, ctx.height);
|
|
1600
3196
|
ctx.putImageData(imageData, 0, 0);
|
|
1601
3197
|
},
|
|
1602
3198
|
readPixels: (ctx) => (x, y, w = 1, h = 1) => {
|
|
@@ -1612,7 +3208,7 @@ var KlintFunctions = {
|
|
|
1612
3208
|
ctx.globalAlpha = ctx.constrain(value, 0, 1);
|
|
1613
3209
|
},
|
|
1614
3210
|
blend: (ctx) => (blend) => {
|
|
1615
|
-
ctx.globalCompositeOperation = blend;
|
|
3211
|
+
ctx.globalCompositeOperation = blend === "default" ? "source-over" : blend;
|
|
1616
3212
|
},
|
|
1617
3213
|
setCanvasOrigin: (ctx) => (type) => {
|
|
1618
3214
|
ctx.__canvasOrigin = type;
|
|
@@ -1650,6 +3246,68 @@ var KlintFunctions = {
|
|
|
1650
3246
|
if (ctx.__canvasOrigin === "center") {
|
|
1651
3247
|
ctx.translate(ctx.width * 0.5, ctx.height * 0.5);
|
|
1652
3248
|
}
|
|
3249
|
+
},
|
|
3250
|
+
clipTo: (ctx) => (callback, fillRule) => {
|
|
3251
|
+
const originalFill = ctx.fill;
|
|
3252
|
+
const originalStroke = ctx.stroke;
|
|
3253
|
+
const originalDrawIfVisible = ctx.drawIfVisible;
|
|
3254
|
+
const originalBeginPath = ctx.beginPath;
|
|
3255
|
+
ctx.fill = () => {
|
|
3256
|
+
};
|
|
3257
|
+
ctx.stroke = () => {
|
|
3258
|
+
};
|
|
3259
|
+
ctx.drawIfVisible = () => {
|
|
3260
|
+
};
|
|
3261
|
+
let allowBeginPath = true;
|
|
3262
|
+
ctx.beginPath = () => {
|
|
3263
|
+
if (allowBeginPath) {
|
|
3264
|
+
originalBeginPath.call(ctx);
|
|
3265
|
+
allowBeginPath = false;
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3268
|
+
ctx.beginPath();
|
|
3269
|
+
callback(ctx);
|
|
3270
|
+
ctx.clip(fillRule || ctx.__fillRule || "nonzero");
|
|
3271
|
+
ctx.beginPath = originalBeginPath;
|
|
3272
|
+
ctx.drawIfVisible = originalDrawIfVisible;
|
|
3273
|
+
ctx.fill = originalFill;
|
|
3274
|
+
ctx.stroke = originalStroke;
|
|
3275
|
+
},
|
|
3276
|
+
canIuseFilter: (ctx) => () => {
|
|
3277
|
+
return ctx.filter !== void 0 && ctx.filter !== null;
|
|
3278
|
+
},
|
|
3279
|
+
blur: (ctx) => (radius) => {
|
|
3280
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3281
|
+
ctx.filter = `blur(${radius}px)`;
|
|
3282
|
+
},
|
|
3283
|
+
SVGfilter: (ctx) => (url) => {
|
|
3284
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3285
|
+
if (!url || !url.startsWith("url(")) return;
|
|
3286
|
+
ctx.filter = url;
|
|
3287
|
+
},
|
|
3288
|
+
dropShadow: (ctx) => (offsetX, offsetY, blurRadius, color) => {
|
|
3289
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3290
|
+
ctx.filter = `drop-shadow(${offsetX}px ${offsetY}px ${blurRadius}px ${color})`;
|
|
3291
|
+
},
|
|
3292
|
+
grayscale: (ctx) => (amount) => {
|
|
3293
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3294
|
+
const value = ctx.constrain(amount, 0, 1);
|
|
3295
|
+
ctx.filter = `grayscale(${value})`;
|
|
3296
|
+
},
|
|
3297
|
+
hue: (ctx) => (angle) => {
|
|
3298
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3299
|
+
const degrees = angle * 180 / Math.PI;
|
|
3300
|
+
ctx.filter = `hue-rotate(${degrees}deg)`;
|
|
3301
|
+
},
|
|
3302
|
+
invert: (ctx) => (amount) => {
|
|
3303
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3304
|
+
const value = ctx.constrain(amount, 0, 1);
|
|
3305
|
+
ctx.filter = `invert(${value})`;
|
|
3306
|
+
},
|
|
3307
|
+
filterOpacity: (ctx) => (value) => {
|
|
3308
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3309
|
+
const amount = ctx.constrain(value, 0, 1);
|
|
3310
|
+
ctx.filter = `opacity(${amount})`;
|
|
1653
3311
|
}
|
|
1654
3312
|
};
|
|
1655
3313
|
|
|
@@ -1687,13 +3345,50 @@ var DEFAULT_GESTURE_STATE = {
|
|
|
1687
3345
|
lastX: 0,
|
|
1688
3346
|
lastY: 0
|
|
1689
3347
|
};
|
|
3348
|
+
var DEFAULT_KEYBOARD_STATE = {
|
|
3349
|
+
pressedKeys: /* @__PURE__ */ new Set(),
|
|
3350
|
+
modifiers: {
|
|
3351
|
+
alt: false,
|
|
3352
|
+
shift: false,
|
|
3353
|
+
ctrl: false,
|
|
3354
|
+
meta: false
|
|
3355
|
+
},
|
|
3356
|
+
lastKey: null,
|
|
3357
|
+
lastKeyTime: 0
|
|
3358
|
+
};
|
|
1690
3359
|
function useKlint() {
|
|
1691
3360
|
const contextRef = useRef2(null);
|
|
1692
3361
|
const mouseRef = useRef2(null);
|
|
1693
3362
|
const scrollRef = useRef2(null);
|
|
1694
3363
|
const gestureRef = useRef2(null);
|
|
3364
|
+
const keyboardRef = useRef2(null);
|
|
1695
3365
|
const useDev = () => {
|
|
1696
|
-
|
|
3366
|
+
useEffect2(() => {
|
|
3367
|
+
if (process.env.NODE_ENV === "development") {
|
|
3368
|
+
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
3369
|
+
console.log("[Klint] hot updated - clearing non-context state");
|
|
3370
|
+
if (contextRef.current) {
|
|
3371
|
+
const ctx = contextRef.current;
|
|
3372
|
+
ctx.frame = 0;
|
|
3373
|
+
ctx.time = 0;
|
|
3374
|
+
ctx.__offscreens?.clear();
|
|
3375
|
+
ctx.__startedShape = false;
|
|
3376
|
+
ctx.__currentShape = null;
|
|
3377
|
+
ctx.__startedContour = false;
|
|
3378
|
+
ctx.__currentContours = null;
|
|
3379
|
+
ctx.__currentContour = null;
|
|
3380
|
+
ctx.__isReadyToDraw = true;
|
|
3381
|
+
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
}, []);
|
|
3386
|
+
useEffect2(() => {
|
|
3387
|
+
if (process.env.NODE_ENV === "development" && contextRef.current) {
|
|
3388
|
+
console.log("[Klint] hot updated - clearing context state");
|
|
3389
|
+
contextRef.current.__isReadyToDraw = true;
|
|
3390
|
+
}
|
|
3391
|
+
});
|
|
1697
3392
|
};
|
|
1698
3393
|
const KlintImage = () => {
|
|
1699
3394
|
const imagesRef = useRef2(/* @__PURE__ */ new Map());
|
|
@@ -1767,6 +3462,8 @@ function useKlint() {
|
|
|
1767
3462
|
if (!contextRef.current?.canvas) return;
|
|
1768
3463
|
const canvas = contextRef.current.canvas;
|
|
1769
3464
|
const ctx = contextRef.current;
|
|
3465
|
+
const controller = new AbortController();
|
|
3466
|
+
const { signal } = controller;
|
|
1770
3467
|
const updateMousePosition = (e) => {
|
|
1771
3468
|
const rect = canvas.getBoundingClientRect();
|
|
1772
3469
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -1805,20 +3502,13 @@ function useKlint() {
|
|
|
1805
3502
|
const handleClick = (e) => {
|
|
1806
3503
|
if (clickCallbackRef.current) clickCallbackRef.current(ctx, e);
|
|
1807
3504
|
};
|
|
1808
|
-
canvas.addEventListener("mousemove", updateMousePosition);
|
|
1809
|
-
canvas.addEventListener("mousedown", handleMouseDown);
|
|
1810
|
-
canvas.addEventListener("mouseup", handleMouseUp);
|
|
1811
|
-
canvas.addEventListener("mouseenter", handleMouseEnter);
|
|
1812
|
-
canvas.addEventListener("mouseleave", handleMouseLeave);
|
|
1813
|
-
canvas.addEventListener("click", handleClick);
|
|
1814
|
-
return () =>
|
|
1815
|
-
canvas.removeEventListener("mousemove", updateMousePosition);
|
|
1816
|
-
canvas.removeEventListener("mousedown", handleMouseDown);
|
|
1817
|
-
canvas.removeEventListener("mouseup", handleMouseUp);
|
|
1818
|
-
canvas.removeEventListener("mouseenter", handleMouseEnter);
|
|
1819
|
-
canvas.removeEventListener("mouseleave", handleMouseLeave);
|
|
1820
|
-
canvas.removeEventListener("click", handleClick);
|
|
1821
|
-
};
|
|
3505
|
+
canvas.addEventListener("mousemove", updateMousePosition, { signal });
|
|
3506
|
+
canvas.addEventListener("mousedown", handleMouseDown, { signal });
|
|
3507
|
+
canvas.addEventListener("mouseup", handleMouseUp, { signal });
|
|
3508
|
+
canvas.addEventListener("mouseenter", handleMouseEnter, { signal });
|
|
3509
|
+
canvas.addEventListener("mouseleave", handleMouseLeave, { signal });
|
|
3510
|
+
canvas.addEventListener("click", handleClick, { signal });
|
|
3511
|
+
return () => controller.abort();
|
|
1822
3512
|
});
|
|
1823
3513
|
return {
|
|
1824
3514
|
mouse: mouseRef.current,
|
|
@@ -1838,6 +3528,8 @@ function useKlint() {
|
|
|
1838
3528
|
if (!contextRef.current?.canvas) return;
|
|
1839
3529
|
const canvas = contextRef.current.canvas;
|
|
1840
3530
|
const ctx = contextRef.current;
|
|
3531
|
+
const controller = new AbortController();
|
|
3532
|
+
const { signal } = controller;
|
|
1841
3533
|
const handleScroll = (e) => {
|
|
1842
3534
|
e.preventDefault();
|
|
1843
3535
|
if (!scrollRef.current) return;
|
|
@@ -1850,8 +3542,8 @@ function useKlint() {
|
|
|
1850
3542
|
scrollCallbackRef.current(ctx, scrollRef.current, e);
|
|
1851
3543
|
}
|
|
1852
3544
|
};
|
|
1853
|
-
canvas.addEventListener("wheel", handleScroll);
|
|
1854
|
-
return () =>
|
|
3545
|
+
canvas.addEventListener("wheel", handleScroll, { signal });
|
|
3546
|
+
return () => controller.abort();
|
|
1855
3547
|
});
|
|
1856
3548
|
return {
|
|
1857
3549
|
scroll: scrollRef.current,
|
|
@@ -1997,18 +3689,19 @@ function useKlint() {
|
|
|
1997
3689
|
touchEndCallbackRef.current(ctx, e, gestureRef.current);
|
|
1998
3690
|
}
|
|
1999
3691
|
};
|
|
3692
|
+
const controller = new AbortController();
|
|
3693
|
+
const { signal } = controller;
|
|
2000
3694
|
canvas.addEventListener("touchstart", handleTouchStart, {
|
|
2001
|
-
passive: false
|
|
3695
|
+
passive: false,
|
|
3696
|
+
signal
|
|
2002
3697
|
});
|
|
2003
|
-
canvas.addEventListener("touchmove", handleTouchMove, {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
canvas.removeEventListener("touchcancel", handleTouchCancel);
|
|
2011
|
-
};
|
|
3698
|
+
canvas.addEventListener("touchmove", handleTouchMove, {
|
|
3699
|
+
passive: false,
|
|
3700
|
+
signal
|
|
3701
|
+
});
|
|
3702
|
+
canvas.addEventListener("touchend", handleTouchEnd, { signal });
|
|
3703
|
+
canvas.addEventListener("touchcancel", handleTouchCancel, { signal });
|
|
3704
|
+
return () => controller.abort();
|
|
2012
3705
|
}, []);
|
|
2013
3706
|
return {
|
|
2014
3707
|
gesture: gestureRef.current,
|
|
@@ -2021,6 +3714,315 @@ function useKlint() {
|
|
|
2021
3714
|
onTouchEnd: (callback) => touchEndCallbackRef.current = callback
|
|
2022
3715
|
};
|
|
2023
3716
|
};
|
|
3717
|
+
const KlintKeyboard = () => {
|
|
3718
|
+
if (!keyboardRef.current) {
|
|
3719
|
+
keyboardRef.current = { ...DEFAULT_KEYBOARD_STATE };
|
|
3720
|
+
}
|
|
3721
|
+
const keyPressedCallbackRef = useRef2(/* @__PURE__ */ new Map());
|
|
3722
|
+
const keyReleasedCallbackRef = useRef2(/* @__PURE__ */ new Map());
|
|
3723
|
+
const keyComboCallbackRef = useRef2(/* @__PURE__ */ new Map());
|
|
3724
|
+
useEffect2(() => {
|
|
3725
|
+
if (!contextRef.current) return;
|
|
3726
|
+
const ctx = contextRef.current;
|
|
3727
|
+
const controller = new AbortController();
|
|
3728
|
+
const { signal } = controller;
|
|
3729
|
+
const updateModifiers = (e) => {
|
|
3730
|
+
if (!keyboardRef.current) return;
|
|
3731
|
+
keyboardRef.current.modifiers.alt = e.altKey;
|
|
3732
|
+
keyboardRef.current.modifiers.shift = e.shiftKey;
|
|
3733
|
+
keyboardRef.current.modifiers.ctrl = e.ctrlKey;
|
|
3734
|
+
keyboardRef.current.modifiers.meta = e.metaKey;
|
|
3735
|
+
};
|
|
3736
|
+
const normalizeKey2 = (key) => {
|
|
3737
|
+
const keyMap = {
|
|
3738
|
+
" ": "Space",
|
|
3739
|
+
Control: "Ctrl",
|
|
3740
|
+
Escape: "Esc"
|
|
3741
|
+
};
|
|
3742
|
+
return keyMap[key] || key;
|
|
3743
|
+
};
|
|
3744
|
+
const handleKeyDown = (e) => {
|
|
3745
|
+
if (!keyboardRef.current) return;
|
|
3746
|
+
const normalizedKey = normalizeKey2(e.key);
|
|
3747
|
+
keyboardRef.current.pressedKeys.add(normalizedKey);
|
|
3748
|
+
keyboardRef.current.lastKey = normalizedKey;
|
|
3749
|
+
keyboardRef.current.lastKeyTime = performance.now();
|
|
3750
|
+
updateModifiers(e);
|
|
3751
|
+
const keyCallback = keyPressedCallbackRef.current.get(normalizedKey);
|
|
3752
|
+
if (keyCallback) {
|
|
3753
|
+
keyCallback(ctx, e);
|
|
3754
|
+
}
|
|
3755
|
+
const pressedKeysArray = Array.from(
|
|
3756
|
+
keyboardRef.current.pressedKeys
|
|
3757
|
+
).sort();
|
|
3758
|
+
const comboKey = pressedKeysArray.join("+");
|
|
3759
|
+
const comboCallback = keyComboCallbackRef.current.get(comboKey);
|
|
3760
|
+
if (comboCallback) {
|
|
3761
|
+
comboCallback(ctx, e);
|
|
3762
|
+
}
|
|
3763
|
+
};
|
|
3764
|
+
const handleKeyUp = (e) => {
|
|
3765
|
+
if (!keyboardRef.current) return;
|
|
3766
|
+
const normalizedKey = normalizeKey2(e.key);
|
|
3767
|
+
keyboardRef.current.pressedKeys.delete(normalizedKey);
|
|
3768
|
+
updateModifiers(e);
|
|
3769
|
+
const keyCallback = keyReleasedCallbackRef.current.get(normalizedKey);
|
|
3770
|
+
if (keyCallback) {
|
|
3771
|
+
keyCallback(ctx, e);
|
|
3772
|
+
}
|
|
3773
|
+
};
|
|
3774
|
+
window.addEventListener("keydown", handleKeyDown, { signal });
|
|
3775
|
+
window.addEventListener("keyup", handleKeyUp, { signal });
|
|
3776
|
+
return () => controller.abort();
|
|
3777
|
+
}, []);
|
|
3778
|
+
const createComboKey = (keys) => {
|
|
3779
|
+
return keys.map(normalizeKey).sort().join("+");
|
|
3780
|
+
};
|
|
3781
|
+
const normalizeKey = (key) => {
|
|
3782
|
+
const keyMap = {
|
|
3783
|
+
" ": "Space",
|
|
3784
|
+
Control: "Ctrl",
|
|
3785
|
+
Escape: "Esc"
|
|
3786
|
+
};
|
|
3787
|
+
return keyMap[key] || key;
|
|
3788
|
+
};
|
|
3789
|
+
return {
|
|
3790
|
+
keyboard: keyboardRef.current,
|
|
3791
|
+
// Register callback for single key press
|
|
3792
|
+
keyPressed: (key, callback) => {
|
|
3793
|
+
keyPressedCallbackRef.current.set(normalizeKey(key), callback);
|
|
3794
|
+
},
|
|
3795
|
+
// Register callback for single key release
|
|
3796
|
+
keyReleased: (key, callback) => {
|
|
3797
|
+
keyReleasedCallbackRef.current.set(normalizeKey(key), callback);
|
|
3798
|
+
},
|
|
3799
|
+
// Register callback for key combination (e.g., ['Alt', 'Shift'])
|
|
3800
|
+
keyCombo: (keys, callback) => {
|
|
3801
|
+
const comboKey = createComboKey(keys);
|
|
3802
|
+
keyComboCallbackRef.current.set(comboKey, callback);
|
|
3803
|
+
},
|
|
3804
|
+
// Utility functions
|
|
3805
|
+
isPressed: (key) => {
|
|
3806
|
+
return keyboardRef.current?.pressedKeys.has(normalizeKey(key)) || false;
|
|
3807
|
+
},
|
|
3808
|
+
arePressed: (keys) => {
|
|
3809
|
+
if (!keyboardRef.current) return false;
|
|
3810
|
+
return keys.every(
|
|
3811
|
+
(key) => keyboardRef.current.pressedKeys.has(normalizeKey(key))
|
|
3812
|
+
);
|
|
3813
|
+
},
|
|
3814
|
+
// Clear all callbacks
|
|
3815
|
+
clearCallbacks: () => {
|
|
3816
|
+
keyPressedCallbackRef.current.clear();
|
|
3817
|
+
keyReleasedCallbackRef.current.clear();
|
|
3818
|
+
keyComboCallbackRef.current.clear();
|
|
3819
|
+
}
|
|
3820
|
+
};
|
|
3821
|
+
};
|
|
3822
|
+
const KlintTimeline = () => {
|
|
3823
|
+
const timelinesRef = useRef2(/* @__PURE__ */ new Map());
|
|
3824
|
+
const callbacksRef = useRef2({ start: [], end: [], loop: [] });
|
|
3825
|
+
const Timeline = {
|
|
3826
|
+
create: (setup, options = {}) => {
|
|
3827
|
+
const tracks = /* @__PURE__ */ new Map();
|
|
3828
|
+
const parents = /* @__PURE__ */ new Set();
|
|
3829
|
+
let currentProgress = 0;
|
|
3830
|
+
let hasStarted = false;
|
|
3831
|
+
let hasEnded = false;
|
|
3832
|
+
const defaultEasing = options.defaultEasing || ((t) => t);
|
|
3833
|
+
const defaultLoop = options.defaultLoop || 0;
|
|
3834
|
+
const executeCallback = (callback, errorMsg = "Callback error") => {
|
|
3835
|
+
try {
|
|
3836
|
+
callback();
|
|
3837
|
+
} catch (e) {
|
|
3838
|
+
console.warn(`${errorMsg}:`, e);
|
|
3839
|
+
}
|
|
3840
|
+
};
|
|
3841
|
+
function createKeyframes() {
|
|
3842
|
+
const segments = [];
|
|
3843
|
+
let currentPos = 0;
|
|
3844
|
+
let loopCount = defaultLoop;
|
|
3845
|
+
let parentTrack = void 0;
|
|
3846
|
+
const parseEasingCallback = (easing, callback) => {
|
|
3847
|
+
if (typeof easing === "function" && typeof callback === "undefined") {
|
|
3848
|
+
if (easing.length === 0) {
|
|
3849
|
+
return {
|
|
3850
|
+
easing: defaultEasing,
|
|
3851
|
+
callback: easing
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
return {
|
|
3855
|
+
easing,
|
|
3856
|
+
callback: void 0
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
return {
|
|
3860
|
+
easing: easing || defaultEasing,
|
|
3861
|
+
callback
|
|
3862
|
+
};
|
|
3863
|
+
};
|
|
3864
|
+
const builder = {
|
|
3865
|
+
start(value, delay = 0, callback) {
|
|
3866
|
+
segments.push({ pos: delay, value, callback, type: "start" });
|
|
3867
|
+
currentPos = delay;
|
|
3868
|
+
return builder;
|
|
3869
|
+
},
|
|
3870
|
+
at(progress, value, easing, callback) {
|
|
3871
|
+
const { easing: finalEasing, callback: finalCallback } = parseEasingCallback(easing, callback);
|
|
3872
|
+
segments.push({
|
|
3873
|
+
pos: progress,
|
|
3874
|
+
value,
|
|
3875
|
+
easing: finalEasing,
|
|
3876
|
+
callback: finalCallback,
|
|
3877
|
+
type: "tween"
|
|
3878
|
+
});
|
|
3879
|
+
currentPos = progress;
|
|
3880
|
+
return builder;
|
|
3881
|
+
},
|
|
3882
|
+
then(value, duration, easing, callback) {
|
|
3883
|
+
const { easing: finalEasing, callback: finalCallback } = parseEasingCallback(easing, callback);
|
|
3884
|
+
const nextPos = currentPos + duration;
|
|
3885
|
+
segments.push({
|
|
3886
|
+
pos: nextPos,
|
|
3887
|
+
value,
|
|
3888
|
+
easing: finalEasing,
|
|
3889
|
+
callback: finalCallback,
|
|
3890
|
+
type: "tween"
|
|
3891
|
+
});
|
|
3892
|
+
currentPos = nextPos;
|
|
3893
|
+
return builder;
|
|
3894
|
+
},
|
|
3895
|
+
loop(count = Infinity) {
|
|
3896
|
+
loopCount = count;
|
|
3897
|
+
return builder;
|
|
3898
|
+
},
|
|
3899
|
+
_compile: () => {
|
|
3900
|
+
segments.sort((a, b) => a.pos - b.pos);
|
|
3901
|
+
return { segments, loopCount, parentTrack };
|
|
3902
|
+
}
|
|
3903
|
+
};
|
|
3904
|
+
return builder;
|
|
3905
|
+
}
|
|
3906
|
+
function interpolateTrack(compiled, progress) {
|
|
3907
|
+
const { segments } = compiled;
|
|
3908
|
+
if (!segments.length) return 0;
|
|
3909
|
+
progress = Math.max(0, Math.min(1, progress));
|
|
3910
|
+
const valueSegments = segments.filter(
|
|
3911
|
+
(seg) => seg.type !== "callback"
|
|
3912
|
+
);
|
|
3913
|
+
if (!valueSegments.length) return 0;
|
|
3914
|
+
let prevSeg = valueSegments[0];
|
|
3915
|
+
for (let i = 1; i < valueSegments.length; i++) {
|
|
3916
|
+
const seg = valueSegments[i];
|
|
3917
|
+
if (progress <= seg.pos) {
|
|
3918
|
+
if (seg.type === "tween" && prevSeg.value !== void 0 && seg.value !== void 0) {
|
|
3919
|
+
const t = (progress - prevSeg.pos) / (seg.pos - prevSeg.pos);
|
|
3920
|
+
const easedT = seg.easing ? seg.easing(t) : t;
|
|
3921
|
+
return prevSeg.value + (seg.value - prevSeg.value) * easedT;
|
|
3922
|
+
}
|
|
3923
|
+
return seg.value || 0;
|
|
3924
|
+
}
|
|
3925
|
+
if (seg.value !== void 0) {
|
|
3926
|
+
prevSeg = seg;
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
return prevSeg.value || 0;
|
|
3930
|
+
}
|
|
3931
|
+
const timeline = {
|
|
3932
|
+
track: (keyframesOrFn) => {
|
|
3933
|
+
const compiled = typeof keyframesOrFn === "function" ? timeline.keyframes(keyframesOrFn) : keyframesOrFn;
|
|
3934
|
+
const track = {
|
|
3935
|
+
...compiled,
|
|
3936
|
+
currentValue: 0,
|
|
3937
|
+
getValue: (progress) => interpolateTrack(compiled, progress),
|
|
3938
|
+
get current() {
|
|
3939
|
+
return this.currentValue;
|
|
3940
|
+
},
|
|
3941
|
+
value: function() {
|
|
3942
|
+
return this.currentValue;
|
|
3943
|
+
}
|
|
3944
|
+
};
|
|
3945
|
+
tracks.set(track, compiled);
|
|
3946
|
+
return track;
|
|
3947
|
+
},
|
|
3948
|
+
keyframes: (fn) => {
|
|
3949
|
+
const kf = createKeyframes();
|
|
3950
|
+
fn(kf);
|
|
3951
|
+
return kf._compile();
|
|
3952
|
+
},
|
|
3953
|
+
stagger: (count, offset, keyframesFn) => {
|
|
3954
|
+
const compiled = timeline.keyframes(keyframesFn);
|
|
3955
|
+
return Array.from({ length: count }, (_, i) => {
|
|
3956
|
+
const track = {
|
|
3957
|
+
...compiled,
|
|
3958
|
+
currentValue: 0,
|
|
3959
|
+
staggerDelay: i * offset,
|
|
3960
|
+
getValue: (progress) => {
|
|
3961
|
+
const staggeredProgress = Math.max(0, progress - i * offset);
|
|
3962
|
+
const normalizedProgress = Math.min(
|
|
3963
|
+
1,
|
|
3964
|
+
staggeredProgress / (1 - i * offset)
|
|
3965
|
+
);
|
|
3966
|
+
return normalizedProgress > 0 ? interpolateTrack(compiled, normalizedProgress) : 0;
|
|
3967
|
+
},
|
|
3968
|
+
get current() {
|
|
3969
|
+
return this.currentValue;
|
|
3970
|
+
},
|
|
3971
|
+
value: function() {
|
|
3972
|
+
return this.currentValue;
|
|
3973
|
+
}
|
|
3974
|
+
};
|
|
3975
|
+
tracks.set(track, compiled);
|
|
3976
|
+
return track;
|
|
3977
|
+
});
|
|
3978
|
+
},
|
|
3979
|
+
update: (progress) => {
|
|
3980
|
+
const prevProgress = currentProgress;
|
|
3981
|
+
currentProgress = progress;
|
|
3982
|
+
if (!hasStarted && progress > 0) {
|
|
3983
|
+
hasStarted = true;
|
|
3984
|
+
callbacksRef.current.start.forEach(
|
|
3985
|
+
(cb) => executeCallback(cb, "Start callback error")
|
|
3986
|
+
);
|
|
3987
|
+
}
|
|
3988
|
+
if (!hasEnded && progress >= 1) {
|
|
3989
|
+
hasEnded = true;
|
|
3990
|
+
callbacksRef.current.end.forEach(
|
|
3991
|
+
(cb) => executeCallback(cb, "End callback error")
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
if (progress < prevProgress) {
|
|
3995
|
+
if (progress === 0) {
|
|
3996
|
+
hasStarted = false;
|
|
3997
|
+
hasEnded = false;
|
|
3998
|
+
} else if (progress < 1) {
|
|
3999
|
+
hasEnded = false;
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
for (const [track, compiled] of tracks) {
|
|
4003
|
+
track.currentValue = track.getValue(progress);
|
|
4004
|
+
if (compiled.segments) {
|
|
4005
|
+
compiled.segments.forEach((seg) => {
|
|
4006
|
+
if (seg.callback && seg.pos <= progress && seg.pos > prevProgress) {
|
|
4007
|
+
executeCallback(seg.callback);
|
|
4008
|
+
}
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
},
|
|
4013
|
+
progress: () => currentProgress
|
|
4014
|
+
};
|
|
4015
|
+
const result = setup(timeline);
|
|
4016
|
+
return { ...result, update: timeline.update };
|
|
4017
|
+
}
|
|
4018
|
+
};
|
|
4019
|
+
return {
|
|
4020
|
+
Timeline,
|
|
4021
|
+
onStart: (fn) => callbacksRef.current.start.push(fn),
|
|
4022
|
+
onEnd: (fn) => callbacksRef.current.end.push(fn),
|
|
4023
|
+
onLoop: (fn) => callbacksRef.current.loop.push(fn)
|
|
4024
|
+
};
|
|
4025
|
+
};
|
|
2024
4026
|
const KlintWindow = () => {
|
|
2025
4027
|
const resizeCallbackRef = useRef2(
|
|
2026
4028
|
null
|
|
@@ -2031,6 +4033,8 @@ function useKlint() {
|
|
|
2031
4033
|
useEffect2(() => {
|
|
2032
4034
|
if (!contextRef.current) return;
|
|
2033
4035
|
const ctx = contextRef.current;
|
|
4036
|
+
const controller = new AbortController();
|
|
4037
|
+
const { signal } = controller;
|
|
2034
4038
|
const handleResize = () => {
|
|
2035
4039
|
if (resizeCallbackRef.current) resizeCallbackRef.current(ctx);
|
|
2036
4040
|
};
|
|
@@ -2046,19 +4050,13 @@ function useKlint() {
|
|
|
2046
4050
|
visibilityChangeCallbackRef.current(ctx, isVisible);
|
|
2047
4051
|
}
|
|
2048
4052
|
};
|
|
2049
|
-
window.addEventListener("resize", handleResize);
|
|
2050
|
-
window.addEventListener("blur", handleBlur);
|
|
2051
|
-
window.addEventListener("focus", handleFocus);
|
|
2052
|
-
document.addEventListener("visibilitychange", handleVisibilityChange
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
window.removeEventListener("focus", handleFocus);
|
|
2057
|
-
document.removeEventListener(
|
|
2058
|
-
"visibilitychange",
|
|
2059
|
-
handleVisibilityChange
|
|
2060
|
-
);
|
|
2061
|
-
};
|
|
4053
|
+
window.addEventListener("resize", handleResize, { signal });
|
|
4054
|
+
window.addEventListener("blur", handleBlur, { signal });
|
|
4055
|
+
window.addEventListener("focus", handleFocus, { signal });
|
|
4056
|
+
document.addEventListener("visibilitychange", handleVisibilityChange, {
|
|
4057
|
+
signal
|
|
4058
|
+
});
|
|
4059
|
+
return () => controller.abort();
|
|
2062
4060
|
}, []);
|
|
2063
4061
|
return {
|
|
2064
4062
|
onResize: (callback) => resizeCallbackRef.current = callback,
|
|
@@ -2081,20 +4079,27 @@ function useKlint() {
|
|
|
2081
4079
|
context.__textWeight = "normal";
|
|
2082
4080
|
context.__textStyle = "normal";
|
|
2083
4081
|
context.__textSize = 72;
|
|
4082
|
+
context.__textLeading = void 0;
|
|
2084
4083
|
context.__textAlignment = {
|
|
2085
4084
|
horizontal: "left",
|
|
2086
4085
|
vertical: "top"
|
|
2087
4086
|
};
|
|
4087
|
+
context.__fillRule = "nonzero";
|
|
2088
4088
|
context.__offscreens = /* @__PURE__ */ new Map();
|
|
2089
4089
|
context.__isPlaying = true;
|
|
2090
4090
|
context.__currentContext = context;
|
|
2091
4091
|
context.Color = new Color_default();
|
|
2092
4092
|
context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
|
|
2093
|
-
context.
|
|
2094
|
-
context.
|
|
2095
|
-
context.Time = new Time_default(context);
|
|
4093
|
+
context.Vector = new Vector_default();
|
|
4094
|
+
context.Easing = new Easing_default();
|
|
2096
4095
|
context.Text = new Text_default(context);
|
|
2097
4096
|
context.Thing = new Thing_default(context);
|
|
4097
|
+
context.Grid = new Grid_default(context);
|
|
4098
|
+
context.Strip = new Strip_default(context);
|
|
4099
|
+
context.Noise = new Noise_default(context);
|
|
4100
|
+
context.Hotspot = new Hotspot_default(context);
|
|
4101
|
+
context.Performance = new Performance_default(context);
|
|
4102
|
+
context.SSR = new SSR_default(context);
|
|
2098
4103
|
Object.entries(KlintCoreFunctions).forEach(([name, fn]) => {
|
|
2099
4104
|
context[name] = fn(context);
|
|
2100
4105
|
});
|
|
@@ -2125,6 +4130,30 @@ function useKlint() {
|
|
|
2125
4130
|
contextRef.current.__isPlaying = !contextRef.current.__isPlaying;
|
|
2126
4131
|
}
|
|
2127
4132
|
}, []);
|
|
4133
|
+
const KlintPerformance = () => {
|
|
4134
|
+
const metricsRef = useRef2(null);
|
|
4135
|
+
useEffect2(() => {
|
|
4136
|
+
if (!contextRef.current?.__performance) return;
|
|
4137
|
+
const updateMetrics = () => {
|
|
4138
|
+
if (contextRef.current?.__performance) {
|
|
4139
|
+
metricsRef.current = { ...contextRef.current.__performance };
|
|
4140
|
+
}
|
|
4141
|
+
};
|
|
4142
|
+
const interval = setInterval(updateMetrics, 100);
|
|
4143
|
+
return () => clearInterval(interval);
|
|
4144
|
+
}, []);
|
|
4145
|
+
return {
|
|
4146
|
+
metrics: metricsRef.current,
|
|
4147
|
+
getMetrics: () => contextRef.current?.__performance || null,
|
|
4148
|
+
reset: () => {
|
|
4149
|
+
if (contextRef.current?.__performance) {
|
|
4150
|
+
contextRef.current.__performance.droppedFrames = 0;
|
|
4151
|
+
contextRef.current.__performance.minFrameTime = Infinity;
|
|
4152
|
+
contextRef.current.__performance.maxFrameTime = 0;
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
};
|
|
4156
|
+
};
|
|
2128
4157
|
return {
|
|
2129
4158
|
context: {
|
|
2130
4159
|
context: contextRef.current,
|
|
@@ -2133,8 +4162,11 @@ function useKlint() {
|
|
|
2133
4162
|
KlintMouse,
|
|
2134
4163
|
KlintScroll,
|
|
2135
4164
|
KlintGesture,
|
|
4165
|
+
KlintKeyboard,
|
|
2136
4166
|
KlintWindow,
|
|
2137
4167
|
KlintImage,
|
|
4168
|
+
KlintTimeline,
|
|
4169
|
+
KlintPerformance,
|
|
2138
4170
|
togglePlay,
|
|
2139
4171
|
useDev
|
|
2140
4172
|
};
|
|
@@ -2180,10 +4212,21 @@ var useStorage = (initialProps = {}) => {
|
|
|
2180
4212
|
};
|
|
2181
4213
|
export {
|
|
2182
4214
|
CONFIG_PROPS,
|
|
4215
|
+
Color_default as Color,
|
|
2183
4216
|
EPSILON,
|
|
4217
|
+
Easing_default as Easing,
|
|
4218
|
+
Grid_default as Grid,
|
|
4219
|
+
Hotspot_default as Hotspot,
|
|
2184
4220
|
Klint,
|
|
2185
4221
|
KlintCoreFunctions,
|
|
2186
4222
|
KlintFunctions,
|
|
4223
|
+
Noise_default as Noise,
|
|
4224
|
+
Performance_default as Performance,
|
|
4225
|
+
SSR_default as SSR,
|
|
4226
|
+
Strip_default as Strip,
|
|
4227
|
+
Text_default as Text,
|
|
4228
|
+
Thing_default as Thing,
|
|
4229
|
+
Vector_default as Vector,
|
|
2187
4230
|
useKlint,
|
|
2188
4231
|
useProps,
|
|
2189
4232
|
useStorage
|