JS Design System v0.6

로그 뷰어

대량 로그를 빠르게 훑고 필터링하는 화면입니다. Compact 표 + 모노스페이스 시간/IP + 심각도 태그 + 실시간/일시정지 전환.

Composition

이 패턴이 조합하는 컴포넌트입니다. 세부 옵션은 각 컴포넌트 페이지를 참고하세요.

밀도
.tbl.compact 44px 행, 시간·IP 는 .log-row 모노스페이스
필터
심각도 Chip 다중 선택 + 검색 + 실시간/일시정지
상세
행 클릭 → 우측 패널(표는 그대로)
시간심각도소스 IP단말메시지
2026-09-07 09:41:12.031CRIT10.0.4.21PC-2041process.create blocked: rundll32.exe → cryptor.dll
2026-09-07 09:41:11.870WARN10.0.4.21PC-2041usb.attach vendor=0x0781 product=0x5583 (unapproved)
2026-09-07 09:41:09.114INFO10.0.0.3Server-03policy.sync ok (rev 2041)
2026-09-07 09:40:58.552HIGH10.0.4.33PC-1187malware.known blocked sha256=9f3a…c21e
2026-09-07 09:40:41.207LOW10.0.0.3Server-03agent.checkin 1284/1284
2026-09-07 09:39:57.980INFO10.0.7.10PC-0932scan.scheduled completed files=48213 threats=0
총 48,213건 · 최신 100건 표시

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 { Toolbar, Stack, SearchBar, Chip, ChipGroup, SelectButton, DataTable, ActionBar, Tag, Pagination, PaginationBar, paginate, Button, ButtonGroup } 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 .searchbar, .chip, .chip-group, .select-btn, .tbl-wrap, .tbl, .action-bar, .tag, .checkbox, .indicator, .pagination, .pagination-bar, .dropdown, .menu, .menu-item, .btn, .btn-group, .toolbar, .stack, .log-row

