(() => {
	const init = (rawScope = document) => {
		const scope = rawScope && rawScope.jquery ? rawScope[0] : rawScope;
		const initPopovers = (root) => {
			const popovers = root.querySelectorAll('[data-tooto-popover]');
			popovers.forEach((wrap) => {
				if (wrap.dataset.tootoInit === '1') return;
				wrap.dataset.tootoInit = '1';

				const trigger = wrap.querySelector('[data-tooto-popover-trigger]');
				const panel = wrap.querySelector('[data-tooto-popover-panel]');
				const arrow = wrap.querySelector('[data-tooto-popover-arrow]');
				if (!trigger || !panel) return;

				const cfg = {
					trigger: wrap.dataset.trigger || 'click',
					placement: wrap.dataset.placement || 'bottom',
					align: wrap.dataset.align || 'center',
					offset: parseInt(wrap.dataset.offset || '8', 10),
					appendToBody: wrap.dataset.appendToBody === 'true',
					closeOnOutside: wrap.dataset.closeOnOutside === 'true',
					closeOnEsc: wrap.dataset.closeOnEsc === 'true',
					hoverOpenDelay: parseInt(wrap.dataset.hoverOpenDelay || '80', 10),
					hoverCloseDelay: parseInt(wrap.dataset.hoverCloseDelay || '120', 10),
				};

				let open = false;
				let placeholder = null;
				let hoverShowTimer = null;
				let hoverHideTimer = null;

				const setPanelAttr = (isOpen) => {
					panel.classList.toggle('is-open', isOpen);
					panel.setAttribute('aria-hidden', isOpen ? 'false' : 'true');
				};

				const clamp = (v, min, max) => Math.max(min, Math.min(v, max));

				const pickPlacement = (pref, rect, pRect) => {
					if (pref !== 'auto') return pref;
					const spaces = {
						top: rect.top,
						bottom: window.innerHeight - rect.bottom,
						left: rect.left,
						right: window.innerWidth - rect.right,
					};
					const want = { top: pRect.height, bottom: pRect.height, left: pRect.width, right: pRect.width };
					const fits = Object.keys(spaces).filter((k) => spaces[k] >= want[k] + cfg.offset);
					if (fits.length) {
						return fits.sort((a, b) => spaces[b] - spaces[a])[0];
					}
					return Object.keys(spaces).sort((a, b) => spaces[b] - spaces[a])[0];
				};

				const position = () => {
					if (!open) return;
					const padding = 8;
					const rect = trigger.getBoundingClientRect();

					// 先显示以便量尺寸
					panel.style.visibility = 'hidden';
					panel.style.display = 'block';
					panel.style.position = 'fixed';
					panel.style.left = '0px';
					panel.style.top = '0px';

					const pRect = panel.getBoundingClientRect();
					let placement = pickPlacement(cfg.placement, rect, pRect);

					// 简单翻转
					const flipIfNeeded = () => {
						if (placement === 'top' && rect.top < pRect.height + cfg.offset) placement = 'bottom';
						if (placement === 'bottom' && window.innerHeight - rect.bottom < pRect.height + cfg.offset) placement = 'top';
						if (placement === 'left' && rect.left < pRect.width + cfg.offset) placement = 'right';
						if (placement === 'right' && window.innerWidth - rect.right < pRect.width + cfg.offset) placement = 'left';
					};
					flipIfNeeded();

					let top = 0;
					let left = 0;
					if (placement === 'top') {
						top = rect.top - pRect.height - cfg.offset;
						if (cfg.align === 'start') left = rect.left;
						else if (cfg.align === 'end') left = rect.right - pRect.width;
						else left = rect.left + rect.width / 2 - pRect.width / 2;
					} else if (placement === 'bottom') {
						top = rect.bottom + cfg.offset;
						if (cfg.align === 'start') left = rect.left;
						else if (cfg.align === 'end') left = rect.right - pRect.width;
						else left = rect.left + rect.width / 2 - pRect.width / 2;
					} else if (placement === 'left') {
						if (cfg.align === 'start') top = rect.top;
						else if (cfg.align === 'end') top = rect.bottom - pRect.height;
						else top = rect.top + rect.height / 2 - pRect.height / 2;
						left = rect.left - pRect.width - cfg.offset;
					} else if (placement === 'right') {
						if (cfg.align === 'start') top = rect.top;
						else if (cfg.align === 'end') top = rect.bottom - pRect.height;
						else top = rect.top + rect.height / 2 - pRect.height / 2;
						left = rect.right + cfg.offset;
					}

					left = clamp(left, padding, window.innerWidth - pRect.width - padding);
					top = clamp(top, padding, window.innerHeight - pRect.height - padding);

					panel.style.left = `${left}px`;
					panel.style.top = `${top}px`;
					panel.dataset.placement = placement;

					// 箭头位置（简化版）
					if (arrow) {
						const centerX = rect.left + rect.width / 2 - left;
						const centerY = rect.top + rect.height / 2 - top;
						if (placement === 'top' || placement === 'bottom') {
							arrow.style.left = `${clamp(centerX, 12, pRect.width - 12)}px`;
							arrow.style.top = '';
						} else {
							arrow.style.top = `${clamp(centerY, 12, pRect.height - 12)}px`;
							arrow.style.left = '';
						}
					}

					panel.style.visibility = 'visible';
				};

				const attachToBodyIfNeeded = () => {
					if (!cfg.appendToBody) return;
					if (!placeholder) {
						placeholder = document.createComment('tooto-popover-placeholder');
						wrap.insertBefore(placeholder, panel);
					}
					document.body.appendChild(panel);
				};

				const restoreFromBody = () => {
					if (!cfg.appendToBody) return;
					if (placeholder && placeholder.parentNode) {
						placeholder.parentNode.insertBefore(panel, placeholder);
						placeholder.parentNode.removeChild(placeholder);
						placeholder = null;
					}
				};

				const openPanel = () => {
					if (open) return;
					open = true;
					attachToBodyIfNeeded();
					setPanelAttr(true);
					position();
					window.addEventListener('scroll', position, true);
					window.addEventListener('resize', position);
				};

				const closePanel = () => {
					if (!open) return;
					open = false;
					setPanelAttr(false);
					panel.style.display = '';
					panel.style.left = '';
					panel.style.top = '';
					panel.style.visibility = '';
					window.removeEventListener('scroll', position, true);
					window.removeEventListener('resize', position);
					restoreFromBody();
				};

				const toggle = () => (open ? closePanel() : openPanel());

				if (cfg.trigger === 'click') {
					trigger.addEventListener('click', (e) => {
						e.preventDefault();
						e.stopPropagation();
						toggle();
					});
					panel.addEventListener('click', (e) => e.stopPropagation());

					if (cfg.closeOnOutside) {
						document.addEventListener('click', (e) => {
							if (!open) return;
							if (wrap.contains(e.target) || panel.contains(e.target)) return;
							closePanel();
						});
					}
				} else {
					const scheduleOpen = () => {
						clearTimeout(hoverHideTimer);
						hoverShowTimer = setTimeout(openPanel, cfg.hoverOpenDelay);
					};
					const scheduleClose = () => {
						clearTimeout(hoverShowTimer);
						hoverHideTimer = setTimeout(closePanel, cfg.hoverCloseDelay);
					};
					trigger.addEventListener('mouseenter', scheduleOpen);
					trigger.addEventListener('mouseleave', scheduleClose);
					panel.addEventListener('mouseenter', () => {
						clearTimeout(hoverHideTimer);
					});
					panel.addEventListener('mouseleave', scheduleClose);
				}

				if (cfg.closeOnEsc) {
					document.addEventListener('keydown', (e) => {
						if (!open) return;
						if (e.key === 'Escape') closePanel();
					});
				}

				// 初始状态
				setPanelAttr(false);
			});
		};

		const initImageCarousel = (root) => {
			const carousels = root.querySelectorAll('.tooto-image-carousel, .tooto-simple-splide-widget');
			carousels.forEach((carousel) => {
				if (carousel.dataset.tootoInit === '1') return;
				carousel.dataset.tootoInit = '1';

				if (!window.Splide) return;

				const config = JSON.parse(carousel.dataset.splide || '{}');
				
				// Mount Extensions if available (AutoScroll)
			const extensions = window.splide && window.splide.Extensions ? window.splide.Extensions : {};

			const splide = new window.Splide(carousel, config);
			splide.mount(extensions);

			// Custom Pause on Hover for Track Only (fixes issue where pause triggers on the whole wrapper)
			if (carousel.dataset.tootoPauseOnHover === 'yes') {
				const track = carousel.querySelector('.splide__track');
				if (track) {
					track.addEventListener('mouseenter', () => {
						if (splide.Components.AutoScroll) {
							splide.Components.AutoScroll.pause();
						}
					});
					track.addEventListener('mouseleave', () => {
						if (splide.Components.AutoScroll) {
							splide.Components.AutoScroll.play();
						}
					});
				}
			}
			
			// Brand Details Interaction
			if (carousel.classList.contains('tooto-image-carousel--brand-details')) {
				const container = carousel.querySelector('.tooto-brand-details-container');
				const titleEl = container.querySelector('.tooto-brand-title');
				const subtitleEl = container.querySelector('.tooto-brand-subtitle');
				const descEl = container.querySelector('.tooto-brand-description');
				const linkEl = container.querySelector('.tooto-brand-link');
				const dots = container.querySelectorAll('.tooto-brand-dot');
				
				const updateDetails = (slideIndex) => {
					// Splide clones slides for loop, so we need to handle index carefully
					// But for clicking, we can rely on the slide element's data attributes
					// For pagination, we need the real index.
					
					// Get the active slide object from Splide
					// Note: Splide's 'active' index might refer to clones. 
					// We should rely on the click event or the active index from splide API.
				};
				
				// Function to update content based on a slide element
				const updateContent = (slideElement) => {
					if (!slideElement) return;
					
					const title = slideElement.dataset.brandTitle || '';
					const subtitle = slideElement.dataset.brandSubtitle || '';
					const desc = slideElement.dataset.brandDescription || '';
					const linkUrl = slideElement.dataset.brandLinkUrl || '';
					const linkText = slideElement.dataset.brandLinkText || 'Learn More';
					const isExternal = slideElement.dataset.brandIsExternal === 'true';
					const nofollow = slideElement.dataset.brandNofollow === 'true';
					
					// Update text
					if (titleEl) titleEl.textContent = title;
					if (subtitleEl) {
						subtitleEl.textContent = subtitle;
						subtitleEl.style.display = subtitle ? 'inline-block' : 'none';
					}
					if (descEl) descEl.textContent = desc;
					
					if (linkEl) {
						if (linkUrl) {
							linkEl.href = linkUrl;
							linkEl.textContent = linkText;
							linkEl.style.display = 'inline-block';
							if (isExternal) linkEl.target = '_blank';
							else linkEl.removeAttribute('target');
							
							if (nofollow) linkEl.rel = 'nofollow';
							else linkEl.removeAttribute('rel');
						} else {
							linkEl.style.display = 'none';
						}
					}
				};

				// Handle Slide Click
				splide.on('click', (slide) => {
					// slide is the Slide object
					updateContent(slide.slide);
					
					// Update active class on slides
					carousel.querySelectorAll('.splide__slide').forEach(s => s.classList.remove('is-active-brand'));
					slide.slide.classList.add('is-active-brand');
					
					// Update dots
					// slide.index is the index of the slide (0 to length-1)
					dots.forEach((dot, index) => {
						if (index === slide.index) dot.classList.add('is-active');
						else dot.classList.remove('is-active');
					});
				});
				
				// Handle Dot Click
				dots.forEach((dot, index) => {
					dot.addEventListener('click', () => {
						// We want to "select" the slide at index.
						// Since it's a marquee, we can't really "go to" it in a static way easily without stopping the scroll.
						// But the user interaction is "Click different brand logo below shows his info".
						// Maybe clicking the dot just updates the info?
						
						// Find the first slide with this index (excluding clones if possible, or just the first one)
						// Splide slides are 0-indexed matching the data source.
						
						// We can use the Components.Slides to find the slide by index
						const targetSlide = splide.Components.Slides.getAt(index);
						if (targetSlide) {
							updateContent(targetSlide.slide);
							
							// Update active class
							carousel.querySelectorAll('.splide__slide').forEach(s => s.classList.remove('is-active-brand'));
							// We need to find all slides that represent this index (including clones)
							// But for simplicity, let's just highlight the visible ones or all matching
							// A simpler way: loop all slides and check if they match the index?
							// Splide doesn't expose original index on DOM element easily unless we add it.
							// But we can just rely on the fact that we updated the content.
							
							// Let's just manually trigger the 'click' logic or reuse it.
							// Visually highlighting the specific slide in the marquee might be tricky if there are clones.
							
							// Update dots
							dots.forEach(d => d.classList.remove('is-active'));
							dot.classList.add('is-active');
						}
					});
				});

				// Initialize with the first slide
				const initActiveState = () => {
					const firstSlide = splide.Components.Slides.getAt(0);
					if (firstSlide) {
						updateContent(firstSlide.slide);
						
						// Highlight first slide
						// In loop mode, there might be clones. We should highlight the one that is logically first.
						// Or just all of them.
						carousel.querySelectorAll('.splide__slide').forEach(s => s.classList.remove('is-active-brand'));
						firstSlide.slide.classList.add('is-active-brand');
						
						// Highlight first dot
						dots.forEach(d => d.classList.remove('is-active'));
						if(dots[0]) dots[0].classList.add('is-active');
					}
				};

				// Run init immediately
				initActiveState();
				
				// Also listen to mounted just in case (though mount is sync usually)
				splide.on('mounted', initActiveState);
			}
			});
		};

		const initLoopCarousel = (root) => {
			const carousels = root.querySelectorAll('.tooto-loop-carousel');

			carousels.forEach((carousel) => {
				// Initialize resizing logic
				if (!carousel.dataset.tootoResizeBound) {
					const handleResize = () => {
						const isMobile = window.innerWidth < 768; // Standard Elementor Mobile Breakpoint
						const mobileBehavior = carousel.dataset.mobileBehavior;
						const wantedMode = (isMobile && mobileBehavior === 'load_more') ? 'load_more' : 'carousel';
						const currentMode = carousel.dataset.activeMode;

						if (currentMode === wantedMode) return;

						// Destroy previous mode
						if (currentMode === 'carousel') {
							if (carousel.splideInstance) {
								carousel.splideInstance.destroy(false); // false = keep markup
								carousel.splideInstance = null;
							}
						} else if (currentMode === 'load_more') {
							destroyLoadMore(carousel);
						}

						// Init new mode
						if (wantedMode === 'load_more') {
							initLoadMore(carousel);
						} else {
							initSplide(carousel);
						}

						carousel.dataset.activeMode = wantedMode;
					};

					// Debounce resize
					let resizeTimer;
					window.addEventListener('resize', () => {
						clearTimeout(resizeTimer);
						resizeTimer = setTimeout(handleResize, 200);
					});

					// Initial Run
					handleResize();
					carousel.dataset.tootoResizeBound = '1';
				} else {
                    // Already bound, but maybe we need to run once if content refreshed?
                    // Usually not needed if element is same.
                }
			});
		};

		const initSplide = (carousel) => {
			if (!window.Splide) return;
			const config = JSON.parse(carousel.dataset.splide || '{}');
			const extensions = window.splide && window.splide.Extensions ? window.splide.Extensions : {};
			const splide = new window.Splide(carousel, config);
			splide.mount(extensions);
			carousel.splideInstance = splide;
		};

		const initLoadMore = (carousel) => {
			const container = carousel.querySelector('.splide__list') || carousel;
			let items = container.querySelectorAll('.splide__slide');
            // Fallback for items selector
            if (!items.length) {
                items = container.querySelectorAll('.elementor-loop-item');
            }
            
            // Look for button in siblings more robustly
            let btnContainer = null;
            let sibling = carousel.nextElementSibling;
            while (sibling) {
                if (sibling.classList && sibling.classList.contains('tooto-mobile-load-more-container')) {
                    btnContainer = sibling;
                    break;
                }
                sibling = sibling.nextElementSibling;
            }

            if (!btnContainer) {
                // Fallback: search in parent (less reliable but fallback)
                btnContainer = carousel.parentNode.querySelector('.tooto-mobile-load-more-container');
            }

			const btn = btnContainer ? btnContainer.querySelector('.tooto-mobile-load-more-btn') : null;

			if (!items.length || !btn) return;

			// Show button container
			if (btnContainer) btnContainer.style.display = 'block';

			const initialCount = 4;
			const increment = 4;

			// Hide items beyond initialCount
			items.forEach((item, index) => {
				if (index >= initialCount) {
					item.style.setProperty('display', 'none', 'important');
				} else {
                    item.style.removeProperty('display'); // Ensure visible
                }
			});

			// If total items <= initialCount, hide button
			if (items.length <= initialCount) {
				if (btnContainer) btnContainer.style.display = 'none';
			} else {
                // Ensure button is visible if we have more items
                if (btnContainer) btnContainer.style.display = 'block';
            }

			let visibleCount = initialCount;

			// Store handler to remove it later if needed? 
            // Simplified: just add event listener. Duplicate listeners might be issue if init called multiple times?
            // handleResize handles "only if mode changed", so initLoadMore calls once per session usually.
            // But if user resizes back and forth, we might add multiple listeners.
            // Better to clone button or remove listener.
            
            const newBtn = btn.cloneNode(true);
            btn.parentNode.replaceChild(newBtn, btn);
            
			newBtn.addEventListener('click', (e) => {
				e.preventDefault();
				let newCount = visibleCount + increment;

				items.forEach((item, index) => {
					if (index >= visibleCount && index < newCount) {
						item.style.setProperty('display', '', ''); // Remove display:none
                        // Also remove !important if present?
                        item.style.display = ''; 
					}
				});

				visibleCount = newCount;

				// If all items shown, hide button
				if (visibleCount >= items.length) {
					if (btnContainer) btnContainer.style.display = 'none';
				}
			});
		};

        const destroyLoadMore = (carousel) => {
            const container = carousel.querySelector('.splide__list') || carousel;
			const items = container.querySelectorAll('.splide__slide');
            
             // Look for button as next sibling
            let btnContainer = null;
            let next = carousel.nextElementSibling;
            if (next && next.classList.contains('tooto-mobile-load-more-container')) {
                btnContainer = next;
            } else {
                btnContainer = carousel.parentNode.querySelector('.tooto-mobile-load-more-container');
            }

            // Show all items
            items.forEach(item => {
                item.style.display = '';
                item.style.removeProperty('display');
            });

            // Hide button
            if (btnContainer) btnContainer.style.display = 'none';
        };

		// Popover 需要独立初始化（页面可能只有 Popover，没有 Solutions Tabs）
		initPopovers(scope);
		initImageCarousel(scope);
		initLoopCarousel(scope);

		// Init Text Type manually if not handled by Elementor hook
		const initTextType = (root) => {
			const wrappers = root.querySelectorAll('.tooto-text-type');
			wrappers.forEach((wrapper) => {
				// Prevent double init
				if (wrapper.dataset.tootoInit === '1') return;
				wrapper.dataset.tootoInit = '1';

				if (window.jQuery) {
					const $ = window.jQuery;
					const $wrapper = $(wrapper);
					const $widget = $wrapper.closest('.elementor-widget');
					if ($widget.length) {
						// Ensure TextTypeWidget is defined
						if (typeof TextTypeWidget !== 'undefined') {
							new TextTypeWidget($widget);
						}
					}
				}
			});
		};
		initTextType(scope);

		const widgets = scope.querySelectorAll('[data-tooto-solutions-tabs]');
		if (!widgets.length) return;

		widgets.forEach((widget) => {
			const tabs = widget.querySelectorAll('.tooto-solutions-tabs__tab');
			const tabsWrap = widget.querySelector('.tooto-solutions-tabs__tabs');
			const swiperEl = widget.querySelector('.tooto-solutions-tabs__swiper');
			const productsRoot = widget.querySelector('[data-tooto-products]');

			if (!swiperEl) return;

			const navPrev = widget.querySelector('[data-tooto-solutions-nav="prev"]');
			const navNext = widget.querySelector('[data-tooto-solutions-nav="next"]');

			const getIndex = (instance) => (typeof instance.realIndex === 'number' ? instance.realIndex : instance.activeIndex);

			const setActive = (index) => {
				const safeIndex = Math.max(0, Math.min(index, tabs.length - 1));
				tabs.forEach((tab, i) => {
					const active = i === safeIndex;
					tab.classList.toggle('is-active', active);
					tab.setAttribute('aria-selected', active ? 'true' : 'false');
				});

				if (tabsWrap && tabs[safeIndex]) {
					tabs[safeIndex].scrollIntoView({ inline: 'center', block: 'nearest', behavior: 'smooth' });
				}

				if (productsRoot) {
					const productsData = productsRoot.dataset.products ? JSON.parse(productsRoot.dataset.products) : [];
					const list = productsData[safeIndex] || [];
					renderProducts(productsRoot, list);
				}
			};

			const renderProducts = (root, list) => {
				if (!root) return;
				root.innerHTML = '';
				if (!list || !list.length) return;

				const grid = document.createElement('div');
				grid.className = 'tooto-solutions-tabs__products-grid';

				list.forEach((item) => {
					const card = document.createElement('div');
					card.className = 'tooto-solutions-tabs__product-card';

					if (item.thumb) {
						const thumb = document.createElement('div');
						thumb.className = 'tooto-solutions-tabs__product-thumb';
						const img = document.createElement('img');
						img.src = item.thumb;
						img.alt = item.title || '';
						img.loading = 'lazy';
						thumb.appendChild(img);
						card.appendChild(thumb);
					}

					const body = document.createElement('div');
					body.className = 'tooto-solutions-tabs__product-body';

					const title = document.createElement('div');
					title.className = 'tooto-solutions-tabs__product-title';
					title.textContent = item.title || '';
					body.appendChild(title);

					card.appendChild(body);

					if (item.permalink) {
						const link = document.createElement('a');
						link.className = 'tooto-solutions-tabs__product-link';
						link.href = item.permalink;
						link.setAttribute('aria-label', item.title || '');
						card.appendChild(link);
					}
					grid.appendChild(card);
				});

				root.appendChild(grid);
			};

			const opts = {
				slidesPerView: 'auto',
				spaceBetween: 12,
				autoHeight: false,
                autoWidth: true,
                speed: 800,
				navigation: navPrev && navNext ? { prevEl: navPrev, nextEl: navNext } : undefined,
			};

			const wireInstance = (instance) => {
				if (!instance) return;
				instance.on('slideChange', () => setActive(getIndex(instance)));
				tabs.forEach((tab, index) => {
					tab.addEventListener('click', () => {
						instance.slideTo(index);
						setActive(index);
					});
				});
				setActive(getIndex(instance) || 0);
			};

			const tryInit = () => {
				const utilsSwiper = window.elementorFrontend && window.elementorFrontend.utils && window.elementorFrontend.utils.swiper;

				if (utilsSwiper) {
					if (typeof utilsSwiper === 'function') {
						const maybe = utilsSwiper(swiperEl, opts);
						if (maybe && typeof maybe.then === 'function') {
							maybe.then(wireInstance);
						} else {
							wireInstance(maybe);
						}
						return true;
					}

					if (typeof utilsSwiper.init === 'function') {
						const maybe = utilsSwiper.init(swiperEl, opts);
						if (maybe && typeof maybe.then === 'function') {
							maybe.then(wireInstance);
						} else {
							wireInstance(maybe);
						}
						return true;
					}
				}

				if (window.Swiper) {
					const instance = new window.Swiper(swiperEl, opts);
					wireInstance(instance);
					return true;
				}

				return false;
			};

			tryInit();
		});

	};

	if (window.elementorFrontend && window.elementorFrontend.hooks) {
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-solutions-tabs.default', (e) => init(e));
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-popover.default', (e) => init(e));
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-image-carousel.default', (e) => init(e));
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-loop-grid.default', (e) => init(e));
	}

	document.addEventListener('DOMContentLoaded', () => init());

	// Posts Pagination
	const initPostsPagination = (rawScope = document) => {
		const scope = rawScope && rawScope.jquery ? rawScope[0] : rawScope;
		const dropdowns = scope.querySelectorAll('.tooto-posts__pagination-dropdown');
		dropdowns.forEach((dropdown) => {
			const toggle = dropdown.querySelector('.tooto-posts__pagination-dropdown-toggle');
			const menu = dropdown.querySelector('.tooto-posts__pagination-dropdown-menu');
			const value = dropdown.querySelector('.tooto-posts__pagination-dropdown-value');
			const items = dropdown.querySelectorAll('.tooto-posts__pagination-dropdown-item');

			if (!toggle || !menu) return;

			const closeMenu = () => {
				toggle.setAttribute('aria-expanded', 'false');
				menu.classList.remove('is-open');
			};

			const openMenu = () => {
				toggle.setAttribute('aria-expanded', 'true');
				menu.classList.add('is-open');
			};

			toggle.addEventListener('click', (e) => {
				e.preventDefault();
				e.stopPropagation();
				const isOpen = toggle.getAttribute('aria-expanded') === 'true';
				if (isOpen) {
					closeMenu();
				} else {
					// 关闭其他打开的下拉菜单
					dropdowns.forEach((d) => {
						if (d !== dropdown) {
							const t = d.querySelector('.tooto-posts__pagination-dropdown-toggle');
							const m = d.querySelector('.tooto-posts__pagination-dropdown-menu');
							if (t && m) {
								t.setAttribute('aria-expanded', 'false');
								m.classList.remove('is-open');
							}
						}
					});
					openMenu();
				}
			});

			items.forEach((item) => {
				item.addEventListener('click', (e) => {
					e.preventDefault();
					const pageValue = item.dataset.value;
					if (pageValue && value) {
						value.textContent = pageValue;
					}
					closeMenu();
					// 跳转到对应页面
					const href = item.getAttribute('href');
					if (href) {
						window.location.href = href;
					}
				});
			});

			// 点击外部关闭
			document.addEventListener('click', (e) => {
				if (!dropdown.contains(e.target)) {
					closeMenu();
				}
			});

			// ESC 键关闭
			document.addEventListener('keydown', (e) => {
				if (e.key === 'Escape' && toggle.getAttribute('aria-expanded') === 'true') {
					closeMenu();
					toggle.focus();
				}
			});
		});
	};

	if (window.elementorFrontend && window.elementorFrontend.hooks) {
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-posts.default', (e) => initPostsPagination(e));
	}

	document.addEventListener('DOMContentLoaded', () => initPostsPagination());

	// Case Add to Cart
	const initCaseAddToCart = (rawScope = document) => {
		const scope = rawScope && rawScope.jquery ? rawScope[0] : rawScope;
		const containers = scope.querySelectorAll('.tooto-case-add-to-cart');
		if (!containers.length) return;

		containers.forEach((container) => {
			const productId = container.dataset.productId;
			const unitsPerCase = parseInt(container.dataset.unitsPerCase || '16', 10);
			const cartQuantityRule = container.dataset.cartQuantityRule || 'total_units';
			const quantityInput = container.querySelector('.tooto-case-add-to-cart__quantity-input');
			const minusBtn = container.querySelector('.tooto-case-add-to-cart__quantity-btn--minus');
			const plusBtn = container.querySelector('.tooto-case-add-to-cart__quantity-btn--plus');
			const addToCartBtn = container.querySelector('.tooto-case-add-to-cart__button');

			if (!quantityInput || !minusBtn || !plusBtn || !addToCartBtn) return;

			const minQuantity = 1;

			const updateQuantity = (newQuantity) => {
				let val = parseInt(newQuantity, 10);
				if (isNaN(val) || val < minQuantity) {
					val = minQuantity;
				}
				quantityInput.value = val;
			};

			quantityInput.addEventListener('change', (e) => {
				updateQuantity(e.target.value);
			});

			minusBtn.addEventListener('click', (e) => {
				e.preventDefault();
				const currentVal = parseInt(quantityInput.value || 0, 10);
				updateQuantity(currentVal - 1);
			});

			plusBtn.addEventListener('click', (e) => {
				e.preventDefault();
				const currentVal = parseInt(quantityInput.value || 0, 10);
				updateQuantity(currentVal + 1);
			});

			addToCartBtn.addEventListener('click', (e) => {
				e.preventDefault();

				// Check for login requirement
				if (addToCartBtn.dataset.loginRequired === 'yes') {
					e.stopPropagation();
					
					// Try to find Blocksy login trigger
					const trigger = document.querySelector('.ct-header-account a[href="#account-modal"]');

					if (trigger) {
						// Dispatch sequence to ensure Blocksy modal opens
						trigger.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
						trigger.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
						trigger.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
						return;
					}

					// Fallback triggers
					const triggers = [
						'.ct-header-account',
						'a[href*="#account-modal"]',
						'.ct-login-popup-trigger',
						'[data-id="account"]'
					];
					
					let found = false;
					for (const selector of triggers) {
						const el = document.querySelector(selector);
						if (el) {
							el.click();
							found = true;
							break;
						}
					}

					if (!found) {
						// Final Fallback: Create a temporary link to trigger the modal
						const link = document.createElement('a');
						link.href = '#account-modal';
						link.style.display = 'none';
						document.body.appendChild(link);
						link.click();
						setTimeout(() => link.remove(), 100);
					}
					return;
				}
				
				let quantity = parseInt(quantityInput.value, 10);
				if (isNaN(quantity) || quantity < 1) {
					quantity = 1;
				}

				// 计算实际数量
				let actualQuantity;
				if (cartQuantityRule === 'case_quantity') {
					actualQuantity = quantity;
				} else {
					// 默认：箱数 * 每箱单位数
					actualQuantity = quantity * unitsPerCase;
				}

				// 禁用按钮，显示加载状态
				addToCartBtn.disabled = true;
				const originalText = addToCartBtn.textContent;
				addToCartBtn.textContent = 'Adding...';

				// 获取 AJAX URL
				let ajaxUrl = '/?wc-ajax=add_to_cart';
				if (typeof wc_add_to_cart_params !== 'undefined' && wc_add_to_cart_params.wc_ajax_url) {
					ajaxUrl = wc_add_to_cart_params.wc_ajax_url.toString().replace('%%endpoint%%', 'add_to_cart');
				}

				// 准备 AJAX 请求数据
				const formData = new FormData();
				formData.append('product_id', productId);
				formData.append('quantity', actualQuantity);

				// 发送 AJAX 请求
				fetch(ajaxUrl, {
					method: 'POST',
					body: formData,
				})
					.then((response) => response.json())
					.then((data) => {
						if (data.error && data.product_url) {
							// 如果有错误，跳转到产品页面
							window.location.href = data.product_url;
							return;
						}

						// 如果设置了重定向到购物车
						if (typeof wc_add_to_cart_params !== 'undefined' && wc_add_to_cart_params.cart_redirect_after_add === 'yes') {
							window.location.href = wc_add_to_cart_params.cart_url || '/cart/';
							return;
						}

						// 触发 WooCommerce 购物车更新事件
						if (typeof jQuery !== 'undefined' && jQuery.fn.trigger) {
							jQuery(document.body).trigger('added_to_cart', [data.fragments, data.cart_hash, jQuery(addToCartBtn)]);
						}

						// 恢复按钮状态
						addToCartBtn.disabled = false;
						addToCartBtn.textContent = originalText;
					})
					.catch((error) => {
						console.error('Add to cart error:', error);
						addToCartBtn.disabled = false;
						addToCartBtn.textContent = originalText;
						alert('添加失败，请重试');
					});
			});
		});
	};

	// --- Product Content Implementation ---
	const initProductContent = (rawScope = document) => {
		const scope = rawScope && rawScope.jquery ? rawScope[0] : rawScope;
		const $ = jQuery;
		
		// 1. Force Elementor to run its ready trigger on the current scope content.
		// We target all nested Elementor elements (sections, columns, widgets) to ensure deep init.
		if (window.elementorFrontend && window.elementorFrontend.elementsHandler) {
			const $scope = $(scope);
			// Init the wrapper itself if it's an elementor element (unlikely for rawScope, but good safety)
			// Then init all children
			$scope.find('.elementor-element').each(function() {
				window.elementorFrontend.elementsHandler.runReadyTrigger($(this));
			});
		}

		// 2. Fix Swiper Parent Issue
		// We need to be very aggressive in finding the parent Swiper.
		// It might be a direct parent, or several levels up, or the scope itself might be inside a slide.
		const $scope = $(scope);
		const $swiperSlide = $scope.closest('.swiper-slide');
		
		console.log('Tooto Product Content Init:', {
			scope: scope,
			foundSlide: $swiperSlide.length > 0,
			slide: $swiperSlide[0]
		});

		if ($swiperSlide.length) {
			const $swiperContainer = $swiperSlide.closest('.swiper-container, .swiper');
			
			console.log('Tooto Product Content Container:', {
				foundContainer: $swiperContainer.length > 0,
				container: $swiperContainer[0],
				hasSwiperInstance: $swiperContainer[0] && $swiperContainer[0].swiper
			});

			if ($swiperContainer.length && $swiperContainer[0].swiper) {
				const swiper = $swiperContainer[0].swiper;
				
				// Helper: Force update and instant re-alignment
				// Solution 3: "The Shake" - Simulate a physical move to trigger deep internal state reset
				// This fixes cases where update() and slideTo() are not enough (e.g. Loop mode index corruption)
				const forceUpdate = () => {
					swiper.update();
					
					// 1. Try to go next (instant)
					swiper.slideNext(0);
					
					// 2. Immediately go back (instant)
					// This forces Swiper to run its full "transitionEnd" logic chain
					swiper.slidePrev(0);

					// 3. Fallback: If for some reason we are stuck, align to current
					swiper.slideTo(swiper.activeIndex, 0);
				};

				// Initial update
				forceUpdate();

				// Solution 2: Re-update when images inside content are loaded
				// Images loading later can change slide dimensions
				const $images = $(scope).find('img');
				if ($images.length) {
					// Use imagesLoaded if available (bundled with Elementor)
					if ($.fn.imagesLoaded) {
						$(scope).imagesLoaded().progress(forceUpdate);
					} else {
						// Fallback to native load event
						$images.on('load', forceUpdate);
					}
				}

				// 3. Handle Loop Mode Clones (Duplicates)
				// If Swiper is in loop mode, it creates clone slides that don't have JS events bound.
				// We need to find these clones and manually trigger Elementor initialization on them.
				if (swiper.params.loop) {
					const fixLoopClones = () => {
						// Find all duplicate slides
						const duplicates = swiperContainer.querySelectorAll('.swiper-slide-duplicate');
						
						duplicates.forEach((slide) => {
							// Check if we already fixed this specific slide instance
							// We use a property on the DOM node to track it
							if (slide.tootoLoopFixed) return;
							slide.tootoLoopFixed = true;
							
							const $slide = $(slide);
							// Find ALL Elementor elements inside the duplicate slide (Widgets, Columns, Sections)
							// We must init them all because Elementor's init is not always recursive
							$slide.find('.elementor-element').each(function() {
								if (window.elementorFrontend && window.elementorFrontend.elementsHandler) {
									window.elementorFrontend.elementsHandler.runReadyTrigger($(this));
								}
							});
						});
					};

					// Run immediately to fix existing clones
					fixLoopClones();
					
					// Run on Swiper updates (in case it rebuilds loop or re-inits)
					swiper.on('update', fixLoopClones);
					swiper.on('init', fixLoopClones); // Just in case
				}
			}
		}
	};

	// --- Hover Image Menu Implementation ---
	const initHoverImageMenu = (rawScope = document) => {
		const scope = rawScope && rawScope.jquery ? rawScope[0] : rawScope;
		const menus = scope.querySelectorAll('.tooto-hover-image-menu');

		menus.forEach((menu) => {
			// Entrance Animation
			if (menu.classList.contains('tooto-animate-entrance')) {
				// Blocksy Mega Menu Integration
				const blocksyParent = menu.closest('li.menu-item-has-children');
				
				if (blocksyParent) {
					// Blocksy mode: toggle visibility based on parent class
					const toggle = () => {
						if (blocksyParent.classList.contains('ct-active')) {
							menu.classList.add('is-visible');
						} else {
							menu.classList.remove('is-visible');
						}
					};
					
					toggle();
					const observer = new MutationObserver(toggle);
					observer.observe(blocksyParent, { attributes: true, attributeFilter: ['class'] });
				} else if ('IntersectionObserver' in window) {
					const observer = new IntersectionObserver((entries) => {
						entries.forEach((entry) => {
							if (entry.isIntersecting) {
								menu.classList.add('is-visible');
							} else {
								menu.classList.remove('is-visible');
							}
						});
					}, {
						threshold: 0.1
					});
					observer.observe(menu);
				} else {
					// Fallback for browsers without IntersectionObserver
					menu.classList.add('is-visible');
				}
			}

			const items = menu.querySelectorAll('.tooto-hover-image-menu__item');
			const images = menu.querySelectorAll('.tooto-hover-image-menu__image');

			items.forEach((item) => {
				item.addEventListener('mouseenter', () => {
					// Remove active class from all items and images
					items.forEach(i => i.classList.remove('active'));
					images.forEach(img => img.classList.remove('active'));

					// Add active class to current item
					item.classList.add('active');

					// Find corresponding image
					const index = item.getAttribute('data-index');
					const targetImage = menu.querySelector(`.tooto-hover-image-menu__image[data-index="${index}"]`);
					
					if (targetImage) {
						targetImage.classList.add('active');
					}
				});
			});
		});
	};

	if (window.elementorFrontend && window.elementorFrontend.hooks) {
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-case-add-to-cart.default', (e) => initCaseAddToCart(e));
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-hover-image-menu.default', (e) => initHoverImageMenu(e));
		window.elementorFrontend.hooks.addAction('frontend/element_ready/tooto-product-content.default', (e) => initProductContent(e));
	}

	document.addEventListener('DOMContentLoaded', () => {
		initCaseAddToCart();
		initHoverImageMenu();
	});

	// --- Text Type Widget Implementation ---
	class TextTypeWidget {
		constructor($element) {
			this.$element = $element;
			this.$wrapper = $element.find('.tooto-text-type');
			this.$textSpan = $element.find('.tooto-text-type__text');
			this.$cursorSpan = $element.find('.tooto-text-type__cursor');
			
			const settings = this.$wrapper.data('tooto-text-type-settings');
			if (!settings) return;

			this.strings = settings.strings || [];
			this.typeSpeed = settings.typeSpeed || 75;
			this.deleteSpeed = settings.deleteSpeed || 30;
			this.startDelay = settings.startDelay || 0;
			this.pauseDuration = settings.pauseDuration || 1500;
			this.loop = settings.loop;
			this.startOnVisible = settings.startOnVisible;
			this.showCursor = settings.showCursor;
			this.hideCursorWhileTyping = settings.hideCursorWhileTyping;

			this.stringIndex = 0;
			this.charIndex = 0;
			this.isDeleting = false;
			this.timeout = null;

			if (this.strings.length === 0) return;

			this.init();
		}

		init() {
			// Check if we are in Elementor Editor
			const isEditor = document.body.classList.contains('elementor-editor-active');
			
			// Always start immediately in Editor
			if (isEditor) {
				this.start();
				return;
			}

			// If startOnVisible is false, or IntersectionObserver is not supported, start immediately
			if (!this.startOnVisible || !window.IntersectionObserver) {
				this.start();
				return;
			}

			// Check if element is already in viewport
			const rect = this.$wrapper[0].getBoundingClientRect();
			const inViewport = (
				rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
				rect.bottom > 0
			);

			if (inViewport) {
				this.start();
				return;
			}

			// Use Observer for elements not yet in viewport
			const observer = new IntersectionObserver((entries) => {
				entries.forEach(entry => {
					if (entry.isIntersecting) {
						this.start();
						observer.disconnect();
					}
				});
			}, { threshold: 0 }); // Threshold 0 means "as soon as one pixel is visible"
			
			observer.observe(this.$wrapper[0]);
		}

		start() {
			if (this.startDelay > 0) {
				this.timeout = setTimeout(() => {
					this.tick();
				}, this.startDelay);
			} else {
				this.tick();
			}
		}

		tick() {
			const currentStringObj = this.strings[this.stringIndex];
			const fullString = currentStringObj.text;
			
			// Set Color
			if (currentStringObj.color) {
				this.$textSpan.css('color', currentStringObj.color);
			} else {
				this.$textSpan.css('color', '');
			}

			if (this.isDeleting) {
				this.charIndex--;
			} else {
				this.charIndex++;
			}

			const currentText = fullString.substring(0, this.charIndex);
			this.$textSpan.text(currentText);

			// Cursor Logic
			if (this.showCursor && this.hideCursorWhileTyping) {
				if (this.charIndex < fullString.length || this.isDeleting) {
					this.$cursorSpan.addClass('tooto-hidden');
				} else {
					this.$cursorSpan.removeClass('tooto-hidden');
				}
			}

			let typeSpeed = this.typeSpeed;

			if (this.isDeleting) {
				typeSpeed = this.deleteSpeed;
			}

			// If finished typing the sentence
			if (!this.isDeleting && this.charIndex === fullString.length) {
				// Pause at end
				typeSpeed = this.pauseDuration;
				this.isDeleting = true;
				
				// If not looping and it's the last string, stop here
				if (!this.loop && this.stringIndex === this.strings.length - 1) {
					return;
				}
			} else if (this.isDeleting && this.charIndex === 0) {
				this.isDeleting = false;
				this.stringIndex++;
				
				// Loop back to start
				if (this.stringIndex >= this.strings.length) {
					this.stringIndex = 0;
				}
				
				// Pause slightly before typing next
				typeSpeed = 500;
			}

			this.timeout = setTimeout(() => {
				this.tick();
			}, typeSpeed);
		}
	}

	const initTextType = ( $scope ) => {
		new TextTypeWidget($scope);
	};

	// File Download Widget - Event Delegation
	document.addEventListener('click', (e) => {
		const button = e.target.closest('.tooto-file-download__button');
		if (!button) return;

		const widget = button.closest('.tooto-file-download');
		if (!widget) return;

		const input = widget.querySelector('.tooto-file-download__input');
		if (!input) return;

		e.preventDefault();
		const email = input.value.trim();
		const fileUrl = widget.dataset.fileUrl;
		const errorMessageText = widget.dataset.errorMessage || 'Please enter a valid email address.';
		const messageElement = widget.querySelector('.tooto-file-download__error-message');

		const validateEmail = (email) => {
			return String(email)
				.toLowerCase()
				.match(
					/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
				);
		};

		const showMessage = (text) => {
			if (messageElement) {
				messageElement.textContent = text;
				messageElement.style.display = 'block';
			} else {
				alert(text);
			}
		};

		const hideMessage = () => {
			if (messageElement) {
				messageElement.style.display = 'none';
			}
		};

		if (!validateEmail(email)) {
			input.classList.add('error');
			showMessage(errorMessageText);
			return;
		}

		if (!fileUrl) {
			console.warn('Tooto File Download: No file URL configured.');
			showMessage('Download not configured.');
			return;
		}

		input.classList.remove('error');
		hideMessage();

		// Trigger download
		const link = document.createElement('a');
		link.href = fileUrl;
		link.download = fileUrl.split('/').pop();
		link.target = '_blank';
		document.body.appendChild(link);
		link.click();
		document.body.removeChild(link);
	});
	
	document.addEventListener('input', (e) => {
		if (e.target.matches('.tooto-file-download__input')) {
			e.target.classList.remove('error');
			const widget = e.target.closest('.tooto-file-download');
			const messageElement = widget ? widget.querySelector('.tooto-file-download__error-message') : null;
			if (messageElement) {
				messageElement.style.display = 'none';
			}
		}
	});

	if (window.elementorFrontend && window.elementorFrontend.hooks) {
		window.elementorFrontend.hooks.addAction( 'frontend/element_ready/tooto-text-type.default', initTextType );
	}
})();

