Nav groups are expanded by default. Click headers to collapse. Remembers collapsed state across refreshes.
// ==UserScript==
// @name BlackRidge - Nav Always Expanded
// @namespace blackridge.alwaysdisplay
// @version 1.0.1
// @description Nav groups are expanded by default. Click headers to collapse. Remembers collapsed state across refreshes.
// @author User
// @match https://blackridgerpg.com/*
// @match https://www.blackridgerpg.com/*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'br-nav-collapsed';
const NAV_GROUPS = [
'navgroup-city',
'navgroup-ops',
'navgroup-arsenal',
'navgroup-finances',
'navgroup-bureau'
];
let isDelegatedToggleBound = false;
let navObserver = null;
let applyScheduled = false;
// Load collapsed set from localStorage
function loadCollapsed() {
try {
return new Set(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'));
} catch {
return new Set();
}
}
// Save collapsed set to localStorage
function saveCollapsed(collapsedSet) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify([...collapsedSet]));
} catch {
// Ignore storage failures; the current click should still update the UI.
}
}
// Apply open/closed visual state to a group
function applyGroupState(id, isCollapsed) {
const group = document.getElementById(id);
if (!group) return;
const items = group.querySelector('.nav-group-items');
const arrow = group.querySelector('.nav-group-arrow');
const header = group.querySelector('.nav-group-header');
group.classList.toggle('open', !isCollapsed);
if (items) {
const desiredDisplay = isCollapsed ? 'none' : '';
if (items.style.display !== desiredDisplay) {
items.style.display = desiredDisplay;
}
}
if (arrow) {
const desiredArrow = isCollapsed ? '\u25b8' : '\u25be';
if (arrow.textContent !== desiredArrow) {
arrow.textContent = desiredArrow;
}
}
if (header) {
const desiredExpanded = String(!isCollapsed);
if (header.getAttribute('aria-expanded') !== desiredExpanded) {
header.setAttribute('aria-expanded', desiredExpanded);
}
if (header.hasAttribute('onclick')) {
header.removeAttribute('onclick');
}
}
}
function toggleGroup(id, collapsedSet) {
if (collapsedSet.has(id)) {
collapsedSet.delete(id);
} else {
collapsedSet.add(id);
}
saveCollapsed(collapsedSet);
applyGroupState(id, collapsedSet.has(id));
}
function applyAllStates(collapsedSet) {
NAV_GROUPS.forEach(id => {
applyGroupState(id, collapsedSet.has(id));
});
}
function bindDelegatedToggle(collapsedSet) {
if (isDelegatedToggleBound) return;
document.addEventListener('click', event => {
if (!(event.target instanceof Element)) return;
const header = event.target.closest('.nav-group-header');
if (!header) return;
const group = header.closest('.nav-group');
if (!group || !NAV_GROUPS.includes(group.id)) return;
// Use script-controlled toggle behavior and avoid conflicts with inline handlers.
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
header.removeAttribute('onclick');
toggleGroup(group.id, collapsedSet);
}, true);
isDelegatedToggleBound = true;
}
function scheduleApplyAllStates(collapsedSet) {
if (applyScheduled) return;
applyScheduled = true;
requestAnimationFrame(() => {
applyScheduled = false;
applyAllStates(collapsedSet);
});
}
function watchNavRerenders(collapsedSet) {
if (navObserver) return;
navObserver = new MutationObserver(() => {
scheduleApplyAllStates(collapsedSet);
});
navObserver.observe(document.documentElement || document, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'onclick', 'style']
});
}
function init() {
const collapsed = loadCollapsed();
bindDelegatedToggle(collapsed);
watchNavRerenders(collapsed);
applyAllStates(collapsed);
scheduleApplyAllStates(collapsed);
}
init();
})();