diff --git a/.agent/workflows/high-contrast-implementation.md b/.agent/workflows/high-contrast-implementation.md
new file mode 100644
index 0000000000..d862d282a0
--- /dev/null
+++ b/.agent/workflows/high-contrast-implementation.md
@@ -0,0 +1,44 @@
+---
+description: Implementation plan for High Contrast Mode feature (Issue #4853)
+---
+
+# High Contrast Mode Implementation Plan
+
+## Overview
+This implementation adds high contrast versions of both light and dark modes to Openverse, following accessibility best practices (WCAG 1.4.6 Enhanced Contrast - 7:1 ratio for normal text).
+
+## Components to Create/Modify
+
+### 1. CSS Variables (`src/styles/tailwind.css`)
+- Add high contrast color palettes for both light and dark modes
+- Add `@media (prefers-contrast: more)` media queries
+- Add `.high-contrast-light-mode` and `.high-contrast-dark-mode` class selectors
+
+### 2. Type Definitions (`stores/ui.ts`)
+- Add `ContrastMode` type: `"normal" | "high" | "system"`
+- Add `contrastMode` to `UiState`
+- Add `setContrastMode()` action
+- Update cookie handling
+
+### 3. Composable (`composables/use-high-contrast.ts`)
+- Create new composable similar to `use-dark-mode.ts`
+- Detect OS preference using `prefers-contrast` media query
+- Return effective contrast mode and CSS class
+
+### 4. Feature Flag (`feat/feature-flags.json`)
+- Add `high_contrast_ui_toggle` feature flag
+
+### 5. UI Component (`components/VContrastSelect/VContrastSelect.vue`)
+- Create dropdown similar to `VThemeSelect`
+- Options: Normal, High Contrast, System
+
+### 6. Integration
+- Update `VFooter` to include contrast selector
+- Update `app.vue` to apply contrast CSS class to body
+
+### 7. Translations
+- Add i18n keys for contrast mode labels
+
+### 8. Tests
+- Unit tests for composable
+- Unit tests for store actions
diff --git a/frontend/feat/feature-flags.json b/frontend/feat/feature-flags.json
index 296616d1d6..3ad2fe2dbc 100644
--- a/frontend/feat/feature-flags.json
+++ b/frontend/feat/feature-flags.json
@@ -39,12 +39,23 @@
"defaultState": "on",
"description": "Display the UI toggle to change the site color theme and respect system preferences.",
"storage": "cookie"
+ },
+ "high_contrast_ui_toggle": {
+ "status": {
+ "staging": "switchable",
+ "production": "switchable"
+ },
+ "defaultState": "on",
+ "description": "Display the UI toggle to enable high contrast mode and respect OS prefers-contrast preferences.",
+ "storage": "cookie"
}
},
"groups": [
{
"title": "analytics",
- "features": ["analytics"]
+ "features": [
+ "analytics"
+ ]
}
]
-}
+}
\ No newline at end of file
diff --git a/frontend/i18n/data/en.json5 b/frontend/i18n/data/en.json5
index 5f092e47be..015d175c56 100644
--- a/frontend/i18n/data/en.json5
+++ b/frontend/i18n/data/en.json5
@@ -760,6 +760,10 @@ _{link}_ part of "The translation for English locale is incomplete. Help us get
"theme.choices.dark": "Dark",
"theme.choices.light": "Light",
"theme.choices.system": "System",
+ "contrast.contrast": "Contrast",
+ "contrast.choices.normal": "Normal",
+ "contrast.choices.high": "High",
+ "contrast.choices.system": "System",
"recentSearches.heading": "Recent searches",
"recentSearches.clear.text": "Clear",
"recentSearches.clear.label": "Clear recent searches",
diff --git a/frontend/shared/types/cookies.ts b/frontend/shared/types/cookies.ts
index 06ec392ac2..31769cc96d 100644
--- a/frontend/shared/types/cookies.ts
+++ b/frontend/shared/types/cookies.ts
@@ -15,6 +15,7 @@ export const persistentCookieOptions = {
export const defaultPersistientCookieState: OpenverseCookieState = {
ui: {
colorMode: "system",
+ contrastMode: "system",
},
}
diff --git a/frontend/src/app.vue b/frontend/src/app.vue
index 169fb113aa..74aef19d9c 100644
--- a/frontend/src/app.vue
+++ b/frontend/src/app.vue
@@ -13,6 +13,7 @@ import { useUiStore } from "~/stores/ui"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useLayout } from "~/composables/use-layout"
import { useDarkMode } from "~/composables/use-dark-mode"
+import { useHighContrast } from "~/composables/use-high-contrast"
import VSkipToContentButton from "~/components/VSkipToContentButton.vue"
@@ -22,6 +23,12 @@ const featureFlagStore = useFeatureFlagStore()
const uiStore = useUiStore()
const darkMode = useDarkMode()
+const highContrast = useHighContrast()
+
+// Combined CSS classes for body: dark mode class + high contrast class
+const bodyClasses = computed(() => {
+ return [darkMode.cssClass.value, highContrast.cssClass.value].filter(Boolean).join(' ')
+})
/* UI store */
const isDesktopLayout = computed(() => uiStore.isDesktopLayout)
@@ -63,7 +70,7 @@ const meta = computed(() => {
useHead({
htmlAttrs: htmlI18nProps,
- bodyAttrs: { class: darkMode.cssClass, style: headerHeight },
+ bodyAttrs: { class: bodyClasses, style: headerHeight },
title: "Openly Licensed Images, Audio and More | Openverse",
meta,
link,
diff --git a/frontend/src/components/VContrastSelect/VContrastSelect.vue b/frontend/src/components/VContrastSelect/VContrastSelect.vue
new file mode 100644
index 0000000000..f7595d5b84
--- /dev/null
+++ b/frontend/src/components/VContrastSelect/VContrastSelect.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/VFooter/VFooter.vue b/frontend/src/components/VFooter/VFooter.vue
index a4c1488fa0..5177decc6b 100644
--- a/frontend/src/components/VFooter/VFooter.vue
+++ b/frontend/src/components/VFooter/VFooter.vue
@@ -15,6 +15,7 @@ import VLink from "~/components/VLink.vue"
import VBrand from "~/components/VBrand/VBrand.vue"
import VLanguageSelect from "~/components/VLanguageSelect/VLanguageSelect.vue"
import VThemeSelect from "~/components/VThemeSelect/VThemeSelect.vue"
+import VContrastSelect from "~/components/VContrastSelect/VContrastSelect.vue"
import VPageLinks from "~/components/VHeader/VPageLinks.vue"
import VWordPressLink from "~/components/VHeader/VWordPressLink.vue"
@@ -96,6 +97,7 @@ const linkColumnHeight = computed(() => ({
class="language max-w-full border-secondary"
/>
+
diff --git a/frontend/src/composables/use-high-contrast.ts b/frontend/src/composables/use-high-contrast.ts
new file mode 100644
index 0000000000..76c2076ffa
--- /dev/null
+++ b/frontend/src/composables/use-high-contrast.ts
@@ -0,0 +1,124 @@
+import { computed, ref, onMounted, watchEffect } from "#imports"
+
+import { useFeatureFlagStore } from "~/stores/feature-flag"
+import { useUiStore } from "~/stores/ui"
+import { useDarkMode } from "~/composables/use-dark-mode"
+
+export const HIGH_CONTRAST_LIGHT_MODE_CLASS = "high-contrast-light-mode"
+export const HIGH_CONTRAST_DARK_MODE_CLASS = "high-contrast-dark-mode"
+
+/**
+ * Determines the high contrast mode setting based on user preference or OS setting.
+ *
+ * When the "high_contrast_ui_toggle" flag is enabled, the site will respect
+ * the user's system preference for high contrast by default (using prefers-contrast media query).
+ *
+ * The high contrast mode works in combination with the color mode (light/dark) to provide
+ * four possible visual themes:
+ * - Light mode (normal contrast)
+ * - Light mode (high contrast)
+ * - Dark mode (normal contrast)
+ * - Dark mode (high contrast)
+ */
+export function useHighContrast() {
+ const uiStore = useUiStore()
+ const featureFlagStore = useFeatureFlagStore()
+ const { effectiveColorMode } = useDarkMode()
+
+ const highContrastToggleable = computed(() =>
+ featureFlagStore.isOn("high_contrast_ui_toggle")
+ )
+
+ /**
+ * Detect OS-level preference for high contrast using the prefers-contrast media query.
+ * Returns true if the user has requested more contrast at the OS level.
+ */
+ const osHighContrast = ref(false)
+
+ onMounted(() => {
+ // Check if the browser supports the prefers-contrast media query
+ const mediaQuery = window.matchMedia("(prefers-contrast: more)")
+ osHighContrast.value = mediaQuery.matches
+
+ // Listen for changes to the OS high contrast preference
+ const handleChange = (e: MediaQueryListEvent) => {
+ osHighContrast.value = e.matches
+ }
+ mediaQuery.addEventListener("change", handleChange)
+
+ // Note: cleanup is handled by Vue's lifecycle
+ })
+
+ /**
+ * The contrast mode setting for the app.
+ *
+ * This can be one of "normal", "high", or "system". If the toggle
+ * feature is disabled, we default to "normal".
+ */
+ const contrastMode = computed(() => {
+ if (highContrastToggleable.value) {
+ return uiStore.contrastMode
+ }
+ return "normal"
+ })
+
+ /**
+ * The effective contrast mode of the app.
+ *
+ * This can be one of "normal" or "high". This is a combination of the
+ * toggle feature flag, the user's preference at the app and OS levels,
+ * and the default value of "normal".
+ */
+ const effectiveContrastMode = computed(() => {
+ if (!highContrastToggleable.value) {
+ return "normal"
+ }
+ if (contrastMode.value === "system") {
+ return osHighContrast.value ? "high" : "normal"
+ }
+ return contrastMode.value
+ })
+
+ /**
+ * Whether high contrast mode is currently active (either explicitly set or via OS preference).
+ */
+ const isHighContrast = computed(() => effectiveContrastMode.value === "high")
+
+ /**
+ * The CSS class to apply to the body element based on the contrast and color mode.
+ * Returns a class that combines both the color mode and contrast mode.
+ *
+ * - high-contrast-light-mode: High contrast + light theme
+ * - high-contrast-dark-mode: High contrast + dark theme
+ * - Empty string: Normal contrast (handled by color mode classes)
+ */
+ const cssClass = computed(() => {
+ if (effectiveContrastMode.value !== "high") {
+ return ""
+ }
+
+ // When high contrast is enabled, we need to combine it with the current color mode
+ return effectiveColorMode.value === "dark"
+ ? HIGH_CONTRAST_DARK_MODE_CLASS
+ : HIGH_CONTRAST_LIGHT_MODE_CLASS
+ })
+
+ /**
+ * The server does not have access to media queries, so the `system` contrast mode
+ * defaults to "normal" on the server.
+ */
+ const serverContrastMode = computed(() => {
+ return !highContrastToggleable.value || contrastMode.value === "system"
+ ? "normal"
+ : contrastMode.value
+ })
+
+ return {
+ contrastMode,
+ osHighContrast,
+ effectiveContrastMode,
+ isHighContrast,
+ serverContrastMode,
+ cssClass,
+ }
+}
diff --git a/frontend/src/error.vue b/frontend/src/error.vue
index c15eca5c3b..860c583096 100644
--- a/frontend/src/error.vue
+++ b/frontend/src/error.vue
@@ -6,6 +6,7 @@ import { meta as commonMeta } from "#shared/constants/meta"
import { favicons } from "#shared/constants/favicons"
import { useUiStore } from "~/stores/ui"
import { useDarkMode } from "~/composables/use-dark-mode"
+import { useHighContrast } from "~/composables/use-high-contrast"
import { useLayout } from "~/composables/use-layout"
import VFourOhFour from "~/components/VFourOhFour.vue"
@@ -30,6 +31,12 @@ onMounted(() => {
})
const darkMode = useDarkMode()
+const highContrast = useHighContrast()
+
+// Combined CSS classes for body: dark mode class + high contrast class
+const bodyClasses = computed(() => {
+ return [darkMode.cssClass.value, highContrast.cssClass.value].filter(Boolean).join(' ')
+})
const head = useLocaleHead({ dir: true, key: "id", seo: true })
const htmlI18nProps = computed(() => ({
@@ -64,7 +71,7 @@ const meta = computed(() => {
useHead({
htmlAttrs: htmlI18nProps,
- bodyAttrs: { class: darkMode.cssClass, style: headerHeight },
+ bodyAttrs: { class: bodyClasses, style: headerHeight },
title: "Openly Licensed Images, Audio and More | Openverse",
meta,
link,
diff --git a/frontend/src/stores/ui.ts b/frontend/src/stores/ui.ts
index fe3bdec05f..f98f74946a 100644
--- a/frontend/src/stores/ui.ts
+++ b/frontend/src/stores/ui.ts
@@ -17,6 +17,7 @@ const desktopBreakpoints: RealBreakpoint[] = ["2xl", "xl", "lg"]
export type SnackbarState = "not_shown" | "visible" | "dismissed"
export type ColorMode = "dark" | "light" | "system"
+export type ContrastMode = "normal" | "high" | "system"
export function isColorMode(value: undefined | string): value is ColorMode {
return (
@@ -24,6 +25,12 @@ export function isColorMode(value: undefined | string): value is ColorMode {
)
}
+export function isContrastMode(value: undefined | string): value is ContrastMode {
+ return (
+ typeof value === "string" && ["normal", "high", "system"].includes(value)
+ )
+}
+
export interface UiState {
/**
* whether to show the instructions snackbar.
@@ -65,6 +72,8 @@ export interface UiState {
colorMode: ColorMode
/** whether the user has seen the dark mode toggle */
isDarkModeSeen: boolean
+ /* The user-chosen contrast mode of the site. */
+ contrastMode: ContrastMode
}
export const breakpoints = Object.keys(ALL_SCREEN_SIZES)
@@ -81,6 +90,7 @@ export const defaultUiState: UiState = {
headerHeight: 80,
colorMode: "system",
isDarkModeSeen: false,
+ contrastMode: "system",
}
export const useUiStore = defineStore("ui", {
@@ -95,6 +105,7 @@ export const useUiStore = defineStore("ui", {
dismissedBanners: Array.from(this.dismissedBanners),
colorMode: state.colorMode,
isDarkModeSeen: state.isDarkModeSeen,
+ contrastMode: state.contrastMode,
}
},
areInstructionsVisible(state): boolean {
@@ -197,6 +208,10 @@ export const useUiStore = defineStore("ui", {
this.setIsDarkModeSeen(cookies.isDarkModeSeen, false)
}
+ if (isContrastMode(cookies.contrastMode)) {
+ this.setContrastMode(cookies.contrastMode, false)
+ }
+
this.writeToCookie()
},
/**
@@ -318,5 +333,19 @@ export const useUiStore = defineStore("ui", {
setHeaderHeight(height: number) {
this.headerHeight = Math.max(height, 80)
},
+
+ /**
+ * Update the user's preferred contrast mode.
+ *
+ * @param contrastMode - the user's preferred contrast mode.
+ * @param saveToCookie - whether to save the new setting in the cookie.
+ */
+ setContrastMode(contrastMode: ContrastMode, saveToCookie = true) {
+ this.contrastMode = contrastMode
+
+ if (saveToCookie) {
+ this.writeToCookie()
+ }
+ },
},
})
diff --git a/frontend/src/styles/tailwind.css b/frontend/src/styles/tailwind.css
index 6610194a0b..d04fec3082 100644
--- a/frontend/src/styles/tailwind.css
+++ b/frontend/src/styles/tailwind.css
@@ -389,6 +389,256 @@
--color-skeleton-secondary: var(--color-gray-10);
}
+ /* High Contrast Light Mode - OS preference */
+ @media (prefers-color-scheme: light) and (prefers-contrast: more) {
+ :root {
+ /* Backgrounds - Higher contrast with pure white/black */
+ --color-bg: var(--color-white);
+ --color-bg-surface: var(--color-white);
+ --color-bg-overlay: var(--color-white);
+ --color-bg-primary: var(--color-pink-10);
+ --color-bg-primary-hover: var(--color-pink-11);
+ --color-bg-secondary: var(--color-gray-1);
+ --color-bg-secondary-hover: var(--color-black);
+ --color-bg-tertiary: var(--color-black);
+ --color-bg-tertiary-hover: var(--color-gray-12);
+ --color-bg-transparent-hover: var(--color-gray-12-20);
+ --color-bg-complementary: var(--color-yellow-3);
+ --color-bg-warning: var(--color-warning-3);
+ --color-bg-info: var(--color-info-3);
+ --color-bg-success: var(--color-success-3);
+ --color-bg-error: var(--color-error-3);
+ --color-bg-disabled: var(--color-gray-6);
+ --color-bg-blur: var(--color-white);
+
+ /* Borders - Stronger, more visible borders */
+ --color-border: var(--color-black);
+ --color-border-hover: var(--color-black);
+ --color-border-secondary: var(--color-gray-12-40);
+ --color-border-secondary-hover: var(--color-black);
+ --color-border-tertiary: var(--color-black);
+ --color-border-transparent-hover: var(--color-gray-12-40);
+ --color-border-overlay: var(--color-black);
+ --color-border-focus: var(--color-pink-10);
+ --color-border-bg-ring: var(--color-white);
+ --color-border-disabled: var(--color-gray-7);
+ --color-focus-ring: var(--color-pink-10);
+
+ /* Text - Maximum contrast with pure black */
+ --color-text: var(--color-black);
+ --color-text-secondary: var(--color-gray-11);
+ --color-text-disabled: var(--color-gray-7);
+ --color-text-link: var(--color-pink-10);
+ --color-text-over-dark: var(--color-white);
+ --color-text-over-negative: var(--color-white);
+ --color-text-secondary-over-dark: var(--color-gray-3);
+
+ /* Icons */
+ --color-icon-warning: var(--color-warning-10);
+ --color-icon-info: var(--color-info-10);
+ --color-icon-success: var(--color-success-10);
+ --color-icon-error: var(--color-error-10);
+
+ /* Waveform */
+ --color-wave-active: var(--color-yellow-10);
+ --color-wave-inactive: var(--color-gray-12-40);
+
+ /* Misc */
+ --color-modal-layer: var(--color-black-72);
+ --color-new-highlight: var(--color-pink-8);
+ --color-gray-new-highlight: var(--color-gray-12-40);
+ --color-skeleton-base: var(--color-gray-3);
+ --color-skeleton-secondary: var(--color-gray-4);
+ }
+ }
+
+ /* High Contrast Dark Mode - OS preference */
+ @media (prefers-color-scheme: dark) and (prefers-contrast: more) {
+ :root {
+ /* Backgrounds - Higher contrast with pure black */
+ --color-bg: var(--color-black);
+ --color-bg-surface: var(--color-gray-13);
+ --color-bg-overlay: var(--color-gray-12);
+ --color-bg-primary: var(--color-yellow-3);
+ --color-bg-primary-hover: var(--color-yellow-2);
+ --color-bg-secondary: var(--color-gray-12);
+ --color-bg-secondary-hover: var(--color-white);
+ --color-bg-tertiary: var(--color-white);
+ --color-bg-tertiary-hover: var(--color-gray-1);
+ --color-bg-transparent-hover: var(--color-gray-1-20);
+ --color-bg-complementary: var(--color-pink-8);
+ --color-bg-warning: var(--color-warning-12);
+ --color-bg-info: var(--color-info-12);
+ --color-bg-success: var(--color-success-12);
+ --color-bg-error: var(--color-error-12);
+ --color-bg-disabled: var(--color-gray-9);
+ --color-bg-blur: var(--color-black);
+
+ /* Borders - Stronger, more visible borders */
+ --color-border: var(--color-white);
+ --color-border-hover: var(--color-white);
+ --color-border-secondary: var(--color-gray-1-40);
+ --color-border-secondary-hover: var(--color-white);
+ --color-border-tertiary: var(--color-white);
+ --color-border-transparent-hover: var(--color-gray-1-40);
+ --color-border-overlay: var(--color-white);
+ --color-border-focus: var(--color-yellow-3);
+ --color-border-bg-ring: var(--color-black);
+ --color-border-disabled: var(--color-gray-7);
+ --color-focus-ring: var(--color-yellow-3);
+
+ /* Text - Maximum contrast with pure white */
+ --color-text: var(--color-white);
+ --color-text-secondary: var(--color-gray-2);
+ --color-text-disabled: var(--color-gray-6);
+ --color-text-link: var(--color-yellow-3);
+ --color-text-over-dark: var(--color-black);
+ --color-text-over-negative: var(--color-black);
+ --color-text-secondary-over-dark: var(--color-gray-10);
+
+ /* Icons */
+ --color-icon-warning: var(--color-warning-4);
+ --color-icon-info: var(--color-info-4);
+ --color-icon-success: var(--color-success-4);
+ --color-icon-error: var(--color-error-4);
+
+ /* Waveform */
+ --color-wave-active: var(--color-pink-3);
+ --color-wave-inactive: var(--color-gray-1-40);
+
+ /* Misc */
+ --color-modal-layer: var(--color-black-72);
+ --color-new-highlight: var(--color-yellow-2);
+ --color-gray-new-highlight: var(--color-gray-1-40);
+ --color-skeleton-base: var(--color-gray-12);
+ --color-skeleton-secondary: var(--color-gray-11);
+ }
+ }
+
+ /* High Contrast Light Mode - Class-based override */
+ :is(.high-contrast-light-mode),
+ :is(.high-contrast-light-mode *) {
+ /* Backgrounds - Higher contrast with pure white/black */
+ --color-bg: var(--color-white);
+ --color-bg-surface: var(--color-white);
+ --color-bg-overlay: var(--color-white);
+ --color-bg-primary: var(--color-pink-10);
+ --color-bg-primary-hover: var(--color-pink-11);
+ --color-bg-secondary: var(--color-gray-1);
+ --color-bg-secondary-hover: var(--color-black);
+ --color-bg-tertiary: var(--color-black);
+ --color-bg-tertiary-hover: var(--color-gray-12);
+ --color-bg-transparent-hover: var(--color-gray-12-20);
+ --color-bg-complementary: var(--color-yellow-3);
+ --color-bg-warning: var(--color-warning-3);
+ --color-bg-info: var(--color-info-3);
+ --color-bg-success: var(--color-success-3);
+ --color-bg-error: var(--color-error-3);
+ --color-bg-disabled: var(--color-gray-6);
+ --color-bg-blur: var(--color-white);
+
+ /* Borders - Stronger, more visible borders */
+ --color-border: var(--color-black);
+ --color-border-hover: var(--color-black);
+ --color-border-secondary: var(--color-gray-12-40);
+ --color-border-secondary-hover: var(--color-black);
+ --color-border-tertiary: var(--color-black);
+ --color-border-transparent-hover: var(--color-gray-12-40);
+ --color-border-overlay: var(--color-black);
+ --color-border-focus: var(--color-pink-10);
+ --color-border-bg-ring: var(--color-white);
+ --color-border-disabled: var(--color-gray-7);
+ --color-focus-ring: var(--color-pink-10);
+
+ /* Text - Maximum contrast with pure black */
+ --color-text: var(--color-black);
+ --color-text-secondary: var(--color-gray-11);
+ --color-text-disabled: var(--color-gray-7);
+ --color-text-link: var(--color-pink-10);
+ --color-text-over-dark: var(--color-white);
+ --color-text-over-negative: var(--color-white);
+ --color-text-secondary-over-dark: var(--color-gray-3);
+
+ /* Icons */
+ --color-icon-warning: var(--color-warning-10);
+ --color-icon-info: var(--color-info-10);
+ --color-icon-success: var(--color-success-10);
+ --color-icon-error: var(--color-error-10);
+
+ /* Waveform */
+ --color-wave-active: var(--color-yellow-10);
+ --color-wave-inactive: var(--color-gray-12-40);
+
+ /* Misc */
+ --color-modal-layer: var(--color-black-72);
+ --color-new-highlight: var(--color-pink-8);
+ --color-gray-new-highlight: var(--color-gray-12-40);
+ --color-skeleton-base: var(--color-gray-3);
+ --color-skeleton-secondary: var(--color-gray-4);
+ }
+
+ /* High Contrast Dark Mode - Class-based override */
+ :is(.high-contrast-dark-mode *),
+ :is(.high-contrast-dark-mode) {
+ /* Backgrounds - Higher contrast with pure black */
+ --color-bg: var(--color-black);
+ --color-bg-surface: var(--color-gray-13);
+ --color-bg-overlay: var(--color-gray-12);
+ --color-bg-primary: var(--color-yellow-3);
+ --color-bg-primary-hover: var(--color-yellow-2);
+ --color-bg-secondary: var(--color-gray-12);
+ --color-bg-secondary-hover: var(--color-white);
+ --color-bg-tertiary: var(--color-white);
+ --color-bg-tertiary-hover: var(--color-gray-1);
+ --color-bg-transparent-hover: var(--color-gray-1-20);
+ --color-bg-complementary: var(--color-pink-8);
+ --color-bg-warning: var(--color-warning-12);
+ --color-bg-info: var(--color-info-12);
+ --color-bg-success: var(--color-success-12);
+ --color-bg-error: var(--color-error-12);
+ --color-bg-disabled: var(--color-gray-9);
+ --color-bg-blur: var(--color-black);
+
+ /* Borders - Stronger, more visible borders */
+ --color-border: var(--color-white);
+ --color-border-hover: var(--color-white);
+ --color-border-secondary: var(--color-gray-1-40);
+ --color-border-secondary-hover: var(--color-white);
+ --color-border-tertiary: var(--color-white);
+ --color-border-transparent-hover: var(--color-gray-1-40);
+ --color-border-overlay: var(--color-white);
+ --color-border-focus: var(--color-yellow-3);
+ --color-border-bg-ring: var(--color-black);
+ --color-border-disabled: var(--color-gray-7);
+ --color-focus-ring: var(--color-yellow-3);
+
+ /* Text - Maximum contrast with pure white */
+ --color-text: var(--color-white);
+ --color-text-secondary: var(--color-gray-2);
+ --color-text-disabled: var(--color-gray-6);
+ --color-text-link: var(--color-yellow-3);
+ --color-text-over-dark: var(--color-black);
+ --color-text-over-negative: var(--color-black);
+ --color-text-secondary-over-dark: var(--color-gray-10);
+
+ /* Icons */
+ --color-icon-warning: var(--color-warning-4);
+ --color-icon-info: var(--color-info-4);
+ --color-icon-success: var(--color-success-4);
+ --color-icon-error: var(--color-error-4);
+
+ /* Waveform */
+ --color-wave-active: var(--color-pink-3);
+ --color-wave-inactive: var(--color-gray-1-40);
+
+ /* Misc */
+ --color-modal-layer: var(--color-black-72);
+ --color-new-highlight: var(--color-yellow-2);
+ --color-gray-new-highlight: var(--color-gray-1-40);
+ --color-skeleton-base: var(--color-gray-12);
+ --color-skeleton-secondary: var(--color-gray-11);
+ }
+
body {
@apply m-0 font-normal leading-loose text-default;
-webkit-font-smoothing: antialiased;