Compare commits

...

5 Commits

Author SHA1 Message Date
Cyril 94daf66f8c (feat) add default color option for captions
Add "Default" option (white text on black background) for font and background.
2026-03-18 20:23:08 +01:00
Cyril d7634bd331 ♻️(refactor) apply caption color customization to subtitles
Use captionFontColor and captionBackgroundColor in Transcription component.
2026-03-18 20:21:29 +01:00
Cyril 85ecf6a846 (feat) add caption font color and background settings
Select font color and background color in Accessibility > Captions.
2026-03-18 20:21:29 +01:00
Cyril 995fd34495 🌐(i18n) add captions color settings translations
EN, FR, DE, NL for Accessibility > Captions > Font color / Background color.
2026-03-18 20:21:29 +01:00
Cyril 97f2cb1699 ️(frontend) add caption font color and background to access store
Persist captionFontColor and captionBackgroundColor in user preferences.
2026-03-18 20:21:29 +01:00
8 changed files with 250 additions and 18 deletions
+3 -3
View File
@@ -22,8 +22,8 @@ and this project adheres to
- ♿️(frontend) Caption text size setting for accessibility #1062
- ♿️(frontend) sync html lang attribute with i18n for screen readers #1111
- ♿️(frontend) improve MoreLink a11y and UX on home page #1112
- ♿(frontend) improve chat toast a11y for screen readers #1109
- ♿(frontend) improve ui and aria labels for help article links #1108
-(frontend) improve chat toast a11y for screen readers #1109
-(frontend) improve ui and aria labels for help article links #1108
- 🌐(frontend) improve German translation #1125
- 🔨(python-env) migrate meet main app to UV #1120
- ♻️(backend) align Application model field with `is_active` convention #1133
@@ -32,6 +32,7 @@ and this project adheres to
- 🔒️(backend) avoid information exposure through exception messages #1144
- ⬆️(dependencies) update PyJWT to v2.12.0 [SECURITY] #1151
- 📌(agents) unpin OpenSSL and related dependencies #1167
- ♿️(frontend) add caption font and background color customization #1122
### Fixed
@@ -41,7 +42,6 @@ and this project adheres to
- 🩹(backend) ignore non-recording uploads in storage webhook handler #1142
- 🐛(frontend) fix dimension mismatch in BackgroundCustomProcessor #1116
## [1.10.0] - 2026-03-05
### Changed
@@ -6,7 +6,9 @@ import { useSnapshot } from 'valtio'
import {
accessibilityStore,
type CaptionTextSize,
type CaptionColor,
CAPTION_TEXT_SIZE_OPTIONS,
CAPTION_COLOR_OPTIONS,
} from '@/stores/accessibility'
export const CaptionsSettings = () => {
@@ -24,6 +26,24 @@ export const CaptionsSettings = () => {
[t]
)
const captionFontColorItems = useMemo(
() =>
CAPTION_COLOR_OPTIONS.map((color) => ({
value: color,
label: t(`fontColor.options.${color}`),
})),
[t]
)
const captionBackgroundColorItems = useMemo(
() =>
CAPTION_COLOR_OPTIONS.map((color) => ({
value: color,
label: t(`backgroundColor.options.${color}`),
})),
[t]
)
return (
<li>
<H
@@ -34,16 +54,44 @@ export const CaptionsSettings = () => {
>
{t('heading')}
</H>
<Field
type="select"
label={t('textSize.label')}
items={captionTextSizeItems}
selectedKey={snap.captionTextSize}
onSelectionChange={(key) => {
accessibilityStore.captionTextSize = key as CaptionTextSize
}}
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
<div
className={css({
display: 'flex',
flexDirection: 'column',
gap: '0.75rem',
})}
>
<Field
type="select"
label={t('textSize.label')}
items={captionTextSizeItems}
selectedKey={snap.captionTextSize}
onSelectionChange={(key) => {
accessibilityStore.captionTextSize = key as CaptionTextSize
}}
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
<Field
type="select"
label={t('fontColor.label')}
items={captionFontColorItems}
selectedKey={snap.captionFontColor}
onSelectionChange={(key) => {
accessibilityStore.captionFontColor = key as CaptionColor
}}
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
<Field
type="select"
label={t('backgroundColor.label')}
items={captionBackgroundColorItems}
selectedKey={snap.captionBackgroundColor}
onSelectionChange={(key) => {
accessibilityStore.captionBackgroundColor = key as CaptionColor
}}
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
</div>
</li>
)
}
@@ -12,6 +12,8 @@ import { useSnapshot } from 'valtio'
import {
accessibilityStore,
CAPTION_TEXT_SIZE_OPTIONS,
CAPTION_FONT_COLOR_VALUES,
CAPTION_BACKGROUND_COLOR_VALUES,
type CaptionTextSize,
} from '@/stores/accessibility'
@@ -92,10 +94,14 @@ const useTranscriptionState = () => {
}
const Transcription = ({ row }: { row: TranscriptionRow }) => {
const { captionTextSize } = useSnapshot(accessibilityStore)
const { captionTextSize, captionFontColor, captionBackgroundColor } =
useSnapshot(accessibilityStore)
const participantColor = getParticipantColor(row.participant)
const participantName = getParticipantName(row.participant)
const { fontSize, lineHeight } = CAPTION_FONT_SIZES[captionTextSize]
const fontColor = CAPTION_FONT_COLOR_VALUES[captionFontColor]
const backgroundColor =
CAPTION_BACKGROUND_COLOR_VALUES[captionBackgroundColor]
const getDisplayText = (row: TranscriptionRow): string => {
return row.segments
@@ -128,9 +134,9 @@ const Transcription = ({ row }: { row: TranscriptionRow }) => {
/>
<div
className={css({
color: 'white',
width: '100%',
})}
style={{ color: fontColor }}
>
<Text variant="h3" margin={false}>
{participantName}
@@ -138,8 +144,10 @@ const Transcription = ({ row }: { row: TranscriptionRow }) => {
<p
className={css({
fontWeight: '400',
borderRadius: '4px',
padding: '0.125rem 0.25rem',
})}
style={{ fontSize, lineHeight }}
style={{ fontSize, lineHeight, backgroundColor }}
>
{displayText}
</p>
+28
View File
@@ -126,6 +126,34 @@
"medium": "Mittel",
"large": "Groß"
}
},
"fontColor": {
"label": "Schriftfarbe",
"options": {
"default": "Standard",
"white": "Weiß",
"black": "Schwarz",
"blue": "Blau",
"green": "Grün",
"red": "Rot",
"yellow": "Gelb",
"cyan": "Cyan",
"magenta": "Magenta"
}
},
"backgroundColor": {
"label": "Hintergrundfarbe",
"options": {
"default": "Standard",
"white": "Weiß",
"black": "Schwarz",
"blue": "Blau",
"green": "Grün",
"red": "Rot",
"yellow": "Gelb",
"cyan": "Cyan",
"magenta": "Magenta"
}
}
}
},
+28
View File
@@ -126,6 +126,34 @@
"medium": "Medium",
"large": "Large"
}
},
"fontColor": {
"label": "Font color",
"options": {
"default": "Default",
"white": "White",
"black": "Black",
"blue": "Blue",
"green": "Green",
"red": "Red",
"yellow": "Yellow",
"cyan": "Cyan",
"magenta": "Magenta"
}
},
"backgroundColor": {
"label": "Background color",
"options": {
"default": "Default",
"white": "White",
"black": "Black",
"blue": "Blue",
"green": "Green",
"red": "Red",
"yellow": "Yellow",
"cyan": "Cyan",
"magenta": "Magenta"
}
}
}
},
+28
View File
@@ -126,6 +126,34 @@
"medium": "Moyen",
"large": "Grand"
}
},
"fontColor": {
"label": "Couleur du texte",
"options": {
"default": "Par défaut",
"white": "Blanc",
"black": "Noir",
"blue": "Bleu",
"green": "Vert",
"red": "Rouge",
"yellow": "Jaune",
"cyan": "Cyan",
"magenta": "Magenta"
}
},
"backgroundColor": {
"label": "Couleur de fond",
"options": {
"default": "Par défaut",
"white": "Blanc",
"black": "Noir",
"blue": "Bleu",
"green": "Vert",
"red": "Rouge",
"yellow": "Jaune",
"cyan": "Cyan",
"magenta": "Magenta"
}
}
}
},
+28
View File
@@ -126,6 +126,34 @@
"medium": "Gemiddeld",
"large": "Groot"
}
},
"fontColor": {
"label": "Tekstkleur",
"options": {
"default": "Standaard",
"white": "Wit",
"black": "Zwart",
"blue": "Blauw",
"green": "Groen",
"red": "Rood",
"yellow": "Geel",
"cyan": "Cyaan",
"magenta": "Magenta"
}
},
"backgroundColor": {
"label": "Achtergrondkleur",
"options": {
"default": "Standaard",
"white": "Wit",
"black": "Zwart",
"blue": "Blauw",
"green": "Groen",
"red": "Rood",
"yellow": "Geel",
"cyan": "Cyaan",
"magenta": "Magenta"
}
}
}
},
+66 -2
View File
@@ -10,14 +10,65 @@ export const CAPTION_TEXT_SIZE_OPTIONS: CaptionTextSize[] = [
'large',
]
export type CaptionColor =
| 'default'
| 'white'
| 'black'
| 'blue'
| 'green'
| 'red'
| 'yellow'
| 'cyan'
| 'magenta'
export const CAPTION_COLOR_OPTIONS: CaptionColor[] = [
'default',
'white',
'black',
'blue',
'green',
'red',
'yellow',
'cyan',
'magenta',
]
export const CAPTION_FONT_COLOR_VALUES: Record<CaptionColor, string> = {
default: '#FFFFFF',
white: '#FFFFFF',
black: '#000000',
blue: '#0000FF',
green: '#00FF00',
red: '#FF0000',
yellow: '#FFFF00',
cyan: '#00FFFF',
magenta: '#FF00FF',
}
export const CAPTION_BACKGROUND_COLOR_VALUES: Record<CaptionColor, string> = {
default: 'rgba(0, 0, 0, 0.75)',
black: 'rgba(0, 0, 0, 0.75)',
white: 'rgba(255, 255, 255, 0.75)',
blue: 'rgba(0, 0, 255, 0.75)',
green: 'rgba(0, 255, 0, 0.75)',
red: 'rgba(255, 0, 0, 0.75)',
yellow: 'rgba(255, 255, 0, 0.75)',
cyan: 'rgba(0, 255, 255, 0.75)',
magenta: 'rgba(255, 0, 255, 0.75)',
}
type AccessibilityState = {
announceReactions: boolean
captionTextSize: CaptionTextSize
captionFontColor: CaptionColor
captionBackgroundColor: CaptionColor
}
const DEFAULT_STATE: AccessibilityState = {
announceReactions: false,
captionTextSize: 'medium',
captionFontColor: 'default',
captionBackgroundColor: 'default',
}
function getAccessibilityState(): AccessibilityState {
@@ -25,10 +76,21 @@ function getAccessibilityState(): AccessibilityState {
const stored = localStorage.getItem(STORAGE_KEYS.ACCESSIBILITY)
if (stored) {
const parsed = JSON.parse(stored)
const validCaptionSizes = CAPTION_TEXT_SIZE_OPTIONS
const captionTextSize = validCaptionSizes.includes(parsed.captionTextSize)
const captionTextSize = CAPTION_TEXT_SIZE_OPTIONS.includes(
parsed.captionTextSize
)
? parsed.captionTextSize
: DEFAULT_STATE.captionTextSize
const captionFontColor = CAPTION_COLOR_OPTIONS.includes(
parsed.captionFontColor
)
? parsed.captionFontColor
: DEFAULT_STATE.captionFontColor
const captionBackgroundColor = CAPTION_COLOR_OPTIONS.includes(
parsed.captionBackgroundColor
)
? parsed.captionBackgroundColor
: DEFAULT_STATE.captionBackgroundColor
return {
...DEFAULT_STATE,
...parsed,
@@ -37,6 +99,8 @@ function getAccessibilityState(): AccessibilityState {
? parsed.announceReactions
: DEFAULT_STATE.announceReactions,
captionTextSize,
captionFontColor,
captionBackgroundColor,
}
}