diff --git a/plugins/audio-visualizer-luna/src/Settings.tsx b/plugins/audio-visualizer-luna/src/Settings.tsx
index d7e8297..1c27e9b 100644
--- a/plugins/audio-visualizer-luna/src/Settings.tsx
+++ b/plugins/audio-visualizer-luna/src/Settings.tsx
@@ -54,6 +54,7 @@ export const settings = await ReactiveStore.getPluginStorage(
scrollingOscilloscope: false,
groupedSlots: false,
transparentContainers: false,
+ idleMode: 1,
miniSlots: [] as string[],
customColors: [] as string[],
},
@@ -92,6 +93,7 @@ export const Settings = () => {
const [transparentContainers, setTransparentContainers] = React.useState(
settings.transparentContainers,
);
+ const [idleMode, setIdleMode] = React.useState(settings.idleMode);
const [showColorPicker, setShowColorPicker] = React.useState(false);
const [isColorAnimIn, setIsColorAnimIn] = React.useState(false);
@@ -313,6 +315,21 @@ export const Settings = () => {
}}
/>
+ ) => {
+ const v = Number(e.target.value);
+ setIdleMode(v);
+ settings.idleMode = v;
+ }}
+ >
+ Enabled
+ Disabled & Hide
+ Disabled & Static
+
+
{/* Color picker modal */}
{shouldRenderColor && (
<>
diff --git a/plugins/audio-visualizer-luna/src/index.ts b/plugins/audio-visualizer-luna/src/index.ts
index 0a80177..a67282a 100644
--- a/plugins/audio-visualizer-luna/src/index.ts
+++ b/plugins/audio-visualizer-luna/src/index.ts
@@ -46,6 +46,7 @@ interface SlotGroup {
const groups = new Map();
let navArrowsEl: HTMLElement | null = null;
+let idleHidden = false;
const getSlot = (key: SlotKey): VisualizerType =>
(settings as unknown as Record)[key] ?? "none";
@@ -134,8 +135,9 @@ const syncGroupHeights = (group: SlotGroup): void => {
const updateGroupVisibility = (group: SlotGroup): void => {
const activeCount = group.slots.filter(s => s.currentType !== "none").length;
const allNone = activeCount === 0;
- group.groupContainer.style.display = allNone ? "none" : "flex";
- if (!allNone) syncGroupHeights(group);
+ const hidden = allNone || idleHidden;
+ group.groupContainer.style.display = hidden ? "none" : "flex";
+ if (!hidden) syncGroupHeights(group);
group.groupContainer.classList.toggle(
"av-grouped",
@@ -143,7 +145,7 @@ const updateGroupVisibility = (group: SlotGroup): void => {
);
if (group === groups.get("topNav-left") && navArrowsEl) {
- navArrowsEl.style.marginRight = allNone ? "" : "0";
+ navArrowsEl.style.marginRight = hidden ? "" : "0";
}
};
@@ -398,6 +400,19 @@ const generateIdleData = (): AudioData => {
};
};
+// Static idle data — flat, silent, no movement
+const STATIC_IDLE_DATA: AudioData = {
+ byteFrequency: new Uint8Array(IDLE_SIZE),
+ byteTimeDomain: new Uint8Array(IDLE_SIZE).fill(128),
+ floatFrequency: new Float32Array(IDLE_SIZE).fill(-100),
+ floatTimeDomain: new Float32Array(IDLE_SIZE),
+ leftTimeDomain: new Float32Array(IDLE_SIZE),
+ rightTimeDomain: new Float32Array(IDLE_SIZE),
+ sampleRate: 44100,
+ fftSize: IDLE_SIZE * 2,
+ binCount: IDLE_SIZE,
+};
+
// Animation Loop
let animationId: number | null = null;
@@ -478,12 +493,24 @@ const animate = (): void => {
scheduleRetry();
}
- const renderData = hasSignal ? data : generateIdleData();
+ // idleMode: 0 = animated, 1 = hide when idle, 2 = static when idle
+ const idleMode = settings.idleMode ?? 0;
+ const newIdleHidden = !hasSignal && idleMode === 1;
+ if (newIdleHidden !== idleHidden) {
+ idleHidden = newIdleHidden;
+ for (const group of groups.values()) updateGroupVisibility(group);
+ }
- for (const group of groups.values()) {
- for (const slot of group.slots) {
- if (!slot.canvas || slot.currentType === "none" || !slot.visualizer) continue;
- slot.visualizer.render(renderData, settings.barColor);
+ if (!idleHidden) {
+ let renderData: AudioData;
+ if (hasSignal) renderData = data as AudioData;
+ else if (idleMode === 2) renderData = STATIC_IDLE_DATA;
+ else renderData = generateIdleData();
+ for (const group of groups.values()) {
+ for (const slot of group.slots) {
+ if (!slot.canvas || slot.currentType === "none" || !slot.visualizer) continue;
+ slot.visualizer.render(renderData, settings.barColor);
+ }
}
}