diff --git a/web/src/App.tsx b/web/src/App.tsx
index be710fa..2e8da78 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -123,15 +123,20 @@ export default function App() {
Plugins werden im Server konfiguriert.
) : (
- /* Render ALL tabs, hide inactive ones with CSS to preserve state */
+ /* Render ALL tabs, hide inactive ones to preserve state.
+ Active tab gets full dimensions; hidden tabs stay in DOM but invisible. */
plugins.map(p => {
const Comp = tabComponents[p.name];
if (!Comp) return null;
+ const isActive = activeTab === p.name;
return (
diff --git a/web/src/plugins/radio/RadioTab.tsx b/web/src/plugins/radio/RadioTab.tsx
index 728ec4f..60b9c89 100644
--- a/web/src/plugins/radio/RadioTab.tsx
+++ b/web/src/plugins/radio/RadioTab.tsx
@@ -89,6 +89,7 @@ export default function RadioTab({ data }: { data: any }) {
const [volume, setVolume] = useState(0.5);
const [voiceStats, setVoiceStats] = useState(null);
const [showConnModal, setShowConnModal] = useState(false);
+ const [containerVisible, setContainerVisible] = useState(false);
const searchTimeout = useRef>(undefined);
const volumeTimeout = useRef>(undefined);
const selectedGuildRef = useRef(selectedGuild);
@@ -211,15 +212,47 @@ export default function RadioTab({ data }: { data: any }) {
.catch(() => setStationsLoading(false));
};
+ // ── Watch container visibility (detects tab becoming active) ──
+ useEffect(() => {
+ const el = containerRef.current;
+ if (!el) return;
+
+ // Check immediately
+ if (el.clientWidth > 0 && el.clientHeight > 0) {
+ setContainerVisible(true);
+ }
+
+ const observer = new ResizeObserver((entries) => {
+ for (const entry of entries) {
+ const { width, height } = entry.contentRect;
+ if (width > 0 && height > 0) {
+ setContainerVisible(true);
+ }
+ }
+ });
+ observer.observe(el);
+
+ return () => observer.disconnect();
+ }, []);
+
// ── Initialize globe ──
useEffect(() => {
if (!containerRef.current || places.length === 0) return;
+ // If container is hidden (display:none), wait for it to become visible
+ const cw = containerRef.current.clientWidth;
+ const ch = containerRef.current.clientHeight;
+
if (globeRef.current) {
globeRef.current.pointsData(places);
+ // Re-apply dimensions in case we were hidden during init
+ if (cw > 0 && ch > 0) globeRef.current.width(cw).height(ch);
return;
}
+ // Don't initialize globe with zero dimensions — containerVisible will re-trigger
+ if (cw === 0 || ch === 0) return;
+
// Read accent color from theme
const initStyle = getComputedStyle(containerRef.current.parentElement!);
const initRgb = initStyle.getPropertyValue('--accent-rgb').trim() || '230, 126, 34';
@@ -286,21 +319,28 @@ export default function RadioTab({ data }: { data: any }) {
const onResize = () => {
if (containerRef.current && globeRef.current) {
- globeRef.current
- .width(containerRef.current.clientWidth)
- .height(containerRef.current.clientHeight);
+ const w = containerRef.current.clientWidth;
+ const h = containerRef.current.clientHeight;
+ if (w > 0 && h > 0) {
+ globeRef.current.width(w).height(h);
+ }
}
};
window.addEventListener('resize', onResize);
+ // ResizeObserver: detects when tab becomes visible (0×0 → real size)
+ const resizeObserver = new ResizeObserver(() => onResize());
+ resizeObserver.observe(el);
+
return () => {
controls.removeEventListener('change', onControlsChange);
el.removeEventListener('mousedown', onInteract);
el.removeEventListener('touchstart', onInteract);
el.removeEventListener('wheel', onInteract);
window.removeEventListener('resize', onResize);
+ resizeObserver.disconnect();
};
- }, [places, pauseRotation]);
+ }, [places, pauseRotation, containerVisible]);
// ── Play handler ──
const handlePlay = useCallback(async (