PageSourceSearch

https://www.pollsmax.com/assets/pollsmax-theme.js?v=mobile-theme-toggle-2

js pollsmax.com collected 2026-09-28 06:38:18 UTC 14,517 bytes, 366 lines download raw bytes

1/**
2 * Pollsmax site-wide dark/light theme.
3 * Attribute: html[data-theme="dark"|"light"]
4 * Storage: localStorage["pollsmax-theme"]
5 */
6(function () {
7    'use strict';
8
9    if (window.PollsmaxTheme && window.PollsmaxTheme.__initialized) {
10        return;
11    }
12
13    var STORAGE_KEY = 'pollsmax-theme';
14    var LEGACY_STORAGE_KEY = 'pollsmax-home-theme';
15    var TOGGLE_ID = 'pollsmax-theme-toggle';
16    var MEDIA_QUERY = '(prefers-color-scheme: light)';
17    var userHasExplicitTheme = false;
18
19    function readSavedTheme() {
20        try {
21            var saved = localStorage.getItem(STORAGE_KEY);
22            if (saved === 'dark' || saved === 'light') return saved;
23            var legacy = localStorage.getItem(LEGACY_STORAGE_KEY);
24            if (legacy === 'dark' || legacy === 'light') {
25                try { localStorage.setItem(STORAGE_KEY, legacy); } catch (eMig) { /* ignore */ }
26                return legacy;
27            }
28        } catch (eRead) { /* ignore */ }
29        return null;
30    }
31
32    function getInitialTheme() {
33        return 'dark';
34    }
35
36    function currentTheme() {
37        return document.documentElement.dataset.theme === 'light' ? 'light' : 'dark';
38    }
39
40    function isLight() {
41        return currentTheme() === 'light';
42    }
43
44    function cssVar(name, fallback) {
45        try {
46            var v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
47            if (v) return v;
48        } catch (eCss) { /* ignore */ }
49        return fallback;
50    }
51
52    function chartColors() {
53        return {
54            background: cssVar('--color-chart-bg', isLight() ? '#ffffff' : '#0c0f14'),
55            chartBg: cssVar('--color-chart-bg', isLight() ? '#ffffff' : '#0c0f14'),
56            grid: cssVar('--color-chart-grid', isLight() ? 'rgba(15, 23, 42, 0.09)' : 'rgba(31, 41, 55, 0.45)'),
57            border: cssVar('--color-chart-grid', isLight() ? 'rgba(15, 23, 42, 0.09)' : 'rgba(31, 41, 55, 0.45)'),
58            tick: cssVar('--color-chart-axis', isLight() ? '#596575' : '#94a3b8'),
59            axis: cssVar('--color-chart-axis', isLight() ? '#596575' : '#94a3b8'),
60            text: cssVar('--color-text-primary', isLight() ? '#10141b' : '#e2e8f0'),
61            secondaryText: cssVar('--color-text-secondary', isLight() ? '#46505f' : '#94a3b8'),
62            textSecondary: cssVar('--color-text-secondary', isLight() ? '#46505f' : '#94a3b8'),
63            tooltipBg: cssVar('--color-chart-tooltip-bg', isLight() ? '#ffffff' : '#161d28'),
64            tooltipBorder: cssVar('--color-chart-tooltip-border', isLight() ? 'rgba(15, 23, 42, 0.17)' : 'rgba(255, 255, 255, 0.14)')
65        };
66    }
67
68    function getThemeChartColors() {
69        return chartColors();
70    }
71
72    function mapNoElectionFill() {
73        return cssVar('--color-map-no-election', isLight() ? '#e2e8f0' : '#0f172a');
74    }
75
76    function mapStateStroke() {
77        /* Dark mode must stay near-black (#1f2937) — never text gray. */
78        return cssVar('--color-map-state-stroke', isLight() ? '#cbd5e1' : '#1f2937');
79    }
80
81    function mapStateHoverStroke() {
82        /* Dedicated map token — do NOT reuse text-secondary (#94a3b8 is light gray). */
83        return cssVar('--color-map-hover-stroke', isLight() ? '#334155' : '#1f2937');
84    }
85
86    function mapFlipOutlineStroke() {
87        return cssVar('--color-map-flip-outline', isLight() ? '#000000' : '#ffffff');
88    }
89
90    function flipChipOutlineStyle() {
91        var stroke = mapFlipOutlineStroke();
92        return 'box-shadow:inset 0 0 0 1px ' + stroke + ';';
93    }
94
95    function themeToggleInnerHtml() {
96        return '<svg class="pollsmax-theme-toggle__moon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">' +
97            '<path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/>' +
98            '</svg>' +
99            '<svg class="pollsmax-theme-toggle__sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">' +
100            '<circle cx="12" cy="12" r="4"/>' +
101            '<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41
101M17.66 6.34l1.41-1.41"/>' +
102            '</svg>';
103    }
104
105    function findThemeToggleBtn() {
106        return document.getElementById(TOGGLE_ID) ||
107            document.getElementById('pollsmax-home-theme-toggle') ||
108            document.querySelector('.theme-toggle, .pollsmax-theme-toggle');
109    }
110
111    function updateThemeToggle(theme) {
112        var btn = findThemeToggleBtn();
113        if (!btn) return;
114        var light = theme === 'light';
115        var label = light ? 'Switch to dark mode' : 'Switch to light mode';
116        btn.setAttribute('aria-pressed', light ? 'true' : 'false');
117        btn.setAttribute('aria-label', label);
118        btn.title = label;
119        btn.classList.toggle('is-light', light);
120    }
121
122    function updateToggleUi() {
123        updateThemeToggle(currentTheme());
124    }
125
126    function bindToggle(btn) {
127        if (!btn || btn.dataset.pmThemeBound === '1') return;
128        btn.dataset.pmThemeBound = '1';
129        btn.addEventListener('click', function () {
130            toggleTheme();
131        });
132    }
133
134    function hideThemeToggles() {
135        var nodes = document.querySelectorAll(
136            '#pollsmax-theme-toggle, #pollsmax-home-theme-toggle, .pollsmax-theme-toggle, .theme-toggle'
137        );
138        for (var i = 0; i < nodes.length; i++) {
139            nodes[i].hidden = true;
140            nodes[i].setAttribute('aria-hidden', 'true');
141            nodes[i].style.setProperty('display', 'none', 'important');
142            nodes[i].style.setProperty('visibility', 'hidden', 'important');
143            nodes[i].style.setProperty('pointer-events', 'none', 'important');
144        }
145    }
146
147    function injectToggle() {
148        hideThemeToggles();
149        return null;
150    }
151
152    function refreshAllCharts() {
153        if (window.ET && typeof ET.refreshAllLineChartAxisThemes === 'function') {
154            ET.refreshAllLineChartAxisThemes();
155            return;
156        }
157        if (!window.Chart || typeof Chart.getChart !== 'function') return;
158        var ax = chartColors();
159        document.querySelectorAll('canvas').forEach(function (canvas) {
160            var ch = Chart.getChart(canvas);
161            if (!ch || !ch.options || !ch.options.scales) return;
162            if (window.ET && typeof ET.applyLineChartAxisGridTheme === 'function') {
163                ET.applyLineChartAxisGridTheme(ch);
164                return;
165            }
166            var scales = ch.options.scales;
167            ['x', 'y'].forEach(function (axisKey) {
168                var scale = scales[axisKey];
169                if (!scale) return;
170                if (scale.ticks) scale.ticks.color = ax.tick;
171                if (scale.grid && typeof scale.grid.color === 'string') {
172                    scale.grid.color = ax.grid;
173                }
174                if (scale.border && scale.border.color) scale.border.color = ax.border;
175            });
176            var labels = (ch.data && ch.data.labels) ? ch.data.labels : [];
177            if (scales.x && scales.x.grid && window.ET && labels.length) {
178                if (typeof ET.senGovHubChartXGridColorFn === 'function') {
179                    scales.x.grid.color = ET.senGovHubChartXGridColorFn(labels, ax.grid);
180                } else if (typeof ET.xGridColorMonthStarts === 'function') {
181                    scales.x.grid.color = ET.xGridColorMonthStarts(labels, ax.grid);
182                }
183            }
184            try { ch.update('none'); } catch (eUp) { /* ignore */ }
185        });
186    }
187
188    function refreshMapsAndBars() {
189        if (!window.ET) return;
190        var mapPairs = [
191            ['usMap', 'mapTip'],
192            ['govMap', 'govMapTip']
193        ];
194        mapPairs.forEach(function (pair) {
195            if (!document.getElementById(pair[0])) return;
196            try {
197                if (pair[0] === 'usMap' && typeof ET.buildMap === 'function') ET.buildMap(pair[0], pair[1]);
198                if (pair[0] === 'govMap' && typeof ET.buildGovMap === 'function') ET.buildGovMap(pair[0], pair[1]);
199            } catch (eM) { /* ignore */ }
200        });
201        try {
202            if (typeof ET.updateSeatBar === 'function' && document.getElementById('barR') &&
203                !(typeof ET.isHouseHubSeatBarPage === 'function' && ET.isHouseHubSeatBarPage())) {
204                ET.updateSeatBar({ r: 'barR', d: 'barD', tie: 'barTie', nr: 'numR', nd: 'numD', rFlip: 'barRFlip', dFlip: 'barDFlip' });
205            }
206        } catch (eB) { /* ignore */ }
207        try {
208            if (typeof ET.updateGovSeatBar === 'function' && document.getElementById('govBarR')) {
209                ET.updateGovSeatBar({ r: 'govBarR', d: 'govBarD', nr: 'govNumR', nd: 'govNumD', rFlip: 'govBarRFlip', dFlip: 'govBarDFlip' });
210            }
211        } catch (eG) { /* ignore */ }
212        if (typeof ET.refreshElectoralHistoryMap === 'function') {
213            try { ET.refreshElectoralHistoryMap(); } catch (eEh) { /* ignore */ }
214        }
215        if (document.getElementById('houseCdMap') && typeof ET.refreshHouseHubFlipOutlineStrokes === 'function') {
216            try { ET.refreshHouseHubFlipOutlineStrokes('houseCdMap'); } catch (eH) { /* ignore */ }
217        }
218    }
219
220    function refreshHomeCharts() {
221        if (typeof window.pollsmaxInitGenericBallotHomeChart === 'function') {
222            window.pollsmaxInitGenericBallotHomeChart();
223        }
224        if (typeof window.pollsmaxInitTrumpApprovalHomeChart === 'function') {
225            window.pollsmaxInitTrumpApprovalHomeChart();
226        }
227        if (typeof window.pollsmaxInit2028PrimaryHomeCharts === 'function') {
228            window.pollsmaxInit2028PrimaryHomeCharts();
229        }
230        if (typeof window.__pollsmaxHomeSearchPollsRerender === 'function') {
231            window.__pollsmaxHomeSearchPollsRerender();
232        }
233    }
234
235    function refreshSiteVisuals() {
236        document.dispatchEvent(new CustomEvent('pollsmax-theme-change', {
237            detail: { theme: currentTheme(), light: isLight() }
238        }));
239        refreshMapsAndBars();
240        refreshHomeCharts();
241        refreshAllCharts();
242        if (window.ET) {
243            if (typeof ET.refreshRaceForecastSimHistogramForTheme === 'function') {
244                try { ET.refreshRaceForecastSimHistogramForTheme(); } catch (eRfHist) { /* ignore */ }
245            } else if (typeof ET.refreshRaceForecastPageIfMounted === 'function') {
246                try { ET.refreshRaceForecastPageIfMounted({ force: true }); } catch (eRf) { /* ignore */ }
247            }
248            if (typeof ET.refreshHubChartsLayout === 'function') {
249                try { ET.refreshHubChartsLayout(); } catch (eHub) { /* ignore */ }
250            }
251        }
252    }
253
254    /* INP: flip theme attrs on the click task; rebuild maps/charts after next paint. */
255    var themeRefreshTimer = 0;
256    function scheduleRefreshSiteVisuals() {
257        if (themeRefreshTimer) return;
258        var run = function () {
259            themeRefreshTimer = 0;
260            refreshSiteVisuals();
261        };
262        var afterPaint = function () {
263            if (typeof window.scheduler === 'object' &&
264                window.scheduler &&
265                typeof window.scheduler.yield === 'function') {
266                window.scheduler.yield().then(run).catch(run);
267                return;
268            }
269            themeRefreshTimer = setTimeout(run, 0);
270        };
271        if (typeof requestAnimationFrame === 'function') {
272            themeRefreshTimer = -1;
273            requestAnimationFrame(function () {
274                themeRefreshTimer = 0;
275                afterPaint();
276            });
277        } else {
278            afterPaint();
279        }
280    }
281
282    function applyTheme(theme, save, opts) {
283        var next = 'dark';
284        var skipRefresh = !!(opts && opts.skipRefresh);
285        document.documentElement.setAttribute('data-theme', next);
286        document.documentElement.removeAttribute('data-pollsmax-theme');
287        document.documentElement.style.colorScheme = next;
288        if (save) {
289            userHasExplicitTheme = true;
290            try {
291                localStorage.setItem(STORAGE_KEY, next);
292                localStorage.setItem(LEGACY_STORAGE_KEY, next);
293            } catch (eStore) { /* ignore */ }
294        }
295        updateThemeToggle(next);
296        if (!skipRefresh) scheduleRefreshSiteVisuals();
297    }
298
299    function setTheme(mode) {
300        applyTheme('dark', true);
301    }
302
303    function toggleTheme() {
304        applyTheme('dark', true);
305    }
306
307    function enableThemeTransitions() {
308        if (document.documentElement.classList.contains('theme-transition-enabled')) return;
309        var enable = function () {
310            document.documentElement.classList.add('theme-transition-enabled');
311        };
312        if (typeof requestAnimationFrame === 'function') {
313            requestAnimationFrame(function () {
314                requestAnimationFrame(enable);
315            });
316        } else {
317            setTimeout(enable, 50);
318        }
319    }
320
321    function bindSystemPreferenceListener() {
322        /* Site is locked to dark mode; ignore OS light/dark. */
323    }
324
325    function init() {
326        if (window.PollsmaxTheme && window.PollsmaxTheme.__initialized) return;
327        applyTheme('dark', true, { skipRefresh: true });
328        hideThemeToggles();
329        enableThemeTransitions();
330        window.addEventListener('resize', hideThemeToggles);
331        if (window.PollsmaxTheme) {
332            window.PollsmaxTheme.__initialized = true;
333        }
334    }
335
336    var api = {
337        __initialized: false,
338        STORAGE_KEY: STORAGE_KEY,
339        isLight: isLight,
340        getTheme: currentTheme,
341        getInitialTheme: getInitialTheme,
342        chartColors: chartColors,
343        getThemeChartColors: getThemeChartColors,
344        mapNoElectionFill: mapNoElectionFill,
345        mapStateStroke: mapStateStroke,
346        mapStateHoverStroke: mapStateHoverStroke,
347        mapFlipOutlineStroke: mapFlipOutlineStroke,
348        flipChipOutlineStyle: flipChipOutlineStyle,
349        applyTheme: applyTheme,
350        setTheme: setTheme,
351        toggleTheme: toggleTheme,
352        injectToggle: injectToggle,
353        updateThemeToggle: updateThemeToggle,
354        refreshSiteVisuals: refreshSiteVisuals
355    };
356
357    window.PollsmaxTheme = api;
358    window.PollsmaxHomeTheme = api;
359    window.getThemeChartColors = getThemeChartColors;
360
361    if (document.readyState === 'loading') {
362        document.addEventListener('DOMContentLoaded', init);
363    } else {
364        init();
365    }
366})();

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.