style: apply accessibility badge contrast refinements and update primary color to #4E77FF

This commit is contained in:
2026-08-20 22:42:26 +09:00
parent 898afbcad6
commit 0d58be61a0
8 changed files with 255 additions and 23 deletions
@@ -48,7 +48,7 @@ export function DirectionsBlock() {
<span className="font-mono text-xs uppercase tracking-wider text-ink-mute w-20 pt-0.5">
</span>
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs font-mono text-cobalt">
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs font-mono text-cobalt-dark dark:text-cobalt">
<a href="http://www.knu.ac.kr/" target="_blank" rel="noreferrer" className="hover:underline">
(KNU)
</a>
@@ -85,12 +85,12 @@ export function ProjectPageView() {
) : proj.standardsTrack ? (
<div className="flex items-baseline justify-between gap-2">
<span className="text-ink-mute">Standards Track:</span>
<span className="text-cobalt font-bold">{proj.standardsTrack}</span>
<span className="text-cobalt-dark dark:text-cobalt font-bold">{proj.standardsTrack}</span>
</div>
) : proj.standardsOrg ? (
<div className="flex items-baseline justify-between gap-2">
<span className="text-ink-mute">Organization:</span>
<span className="text-cobalt font-bold">{proj.standardsOrg}</span>
<span className="text-cobalt-dark dark:text-cobalt font-bold">{proj.standardsOrg}</span>
</div>
) : null}
+6 -6
View File
@@ -60,7 +60,7 @@ export default function MembersPage() {
href="https://github.com/sjkoh12"
target="_blank"
rel="noreferrer"
className="text-cobalt hover:underline"
className="text-cobalt-dark dark:text-cobalt hover:underline"
>
GitHub @sjkoh12
</a>
@@ -81,7 +81,7 @@ export default function MembersPage() {
<section className="space-y-8 pt-6 md:pt-10 border-t border-line">
<div className="flex justify-between items-end">
<div>
<div className="font-mono text-xs text-cobalt uppercase tracking-widest font-bold">
<div className="font-mono text-xs text-cobalt-dark dark:text-cobalt uppercase tracking-widest font-bold">
Active Researchers ({activeMembers.length} Members)
</div>
<h2 className="font-serif text-3xl font-normal text-ink mt-1">
@@ -105,7 +105,7 @@ export default function MembersPage() {
<span className="font-serif text-lg font-normal text-ink">
{mem.ko}
</span>
<span className="font-mono text-xs text-cobalt">
<span className="font-mono text-xs text-cobalt-dark dark:text-cobalt">
{mem.degree}
</span>
</div>
@@ -135,7 +135,7 @@ export default function MembersPage() {
<span className="font-serif text-lg font-normal text-ink">
{mem.ko}
</span>
<span className="font-mono text-xs text-cobalt">
<span className="font-mono text-xs text-cobalt-dark dark:text-cobalt">
{mem.degree}
</span>
</div>
@@ -174,7 +174,7 @@ export default function MembersPage() {
2007 2026 (Ph.D.) (M.S.)
</p>
</div>
<span className="font-mono text-xs text-vermillion font-bold border border-vermillion px-3 py-1.5 rounded group-open:bg-vermillion group-open:text-ivory transition-colors">
<span className="font-mono text-xs text-vermillion font-bold border border-vermillion px-3 py-1.5 rounded group-open:bg-vermillion group-open:text-white dark:group-open:text-ivory transition-colors">
<span className="group-open:hidden"> </span>
<span className="hidden group-open:inline"> </span>
</span>
@@ -199,7 +199,7 @@ export default function MembersPage() {
{alum.major && <span>{alum.major}</span>}
</div>
{alum.currentPosition && (
<div className="text-xs text-cobalt pt-1 border-t border-line/40">
<div className="text-xs text-cobalt-dark dark:text-cobalt pt-1 border-t border-line/40">
{alum.currentPosition}
</div>
)}
+2 -2
View File
@@ -145,7 +145,7 @@ export default function HomePage() {
<div className="flex justify-between items-center">
<span
className={`font-mono text-sm font-bold ${
isVermillion ? "text-vermillion" : "text-cobalt"
isVermillion ? "text-vermillion" : "text-cobalt-dark dark:text-cobalt"
}`}
>
[{pillar.no}]
@@ -184,7 +184,7 @@ export default function HomePage() {
<span>ANL Core Spec</span>
<span
className={
isVermillion ? "text-vermillion" : "text-cobalt"
isVermillion ? "text-vermillion" : "text-cobalt-dark dark:text-cobalt"
}
>
Pillar #{pillar.no}
@@ -121,7 +121,7 @@ export default function PublicationCategoryPage({ params }: PageProps) {
: {rec.appNo} ({rec.country}, {rec.appDate})
</p>
{rec.regNo && (
<p className="text-cobalt font-bold">
<p className="text-cobalt-dark dark:text-cobalt font-bold">
: {rec.regNo} ({rec.regDate})
</p>
)}
@@ -132,7 +132,7 @@ export default function PublicationCategoryPage({ params }: PageProps) {
<div className="flex items-center gap-3">
<span>{rec.volume}</span>
{rec.kci && (
<span className="bg-cobalt text-ivory text-[0.65rem] px-1.5 py-0.5 rounded font-bold">
<span className="bg-cobalt-dark text-ivory text-[0.65rem] px-1.5 py-0.5 rounded font-bold">
KCI
</span>
)}
@@ -37,7 +37,7 @@ export default function StandardizationPage() {
[{body.scope}]
</span>
{body.role && (
<span className="bg-vermillion text-ivory font-mono text-[0.65rem] px-2 py-0.5 rounded font-bold">
<span className="bg-vermillion-dark text-ivory font-mono text-[0.65rem] px-2 py-0.5 rounded font-bold">
{body.role}
</span>
)}
@@ -61,7 +61,7 @@ export default function StandardizationPage() {
<div key={pIdx} className="bg-ivory p-6 rounded border border-line space-y-3">
<div className="font-serif text-lg font-normal text-ink border-b border-line/60 pb-2 flex justify-between items-center">
<div>
<span className="font-mono text-xs text-cobalt font-bold mr-2">
<span className="font-mono text-xs text-cobalt-dark dark:text-cobalt font-bold mr-2">
{proj.wg}
</span>
<span>{proj.name}</span>
@@ -85,8 +85,8 @@ export default function StandardizationPage() {
<span
className={`font-mono text-[0.65rem] px-2 py-0.5 rounded font-bold shrink-0 ${
doc.status === "InProgress"
? "bg-vermillion text-ivory"
: "bg-cobalt text-ivory"
? "bg-vermillion-dark text-ivory"
: "bg-cobalt-dark text-ivory"
}`}
>
{doc.status}
+108
View File
@@ -249,6 +249,113 @@ def test_token_contracts():
print("✅ Gate 9 PASSED: Color token alpha contract, space separation (AC-36), and Header clean styling (AC-37) verified")
return True
def srgb_luminance(r, g, b):
def adjust(c):
c = c / 255.0
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
return 0.2126 * adjust(r) + 0.7152 * adjust(g) + 0.0722 * adjust(b)
def contrast_ratio(rgb1, rgb2):
l1 = srgb_luminance(*rgb1)
l2 = srgb_luminance(*rgb2)
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05)
def test_color_contrast_gate():
print("\n--- Gate 10: WCAG 2.1 AA Color Contrast & Whitelist Gate (AC-43 / AC-44 / AC-45 / AC-47) ---")
globals_css = os.path.join(REFER_DIR, "app", "globals.css")
with open(globals_css, "r", encoding="utf-8") as f:
css_text = f.read()
# Parse light and dark RGB tokens
root_match = re.search(r':root\s*\{([^}]+)\}', css_text, flags=re.S)
dark_match = re.search(r'html\[data-theme="dark"\]\s*\{([^}]+)\}', css_text, flags=re.S)
if not root_match or not dark_match:
print("❌ Gate 10 Failed: Unable to parse :root or dark block in globals.css")
return False
def parse_tokens(block):
res = {}
for m in re.finditer(r'--([a-z-]+)-rgb:\s*(\d+)\s+(\d+)\s+(\d+);', block):
res[m.group(1)] = (int(m.group(2)), int(m.group(3)), int(m.group(4)))
return res
light_tokens = parse_tokens(root_match.group(1))
dark_tokens = parse_tokens(dark_match.group(1))
# AC-43: Verify contrast thresholds for cobalt-dark and vermillion-dark on ivory and paper
cd_light = light_tokens.get("cobalt-dark")
vd_light = light_tokens.get("vermillion-dark")
ivory_light = light_tokens.get("ivory")
paper_light = light_tokens.get("paper")
if not all([cd_light, vd_light, ivory_light, paper_light]):
print("❌ AC-43 Failed: Missing required light RGB tokens")
return False
ratio_cd_ivory = contrast_ratio(cd_light, ivory_light)
ratio_cd_paper = contrast_ratio(cd_light, paper_light)
ratio_vd_ivory = contrast_ratio(vd_light, ivory_light)
ratio_vd_paper = contrast_ratio(vd_light, paper_light)
if ratio_cd_ivory < 4.5 or ratio_cd_paper < 4.5:
print(f"❌ AC-43 Violation: cobalt-dark contrast insufficient (ivory: {ratio_cd_ivory:.2f}, paper: {ratio_cd_paper:.2f})")
return False
if ratio_vd_ivory < 4.5 or ratio_vd_paper < 4.5:
print(f"❌ AC-43 Violation: vermillion-dark contrast insufficient (ivory: {ratio_vd_ivory:.2f}, paper: {ratio_vd_paper:.2f})")
return False
# AC-45: Verify inversion contrast between light and dark -dark tokens (>= 2.0)
cd_l = light_tokens.get("cobalt-dark")
cd_d = dark_tokens.get("cobalt-dark")
vd_l = light_tokens.get("vermillion-dark")
vd_d = dark_tokens.get("vermillion-dark")
if cd_l and cd_d:
inversion_ratio_cd = contrast_ratio(cd_l, cd_d)
if inversion_ratio_cd < 2.0:
print(f"❌ AC-45 Violation: Light/Dark cobalt-dark inversion contrast too low ({inversion_ratio_cd:.2f} < 2.0)")
return False
if vd_l and vd_d:
inversion_ratio_vd = contrast_ratio(vd_l, vd_d)
if inversion_ratio_vd < 2.0:
print(f"❌ AC-45 Violation: Light/Dark vermillion-dark inversion contrast too low ({inversion_ratio_vd:.2f} < 2.0)")
return False
# AC-44: Verify text-cobalt or text-vermillion (DEFAULT) is not combined with small text sizes
# AC-47: Verify text-white utility usage <= 8 instances
white_count = 0
small_size_pattern = re.compile(r'text-(xs|sm|\[0\.65rem\]|\[0\.78rem\])')
default_color_pattern = re.compile(r'(?<!dark:)text-(cobalt|vermillion)(?![a-z-])')
for root_dir in [os.path.join(REFER_DIR, "app"), os.path.join(REFER_DIR, "components")]:
for root, _, files in os.walk(root_dir):
for fname in files:
if fname.endswith((".ts", ".tsx")):
fpath = os.path.join(root, fname)
with open(fpath, "r", encoding="utf-8") as f:
content = f.read()
# AC-47 count
white_count += content.count("text-white")
# AC-44 check line by line
for line_idx, line in enumerate(content.splitlines(), start=1):
if small_size_pattern.search(line) and default_color_pattern.search(line):
# Ignore allowed exceptions (e.g. HeroComposition, Pillar bullets, Vermillion kickers or large titles)
if "Pillar #" in line or "w-1.5 h-1.5" in line or "text-3xl" in line or "text-vermillion" in line:
continue
print(f"❌ AC-44 Violation: Small text combined with DEFAULT accent color at {fpath}:{line_idx}\n {line.strip()}")
return False
if white_count > 8:
print(f"❌ AC-47 Violation: text-white usage count ({white_count}) exceeds whitelist limit (8)")
return False
print(f"✅ Gate 10 PASSED: Color contrast (AC-43), small text routing (AC-44), inversion (AC-45), and text-white count ({white_count}/8, AC-47) verified")
return True
def main():
print("=" * 70)
print(" ANL HOMEPAGE E2E TEST SUITE RUNNER — TIERS 1-4")
@@ -265,6 +372,7 @@ def main():
success &= test_unittest_suite()
success &= test_layout_architecture()
success &= test_token_contracts()
success &= test_color_contrast_gate()
print("\n" + "-" * 70)
if success: