@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,821 @@
1
+ import { animationStack, reverseAnimationFrameId, setReverseAnimationFrameId } from "../../state/css3d-state";
2
+ /**
3
+ * Pop and play the next animation from the stack in reverse
4
+ * @param {HTMLIFrameElement} iframe - The iframe containing animations
5
+ */
6
+ export function playNextReverseAnimation(iframe) {
7
+ if (animationStack.length === 0) {
8
+ console.debug('No more animations to play in reverse');
9
+ return;
10
+ }
11
+
12
+ // Cancel any pending reverse animation
13
+ if (reverseAnimationFrameId) {
14
+ cancelAnimationFrame(reverseAnimationFrameId);
15
+ setReverseAnimationFrameId(null);
16
+ }
17
+
18
+ // Pop the next animation from the stack
19
+ const nextItem = animationStack.pop();
20
+
21
+ // Check if this is a delay marker
22
+ if (nextItem.type === 'delay') {
23
+ console.debug(`Processing delay of ${nextItem.duration}ms`);
24
+ // For delays, wait the specified time before processing the next animation
25
+ setTimeout(() => {
26
+ playNextReverseAnimation(iframe);
27
+ }, nextItem.duration);
28
+ return;
29
+ }
30
+
31
+ // Check if this is a batch of animations
32
+ if (nextItem.type === 'batch') {
33
+ console.debug(`Playing batch of ${nextItem.animations.length} animations in reverse`);
34
+
35
+ // Process each animation in the batch simultaneously
36
+ const animations = nextItem.animations;
37
+ let maxDuration = 0;
38
+
39
+ // First pass to find elements and calculate max duration
40
+ animations.forEach(animation => {
41
+ // Find the target element for this animation
42
+ const target = findTargetElement(iframe, animation);
43
+ animation._target = target; // Store the found target for use in the second pass
44
+
45
+ // Track the maximum duration in this batch
46
+ const duration = (animation.duration || 0.3) * 1000;
47
+ if (duration > maxDuration) {
48
+ maxDuration = duration;
49
+ }
50
+ });
51
+
52
+ // Second pass to apply all animations simultaneously
53
+ animations.forEach(animation => {
54
+ const target = animation._target;
55
+ if (!target) {
56
+ console.debug(`No element found for animation: ${animation.name || 'unnamed'}`);
57
+ return;
58
+ }
59
+
60
+ try {
61
+ // Apply the reverse animation based on type
62
+ if (animation.type === 'transition') {
63
+ applyReverseTransition(target, animation);
64
+ } else {
65
+ applyReverseAnimation(target, animation);
66
+ }
67
+ } catch (err) {
68
+ console.error('Error applying reverse animation in batch:', err);
69
+ }
70
+ });
71
+
72
+ // Wait for the longest animation in the batch to complete before proceeding
73
+ setTimeout(() => {
74
+ playNextReverseAnimation(iframe);
75
+ }, maxDuration + 50); // Add small buffer
76
+
77
+ return;
78
+ }
79
+
80
+ // For backward compatibility - handle individual animation item
81
+ console.debug(`Playing individual animation in reverse: ${nextItem.name || 'unnamed'}`);
82
+
83
+ // Try multiple strategies to find the element
84
+ const target = findTargetElement(iframe, nextItem);
85
+
86
+ if (!target) {
87
+ console.debug(`No element found for animation: ${nextItem.name}`);
88
+ // Continue with the next animation after a short delay
89
+ setTimeout(() => {
90
+ playNextReverseAnimation(iframe);
91
+ }, 50);
92
+ return;
93
+ }
94
+
95
+ // Apply appropriate reversal based on animation type
96
+ try {
97
+ if (nextItem.type === 'transition') {
98
+ // Handle transitions using stored initial/target values
99
+ applyReverseTransition(target, nextItem);
100
+ } else {
101
+ // Handle CSS animations using more intelligent logic
102
+ applyReverseAnimation(target, nextItem);
103
+ }
104
+
105
+ // Calculate when to play the next animation
106
+ const totalDuration = (nextItem.duration || 0.3) * 1000;
107
+
108
+ // After this animation completes, play the next one
109
+ setTimeout(() => {
110
+ playNextReverseAnimation(iframe);
111
+ }, totalDuration + 50); // Add a small buffer
112
+ } catch (err) {
113
+ console.error('Error applying reverse animation:', err);
114
+ // Continue with the next animation
115
+ setTimeout(() => {
116
+ playNextReverseAnimation(iframe);
117
+ }, 50);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Helper function to find the target element for an animation
123
+ * @param {HTMLIFrameElement} iframe - The iframe containing the document
124
+ * @param {Object} animation - The animation data
125
+ * @returns {Element|null} The found element or null
126
+ */
127
+ function findTargetElement(iframe, animation) {
128
+ if (!iframe || !animation) return null;
129
+
130
+ let target = null;
131
+ try {
132
+ if (iframe.contentDocument) {
133
+ // Strategy 1: Try the stored selector
134
+ if (animation.selector) {
135
+ console.debug(`Finding element with selector: ${animation.selector}`);
136
+ try {
137
+ target = iframe.contentDocument.querySelector(animation.selector);
138
+ } catch (selectorErr) {
139
+ console.debug(`Invalid selector: ${animation.selector}`);
140
+ }
141
+ }
142
+
143
+ // Strategy 2: Try by ID
144
+ if (!target && animation.elementId) {
145
+ console.debug(`Finding element by ID: ${animation.elementId}`);
146
+ target = iframe.contentDocument.getElementById(animation.elementId);
147
+ }
148
+
149
+ // Strategy 3: Try by tag and class
150
+ if (!target && animation.elementTagName) {
151
+ console.debug(`Finding element by tag+class`);
152
+ const className = animation.elementClasses || '';
153
+ const tagName = animation.elementTagName.toLowerCase();
154
+
155
+ // If we have classes, try using them in the selector
156
+ if (className) {
157
+ try {
158
+ // Handle space-separated class names
159
+ const classes = className.split(' ').join('.');
160
+ const complexSelector = classes ? `${tagName}.${classes}` : tagName;
161
+ target = iframe.contentDocument.querySelector(complexSelector);
162
+ } catch (complexErr) {
163
+ console.debug(`Complex selector failed, trying tag name only`);
164
+ }
165
+ }
166
+
167
+ // If still no target, try just by tag name
168
+ if (!target) {
169
+ const elements = iframe.contentDocument.getElementsByTagName(tagName);
170
+ if (elements.length > 0) {
171
+ target = elements[0];
172
+ console.debug(`Finding element by tag name: ${tagName}, found ${elements.length}`);
173
+ }
174
+ }
175
+ }
176
+
177
+ // Strategy 4: For fadeIn/fadeOut elements - try a looser match by animation name
178
+ if (!target && animation.type === 'animation' &&
179
+ animation.name.toLowerCase().includes('fade')) {
180
+ console.debug(`Trying looser match for fade animation`);
181
+
182
+ // Try to find elements that might be visible
183
+ const possibleTargets = iframe.contentDocument.querySelectorAll('div, span, p');
184
+ for (const el of possibleTargets) {
185
+ // If it's visible, it could be our fade target
186
+ const style = window.getComputedStyle(el);
187
+ if (style.display !== 'none' && style.visibility !== 'hidden' &&
188
+ parseFloat(style.opacity) > 0) {
189
+ target = el;
190
+ console.debug(`Found possible fade target: ${el.tagName}`);
191
+ break;
192
+ }
193
+ }
194
+ }
195
+
196
+ // Strategy 5: Check if this is an element that was removed and needs to be re-added
197
+ // Look for removed elements (especially for animations like fadeout, slideout, etc.)
198
+ if (!target) {
199
+ console.debug('This element may have been removed from the DOM, attempting to recreate it');
200
+
201
+ // For elements with a clear parent selector, try to recreate them
202
+ let parentElement = null;
203
+
204
+ // First try to find the parent from stored selectors
205
+ if (animation.parentSelector) {
206
+ parentElement = iframe.contentDocument.querySelector(animation.parentSelector);
207
+ } else if (animation.initialStyle && animation.initialStyle.parentSelector) {
208
+ parentElement = iframe.contentDocument.querySelector(animation.initialStyle.parentSelector);
209
+ } else if (animation.selector) {
210
+ // Try to extract a parent selector from the element's selector
211
+ const selectorParts = animation.selector.split('>');
212
+ if (selectorParts.length > 1) {
213
+ // Remove the last part (the element itself) and join the parent parts
214
+ const parentSelector = selectorParts.slice(0, -1).join('>').trim();
215
+ if (parentSelector) {
216
+ try {
217
+ parentElement = iframe.contentDocument.querySelector(parentSelector);
218
+ console.debug(`Found parent using selector part: ${parentSelector}`);
219
+ } catch (err) {
220
+ console.debug(`Error finding parent with selector: ${parentSelector}`);
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ // If no specific parent found, try fallback to common containers
227
+ if (!parentElement) {
228
+ // For typing indicators, try to find the chat or typing container
229
+ if (animation.name.toLowerCase().includes('blink') ||
230
+ (animation.elementClasses && animation.elementClasses.includes('typing'))) {
231
+ // Look for typing container first
232
+ parentElement = iframe.contentDocument.querySelector('.typing');
233
+
234
+ // If no typing container, look for chat container
235
+ if (!parentElement) {
236
+ parentElement = iframe.contentDocument.querySelector('#chat, .chat, .messages, .conversation');
237
+
238
+ // If chat container found but no typing container, create typing container
239
+ if (parentElement && !iframe.contentDocument.querySelector('.typing')) {
240
+ const typingDiv = iframe.contentDocument.createElement('div');
241
+ typingDiv.className = 'typing';
242
+ parentElement.appendChild(typingDiv);
243
+ parentElement = typingDiv;
244
+ console.debug('Created new typing container in chat');
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ // If we found a parent element, try to recreate the missing element
251
+ if (parentElement) {
252
+ console.debug(`Found parent element ${parentElement.tagName}, recreating removed element`);
253
+
254
+ // Create a new element of the right type
255
+ const newElement = iframe.contentDocument.createElement(
256
+ animation.elementTagName || 'div'
257
+ );
258
+
259
+ // Add any classes it had
260
+ if (animation.elementClasses) {
261
+ newElement.className = animation.elementClasses;
262
+ }
263
+
264
+ // Add ID if it had one
265
+ if (animation.elementId) {
266
+ newElement.id = animation.elementId;
267
+ }
268
+
269
+ // Handle special case for blink animations (like typing indicators)
270
+ if (animation.name.toLowerCase().includes('blink')) {
271
+ // For blink animations, set initial styles and content
272
+ newElement.style.opacity = '0';
273
+
274
+ // If it's a typing indicator dot, add the dot content
275
+ if (animation.selector && animation.selector.includes('span')) {
276
+ newElement.textContent = '•';
277
+ console.debug('Added typing indicator dot content');
278
+ }
279
+ } else {
280
+ // For other animations, start with initial display state
281
+ newElement.style.display = 'none';
282
+ newElement.style.opacity = '0';
283
+ }
284
+
285
+ // Try to position the element correctly in the parent
286
+ // Check the selector to determine position
287
+ let position = 0;
288
+ if (animation.selector) {
289
+ // Extract nth-child information if available
290
+ const nthChildMatch = animation.selector.match(/:nth-child\((\d+)\)/);
291
+ if (nthChildMatch && nthChildMatch[1]) {
292
+ position = parseInt(nthChildMatch[1]) - 1;
293
+ console.debug(`Found position from selector: ${position}`);
294
+ }
295
+ }
296
+
297
+ // Insert at the right position if possible
298
+ if (position > 0 && position < parentElement.children.length) {
299
+ parentElement.insertBefore(newElement, parentElement.children[position]);
300
+ console.debug(`Inserted element at position ${position}`);
301
+ } else {
302
+ // Otherwise append to the end
303
+ parentElement.appendChild(newElement);
304
+ console.debug(`Appended element to parent`);
305
+ }
306
+
307
+ // Use this as our target
308
+ target = newElement;
309
+
310
+ console.debug(`Created new element to replace removed one: ${animation.elementTagName || 'div'}`);
311
+ } else {
312
+ console.debug('Could not find parent element, cannot recreate removed element');
313
+ }
314
+ }
315
+ }
316
+ } catch (err) {
317
+ console.error('Error finding element:', err);
318
+ }
319
+
320
+ return target;
321
+ }
322
+
323
+ /**
324
+ * Apply a reverse CSS animation intelligently
325
+ * @param {Element} target - The target element
326
+ * @param {Object} animation - The animation data
327
+ */
328
+ function applyReverseAnimation(target, animation) {
329
+ if (!target || !animation) return;
330
+
331
+ console.debug(`Found element for animation: ${animation.name}`);
332
+
333
+ const classified = animation.classified || {};
334
+ const initialStyle = animation.initialStyle || {};
335
+ const finalStyle = animation.finalStyle || {};
336
+
337
+ // Use the animation's own duration or default to 0.3s
338
+ const duration = animation.duration || 0.3;
339
+ const timing = animation.timingFunction || 'ease';
340
+
341
+ // Special handling for blink animations (typing indicators)
342
+ if (animation.name.toLowerCase().includes('blink')) {
343
+ console.debug(`Applying reverse for blink animation`);
344
+
345
+ // Show the element immediately since these are usually dots that blink
346
+ target.style.animation = 'none';
347
+ target.style.transition = 'none';
348
+ target.style.opacity = '1';
349
+ target.style.visibility = 'visible';
350
+ target.style.display = 'inline-block';
351
+
352
+ // If this element is part of a typing indicator, ensure other elements are visible too
353
+ const parent = target.parentElement;
354
+ if (parent && parent.classList.contains('typing')) {
355
+ parent.style.display = 'block';
356
+ parent.style.visibility = 'visible';
357
+ parent.style.opacity = '1';
358
+ }
359
+
360
+ // Apply the blink animation in reverse
361
+ void target.offsetWidth; // Force reflow
362
+ target.style.animation = `${animation.name} ${duration}s ${timing} infinite`;
363
+
364
+ // Set a timeout to fade out the typing indicator when we're done with this animation
365
+ setTimeout(() => {
366
+ // Find all the spans in the typing container
367
+ const parent = target.parentElement;
368
+ if (parent) {
369
+ parent.style.transition = `opacity ${duration}s ${timing}`;
370
+ parent.style.opacity = '0';
371
+
372
+ setTimeout(() => {
373
+ parent.style.display = 'none';
374
+ }, duration * 1000);
375
+ }
376
+ }, duration * 1000 * 3); // Show for longer (3x duration) to make it visible
377
+
378
+ return;
379
+ }
380
+
381
+ // Special handling for fades - they need opposite animations
382
+ else if (classified.category === 'fade') {
383
+ if (classified.isFadeIn) {
384
+ // Fade in -> Fade out
385
+ console.debug(`Applying fadeOut for fadeIn animation`);
386
+
387
+ // Stop any existing animations
388
+ target.style.animation = 'none';
389
+ target.style.transition = 'none';
390
+
391
+ // Force reflow
392
+ void target.offsetWidth;
393
+
394
+ // Apply immediate styles without animation first to ensure visibility
395
+ target.style.opacity = '1';
396
+ target.style.visibility = 'visible';
397
+ target.style.display = finalStyle.display || 'block';
398
+
399
+ // Force reflow again
400
+ void target.offsetWidth;
401
+
402
+ // Now set up the fade out transition
403
+ target.style.transition = `opacity ${duration}s ${timing}`;
404
+
405
+ // Use requestAnimationFrame to ensure the browser has time to apply the initial state
406
+ requestAnimationFrame(() => {
407
+ // Set opacity to 0 for fade out
408
+ target.style.opacity = '0';
409
+
410
+ // Set a callback to handle cleanup after the animation
411
+ setTimeout(() => {
412
+ // If the element was originally hidden, restore that state
413
+ if (initialStyle.opacity === '0' || parseFloat(initialStyle.opacity) < 0.1 ||
414
+ initialStyle.visibility === 'hidden' || initialStyle.display === 'none') {
415
+ target.style.visibility = 'hidden';
416
+ target.style.display = 'none';
417
+ }
418
+ }, duration * 1000);
419
+ });
420
+ } else {
421
+ // Fade out -> Fade in
422
+ console.debug(`Applying fadeIn for fadeOut animation`);
423
+
424
+ // Stop any existing animations
425
+ target.style.animation = 'none';
426
+ target.style.transition = 'none';
427
+
428
+ // First check if the element should actually be visible
429
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
430
+ console.debug('Element was originally hidden, keeping it hidden');
431
+
432
+ // Simply ensure the element is hidden
433
+ target.style.opacity = '0';
434
+ target.style.visibility = 'hidden';
435
+ target.style.display = 'none';
436
+ return;
437
+ }
438
+
439
+ // Apply immediate styles without animation first
440
+ target.style.opacity = '0';
441
+ target.style.visibility = 'visible';
442
+ target.style.display = initialStyle.display || 'block';
443
+
444
+ // Force reflow
445
+ void target.offsetWidth;
446
+
447
+ // Now set up the fade in transition
448
+ target.style.transition = `opacity ${duration}s ${timing}`;
449
+
450
+ // Use requestAnimationFrame to ensure the browser has time to apply the initial state
451
+ requestAnimationFrame(() => {
452
+ // Set opacity to 1 for fade in
453
+ target.style.opacity = '1';
454
+ });
455
+ }
456
+ }
457
+ // Special handling for slides
458
+ else if (classified.category === 'slide') {
459
+ if (classified.isSlideIn) {
460
+ // Slide in -> Slide out
461
+ console.debug(`Applying slideOut for slideIn animation`);
462
+
463
+ // Clear existing animations
464
+ target.style.animation = 'none';
465
+ target.style.transition = 'none';
466
+
467
+ // Force reflow
468
+ void target.offsetWidth;
469
+
470
+ // Make sure element is visible first
471
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
472
+ target.style.visibility = 'visible';
473
+ target.style.display = 'block';
474
+ }
475
+
476
+ // Use an opposite animation
477
+ const oppositeAnim = getOppositeAnimation(animation.name, classified);
478
+ target.style.animation = `${oppositeAnim} ${duration}s ${timing} forwards`;
479
+
480
+ // Hide the element after animation is complete if it was originally hidden
481
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
482
+ setTimeout(() => {
483
+ target.style.visibility = 'hidden';
484
+ target.style.display = 'none';
485
+ }, duration * 1000);
486
+ }
487
+ } else {
488
+ // Slide out -> Slide in
489
+ console.debug(`Applying slideIn for slideOut animation`);
490
+
491
+ // Check if element should be visible in its original state
492
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
493
+ console.debug('Element was originally hidden, keeping it hidden');
494
+ target.style.visibility = 'hidden';
495
+ target.style.display = 'none';
496
+ return;
497
+ }
498
+
499
+ // Clear existing animations
500
+ target.style.animation = 'none';
501
+ target.style.transition = 'none';
502
+
503
+ // Force reflow
504
+ void target.offsetWidth;
505
+
506
+ // Ensure the element is visible
507
+ target.style.visibility = 'visible';
508
+ target.style.display = initialStyle.display || 'block';
509
+
510
+ // Use an opposite animation
511
+ const oppositeAnim = getOppositeAnimation(animation.name, classified);
512
+ target.style.animation = `${oppositeAnim} ${duration}s ${timing} forwards`;
513
+ }
514
+ }
515
+ // Special handling for zoom
516
+ else if (classified.category === 'zoom') {
517
+ if (classified.isZoomIn) {
518
+ // Zoom in -> Zoom out
519
+ console.debug(`Applying zoomOut for zoomIn animation`);
520
+
521
+ // Clear existing animations
522
+ target.style.animation = 'none';
523
+ target.style.transition = 'none';
524
+
525
+ // Force reflow
526
+ void target.offsetWidth;
527
+
528
+ // Make sure element is visible first
529
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
530
+ target.style.visibility = 'visible';
531
+ target.style.display = 'block';
532
+ }
533
+
534
+ // Use an opposite animation
535
+ const oppositeAnim = getOppositeAnimation(animation.name, classified);
536
+ target.style.animation = `${oppositeAnim} ${duration}s ${timing} forwards`;
537
+
538
+ // Hide the element after animation is complete if it was originally hidden
539
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
540
+ setTimeout(() => {
541
+ target.style.visibility = 'hidden';
542
+ target.style.display = 'none';
543
+ }, duration * 1000);
544
+ }
545
+ } else {
546
+ // Zoom out -> Zoom in
547
+ console.debug(`Applying zoomIn for zoomOut animation`);
548
+
549
+ // Check if element should be visible in its original state
550
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
551
+ console.debug('Element was originally hidden, keeping it hidden');
552
+ target.style.visibility = 'hidden';
553
+ target.style.display = 'none';
554
+ return;
555
+ }
556
+
557
+ // Clear existing animations
558
+ target.style.animation = 'none';
559
+ target.style.transition = 'none';
560
+
561
+ // Force reflow
562
+ void target.offsetWidth;
563
+
564
+ // Ensure element is visible
565
+ target.style.visibility = 'visible';
566
+ target.style.display = initialStyle.display || 'block';
567
+
568
+ // Use an opposite animation
569
+ const oppositeAnim = getOppositeAnimation(animation.name, classified);
570
+ target.style.animation = `${oppositeAnim} ${duration}s ${timing} forwards`;
571
+ }
572
+ }
573
+ // For other animations, apply a standard reverse
574
+ else {
575
+ // Standard reverse animation
576
+ console.debug(`Applying reverse animation: ${animation.name}`);
577
+
578
+ // Clear existing animations
579
+ target.style.animation = 'none';
580
+ target.style.transition = 'none';
581
+
582
+ // Handle visibility based on initial state
583
+ if (initialStyle.display === 'none' || initialStyle.visibility === 'hidden') {
584
+ // If the element was originally hidden, restore that state
585
+ target.style.visibility = 'hidden';
586
+ target.style.display = 'none';
587
+ } else {
588
+ // Ensure element is visible for the animation
589
+ target.style.visibility = 'visible';
590
+ target.style.display = initialStyle.display || 'block';
591
+
592
+ // Force reflow
593
+ void target.offsetWidth;
594
+
595
+ // Apply the reverse animation
596
+ target.style.animation = `${animation.name} ${duration}s ${timing} reverse forwards`;
597
+ }
598
+ }
599
+
600
+ // Force a reflow to ensure the animation runs
601
+ void target.offsetWidth;
602
+ }
603
+
604
+ /**
605
+ * Create opposite animation for an animation property
606
+ * @param {string} animationName - Original animation name
607
+ * @param {Object} classification - Animation classification
608
+ * @returns {string} Opposite animation name
609
+ */
610
+ function getOppositeAnimation(animationName, classification) {
611
+ if (!animationName) return '';
612
+
613
+ // If we have a valid classification, use it
614
+ if (classification) {
615
+ if (classification.isFadeIn) return animationName.replace(/fadein|fade-in|fade-in/i, 'fadeout');
616
+ if (classification.isFadeOut) return animationName.replace(/fadeout|fade-out|fade-out/i, 'fadein');
617
+ if (classification.isSlideIn) return animationName.replace(/slidein|slide-in|slide-in/i, 'slideout');
618
+ if (classification.isSlideOut) return animationName.replace(/slideout|slide-out|slide-out/i, 'slidein');
619
+ if (classification.isZoomIn) return animationName.replace(/zoomin|zoom-in|zoom-in/i, 'zoomout');
620
+ if (classification.isZoomOut) return animationName.replace(/zoomout|zoom-out|zoom-out/i, 'zoomin');
621
+ }
622
+
623
+ const name = animationName.toLowerCase();
624
+
625
+ // Common animation pairs
626
+ const pairs = {
627
+ 'fadein': 'fadeout',
628
+ 'fade-in': 'fade-out',
629
+ 'fadeout': 'fadein',
630
+ 'fade-out': 'fade-in',
631
+ 'slidein': 'slideout',
632
+ 'slide-in': 'slide-out',
633
+ 'slideout': 'slidein',
634
+ 'slide-out': 'slide-in',
635
+ 'zoomin': 'zoomout',
636
+ 'zoom-in': 'zoom-out',
637
+ 'zoomout': 'zoomin',
638
+ 'zoom-out': 'zoom-in',
639
+ 'rotatein': 'rotateout',
640
+ 'rotate-in': 'rotate-out',
641
+ 'rotateout': 'rotatein',
642
+ 'rotate-out': 'rotate-in',
643
+ 'scalein': 'scaleout',
644
+ 'scale-in': 'scale-out',
645
+ 'scaleout': 'scalein',
646
+ 'scale-out': 'scale-in',
647
+ 'expand': 'collapse',
648
+ 'collapse': 'expand',
649
+ 'show': 'hide',
650
+ 'hide': 'show',
651
+ 'open': 'close',
652
+ 'close': 'open'
653
+ };
654
+
655
+ // Check for each pattern in the animation name
656
+ for (const [pattern, opposite] of Object.entries(pairs)) {
657
+ if (name.includes(pattern)) {
658
+ // Replace the pattern with its opposite
659
+ return animationName.replace(new RegExp(pattern, 'i'), opposite);
660
+ }
661
+ }
662
+
663
+ // If no specific pattern is found, just use reverse
664
+ return animationName;
665
+ }
666
+
667
+ /**
668
+ * Apply a reverse transition intelligently
669
+ * @param {Element} target - The target element
670
+ * @param {Object} transition - The transition data
671
+ */
672
+ function applyReverseTransition(target, transition) {
673
+ if (!target || !transition) return;
674
+
675
+ console.debug(`Applying reverse transition for: ${transition.property}`);
676
+
677
+ const classified = transition.classified || {};
678
+ const property = transition.property;
679
+ const duration = transition.duration || 0.3;
680
+ const timing = transition.timingFunction || 'ease';
681
+ const initialValue = transition.initialValue || null;
682
+ const finalValue = transition.finalValue || null;
683
+ const initialStyle = transition.initialStyle || {};
684
+
685
+ if (!property) {
686
+ console.debug('No property defined for transition');
687
+ return;
688
+ }
689
+
690
+ // First, remove any existing transitions
691
+ target.style.transition = 'none';
692
+
693
+ // Force a reflow to ensure previous transitions are cleared
694
+ void target.offsetWidth;
695
+
696
+ // Set up the new transition
697
+ if (property === 'opacity') {
698
+ // For opacity transitions, handle them specially to ensure visibility
699
+ const currentOpacity = window.getComputedStyle(target).opacity;
700
+ const targetOpacity = initialValue !== null ? initialValue :
701
+ (classified.isFadeIn ? '0' : '1');
702
+
703
+ // Check if the element should be visible in its original state
704
+ const wasOriginallyHidden = initialStyle.display === 'none' ||
705
+ initialStyle.visibility === 'hidden' ||
706
+ parseFloat(initialStyle.opacity) < 0.1;
707
+
708
+ if (parseFloat(currentOpacity) > 0 && parseFloat(targetOpacity) === 0) {
709
+ // Currently visible, going to fade out
710
+ target.style.transition = 'none';
711
+ target.style.visibility = 'visible';
712
+ target.style.opacity = '1';
713
+
714
+ // Force reflow
715
+ void target.offsetWidth;
716
+
717
+ // Now set up the transition
718
+ target.style.transition = `opacity ${duration}s ${timing}`;
719
+
720
+ // Apply the new opacity value using requestAnimationFrame
721
+ requestAnimationFrame(() => {
722
+ target.style.opacity = targetOpacity;
723
+
724
+ // Once fade out is complete, update visibility based on original state
725
+ setTimeout(() => {
726
+ if (wasOriginallyHidden) {
727
+ target.style.visibility = 'hidden';
728
+ target.style.display = 'none';
729
+ }
730
+ }, duration * 1000);
731
+ });
732
+ } else if (parseFloat(currentOpacity) < 0.1) {
733
+ // Currently invisible, but should it fade in?
734
+ if (wasOriginallyHidden) {
735
+ // It was originally hidden, so keep it hidden
736
+ console.debug('Element was originally hidden, keeping it hidden');
737
+ target.style.opacity = '0';
738
+ target.style.visibility = 'hidden';
739
+ target.style.display = 'none';
740
+ } else {
741
+ // It was originally visible, so fade it in
742
+ target.style.transition = 'none';
743
+ target.style.visibility = 'visible';
744
+ target.style.display = 'block';
745
+ target.style.opacity = '0';
746
+
747
+ // Force reflow
748
+ void target.offsetWidth;
749
+
750
+ // Set up transition
751
+ target.style.transition = `opacity ${duration}s ${timing}`;
752
+
753
+ // Apply the new opacity value using requestAnimationFrame
754
+ requestAnimationFrame(() => {
755
+ target.style.opacity = targetOpacity;
756
+ });
757
+ }
758
+ } else {
759
+ // Regular opacity transition
760
+ target.style.transition = `opacity ${duration}s ${timing}`;
761
+ // Force reflow
762
+ void target.offsetWidth;
763
+ // Apply the opacity value
764
+ target.style.opacity = targetOpacity;
765
+ }
766
+ } else {
767
+ // For non-opacity transitions, use a simpler approach
768
+ target.style.transition = `${property} ${duration}s ${timing}`;
769
+
770
+ // Force a reflow
771
+ void target.offsetWidth;
772
+
773
+ // For transitions, we want to go back to the initial value
774
+ if (initialValue) {
775
+ // If we have a valid initial value, use it
776
+ console.debug(`Reversing ${property} to initial value: ${initialValue}`);
777
+ requestAnimationFrame(() => {
778
+ target.style[property] = initialValue;
779
+ });
780
+ } else {
781
+ // Handle based on the property type
782
+ switch (classified.category) {
783
+ case 'move':
784
+ // Move transitions typically use position properties
785
+ requestAnimationFrame(() => {
786
+ target.style[property] = '0';
787
+ });
788
+ break;
789
+
790
+ case 'size':
791
+ if (classified.isExpand) {
792
+ // Was expanding, now shrink
793
+ requestAnimationFrame(() => {
794
+ target.style[property] = '0';
795
+ });
796
+ } else {
797
+ // Was shrinking, now expand
798
+ requestAnimationFrame(() => {
799
+ target.style[property] = 'auto';
800
+ });
801
+ }
802
+ break;
803
+
804
+ case 'transform':
805
+ requestAnimationFrame(() => {
806
+ target.style.transform = 'none';
807
+ });
808
+ break;
809
+
810
+ default:
811
+ // Reset to a sensible default
812
+ console.debug(`Using default reversal for ${property}`);
813
+ if (target.getAttribute(`data-initial-${property}`)) {
814
+ requestAnimationFrame(() => {
815
+ target.style[property] = target.getAttribute(`data-initial-${property}`);
816
+ });
817
+ }
818
+ }
819
+ }
820
+ }
821
+ }