// CDJ-900NXS Mods — small helpers: theme toggle, before/after slider, docs TOC highlight.
(function () {
var root = document.documentElement;
function storedTheme() {
try { return localStorage.getItem('theme'); } catch (e) { return null; }
}
var t = storedTheme();
if (t === 'light' || t === 'dark') root.setAttribute('data-theme', t);
document.addEventListener('DOMContentLoaded', function () {
var btn = document.querySelector('.theme-btn');
function label() {
if (btn) btn.textContent = root.getAttribute('data-theme') === 'light' ? '☾' : '☀';
}
label();
if (btn) btn.addEventListener('click', function () {
var next = root.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
root.setAttribute('data-theme', next);
try { localStorage.setItem('theme', next); } catch (e) { /* private mode */ }
label();
});
// version badge and changelog (assets/versions.js, made by tools/make_builder_data.py)
var V = window.CDJ_VERSIONS;
var head = document.querySelector('.top .wrap');
if (V && head) {
var esc = function (t) { return String(t).replace(/[&<>]/g, function (c) { return { '&': '&', '<': '<', '>': '>' }[c]; }); };
var vb = document.createElement('button');
vb.type = 'button'; vb.className = 'ver-btn'; vb.textContent = 'v' + V.site;
vb.setAttribute('aria-haspopup', 'dialog'); vb.title = 'Changelog';
var brand = head.querySelector('.brand');
if (brand && brand.nextSibling) head.insertBefore(vb, brand.nextSibling); else head.appendChild(vb);
var pop = document.createElement('div');
pop.className = 'ver-pop'; pop.setAttribute('role', 'dialog'); pop.setAttribute('aria-label', 'Changelog');
var html = '
' +
'
Changelog
Site ' + esc(V.site) + ' · ' + esc(V.date) + ' · Semantic Versioning, dates ISO 8601
';
V.log.forEach(function (e) {
html += '
' + esc(e.version) + '' + esc(e.date) + '
' +
e.changes.map(function (c) { return '- ' + esc(c) + '
'; }).join('') + '
';
});
html += '
Mods
' + Object.keys(V.mods).map(function (m) {
return '' + esc(m) + '' + esc(V.mods[m]) + '';
}).join('') + '
';
pop.innerHTML = html;
document.body.appendChild(pop);
var close = function () { pop.classList.remove('open'); vb.focus(); };
vb.addEventListener('click', function () { pop.classList.add('open'); pop.querySelector('.close').focus(); });
pop.querySelector('.close').addEventListener('click', close);
pop.addEventListener('click', function (e) { if (e.target === pop) close(); });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && pop.classList.contains('open')) close(); });
// every mod card's version beside its name
document.querySelectorAll('.card h3 code').forEach(function (c) {
var v = V.mods[c.textContent.trim()];
if (v) { var s = document.createElement('span'); s.className = 'ver'; s.textContent = 'v' + v; c.parentNode.appendChild(s); }
});
}
// before / after
document.querySelectorAll('.compare').forEach(function (box) {
var range = box.querySelector('input[type="range"]');
if (!range) return;
function set() { box.style.setProperty('--pos', range.value + '%'); }
range.addEventListener('input', set);
set();
});
// docs: highlight the section in view
var links = Array.prototype.slice.call(document.querySelectorAll('.toc a[href^="#"]'));
if (links.length && 'IntersectionObserver' in window) {
var byId = {};
links.forEach(function (a) { byId[a.getAttribute('href').slice(1)] = a; });
var obs = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting && byId[e.target.id]) {
links.forEach(function (a) { a.classList.remove('active'); });
byId[e.target.id].classList.add('active');
}
});
}, { rootMargin: '-80px 0px -70% 0px' });
Object.keys(byId).forEach(function (id) {
var el = document.getElementById(id);
if (el) obs.observe(el);
});
}
});
})();