Added additional themes
This commit is contained in:
+115
-4
@@ -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
|
# Allowed values for every setting except accent_color (validated separately
|
||||||
# as a hex string). Anything outside these sets is rejected/ignored on save.
|
# as a hex string). Anything outside these sets is rejected/ignored on save.
|
||||||
SETTINGS_CHOICES = {
|
SETTINGS_CHOICES = {
|
||||||
'theme': {'dark', 'light'},
|
'theme': {
|
||||||
'font_family': {'system', 'sans', 'serif', 'monospace'},
|
'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'},
|
'font_size': {'small', 'medium', 'large', 'xlarge'},
|
||||||
'layout_width': {'normal', 'wide', 'full'},
|
'layout_width': {'normal', 'wide', 'full'},
|
||||||
'density': {'comfortable', 'compact'},
|
'density': {'comfortable', 'compact'},
|
||||||
@@ -67,6 +70,89 @@ SETTINGS_CHOICES = {
|
|||||||
'corner_radius': {'sharp', 'rounded', 'pill'},
|
'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
|
# 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
|
# (rather than duplicating the mapping in JS) means the client just applies
|
||||||
# whatever /api/settings hands back.
|
# whatever /api/settings hands back.
|
||||||
@@ -75,6 +161,10 @@ FONT_FAMILY_CSS = {
|
|||||||
'sans': "Arial, Helvetica, sans-serif",
|
'sans': "Arial, Helvetica, sans-serif",
|
||||||
'serif': "Georgia, 'Times New Roman', serif",
|
'serif': "Georgia, 'Times New Roman', serif",
|
||||||
'monospace': "'Courier New', Consolas, monospace",
|
'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'}
|
FONT_SIZE_CSS = {'small': '14px', 'medium': '16px', 'large': '18px', 'xlarge': '20px'}
|
||||||
LAYOUT_WIDTH_CSS = {'normal': '1000px', 'wide': '1600px', 'full': '100%'}
|
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():
|
for key, value in new_settings.items():
|
||||||
if key == 'accent_color' and isinstance(value, str) and HEX_COLOR_RE.match(value):
|
if key == 'accent_color' and isinstance(value, str) and HEX_COLOR_RE.match(value):
|
||||||
current[key] = 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):
|
elif key == 'library_path' and isinstance(value, str):
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if value and not os.path.isdir(value):
|
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]:
|
def settings_css_vars(settings: Dict[str, Any]) -> Dict[str, str]:
|
||||||
"""Resolve a settings dict into the actual CSS custom-property values."""
|
"""Resolve a settings dict into the actual CSS custom-property values."""
|
||||||
|
palette = THEME_PALETTES.get(settings['theme'], THEME_PALETTES['dark'])
|
||||||
return {
|
return {
|
||||||
'--accent': settings['accent_color'],
|
'--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-family': FONT_FAMILY_CSS[settings['font_family']],
|
||||||
'--font-size-base': FONT_SIZE_CSS[settings['font_size']],
|
'--font-size-base': FONT_SIZE_CSS[settings['font_size']],
|
||||||
'--container-max-width': LAYOUT_WIDTH_CSS[settings['layout_width']],
|
'--container-max-width': LAYOUT_WIDTH_CSS[settings['layout_width']],
|
||||||
@@ -737,12 +843,17 @@ def browse_library():
|
|||||||
def settings_page():
|
def settings_page():
|
||||||
"""Render the display-settings page."""
|
"""Render the display-settings page."""
|
||||||
current = load_settings()
|
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(
|
return render_template(
|
||||||
'settings.html',
|
'settings.html',
|
||||||
settings=current,
|
settings=current,
|
||||||
default_library_path=LIBRARY_PATH,
|
default_library_path=LIBRARY_PATH,
|
||||||
theme_choices=sorted(SETTINGS_CHOICES['theme']),
|
theme_choices=['dark', 'light'] + named_presets,
|
||||||
font_family_choices=['system', 'sans', 'serif', 'monospace'],
|
theme_display_names=THEME_DISPLAY_NAMES,
|
||||||
|
font_family_choices=['system', 'sans', 'serif', 'monospace', 'monaspace'],
|
||||||
font_size_choices=['small', 'medium', 'large', 'xlarge'],
|
font_size_choices=['small', 'medium', 'large', 'xlarge'],
|
||||||
layout_width_choices=['normal', 'wide', 'full'],
|
layout_width_choices=['normal', 'wide', 'full'],
|
||||||
density_choices=['comfortable', 'compact'],
|
density_choices=['comfortable', 'compact'],
|
||||||
|
|||||||
@@ -233,7 +233,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<select id="theme" data-setting="theme">
|
<select id="theme" data-setting="theme">
|
||||||
{% for choice in theme_choices %}
|
{% for choice in theme_choices %}
|
||||||
<option value="{{ choice }}" {% if settings.theme == choice %}selected{% endif %}>{{ choice|capitalize }}</option>
|
<option value="{{ choice }}" {% if settings.theme == choice %}selected{% endif %}>{{ theme_display_names[choice] }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<select id="font_family" data-setting="font_family">
|
<select id="font_family" data-setting="font_family">
|
||||||
{% for choice in font_family_choices %}
|
{% for choice in font_family_choices %}
|
||||||
<option value="{{ choice }}" {% if settings.font_family == choice %}selected{% endif %}>{{ choice|capitalize }}</option>
|
<option value="{{ choice }}" {% if settings.font_family == choice %}selected{% endif %}>{{ 'Monaspace' if choice == 'monaspace' else choice|capitalize }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user