PageSourceSearch

https://reactivezero.com/js/theme.js?v=20260915-phone

js reactivezero.com collected 2026-09-26 14:38:32 UTC 5,059 bytes, 122 lines download raw bytes

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.