import { initializeTerminalComponent } from './terminal.js'; // Livewire 3.5.19+ re-applies `x-cloak` to morphed elements during wire:navigate // (via replaceHtmlAttributes). With `[x-cloak]{display:none}` on the app wrapper, // this blanks the whole page on every navigation until Alpine re-processes it. // Strip leftover x-cloak after each navigation; the initial-load FOUC guard stays. document.addEventListener('livewire:navigated', () => { document.querySelectorAll('[x-cloak]').forEach((el) => el.removeAttribute('x-cloak')); }); // Register the terminal data provider before Alpine initializes the page. // Keeping this registration independent from the current route also makes it // available before Alpine processes terminal markup after wire:navigate. document.addEventListener('alpine:init', initializeTerminalComponent); /** * Smooth-scroll a settings section into view, then flash its border for 500ms * after the scroll has settled. Starting the flash immediately makes long * jumps (top → bottom) finish scrolling after the animation has already ended. * * @param {string} id */ window.scrollToSettingsSection = function scrollToSettingsSection(id) { const el = document.getElementById(id); if (!el) { return; } if (typeof el._sectionHighlightCleanup === 'function') { el._sectionHighlightCleanup(); } const runHighlight = () => { el.classList.remove('is-section-highlight'); // Force reflow so the 500ms highlight can re-run on repeated clicks. void el.offsetWidth; el.classList.add('is-section-highlight'); el._sectionHighlightTimer = window.setTimeout(() => { el.classList.remove('is-section-highlight'); }, 500); }; let finished = false; let rafId = 0; let scrollEndHandler = null; const cleanup = () => { if (rafId) { window.cancelAnimationFrame(rafId); rafId = 0; } if (scrollEndHandler) { window.removeEventListener('scrollend', scrollEndHandler); scrollEndHandler = null; } if (el._sectionHighlightTimer) { window.clearTimeout(el._sectionHighlightTimer); el._sectionHighlightTimer = null; } }; const finish = () => { if (finished) { return; } finished = true; cleanup(); runHighlight(); }; el._sectionHighlightCleanup = () => { finished = true; cleanup(); el.classList.remove('is-section-highlight'); el._sectionHighlightCleanup = null; }; el.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Prefer the native scrollend event when the browser fires it. scrollEndHandler = () => finish(); window.addEventListener('scrollend', scrollEndHandler, { once: true }); // Fallback: wait until the target's Y position is stable for a few frames // (covers browsers without scrollend, and no-op scrolls when already in view). let lastTop = null; let stableFrames = 0; let frames = 0; const maxFrames = 180; // ~3s safety cap const tick = () => { if (finished) { return; } frames += 1; const top = el.getBoundingClientRect().top; if (lastTop !== null && Math.abs(top - lastTop) < 0.5) { stableFrames += 1; } else { stableFrames = 0; } lastTop = top; // Skip the first couple frames so we don't flash before smooth scroll starts. if (frames > 4 && stableFrames >= 4) { finish(); return; } if (frames >= maxFrames) { finish(); return; } rafId = window.requestAnimationFrame(tick); }; rafId = window.requestAnimationFrame(tick); };