feat(B09): 박스보기 카드 제목 줄 · 합계 줄 바탕색 구분(밝음 · 어두움 테마 토큰)(200-3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G
This commit is contained in:
@@ -116,3 +116,85 @@ def test_잇기():
|
||||
assert "attachHoverInfo(hint," in groups
|
||||
cards = (ROOT / COMMON).read_text("utf-8")
|
||||
assert "if (opts.lead) bar.append(opts.lead);" in cards
|
||||
|
||||
|
||||
# ── 200-3 카드 제목 줄 · 합계 줄 바탕색(밝음 · 어두움) ─────────────────────────
|
||||
|
||||
|
||||
def _theme_vars(dark: bool) -> dict:
|
||||
"""테마 CSS 의 `:root`(밝음) · 어두움 덮어쓰기 토큰 → 한 벌."""
|
||||
css = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
|
||||
light = dict(re.findall(r"(--[\w-]+):\s*([^;]+);", _block(css, ":root")))
|
||||
if dark:
|
||||
light.update(re.findall(r"(--[\w-]+):\s*([^;]+);", _block(css, ':root[data-theme="dark"]')))
|
||||
return light
|
||||
|
||||
|
||||
def _rgb(token: str, theme: dict) -> tuple:
|
||||
"""토큰 → (r, g, b) · var() 별칭 따라감 · 16진수만."""
|
||||
val = theme[token].split("/*")[0].strip()
|
||||
while m := re.fullmatch(r"var\((--[\w-]+)\)", val):
|
||||
val = theme[m.group(1)].split("/*")[0].strip()
|
||||
assert re.fullmatch(r"#[0-9a-fA-F]{6}", val), (token, val)
|
||||
return tuple(int(val[i : i + 2], 16) for i in (1, 3, 5))
|
||||
|
||||
|
||||
def _mix(spec: str, theme: dict) -> tuple:
|
||||
"""`color-mix(in srgb, var(A) N%, var(B))` → 섞은 (r, g, b)."""
|
||||
m = re.fullmatch(
|
||||
r"color-mix\(in srgb,\s*var\((--[\w-]+)\)\s*(\d+)%,\s*var\((--[\w-]+)\)\)", spec
|
||||
)
|
||||
assert m, spec
|
||||
a, n, b = _rgb(m.group(1), theme), int(m.group(2)) / 100, _rgb(m.group(3), theme)
|
||||
return tuple(a[i] * n + b[i] * (1 - n) for i in range(3))
|
||||
|
||||
|
||||
def _lum(c: tuple) -> float:
|
||||
ch = [
|
||||
(v / 255) / 12.92 if v / 255 <= 0.03928 else (((v / 255) + 0.055) / 1.055) ** 2.4 for v in c
|
||||
]
|
||||
return 0.2126 * ch[0] + 0.7152 * ch[1] + 0.0722 * ch[2]
|
||||
|
||||
|
||||
def _contrast(a: tuple, b: tuple) -> float:
|
||||
hi, lo = sorted((_lum(a), _lum(b)), reverse=True)
|
||||
return (hi + 0.05) / (lo + 0.05)
|
||||
|
||||
|
||||
def _tokens() -> tuple:
|
||||
css = (ROOT / "ui_template" / "ui_template_group_cards.css").read_text("utf-8")
|
||||
root = _block(css, ".ui-group-cards")
|
||||
head = re.search(r"--group-head-bg:\s*([^;]+);", root)
|
||||
foot = re.search(r"--group-foot-bg:\s*([^;]+);", root)
|
||||
assert head and foot
|
||||
return css, head.group(1), foot.group(1)
|
||||
|
||||
|
||||
def test_카드_제목_줄과_합계_줄_바탕색_토큰():
|
||||
css, head, foot = _tokens()
|
||||
assert head != foot
|
||||
assert "background-color: var(--group-head-bg)" in _block(css, ".ui-group-card__head")
|
||||
assert "background-color: var(--group-foot-bg)" in _block(css, ".ui-group-card__foot")
|
||||
for spec in (head, foot): # 고른 값 없이 테마 변수만(색 고정값 없음)
|
||||
assert "#" not in spec and "rgb(" not in spec
|
||||
|
||||
|
||||
def test_밝음_어두움_두_색이_다르고_글_대비_유지():
|
||||
_, head, foot = _tokens()
|
||||
for dark in (False, True):
|
||||
theme = _theme_vars(dark)
|
||||
h, f = _mix(head, theme), _mix(foot, theme)
|
||||
assert sum(abs(h[i] - f[i]) for i in range(3)) >= 40, (dark, h, f) # 서로 다른 색
|
||||
card = _rgb("--color-surface-raised", theme)
|
||||
assert sum(abs(f[i] - card[i]) for i in range(3)) >= 40 # 카드 바탕과도 구분
|
||||
for color in ("--color-text", "--color-text-secondary", "--color-accent"):
|
||||
assert _contrast(_rgb(color, theme), h) >= 4.5, (dark, "head", color)
|
||||
for color in ("--color-text", "--color-text-body", "--color-text-secondary"):
|
||||
assert _contrast(_rgb(color, theme), f) >= 4.5, (dark, "foot", color)
|
||||
|
||||
|
||||
def test_어두움_테마_값():
|
||||
# 어두움 테마에서 쓰는 토큰이 실제로 어두움 값으로 덮임(밝음 값 그대로가 아님)
|
||||
light, dark = _theme_vars(False), _theme_vars(True)
|
||||
for token in ("--color-surface", "--color-surface-raised", "--color-accent"):
|
||||
assert _rgb(token, light) != _rgb(token, dark), token
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* 묶음 카드 공용 양식(171-2) — 글자 14px 한 벌 · 줄 간격 좁게 · 숫자 오른쪽. 색은 테마 변수만. */
|
||||
|
||||
.ui-group-cards {
|
||||
/* 200-3 제목 줄 = 표 머리색 계열(옅게) · 합계 줄 = 강조색 더 짙게 — 둘 다 테마 변수에서 섞음(밝음 · 어두움 같이) */
|
||||
--group-head-bg: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface-raised));
|
||||
--group-foot-bg: color-mix(in srgb, var(--color-accent) 15%, var(--color-surface-raised));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
@@ -68,7 +71,8 @@
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
column-gap: var(--spacing-8);
|
||||
padding: 2px 0;
|
||||
padding: 2px var(--spacing-4);
|
||||
background-color: var(--group-head-bg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
@@ -148,6 +152,7 @@
|
||||
justify-content: flex-end;
|
||||
column-gap: var(--spacing-8);
|
||||
padding: 2px var(--spacing-4);
|
||||
background-color: var(--group-foot-bg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user