diff --git a/src/App.css b/src/App.css
index eab9bdd..702340e 100644
--- a/src/App.css
+++ b/src/App.css
@@ -47,6 +47,15 @@
color: #cce6ff;
border-color: #2a5a8c;
}
+.palette-btn.bcast-all.on {
+ background: #4a3010;
+ color: #f0c060;
+ border-color: #c98a1f;
+}
+.palette-btn.bcast-all.on.partial {
+ background: #2a2010;
+ color: #c98a1f;
+}
.preset-btn {
min-width: 28px;
text-align: center;
diff --git a/src/App.tsx b/src/App.tsx
index 5174ceb..7a4b5ef 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -21,6 +21,7 @@ import {
changeDistro,
changeLabel,
toggleBroadcast as toggleBroadcastInTree,
+ setAllBroadcast,
serialize,
deserialize,
presetSingle,
@@ -282,6 +283,29 @@ export default function App() {
[paletteOpen, tree],
);
+ // ---- global broadcast state (derived from tree) -------------------------
+ const broadcastStats = useMemo(() => {
+ let on = 0;
+ let total = 0;
+ for (const leaf of walkLeaves(tree)) {
+ total++;
+ if (leaf.broadcast) on++;
+ }
+ return { on, total };
+ }, [tree]);
+
+ const toggleBroadcastAll = useCallback(() => {
+ // If any pane is broadcasting, turn them all off. Otherwise turn them all on.
+ setTree((t) => setAllBroadcast(t, broadcastStats.on === 0));
+ }, [broadcastStats.on]);
+
+ const broadcastBtnLabel =
+ broadcastStats.on === 0
+ ? "📡 all off"
+ : broadcastStats.on === broadcastStats.total
+ ? "📡 all on"
+ : `📡 ${broadcastStats.on}/${broadcastStats.total}`;
+
const onPalettePick = useCallback((leafId: string) => {
setActiveLeafId(leafId);
setPaletteOpen(false);
@@ -321,6 +345,20 @@ export default function App() {
+
+