diff --git a/src/App.tsx b/src/App.tsx
index 6ac5c4fd..a04b62cd 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -54,6 +54,7 @@ import SongInfoModal from './components/SongInfoModal';
import DownloadFolderModal from './components/DownloadFolderModal';
import GlobalConfirmModal from './components/GlobalConfirmModal';
import OrbitAccountPicker from './components/OrbitAccountPicker';
+import OrbitHelpModal from './components/OrbitHelpModal';
import { DragDropProvider } from './contexts/DragDropContext';
import TooltipPortal from './components/TooltipPortal';
import OverlayScrollArea from './components/OverlayScrollArea';
@@ -522,6 +523,7 @@ function AppShell() {
+
diff --git a/src/components/OrbitHelpModal.tsx b/src/components/OrbitHelpModal.tsx
new file mode 100644
index 00000000..7d565671
--- /dev/null
+++ b/src/components/OrbitHelpModal.tsx
@@ -0,0 +1,102 @@
+import { useEffect } from 'react';
+import { createPortal } from 'react-dom';
+import {
+ X, Sparkles, Users, Share2, LogIn, MousePointerClick,
+ ListMusic, Inbox, Sliders, LogOut,
+} from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+import { useHelpModalStore } from '../store/helpModalStore';
+import SettingsSubSection from './SettingsSubSection';
+
+/**
+ * Orbit help modal. Rendered once at the app root; triggered from the
+ * launch popover ("How does this work?") and the in-session bar's help
+ * button. 9 accordion sections built on SettingsSubSection; all default
+ * closed so the modal opens compact. Does not touch playback.
+ */
+export default function OrbitHelpModal() {
+ const { t } = useTranslation();
+ const { isOpen, close } = useHelpModalStore();
+
+ useEffect(() => {
+ if (!isOpen) return;
+ const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') close(); };
+ window.addEventListener('keydown', onKey);
+ return () => window.removeEventListener('keydown', onKey);
+ }, [isOpen, close]);
+
+ if (!isOpen) return null;
+
+ const hostOnlyLabel = t('orbit.helpHostOnly');
+
+ return createPortal(
+
{ if (e.target === e.currentTarget) close(); }}
+ role="dialog"
+ aria-modal="true"
+ aria-labelledby="orbit-help-title"
+ style={{ alignItems: 'center', paddingTop: 0 }}
+ >
+
+
+
+
+ {t('orbit.helpTitle')}
+
+
{t('orbit.helpIntro')}
+
+
+
}>
+
{t('orbit.helpSec1Body')}
+
+
+
}>
+
{t('orbit.helpSec2Body')}
+
+ {t('orbit.helpSec2WarnHead')}
+ {t('orbit.helpSec2WarnBody')}
+
+
+
+
}>
+
{t('orbit.helpSec3Body')}
+
+
+
}>
+
{t('orbit.helpSec4Body')}
+
+
+
}>
+
{t('orbit.helpSec5Body')}
+
+
+
}>
+
{t('orbit.helpSec6Body')}
+
+
+
}
+ >
+
{t('orbit.helpSec7Body')}
+
+
+
}
+ >
+
{t('orbit.helpSec8Body')}
+
+
+
}>
+
{t('orbit.helpSec9Body')}
+
+
+
+
,
+ document.body,
+ );
+}
diff --git a/src/components/OrbitSessionBar.tsx b/src/components/OrbitSessionBar.tsx
index f0933b4c..35dd8d24 100644
--- a/src/components/OrbitSessionBar.tsx
+++ b/src/components/OrbitSessionBar.tsx
@@ -1,7 +1,8 @@
import { useEffect, useRef, useState } from 'react';
-import { X, RefreshCw, Shuffle, Settings2, Share2 } from 'lucide-react';
+import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '../store/orbitStore';
+import { useHelpModalStore } from '../store/helpModalStore';
import { usePlayerStore, songToTrack } from '../store/playerStore';
import { getSong } from '../api/subsonic';
import {
@@ -208,6 +209,15 @@ export default function OrbitSessionBar() {
{t('orbit.catchUpLabel')}
)}
+