add: theming dark/light mode + system colors for the recording bar, to avoid icons being wrong color, text too etc..

This commit is contained in:
Alan Trebugeais
2026-05-07 12:58:51 +02:00
parent ba3d807c39
commit 9575ef3bd2
9 changed files with 120 additions and 68 deletions
+21 -29
View File
@@ -12,14 +12,11 @@
gap: 10px;
width: max-content;
max-width: 1200px;
background: rgba(18, 18, 24, 0.97);
border: 1px solid rgba(255, 255, 255, 0.07);
background: var(--launch-bar-bg);
border: 1px solid var(--launch-bar-border);
border-radius: 20px;
padding: 11px 20px 11px 18px;
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.24),
0 2px 10px rgba(0, 0, 0, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
box-shadow: var(--launch-bar-shadow);
overflow: visible;
position: relative;
transform-origin: center bottom;
@@ -48,7 +45,7 @@
.sep {
width: 1px;
height: 26px;
background: #2a2a34;
background: var(--launch-border-strong);
margin: 0 6px;
flex-shrink: 0;
}
@@ -60,7 +57,7 @@
border-radius: 11px;
border: none;
background: transparent;
color: #6b6b78;
color: var(--launch-text-muted);
cursor: pointer;
display: inline-flex;
align-items: center;
@@ -70,8 +67,8 @@
}
.ib:hover {
background: rgba(255, 255, 255, 0.07);
color: #eeeef2;
background: var(--launch-hover);
color: var(--launch-text);
}
.ibActive {
@@ -104,8 +101,8 @@
width: 300px;
max-height: 400px;
overflow-y: auto;
background: var(--launch-surface, rgba(18, 18, 24, 0.97));
border: 1px solid var(--launch-border, rgba(255, 255, 255, 0.07));
background: var(--launch-surface);
border: 1px solid var(--launch-border);
border-radius: 16px;
backdrop-filter: blur(var(--launch-popover-blur, 24px))
saturate(var(--launch-popover-saturate, 145%));
@@ -114,10 +111,7 @@
padding: 8px;
margin-top: auto;
margin-bottom: 8px;
box-shadow:
0 16px 40px rgba(0, 0, 0, 0.28),
0 2px 12px rgba(0, 0, 0, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
box-shadow: var(--launch-bar-shadow);
pointer-events: auto;
animation: menuCardIn 0.18s ease;
}
@@ -142,14 +136,14 @@
}
.menuCard::-webkit-scrollbar-thumb {
background: #2a2a34;
border-radius: 2px;
background: var(--launch-border-strong);
border-radius: 10px;
}
.ddLabel {
font-size: 9px;
font-weight: 600;
color: var(--launch-label, #8f99b1);
color: var(--launch-label);
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 6px 10px 4px;
@@ -165,20 +159,20 @@
border: none;
background: transparent;
font-size: 12px;
color: var(--launch-text-muted, #aab3c7);
color: var(--launch-text-muted);
cursor: pointer;
transition: all 0.12s ease;
text-align: left;
}
.ddItem:hover {
background: var(--launch-hover, rgba(255, 255, 255, 0.1));
color: var(--launch-text, #f3f6ff);
background: var(--launch-hover);
color: var(--launch-text);
}
.ddItemSelected {
background: var(--launch-selected, rgba(92, 153, 255, 0.22));
color: var(--launch-accent-soft, #dbeafe);
background: var(--launch-selected);
color: var(--launch-accent);
}
.finalizingState {
@@ -270,11 +264,9 @@
width: 288px;
overflow: hidden;
border-radius: 24px;
background: rgba(22, 22, 30, 0.95);
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow:
0 10px 24px rgba(0, 0, 0, 0.28),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
background: var(--launch-surface);
border: 1px solid var(--launch-border-strong);
box-shadow: var(--launch-bar-shadow);
cursor: grab;
touch-action: none;
user-select: none;
+2 -2
View File
@@ -231,7 +231,7 @@ function LaunchWindowContent() {
<Button
variant="outline"
size="lg"
className={`${styles.electronNoDrag} group gap-2 px-3 min-w-0 max-w-[180px] rounded-[11px] font-medium text-[12px] shrink-0 border-[#2a2a34] bg-[#1a1a22] text-[#eeeef2] hover:border-[#3e3e4c] hover:bg-[#20202a] transition-all ${openId === "sources" ? "border-[#3e3e4c] bg-[#20202a]" : ""}`}
className={`${styles.electronNoDrag} group gap-2 px-3 min-w-0 max-w-[180px] rounded-[11px] font-medium text-[12px] shrink-0 border-[var(--launch-border)] bg-[var(--launch-surface)] text-[var(--launch-text)] hover:border-[var(--launch-border-strong)] hover:bg-[var(--launch-hover)] transition-all ${openId === "sources" ? "border-[var(--launch-border-strong)] bg-[var(--launch-hover)]" : ""}`}
title={selectedSource}
>
<MonitorIcon size={16} className="shrink-0" />
@@ -449,7 +449,7 @@ function LaunchWindowContent() {
ref={hudBarRef}
layout={!showRecordingWebcamPreview && !isHudDragging}
transition={hudStateTransition}
className={`${styles.bar} mb-2`}
className={`${styles.bar} launch-theme mb-2`}
>
<div
// On Linux (especially Wayland) the compositor owns window
+1 -1
View File
@@ -50,7 +50,7 @@ export const RecordingControls = ({
<span
className={`font-mono text-xs font-semibold min-w-[52px] text-center tracking-[0.02em] ${
paused ? "text-[#fbbf24]" : "text-[#eeeef2]"
paused ? "text-[#fbbf24]" : "text-[var(--launch-text)]"
}`}
>
{formatTime(elapsed)}
+16 -19
View File
@@ -1,53 +1,51 @@
.source-selector-popover {
background: var(--launch-surface, rgba(18, 18, 24, 0.97));
border: 1px solid var(--launch-border, rgba(255, 255, 255, 0.07));
background: var(--launch-surface);
border: 1px solid var(--launch-border);
border-radius: 16px;
backdrop-filter: blur(var(--launch-popover-blur, 24px))
saturate(var(--launch-popover-saturate, 145%));
-webkit-backdrop-filter: blur(var(--launch-popover-blur, 24px))
saturate(var(--launch-popover-saturate, 145%));
box-shadow:
0 16px 40px rgba(0, 0, 0, 0.28),
0 2px 12px rgba(0, 0, 0, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
box-shadow: var(--launch-bar-shadow);
}
.source-selector-item {
color: var(--launch-text-muted, #aab3c7);
color: var(--launch-text-muted);
}
.source-selector-item:hover {
background: var(--launch-hover, rgba(255, 255, 255, 0.1));
color: var(--launch-text, #f3f6ff);
background: var(--launch-hover);
color: var(--launch-text);
}
.source-selector-item-selected {
background: var(--launch-selected, rgba(92, 153, 255, 0.22));
color: var(--launch-accent-soft, #dbeafe);
background: var(--launch-selected);
color: var(--launch-accent);
}
.source-selector-thumb-fallback {
background: #23232d;
background: var(--launch-panel);
}
.source-selector-text {
color: var(--launch-text, #f3f6ff);
color: var(--launch-text);
}
.source-selector-muted {
color: var(--launch-text-muted, #aab3c7);
color: var(--launch-text-muted);
}
.source-selector-subtle {
color: #747483;
color: var(--launch-text-muted);
opacity: 0.7;
}
.source-selector-label {
color: var(--launch-label, #8f99b1);
color: var(--launch-label);
}
.source-selector-dot {
background: var(--launch-accent, #3d8bff);
background: var(--launch-accent);
}
.source-selector-dot-inner {
@@ -55,7 +53,7 @@
}
.source-selector-accent-border {
border-color: var(--launch-accent, #3d8bff);
border-color: var(--launch-accent);
}
/* Custom Scrollbar for Source Selector */
@@ -96,7 +94,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
}
.source-selector-marquee-animated {
+27 -2
View File
@@ -1,9 +1,31 @@
.launch-theme {
/* Ultra-Refined Light Mode (Premium OS style) */
--launch-surface: #ffffff;
--launch-panel: #f8fafc;
--launch-border: rgba(0, 0, 0, 0.08);
--launch-border-strong: rgba(0, 0, 0, 0.16);
--launch-text: #020617; /* Deepest Navy/Black */
--launch-text-muted: #475569; /* Slate 600 */
--launch-label: #64748b; /* Slate 500 - increased contrast */
--launch-hover: rgba(0, 0, 0, 0.045);
--launch-selected: rgba(37, 99, 235, 0.12); /* Blue 600 at 12% */
--launch-accent: #2563eb; /* Blue 600 */
--launch-accent-soft: #f1f5f9; /* Slate 100 */
--launch-popover-blur: 40px;
--launch-popover-saturate: 200%;
--launch-bar-bg: rgba(255, 255, 255, 0.98);
--launch-bar-border: rgba(0, 0, 0, 0.08);
--launch-bar-shadow: 0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06);
color: var(--launch-text);
}
.dark .launch-theme,
.launch-theme.dark {
/* Dark Mode values */
--launch-surface: rgba(18, 18, 24, 0.97);
--launch-panel: rgba(18, 18, 24, 0.82);
--launch-border: rgba(255, 255, 255, 0.07);
--launch-popover-blur: 24px;
--launch-popover-saturate: 145%;
--launch-border-strong: rgba(255, 255, 255, 0.12);
--launch-text: #f3f6ff;
--launch-text-muted: #aab3c7;
@@ -12,4 +34,7 @@
--launch-selected: rgba(92, 153, 255, 0.22);
--launch-accent: #3d8bff;
--launch-accent-soft: #dbeafe;
--launch-bar-bg: rgba(18, 18, 24, 0.97);
--launch-bar-border: rgba(255, 255, 255, 0.07);
--launch-bar-shadow: 0 10px 30px rgba(0, 0, 0, 0.24), 0 2px 10px rgba(0, 0, 0, 0.12);
}
@@ -1,5 +1,5 @@
import { TimerIcon } from "@phosphor-icons/react";
import type { ReactNode } from "react";
import type { ReactElement } from "react";
import { useScopedT } from "@/contexts/I18nContext";
import styles from "../LaunchWindow.module.css";
import { DropdownItem, HudPopover } from "./PopoverScaffold";
@@ -13,7 +13,7 @@ export function CountdownPopover({
countdownDelay,
onSelectDelay,
}: {
trigger: ReactNode;
trigger: ReactElement;
countdownDelay: number;
onSelectDelay: (delay: number) => void;
}) {
@@ -3,7 +3,7 @@ import { useScopedT } from "@/contexts/I18nContext";
import { DropdownItem, HudPopover, MicDeviceRow } from "./PopoverScaffold";
import { useLaunchPopoverCoordinator } from "./LaunchPopoverCoordinator";
import type { DeviceOption } from "./launchPopoverTypes";
import type { ReactNode } from "react";
import type { ReactElement } from "react";
import styles from "../LaunchWindow.module.css";
const POPOVER_ID = "mic";
@@ -20,7 +20,7 @@ export function MicPopover({
selectedDeviceId,
onSelectDevice,
}: {
trigger: ReactNode;
trigger: ReactElement;
disabled?: boolean;
systemAudioEnabled: boolean;
onToggleSystemAudio: () => void;
@@ -73,7 +73,7 @@ export function MicPopover({
</DropdownItem>
)}
{!microphoneEnabled && (
<div className="px-3 py-2 text-xs text-[#6b6b78]">{t("recording.selectMicToEnable")}</div>
<div className="px-3 py-2 text-xs text-[var(--launch-text-muted)]">{t("recording.selectMicToEnable")}</div>
)}
{devices.map((device) => (
<MicDeviceRow
@@ -87,7 +87,7 @@ export function MicPopover({
/>
))}
{devices.length === 0 && (
<div className="text-center text-xs text-[#6b6b78] py-4">{t("recording.noMicrophonesFound")}</div>
<div className="text-center text-xs text-[var(--launch-text-muted)] py-4">{t("recording.noMicrophonesFound")}</div>
)}
</HudPopover>
);
+41 -3
View File
@@ -5,10 +5,14 @@ import {
TranslateIcon,
VideoCameraIcon,
ArrowClockwiseIcon,
SunIcon,
MoonIcon,
DesktopIcon,
} from "@phosphor-icons/react";
import type { ReactNode } from "react";
import type { ReactElement } from "react";
import { useI18n } from "@/contexts/I18nContext";
import { useScopedT } from "@/contexts/I18nContext";
import { useTheme } from "@/contexts/ThemeContext";
import type { AppLocale } from "@/i18n/config";
import { SUPPORTED_LOCALES } from "@/i18n/config";
import styles from "../LaunchWindow.module.css";
@@ -40,7 +44,7 @@ export function MorePopover({
onPreviewUpdateUi,
appVersion,
}: {
trigger: ReactNode;
trigger: ReactElement;
supportsHudCaptureProtection: boolean;
hideHudFromCapture: boolean;
onToggleHudCaptureProtection: () => void;
@@ -53,6 +57,7 @@ export function MorePopover({
}) {
const t = useScopedT("launch");
const { locale, setLocale } = useI18n();
const { preference, setPreference } = useTheme();
const { isOpen, requestOpen, requestClose } = useLaunchPopoverCoordinator();
const open = isOpen(POPOVER_ID);
@@ -118,6 +123,39 @@ export function MorePopover({
{t("recording.previewUpdateUi", "Preview Update UI")}
</DropdownItem>
) : null}
<div className={styles.ddLabel} style={{ marginTop: 4 }}>
{t("recording.appearance", "Appearance")}
</div>
<DropdownItem
icon={<SunIcon size={16} />}
selected={preference === "light"}
onClick={() => {
setPreference("light");
requestClose(POPOVER_ID);
}}
>
{t("common.light", "Light")}
</DropdownItem>
<DropdownItem
icon={<MoonIcon size={16} />}
selected={preference === "dark"}
onClick={() => {
setPreference("dark");
requestClose(POPOVER_ID);
}}
>
{t("common.dark", "Dark")}
</DropdownItem>
<DropdownItem
icon={<DesktopIcon size={16} />}
selected={preference === "system"}
onClick={() => {
setPreference("system");
requestClose(POPOVER_ID);
}}
>
{t("common.system", "System")}
</DropdownItem>
<div className={styles.ddLabel} style={{ marginTop: 4 }}>
{t("recording.language")}
</div>
@@ -140,7 +178,7 @@ export function MorePopover({
marginTop: 8,
padding: "4px 12px",
fontSize: 11,
color: "#6b6b78",
color: "var(--launch-text-muted)",
textAlign: "center",
userSelect: "text",
}}
@@ -8,7 +8,7 @@ import { useScopedT } from "@/contexts/I18nContext";
import { DropdownItem, HudPopover } from "./PopoverScaffold";
import { useLaunchPopoverCoordinator } from "./LaunchPopoverCoordinator";
import type { DeviceOption } from "./launchPopoverTypes";
import type { ReactNode } from "react";
import type { ReactElement } from "react";
const POPOVER_ID = "webcam";
@@ -27,7 +27,7 @@ export function WebcamPopover({
selectedVideoDeviceId,
onSelectVideoDevice,
}: {
trigger: ReactNode;
trigger: ReactElement;
disabled?: boolean;
webcamEnabled: boolean;
onDisableWebcam: () => void;
@@ -61,7 +61,7 @@ export function WebcamPopover({
trigger={trigger}
align="center"
>
<div className="px-3 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-[#6b6b78]">
<div className="px-3 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-[var(--launch-label)]">
{t("recording.webcam")}
</div>
{webcamEnabled && (
@@ -86,11 +86,11 @@ export function WebcamPopover({
</>
)}
{!webcamEnabled && (
<div className="px-3 py-2 text-xs text-[#6b6b78]">{t("recording.selectWebcamToEnable")}</div>
<div className="px-3 py-2 text-xs text-[var(--launch-text-muted)]">{t("recording.selectWebcamToEnable")}</div>
)}
{showWebcamControls && (
<div className="flex justify-center px-3 py-2">
<div className="h-24 w-24 overflow-hidden rounded-2xl bg-white/5 ring-1 ring-white/10">
<div className="h-24 w-24 overflow-hidden rounded-2xl bg-[var(--launch-hover)] ring-1 ring-[var(--launch-border-strong)]">
<video
ref={setWebcamPreviewNode}
className="h-full w-full object-cover"
@@ -122,7 +122,7 @@ export function WebcamPopover({
</DropdownItem>
))}
{videoDevices.length === 0 && (
<div className="text-center text-xs text-[#6b6b78] py-4">{t("recording.noWebcamsFound")}</div>
<div className="text-center text-xs text-[var(--launch-text-muted)] py-4">{t("recording.noWebcamsFound")}</div>
)}
</HudPopover>
);