feat(profile): 관리자 인증 뱃지 표시 (사번 옆 골드 씰)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 14:15:04 +09:00
parent 75685e41da
commit d95f968800
6 changed files with 56 additions and 8 deletions

View File

@@ -42,6 +42,49 @@ export function icon(name, opts = {}) {
);
}
// 관리자 인증 뱃지 — 골드 스캘럽 씰 + 흰 체크(다색이라 icon() 대신 전용 SVG).
// 관리자 판별은 Keycloak 그룹 기준(users.is_admin 에 로그인 시 저장). 프로필 사번 옆에 표시.
function scallopPath(cx, cy, n, rValley, rOuter) {
// 계곡점(rValley)들을 바깥으로 볼록한 원호로 이어 씰 톱니를 만든다.
const half = Math.PI / n;
const sag = rOuter - rValley * Math.cos(half); // 볼록 높이(sagitta)
const c = 2 * rValley * Math.sin(half); // 현 길이
const r = (sag * sag + (c / 2) * (c / 2)) / (2 * sag); // 원호 반지름
let d = "";
for (let i = 0; i < n; i++) {
const a0 = (i / n) * 2 * Math.PI - Math.PI / 2;
const a1 = ((i + 1) / n) * 2 * Math.PI - Math.PI / 2;
const x0 = (cx + rValley * Math.cos(a0)).toFixed(2), y0 = (cy + rValley * Math.sin(a0)).toFixed(2);
const x1 = (cx + rValley * Math.cos(a1)).toFixed(2), y1 = (cy + rValley * Math.sin(a1)).toFixed(2);
if (i === 0) d += `M ${x0} ${y0} `;
d += `A ${r.toFixed(2)} ${r.toFixed(2)} 0 0 1 ${x1} ${y1} `;
}
return d + "Z";
}
export function adminBadge(opts = {}) {
const size = opts.size || 15;
const title = opts.title || "관리자 인증";
const scallop = scallopPath(8, 8, 11, 5.7, 7.5);
return (
`<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 16 16" ` +
`class="admin-badge" role="img" aria-label="${title}" style="display:block"><title>${title}</title>` +
`<defs>` +
`<linearGradient id="aidevAdminGold" x1="0" y1="0" x2="0" y2="1">` +
`<stop offset="0" stop-color="#fbe58c"/><stop offset="0.45" stop-color="#eabf34"/><stop offset="1" stop-color="#c68f16"/>` +
`</linearGradient>` +
`<radialGradient id="aidevAdminGloss" cx="0.5" cy="0.32" r="0.65">` +
`<stop offset="0" stop-color="#ffffff" stop-opacity="0.55"/><stop offset="0.55" stop-color="#ffffff" stop-opacity="0"/>` +
`</radialGradient>` +
`</defs>` +
`<circle cx="8" cy="8" r="5.7" fill="url(#aidevAdminGold)"/>` +
`<path d="${scallop}" fill="url(#aidevAdminGold)" stroke="#a9790f" stroke-opacity="0.55" stroke-width="0.5"/>` +
`<circle cx="8" cy="8" r="5.7" fill="url(#aidevAdminGloss)"/>` +
`<path d="M4.8 8.25 L6.85 10.25 L11.15 5.55" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>` +
`</svg>`
);
}
// 프로젝트 설명(README 형식) 마크다운 렌더링.
// html:false → 원문 속 raw HTML 은 태그가 아니라 텍스트로 이스케이프되어 XSS 를 막는다.
// markdown-it 기본 validateLink 가 javascript:/vbscript:/data: 링크도 차단한다.