style(M02): 구조물 조합 삭제 단추를 공용 위험(빨강) 단추로

- 카드 셋 머리 [삭제] · 일위대가 줄 [삭제] 두 곳 = ui-btn--danger(테마 토큰)
- 화면 확인 시나리오 45-6 추가(밝음 5.44 · 어두움 5.79 대비 · 옆 단추와 같은 키)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
2026-10-05 13:04:28 +09:00
co-authored by Claude Opus 5.5
parent 9e2c034d9f
commit aa2845eef7
3 changed files with 79 additions and 5 deletions
@@ -0,0 +1,70 @@
/* 45-6 — M02 구조물 조합 카드 [삭제] = 공용 위험(빨강) 단추 · 옆 단추와 같은 키 · 밝음/어두움 대비 ≥ 4.5
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 ·
* 나무에 item1(구조물 조합)이 있음
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌)
* 기대 수치(light · dark 각각):
* - dels ≥ 1 · 모두 ui-btn--danger · 알약 꼴 아님(round = false)
* - bg = --color-danger(light rgb(192, 57, 43) · dark rgb(232, 105, 90)) · fg = --color-canvas
* - contrast ≥ 4.5 · sameHeight = true(옆 [수정] 단추와 높이 같음)
* - 끝에 data-theme 원래 값으로 되돌림(themeAfter = themeBefore)
*/
(async () => {
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const until = async (fn, ms = 6000) => {
for (let t = 0; t < ms; t += 150) {
const v = fn();
if (v) return v;
await w(150);
}
return null;
};
const vis = (b) => b && !b.hidden && b.offsetParent !== null;
const rgb = (s) => s.match(/[\d.]+/g).slice(0, 3).map(Number);
const lum = (c) =>
c
.map((v) => v / 255)
.map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4))
.reduce((a, v, i) => a + v * [0.2126, 0.7152, 0.0722][i], 0);
const ratio = (a, b) => {
const [x, y] = [lum(rgb(a)), lum(rgb(b))].sort((p, q) => q - p);
return Math.round(((x + 0.05) / (y + 0.05)) * 100) / 100;
};
const node = await until(() => document.querySelector('[data-node="i|item1"]'));
if (!node) return JSON.stringify({ error: "item1 없음 — 기슭막이 폴더를 펼쳐 둘 것" });
node.click();
await until(() => document.querySelector(".m02-struct__card-tools .ui-btn"));
await w(1500);
const root = document.documentElement;
const themeBefore = root.getAttribute("data-theme");
const out = { themeBefore };
// 숨은 탭에선 색 전환(transition)이 안 흘러 옛 색에 멈춤 — 재는 동안만 끔
const still = document.head.appendChild(document.createElement("style"));
still.textContent = "*{transition:none!important}";
for (const theme of ["light", "dark"]) {
root.setAttribute("data-theme", theme);
await w(300);
const rows = [...document.querySelectorAll(".m02-struct__card-tools")].map((tools) => {
const btns = [...tools.querySelectorAll("button")].filter(vis);
const del = btns.find((b) => b.innerText.trim() === "삭제");
const edit = btns.find((b) => b.innerText.trim() === "수정") ?? btns.find((b) => b !== del);
if (!del) return null;
const cs = getComputedStyle(del);
const h = del.getBoundingClientRect().height;
return {
cls: del.classList.contains("ui-btn--danger") ? "danger" : del.className,
bg: cs.backgroundColor,
fg: cs.color,
contrast: ratio(cs.backgroundColor, cs.color),
round: parseFloat(cs.borderTopLeftRadius) >= h / 2 - 1,
h,
sameHeight: edit ? Math.abs(edit.getBoundingClientRect().height - h) < 0.5 : null,
};
});
out[theme] = rows.filter(Boolean);
}
still.remove();
if (themeBefore === null) root.removeAttribute("data-theme");
else root.setAttribute("data-theme", themeBefore);
out.themeAfter = root.getAttribute("data-theme");
return JSON.stringify(out);
})()