formatters, device profile, media matching, subtitle utils, syncplay, trakt
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user