93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
import { usePreferencesStore } from '../stores/preferences-store'
|
|
import type { AppSettings } from '../api/types'
|
|
|
|
/**
|
|
* Builds styling for vidstack's <Captions> renderer based on the user's
|
|
* subtitle preferences. Font size is returned as an inline CSS custom
|
|
* property because Tailwind can't generate classes for arbitrary pixel
|
|
* values that only exist at runtime. The other properties use Tailwind
|
|
* arbitrary-variant selectors (`[&_[data-cue]]:...`) to target the cue
|
|
* elements that vidstack injects.
|
|
*
|
|
* The same builder powers the live preview in Settings so what users see
|
|
* there matches what they'll see in the player.
|
|
*/
|
|
|
|
export type SubtitleStyle = { className: string; style: React.CSSProperties }
|
|
|
|
type S = Pick<
|
|
AppSettings,
|
|
'subtitleFontSize' | 'subtitleFontFamily' | 'subtitleBackground' | 'subtitleEdge' | 'subtitlePosition' | 'subtitleColor'
|
|
>
|
|
|
|
const FAMILY_CLASS: Record<AppSettings['subtitleFontFamily'], string> = {
|
|
sans: '[&_[data-cue]]:font-sans',
|
|
serif: '[&_[data-cue]]:[font-family:var(--font-display)]',
|
|
mono: '[&_[data-cue]]:font-mono',
|
|
}
|
|
|
|
const BG_CLASS: Record<AppSettings['subtitleBackground'], string> = {
|
|
none: '',
|
|
subtle:
|
|
'[&_[data-cue]]:bg-black/55 [&_[data-cue]]:rounded-md [&_[data-cue]]:px-3 [&_[data-cue]]:py-1',
|
|
solid: '[&_[data-cue]]:bg-black [&_[data-cue]]:px-3 [&_[data-cue]]:py-1',
|
|
}
|
|
|
|
const EDGE_CLASS: Record<AppSettings['subtitleEdge'], string> = {
|
|
none: '',
|
|
shadow: '[&_[data-cue]]:[text-shadow:0_2px_6px_rgba(0,0,0,0.85)]',
|
|
outline:
|
|
'[&_[data-cue]]:[text-shadow:-1px_-1px_0_#000,1px_-1px_0_#000,-1px_1px_0_#000,1px_1px_0_#000,0_2px_4px_rgba(0,0,0,0.7)]',
|
|
}
|
|
|
|
const COLOR_CLASS: Record<AppSettings['subtitleColor'], string> = {
|
|
white: '[&_[data-cue]]:text-white',
|
|
yellow: '[&_[data-cue]]:text-yellow-300',
|
|
cyan: '[&_[data-cue]]:text-cyan-200',
|
|
}
|
|
|
|
const POSITION_CLASS: Record<AppSettings['subtitlePosition'], string> = {
|
|
bottom: 'bottom-32',
|
|
top: 'top-24',
|
|
}
|
|
|
|
/** Compose the className + inline style for the Captions container. */
|
|
export function subtitleClasses(s: S): SubtitleStyle {
|
|
const className = [
|
|
'absolute inset-x-0 px-7 pointer-events-none text-center font-semibold tracking-tight',
|
|
'[&_[data-cue]]:inline-block [&_[data-cue]]:leading-tight',
|
|
POSITION_CLASS[s.subtitlePosition],
|
|
FAMILY_CLASS[s.subtitleFontFamily],
|
|
BG_CLASS[s.subtitleBackground],
|
|
EDGE_CLASS[s.subtitleEdge],
|
|
COLOR_CLASS[s.subtitleColor],
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')
|
|
|
|
const style: React.CSSProperties = {
|
|
'--cue-font-size': `${s.subtitleFontSize}px`,
|
|
'--cue-font-size-md': `${Math.round(s.subtitleFontSize * 1.36)}px`,
|
|
} as React.CSSProperties
|
|
|
|
return { className, style }
|
|
}
|
|
|
|
/** Hook: composes subtitle styling for live use, subscribed to preference changes. */
|
|
export function useSubtitleStyles(): SubtitleStyle {
|
|
const subtitleFontSize = usePreferencesStore(s => s.subtitleFontSize)
|
|
const subtitleFontFamily = usePreferencesStore(s => s.subtitleFontFamily)
|
|
const subtitleBackground = usePreferencesStore(s => s.subtitleBackground)
|
|
const subtitleEdge = usePreferencesStore(s => s.subtitleEdge)
|
|
const subtitlePosition = usePreferencesStore(s => s.subtitlePosition)
|
|
const subtitleColor = usePreferencesStore(s => s.subtitleColor)
|
|
return subtitleClasses({
|
|
subtitleFontSize,
|
|
subtitleFontFamily,
|
|
subtitleBackground,
|
|
subtitleEdge,
|
|
subtitlePosition,
|
|
subtitleColor,
|
|
})
|
|
}
|