PageSourceSearch

https://www.picklebeastpickleball.com/assets/theme-toggle-ddff1c9b…2439f4dac2b90bdbda8077943ad54ba439e9aba1.js

js picklebeastpickleball.com collected 2026-09-24 14:18:52 UTC 4,656 bytes, 139 lines download raw bytes

1// Theme Toggle - Robust Version
2(function() {
3  var listenerAttached = false;
4
5  // Marketing/landing pages set window.__pbForceDark (see shared/_landing_nav).
6  // Those surfaces (nav + hero + cards) are hardcoded dark-only, so a stale
7  // saved `pb-theme=light` must NOT leak onto them — that renders a dark-styled
8  // nav over a light body (the "not nice in light mode" bug). When forced, we
9  // pin data-theme=dark and DO NOT touch localStorage, so the user's real
10  // preference persists for the light-capable surfaces (member portal, /browse).
11  function isForcedDark() {
12    return window.__pbForceDark === true;
13  }
14
15  function getCurrentTheme() {
16    if (isForcedDark()) return 'dark';
17    return localStorage.getItem('pb-theme') ||
18           document.documentElement.getAttribute('data-theme') ||
19           (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
20  }
21  
22  function applyTheme(theme, skipLog) {
23    document.documentElement.setAttribute('data-theme', theme);
24    localStorage.setItem('pb-theme', theme);
25    updateAllIcons(theme);
26    if (!skipLog) console.log('Theme switched to:', theme);
27  }
28  
29  function toggleTheme(e) {
30    if (e) {
31      e.preventDefault();
32      e.stopPropagation();
33    }
34    var currentTheme = getCurrentTheme();
35    var newTheme = currentTheme === 'dark' ? 'light' : 'dark';
36    applyTheme(newTheme);
37  }
38  
39  // Icons show what you'll SWITCH TO (opposite of current)
40  // Dark mode = show sun (click to go light)
41  // Light mode = show moon (click to go dark)
42  function updateAllIcons(theme) {
43    document.querySelectorAll('[data-theme-toggle]').forEach(function(btn) {
44      var sunIcon = btn.querySelector('.theme-icon-sun');
45      var moonIcon = btn.querySelector('.theme-icon-moon');
46      if (sunIcon && moonIcon) {
47        // In dark mode, show sun (to switch to light)
48        // In light mode, show moon (to switch to dark)
49        if (theme === 'dark') {
50          sunIcon.style.display = 'block';
51          moonIcon.style.display = 'none';
52        } else {
53          sunIcon.style.display = 'none';
54          moonIcon.style.display = 'block';
55        }
56      }
57    });
58  }
59  
60  function syncTheme() {
61    if (isForcedDark()) {
62      // Pin dark WITHOUT writing localStorage — preserve the real saved pref.
63      document.documentElement.setAttribute('data-theme', 'dark');
64      updateAllIcons('dark');
65      return;
66    }
67    var theme = getCurrentTheme();
68    // Ensure DOM matches localStorage
69    document.documentElement.setAttribute('data-theme', theme);
70    // Update all icons
71    updateAllIcons(theme);
72  }
73  
74  function attachListener() {
75    if (listenerAttached) return;
76    listenerAttached = true;
77    
78    document.addEventListener('click', function(e) {
79      var toggleBtn = e.target.closest('[data-theme-toggle]');
80      if (toggleBtn) {
81        toggleTheme(e);
82      }
83    }, { capture: true });
84    
85    // Sync across tabs (but never override a forced-dark surface)
86    window.addEventListener('storage', function(e) {
87      if (e.key === 'pb-theme' && e.newValue) {
88        if (isForcedDark()) { syncTheme(); return; }
89        applyTheme(e.newValue, true);
90      }
91    });
92    
93    // Re-sync on tab focus
94    document.addEventListener('visibilitychange', function() {
95      if (document.visibilityState === 'visible') {
96        syncTheme();
97      }
98    });
99  }
100  
101  function init() {
102    attachListener();
103    syncTheme();
104  }
105  
106  // Run immediately
107  if (document.readyState === 'loading') {
108    document.addEventListener('DOMContentLoaded', init);
109  } else {
110    init();
111  }
112  
113  // Turbo navigations - sync immediately and after a delay
114  ['turbo:load', 'turbo:render', 'turbo:frame-load', 'turbolinks:load'].forEach(function(event) {
115    document.addEventListener(event, function() {
116      syncTheme();
117      // Also sync after short delay to catch race conditions
118      setTimeout(syncTheme, 10);
119      setTimeout(syncTheme, 100);
120    });
121  });
122  
123  // MutationObserver to catch any dynamic sidebar changes
124  var observer = new MutationObserver(function(mutations) {
125    mutations.forEach(function(mutation) {
126      if (mutation.addedNodes.length) {
127        mutation.addedNodes.forEach(function(node) {
128          if (node.nodeType === 1 && (node.hasAttribute && node.hasAttribute('data-theme-toggle') || node.querySelector && node.querySelector('[data-theme-toggle]'))) {
129            syncTheme();
130          }
131        });
132      }
133    });
134  });
135  
136  observer.observe(document.body, { childList: true, subtree: true });
137  
138  window.PBTheme = { toggle: toggleTheme, set: applyTheme, get: getCurrentTheme, sync: syncTheme };
139})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.