Added additional themes

This commit is contained in:
2026-08-20 20:09:58 -04:00
parent f6ec1ad827
commit b06b8cf94c
2 changed files with 117 additions and 6 deletions
+115 -4
View File
@@ -58,8 +58,11 @@ VIDEO_SIZE_BOUNDS = {'video_width': (200, 4000), 'video_height': (120, 3000)}
# Allowed values for every setting except accent_color (validated separately
# as a hex string). Anything outside these sets is rejected/ignored on save.
SETTINGS_CHOICES = {
'theme': {'dark', 'light'},
'font_family': {'system', 'sans', 'serif', 'monospace'},
'theme': {
'dark', 'light', 'dracula', 'tokyo_night', 'catppuccin_mocha', 'ayu_dark',
'github_dark', 'atom_one_dark', 'houston', 'night_owl', 'dainty', 'matcha',
},
'font_family': {'system', 'sans', 'serif', 'monospace', 'monaspace'},
'font_size': {'small', 'medium', 'large', 'xlarge'},
'layout_width': {'normal', 'wide', 'full'},
'density': {'comfortable', 'compact'},
@@ -67,6 +70,89 @@ SETTINGS_CHOICES = {
'corner_radius': {'sharp', 'rounded', 'pill'},
}
# Display names for the theme dropdown - only needed where the raw key
# (e.g. 'tokyo_night') isn't already a clean label via .capitalize().
THEME_DISPLAY_NAMES = {
'dark': 'Dark', 'light': 'Light', 'dracula': 'Dracula',
'tokyo_night': 'Tokyo Night', 'catppuccin_mocha': 'Catppuccin Mocha',
'ayu_dark': 'Ayu Dark', 'github_dark': 'GitHub Dark',
'atom_one_dark': 'Atom One Dark', 'houston': 'Houston',
'night_owl': 'Night Owl', 'dainty': 'Dainty', 'matcha': 'Matcha',
}
# Full color palette per theme. 'accent' here is only the *default* accent
# offered when a theme is first selected - the accent_color setting is what
# actually drives --accent afterward, so it stays independently editable.
# Sourced from each theme's official palette (Dracula, Tokyo Night,
# Catppuccin Mocha, Ayu, GitHub Dark, Atom One Dark, Houston all verified
# against upstream repos/specs). Dainty and Matcha don't have one single
# fixed official hex set (Dainty is a theme *generator*; Matcha's exact
# source values weren't available) - built in the spirit of their
# documented look (refined/minimal, earthy green-and-gray) rather than
# claimed as an exact match.
THEME_PALETTES = {
'dark': {
'bg-primary': '#1a1a1a', 'bg-secondary': '#2d2d2d', 'bg-tertiary': '#3d3d3d',
'bg-tertiary-hover': '#404040', 'text-primary': '#e0e0e0', 'text-muted': '#999999',
'border-color': '#555555', 'accent': '#007acc', 'accent-hover': '#005a9e',
},
'light': {
'bg-primary': '#f2f2f2', 'bg-secondary': '#ffffff', 'bg-tertiary': '#eeeeee',
'bg-tertiary-hover': '#e2e2e2', 'text-primary': '#222222', 'text-muted': '#666666',
'border-color': '#cccccc', 'accent': '#007acc', 'accent-hover': '#005a9e',
},
'dracula': {
'bg-primary': '#282a36', 'bg-secondary': '#343746', 'bg-tertiary': '#44475a',
'bg-tertiary-hover': '#4d5066', 'text-primary': '#f8f8f2', 'text-muted': '#6272a4',
'border-color': '#44475a', 'accent': '#bd93f9', 'accent-hover': '#a672f0',
},
'tokyo_night': {
'bg-primary': '#1a1b26', 'bg-secondary': '#1f2335', 'bg-tertiary': '#283457',
'bg-tertiary-hover': '#364a73', 'text-primary': '#c0caf5', 'text-muted': '#565f89',
'border-color': '#292e42', 'accent': '#7aa2f7', 'accent-hover': '#6183bb',
},
'catppuccin_mocha': {
'bg-primary': '#1e1e2e', 'bg-secondary': '#313244', 'bg-tertiary': '#45475a',
'bg-tertiary-hover': '#585b70', 'text-primary': '#cdd6f4', 'text-muted': '#a6adc8',
'border-color': '#585b70', 'accent': '#cba6f7', 'accent-hover': '#b48ee0',
},
'ayu_dark': {
'bg-primary': '#0a0e14', 'bg-secondary': '#131721', 'bg-tertiary': '#1f2430',
'bg-tertiary-hover': '#272d3b', 'text-primary': '#b3b1ad', 'text-muted': '#626d7a',
'border-color': '#1f2430', 'accent': '#e6b450', 'accent-hover': '#ffb454',
},
'github_dark': {
'bg-primary': '#0d1117', 'bg-secondary': '#161b22', 'bg-tertiary': '#21262d',
'bg-tertiary-hover': '#30363d', 'text-primary': '#e6edf3', 'text-muted': '#8b949e',
'border-color': '#30363d', 'accent': '#2f81f7', 'accent-hover': '#1f6feb',
},
'atom_one_dark': {
'bg-primary': '#282c34', 'bg-secondary': '#2c313a', 'bg-tertiary': '#3b414d',
'bg-tertiary-hover': '#454b59', 'text-primary': '#abb2bf', 'text-muted': '#5c6370',
'border-color': '#3b414d', 'accent': '#61afef', 'accent-hover': '#528bff',
},
'houston': {
'bg-primary': '#17191e', 'bg-secondary': '#2b2d33', 'bg-tertiary': '#34363d',
'bg-tertiary-hover': '#3d3f47', 'text-primary': '#eef0f9', 'text-muted': '#79808f',
'border-color': '#2b2d33', 'accent': '#acafff', 'accent-hover': '#54b9ff',
},
'night_owl': {
'bg-primary': '#011627', 'bg-secondary': '#001122', 'bg-tertiary': '#0b2942',
'bg-tertiary-hover': '#123a5c', 'text-primary': '#d6deeb', 'text-muted': '#5f7e97',
'border-color': '#102a44', 'accent': '#82aaff', 'accent-hover': '#6690e0',
},
'dainty': {
'bg-primary': '#1c1c22', 'bg-secondary': '#232329', 'bg-tertiary': '#2c2c34',
'bg-tertiary-hover': '#35353e', 'text-primary': '#e8e6f0', 'text-muted': '#8a8894',
'border-color': '#38383f', 'accent': '#c9a0dc', 'accent-hover': '#b285c7',
},
'matcha': {
'bg-primary': '#1e2320', 'bg-secondary': '#262b27', 'bg-tertiary': '#333a34',
'bg-tertiary-hover': '#3d4539', 'text-primary': '#d6ddd2', 'text-muted': '#7d8a7c',
'border-color': '#3d443e', 'accent': '#a3c585', 'accent-hover': '#8fb86e',
},
}
# How each choice resolves to an actual CSS value. Keeping this server-side
# (rather than duplicating the mapping in JS) means the client just applies
# whatever /api/settings hands back.
@@ -75,6 +161,10 @@ FONT_FAMILY_CSS = {
'sans': "Arial, Helvetica, sans-serif",
'serif': "Georgia, 'Times New Roman', serif",
'monospace': "'Courier New', Consolas, monospace",
# Monaspace is GitHub's monospace font superfamily, not a color theme -
# falls back to Consolas/monospace if the font isn't installed locally,
# same as any other web font declaration.
'monaspace': "'Monaspace Neon', 'Monaspace Argon', 'Cascadia Code', Consolas, monospace",
}
FONT_SIZE_CSS = {'small': '14px', 'medium': '16px', 'large': '18px', 'xlarge': '20px'}
LAYOUT_WIDTH_CSS = {'normal': '1000px', 'wide': '1600px', 'full': '100%'}
@@ -113,6 +203,13 @@ def save_settings(new_settings: Dict[str, Any]) -> Dict[str, Any]:
for key, value in new_settings.items():
if key == 'accent_color' and isinstance(value, str) and HEX_COLOR_RE.match(value):
current[key] = value
elif key == 'theme' and value in SETTINGS_CHOICES['theme']:
current['theme'] = value
# Switching to a named preset also adopts its signature accent,
# unless this same request is also setting accent_color
# explicitly (in which case that wins).
if 'accent_color' not in new_settings:
current['accent_color'] = THEME_PALETTES[value]['accent']
elif key == 'library_path' and isinstance(value, str):
value = value.strip()
if value and not os.path.isdir(value):
@@ -140,8 +237,17 @@ def save_settings(new_settings: Dict[str, Any]) -> Dict[str, Any]:
def settings_css_vars(settings: Dict[str, Any]) -> Dict[str, str]:
"""Resolve a settings dict into the actual CSS custom-property values."""
palette = THEME_PALETTES.get(settings['theme'], THEME_PALETTES['dark'])
return {
'--accent': settings['accent_color'],
'--accent-hover': palette['accent-hover'],
'--bg-primary': palette['bg-primary'],
'--bg-secondary': palette['bg-secondary'],
'--bg-tertiary': palette['bg-tertiary'],
'--bg-tertiary-hover': palette['bg-tertiary-hover'],
'--text-primary': palette['text-primary'],
'--text-muted': palette['text-muted'],
'--border-color': palette['border-color'],
'--font-family': FONT_FAMILY_CSS[settings['font_family']],
'--font-size-base': FONT_SIZE_CSS[settings['font_size']],
'--container-max-width': LAYOUT_WIDTH_CSS[settings['layout_width']],
@@ -737,12 +843,17 @@ def browse_library():
def settings_page():
"""Render the display-settings page."""
current = load_settings()
named_presets = sorted(
(t for t in SETTINGS_CHOICES['theme'] if t not in ('dark', 'light')),
key=lambda t: THEME_DISPLAY_NAMES[t]
)
return render_template(
'settings.html',
settings=current,
default_library_path=LIBRARY_PATH,
theme_choices=sorted(SETTINGS_CHOICES['theme']),
font_family_choices=['system', 'sans', 'serif', 'monospace'],
theme_choices=['dark', 'light'] + named_presets,
theme_display_names=THEME_DISPLAY_NAMES,
font_family_choices=['system', 'sans', 'serif', 'monospace', 'monaspace'],
font_size_choices=['small', 'medium', 'large', 'xlarge'],
layout_width_choices=['normal', 'wide', 'full'],
density_choices=['comfortable', 'compact'],