fix(highway_3d): re-home Butterchurn panel to a surviving highway (splitscreen) (#599)

The Butterchurn control panel is a singleton, created only when a controller
is created and parented to that controller's wrap. In splitscreen the panel
followed the last-created controller; when that controller was torn down,
destroy() only removed the panel DOM if it was the LAST controller, so with
another highway still alive the panel stayed orphaned on the destroyed wrap
and the surviving highway was left with no visualizer controls.

Track each controller's wrap (ctrl.wrap) and, on destroy with another
controller still alive, re-home the panel+pane onto the surviving primary's
wrap via _bcEnsurePanel (which moves them when connected, or rebuilds them on
the survivor if the old wrap was already detached).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Byron Gamatos
2026-06-26 14:08:13 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 3bb55ab854
commit 6fa5aabba8
+11 -1
View File
@@ -459,7 +459,7 @@
// Create a Butterchurn background controller bound to a wrap element.
function _bcCreateController(wrap, sizeProvider, audioProvider) {
const ctrl = { viz: null, actx: null, guitar: null, map: null, keys: [], cycle: 0, dead: false, lastW: -1, lastH: -1, canvas: null, backdrop: null, scrim: null, tint: null };
const ctrl = { viz: null, actx: null, guitar: null, map: null, keys: [], cycle: 0, dead: false, lastW: -1, lastH: -1, canvas: null, backdrop: null, scrim: null, tint: null, wrap: wrap };
// Layered DOM in the wrap, all BEHIND the transparent 3D highway:
// backdrop(z-4 dark) → bc canvas(z-3) → tint(z-2 instrument color) → scrim(z-1 lane dim)
const mkLayer = (cls, css) => { const d = document.createElement('div'); d.className = cls; d.style.cssText = css; wrap.appendChild(d); return d; };
@@ -661,6 +661,16 @@
if (_bcPanel && _bcPanel.parentNode) _bcPanel.parentNode.removeChild(_bcPanel);
if (_bcPane && _bcPane.parentNode) _bcPane.parentNode.removeChild(_bcPane);
_bcPanel = null; _bcPane = null; _bcListEl = null; _bcFilterEl = null; _bcPaneOpen = false;
} else if (_bcPrimary && _bcPrimary.wrap) {
// Splitscreen: a controller other than this one is still
// alive. The singleton panel was parented to THIS (now
// destroyed) wrap, so re-home it onto the surviving primary's
// wrap — otherwise the panel is orphaned on the dead wrap and
// the surviving highway is left with no visualizer controls
// (_bcEnsurePanel only runs at controller creation). It moves
// the existing panel+pane when connected, or rebuilds them on
// the survivor if this wrap was already detached.
try { _bcEnsurePanel(_bcPrimary.wrap); _bcUpdatePanelPreset(); } catch (e) {}
}
},
};