diff --git a/web/index.html b/web/index.html
index b764f311..fabb2eee 100644
--- a/web/index.html
+++ b/web/index.html
@@ -8,8 +8,7 @@
/>
-
-
+
@@ -25,13 +24,6 @@
-
-
+
+
HAPI
diff --git a/web/src/hooks/useTheme.test.ts b/web/src/hooks/useTheme.test.ts
new file mode 100644
index 00000000..9468320a
--- /dev/null
+++ b/web/src/hooks/useTheme.test.ts
@@ -0,0 +1,46 @@
+import { act, renderHook } from '@testing-library/react'
+import { beforeEach, describe, expect, it } from 'vitest'
+import { getThemeColor, initializeTheme, useAppearance } from '@/hooks/useTheme'
+
+describe('useTheme', () => {
+ beforeEach(() => {
+ localStorage.clear()
+ document.documentElement.removeAttribute('data-theme')
+ document.head.querySelectorAll('meta[name="theme-color"]').forEach((meta) => meta.remove())
+ })
+
+ it('applies the stored dark appearance to the document and browser theme color', () => {
+ localStorage.setItem('hapi-appearance', 'dark')
+
+ initializeTheme()
+
+ expect(document.documentElement).toHaveAttribute('data-theme', 'dark')
+ expect(document.querySelector('meta[name="theme-color"]')?.content).toBe(getThemeColor('dark'))
+ })
+
+ it('creates a browser theme color meta tag when the page does not provide one', () => {
+ initializeTheme()
+
+ const meta = document.querySelector('meta[name="theme-color"]')
+ expect(meta?.content).toBe(getThemeColor('light'))
+ expect(meta?.hasAttribute('media')).toBe(false)
+ })
+
+ it('updates the browser theme color when appearance changes', () => {
+ const { result } = renderHook(() => useAppearance())
+
+ act(() => {
+ result.current.setAppearance('dark')
+ })
+
+ expect(document.documentElement).toHaveAttribute('data-theme', 'dark')
+ expect(document.querySelector('meta[name="theme-color"]')?.content).toBe(getThemeColor('dark'))
+
+ act(() => {
+ result.current.setAppearance('light')
+ })
+
+ expect(document.documentElement).toHaveAttribute('data-theme', 'light')
+ expect(document.querySelector('meta[name="theme-color"]')?.content).toBe(getThemeColor('light'))
+ })
+})
diff --git a/web/src/hooks/useTheme.ts b/web/src/hooks/useTheme.ts
index 6d177fcb..7fda73dd 100644
--- a/web/src/hooks/useTheme.ts
+++ b/web/src/hooks/useTheme.ts
@@ -6,6 +6,10 @@ type ColorScheme = 'light' | 'dark'
export type AppearancePreference = 'system' | 'dark' | 'light'
const APPEARANCE_KEY = 'hapi-appearance'
+const THEME_COLORS: Record = {
+ light: '#ffffff',
+ dark: '#1c1c1e',
+}
function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined'
@@ -77,8 +81,27 @@ function isIOS(): boolean {
return /iPad|iPhone|iPod/.test(navigator.userAgent)
}
+function applyBrowserThemeColor(scheme: ColorScheme): void {
+ if (!isBrowser()) return
+
+ let meta = document.querySelector('meta[name="theme-color"]')
+ if (!meta) {
+ meta = document.createElement('meta')
+ meta.name = 'theme-color'
+ document.head.appendChild(meta)
+ }
+
+ meta.content = THEME_COLORS[scheme]
+ meta.removeAttribute('media')
+}
+
+export function getThemeColor(scheme: ColorScheme): string {
+ return THEME_COLORS[scheme]
+}
+
function applyTheme(scheme: ColorScheme): void {
document.documentElement.setAttribute('data-theme', scheme)
+ applyBrowserThemeColor(scheme)
}
function applyPlatform(): void {