정책 설정
정책·시스템 설정 화면입니다. 섹션별 카드에 '라벨+설명 | 컨트롤' 행을 쌓고, 즉시 적용(Switch)과 저장 필요(폼)를 구분합니다.
Composition
이 패턴이 조합하는 컴포넌트입니다. 세부 옵션은 각 컴포넌트 페이지를 참고하세요.
외부 USB 차단
POL-2041 · 전체 그룹 · 마지막 배포 2026-09-06
배포되지 않은 변경 2건변경 사항은 '배포'를 눌러야 에이전트에 적용됩니다.
탐지
실시간 보호파일 실행 시 즉시 검사합니다
탐지 민감도높을수록 오탐 가능성이 커집니다
탐지 시 동작차단 후 관리자에게 알립니다
저장장치
USB 저장장치미승인 장치 연결을 차단합니다
읽기 전용 허용차단 대신 읽기만 허용
허용 장치 시리얼쉼표로 구분
HTML
<div class="stack" style="max-width:760px">
<div class="page-head"><div><h2 class="title">외부 USB 차단</h2><p class="sub">POL-2041 · 전체 그룹 · 마지막 배포 2026-09-06</p></div><div class="actions"><button type="button" class="btn md secondary">복제</button><button type="button" class="btn md primary">배포</button></div></div>
<div class="notice warning" role="status"><span class="ico"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4"/><path d="M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636 -2.87l-8.106 -13.536a1.914 1.914 0 0 0 -3.274 0"/><path d="M12 16h.01"/></svg></span><div><b>배포되지 않은 변경 2건</b>변경 사항은 '배포'를 눌러야 에이전트에 적용됩니다.</div></div>
<div class="settings-section">
<div class="section-head">탐지</div>
<div class="setting"><div class="info"><b>실시간 보호</b><span>파일 실행 시 즉시 검사합니다</span></div><div class="ctrl"><label class="switch"><input type="checkbox" role="switch" checked aria-label="실시간 보호"></label></div></div>
<div class="setting"><div class="info"><b>탐지 민감도</b><span>높을수록 오탐 가능성이 커집니다</span></div><div class="ctrl"><div class="slider" style="min-width:220px"><div class="row"><label for="sens">민감도</label><span class="val">60</span></div><input id="sens" type="range" min="0" max="100" value="60" style="--p:60%"></div></div></div>
<div class="setting"><div class="info"><b>탐지 시 동작</b><span>차단 후 관리자에게 알립니다</span></div><div class="ctrl"><div class="dropdown"><button type="button" class="trigger" aria-haspopup="listbox" aria-expanded="false" style="min-width:180px">차단 후 알림</button></div></div></div>
</div>
<div class="settings-section">
<div class="section-head">저장장치</div>
<div class="setting"><div class="info"><b>USB 저장장치</b><span>미승인 장치 연결을 차단합니다</span></div><div class="ctrl"><label class="switch"><input type="checkbox" role="switch" checked aria-label="USB 저장장치 차단"></label></div></div>
<div class="setting"><div class="info"><b>읽기 전용 허용</b><span>차단 대신 읽기만 허용</span></div><div class="ctrl"><label class="switch"><input type="checkbox" role="switch" aria-label="읽기 전용 허용"></label></div></div>
<div class="setting"><div class="info"><b>허용 장치 시리얼</b><span>쉼표로 구분</span></div><div class="ctrl"><div class="field" style="min-width:280px"><input value="4C530001, 4C530002" aria-label="허용 장치 시리얼"></div></div></div>
</div>
</div>CSS · .switch, .dropdown, .menu, .menu-item, .slider, .field, .radio, .radio-group, .notice, .toast, .toast-stack, .banner, .btn, .btn-group, .settings-section, .setting, .page-head, .stack
/* JS Design System v0.6 · .switch, .dropdown, .menu, .menu-item, .slider, .field, .radio, .radio-group, .notice, .toast, .toast-stack, .banner, .btn, .btn-group, .settings-section, .setting, .page-head, .stack · 토큰(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%}
.field{display:flex;flex-direction:column;gap:6px;min-width:260px;font-size:14px}
.field label{font-weight:600}
.field label em{color:var(--danger-text);font-style:normal;margin-left:2px}
.field input,.field textarea{height:44px;border:1px solid var(--border-strong);border-radius:var(--radius-md);padding:0 14px;font:inherit;font-size:14px;background:var(--bg-surface);color:var(--text-primary);width:100%;transition:border-color var(--motion-fast)}
.field textarea{height:auto;min-height:96px;padding:10px 14px;resize:vertical;line-height:1.5}
.field input::placeholder,.field textarea::placeholder{color:var(--text-disabled)}
.field input:hover,.field textarea:hover{border-color:var(--border-strong)}
.field input:focus,.field textarea:focus-within{border-color:var(--border-focus)}
.field input:disabled,.field textarea:disabled{background:var(--bg-panel);color:var(--text-disabled);cursor:not-allowed}
.field input:read-only{background:var(--bg-subtle)}
.field .help{color:var(--text-tertiary);font-size:12px}
.field.error input,.field.error textarea,.field.error .affix{border-color:var(--danger)}
.field.error .help{color:var(--danger-text)}
.field.sm input{height:36px;font-size:12px;padding:0 12px}
.field.lg input{height:52px;font-size:16px;padding:0 16px}
.field .affix{display:flex;align-items:center;gap:8px;height:44px;padding:0 12px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface)}
.field .affix input{border:0;height:auto;padding:0;flex:1;background:transparent;outline:0;min-width:0}
.field .affix:focus-within{border-color:var(--border-focus)}
.field .affix span{color:var(--text-tertiary);font-size:12px;white-space:nowrap}
.field .counter{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.field .row{display:flex;justify-content:space-between;align-items:baseline}
.slider{display:flex;flex-direction:column;gap:8px;min-width:280px;font-size:12px}
.slider .row{display:flex;justify-content:space-between;color:var(--text-secondary)}
.slider .val{font-weight:600;color:var(--text-primary)}
.slider input[type=range]{width:100%;height:20px;margin:0;appearance:none;-webkit-appearance:none;background:transparent;cursor:pointer}
.slider input[type=range]::-webkit-slider-runnable-track{height:6px;border-radius:var(--radius-full);background:linear-gradient(to right,var(--accent-graphic) var(--p,50%),var(--fill-strong) var(--p,50%))}
.slider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:20px;height:20px;border-radius:50%;background:var(--bg-surface);border:2px solid var(--accent);margin-top:-7px;box-shadow:var(--shadow-1)}
.slider input[type=range]::-moz-range-track{height:6px;border-radius:var(--radius-full);background:linear-gradient(to right,var(--accent-graphic) var(--p,50%),var(--fill-strong) var(--p,50%))}
.slider input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--bg-surface);border:2px solid var(--accent)}
.slider input[type=range]:disabled{opacity:.4;cursor:not-allowed}
.slider .ticks{display:flex;justify-content:space-between;font-size:12px;color:var(--text-tertiary);padding:0 4px}
.radio{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.radio 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:50%;background:var(--bg-surface);cursor:inherit;display:grid;place-items:center;transition:background var(--motion-fast),border-color var(--motion-fast)}
.radio input:hover{border-color:var(--border-strong)}
.radio input:checked{border-color:var(--accent);background:var(--accent)}
.radio input:checked::after{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent-on)}
.radio input:disabled{opacity:.4;cursor:not-allowed}
.radio input:disabled+span{color:var(--text-disabled)}
.radio small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.radio-group{display:flex;flex-direction:column;gap:12px}
.radio-group.row{flex-direction:row;gap:24px}
.radio.card{padding:14px 16px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--bg-surface);min-width:220px;flex-direction:row;gap:8px;box-shadow:none;position:static}
.radio.card:has(input:checked){border-color:var(--accent);background:var(--accent-subtle)}
.switch{display:inline-flex;align-items:center;gap:10px;font-size:14px;cursor:pointer}
.switch input{appearance:none;-webkit-appearance:none;flex:none;width:44px;height:24px;margin:0;border-radius:var(--radius-full);background:var(--border-strong);position:relative;cursor:inherit;transition:background var(--motion-fast)}
.switch input::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;background:var(--static-white);-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=%22black%22 %3E%3Cpath d=%22M7 3.34a10 10 0 1 1 -4.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 4.995 -8.336z%22 /%3E%3C/svg%3E") center/120% 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=%22black%22 %3E%3Cpath d=%22M7 3.34a10 10 0 1 1 -4.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 4.995 -8.336z%22 /%3E%3C/svg%3E") center/120% no-repeat;transition:left var(--motion-fast)}
.switch input:checked{background:var(--accent)}
.switch input:checked::after{left:22px}
.switch input:disabled{opacity:.4;cursor:not-allowed}
.switch input:disabled+span{color:var(--text-disabled)}
.switch.sm input{width:36px;height:20px}
.switch.sm input::after{width:16px;height:16px}
.switch.sm input:checked::after{left:18px}
.switch.label-left{flex-direction:row-reverse}
.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-bar .dropdown .trigger{height:32px;min-width:120px;font-size:12px}
.notice{display:flex;gap:12px;align-items:flex-start;border-radius:var(--radius-lg);padding:14px 18px;font-size:14px;border:1px solid var(--border-default);width:100%;background:var(--bg-surface);line-height:1.55}
.notice b{display:block;margin-bottom:2px}
.notice .ico{flex:none;width:20px;height:20px;display:grid;place-items:center;color:var(--info-text);margin-top:1px}
.notice .ico svg{width:20px;height:20px}
.notice .close{margin-left:auto;flex:none;border:0;background:transparent;color:var(--text-tertiary);font:inherit;line-height:1;cursor:pointer;padding:2px;display:grid;place-items:center}
.notice .close svg{width:14px;height:14px}
.notice .close:hover{color:var(--text-primary)}
.notice .actions{display:flex;gap:8px;margin-top:8px}
.notice.info{background:var(--info-subtle);border-color:color-mix(in srgb,var(--info) 30%,transparent)}
.notice.success{background:var(--success-subtle);border-color:color-mix(in srgb,var(--success) 30%,transparent)}
.notice.success .ico{color:var(--success-text)}
.notice.warning{background:var(--warning-subtle);border-color:color-mix(in srgb,var(--warning) 30%,transparent)}
.notice.warning .ico{color:var(--warning-text)}
.notice.danger{background:var(--danger-subtle);border-color:color-mix(in srgb,var(--danger) 30%,transparent)}
.notice.danger .ico{color:var(--danger-text)}
.toast{display:inline-flex;align-items:center;gap:12px;width:360px;max-width:100%;padding:12px 16px;border-radius:var(--radius-lg);background:var(--bg-inverse);color:var(--text-inverse);font-size:14px;box-shadow:var(--shadow-3)}
.toast .ico{flex:none;width:20px;height:20px;display:grid;place-items:center;color:var(--success)}
.toast .ico svg{width:20px;height:20px}
.toast.danger .ico{color:var(--danger)}
.toast .action{margin-left:auto;background:none;border:0;color:var(--accent-inverse);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}
.toast .action:hover{text-decoration:underline}
.toast-stack{display:flex;flex-direction:column;gap:8px;align-items:flex-end}
.toast-stack.fixed{position:fixed;right:24px;bottom:24px;z-index:110}
.banner{display:flex;align-items:center;gap:12px;width:100%;padding:10px 20px;background:var(--accent);color:var(--accent-on);font-size:14px;font-weight:500}
.banner .btn{margin-left:auto}
.banner.warning{background:var(--warning);color:var(--warning-on)}
.banner.danger{background:var(--danger);color:var(--danger-on)}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;width:100%}
.page-head .title{font-size:24px;font-weight:700;margin:0;letter-spacing:-.02em;line-height:1.3}
.page-head .sub{font-size:14px;color:var(--text-secondary);margin:4px 0 0}
.page-head .actions{display:flex;gap:8px;flex:none}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.form .form-row .field{min-width:0}
.form .field{min-width:0}
.empty .btn{margin-top:10px}
.settings-section{width:100%;background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:8px 24px}
.settings-section .section-head{display:flex;align-items:center;justify-content:space-between;padding:14px 0;font-size:16px;font-weight:600;border-bottom:1px solid var(--border-default)}
.setting{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:14px 0;border-bottom:1px solid var(--border-default)}
.setting:last-child{border-bottom:0}
.setting .info b{display:block;font-size:14px;font-weight:600}
.setting .info span{font-size:12px;color:var(--text-tertiary)}
.setting .ctrl{flex:none;display:flex;align-items:center;gap:8px}
@keyframes spin{to{transform:rotate(360deg)}}React · @jiran/ds-react
import { Button, Notice, PageHead, Select, Setting, SettingsSection, Slider, Stack, Switch, TextField } from "@jiran/ds-react";
export default function Example() {
return (
<Stack style={{ maxWidth: 760 }}>
<PageHead title="외부 USB 차단" sub="POL-2041 · 전체 그룹 · 마지막 배포 2026-09-06" actions={<><Button variant="secondary">복제</Button><Button>배포</Button></>} />
<Notice tone="warning" title="배포되지 않은 변경 2건">변경 사항은 '배포'를 눌러야 에이전트에 적용됩니다.</Notice>
<SettingsSection title="탐지">
<Setting title="실시간 보호" description="파일 실행 시 즉시 검사합니다"><Switch defaultChecked aria-label="실시간 보호" /></Setting>
<Setting title="탐지 민감도" description="높을수록 오탐 가능성이 커집니다"><Slider id="sens" label="민감도" defaultValue={60} wrapperStyle={{ minWidth: 220 }} /></Setting>
<Setting title="탐지 시 동작" description="차단 후 관리자에게 알립니다">
<Select options={[{ value: "block", label: "차단 후 알림" }, { value: "notify", label: "알림만" }, { value: "log", label: "기록만" }]} defaultValue="block" triggerStyle={{ minWidth: 180 }} />
</Setting>
</SettingsSection>
<SettingsSection title="저장장치">
<Setting title="USB 저장장치" description="미승인 장치 연결을 차단합니다"><Switch defaultChecked aria-label="USB 저장장치 차단" /></Setting>
<Setting title="읽기 전용 허용" description="차단 대신 읽기만 허용"><Switch aria-label="읽기 전용 허용" /></Setting>
<Setting title="허용 장치 시리얼" description="쉼표로 구분"><TextField defaultValue="4C530001, 4C530002" aria-label="허용 장치 시리얼" wrapperStyle={{ minWidth: 280 }} /></Setting>
</SettingsSection>
</Stack>
);
}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 { SettingsSection, Setting, PageHead, Switch, Select, Slider, Field, TextField, TextArea, Radio, RadioGroup, Notice, Toast, ToastStack, Banner, ToastProvider, useToast, 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 .switch, .dropdown, .menu, .menu-item, .slider, .field, .radio, .radio-group, .notice, .toast, .toast-stack, .banner, .btn, .btn-group, .settings-section, .setting, .page-head, .stack
/* JS Design System v0.6 · .switch, .dropdown, .menu, .menu-item, .slider, .field, .radio, .radio-group, .notice, .toast, .toast-stack, .banner, .btn, .btn-group, .settings-section, .setting, .page-head, .stack · 토큰(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%}
.field{display:flex;flex-direction:column;gap:6px;min-width:260px;font-size:14px}
.field label{font-weight:600}
.field label em{color:var(--danger-text);font-style:normal;margin-left:2px}
.field input,.field textarea{height:44px;border:1px solid var(--border-strong);border-radius:var(--radius-md);padding:0 14px;font:inherit;font-size:14px;background:var(--bg-surface);color:var(--text-primary);width:100%;transition:border-color var(--motion-fast)}
.field textarea{height:auto;min-height:96px;padding:10px 14px;resize:vertical;line-height:1.5}
.field input::placeholder,.field textarea::placeholder{color:var(--text-disabled)}
.field input:hover,.field textarea:hover{border-color:var(--border-strong)}
.field input:focus,.field textarea:focus-within{border-color:var(--border-focus)}
.field input:disabled,.field textarea:disabled{background:var(--bg-panel);color:var(--text-disabled);cursor:not-allowed}
.field input:read-only{background:var(--bg-subtle)}
.field .help{color:var(--text-tertiary);font-size:12px}
.field.error input,.field.error textarea,.field.error .affix{border-color:var(--danger)}
.field.error .help{color:var(--danger-text)}
.field.sm input{height:36px;font-size:12px;padding:0 12px}
.field.lg input{height:52px;font-size:16px;padding:0 16px}
.field .affix{display:flex;align-items:center;gap:8px;height:44px;padding:0 12px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface)}
.field .affix input{border:0;height:auto;padding:0;flex:1;background:transparent;outline:0;min-width:0}
.field .affix:focus-within{border-color:var(--border-focus)}
.field .affix span{color:var(--text-tertiary);font-size:12px;white-space:nowrap}
.field .counter{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.field .row{display:flex;justify-content:space-between;align-items:baseline}
.slider{display:flex;flex-direction:column;gap:8px;min-width:280px;font-size:12px}
.slider .row{display:flex;justify-content:space-between;color:var(--text-secondary)}
.slider .val{font-weight:600;color:var(--text-primary)}
.slider input[type=range]{width:100%;height:20px;margin:0;appearance:none;-webkit-appearance:none;background:transparent;cursor:pointer}
.slider input[type=range]::-webkit-slider-runnable-track{height:6px;border-radius:var(--radius-full);background:linear-gradient(to right,var(--accent-graphic) var(--p,50%),var(--fill-strong) var(--p,50%))}
.slider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:20px;height:20px;border-radius:50%;background:var(--bg-surface);border:2px solid var(--accent);margin-top:-7px;box-shadow:var(--shadow-1)}
.slider input[type=range]::-moz-range-track{height:6px;border-radius:var(--radius-full);background:linear-gradient(to right,var(--accent-graphic) var(--p,50%),var(--fill-strong) var(--p,50%))}
.slider input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--bg-surface);border:2px solid var(--accent)}
.slider input[type=range]:disabled{opacity:.4;cursor:not-allowed}
.slider .ticks{display:flex;justify-content:space-between;font-size:12px;color:var(--text-tertiary);padding:0 4px}
.radio{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.radio 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:50%;background:var(--bg-surface);cursor:inherit;display:grid;place-items:center;transition:background var(--motion-fast),border-color var(--motion-fast)}
.radio input:hover{border-color:var(--border-strong)}
.radio input:checked{border-color:var(--accent);background:var(--accent)}
.radio input:checked::after{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent-on)}
.radio input:disabled{opacity:.4;cursor:not-allowed}
.radio input:disabled+span{color:var(--text-disabled)}
.radio small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.radio-group{display:flex;flex-direction:column;gap:12px}
.radio-group.row{flex-direction:row;gap:24px}
.radio.card{padding:14px 16px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--bg-surface);min-width:220px;flex-direction:row;gap:8px;box-shadow:none;position:static}
.radio.card:has(input:checked){border-color:var(--accent);background:var(--accent-subtle)}
.switch{display:inline-flex;align-items:center;gap:10px;font-size:14px;cursor:pointer}
.switch input{appearance:none;-webkit-appearance:none;flex:none;width:44px;height:24px;margin:0;border-radius:var(--radius-full);background:var(--border-strong);position:relative;cursor:inherit;transition:background var(--motion-fast)}
.switch input::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;background:var(--static-white);-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=%22black%22 %3E%3Cpath d=%22M7 3.34a10 10 0 1 1 -4.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 4.995 -8.336z%22 /%3E%3C/svg%3E") center/120% 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=%22black%22 %3E%3Cpath d=%22M7 3.34a10 10 0 1 1 -4.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 4.995 -8.336z%22 /%3E%3C/svg%3E") center/120% no-repeat;transition:left var(--motion-fast)}
.switch input:checked{background:var(--accent)}
.switch input:checked::after{left:22px}
.switch input:disabled{opacity:.4;cursor:not-allowed}
.switch input:disabled+span{color:var(--text-disabled)}
.switch.sm input{width:36px;height:20px}
.switch.sm input::after{width:16px;height:16px}
.switch.sm input:checked::after{left:18px}
.switch.label-left{flex-direction:row-reverse}
.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-bar .dropdown .trigger{height:32px;min-width:120px;font-size:12px}
.notice{display:flex;gap:12px;align-items:flex-start;border-radius:var(--radius-lg);padding:14px 18px;font-size:14px;border:1px solid var(--border-default);width:100%;background:var(--bg-surface);line-height:1.55}
.notice b{display:block;margin-bottom:2px}
.notice .ico{flex:none;width:20px;height:20px;display:grid;place-items:center;color:var(--info-text);margin-top:1px}
.notice .ico svg{width:20px;height:20px}
.notice .close{margin-left:auto;flex:none;border:0;background:transparent;color:var(--text-tertiary);font:inherit;line-height:1;cursor:pointer;padding:2px;display:grid;place-items:center}
.notice .close svg{width:14px;height:14px}
.notice .close:hover{color:var(--text-primary)}
.notice .actions{display:flex;gap:8px;margin-top:8px}
.notice.info{background:var(--info-subtle);border-color:color-mix(in srgb,var(--info) 30%,transparent)}
.notice.success{background:var(--success-subtle);border-color:color-mix(in srgb,var(--success) 30%,transparent)}
.notice.success .ico{color:var(--success-text)}
.notice.warning{background:var(--warning-subtle);border-color:color-mix(in srgb,var(--warning) 30%,transparent)}
.notice.warning .ico{color:var(--warning-text)}
.notice.danger{background:var(--danger-subtle);border-color:color-mix(in srgb,var(--danger) 30%,transparent)}
.notice.danger .ico{color:var(--danger-text)}
.toast{display:inline-flex;align-items:center;gap:12px;width:360px;max-width:100%;padding:12px 16px;border-radius:var(--radius-lg);background:var(--bg-inverse);color:var(--text-inverse);font-size:14px;box-shadow:var(--shadow-3)}
.toast .ico{flex:none;width:20px;height:20px;display:grid;place-items:center;color:var(--success)}
.toast .ico svg{width:20px;height:20px}
.toast.danger .ico{color:var(--danger)}
.toast .action{margin-left:auto;background:none;border:0;color:var(--accent-inverse);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}
.toast .action:hover{text-decoration:underline}
.toast-stack{display:flex;flex-direction:column;gap:8px;align-items:flex-end}
.toast-stack.fixed{position:fixed;right:24px;bottom:24px;z-index:110}
.banner{display:flex;align-items:center;gap:12px;width:100%;padding:10px 20px;background:var(--accent);color:var(--accent-on);font-size:14px;font-weight:500}
.banner .btn{margin-left:auto}
.banner.warning{background:var(--warning);color:var(--warning-on)}
.banner.danger{background:var(--danger);color:var(--danger-on)}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;width:100%}
.page-head .title{font-size:24px;font-weight:700;margin:0;letter-spacing:-.02em;line-height:1.3}
.page-head .sub{font-size:14px;color:var(--text-secondary);margin:4px 0 0}
.page-head .actions{display:flex;gap:8px;flex:none}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.form .form-row .field{min-width:0}
.form .field{min-width:0}
.empty .btn{margin-top:10px}
.settings-section{width:100%;background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:8px 24px}
.settings-section .section-head{display:flex;align-items:center;justify-content:space-between;padding:14px 0;font-size:16px;font-weight:600;border-bottom:1px solid var(--border-default)}
.setting{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:14px 0;border-bottom:1px solid var(--border-default)}
.setting:last-child{border-bottom:0}
.setting .info b{display:block;font-size:14px;font-weight:600}
.setting .info span{font-size:12px;color:var(--text-tertiary)}
.setting .ctrl{flex:none;display:flex;align-items:center;gap:8px}
@keyframes spin{to{transform:rotate(360deg)}}