1/* ReactiveZero theme controller. 2 Loaded synchronously in <head> so the saved theme applies before first paint. */ 3(function () { 4 'use strict'; 5 6 var KEY = 'rz-theme'; 7 var root = document.documentElement; 8 var stored = null; 9 try { stored = localStorage.getItem(KEY); } catch (e) {} 10 if (stored !== 'dark' && stored !== 'light') stored = null; 11 12 /* before a choice is made, follow the OS preference */ 13 var initial = stored; 14 if (!initial) { 15 try { 16 initial = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 17 } catch (e) { initial = 'light'; } 18 } 19 root.setAttribute('data-theme', initial); 20 21 function apply(theme, persist) { 22 theme = theme === 'dark' ? 'dark' : 'light'; 23 root.setAttribute('data-theme', theme); 24 if (persist) { 25 try { localStorage.setItem(KEY, theme); } catch (e) {} 26 stored = theme; 27 } 28 try { 29 document.dispatchEvent(new CustomEvent('rz-theme-change', { detail: { theme: theme } })); 30 } catch (e) {} 31 } 32 33 window.rzSetTheme = function (theme) { apply(theme, true); }; 34 35 var SUN = '<svg class="rz-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="4.2"/><path d="M12 2.5v2.4M12 19.1v2.4M2.5 12h2.4M19.1 12h2.4M5.2 5.2l1.7 1.7M17.1 17.1l1.7 1.7M18.8 5.2l-1.7 1.7M6.9 17.1l
35-1.7 1.7"/></svg>'; 36 var MOON = '<svg class="rz-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20.4 14.2A8.6 8.6 0 0 1 9.8 3.6a8.6 8.6 0 1 0 10.6 10.6z"/></svg>'; 37 38 function injectToggle() { 39 if (document.getElementById('rz-theme-toggle')) return; 40 var menu = document.querySelector('.navbar2_menu'); 41 if (!menu) return; 42 var btn = document.createElement('button'); 43 btn.id = 'rz-theme-toggle'; 44 btn.className = 'rz-theme-toggle'; 45 btn.type = 'button'; 46 btn.setAttribute('aria-label', 'Toggle dark / light mode'); 47 btn.innerHTML = MOON + SUN; 48 btn.addEventListener('click', function () { 49 apply(root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark', true); 50 }); 51 menu.appendChild(btn); 52 } 53 54 function showChooser() { 55 if (document.getElementById('rz-theme-overlay')) return; 56 var overlay = document.createElement('div'); 57 overlay.id = 'rz-theme-overlay'; 58 overlay.setAttribute('role', 'dialog'); 59 overlay.setAttribute('aria-modal', 'true'); 60 overlay.setAttribute('aria-labelledby', 'rz-theme-modal-title'); 61 overlay.innerHTML = 62 '<div id="rz-theme-modal">' + 63 '<span class="rz-theme-modal-eyebrow">Display Mode</span>' + 64 '<h3 id="rz-theme-modal-title">Choose your interface</h3>' + 65 '<p>Pick how ReactiveZero renders for you. You can switch anytime from the navigation bar.</p>' + 66 '<div class="rz-theme-opts">' + 67 '<button type="button" class="rz-theme-opt" data-pick="dark">' + 68 '<span class="rz-theme-swatch rz-swatch-dark"><span class="rz-swatch-spectrum"></span></span>' + 69 '<strong>Dark</strong><small>Blueprint on black</small>' + 70 '</button>' + 71 '<button type="button" class="rz-theme-opt" data-pick="light">' + 72 '<span class="rz-theme-swatch rz-swatch-light"><span class="rz-swatch-spectrum"></span></span>' + 73 '<strong>Light</strong><small>Blueprint on white</small>' + 74 '</button>' + 75 '</div>' + 76 '</div>'; 77 document.body.appendChild(overlay); 78 if (window.rzApplyI18n) window.rzApplyI18n(); 79 80 var preHover = root.getAttribute('data-theme'); 81 82 function closeChooser() { 83 overlay.classList.remove('open'); 84 document.removeEventListener('keydown', onKey); 85 setTimeout(function () { 86 if (overlay.parentNode) overlay.parentNode.removeChild(overlay); 87 }, 320); 88 } 89 90 function onKey(e) { 91 if (e.key === 'Escape') { apply(preHover, false); closeChooser(); } 92 } 93 94 overlay.querySelectorAll('.rz-theme-opt').forEach(function (btn) { 95 var pick = btn.getAttribute('data-pick'); 96 /* live preview while hovering an option */ 97 btn.addEventListener('mouseenter', function () { apply(pick, false); }); 98 btn.addEventListener('mouseleave', function () { apply(stored || preHover, false); }); 99 btn.addEventListener('click', function () { apply(pick, true); closeChooser(); }); 100 }); 101 /* dismiss without saving: ask again next visit */ 102 overlay.addEventListener('click', function (e) { 103 if (e.target === overlay) { apply(preHover, false); closeChooser(); } 104 }); 105 document.addEventListener('keydown', onKey); 106 107 requestAnimationFrame(function () { overlay.classList.add('open'); }); 108 var first = overlay.querySelector('.rz-theme-opt'); 109 if (first) first.focus(); 110 } 111 112 function onReady() { 113 injectToggle(); 114 if (!stored) showChooser(); 115 } 116 117 if (document.readyState === 'loading') { 118 document.addEventListener('DOMContentLoaded', onReady); 119 } else { 120 onReady(); 121 } 122})();
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.