/* JS Design System v0.6 · .searchbar, .chip, .chip-group, .select-btn, .tbl-wrap, .tbl, .action-bar, .tag, .checkbox, .indicator, .pagination, .pagination-bar, .dropdown, .menu, .menu-item, .btn, .btn-group, .toolbar, .stack, .log-row · 토큰(var(--*))은 assets/style.css :root 참고 */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:44px;padding:0 20px;border-radius:var(--radius-md);border:1px solid transparent;font:inherit;font-size:14px;font-weight:600;line-height:1;cursor:pointer;white-space:nowrap;text-decoration:none;transition:background var(--motion-fast),border-color var(--motion-fast)}
.btn.sm{height:36px;padding:0 14px;font-size:12px}
.btn.md{height:44px;padding:0 20px;font-size:14px}
.btn.lg{height:52px;padding:0 26px;font-size:16px}
.btn.primary{background:var(--accent);color:var(--accent-on)}
.btn.primary:hover{background:var(--accent-hover)}
.btn.primary:active{background:var(--accent-pressed)}
.btn.secondary{background:transparent;border-color:var(--border-strong);color:var(--text-primary)}
.btn.secondary:hover{background:var(--fill-normal)}
.btn.secondary:active{background:var(--fill-strong)}
.btn.tertiary{background:var(--fill-alternative);color:var(--text-primary)}
.btn.tertiary:hover{background:var(--fill-normal)}
.btn.tertiary:active{background:var(--fill-strong)}
.btn.text{background:transparent;color:var(--accent-text);padding-left:8px;padding-right:8px}
.btn.text:hover{background:var(--accent-subtle)}
.btn.text:active{background:var(--fill-strong)}
.btn.danger{background:var(--danger);color:var(--danger-on)}
.btn.danger:hover{background:var(--danger-hover)}
.btn.danger:active{background:var(--danger-pressed)}
.btn.danger.secondary{background:transparent;color:var(--danger-text);border-color:var(--danger)}
.btn.danger.secondary:hover{background:var(--danger-subtle)}
.btn.danger.secondary:active{background:var(--fill-strong)}
.btn.icon{width:44px;padding:0}
.btn.icon.sm{width:36px}
.btn.icon.lg{width:52px}
.btn svg{width:18px;height:18px;flex:none}
.btn.sm svg{width:16px;height:16px}
.btn.lg svg{width:20px;height:20px}
.btn.block{width:100%}
.btn[disabled],.btn[aria-disabled="true"]{background:var(--interaction-disable);border-color:transparent;color:var(--text-disabled);cursor:not-allowed;pointer-events:none}
.btn.secondary[disabled],.btn.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable)}
.btn.danger.secondary[disabled],.btn.danger.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable);color:var(--text-disabled)}
.btn.text[disabled],.btn.text[aria-disabled="true"]{background:transparent}
.btn.loading{position:relative;color:transparent;pointer-events:none}
.btn.loading::after{content:"";position:absolute;width:16px;height:16px;border:2px solid var(--fill-strong);border-top-color:var(--accent-on);border-radius:50%;animation:spin .8s linear infinite}
.btn.secondary.loading::after,.btn.tertiary.loading::after{border-top-color:var(--accent)}
.btn.text.loading::after{border-top-color:var(--accent-text)}
.btn.danger.loading::after{border-top-color:var(--danger-on)}
.btn.danger.secondary.loading::after{border-top-color:var(--danger)}
.btn-group{display:inline-flex;gap:8px;align-items:center}
.btn-group.end{justify-content:flex-end;width:100%}
.searchbar{display:flex;align-items:center;gap:8px;height:40px;padding:0 12px;min-width:280px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface);transition:border-color var(--motion-fast)}
.searchbar svg{width:18px;height:18px;color:var(--text-tertiary);flex:none}
.searchbar input{flex:1;border:0;background:transparent;font:inherit;font-size:14px;color:var(--text-primary);outline:0;min-width:0}
.searchbar input::placeholder{color:var(--text-disabled)}
.searchbar:focus-within{border-color:var(--border-focus)}
.searchbar .clear{border:0;padding:0;background:var(--bg-panel);color:var(--text-secondary);width:20px;height:20px;border-radius:50%;font:inherit;font-size:12px;line-height:0;cursor:pointer;display:grid;place-items:center;flex:none}
.searchbar .clear svg{display:block;width:12px;height:12px}
.searchbar kbd{font-size:12px;color:var(--text-tertiary);border:1px solid var(--border-default);border-radius:4px;padding:1px 5px;background:var(--bg-subtle)}
.searchbar.lg{height:48px}
.searchbar.lg input{font-size:16px}
.searchbar.block{width:100%}
.select-btn{display:inline-flex;padding:3px;gap:2px;background:var(--bg-panel);border-radius:var(--radius-md)}
.select-btn button{display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 14px;border:0;border-radius:6px;background:transparent;font:inherit;font-size:14px;font-weight:500;color:var(--text-secondary);cursor:pointer;white-space:nowrap}
.select-btn button svg{width:16px;height:16px;flex:none}
.select-btn button:hover{color:var(--text-primary)}
.select-btn button.on{background:var(--bg-surface);color:var(--text-primary);font-weight:600;box-shadow:var(--shadow-1)}
.select-btn button[disabled]{opacity:.4;cursor:not-allowed}
.select-btn.outline{background:transparent;padding:0;gap:0;border:1px solid var(--border-strong);overflow:hidden}
.select-btn.outline button{border-radius:0;height:36px}
.select-btn.outline button+button{border-left:1px solid var(--border-strong)}
.select-btn.outline button.on{background:var(--accent-subtle);color:var(--accent-text);box-shadow:none}
.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}
.chip{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border-radius:var(--radius-full);border:1px solid var(--border-strong);background:var(--bg-surface);font:inherit;font-size:12px;font-weight:500;color:var(--text-primary);cursor:pointer;white-space:nowrap;transition:background var(--motion-fast),border-color var(--motion-fast)}
.chip:hover{background:var(--bg-subtle)}
.chip.on{background:var(--accent-subtle);border-color:var(--accent);color:var(--accent-text)}
.chip[aria-pressed]::before{content:"";width:14px;height:14px;flex:none;background:var(--text-disabled);-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=%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=%222%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}
.chip.on::before{background:currentColor}
.chip .x{display:inline-grid;place-items:center;width:16px;height:16px;border-radius:50%;background:var(--bg-panel);color:var(--text-secondary);font-size:10px;line-height:1;margin-right:-4px}
.chip .x:hover{background:var(--border-strong)}
.chip .x svg{width:10px;height:10px}
.chip.sm{height:26px;padding:0 10px;font-size:12px}
.chip[disabled]{opacity:.4;cursor:not-allowed;pointer-events:none}
.chip-group{display:flex;flex-wrap:wrap;gap:8px}
.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)}
.dropdown{position:relative;display:inline-block}
.dropdown .trigger{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 12px 0 14px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface);font:inherit;font-size:14px;color:var(--text-primary);cursor:pointer;min-width:200px;justify-content:space-between}
.dropdown .trigger:hover{border-color:var(--border-strong)}
.dropdown .trigger .placeholder{color:var(--text-disabled)}
.dropdown .trigger::after{content:"";width:16px;height:16px;flex:none;background:var(--text-secondary);-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=%22M6 9l6 6l6 -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=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;transition:transform var(--motion-fast)}
.dropdown.open .trigger{border-color:var(--border-focus)}
.dropdown.open .trigger::after{transform:rotate(180deg)}
.dropdown .trigger[disabled]{opacity:.4;cursor:not-allowed}
.dropdown .menu{position:absolute;top:calc(100% + 4px);left:0;display:none;z-index:5;min-width:100%}
.dropdown.open .menu{display:block}
.dropdown.up .menu{top:auto;bottom:calc(100% + 4px)}
.menu{min-width:200px;padding:6px;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);box-shadow:var(--shadow-2);list-style:none;margin:0;max-height:min(320px,60vh);overflow-y:auto;overscroll-behavior:contain}
.menu-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:6px;font-size:14px;cursor:pointer;color:var(--text-primary);white-space:nowrap}
.menu-item:hover{background:var(--bg-subtle)}
.menu-item.on{background:var(--accent-subtle);color:var(--accent-text);font-weight:600}
.menu-item.danger{color:var(--danger-text)}
.menu-item[aria-disabled="true"]{opacity:.4;cursor:not-allowed;pointer-events:none}
.menu-item .hint{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.menu-item svg{width:16px;height:16px;flex:none;color:var(--text-tertiary)}
.pagination{display:inline-flex;align-items:center;gap:4px}
.pagination button{min-width:36px;height:36px;padding:0 8px;border:1px solid transparent;border-radius:var(--radius-md);background:transparent;font:inherit;font-size:14px;color:var(--text-secondary);cursor:pointer}
.pagination button:hover{background:var(--bg-subtle);color:var(--text-primary)}
.pagination button.on{background:var(--accent);color:var(--accent-on);font-weight:600}
.pagination button[disabled]{opacity:.4;cursor:not-allowed;pointer-events:none}
.pagination button svg{width:16px;height:16px;display:block}
.pagination .gap{min-width:36px;text-align:center;color:var(--text-disabled)}
.pagination.compact button{min-width:32px;height:32px;font-size:12px}
.pagination.outline button{border-color:var(--border-default);background:var(--bg-surface)}
.pagination.outline button.on{border-color:var(--accent); color:var(--accent-text);}
.pagination-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;font-size:12px;color:var(--text-secondary)}
.pagination-bar .dropdown .trigger{height:32px;min-width:120px;font-size:12px}
.topbar .searchbar{min-width:220px;height:36px}
.banner .btn{margin-left:auto}
.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}
.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}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.toolbar{display:flex;align-items:center;gap:8px;width:100%;flex-wrap:wrap}
.toolbar .spacer{flex:1}
.empty .btn{margin-top:10px}
.status-item .indicator{font-weight:600}
.log-row{font-size:12px;}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes pulse{70%{box-shadow:0 0 0 8px transparent}}