Move all three skills into skills/; add root README; fix paths

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Sonnet 5.5 committed 2026-10-16 17:04:38 +05:30
1 parent 312742cfef
commit d95e5d452d
84 files changed
+97 -31

No files matched your search

@@ -0,0 +1,100 @@
"""Derive a custom dashboard palette from one brand hex, then check text contrast.
Usage: python palette.py "#7a3cff"
Prints the accent tokens for the body{} (light) and body.dark{} blocks of
dashboard.html, which keeps the Tebra Sky base for everything else, then a
contrast table for light and dark. A brand colour that is too light or too dark
is nudged until text on it is readable. Exits 1 if any check still fails.
"""
import sys
WHITE, BLACK = "#ffffff", "#000000"
# Tebra Sky base colours the accents sit on. Keep in step with dashboard.html.
BASE = {"light": {"bg": "#eaf6fb", "ink": "#1c2b33"}, "dark": {"bg": "#182a38", "ink": "#e6eef4"}}
LOG_TEXT = "#f68c82" # dimmest log line colour; the log box background is --accent-dark
def rgb(h):
return [int(h[i:i + 2], 16) for i in (1, 3, 5)]
def mix(a, b, t):
"""Blend hex colour a toward b by t (0..1)."""
return "#" + "".join(f"{round(x + (y - x) * t):02x}" for x, y in zip(rgb(a), rgb(b)))
def contrast(a, b):
"""WCAG 2 contrast ratio between two hex colours."""
def lum(h):
c = [v / 255 for v in rgb(h)]
c = [x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055) ** 2.4 for x in c]
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
hi, lo = sorted((lum(a), lum(b)), reverse=True)
return (hi + 0.05) / (lo + 0.05)
def checks(t):
"""(mode, what, ratio, needed) for every text pair the accents affect."""
rows = []
for mode, m in t.items():
bg, ink = BASE[mode]["bg"], BASE[mode]["ink"]
rows += [(mode, "white button text on --accent", contrast(WHITE, m["--accent"]), 3),
(mode, "white button text on --accent-2", contrast(WHITE, m["--accent-2"]), 3),
(mode, "--accent text on the page", contrast(m["--accent"], bg), 3),
(mode, "ink on --accent-soft buttons", contrast(ink, m["--accent-soft"]), 4.5),
(mode, "log text on --accent-dark", contrast(LOG_TEXT, m["--accent-dark"]), 4.5)]
return rows
def nudge(colour, fix):
"""Step colour toward whatever fix() returns (WHITE/BLACK) until it returns None."""
for _ in range(60):
toward = fix(colour)
if toward is None:
break
colour = mix(colour, toward, 0.06)
return colour
def derive(brand):
brand = brand.strip().lower()
if len(brand) == 4: # #abc -> #aabbcc
brand = "#" + "".join(ch * 2 for ch in brand[1:])
out = {}
for mode, lift in [("light", 0.15), ("dark", 0.10)]:
bg, ink = BASE[mode]["bg"], BASE[mode]["ink"]
dark_page = mode == "dark"
def fix_accent(c):
if contrast(c, bg) < 3: # accent text must stand out from the page
return WHITE if dark_page else BLACK
if contrast(WHITE, mix(c, WHITE, lift)) < 3: # white text on the button
return BLACK
return None
a = nudge(brand, fix_accent)
soft = mix(a, BLACK, 0.35) if dark_page else mix(a, WHITE, 0.82)
out[mode] = {
"--accent": a,
"--accent-2": mix(a, WHITE, lift),
"--accent-dark": nudge(mix(a, BLACK, 0.45 if dark_page else 0.75),
lambda c: BLACK if contrast(LOG_TEXT, c) < 4.5 else None),
"--accent-soft": nudge(soft, lambda c: None if contrast(ink, c) >= 4.5 else BLACK if dark_page else WHITE),
"--accent-soft-2": mix(a, BLACK, 0.6) if dark_page else mix(a, WHITE, 0.62),
}
return out
if __name__ == "__main__":
if len(sys.argv) != 2 or not sys.argv[1].startswith("#"):
sys.exit(__doc__)
t = derive(sys.argv[1])
for mode, block in [("light", "body{"), ("dark", "body.dark{")]:
print(f"/* {mode}: replace these tokens in the {block} block */")
print(block + " " + "; ".join(f"{k}:{v}" for k, v in t[mode].items()) + "; }")
print()
rows = checks(t)
for mode, what, ratio, need in rows:
print(f"{mode:<6} {what:<34} {ratio:5.2f}:1 needs {need}:1 {'PASS' if ratio >= need else 'FAIL'}")
sys.exit(0 if all(r >= n for *_, r, n in rows) else 1)