// The light/dark switch. The stylesheet already carries both themes and // already follows the reader's system setting on its own — all this adds is // a way to override that, by setting the data-theme attribute the CSS is // written to look for. Progressive enhancement, like the copy behaviour // below: without it the sheet still tracks the system setting, and the // control is not printed. // // No attribute is set unless the reader asks for one, so "follow my system" // stays the state a sheet arrives in. (function () { var KEY = 'bi122g-sheet-theme'; var root = document.documentElement; // A sheet is often read from a file:// URL or a locked-down browser, where // storage throws rather than returning null. Losing the preference is fine; // losing the switch is not. function stored() { try { return localStorage.getItem(KEY); } catch (e) { return null; } } function remember(v) { try { localStorage.setItem(KEY, v); } catch (e) {} } var system = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)'); var saved = stored(); var choice = saved === 'dark' || saved === 'light' ? saved : null; // Normally already set by the head script the exporter writes (see // document.js) — repeated here so the switch also works in any page that // includes this file on its own. if (choice) root.setAttribute('data-theme', choice); function current() { return choice || (system && system.matches ? 'dark' : 'light'); } var btn = document.createElement('button'); btn.className = 'themetoggle'; btn.type = 'button'; function label() { var next = current() === 'dark' ? 'light' : 'dark'; btn.textContent = current() === 'dark' ? '☀' : '☾'; btn.setAttribute('title', 'Switch to ' + next + ' mode'); btn.setAttribute('aria-label', 'Switch to ' + next + ' mode'); } btn.addEventListener('click', function () { choice = current() === 'dark' ? 'light' : 'dark'; root.setAttribute('data-theme', choice); remember(choice); label(); }); // still following the system: keep the glyph honest if it changes mid-read if (system && system.addEventListener) { system.addEventListener('change', function () { if (!choice) label(); }); } label(); document.body.appendChild(btn); })(); // Click anywhere on a command box to copy it. Progressive enhancement only: // the page is complete without this, and nothing of it appears in print. document.querySelectorAll('.cmd').forEach(function (block) { var payload = block.textContent.trim(); block.setAttribute('role', 'button'); block.setAttribute('tabindex', '0'); block.setAttribute('aria-label', 'Copy command: ' + payload); function copy() { navigator.clipboard.writeText(payload).then(function () { block.classList.add('copied'); setTimeout(function () { block.classList.remove('copied'); }, 1400); }).catch(function () { // clipboard blocked (insecure context, or permission denied) — select // the text instead so the reader can copy it by hand var range = document.createRange(); range.selectNodeContents(block); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); }); } block.addEventListener('click', copy); block.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); copy(); } }); });