mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 15:02:20 +02:00
Snapshot current maintainer-preview application changes and regression fixtures for integration into lab. Excludes local runtime data, evaluation outputs and source backups. Focused Python regression selection: 140 passed; full suite not certified.
65 lines
3.2 KiB
Python
65 lines
3.2 KiB
Python
"""Normalize the small theme palette without discarding explicit choices."""
|
|
import re
|
|
|
|
THEME_PRESETS = ('dark', 'light', 'midnight', 'cyberpunk', 'retrowave', 'forest',
|
|
'ocean', 'ume', 'terminal', 'organs', 'gpt', 'claude', 'cute',
|
|
'eclipse', 'porcelain', 'arcade', 'blueprint', 'monolith', 'yoyo')
|
|
|
|
BACKGROUND_PATTERNS = ('none', 'dots', 'synapse', 'rain', 'constellations',
|
|
'perlin-flow', 'petals', 'sparkles', 'embers',
|
|
'starfield-depth', 'ascii-fireflies')
|
|
|
|
|
|
def normalize_theme_background(background, accent):
|
|
if background is None:
|
|
background = {'pattern': 'none'}
|
|
if not isinstance(background, dict):
|
|
raise ValueError('background must be an object with a pattern name.')
|
|
pattern = background.get('pattern', 'none')
|
|
if pattern == 'random':
|
|
import random
|
|
pattern = random.choice(BACKGROUND_PATTERNS[1:])
|
|
if pattern not in BACKGROUND_PATTERNS:
|
|
raise ValueError('Unknown background.pattern. Choose: ' + ', '.join(BACKGROUND_PATTERNS) + ', random.')
|
|
result = {'bgPattern': pattern, 'bgEffectColor': accent}
|
|
for key, low, high in (('intensity', 0, 1), ('size', .2, 3), ('speed', .05, 2.5)):
|
|
value = background.get(key, 1)
|
|
if isinstance(value, bool) or not isinstance(value, (float, int)) or not low <= value <= high:
|
|
raise ValueError(f'background.{key} must be a number between {low} and {high}.')
|
|
result['bgEffect' + key.title()] = value
|
|
return result
|
|
|
|
|
|
def normalize_theme_colors(colors):
|
|
if not isinstance(colors, dict):
|
|
raise ValueError('colors must be an object with bg and accent hex colors.')
|
|
result = {}
|
|
for key, value in colors.items():
|
|
if not isinstance(value, str):
|
|
raise ValueError(f'colors.{key} must be a hex color, for example #d93025.')
|
|
value = value.strip()
|
|
if re.fullmatch(r'#?[0-9a-fA-F]{3}|#?[0-9a-fA-F]{6}', value) is None:
|
|
raise ValueError(f'colors.{key}={value!r} is invalid. Use #RGB or #RRGGBB.')
|
|
value = value.lstrip('#')
|
|
if len(value) == 3:
|
|
value = ''.join(c * 2 for c in value)
|
|
result[key] = '#' + value.lower()
|
|
if 'accent' not in result and 'red' in result:
|
|
result['accent'] = result.pop('red')
|
|
missing = {'bg', 'accent'} - result.keys()
|
|
if missing:
|
|
raise ValueError('Missing colors: ' + ', '.join(sorted(missing)) + '. Other colors are optional.')
|
|
rgb = [int(result['bg'][i:i + 2], 16) / 255 for i in (1, 3, 5)]
|
|
linear = [c / 12.92 if c <= .04045 else ((c + .055) / 1.055) ** 2.4 for c in rgb]
|
|
luminance = sum(c * w for c, w in zip(linear, (.2126, .7152, .0722)))
|
|
light_text = (1.05 / (luminance + .05)) >= ((luminance + .05) / .05)
|
|
result.setdefault('fg', '#ffffff' if light_text else '#000000')
|
|
# Subtle surfaces move toward the contrasting pole, independent of an
|
|
# explicitly chosen foreground that might itself be low contrast.
|
|
target = 255 if light_text else 0
|
|
def surface(amount):
|
|
return '#' + ''.join(f'{round(c * 255 * (1 - amount) + target * amount):02x}' for c in rgb)
|
|
result.setdefault('panel', surface(.06))
|
|
result.setdefault('border', surface(.20))
|
|
return result
|