"""Fenris theme presets (issue #80). Three accessible colour presets: Amber (default), Nord, and High Contrast. Themes style chrome, borders, accents, muted text, and graph roles; status semantic colours/glyphs/text always win. Theme roles for graph rendering expose distinct colours per preset so the bar graph can reflect the user's visual preference without depending on graph-ticket completion. Criteria: TPH-10, AC80-1, AC80-5. """ from typing import Dict from textual.theme import Theme # --------------------------------------------------------------------------- # Status semantic colours — always win, never themed (AC80-1) # --------------------------------------------------------------------------- STATUS_COLORS = { "monitoring": "green", "collecting": "green", "paused": "yellow", "waiting": "yellow", "interrupted": "red", "error": "red", "stale": "red", "unknown": "dim", } # --------------------------------------------------------------------------- # Amber theme — warm golden tones (default, amber graph role) # --------------------------------------------------------------------------- _AMBER = Theme( name="fenris-amber", primary="#d4a017", # warm amber secondary="#c49b0a", # darker amber accent="#ffd54f", # light amber highlight warning="#e6a817", # amber warning error="#e74c3c", # red error success="#27ae60", # green success foreground="#e8e0d0", # warm light background="#1a1510", # warm dark surface="#241f16", # warm surface panel="#2a2318", # warm panel boost="#332a1c", # warm boost dark=True, variables={ "graph-allocated": "#d4a017", "graph-unallocated": "#8b6914", "graph-gap": "#554422", "graph-zero": "#665533", "graph-partial": "#aa8822", "graph-selection": "#ffd54f", "border-default": "#554422", "muted-text": "#887755", }, ) # --------------------------------------------------------------------------- # Nord theme — cool blue-gray polar night palette # --------------------------------------------------------------------------- _NORD = Theme( name="fenris-nord", primary="#88c0d0", # nord8 frost secondary="#81a1c1", # nord9 accent="#8fbcbb", # nord7 warning="#ebcb8b", # nord13 error="#bf616a", # nord11 success="#a3be8c", # nord14 foreground="#eceff4", # nord6 background="#2e3440", # nord0 surface="#3b4252", # nord1 panel="#434c5e", # nord2 boost="#4c566a", # nord3 dark=True, variables={ "graph-allocated": "#88c0d0", "graph-unallocated": "#5e81ac", "graph-gap": "#4c566a", "graph-zero": "#616e88", "graph-partial": "#81a1c1", "graph-selection": "#8fbcbb", "border-default": "#4c566a", "muted-text": "#7b88a1", }, ) # --------------------------------------------------------------------------- # High Contrast — maximum readability, pure black and white # --------------------------------------------------------------------------- _HIGH_CONTRAST = Theme( name="fenris-high-contrast", primary="#ffffff", # pure white secondary="#dddddd", # light gray accent="#ffff00", # bright yellow warning="#ff8800", # bright orange error="#ff0000", # pure red success="#00ff00", # pure green foreground="#ffffff", # pure white background="#000000", # pure black surface="#111111", # near-black surface panel="#1a1a1a", # near-black panel boost="#222222", # near-black boost dark=True, variables={ "graph-allocated": "#ffffff", "graph-unallocated": "#aaaaaa", "graph-gap": "#555555", "graph-zero": "#666666", "graph-partial": "#cccccc", "graph-selection": "#ffff00", "border-default": "#ffffff", "muted-text": "#aaaaaa", }, ) # --------------------------------------------------------------------------- # Theme registry # --------------------------------------------------------------------------- THEMES = { "amber": _AMBER, "nord": _NORD, "high_contrast": _HIGH_CONTRAST, } THEME_NAMES = set(THEMES.keys()) def get_theme(name: str) -> Theme: """Return a registered theme by preset name. Unknown names fall back to Amber. """ return THEMES.get(name, _AMBER) def get_graph_colors(theme_name: str) -> Dict[str, str]: """Return the graph colour roles for a theme preset. Returns a dict with keys: allocated, unallocated, gap, zero, partial, selection. Falls back to Amber for unknown names. """ theme = get_theme(theme_name) variables = theme.variables or {} return { "allocated": variables.get("graph-allocated", "#d4a017"), "unallocated": variables.get("graph-unallocated", "#8b6914"), "gap": variables.get("graph-gap", "#554422"), "zero": variables.get("graph-zero", "#665533"), "partial": variables.get("graph-partial", "#aa8822"), "selection": variables.get("graph-selection", "#ffd54f"), }