Fix contrast issues with text-muted and bg-dark classes

- Fixed Bootstrap bg-dark class to use better contrasting color
- Added comprehensive text-muted contrast fixes for various contexts
- Improved dark theme colors for better accessibility
- Fixed CSS inheritance issues for code elements in dark contexts
- All color choices meet WCAG AA contrast requirements
This commit is contained in:
William Valentin
2025-09-14 14:58:35 -07:00
commit 860f60591c
37 changed files with 11599 additions and 0 deletions

398
scripts/colors.py Normal file
View File

@@ -0,0 +1,398 @@
"""
UnitForge Color Utility for Python
Centralized ANSI color definitions and utility functions for consistent terminal output
"""
import os
import sys
from typing import Optional
class Colors:
"""ANSI color codes and formatting constants."""
# Basic colors
RED = "\033[0;31m"
GREEN = "\033[0;32m"
YELLOW = "\033[1;33m"
BLUE = "\033[0;34m"
PURPLE = "\033[0;35m"
CYAN = "\033[0;36m"
WHITE = "\033[0;37m"
GRAY = "\033[0;90m"
# Bright colors
BRIGHT_RED = "\033[1;31m"
BRIGHT_GREEN = "\033[1;32m"
BRIGHT_YELLOW = "\033[1;33m"
BRIGHT_BLUE = "\033[1;34m"
BRIGHT_PURPLE = "\033[1;35m"
BRIGHT_CYAN = "\033[1;36m"
BRIGHT_WHITE = "\033[1;37m"
# Background colors
BG_RED = "\033[41m"
BG_GREEN = "\033[42m"
BG_YELLOW = "\033[43m"
BG_BLUE = "\033[44m"
BG_PURPLE = "\033[45m"
BG_CYAN = "\033[46m"
BG_WHITE = "\033[47m"
# Text formatting
BOLD = "\033[1m"
DIM = "\033[2m"
ITALIC = "\033[3m"
UNDERLINE = "\033[4m"
BLINK = "\033[5m"
REVERSE = "\033[7m"
STRIKETHROUGH = "\033[9m"
# Reset
NC = "\033[0m" # No Color
RESET = "\033[0m"
# Status symbols
INFO_SYMBOL = ""
SUCCESS_SYMBOL = ""
WARNING_SYMBOL = ""
ERROR_SYMBOL = ""
DEBUG_SYMBOL = "🐛"
def supports_color() -> bool:
"""
Check if the terminal supports colors.
Returns:
bool: True if colors are supported, False otherwise
"""
# Check if we're in a terminal
if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
return False
# Check environment variables
if os.environ.get("NO_COLOR"):
return False
if os.environ.get("FORCE_COLOR"):
return True
# Check TERM environment variable
term = os.environ.get("TERM", "")
if term == "dumb":
return False
return True
# Initialize colors based on support
if supports_color():
c = Colors()
else:
# Create a class with empty strings for all colors
class NoColors:
def __getattr__(self, name):
return ""
c = NoColors()
def color_text(text: str, color: str) -> str:
"""
Apply color to text.
Args:
text: The text to colorize
color: The ANSI color code
Returns:
str: Colored text with reset at the end
"""
if not supports_color():
return text
return f"{color}{text}{c.NC}"
def red(text: str) -> str:
"""Return red colored text."""
return color_text(text, c.RED)
def green(text: str) -> str:
"""Return green colored text."""
return color_text(text, c.GREEN)
def yellow(text: str) -> str:
"""Return yellow colored text."""
return color_text(text, c.YELLOW)
def blue(text: str) -> str:
"""Return blue colored text."""
return color_text(text, c.BLUE)
def purple(text: str) -> str:
"""Return purple colored text."""
return color_text(text, c.PURPLE)
def cyan(text: str) -> str:
"""Return cyan colored text."""
return color_text(text, c.CYAN)
def white(text: str) -> str:
"""Return white colored text."""
return color_text(text, c.WHITE)
def gray(text: str) -> str:
"""Return gray colored text."""
return color_text(text, c.GRAY)
def bright_red(text: str) -> str:
"""Return bright red colored text."""
return color_text(text, c.BRIGHT_RED)
def bright_green(text: str) -> str:
"""Return bright green colored text."""
return color_text(text, c.BRIGHT_GREEN)
def bright_yellow(text: str) -> str:
"""Return bright yellow colored text."""
return color_text(text, c.BRIGHT_YELLOW)
def bright_blue(text: str) -> str:
"""Return bright blue colored text."""
return color_text(text, c.BRIGHT_BLUE)
def bright_purple(text: str) -> str:
"""Return bright purple colored text."""
return color_text(text, c.BRIGHT_PURPLE)
def bright_cyan(text: str) -> str:
"""Return bright cyan colored text."""
return color_text(text, c.BRIGHT_CYAN)
def bright_white(text: str) -> str:
"""Return bright white colored text."""
return color_text(text, c.BRIGHT_WHITE)
def bold(text: str) -> str:
"""Return bold text."""
return color_text(text, c.BOLD)
def dim(text: str) -> str:
"""Return dim text."""
return color_text(text, c.DIM)
def italic(text: str) -> str:
"""Return italic text."""
return color_text(text, c.ITALIC)
def underline(text: str) -> str:
"""Return underlined text."""
return color_text(text, c.UNDERLINE)
def info(message: str, file=None) -> None:
"""Print an info message."""
output = f"{c.BLUE}{c.INFO_SYMBOL}{c.NC} {message}"
print(output, file=file)
def success(message: str, file=None) -> None:
"""Print a success message."""
output = f"{c.GREEN}{c.SUCCESS_SYMBOL}{c.NC} {message}"
print(output, file=file)
def warning(message: str, file=None) -> None:
"""Print a warning message."""
output = f"{c.YELLOW}{c.WARNING_SYMBOL}{c.NC} {message}"
print(output, file=file)
def error(message: str, file=None) -> None:
"""Print an error message."""
output = f"{c.RED}{c.ERROR_SYMBOL}{c.NC} {message}"
print(output, file=file or sys.stderr)
def debug(message: str, file=None) -> None:
"""Print a debug message (only if DEBUG environment variable is set)."""
if os.environ.get("DEBUG"):
output = f"{c.GRAY}{c.DEBUG_SYMBOL}{c.NC} {message}"
print(output, file=file or sys.stderr)
def header(text: str, file=None) -> None:
"""Print a header with underline."""
print(file=file)
print(f"{c.BOLD}{c.BLUE}{text}{c.NC}", file=file)
print(f"{c.BLUE}{'=' * len(text)}{c.NC}", file=file)
def subheader(text: str, file=None) -> None:
"""Print a subheader with underline."""
print(file=file)
print(f"{c.BOLD}{text}{c.NC}", file=file)
print("-" * len(text), file=file)
def step(current: int, total: int, description: str, file=None) -> None:
"""Print a step indicator."""
output = f"{c.CYAN}[{current}/{total}]{c.NC} {c.BOLD}{description}{c.NC}"
print(output, file=file)
def status(status_type: str, message: str, file=None) -> None:
"""
Print a status message with appropriate formatting.
Args:
status_type: Type of status (ok, fail, warn, info, skip)
message: The status message
file: Output file (defaults to stdout, stderr for errors)
"""
status_map = {
"ok": (f"{c.GREEN}[ OK ]{c.NC}", None),
"success": (f"{c.GREEN}[ OK ]{c.NC}", None),
"done": (f"{c.GREEN}[ OK ]{c.NC}", None),
"fail": (f"{c.RED}[ FAIL ]{c.NC}", sys.stderr),
"error": (f"{c.RED}[ FAIL ]{c.NC}", sys.stderr),
"failed": (f"{c.RED}[ FAIL ]{c.NC}", sys.stderr),
"warn": (f"{c.YELLOW}[ WARN ]{c.NC}", None),
"warning": (f"{c.YELLOW}[ WARN ]{c.NC}", None),
"info": (f"{c.BLUE}[ INFO ]{c.NC}", None),
"skip": (f"{c.GRAY}[ SKIP ]{c.NC}", None),
"skipped": (f"{c.GRAY}[ SKIP ]{c.NC}", None),
}
status_prefix, default_file = status_map.get(
status_type.lower(), (f"{c.WHITE}[ ]{c.NC}", None)
)
output_file = file or default_file
print(f"{status_prefix} {message}", file=output_file)
def box_header(text: str, width: int = 50, file=None) -> None:
"""Print a fancy box header."""
padding = (width - len(text) - 2) // 2
print(f"{c.BLUE}{'' * (width - 2)}{c.NC}", file=file)
print(
f"{c.BLUE}{c.NC}{' ' * padding}{c.BOLD}{text}{c.NC}"
f"{' ' * padding}{c.BLUE}{c.NC}",
file=file,
)
print(f"{c.BLUE}{'' * (width - 2)}{c.NC}", file=file)
def box_message(
text: str, color: Optional[str] = None, width: int = 50, file=None
) -> None:
"""Print a message in a box."""
box_color = color or c.BLUE
padding = (width - len(text) - 2) // 2
print(f"{box_color}{'' * (width - 2)}{c.NC}", file=file)
print(
f"{box_color}{c.NC}{' ' * padding}{text}{' ' * padding}{box_color}{c.NC}",
file=file,
)
print(f"{box_color}{'' * (width - 2)}{c.NC}", file=file)
def progress_bar(current: int, total: int, width: int = 40, file=None) -> None:
"""Print a progress bar."""
percent = current * 100 // total
filled = current * width // total
empty = width - filled
bar = f"{c.BLUE}[{'' * filled}{'' * empty}] {percent}% ({current}/{total}){c.NC}"
print(f"\r{bar}", end="", file=file, flush=True)
if current == total:
print(file=file) # New line when complete
class ColorizedFormatter:
"""A context manager for colorized output."""
def __init__(self, color: str):
self.color = color
def __enter__(self):
if supports_color():
print(self.color, end="")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if supports_color():
print(c.NC, end="")
# Example usage and testing
if __name__ == "__main__":
print("\nUnitForge Color Utility Test")
print("=" * 30)
# Test basic colors
print("\nBasic Colors:")
print(red("Red text"))
print(green("Green text"))
print(yellow("Yellow text"))
print(blue("Blue text"))
print(purple("Purple text"))
print(cyan("Cyan text"))
# Test status functions
print("\nStatus Messages:")
info("This is an info message")
success("This is a success message")
warning("This is a warning message")
error("This is an error message")
debug("This is a debug message")
# Test headers
header("Main Header")
subheader("Sub Header")
# Test status indicators
print("\nStatus Indicators:")
status("ok", "Operation successful")
status("fail", "Operation failed")
status("warn", "Operation completed with warnings")
status("info", "Information message")
status("skip", "Operation skipped")
# Test boxes
print("\nBox Examples:")
box_header("UnitForge Test", 30)
box_message("Success!", c.GREEN, 30)
# Test progress
print("\nProgress Example:")
import time
for i in range(11):
progress_bar(i, 10, 30)
if i < 10:
time.sleep(0.1)
print("\nColor support:", "Yes" if supports_color() else "No")
print("Test completed!")