심각도 표시
위협 심각도 5단계(Critical · High · Medium · Low · Info)를 화면 전반에서 일관되게 표현하는 규칙입니다. 색·태그·밴드·정렬 순서를 통일합니다.
Composition
이 패턴이 조합하는 컴포넌트입니다. 세부 옵션은 각 컴포넌트 페이지를 참고하세요.
--sev-*(채운 태그·바·밴드) · 글자 --sev-*-text(기본형 태그·KPI) · 면 위 글자 --sev-*-on. 단계 값과 테마별 차이는 Colors › 심각도의 3층1. 5단계 스케일
Tag 3가지 스타일 + 인디케이터. 같은 토큰을 공유합니다.
CriticalHighMediumLowInfo
CriticalHighMediumLowInfo
CriticalHighMediumLowInfo
HTML
<div class="stack" style="max-width:640px;gap:20px">
<div style="display:flex;gap:8px;flex-wrap:wrap"><span class="tag critical">Critical</span><span class="tag high">High</span><span class="tag medium">Medium</span><span class="tag low">Low</span><span class="tag info">Info</span></div>
<div style="display:flex;gap:8px;flex-wrap:wrap"><span class="tag critical solid">Critical</span><span class="tag high solid">High</span><span class="tag medium solid">Medium</span><span class="tag low solid">Low</span><span class="tag info solid">Info</span></div>
<div style="display:flex;gap:16px;flex-wrap:wrap"><span class="sev-cell"><i class="critical"></i>Critical</span><span class="sev-cell"><i class="high"></i>High</span><span class="sev-cell"><i class="medium"></i>Medium</span><span class="sev-cell"><i class="low"></i>Low</span><span class="sev-cell"><i></i>Info</span></div>
</div>CSS · .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack
/* JS Design System v0.6 · .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack · 토큰(var(--*))은 assets/style.css :root 참고 */
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.tag{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;padding:3px 10px;border-radius:var(--radius-full);line-height:1.6;background:var(--bg-panel);color:var(--text-secondary);white-space:nowrap}
.tag::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.tag.critical{background:var(--sev-critical-bg);color:var(--sev-critical-text)}
.tag.high{background:var(--sev-high-bg);color:var(--sev-high-text)}
.tag.medium{background:var(--sev-medium-bg);color:var(--sev-medium-text)}
.tag.low{background:var(--sev-low-bg);color:var(--sev-low-text)}
.tag.info{background:var(--sev-info-bg);color:var(--sev-info-text)}
.tag.ok{background:var(--success-subtle);color:var(--success-text)}
.tag.accent{background:var(--accent-subtle);color:var(--accent-text)}
.tag.sm{font-size:12px;padding:1px 8px}
.tag.lg{font-size:12px;padding:5px 12px}
.tag.plain::before{display:none}
.tag.outline{background:transparent;box-shadow:inset 0 0 0 1px currentColor}
.tag.solid.critical{background:var(--sev-critical);color:var(--sev-critical-on)}
.tag.solid.high{background:var(--sev-high);color:var(--sev-high-on)}
.tag.solid.medium{background:var(--sev-medium);color:var(--sev-medium-on)}
.tag.solid.low{background:var(--sev-low);color:var(--sev-low-on)}
.tag.solid.info{background:var(--sev-info);color:var(--sev-info-on)}
.tag.solid.ok{background:var(--success);color:var(--success-on)}
.tag.solid.accent{background:var(--accent);color:var(--accent-on)}
.tabs .tab .count{font-size:12px;font-weight:700;padding:1px 6px;border-radius:var(--radius-full);background:var(--bg-panel);color:var(--text-secondary);min-width:0;height:auto}
.tabs .tab.on .count{background:var(--accent-subtle);color:var(--accent-text)}
.sidenav a .count{margin-left:auto}
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count{display:none}
.indicator{display:inline-flex;align-items:center;gap:8px;font-size:14px}
.indicator::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--interaction-inactive);flex:none}
.indicator.ok::before{background:var(--success)}
.indicator.warn::before{background:var(--warning)}
.indicator.danger::before{background:var(--danger)}
.indicator.info::before{background:var(--info)}
.indicator.accent::before{background:var(--accent-graphic)}
.indicator.pulse::before{box-shadow:0 0 0 0 var(--danger);animation:pulse 1.6s infinite}
.count{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--radius-full);background:var(--danger);color:var(--danger-on);font-size:12px;font-weight:700;line-height:1;}
.count.neutral{background:var(--bg-panel);color:var(--text-secondary)}
.count.accent{background:var(--accent);color:var(--accent-on)}
.count.dot{min-width:8px;width:8px;height:8px;padding:0}
.with-count{position:relative;display:inline-flex}
.with-count .count{position:absolute;top:-6px;right:-6px}
.steps{display:flex;align-items:center;gap:8px;list-style:none;padding:0;margin:0;font-size:12px;max-width:none}
.steps li{display:flex;align-items:center;gap:8px;color:var(--text-tertiary);margin:0;white-space:nowrap}
.steps li i{width:24px;height:24px;border-radius:50%;display:inline-grid;place-items:center;font-style:normal;font-size:12px;font-weight:700;background:var(--fill-strong);color:var(--text-secondary);flex:none}
.steps li i svg{width:12px;height:12px;stroke-width:3}
.steps li.done i{background:var(--success);color:var(--success-on)}
.steps li.on{color:var(--text-primary);font-weight:600}
.steps li.on i{background:var(--accent);color:var(--accent-on)}
.steps li+li::before{content:"";width:32px;height:1px;background:var(--border-strong)}
.steps.vertical{flex-direction:column;align-items:flex-start}
.steps.vertical li+li::before{display:none}
.tbl-wrap{overflow-x:auto;border:1px solid var(--border-default);border-radius:var(--radius-lg);background:var(--bg-surface);width:100%}
.tbl{width:100%;border-collapse:collapse;font-size:14px;margin:0}
.tbl th{text-align:left;font-weight:600;color:var(--text-tertiary);font-size:12px;height:44px;padding:0 16px;border-bottom:1px solid var(--border-default);white-space:nowrap;background:var(--bg-subtle)}
.tbl td{height:52px;padding:12px 16px;border-bottom:1px solid var(--border-default);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl tbody tr:hover td{background:var(--bg-subtle)}
.tbl tr.on td{background:var(--accent-subtle)}
.tbl tr.disabled td{color:var(--text-disabled)}
.tbl.compact td{height:44px;padding:8px 16px;font-size:14px}
.tbl.compact th{height:40px}
.tbl.zebra tbody tr:nth-child(even) td{background:var(--bg-subtle)}
.tbl th.sortable{cursor:pointer;user-select:none}
.tbl th.sortable::after{content:"";display:inline-block;width:14px;height:14px;margin-left:6px;vertical-align:-3px;opacity:.45;background:currentColor;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sortable:hover{color:var(--text-primary)}
.tbl th.sorted{color:var(--accent-text)}
.tbl th.sorted::after{opacity:1;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sorted.desc::after{-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl .num{text-align:right;}
.tbl td svg{vertical-align:middle}
.tbl .check{width:44px;padding-right:0}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions{text-align:right;white-space:nowrap}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.tbl.kv th{width:200px;height:auto;padding:12px 16px;font-size:14px;color:var(--text-secondary);font-weight:500;border-right:1px solid var(--border-default);white-space:normal;vertical-align:top}
.tbl.kv td{height:auto}
.tbl td.empty{display:table-cell;text-align:center;color:var(--text-tertiary);height:auto;padding:24px 16px;font-size:14px;border:none;border-radius:0;background:transparent}
.tbl .link{color:var(--accent-text);text-decoration:none;font-weight:500}
.tbl .link:hover{text-decoration:underline}
.action-bar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--accent-subtle);border-bottom:1px solid var(--border-default);font-size:14px}
.action-bar b{color:var(--accent-text)}
.action-bar .spacer{flex:1}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.tile .tag{position:absolute;top:12px;right:12px}
.kpi{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;min-width:180px;display:flex;flex-direction:column;gap:4px}
.kpi .label{font-size:12px;color:var(--text-tertiary);font-weight:500}
.kpi .value{font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.2;white-space:nowrap}
.kpi .value small{font-size:16px;font-weight:500;color:var(--text-tertiary);margin-left:2px}
.kpi .delta{font-size:12px;font-weight:600;color:var(--text-secondary)}
.kpi .delta svg{width:12px;height:12px;vertical-align:-1px}
.kpi .delta.up{color:var(--danger-text)}
.kpi .delta.down{color:var(--success-text)}
.kpi.critical .value{color:var(--sev-critical-text)}
.kpi.high .value{color:var(--sev-high-text)}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;width:100%}
.chart{display:flex;flex-direction:column;gap:10px;width:100%;font-size:12px}
.chart .row{display:grid;grid-template-columns:120px 1fr 56px;align-items:center;gap:12px}
.chart .row .lbl{color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chart .bar{height:12px;border-radius:var(--radius-full);background:var(--bg-panel);overflow:hidden}
.chart .bar i{display:block;height:100%;background:var(--accent-graphic);border-radius:var(--radius-full)}
.chart .bar.critical i{background:var(--sev-critical)}
.chart .bar.high i{background:var(--sev-high)}
.chart .bar.medium i{background:var(--sev-medium)}
.chart .bar.low i{background:var(--sev-low)}
.chart .num{text-align:right;color:var(--text-secondary)}
.chart-card{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;display:flex;flex-direction:column;gap:16px;width:100%}
.chart-card .chart-title{font-size:16px;font-weight:600;margin:0;display:flex;justify-content:space-between;align-items:center}
.donut{width:120px;height:120px;border-radius:50%;display:grid;place-items:center;flex:none;background:conic-gradient(var(--sev-critical) 0 18%,var(--sev-high) 18% 45%,var(--sev-medium) 45% 75%,var(--sev-low) 75% 100%)}
.donut::after{content:attr(data-label);width:80px;height:80px;border-radius:50%;background:var(--bg-surface);display:grid;place-items:center;font-weight:700;font-size:16px;}
.donut-row{display:flex;align-items:center;gap:24px}
.chart-legend{display:flex;flex-wrap:wrap;gap:8px 20px;font-size:12px;color:var(--text-secondary)}
.chart-legend span{display:inline-flex;align-items:center;gap:6px}
.chart-legend i{width:10px;height:10px;border-radius:2px;background:var(--interaction-inactive);flex:none}
.chart-legend b{color:var(--text-primary);}
.sparkline{display:flex;align-items:flex-end;gap:3px;height:32px}
.sparkline i{display:block;width:6px;background:var(--accent-300);border-radius:2px}
.sparkline i.hi{background:var(--accent-graphic)}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.status-item .indicator{font-weight:600}
.sev-band{display:flex;height:10px;border-radius:var(--radius-full);overflow:hidden;width:100%}
.sev-band i{display:block;height:100%}
.sev-band i.critical{background:var(--sev-critical)}
.sev-band i.high{background:var(--sev-high)}
.sev-band i.medium{background:var(--sev-medium)}
.sev-band i.low{background:var(--sev-low)}
.sev-band i.info{background:var(--sev-info)}
.sev-cell{display:inline-flex;align-items:center;gap:8px}
.sev-cell i{width:3px;height:16px;border-radius:2px;background:var(--sev-info)}
.sev-cell i.critical{background:var(--sev-critical)}
.sev-cell i.high{background:var(--sev-high)}
.sev-cell i.medium{background:var(--sev-medium)}
.sev-cell i.low{background:var(--sev-low)}
@keyframes pulse{70%{box-shadow:0 0 0 8px transparent}}React · @jiran/ds-react
import { SevCell, Stack, Tag } from "@jiran/ds-react";
const LEVELS = [["critical", "Critical"], ["high", "High"], ["medium", "Medium"], ["low", "Low"], ["info", "Info"]] as const;
export default function Example() {
return (
<Stack style={{ maxWidth: 640, gap: 20 }}>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
{LEVELS.map(([t, l]) => <Tag key={t} tone={t}>{l}</Tag>)}
</div>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
{LEVELS.map(([t, l]) => <Tag key={t} tone={t} variant="solid">{l}</Tag>)}
</div>
<div style={{ display: "flex", gap: 16, flexWrap: "wrap" }}>
<SevCell tone="critical">Critical</SevCell>
<SevCell tone="high">High</SevCell>
<SevCell tone="medium">Medium</SevCell>
<SevCell tone="low">Low</SevCell>
<SevCell>Info</SevCell>
</div>
</Stack>
);
}2. 분포 밴드
전체 대비 비율. flex 값이 비율, 아래 범례로 수치.
Critical 190High 285Medium 316Low 263
HTML
<div class="stack" style="max-width:640px;gap:10px">
<div class="sev-band" role="img" aria-label="Critical 18%, High 27%, Medium 30%, Low 25%"><i class="critical" style="flex:18"></i><i class="high" style="flex:27"></i><i class="medium" style="flex:30"></i><i class="low" style="flex:25"></i></div>
<div class="chart-legend"><span><i style="background:var(--sev-critical)"></i>Critical <b>190</b></span><span><i style="background:var(--sev-high)"></i>High <b>285</b></span><span><i style="background:var(--sev-medium)"></i>Medium <b>316</b></span><span><i style="background:var(--sev-low)"></i>Low <b>263</b></span></div>
</div>CSS · .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack
/* JS Design System v0.6 · .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack · 토큰(var(--*))은 assets/style.css :root 참고 */
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.tag{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;padding:3px 10px;border-radius:var(--radius-full);line-height:1.6;background:var(--bg-panel);color:var(--text-secondary);white-space:nowrap}
.tag::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.tag.critical{background:var(--sev-critical-bg);color:var(--sev-critical-text)}
.tag.high{background:var(--sev-high-bg);color:var(--sev-high-text)}
.tag.medium{background:var(--sev-medium-bg);color:var(--sev-medium-text)}
.tag.low{background:var(--sev-low-bg);color:var(--sev-low-text)}
.tag.info{background:var(--sev-info-bg);color:var(--sev-info-text)}
.tag.ok{background:var(--success-subtle);color:var(--success-text)}
.tag.accent{background:var(--accent-subtle);color:var(--accent-text)}
.tag.sm{font-size:12px;padding:1px 8px}
.tag.lg{font-size:12px;padding:5px 12px}
.tag.plain::before{display:none}
.tag.outline{background:transparent;box-shadow:inset 0 0 0 1px currentColor}
.tag.solid.critical{background:var(--sev-critical);color:var(--sev-critical-on)}
.tag.solid.high{background:var(--sev-high);color:var(--sev-high-on)}
.tag.solid.medium{background:var(--sev-medium);color:var(--sev-medium-on)}
.tag.solid.low{background:var(--sev-low);color:var(--sev-low-on)}
.tag.solid.info{background:var(--sev-info);color:var(--sev-info-on)}
.tag.solid.ok{background:var(--success);color:var(--success-on)}
.tag.solid.accent{background:var(--accent);color:var(--accent-on)}
.tabs .tab .count{font-size:12px;font-weight:700;padding:1px 6px;border-radius:var(--radius-full);background:var(--bg-panel);color:var(--text-secondary);min-width:0;height:auto}
.tabs .tab.on .count{background:var(--accent-subtle);color:var(--accent-text)}
.sidenav a .count{margin-left:auto}
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count{display:none}
.indicator{display:inline-flex;align-items:center;gap:8px;font-size:14px}
.indicator::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--interaction-inactive);flex:none}
.indicator.ok::before{background:var(--success)}
.indicator.warn::before{background:var(--warning)}
.indicator.danger::before{background:var(--danger)}
.indicator.info::before{background:var(--info)}
.indicator.accent::before{background:var(--accent-graphic)}
.indicator.pulse::before{box-shadow:0 0 0 0 var(--danger);animation:pulse 1.6s infinite}
.count{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--radius-full);background:var(--danger);color:var(--danger-on);font-size:12px;font-weight:700;line-height:1;}
.count.neutral{background:var(--bg-panel);color:var(--text-secondary)}
.count.accent{background:var(--accent);color:var(--accent-on)}
.count.dot{min-width:8px;width:8px;height:8px;padding:0}
.with-count{position:relative;display:inline-flex}
.with-count .count{position:absolute;top:-6px;right:-6px}
.steps{display:flex;align-items:center;gap:8px;list-style:none;padding:0;margin:0;font-size:12px;max-width:none}
.steps li{display:flex;align-items:center;gap:8px;color:var(--text-tertiary);margin:0;white-space:nowrap}
.steps li i{width:24px;height:24px;border-radius:50%;display:inline-grid;place-items:center;font-style:normal;font-size:12px;font-weight:700;background:var(--fill-strong);color:var(--text-secondary);flex:none}
.steps li i svg{width:12px;height:12px;stroke-width:3}
.steps li.done i{background:var(--success);color:var(--success-on)}
.steps li.on{color:var(--text-primary);font-weight:600}
.steps li.on i{background:var(--accent);color:var(--accent-on)}
.steps li+li::before{content:"";width:32px;height:1px;background:var(--border-strong)}
.steps.vertical{flex-direction:column;align-items:flex-start}
.steps.vertical li+li::before{display:none}
.tbl-wrap{overflow-x:auto;border:1px solid var(--border-default);border-radius:var(--radius-lg);background:var(--bg-surface);width:100%}
.tbl{width:100%;border-collapse:collapse;font-size:14px;margin:0}
.tbl th{text-align:left;font-weight:600;color:var(--text-tertiary);font-size:12px;height:44px;padding:0 16px;border-bottom:1px solid var(--border-default);white-space:nowrap;background:var(--bg-subtle)}
.tbl td{height:52px;padding:12px 16px;border-bottom:1px solid var(--border-default);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl tbody tr:hover td{background:var(--bg-subtle)}
.tbl tr.on td{background:var(--accent-subtle)}
.tbl tr.disabled td{color:var(--text-disabled)}
.tbl.compact td{height:44px;padding:8px 16px;font-size:14px}
.tbl.compact th{height:40px}
.tbl.zebra tbody tr:nth-child(even) td{background:var(--bg-subtle)}
.tbl th.sortable{cursor:pointer;user-select:none}
.tbl th.sortable::after{content:"";display:inline-block;width:14px;height:14px;margin-left:6px;vertical-align:-3px;opacity:.45;background:currentColor;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sortable:hover{color:var(--text-primary)}
.tbl th.sorted{color:var(--accent-text)}
.tbl th.sorted::after{opacity:1;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sorted.desc::after{-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl .num{text-align:right;}
.tbl td svg{vertical-align:middle}
.tbl .check{width:44px;padding-right:0}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions{text-align:right;white-space:nowrap}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.tbl.kv th{width:200px;height:auto;padding:12px 16px;font-size:14px;color:var(--text-secondary);font-weight:500;border-right:1px solid var(--border-default);white-space:normal;vertical-align:top}
.tbl.kv td{height:auto}
.tbl td.empty{display:table-cell;text-align:center;color:var(--text-tertiary);height:auto;padding:24px 16px;font-size:14px;border:none;border-radius:0;background:transparent}
.tbl .link{color:var(--accent-text);text-decoration:none;font-weight:500}
.tbl .link:hover{text-decoration:underline}
.action-bar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--accent-subtle);border-bottom:1px solid var(--border-default);font-size:14px}
.action-bar b{color:var(--accent-text)}
.action-bar .spacer{flex:1}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.tile .tag{position:absolute;top:12px;right:12px}
.kpi{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;min-width:180px;display:flex;flex-direction:column;gap:4px}
.kpi .label{font-size:12px;color:var(--text-tertiary);font-weight:500}
.kpi .value{font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.2;white-space:nowrap}
.kpi .value small{font-size:16px;font-weight:500;color:var(--text-tertiary);margin-left:2px}
.kpi .delta{font-size:12px;font-weight:600;color:var(--text-secondary)}
.kpi .delta svg{width:12px;height:12px;vertical-align:-1px}
.kpi .delta.up{color:var(--danger-text)}
.kpi .delta.down{color:var(--success-text)}
.kpi.critical .value{color:var(--sev-critical-text)}
.kpi.high .value{color:var(--sev-high-text)}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;width:100%}
.chart{display:flex;flex-direction:column;gap:10px;width:100%;font-size:12px}
.chart .row{display:grid;grid-template-columns:120px 1fr 56px;align-items:center;gap:12px}
.chart .row .lbl{color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chart .bar{height:12px;border-radius:var(--radius-full);background:var(--bg-panel);overflow:hidden}
.chart .bar i{display:block;height:100%;background:var(--accent-graphic);border-radius:var(--radius-full)}
.chart .bar.critical i{background:var(--sev-critical)}
.chart .bar.high i{background:var(--sev-high)}
.chart .bar.medium i{background:var(--sev-medium)}
.chart .bar.low i{background:var(--sev-low)}
.chart .num{text-align:right;color:var(--text-secondary)}
.chart-card{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;display:flex;flex-direction:column;gap:16px;width:100%}
.chart-card .chart-title{font-size:16px;font-weight:600;margin:0;display:flex;justify-content:space-between;align-items:center}
.donut{width:120px;height:120px;border-radius:50%;display:grid;place-items:center;flex:none;background:conic-gradient(var(--sev-critical) 0 18%,var(--sev-high) 18% 45%,var(--sev-medium) 45% 75%,var(--sev-low) 75% 100%)}
.donut::after{content:attr(data-label);width:80px;height:80px;border-radius:50%;background:var(--bg-surface);display:grid;place-items:center;font-weight:700;font-size:16px;}
.donut-row{display:flex;align-items:center;gap:24px}
.chart-legend{display:flex;flex-wrap:wrap;gap:8px 20px;font-size:12px;color:var(--text-secondary)}
.chart-legend span{display:inline-flex;align-items:center;gap:6px}
.chart-legend i{width:10px;height:10px;border-radius:2px;background:var(--interaction-inactive);flex:none}
.chart-legend b{color:var(--text-primary);}
.sparkline{display:flex;align-items:flex-end;gap:3px;height:32px}
.sparkline i{display:block;width:6px;background:var(--accent-300);border-radius:2px}
.sparkline i.hi{background:var(--accent-graphic)}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.status-item .indicator{font-weight:600}
.sev-band{display:flex;height:10px;border-radius:var(--radius-full);overflow:hidden;width:100%}
.sev-band i{display:block;height:100%}
.sev-band i.critical{background:var(--sev-critical)}
.sev-band i.high{background:var(--sev-high)}
.sev-band i.medium{background:var(--sev-medium)}
.sev-band i.low{background:var(--sev-low)}
.sev-band i.info{background:var(--sev-info)}
.sev-cell{display:inline-flex;align-items:center;gap:8px}
.sev-cell i{width:3px;height:16px;border-radius:2px;background:var(--sev-info)}
.sev-cell i.critical{background:var(--sev-critical)}
.sev-cell i.high{background:var(--sev-high)}
.sev-cell i.medium{background:var(--sev-medium)}
.sev-cell i.low{background:var(--sev-low)}
@keyframes pulse{70%{box-shadow:0 0 0 8px transparent}}React · @jiran/ds-react
import { ChartLegend, SevBand, Stack } from "@jiran/ds-react";
const DIST = [
{ tone: "critical" as const, label: "Critical", value: 190 },
{ tone: "high" as const, label: "High", value: 285 },
{ tone: "medium" as const, label: "Medium", value: 316 },
{ tone: "low" as const, label: "Low", value: 263 },
];
export default function Example() {
return (
<Stack style={{ maxWidth: 640, gap: 10 }}>
<SevBand segments={DIST} />
<ChartLegend items={DIST.map((d) => ({ label: d.label, value: d.value, tone: d.tone }))} />
</Stack>
);
}3. 표 안의 심각도
행 좌측 색 막대(.sev-cell) + Tag. 정렬 기본값은 심각도 내림차순.
| 심각도 | 이벤트 | 대상 | 건수 |
|---|---|---|---|
| Critical | 랜섬웨어 행위 탐지 | PC-2041 | 3 |
| High | 알려진 악성코드 차단 | PC-0932 | 2 |
| Medium | 미승인 USB 연결 | PC-1187 | 1 |
| Low | 정책 동기화 지연 | Server-03 | 12 |
| Info | 스케줄 검사 완료 | PC-0411 | 48 |
HTML
<div class="tbl-wrap" style="max-width:720px">
<table class="tbl compact">
<thead><tr><th class="sortable sorted desc" aria-sort="descending">심각도</th><th>이벤트</th><th>대상</th><th class="num">건수</th></tr></thead>
<tbody>
<tr><td><span class="sev-cell"><i class="critical"></i><span class="tag critical sm">Critical</span></span></td><td>랜섬웨어 행위 탐지</td><td>PC-2041</td><td class="num">3</td></tr>
<tr><td><span class="sev-cell"><i class="high"></i><span class="tag high sm">High</span></span></td><td>알려진 악성코드 차단</td><td>PC-0932</td><td class="num">2</td></tr>
<tr><td><span class="sev-cell"><i class="medium"></i><span class="tag medium sm">Medium</span></span></td><td>미승인 USB 연결</td><td>PC-1187</td><td class="num">1</td></tr>
<tr><td><span class="sev-cell"><i class="low"></i><span class="tag low sm">Low</span></span></td><td>정책 동기화 지연</td><td>Server-03</td><td class="num">12</td></tr>
<tr><td><span class="sev-cell"><i></i><span class="tag info sm">Info</span></span></td><td>스케줄 검사 완료</td><td>PC-0411</td><td class="num">48</td></tr>
</tbody>
</table>
</div>CSS · .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack
/* JS Design System v0.6 · .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack · 토큰(var(--*))은 assets/style.css :root 참고 */
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.tag{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;padding:3px 10px;border-radius:var(--radius-full);line-height:1.6;background:var(--bg-panel);color:var(--text-secondary);white-space:nowrap}
.tag::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.tag.critical{background:var(--sev-critical-bg);color:var(--sev-critical-text)}
.tag.high{background:var(--sev-high-bg);color:var(--sev-high-text)}
.tag.medium{background:var(--sev-medium-bg);color:var(--sev-medium-text)}
.tag.low{background:var(--sev-low-bg);color:var(--sev-low-text)}
.tag.info{background:var(--sev-info-bg);color:var(--sev-info-text)}
.tag.ok{background:var(--success-subtle);color:var(--success-text)}
.tag.accent{background:var(--accent-subtle);color:var(--accent-text)}
.tag.sm{font-size:12px;padding:1px 8px}
.tag.lg{font-size:12px;padding:5px 12px}
.tag.plain::before{display:none}
.tag.outline{background:transparent;box-shadow:inset 0 0 0 1px currentColor}
.tag.solid.critical{background:var(--sev-critical);color:var(--sev-critical-on)}
.tag.solid.high{background:var(--sev-high);color:var(--sev-high-on)}
.tag.solid.medium{background:var(--sev-medium);color:var(--sev-medium-on)}
.tag.solid.low{background:var(--sev-low);color:var(--sev-low-on)}
.tag.solid.info{background:var(--sev-info);color:var(--sev-info-on)}
.tag.solid.ok{background:var(--success);color:var(--success-on)}
.tag.solid.accent{background:var(--accent);color:var(--accent-on)}
.tabs .tab .count{font-size:12px;font-weight:700;padding:1px 6px;border-radius:var(--radius-full);background:var(--bg-panel);color:var(--text-secondary);min-width:0;height:auto}
.tabs .tab.on .count{background:var(--accent-subtle);color:var(--accent-text)}
.sidenav a .count{margin-left:auto}
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count{display:none}
.indicator{display:inline-flex;align-items:center;gap:8px;font-size:14px}
.indicator::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--interaction-inactive);flex:none}
.indicator.ok::before{background:var(--success)}
.indicator.warn::before{background:var(--warning)}
.indicator.danger::before{background:var(--danger)}
.indicator.info::before{background:var(--info)}
.indicator.accent::before{background:var(--accent-graphic)}
.indicator.pulse::before{box-shadow:0 0 0 0 var(--danger);animation:pulse 1.6s infinite}
.count{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--radius-full);background:var(--danger);color:var(--danger-on);font-size:12px;font-weight:700;line-height:1;}
.count.neutral{background:var(--bg-panel);color:var(--text-secondary)}
.count.accent{background:var(--accent);color:var(--accent-on)}
.count.dot{min-width:8px;width:8px;height:8px;padding:0}
.with-count{position:relative;display:inline-flex}
.with-count .count{position:absolute;top:-6px;right:-6px}
.steps{display:flex;align-items:center;gap:8px;list-style:none;padding:0;margin:0;font-size:12px;max-width:none}
.steps li{display:flex;align-items:center;gap:8px;color:var(--text-tertiary);margin:0;white-space:nowrap}
.steps li i{width:24px;height:24px;border-radius:50%;display:inline-grid;place-items:center;font-style:normal;font-size:12px;font-weight:700;background:var(--fill-strong);color:var(--text-secondary);flex:none}
.steps li i svg{width:12px;height:12px;stroke-width:3}
.steps li.done i{background:var(--success);color:var(--success-on)}
.steps li.on{color:var(--text-primary);font-weight:600}
.steps li.on i{background:var(--accent);color:var(--accent-on)}
.steps li+li::before{content:"";width:32px;height:1px;background:var(--border-strong)}
.steps.vertical{flex-direction:column;align-items:flex-start}
.steps.vertical li+li::before{display:none}
.tbl-wrap{overflow-x:auto;border:1px solid var(--border-default);border-radius:var(--radius-lg);background:var(--bg-surface);width:100%}
.tbl{width:100%;border-collapse:collapse;font-size:14px;margin:0}
.tbl th{text-align:left;font-weight:600;color:var(--text-tertiary);font-size:12px;height:44px;padding:0 16px;border-bottom:1px solid var(--border-default);white-space:nowrap;background:var(--bg-subtle)}
.tbl td{height:52px;padding:12px 16px;border-bottom:1px solid var(--border-default);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl tbody tr:hover td{background:var(--bg-subtle)}
.tbl tr.on td{background:var(--accent-subtle)}
.tbl tr.disabled td{color:var(--text-disabled)}
.tbl.compact td{height:44px;padding:8px 16px;font-size:14px}
.tbl.compact th{height:40px}
.tbl.zebra tbody tr:nth-child(even) td{background:var(--bg-subtle)}
.tbl th.sortable{cursor:pointer;user-select:none}
.tbl th.sortable::after{content:"";display:inline-block;width:14px;height:14px;margin-left:6px;vertical-align:-3px;opacity:.45;background:currentColor;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sortable:hover{color:var(--text-primary)}
.tbl th.sorted{color:var(--accent-text)}
.tbl th.sorted::after{opacity:1;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sorted.desc::after{-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl .num{text-align:right;}
.tbl td svg{vertical-align:middle}
.tbl .check{width:44px;padding-right:0}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions{text-align:right;white-space:nowrap}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.tbl.kv th{width:200px;height:auto;padding:12px 16px;font-size:14px;color:var(--text-secondary);font-weight:500;border-right:1px solid var(--border-default);white-space:normal;vertical-align:top}
.tbl.kv td{height:auto}
.tbl td.empty{display:table-cell;text-align:center;color:var(--text-tertiary);height:auto;padding:24px 16px;font-size:14px;border:none;border-radius:0;background:transparent}
.tbl .link{color:var(--accent-text);text-decoration:none;font-weight:500}
.tbl .link:hover{text-decoration:underline}
.action-bar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--accent-subtle);border-bottom:1px solid var(--border-default);font-size:14px}
.action-bar b{color:var(--accent-text)}
.action-bar .spacer{flex:1}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.tile .tag{position:absolute;top:12px;right:12px}
.kpi{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;min-width:180px;display:flex;flex-direction:column;gap:4px}
.kpi .label{font-size:12px;color:var(--text-tertiary);font-weight:500}
.kpi .value{font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.2;white-space:nowrap}
.kpi .value small{font-size:16px;font-weight:500;color:var(--text-tertiary);margin-left:2px}
.kpi .delta{font-size:12px;font-weight:600;color:var(--text-secondary)}
.kpi .delta svg{width:12px;height:12px;vertical-align:-1px}
.kpi .delta.up{color:var(--danger-text)}
.kpi .delta.down{color:var(--success-text)}
.kpi.critical .value{color:var(--sev-critical-text)}
.kpi.high .value{color:var(--sev-high-text)}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;width:100%}
.chart{display:flex;flex-direction:column;gap:10px;width:100%;font-size:12px}
.chart .row{display:grid;grid-template-columns:120px 1fr 56px;align-items:center;gap:12px}
.chart .row .lbl{color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chart .bar{height:12px;border-radius:var(--radius-full);background:var(--bg-panel);overflow:hidden}
.chart .bar i{display:block;height:100%;background:var(--accent-graphic);border-radius:var(--radius-full)}
.chart .bar.critical i{background:var(--sev-critical)}
.chart .bar.high i{background:var(--sev-high)}
.chart .bar.medium i{background:var(--sev-medium)}
.chart .bar.low i{background:var(--sev-low)}
.chart .num{text-align:right;color:var(--text-secondary)}
.chart-card{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;display:flex;flex-direction:column;gap:16px;width:100%}
.chart-card .chart-title{font-size:16px;font-weight:600;margin:0;display:flex;justify-content:space-between;align-items:center}
.donut{width:120px;height:120px;border-radius:50%;display:grid;place-items:center;flex:none;background:conic-gradient(var(--sev-critical) 0 18%,var(--sev-high) 18% 45%,var(--sev-medium) 45% 75%,var(--sev-low) 75% 100%)}
.donut::after{content:attr(data-label);width:80px;height:80px;border-radius:50%;background:var(--bg-surface);display:grid;place-items:center;font-weight:700;font-size:16px;}
.donut-row{display:flex;align-items:center;gap:24px}
.chart-legend{display:flex;flex-wrap:wrap;gap:8px 20px;font-size:12px;color:var(--text-secondary)}
.chart-legend span{display:inline-flex;align-items:center;gap:6px}
.chart-legend i{width:10px;height:10px;border-radius:2px;background:var(--interaction-inactive);flex:none}
.chart-legend b{color:var(--text-primary);}
.sparkline{display:flex;align-items:flex-end;gap:3px;height:32px}
.sparkline i{display:block;width:6px;background:var(--accent-300);border-radius:2px}
.sparkline i.hi{background:var(--accent-graphic)}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.status-item .indicator{font-weight:600}
.sev-band{display:flex;height:10px;border-radius:var(--radius-full);overflow:hidden;width:100%}
.sev-band i{display:block;height:100%}
.sev-band i.critical{background:var(--sev-critical)}
.sev-band i.high{background:var(--sev-high)}
.sev-band i.medium{background:var(--sev-medium)}
.sev-band i.low{background:var(--sev-low)}
.sev-band i.info{background:var(--sev-info)}
.sev-cell{display:inline-flex;align-items:center;gap:8px}
.sev-cell i{width:3px;height:16px;border-radius:2px;background:var(--sev-info)}
.sev-cell i.critical{background:var(--sev-critical)}
.sev-cell i.high{background:var(--sev-high)}
.sev-cell i.medium{background:var(--sev-medium)}
.sev-cell i.low{background:var(--sev-low)}
@keyframes pulse{70%{box-shadow:0 0 0 8px transparent}}React · @jiran/ds-react
import { DataTable, SevCell, Tag, type DataTableColumn } from "@jiran/ds-react";
type Sev = "critical" | "high" | "medium" | "low" | "info";
type Row = { id: string; sev: Sev; event: string; target: string; count: number };
const RANK: Record<Sev, number> = { critical: 5, high: 4, medium: 3, low: 2, info: 1 };
const LABEL: Record<Sev, string> = { critical: "Critical", high: "High", medium: "Medium", low: "Low", info: "Info" };
const ROWS: Row[] = [
{ id: "1", sev: "critical", event: "랜섬웨어 행위 탐지", target: "PC-2041", count: 3 },
{ id: "2", sev: "high", event: "알려진 악성코드 차단", target: "PC-0932", count: 2 },
{ id: "3", sev: "medium", event: "미승인 USB 연결", target: "PC-1187", count: 1 },
{ id: "4", sev: "low", event: "정책 동기화 지연", target: "Server-03", count: 12 },
{ id: "5", sev: "info", event: "스케줄 검사 완료", target: "PC-0411", count: 48 },
];
const COLUMNS: DataTableColumn<Row>[] = [
{
key: "sev", header: "심각도", sortable: true, sortValue: (r) => RANK[r.sev],
render: (r) => (
<SevCell tone={r.sev === "info" ? undefined : r.sev}>
<Tag tone={r.sev} size="sm">{LABEL[r.sev]}</Tag>
</SevCell>
),
},
{ key: "event", header: "이벤트" },
{ key: "target", header: "대상" },
{ key: "count", header: "건수", num: true },
];
export default function Example() {
return <DataTable compact columns={COLUMNS} rows={ROWS} rowKey={(r) => r.id} defaultSort={{ key: "sev", dir: "desc" }} wrapStyle={{ maxWidth: 720 }} />;
}Usage
React: @jiran/ds-react 패키지를 설치하고 style.css 를 한 번 import 하면 위 React 탭 코드를 그대로 쓸 수 있습니다(React Package). HTML: assets/style.css 를 연결하고(일부만 필요하면 아래 컴포넌트 CSS 복사), 동작이 필요하면 assets/ds.js 를 함께 넣습니다 — 이 페이지의 프리뷰가 그 스크립트로 동작합니다. 색은 전부 토큰(var(--accent) 등)을 참조하므로 --accent-* 만 제품 컬러로 바꾸면 됩니다.
React @jiran/ds-react
npm i @jiran/ds-react
// main.tsx — 앱 진입점에서 한 번
import "@jiran/ds-react/style.css";
// 컴포넌트
import { SevBand, SevCell, Tag, Indicator, Count, WithCount, Steps, Kpi, KpiGrid, ChartCard, BarChart, Donut, DonutRow, ChartLegend, Sparkline, DataTable, ActionBar } from "@jiran/ds-react";HTML
<!-- 1) 토큰 + 전체 컴포넌트 스타일 한 번에 연결 -->
<link rel="stylesheet" href="assets/style.css">
<!-- 동작(드롭다운·탭·달력·표 정렬/선택·토스트 등) — 순수 JS 한 파일. React 제품은 불필요 -->
<script src="assets/ds.js" defer></script>
<!-- 2) 또는 아래 "Component CSS" 만 프로젝트 CSS 에 붙이고,
:root 토큰은 Foundations › Design Token 에서 복사 -->Component CSS .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack
/* JS Design System v0.6 · .tag, .indicator, .count, .with-count, .steps, .kpi, .kpi-grid, .chart, .chart-card, .donut, .donut-row, .chart-legend, .sparkline, .tbl-wrap, .tbl, .action-bar, .checkbox, .sev-band, .sev-cell, .stack · 토큰(var(--*))은 assets/style.css :root 참고 */
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.tag{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;padding:3px 10px;border-radius:var(--radius-full);line-height:1.6;background:var(--bg-panel);color:var(--text-secondary);white-space:nowrap}
.tag::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.tag.critical{background:var(--sev-critical-bg);color:var(--sev-critical-text)}
.tag.high{background:var(--sev-high-bg);color:var(--sev-high-text)}
.tag.medium{background:var(--sev-medium-bg);color:var(--sev-medium-text)}
.tag.low{background:var(--sev-low-bg);color:var(--sev-low-text)}
.tag.info{background:var(--sev-info-bg);color:var(--sev-info-text)}
.tag.ok{background:var(--success-subtle);color:var(--success-text)}
.tag.accent{background:var(--accent-subtle);color:var(--accent-text)}
.tag.sm{font-size:12px;padding:1px 8px}
.tag.lg{font-size:12px;padding:5px 12px}
.tag.plain::before{display:none}
.tag.outline{background:transparent;box-shadow:inset 0 0 0 1px currentColor}
.tag.solid.critical{background:var(--sev-critical);color:var(--sev-critical-on)}
.tag.solid.high{background:var(--sev-high);color:var(--sev-high-on)}
.tag.solid.medium{background:var(--sev-medium);color:var(--sev-medium-on)}
.tag.solid.low{background:var(--sev-low);color:var(--sev-low-on)}
.tag.solid.info{background:var(--sev-info);color:var(--sev-info-on)}
.tag.solid.ok{background:var(--success);color:var(--success-on)}
.tag.solid.accent{background:var(--accent);color:var(--accent-on)}
.tabs .tab .count{font-size:12px;font-weight:700;padding:1px 6px;border-radius:var(--radius-full);background:var(--bg-panel);color:var(--text-secondary);min-width:0;height:auto}
.tabs .tab.on .count{background:var(--accent-subtle);color:var(--accent-text)}
.sidenav a .count{margin-left:auto}
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count{display:none}
.indicator{display:inline-flex;align-items:center;gap:8px;font-size:14px}
.indicator::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--interaction-inactive);flex:none}
.indicator.ok::before{background:var(--success)}
.indicator.warn::before{background:var(--warning)}
.indicator.danger::before{background:var(--danger)}
.indicator.info::before{background:var(--info)}
.indicator.accent::before{background:var(--accent-graphic)}
.indicator.pulse::before{box-shadow:0 0 0 0 var(--danger);animation:pulse 1.6s infinite}
.count{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--radius-full);background:var(--danger);color:var(--danger-on);font-size:12px;font-weight:700;line-height:1;}
.count.neutral{background:var(--bg-panel);color:var(--text-secondary)}
.count.accent{background:var(--accent);color:var(--accent-on)}
.count.dot{min-width:8px;width:8px;height:8px;padding:0}
.with-count{position:relative;display:inline-flex}
.with-count .count{position:absolute;top:-6px;right:-6px}
.steps{display:flex;align-items:center;gap:8px;list-style:none;padding:0;margin:0;font-size:12px;max-width:none}
.steps li{display:flex;align-items:center;gap:8px;color:var(--text-tertiary);margin:0;white-space:nowrap}
.steps li i{width:24px;height:24px;border-radius:50%;display:inline-grid;place-items:center;font-style:normal;font-size:12px;font-weight:700;background:var(--fill-strong);color:var(--text-secondary);flex:none}
.steps li i svg{width:12px;height:12px;stroke-width:3}
.steps li.done i{background:var(--success);color:var(--success-on)}
.steps li.on{color:var(--text-primary);font-weight:600}
.steps li.on i{background:var(--accent);color:var(--accent-on)}
.steps li+li::before{content:"";width:32px;height:1px;background:var(--border-strong)}
.steps.vertical{flex-direction:column;align-items:flex-start}
.steps.vertical li+li::before{display:none}
.tbl-wrap{overflow-x:auto;border:1px solid var(--border-default);border-radius:var(--radius-lg);background:var(--bg-surface);width:100%}
.tbl{width:100%;border-collapse:collapse;font-size:14px;margin:0}
.tbl th{text-align:left;font-weight:600;color:var(--text-tertiary);font-size:12px;height:44px;padding:0 16px;border-bottom:1px solid var(--border-default);white-space:nowrap;background:var(--bg-subtle)}
.tbl td{height:52px;padding:12px 16px;border-bottom:1px solid var(--border-default);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl tbody tr:hover td{background:var(--bg-subtle)}
.tbl tr.on td{background:var(--accent-subtle)}
.tbl tr.disabled td{color:var(--text-disabled)}
.tbl.compact td{height:44px;padding:8px 16px;font-size:14px}
.tbl.compact th{height:40px}
.tbl.zebra tbody tr:nth-child(even) td{background:var(--bg-subtle)}
.tbl th.sortable{cursor:pointer;user-select:none}
.tbl th.sortable::after{content:"";display:inline-block;width:14px;height:14px;margin-left:6px;vertical-align:-3px;opacity:.45;background:currentColor;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sortable:hover{color:var(--text-primary)}
.tbl th.sorted{color:var(--accent-text)}
.tbl th.sorted::after{opacity:1;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sorted.desc::after{-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl .num{text-align:right;}
.tbl td svg{vertical-align:middle}
.tbl .check{width:44px;padding-right:0}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions{text-align:right;white-space:nowrap}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.tbl.kv th{width:200px;height:auto;padding:12px 16px;font-size:14px;color:var(--text-secondary);font-weight:500;border-right:1px solid var(--border-default);white-space:normal;vertical-align:top}
.tbl.kv td{height:auto}
.tbl td.empty{display:table-cell;text-align:center;color:var(--text-tertiary);height:auto;padding:24px 16px;font-size:14px;border:none;border-radius:0;background:transparent}
.tbl .link{color:var(--accent-text);text-decoration:none;font-weight:500}
.tbl .link:hover{text-decoration:underline}
.action-bar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--accent-subtle);border-bottom:1px solid var(--border-default);font-size:14px}
.action-bar b{color:var(--accent-text)}
.action-bar .spacer{flex:1}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.tile .tag{position:absolute;top:12px;right:12px}
.kpi{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;min-width:180px;display:flex;flex-direction:column;gap:4px}
.kpi .label{font-size:12px;color:var(--text-tertiary);font-weight:500}
.kpi .value{font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.2;white-space:nowrap}
.kpi .value small{font-size:16px;font-weight:500;color:var(--text-tertiary);margin-left:2px}
.kpi .delta{font-size:12px;font-weight:600;color:var(--text-secondary)}
.kpi .delta svg{width:12px;height:12px;vertical-align:-1px}
.kpi .delta.up{color:var(--danger-text)}
.kpi .delta.down{color:var(--success-text)}
.kpi.critical .value{color:var(--sev-critical-text)}
.kpi.high .value{color:var(--sev-high-text)}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;width:100%}
.chart{display:flex;flex-direction:column;gap:10px;width:100%;font-size:12px}
.chart .row{display:grid;grid-template-columns:120px 1fr 56px;align-items:center;gap:12px}
.chart .row .lbl{color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chart .bar{height:12px;border-radius:var(--radius-full);background:var(--bg-panel);overflow:hidden}
.chart .bar i{display:block;height:100%;background:var(--accent-graphic);border-radius:var(--radius-full)}
.chart .bar.critical i{background:var(--sev-critical)}
.chart .bar.high i{background:var(--sev-high)}
.chart .bar.medium i{background:var(--sev-medium)}
.chart .bar.low i{background:var(--sev-low)}
.chart .num{text-align:right;color:var(--text-secondary)}
.chart-card{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:20px 24px;display:flex;flex-direction:column;gap:16px;width:100%}
.chart-card .chart-title{font-size:16px;font-weight:600;margin:0;display:flex;justify-content:space-between;align-items:center}
.donut{width:120px;height:120px;border-radius:50%;display:grid;place-items:center;flex:none;background:conic-gradient(var(--sev-critical) 0 18%,var(--sev-high) 18% 45%,var(--sev-medium) 45% 75%,var(--sev-low) 75% 100%)}
.donut::after{content:attr(data-label);width:80px;height:80px;border-radius:50%;background:var(--bg-surface);display:grid;place-items:center;font-weight:700;font-size:16px;}
.donut-row{display:flex;align-items:center;gap:24px}
.chart-legend{display:flex;flex-wrap:wrap;gap:8px 20px;font-size:12px;color:var(--text-secondary)}
.chart-legend span{display:inline-flex;align-items:center;gap:6px}
.chart-legend i{width:10px;height:10px;border-radius:2px;background:var(--interaction-inactive);flex:none}
.chart-legend b{color:var(--text-primary);}
.sparkline{display:flex;align-items:flex-end;gap:3px;height:32px}
.sparkline i{display:block;width:6px;background:var(--accent-300);border-radius:2px}
.sparkline i.hi{background:var(--accent-graphic)}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.status-item .indicator{font-weight:600}
.sev-band{display:flex;height:10px;border-radius:var(--radius-full);overflow:hidden;width:100%}
.sev-band i{display:block;height:100%}
.sev-band i.critical{background:var(--sev-critical)}
.sev-band i.high{background:var(--sev-high)}
.sev-band i.medium{background:var(--sev-medium)}
.sev-band i.low{background:var(--sev-low)}
.sev-band i.info{background:var(--sev-info)}
.sev-cell{display:inline-flex;align-items:center;gap:8px}
.sev-cell i{width:3px;height:16px;border-radius:2px;background:var(--sev-info)}
.sev-cell i.critical{background:var(--sev-critical)}
.sev-cell i.high{background:var(--sev-high)}
.sev-cell i.medium{background:var(--sev-medium)}
.sev-cell i.low{background:var(--sev-low)}
@keyframes pulse{70%{box-shadow:0 0 0 8px transparent}}