spaxel/dashboard/css/_fix_html.py
jedarden 8f878c6cea style(dashboard): complete CSS tokenization and add live-view grid layout classes
Replace remaining hardcoded border-radius and color values across 22 CSS
files with design system tokens. Add .live-status-bar, .live-scene, and
.live-panel-* classes to layout.css for the grid-based live view shell.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 15:14:40 -04:00

210 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""Replace hard-coded CSS values in HTML inline styles with design system tokens."""
import re, sys, os
# Same color map as _tokenize.py but for HTML files
COLOR_MAP = {
# Backgrounds
'#1a1a2e': 'var(--bg-page)',
'#1e1e3a': 'var(--bg-card)',
'#1e1e2e': 'var(--bg-card)',
'#121225': 'var(--bg-page)',
'#18191b': 'var(--bg-page)',
# Text colors
'#eee': 'var(--text-primary)',
'#eeeeee': 'var(--text-primary)',
'#ccc': 'var(--text-secondary)',
'#cccccc': 'var(--text-secondary)',
'#e0e0e0': 'var(--text-primary)',
'#aaa': 'var(--text-secondary)',
'#888': 'var(--text-muted)',
'#888888': 'var(--text-muted)',
'#666': 'var(--text-muted)',
'#666666': 'var(--text-muted)',
'#555': 'var(--text-muted)',
'#555555': 'var(--text-muted)',
'#fff': 'var(--text-on-accent)',
'#ffffff': 'var(--text-on-accent)',
# Blues
'#4fc3f7': 'var(--blue-10)',
'#42a5f5': 'var(--blue-10)',
'#4a9eff': 'var(--blue-9)',
'#29b6f6': 'var(--blue-10)',
'#3b82f6': 'var(--blue-9)',
'#2196f3': 'var(--blue-9)',
'#1a73e8': 'var(--blue-9)',
# Greens
'#4caf50': 'var(--ok)',
'#66bb6a': 'var(--ok)',
'#81c784': 'var(--ok)',
'#2ecc71': 'var(--ok)',
'#22c55e': 'var(--ok)',
'#1abc9c': 'var(--ok)',
# Reds
'#f44336': 'var(--alert)',
'#ef5350': 'var(--alert)',
'#e57373': 'var(--alert)',
'#dc3545': 'var(--alert)',
'#ff4757': 'var(--alert)',
'#f66': 'var(--alert)',
# Ambers
'#ffc107': 'var(--warn)',
'#ffa726': 'var(--warn)',
'#fbbf24': 'var(--warn)',
'#ff9800': 'var(--warn)',
# Purples → blue accent (dark-only)
'#ab47bc': 'var(--blue-9)',
'#9c27b0': 'var(--blue-8)',
'#ba68c8': 'var(--blue-10)',
}
# RGBA replacements
RGBA_MAP = {
'rgba(79, 195, 247, 0.2)': 'var(--blue-muted)',
'rgba(79,195,247,0.2)': 'var(--blue-muted)',
'rgba(79, 195, 247, 0.25)': 'var(--blue-muted)',
'rgba(79,195,247,0.25)': 'var(--blue-muted)',
'rgba(79, 195, 247, 0.5)': 'var(--blue-border)',
'rgba(79,195,247,0.5)': 'var(--blue-border)',
'rgba(255, 255, 255, 0.03)': 'var(--bg-hover)',
'rgba(255,255,255,0.03)': 'var(--bg-hover)',
'rgba(255, 255, 255, 0.05)': 'var(--bg-hover)',
'rgba(255,255,255,0.05)': 'var(--bg-hover)',
'rgba(255, 255, 255, 0.08)': 'var(--border-default)',
'rgba(255,255,255,0.08)': 'var(--border-default)',
'rgba(255, 255, 255, 0.1)': 'var(--bg-hover)',
'rgba(255,255,255,0.1)': 'var(--bg-hover)',
'rgba(255, 255, 255, 0.12)': 'var(--bg-hover)',
'rgba(255,255,255,0.12)': 'var(--bg-hover)',
'rgba(255, 255, 255, 0.15)': 'var(--border-strong)',
'rgba(255,255,255,0.15)': 'var(--border-strong)',
'rgba(255, 255, 255, 0.5)': 'var(--text-muted)',
'rgba(255,255,255,0.5)': 'var(--text-muted)',
'rgba(255, 255, 255, 0.7)': 'var(--text-secondary)',
'rgba(255,255,255,0.7)': 'var(--text-secondary)',
'rgba(255, 255, 255, 0.9)': 'var(--text-primary)',
'rgba(255,255,255,0.9)': 'var(--text-primary)',
'rgba(0, 0, 0, 0.85)': 'var(--overlay-panel)',
'rgba(0,0,0,0.85)': 'var(--overlay-panel)',
'rgba(0, 0, 0, 0.8)': 'var(--overlay-strong)',
'rgba(0,0,0,0.8)': 'var(--overlay-strong)',
'rgba(0, 0, 0, 0.5)': 'var(--overlay)',
'rgba(0,0,0,0.5)': 'var(--overlay)',
'rgba(244, 67, 54, 0.3)': 'var(--alert-muted)',
'rgba(244,67,54,0.3)': 'var(--alert-muted)',
'rgba(244, 67, 54, 0.25)': 'var(--alert-muted)',
'rgba(244,67,54,0.25)': 'var(--alert-muted)',
'rgba(76, 175, 80, 0.3)': 'var(--ok-muted)',
'rgba(76,175,80,0.3)': 'var(--ok-muted)',
'rgba(76, 175, 80, 0.2)': 'var(--ok-muted)',
'rgba(76,175,80,0.2)': 'var(--ok-muted)',
'rgba(76, 175, 80, 0.15)': 'var(--ok-bg)',
'rgba(76,175,80,0.15)': 'var(--ok-bg)',
'rgba(102, 187, 106, 0.5)': 'var(--ok-muted)',
'rgba(102,187,106,0.5)': 'var(--ok-muted)',
'rgba(255, 167, 38, 0.5)': 'var(--warn-muted)',
'rgba(255,167,38,0.5)': 'var(--warn-muted)',
'rgba(239, 83, 80, 0.5)': 'var(--alert-muted)',
'rgba(239,83,80,0.5)': 'var(--alert-muted)',
'rgba(66, 165, 245, 0.7)': 'var(--blue-muted)',
'rgba(66,165,245,0.7)': 'var(--blue-muted)',
'rgba(66, 165, 245, 0.9)': 'var(--blue-muted)',
'rgba(66,165,245,0.9)': 'var(--blue-muted)',
'rgba(66, 165, 245, 0.15)': 'var(--blue-muted)',
'rgba(66,165,245,0.15)': 'var(--blue-muted)',
'rgba(66, 165, 245, 0.25)': 'var(--blue-muted)',
'rgba(66,165,245,0.25)': 'var(--blue-muted)',
'rgba(171, 71, 188, 0.5)': 'var(--blue-muted)',
'rgba(171,71,188,0.5)': 'var(--blue-muted)',
'rgba(255, 193, 7, 0.2)': 'var(--warn-bg)',
'rgba(255,193,7,0.2)': 'var(--warn-bg)',
'rgba(255, 193, 7, 0.4)': 'var(--warn-border)',
'rgba(255,193,7,0.4)': 'var(--warn-border)',
'rgba(255, 193, 7, 0.3)': 'var(--warn-muted)',
'rgba(255,193,7,0.3)': 'var(--warn-muted)',
'rgba(255, 193, 7, 0.6)': 'var(--warn-border)',
'rgba(255,193,7,0.6)': 'var(--warn-border)',
'rgba(255, 193, 7, 0.4)': 'var(--warn-border)',
'rgba(255,193,7,0.4)': 'var(--warn-border)',
}
def process_file(filepath):
with open(filepath, 'r') as f:
content = f.read()
changes = 0
# Replace rgba values first (more specific)
for old, new in RGBA_MAP.items():
count = content.count(old)
if count > 0:
content = content.replace(old, new)
changes += count
# Replace hex colors
lines = content.split('\n')
result = []
for line in lines:
new_line = line
hex_pattern = re.compile(r'#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b')
matches = list(hex_pattern.finditer(line))
for m in reversed(matches):
hex_val = m.group(0).lower()
# Skip meta theme-color tags
prefix = line[:m.start()].rstrip()
if 'content=' in prefix and 'theme-color' in line[:m.start()]:
continue
if hex_val in COLOR_MAP:
replacement = COLOR_MAP[hex_val]
new_line = new_line[:m.start()] + replacement + new_line[m.end():]
changes += 1
result.append(new_line)
content = '\n'.join(result)
# Replace font-family
body_font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif"
if body_font in content:
count = content.count(body_font)
content = content.replace(body_font, 'var(--font-body)')
changes += count
# Replace bare monospace in CSS context
content = re.sub(r"font-family:\s*monospace;", "font-family: var(--font-mono);", content)
changes_add = len(re.findall(r"font-family: var\(--font-mono\);", content))
# Replace border-radius values
# 2px, 3px → var(--radius-control)
content = re.sub(r'border-radius:\s*([2-4])px(?!\s)', r'border-radius: var(--radius-control)', content)
# 6px → var(--radius-control)
content = re.sub(r'border-radius:\s*6px(?!\s)', 'border-radius: var(--radius-control)', content)
# 8px, 10px, 12px → var(--radius-card)
content = re.sub(r'border-radius:\s*(?:8|10|12)px(?!\s)', 'border-radius: var(--radius-card)', content)
# 20px, 24px → var(--radius-modal)
content = re.sub(r'border-radius:\s*(?:20|24)px(?!\s)', 'border-radius: var(--radius-modal)', content)
if changes > 0:
with open(filepath, 'w') as f:
f.write(content)
print(f" {os.path.basename(filepath)}: {changes} replacements")
else:
print(f" {os.path.basename(filepath)}: no changes needed")
return changes
if __name__ == '__main__':
html_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
total = 0
for fname in sorted(os.listdir(html_dir)):
if not fname.endswith('.html'):
continue
fpath = os.path.join(html_dir, fname)
total += process_file(fpath)
print(f"\nTotal replacements: {total}")