{background === "glow" ? (
// Fixed to the viewport, like the welcome overlay, so its center
// lines up exactly with the overlay's glow during the handoff.
@@ -51,7 +51,7 @@ export function OnboardingLayout({
{progress && (
))}
-
+
{progress.current} / {progress.total}
@@ -86,7 +86,7 @@ export function OnboardingLayout({
diff --git a/packages/extension/src/components/onboarding/OnboardingPage.tsx b/packages/extension/src/components/onboarding/OnboardingPage.tsx
index a6843cf6..c123341f 100644
--- a/packages/extension/src/components/onboarding/OnboardingPage.tsx
+++ b/packages/extension/src/components/onboarding/OnboardingPage.tsx
@@ -5,6 +5,7 @@ import { Popup } from "@/components/Popup"
import { LinkSelector } from "@/components/LinkSelector"
import { useOnboardingState } from "./useOnboardingState"
import { useOnboardingVariant } from "./useOnboardingVariant"
+import { useTheme, themeContext } from "@/hooks/useTheme"
import { OnboardingLayout } from "./OnboardingLayout"
import { OnboardingWelcome } from "./OnboardingWelcome"
import { StepIntro } from "./steps/StepIntro"
@@ -20,15 +21,21 @@ import "@/components/App.css"
export function OnboardingPage() {
const variant = useOnboardingVariant()
+ const theme = useTheme(document.documentElement)
- // Hold a blank canvas until the A/B assignment is known (normally a single
- // chrome.storage.local read, since the service worker assigns on
- // install). Rendering a default arm first would flash variant A's INTRO
- // screen at variant C users.
- if (variant == null) {
- return
- }
- return
+ return (
+
+ {variant == null ? (
+ // Hold a blank canvas until the A/B assignment is known (normally a
+ // single chrome.storage.local read, since the service worker assigns
+ // on install). Rendering a default arm first would flash variant A's
+ // INTRO screen at variant C users.
+
+ ) : (
+
+ )}
+
+ )
}
function OnboardingFlow({ variant }: { variant: ExperimentVariant }) {
diff --git a/packages/extension/src/components/onboarding/OnboardingRail.tsx b/packages/extension/src/components/onboarding/OnboardingRail.tsx
index f87bda83..581c722c 100644
--- a/packages/extension/src/components/onboarding/OnboardingRail.tsx
+++ b/packages/extension/src/components/onboarding/OnboardingRail.tsx
@@ -60,7 +60,9 @@ export function OnboardingRail({
{state === "done" && (
@@ -93,12 +95,16 @@ export function OnboardingRail({
/>
)}
{state === "current" && (
-
-
+
+
)}
-
+
{label}
diff --git a/packages/extension/src/components/onboarding/OnboardingTargetText.tsx b/packages/extension/src/components/onboarding/OnboardingTargetText.tsx
index 5ca3c32c..f1f50c6b 100644
--- a/packages/extension/src/components/onboarding/OnboardingTargetText.tsx
+++ b/packages/extension/src/components/onboarding/OnboardingTargetText.tsx
@@ -53,12 +53,12 @@ export function OnboardingTargetText({
{text}
@@ -78,7 +79,7 @@ export function OnboardingTargetText({
diff --git a/packages/extension/src/components/onboarding/OnboardingWelcome.tsx b/packages/extension/src/components/onboarding/OnboardingWelcome.tsx
index ad75db20..8fbe5a2e 100644
--- a/packages/extension/src/components/onboarding/OnboardingWelcome.tsx
+++ b/packages/extension/src/components/onboarding/OnboardingWelcome.tsx
@@ -7,8 +7,7 @@ import {
DEFAULT_WELCOME_UI,
type OnboardingWelcomeUi,
} from "./onboardingVariantUi"
-
-const ICON_URL = chrome.runtime.getURL("SelectionCommandLogo.png")
+import { useLogoUrl } from "./onboardingLogo"
/** How long variant C's overlay stays up before advancing on its own. */
export const WELCOME_DURATION_MS = DEFAULT_WELCOME_UI.durationMs
@@ -30,6 +29,7 @@ type Props = {
// when the user clicks - there is nothing to read here, so making people
// wait out the timer would just be a second thing to sit through.
export function OnboardingWelcome({ onDone, ui = DEFAULT_WELCOME_UI }: Props) {
+ const logoUrl = useLogoUrl()
const [exiting, setExiting] = useState(false)
const timersRef = useRef[]>([])
const finishedRef = useRef(false)
@@ -69,7 +69,7 @@ export function OnboardingWelcome({ onDone, ui = DEFAULT_WELCOME_UI }: Props) {
aria-label={t("onboarding_welcomeContinue")}
data-testid="onboarding-welcome"
className={clsx(
- "fixed inset-0 z-30 flex cursor-default flex-col items-center justify-center overflow-hidden bg-white",
+ "outline-none fixed inset-0 z-30 flex cursor-default flex-col items-center justify-center overflow-hidden bg-background",
ui.gapClassName,
exiting && "animate-onboarding-blur-out motion-reduce:animate-none",
)}
@@ -87,15 +87,15 @@ export function OnboardingWelcome({ onDone, ui = DEFAULT_WELCOME_UI }: Props) {
delay={ui.logoDelay}
className={ui.logoClassName}
>
-
+
-
+
{t("onboarding_welcomeMessage")}
-
+
{t("onboarding_welcomeIntroduction")}
diff --git a/packages/extension/src/components/onboarding/__tests__/onboardingLogo.test.tsx b/packages/extension/src/components/onboarding/__tests__/onboardingLogo.test.tsx
new file mode 100644
index 00000000..1f47bf56
--- /dev/null
+++ b/packages/extension/src/components/onboarding/__tests__/onboardingLogo.test.tsx
@@ -0,0 +1,30 @@
+import { describe, it, expect, vi } from "vitest"
+import { renderHook } from "@testing-library/react"
+import { themeContext, type ResolvedTheme } from "@/hooks/useTheme"
+import { useLogoUrl } from "../onboardingLogo"
+
+// The logo URLs are resolved when the module loads, before any test runs.
+vi.hoisted(() => {
+ vi.mocked(chrome.runtime.getURL).mockImplementation(
+ (path: string) => `chrome-extension://test/${path}`,
+ )
+})
+
+const renderWithTheme = (theme: ResolvedTheme) =>
+ renderHook(() => useLogoUrl(), {
+ wrapper: ({ children }) => (
+ {children}
+ ),
+ })
+
+describe("useLogoUrl", () => {
+ it("OL-01: 正常系: ライトテーマでは通常のロゴを返す", () => {
+ const { result } = renderWithTheme("light")
+ expect(result.current).toContain("SelectionCommandLogo.png")
+ })
+
+ it("OL-02: 正常系: ダークテーマではダーク用のロゴを返す", () => {
+ const { result } = renderWithTheme("dark")
+ expect(result.current).toContain("SelectionCommandLogo_dark.png")
+ })
+})
diff --git a/packages/extension/src/components/onboarding/onboardingLogo.ts b/packages/extension/src/components/onboarding/onboardingLogo.ts
new file mode 100644
index 00000000..918aa12e
--- /dev/null
+++ b/packages/extension/src/components/onboarding/onboardingLogo.ts
@@ -0,0 +1,10 @@
+import { useThemeContext } from "@/hooks/useTheme"
+
+const LOGO_URL = chrome.runtime.getURL("SelectionCommandLogo.png")
+// The same logo with the wordmark's lightness inverted (hue kept) and the
+// icon untouched, since the original's dark text vanishes on a dark page.
+const LOGO_DARK_URL = chrome.runtime.getURL("SelectionCommandLogo_dark.png")
+
+/** Returns the logo image URL for the current theme. */
+export const useLogoUrl = (): string =>
+ useThemeContext() === "dark" ? LOGO_DARK_URL : LOGO_URL
diff --git a/packages/extension/src/components/onboarding/steps/StepAiPromptCommand.tsx b/packages/extension/src/components/onboarding/steps/StepAiPromptCommand.tsx
index 5786340c..51e23ebc 100644
--- a/packages/extension/src/components/onboarding/steps/StepAiPromptCommand.tsx
+++ b/packages/extension/src/components/onboarding/steps/StepAiPromptCommand.tsx
@@ -132,7 +132,7 @@ export function StepAiPromptCommand({ onboarding }: Props) {
return (