diff --git a/electron/windows.ts b/electron/windows.ts
index a7623668..f5a46a54 100644
--- a/electron/windows.ts
+++ b/electron/windows.ts
@@ -28,8 +28,8 @@ export function createHudOverlayWindow(): BrowserWindow {
const { workArea } = primaryDisplay;
- const windowWidth = 600;
- const windowHeight = 450;
+ const windowWidth = 620;
+ const windowHeight = 520;
const x = Math.floor(workArea.x + (workArea.width - windowWidth) / 2);
const y = Math.floor(workArea.y + workArea.height - windowHeight - 5);
@@ -37,10 +37,10 @@ export function createHudOverlayWindow(): BrowserWindow {
const win = new BrowserWindow({
width: windowWidth,
height: windowHeight,
- minWidth: 600,
- maxWidth: 600,
- minHeight: 450,
- maxHeight: 450,
+ minWidth: 620,
+ maxWidth: 620,
+ minHeight: 520,
+ maxHeight: 520,
x: x,
y: y,
frame: false,
diff --git a/src/components/launch/LaunchWindow.module.css b/src/components/launch/LaunchWindow.module.css
index 30304580..2ca0623e 100644
--- a/src/components/launch/LaunchWindow.module.css
+++ b/src/components/launch/LaunchWindow.module.css
@@ -9,11 +9,11 @@
.bar {
display: flex;
align-items: center;
- gap: 6px;
+ gap: 8px;
background: rgba(18, 18, 24, 0.97);
border: 1px solid rgba(255, 255, 255, 0.07);
- border-radius: 16px;
- padding: 7px 10px;
+ border-radius: 18px;
+ padding: 10px 14px;
box-shadow:
0 8px 40px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
@@ -23,7 +23,7 @@
.sep {
width: 1px;
- height: 22px;
+ height: 26px;
background: #2a2a34;
margin: 0 4px;
flex-shrink: 0;
@@ -31,9 +31,9 @@
.ib {
position: relative;
- width: 36px;
- height: 36px;
- border-radius: 10px;
+ width: 40px;
+ height: 40px;
+ border-radius: 11px;
border: none;
background: transparent;
color: #6b6b78;
@@ -79,9 +79,9 @@
display: inline-flex;
align-items: center;
gap: 7px;
- height: 36px;
- padding: 0 12px 0 10px;
- border-radius: 10px;
+ height: 40px;
+ padding: 0 14px 0 12px;
+ border-radius: 11px;
border: 1px solid #2a2a34;
background: #1a1a22;
color: #eeeef2;
@@ -97,23 +97,35 @@
background: #20202a;
}
-.dropdown {
- width: 260px;
- max-height: 320px;
+.menuArea {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ pointer-events: none;
+ overflow: hidden;
+ min-height: 0;
+}
+
+.menuCard {
+ width: 300px;
+ max-height: 400px;
overflow-y: auto;
background: rgba(22, 22, 30, 0.96);
border: 1px solid rgba(255, 255, 255, 0.07);
- border-radius: 12px;
- padding: 6px;
+ border-radius: 14px;
+ padding: 8px;
+ margin-top: auto;
margin-bottom: 8px;
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
- animation: dropdownIn 0.15s ease;
+ pointer-events: auto;
+ animation: menuCardIn 0.18s ease;
}
-@keyframes dropdownIn {
+@keyframes menuCardIn {
from {
opacity: 0;
- transform: translateY(8px);
+ transform: translateY(12px);
}
to {
opacity: 1;
@@ -121,15 +133,15 @@
}
}
-.dropdown::-webkit-scrollbar {
+.menuCard::-webkit-scrollbar {
width: 4px;
}
-.dropdown::-webkit-scrollbar-track {
+.menuCard::-webkit-scrollbar-track {
background: transparent;
}
-.dropdown::-webkit-scrollbar-thumb {
+.menuCard::-webkit-scrollbar-thumb {
background: #2a2a34;
border-radius: 2px;
}
@@ -170,8 +182,8 @@
.recBtn {
position: relative;
- width: 42px;
- height: 42px;
+ width: 46px;
+ height: 46px;
border-radius: 50%;
border: none;
background: #f43f5e;
diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx
index e97cae17..3e5da6e4 100644
--- a/src/components/launch/LaunchWindow.tsx
+++ b/src/components/launch/LaunchWindow.tsx
@@ -100,6 +100,33 @@ function Separator() {
return
;
}
+function MicDeviceRow({
+ device,
+ selected,
+ onSelect,
+}: {
+ device: { deviceId: string; label: string };
+ selected: boolean;
+ onSelect: () => void;
+}) {
+ const { level } = useAudioLevelMeter({
+ enabled: true,
+ deviceId: device.deviceId,
+ });
+
+ return (
+
+ );
+}
+
export function LaunchWindow() {
const { locale, setLocale } = useI18n();
const t = useScopedT("launch");
@@ -126,18 +153,14 @@ export function LaunchWindow() {
const [selectedSource, setSelectedSource] = useState("Screen");
const [hasSelectedSource, setHasSelectedSource] = useState(false);
const [recordingsDirectory, setRecordingsDirectory] = useState(null);
- const [activeDropdown, setActiveDropdown] = useState<"none" | "sources" | "more">("none");
+ const [activeDropdown, setActiveDropdown] = useState<"none" | "sources" | "more" | "mic">("none");
const [sources, setSources] = useState([]);
const [sourcesLoading, setSourcesLoading] = useState(false);
const dropdownRef = useRef(null);
- const showMicControls = microphoneEnabled && !recording;
+ const micDropdownOpen = activeDropdown === "mic";
const { devices, selectedDeviceId, setSelectedDeviceId } =
- useMicrophoneDevices(microphoneEnabled);
- const { level } = useAudioLevelMeter({
- enabled: showMicControls,
- deviceId: microphoneDeviceId,
- });
+ useMicrophoneDevices(microphoneEnabled || micDropdownOpen);
useEffect(() => {
if (selectedDeviceId && selectedDeviceId !== "default") {
@@ -260,7 +283,7 @@ export function LaunchWindow() {
}
}, []);
- const toggleDropdown = (which: "sources" | "more") => {
+ const toggleDropdown = (which: "sources" | "more" | "mic") => {
setActiveDropdown(activeDropdown === which ? "none" : which);
if (activeDropdown !== which && which === "sources") fetchSources();
};
@@ -302,7 +325,8 @@ export function LaunchWindow() {
};
const toggleMicrophone = () => {
- if (!recording) setMicrophoneEnabled(!microphoneEnabled);
+ if (recording) return;
+ toggleDropdown("mic");
};
const screenSources = sources.filter((s) => s.sourceType === "screen");
@@ -310,113 +334,140 @@ export function LaunchWindow() {
return (
- {activeDropdown !== "none" && (
-
- {activeDropdown === "sources" && (
- <>
- {sourcesLoading ? (
-
- ) : (
- <>
- {screenSources.length > 0 && (
- <>
-
Screens
- {screenSources.map((source) => (
-
}
- selected={selectedSource === source.name}
- onClick={() => handleSourceSelect(source)}
- >
- {source.name}
-
- ))}
- >
- )}
- {windowSources.length > 0 && (
- <>
-
0 ? { marginTop: 4 } : undefined}>
- Windows
+ {/* Top area — flex:1 reserves space, menu card sits at bottom of this area */}
+
+ {activeDropdown !== "none" && (
+
+ {activeDropdown === "sources" && (
+ <>
+ {sourcesLoading ? (
+
+ ) : (
+ <>
+ {screenSources.length > 0 && (
+ <>
+
Screens
+ {screenSources.map((source) => (
+
}
+ selected={selectedSource === source.name}
+ onClick={() => handleSourceSelect(source)}
+ >
+ {source.name}
+
+ ))}
+ >
+ )}
+ {windowSources.length > 0 && (
+ <>
+
0 ? { marginTop: 4 } : undefined}>
+ Windows
+
+ {windowSources.map((source) => (
+
}
+ selected={selectedSource === source.name}
+ onClick={() => handleSourceSelect(source)}
+ >
+ {source.appName && source.appName !== source.name
+ ? `${source.appName} — ${source.name}`
+ : source.name}
+
+ ))}
+ >
+ )}
+ {screenSources.length === 0 && windowSources.length === 0 && (
+
+ No sources found
- {windowSources.map((source) => (
-
}
- selected={selectedSource === source.name}
- onClick={() => handleSourceSelect(source)}
- >
- {source.appName && source.appName !== source.name
- ? `${source.appName} — ${source.name}`
- : source.name}
-
- ))}
- >
- )}
- {screenSources.length === 0 && windowSources.length === 0 && (
-
- No sources found
-
- )}
- >
- )}
- >
- )}
+ )}
+ >
+ )}
+ >
+ )}
- {activeDropdown === "more" && (
- <>
-
:
}
- onClick={() => setSystemAudioEnabled(!systemAudioEnabled)}
- trailing={systemAudioEnabled ?
✓ : undefined}
- >
- System Audio
-
-
} onClick={chooseRecordingsDirectory}>
- Recordings Folder
-
-
} onClick={openVideoFile}>
- {t("recording.openVideoFile")}
-
-
} onClick={openProjectFile}>
- {t("recording.openProject")}
-
-
Language
- {SUPPORTED_LOCALES.map((code) => (
+ {activeDropdown === "mic" && (
+ <>
+
Microphone
+ {microphoneEnabled && (
+
}
+ onClick={() => { setMicrophoneEnabled(false); setActiveDropdown("none"); }}
+ >
+ Turn Off Microphone
+
+ )}
+ {!microphoneEnabled && (
+
+ Select a microphone to enable
+
+ )}
+ {devices.map((device) => (
+
{
+ setMicrophoneEnabled(true);
+ setSelectedDeviceId(device.deviceId);
+ setMicrophoneDeviceId(device.deviceId);
+ }}
+ />
+ ))}
+ {devices.length === 0 && (
+
+ No microphones found
+
+ )}
+ >
+ )}
+
+ {activeDropdown === "more" && (
+ <>
}
- selected={locale === code}
- onClick={() => { setLocale(code as AppLocale); setActiveDropdown("none"); }}
+ icon={systemAudioEnabled ? : }
+ onClick={() => setSystemAudioEnabled(!systemAudioEnabled)}
+ trailing={systemAudioEnabled ? ✓ : undefined}
>
- {LOCALE_LABELS[code] ?? code}
+ System Audio
- ))}
- >
- )}
-
- )}
+
} onClick={chooseRecordingsDirectory}>
+ Recordings Folder
+
+
} onClick={openVideoFile}>
+ {t("recording.openVideoFile")}
+
+
} onClick={openProjectFile}>
+ {t("recording.openProject")}
+
+
Language
+ {SUPPORTED_LOCALES.map((code) => (
+
}
+ selected={locale === code}
+ onClick={() => { setLocale(code as AppLocale); setActiveDropdown("none"); }}
+ >
+ {LOCALE_LABELS[code] ?? code}
+
+ ))}
+ >
+ )}
+
+ )}
+
- {showMicControls && (
-
-
-
-
- )}
-
-
+ {/* Bottom section — fixed height, bar always stays here */}
+
+
@@ -503,6 +554,7 @@ export function LaunchWindow() {
>
)}
+
);
diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts
index d6af4091..26a43851 100644
--- a/src/hooks/useScreenRecorder.ts
+++ b/src/hooks/useScreenRecorder.ts
@@ -185,13 +185,14 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
return;
}
- const recorderState = mediaRecorder.current?.state;
- if (recorderState === "recording" || recorderState === "paused") {
+ const recorder = mediaRecorder.current;
+ const recorderState = recorder?.state;
+ if (recorder && (recorderState === "recording" || recorderState === "paused")) {
if (recorderState === "paused") {
- mediaRecorder.current.resume();
+ recorder.resume();
}
cleanupCapturedMedia();
- mediaRecorder.current.stop();
+ recorder.stop();
setRecording(false);
window.electronAPI?.setRecordingState(false);
}