formatters, device profile, media matching, subtitle utils, syncplay, trakt

This commit is contained in:
2026-03-24 10:48:59 +02:00
parent 292b3f42cf
commit 996a85de76
41 changed files with 4306 additions and 0 deletions
+92
View File
@@ -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,
})
}