@littlecarlito/blorktools 0.50.3 → 0.51.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.
Files changed (114) hide show
  1. package/bin/cli.js +69 -0
  2. package/package.json +13 -7
  3. package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
  4. package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
  5. package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
  6. package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
  7. package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
  8. package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
  9. package/src/asset_debugger/header/header.css +73 -0
  10. package/src/asset_debugger/header/header.html +24 -0
  11. package/src/asset_debugger/header/header.js +224 -0
  12. package/src/asset_debugger/index.html +76 -0
  13. package/src/asset_debugger/landing-page/landing-page.css +396 -0
  14. package/src/asset_debugger/landing-page/landing-page.html +81 -0
  15. package/src/asset_debugger/landing-page/landing-page.js +611 -0
  16. package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
  17. package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
  18. package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
  19. package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
  20. package/src/asset_debugger/main.css +14 -0
  21. package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
  22. package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
  23. package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
  24. package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
  25. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
  26. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
  27. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
  28. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
  29. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
  30. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
  31. package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
  32. package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
  33. package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
  34. package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
  35. package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
  36. package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
  37. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
  38. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
  39. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
  40. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
  41. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
  42. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
  43. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
  44. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
  45. package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
  46. package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
  47. package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
  48. package/src/asset_debugger/router.js +190 -0
  49. package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
  50. package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
  51. package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
  52. package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
  53. package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
  54. package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
  55. package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
  56. package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
  57. package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
  58. package/src/asset_debugger/util/common.css +280 -0
  59. package/src/asset_debugger/util/data/animation-classifier.js +323 -0
  60. package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
  61. package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
  62. package/src/asset_debugger/util/data/glb-classifier.js +290 -0
  63. package/src/asset_debugger/util/data/html-formatter.js +76 -0
  64. package/src/asset_debugger/util/data/html-linter.js +276 -0
  65. package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
  66. package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
  67. package/src/asset_debugger/util/data/string-serder.js +303 -0
  68. package/src/asset_debugger/util/data/texture-classifier.js +663 -0
  69. package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
  70. package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
  71. package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
  72. package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
  73. package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
  74. package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
  75. package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
  76. package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
  77. package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
  78. package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
  79. package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
  80. package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
  81. package/src/asset_debugger/util/rig/rig-controller.js +612 -0
  82. package/src/asset_debugger/util/rig/rig-factory.js +628 -0
  83. package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
  84. package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
  85. package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
  86. package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
  87. package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
  88. package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
  89. package/src/asset_debugger/util/scene/background-manager.js +284 -0
  90. package/src/asset_debugger/util/scene/camera-controller.js +243 -0
  91. package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
  92. package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
  93. package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
  94. package/src/asset_debugger/util/scene/glb-controller.js +208 -0
  95. package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
  96. package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
  97. package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
  98. package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
  99. package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
  100. package/src/asset_debugger/util/scene/ui-manager.js +107 -0
  101. package/src/asset_debugger/util/state/animation-state.js +128 -0
  102. package/src/asset_debugger/util/state/css3d-state.js +83 -0
  103. package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
  104. package/src/asset_debugger/util/state/log-util.js +197 -0
  105. package/src/asset_debugger/util/state/scene-state.js +452 -0
  106. package/src/asset_debugger/util/state/threejs-state.js +54 -0
  107. package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
  108. package/src/asset_debugger/util/workers/model-worker.js +109 -0
  109. package/src/asset_debugger/util/workers/texture-worker.js +54 -0
  110. package/src/asset_debugger/util/workers/worker-manager.js +212 -0
  111. package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
  112. package/src/index.html +261 -0
  113. package/src/index.js +8 -0
  114. package/vite.config.js +66 -0
