/** * TieMeasureFlow - Alpine.js Theme Initialization * * This script MUST load before Alpine.js (which uses defer). * It reads the saved theme from localStorage or the system preference, * applies the 'dark' class to immediately (no flash), and * registers an Alpine.store('theme') for reactive toggling. */ (function () { 'use strict'; // ---- Immediate theme application (before paint) ---- var STORAGE_KEY = 'tmf-theme'; /** * Resolve the initial dark-mode state. * Priority: localStorage > system preference > light (default). */ function getInitialDark() { var stored = null; try { stored = localStorage.getItem(STORAGE_KEY); } catch (_) { // localStorage may be unavailable (private browsing, etc.) } if (stored === 'dark') return true; if (stored === 'light') return false; // Fall back to OS preference if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return true; } return false; } var isDark = getInitialDark(); // Apply immediately to prevent flash of wrong theme if (isDark) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } // ---- Enable smooth transitions after first paint ---- // Use requestAnimationFrame to wait one frame, then enable transitions // so the initial theme application does not animate. if (typeof requestAnimationFrame === 'function') { requestAnimationFrame(function () { requestAnimationFrame(function () { document.documentElement.classList.add('theme-transitions'); }); }); } // ---- Alpine.js Store Registration ---- // Alpine.store is registered via the alpine:init event, which fires // when Alpine begins initialization but before it processes x-data. document.addEventListener('alpine:init', function () { Alpine.store('theme', { dark: isDark, toggle: function () { this.dark = !this.dark; this._apply(); }, set: function (dark) { this.dark = dark; this._apply(); }, _apply: function () { if (this.dark) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } try { localStorage.setItem(STORAGE_KEY, this.dark ? 'dark' : 'light'); } catch (_) { // Silently ignore storage errors } } }); }); // ---- Listen for system preference changes ---- if (window.matchMedia) { try { var mq = window.matchMedia('(prefers-color-scheme: dark)'); mq.addEventListener('change', function (e) { // Only auto-switch if the user hasn't manually set a preference var stored = null; try { stored = localStorage.getItem(STORAGE_KEY); } catch (_) {} if (!stored) { var newDark = e.matches; if (typeof Alpine !== 'undefined' && Alpine.store('theme')) { Alpine.store('theme').set(newDark); } else { // Alpine not loaded yet, apply directly if (newDark) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } } }); } catch (_) { // Older browsers may not support addEventListener on matchMedia } } })();