@@ -0,0 +1,82 @@
1
+ import * as THREE from 'three';
2
+ import { getState, updateState } from '../../state/scene-state.js';
3
+
4
+ /**
5
+ * Create a PBR material with the loaded textures
6
+ * @returns {THREE.MeshStandardMaterial} The created material
7
+ */
8
+ export function createMaterial() {
9
+ const state = getState();
10
+
11
+ // Set proper texture parameters for all textures
12
+ setupTextureParameters();
13
+
14
+ // Create a material configuration with available textures
15
+ const materialConfig = {
16
+ side: THREE.DoubleSide // Make material double-sided
17
+ };
18
+
19
+ // Apply baseColor texture if available
20
+ if (state.textureObjects.baseColor) {
21
+ materialConfig.map = state.textureObjects.baseColor;
22
+ materialConfig.color = 0xffffff; // White color to let the texture show properly
23
+ } else {
24
+ // If no base color texture, use a light gray color
25
+ materialConfig.color = 0xcccccc;
26
+ }
27
+
28
+ // Apply normal map if available
29
+ if (state.textureObjects.normal) {
30
+ materialConfig.normalMap = state.textureObjects.normal;
31
+ materialConfig.normalScale = new THREE.Vector2(1, 1);
32
+ }
33
+
34
+ // Apply ORM texture if available
35
+ if (state.textureObjects.orm) {
36
+ // If we have the ORM texture, apply all its channels
37
+ materialConfig.aoMap = state.textureObjects.orm;
38
+ materialConfig.roughnessMap = state.textureObjects.orm;
39
+ materialConfig.metalnessMap = state.textureObjects.orm;
40
+ // When ORM is available, use its full range
41
+ materialConfig.roughness = 1.0;
42
+ materialConfig.metalness = 1.0;
43
+ } else {
44
+ // If we don't have ORM, use reasonable defaults
45
+ materialConfig.roughness = 0.7;
46
+ materialConfig.metalness = 0.2;
47
+ }
48
+
49
+ // Create material with properly configured textures
50
+ // Explicitly ensure transparency is disabled
51
+ materialConfig.transparent = false;
52
+ materialConfig.alphaTest = 0;
53
+
54
+ return new THREE.MeshStandardMaterial(materialConfig);
55
+ }
56
+
57
+ /**
58
+ * Setup texture parameters for all loaded textures
59
+ */
60
+ function setupTextureParameters() {
61
+ const state = getState();
62
+
63
+ if (state.textureObjects.baseColor) {
64
+ state.textureObjects.baseColor.encoding = THREE.sRGBEncoding;
65
+ state.textureObjects.baseColor.wrapS = THREE.RepeatWrapping;
66
+ state.textureObjects.baseColor.wrapT = THREE.RepeatWrapping;
67
+ }
68
+
69
+ if (state.textureObjects.normal) {
70
+ state.textureObjects.normal.encoding = THREE.LinearEncoding;
71
+ state.textureObjects.normal.wrapS = THREE.RepeatWrapping;
72
+ state.textureObjects.normal.wrapT = THREE.RepeatWrapping;
73
+ }
74
+
75
+ if (state.textureObjects.orm) {
76
+ state.textureObjects.orm.encoding = THREE.LinearEncoding;
77
+ state.textureObjects.orm.wrapS = THREE.RepeatWrapping;
78
+ state.textureObjects.orm.wrapT = THREE.RepeatWrapping;
79
+ }
80
+ }
81
+
82
+
@@ -0,0 +1,280 @@
1
+ /* Blorkvisor-inspired theme */
2
+ :root {
3
+ --primary-color: #4CAF50; /* Terminal green */
4
+ --tool-color: #F1C40F; /* Blorkvisor tool color (yellow) for borders */
5
+ --fallout-yellow: #f8d73e; /* Adjusted to be more yellow and less orange */
6
+ --button-color: #1E88E5; /* Blorkvisor button color (blue) */
7
+ --secondary-color: #1a1a1a; /* Dark background but not pure black */
8
+ --bg-color: #121212; /* Dark background */
9
+ --panel-bg: #1e1e1e; /* Panel background */
10
+ --panel-bg-lighter: #252525; /* Lighter panel background */
11
+ --panel-border: #333; /* Panel border */
12
+ --text-color: #b8b8b8; /* Light gray for regular text */
13
+ --card-border: #333; /* Card border color */
14
+ --input-bg: #252525; /* Input background */
15
+ --input-text: #dadada; /* Input text */
16
+ --label-text: #959595; /* Label text */
17
+ --hover-glow: 0 0 5px rgba(241, 196, 15, 0.7); /* Yellow glow for hover */
18
+ --success-glow: 0 0 5px rgba(76, 175, 80, 0.7); /* Green glow for success */
19
+ --tooltip-bg: rgba(40, 40, 40, 0.95); /* Tooltip background */
20
+ --tooltip-border: #555; /* Tooltip border */
21
+ }
22
+
23
+ html, body {
24
+ margin: 0;
25
+ padding: 0;
26
+ height: 100%;
27
+ overflow: hidden;
28
+ }
29
+
30
+ body {
31
+ font-family: monospace;
32
+ background-color: var(--bg-color);
33
+ color: var(--text-color);
34
+ transition: background-color 0.3s ease, color 0.3s ease;
35
+ }
36
+
37
+ header {
38
+ display: flex;
39
+ justify-content: space-between;
40
+ align-items: center;
41
+ padding: 5px 20px;
42
+ background-color: rgba(26, 26, 26, 0.85);
43
+ color: var(--primary-color);
44
+ border-bottom: 1px solid var(--panel-border);
45
+ position: fixed;
46
+ top: 0;
47
+ left: 0;
48
+ width: 100%;
49
+ box-sizing: border-box;
50
+ z-index: 1000;
51
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
52
+ height: 50px;
53
+ backdrop-filter: blur(5px);
54
+ }
55
+
56
+ h1 {
57
+ margin: 0;
58
+ font-size: 24px;
59
+ color: var(--primary-color);
60
+ }
61
+
62
+ h2 {
63
+ font-size: 18px;
64
+ margin: 5px 0;
65
+ }
66
+
67
+ h3 {
68
+ margin: 0;
69
+ font-size: 16px;
70
+ color: var(--primary-color);
71
+ }
72
+
73
+ h2, h3 {
74
+ margin: 5px 0;
75
+ color: var(--primary-color);
76
+ font-weight: normal;
77
+ }
78
+
79
+
80
+ /* Shared toggle theme */
81
+ .theme-toggle {
82
+ background-color: transparent;
83
+ border: 1px solid var(--button-color);
84
+ color: var(--button-color);
85
+ cursor: pointer;
86
+ font-size: 14px;
87
+ padding: 8px 12px;
88
+ font-family: monospace;
89
+ transition: all 0.3s ease;
90
+ }
91
+
92
+ .theme-toggle:hover {
93
+ background-color: rgba(30, 136, 229, 0.2);
94
+ }
95
+
96
+ .theme-toggle.active {
97
+ background-color: rgba(30, 136, 229, 0.3);
98
+ color: white;
99
+ border-color: var(--button-color);
100
+ }
101
+
102
+
103
+ /* Shared modal styling */
104
+ .modal-overlay {
105
+ position: fixed;
106
+ top: 0;
107
+ left: 0;
108
+ right: 0;
109
+ bottom: 0;
110
+ background-color: rgba(0, 0, 0, 0.7);
111
+ backdrop-filter: blur(3px);
112
+ display: none;
113
+ justify-content: center;
114
+ align-items: center;
115
+ z-index: 2000;
116
+ }
117
+
118
+ .modal-container {
119
+ background-color: var(--panel-bg);
120
+ width: 400px;
121
+ max-width: 90%;
122
+ border: 1px solid var(--panel-border);
123
+ overflow: hidden;
124
+ }
125
+
126
+ .modal-header {
127
+ display: flex;
128
+ justify-content: space-between;
129
+ align-items: center;
130
+ padding: 15px 20px;
131
+ border-bottom: 1px solid var(--panel-border);
132
+ }
133
+
134
+ .modal-title {
135
+ font-weight: bold;
136
+ font-size: 18px;
137
+ margin: 0;
138
+ color: var(--primary-color);
139
+ }
140
+
141
+ .modal-close {
142
+ background: none;
143
+ border: none;
144
+ font-size: 24px;
145
+ cursor: pointer;
146
+ color: var(--text-color);
147
+ }
148
+
149
+ .modal-body {
150
+ padding: 20px;
151
+ max-height: 70vh;
152
+ overflow-y: auto;
153
+ }
154
+
155
+ .modal-footer {
156
+ padding: 15px 20px;
157
+ border-top: 1px solid var(--panel-border);
158
+ display: flex;
159
+ justify-content: flex-end;
160
+ }
161
+
162
+ .modal-btn {
163
+ padding: 8px 16px;
164
+ border: none;
165
+ cursor: pointer;
166
+ margin-left: 10px;
167
+ font-family: monospace;
168
+ transition: all 0.3s ease;
169
+ }
170
+
171
+ .modal-btn-primary {
172
+ background-color: var(--button-color);
173
+ color: white;
174
+ }
175
+
176
+ .modal-btn-secondary {
177
+ background-color: #555;
178
+ color: white;
179
+ }
180
+
181
+ .modal-btn:hover {
182
+ opacity: 0.9;
183
+ }
184
+
185
+
186
+ /* Shared settings styles */
187
+ .settings-group {
188
+ margin-bottom: 20px;
189
+ border: 1px solid var(--panel-border);
190
+ padding: 15px;
191
+ background-color: rgba(0,0,0,0.1);
192
+ }
193
+
194
+ .settings-option {
195
+ flex: 0 0 48%;
196
+ margin-bottom: 12px;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: flex-start;
200
+ }
201
+
202
+ .settings-option label {
203
+ width: 120px;
204
+ margin-left: 10px;
205
+ color: var(--label-text);
206
+ }
207
+
208
+ .settings-select {
209
+ width: 100%;
210
+ padding: 8px;
211
+ border: 1px solid var(--panel-border);
212
+ background-color: var(--input-bg);
213
+ color: var(--input-text);
214
+ font-family: monospace;
215
+ }
216
+
217
+ .settings-slider {
218
+ width: 80%;
219
+ margin-right: 10px;
220
+ background-color: var(--secondary-color);
221
+ border: 1px solid var(--panel-border);
222
+ height: 5px;
223
+ }
224
+
225
+ .slider-container {
226
+ display: flex;
227
+ align-items: center;
228
+ margin-top: 5px;
229
+ }
230
+
231
+
232
+ /* Shared collapsible styles */
233
+ .collapsible-header {
234
+ display: flex;
235
+ justify-content: space-between;
236
+ align-items: center;
237
+ cursor: pointer;
238
+ padding: 4px 0;
239
+ }
240
+
241
+ .collapsible-header .settings-subheading {
242
+ margin-bottom: 0;
243
+ flex: 1;
244
+ }
245
+
246
+ .collapse-indicator {
247
+ font-size: 14px;
248
+ color: #777;
249
+ margin-right: 4px;
250
+ transition: none;
251
+ }
252
+
253
+ .collapsible-header.expanded .collapse-indicator {
254
+ transform: none;
255
+ }
256
+
257
+ /* Shared no image container*/
258
+ .no-image-message-container {
259
+ position: absolute;
260
+ top: 50%;
261
+ left: 50%;
262
+ transform: translate(-50%, -50%);
263
+ text-align: center;
264
+ color: #777;
265
+ font-size: 0.9em;
266
+ font-style: italic;
267
+ padding: 20px;
268
+ background-color: rgba(30, 30, 30, 0.7);
269
+ border-radius: 4px;
270
+ width: 80%;
271
+ display: block;
272
+ }
273
+
274
+ .no-image-message-container.visible {
275
+ display: block;
276
+ }
277
+
278
+ .no-image-message-container.hidden {
279
+ display: none;
280
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Classify the type of animation based on name and initial style
3
+ * @param {string} animationName - The animation name
4
+ * @param {Object} initialStyle - Initial computed style
5
+ * @returns {Object} Classification information
6
+ */
7
+ export function classifyAnimation(animationName, initialStyle) {
8
+ const name = animationName.toLowerCase();
9
+
10
+ // Build a classification object
11
+ const classification = {
12
+ isFadeIn: name.includes('fadein') || (name.includes('fade') && name.includes('in')),
13
+ isFadeOut: name.includes('fadeout') || (name.includes('fade') && name.includes('out')),
14
+ isSlideIn: name.includes('slidein') || (name.includes('slide') && name.includes('in')),
15
+ isSlideOut: name.includes('slideout') || (name.includes('slide') && name.includes('out')),
16
+ isZoomIn: name.includes('zoomin') || (name.includes('zoom') && name.includes('in')),
17
+ isZoomOut: name.includes('zoomout') || (name.includes('zoom') && name.includes('out')),
18
+ isBounce: name.includes('bounce'),
19
+ isRotate: name.includes('rotate'),
20
+ isPulse: name.includes('pulse'),
21
+ isFlash: name.includes('flash'),
22
+ isShake: name.includes('shake'),
23
+ isWobble: name.includes('wobble'),
24
+ isJello: name.includes('jello'),
25
+ isFlip: name.includes('flip'),
26
+ isHeartBeat: name.includes('heartbeat') || name.includes('heart-beat'),
27
+ isHinge: name.includes('hinge'),
28
+ isBlink: name.includes('blink')
29
+ };
30
+
31
+ // Determine animation category
32
+ if (classification.isFadeIn || classification.isFadeOut) {
33
+ classification.category = 'fade';
34
+ } else if (classification.isSlideIn || classification.isSlideOut) {
35
+ classification.category = 'slide';
36
+ } else if (classification.isZoomIn || classification.isZoomOut) {
37
+ classification.category = 'zoom';
38
+ } else if (classification.isRotate) {
39
+ classification.category = 'rotate';
40
+ } else if (classification.isBounce || classification.isPulse || classification.isFlash ||
41
+ classification.isShake || classification.isWobble || classification.isJello ||
42
+ classification.isFlip || classification.isHeartBeat || classification.isHinge ||
43
+ classification.isBlink) {
44
+ classification.category = 'attention';
45
+ } else {
46
+ classification.category = 'other';
47
+ }
48
+
49
+ return classification;
50
+ }
51
+
52
+ /**
53
+ * Classify the type of transition
54
+ * @param {string} property - The CSS property being transitioned
55
+ * @param {string} initialValue - Starting value
56
+ * @param {string} currentValue - Current value during transition
57
+ * @returns {Object} Classification information
58
+ */
59
+ export function classifyTransition(property, initialValue, currentValue) {
60
+ const classification = {
61
+ property: property
62
+ };
63
+
64
+ // Determine transition type based on property
65
+ switch (property) {
66
+ case 'opacity':
67
+ const initialOpacity = parseFloat(initialValue) || 0;
68
+ const currentOpacity = parseFloat(currentValue) || 0;
69
+ classification.isFadeIn = initialOpacity < currentOpacity;
70
+ classification.isFadeOut = initialOpacity > currentOpacity;
71
+ classification.category = 'fade';
72
+ break;
73
+
74
+ case 'transform':
75
+ if (initialValue.includes('scale')) {
76
+ const initialScale = extractScaleValue(initialValue);
77
+ const currentScale = extractScaleValue(currentValue);
78
+ classification.isZoomIn = initialScale < currentScale;
79
+ classification.isZoomOut = initialScale > currentScale;
80
+ classification.category = 'zoom';
81
+ } else if (initialValue.includes('rotate')) {
82
+ classification.isRotate = true;
83
+ classification.category = 'rotate';
84
+ } else if (initialValue.includes('translate')) {
85
+ classification.isMove = true;
86
+ classification.category = 'move';
87
+ } else {
88
+ classification.category = 'transform';
89
+ }
90
+ break;
91
+
92
+ case 'left':
93
+ case 'right':
94
+ case 'top':
95
+ case 'bottom':
96
+ classification.isMove = true;
97
+ classification.category = 'move';
98
+ break;
99
+
100
+ case 'width':
101
+ case 'height':
102
+ const initialSize = parseFloat(initialValue) || 0;
103
+ const currentSize = parseFloat(currentValue) || 0;
104
+ classification.isExpand = initialSize < currentSize;
105
+ classification.isShrink = initialSize > currentSize;
106
+ classification.category = 'size';
107
+ break;
108
+
109
+ default:
110
+ classification.category = 'other';
111
+ }
112
+
113
+ return classification;
114
+ }
115
+
116
+ /**
117
+ * Extract scale value from a transform string
118
+ * @param {string} transform - CSS transform value
119
+ * @returns {number} Extracted scale or 1 if not found
120
+ */
121
+ function extractScaleValue(transform) {
122
+ try {
123
+ const scaleMatch = transform.match(/scale\(([^)]+)\)/);
124
+ if (scaleMatch && scaleMatch[1]) {
125
+ return parseFloat(scaleMatch[1]) || 1;
126
+ }
127
+
128
+ const scale3dMatch = transform.match(/scale3d\(([^,]+),/);
129
+ if (scale3dMatch && scale3dMatch[1]) {
130
+ return parseFloat(scale3dMatch[1]) || 1;
131
+ }
132
+ } catch (e) {}
133
+
134
+ return 1;
135
+ }
136
+
137
+ // Unified animation detection script for both CSS3D and Image2Texture modes
138
+ export function injectUnifiedAnimationDetectionScript(iframe, mode = 'auto') {
139
+ if (!iframe || !iframe.contentDocument) return;
140
+
141
+ const script = iframe.contentDocument.createElement('script');
142
+ script.textContent = `
143
+ // Unified animation detection system
144
+ window.__animationDetection = {
145
+ // Basic counters
146
+ setTimeout: 0,
147
+ setInterval: 0,
148
+ rAF: 0,
149
+ activeTimeouts: 0,
150
+ activeIntervals: 0,
151
+ animationFrameIds: new Set(),
152
+
153
+ // CSS animation tracking
154
+ cssAnimations: new Set(),
155
+ cssTransitions: new Set(),
156
+
157
+ // DOM change tracking
158
+ domChanges: 0,
159
+ lastDomChange: 0,
160
+ styleChanges: false,
161
+
162
+ // Mode configuration
163
+ mode: '${mode}', // 'css3d', 'image2texture', or 'auto'
164
+
165
+ // Advanced detection
166
+ isAnimationLoop: false,
167
+ loopDetectionThreshold: 5
168
+ };
169
+
170
+ const detection = window.__animationDetection;
171
+
172
+ // Override setTimeout
173
+ const originalSetTimeout = window.setTimeout;
174
+ window.setTimeout = function(callback, delay) {
175
+ detection.setTimeout++;
176
+ detection.activeTimeouts++;
177
+ const id = originalSetTimeout.call(this, function() {
178
+ detection.activeTimeouts--;
179
+ if (typeof callback === 'function') callback();
180
+ }, delay);
181
+ return id;
182
+ };
183
+
184
+ // Override setInterval
185
+ const originalSetInterval = window.setInterval;
186
+ window.setInterval = function(callback, delay) {
187
+ detection.setInterval++;
188
+ detection.activeIntervals++;
189
+ return originalSetInterval.call(this, callback, delay);
190
+ };
191
+
192
+ // Override requestAnimationFrame
193
+ const originalRAF = window.requestAnimationFrame;
194
+ window.requestAnimationFrame = function(callback) {
195
+ detection.rAF++;
196
+ const id = originalRAF.call(this, function(timestamp) {
197
+ detection.animationFrameIds.add(id);
198
+ if (typeof callback === 'function') callback(timestamp);
199
+
200
+ // Animation loop detection for image2texture mode
201
+ if (detection.mode === 'image2texture' || detection.mode === 'auto') {
202
+ if (detection.animationFrameIds.size > detection.loopDetectionThreshold) {
203
+ detection.isAnimationLoop = true;
204
+ }
205
+ }
206
+ });
207
+ return id;
208
+ };
209
+
210
+ // CSS Animation and Transition Event Listeners (for CSS3D mode or auto)
211
+ if (detection.mode === 'css3d' || detection.mode === 'auto') {
212
+ // CSS animation events
213
+ document.addEventListener('animationstart', (event) => {
214
+ detection.cssAnimations.add(event.animationName);
215
+ });
216
+
217
+ document.addEventListener('animationend', (event) => {
218
+ detection.cssAnimations.delete(event.animationName);
219
+ });
220
+
221
+ document.addEventListener('animationiteration', (event) => {
222
+ detection.lastDomChange = Date.now();
223
+ });
224
+
225
+ // CSS transition events
226
+ document.addEventListener('transitionstart', (event) => {
227
+ detection.cssTransitions.add(event.propertyName);
228
+ });
229
+
230
+ document.addEventListener('transitionend', (event) => {
231
+ detection.cssTransitions.delete(event.propertyName);
232
+ });
233
+
234
+ document.addEventListener('transitionrun', (event) => {
235
+ detection.lastDomChange = Date.now();
236
+ });
237
+ }
238
+
239
+ // DOM Mutation Observer (enhanced for both modes)
240
+ try {
241
+ const observer = new MutationObserver(mutations => {
242
+ detection.domChanges += mutations.length;
243
+ detection.lastDomChange = Date.now();
244
+
245
+ for (const mutation of mutations) {
246
+ // Check for style or class changes
247
+ if (mutation.type === 'attributes' &&
248
+ (mutation.attributeName === 'style' || mutation.attributeName === 'class')) {
249
+ detection.styleChanges = true;
250
+ }
251
+
252
+ // Check for added/removed nodes (image2texture specific)
253
+ if (detection.mode === 'image2texture' || detection.mode === 'auto') {
254
+ if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) {
255
+ detection.domChanges++;
256
+ }
257
+ }
258
+ }
259
+ });
260
+
261
+ // Configure observer based on mode
262
+ const observerConfig = {
263
+ attributes: true,
264
+ childList: true,
265
+ subtree: true
266
+ };
267
+
268
+ // Add attribute filter for CSS3D mode for better performance
269
+ if (detection.mode === 'css3d') {
270
+ observerConfig.attributeFilter = ['style', 'class'];
271
+ }
272
+
273
+ observer.observe(document.documentElement, observerConfig);
274
+
275
+ } catch (e) {
276
+ console.debug('MutationObserver not available:', e);
277
+ }
278
+
279
+ // Utility methods for external access
280
+ window.__animationDetection.getStatus = function() {
281
+ const now = Date.now();
282
+ return {
283
+ hasActiveTimeouts: detection.activeTimeouts > 0,
284
+ hasActiveIntervals: detection.activeIntervals > 0,
285
+ hasActiveRAF: detection.rAF > 0 && detection.animationFrameIds.size > 0,
286
+ hasCssAnimations: detection.cssAnimations.size > 0,
287
+ hasCssTransitions: detection.cssTransitions.size > 0,
288
+ hasRecentDomChanges: (now - detection.lastDomChange) < 500,
289
+ timeSinceLastChange: now - detection.lastDomChange,
290
+ totalChanges: detection.domChanges,
291
+ isAnimationLoop: detection.isAnimationLoop,
292
+ mode: detection.mode
293
+ };
294
+ };
295
+
296
+ window.__animationDetection.isAnimating = function() {
297
+ const status = this.getStatus();
298
+ return status.hasActiveTimeouts ||
299
+ status.hasActiveIntervals ||
300
+ status.hasActiveRAF ||
301
+ status.hasCssAnimations ||
302
+ status.hasCssTransitions ||
303
+ status.hasRecentDomChanges;
304
+ };
305
+
306
+ window.__animationDetection.reset = function() {
307
+ detection.setTimeout = 0;
308
+ detection.setInterval = 0;
309
+ detection.rAF = 0;
310
+ detection.activeTimeouts = 0;
311
+ detection.activeIntervals = 0;
312
+ detection.animationFrameIds.clear();
313
+ detection.cssAnimations.clear();
314
+ detection.cssTransitions.clear();
315
+ detection.domChanges = 0;
316
+ detection.lastDomChange = 0;
317
+ detection.styleChanges = false;
318
+ detection.isAnimationLoop = false;
319
+ };
320
+ `;
321
+
322
+ iframe.contentDocument.head.appendChild(script);
323
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Deduplicate items by name, counting duplicates
3
+ * @param {Array} items - Array of items to deduplicate
4
+ * @returns {Array} Deduplicated items with count property
5
+ */
6
+ export function deduplicateItemsByName(items) {
7
+ const uniqueItems = new Map();
8
+
9
+ items.forEach(item => {
10
+ const key = item.name;
11
+ if (uniqueItems.has(key)) {
12
+ const existingItem = uniqueItems.get(key);
13
+ existingItem.count = (existingItem.count || 1) + 1;
14
+ } else {
15
+ uniqueItems.set(key, {...item, count: 1});
16
+ }
17
+ });
18
+
19
+ return Array.from(uniqueItems.values());
20
+ }