/* ui/tokens.css */
/* 부품 라이브러리의 토큰(D-115). 색·간격·글꼴·컨트롤 크기의 원천은 이 파일 하나다. 부품 CSS는 여기의 변수만 쓰고 색 값을 직접 적지 않는다.
   밝음·어두움 두 벌이다. 운영체제 설정을 따르고, data-theme="light|dark"가 달린 요소 안은 그것을 따른다(<html>에 달면 화면 전체,
   도감처럼 한 칸에만 달면 그 칸만). 고른 테마를 어디에 둘지는 앱이 정한다.
   데스크톱식 배치라 컨트롤은 촘촘하게(32px) 둔다. 글꼴은 기기 글꼴만 쓴다(바깥 글꼴 서버를 부르지 않는다). */
:root, [data-theme="light"] {
  --bg: #f6f7f9; --surface: #ffffff; --surface-sunken: #f1f3f5; --surface-raised: #ffffff;
  --line: #e4e7ec; --line-strong: #d0d5dd;
  --ink: #111827; --muted: #5f6878; --faint: #98a2b3;
  --accent: #2563eb; --on-accent: #ffffff; --accent-ink: #1d4ed8; --accent-soft: #eef3ff;
  --live: #b45309; --live-soft: #fef3c7; --done: #15803d; --done-soft: #dcfce7;
  --danger: #b91c1c; --on-danger: #ffffff; --danger-soft: #fee2e2;
  /* 에이전트 종류(엔진) 표시 색: 대화창 알약의 이름표가 쓴다(D-122). 뜻이 있는 색(진행·끝남·위험)과 겹치지 않게 따로 둔다. */
  --engine-claude: #a84a24; --engine-claude-soft: #fbe9e1; --engine-codex: #ffffff; --engine-codex-soft: #000000;
  --engine-lmstudio: #6d28d9; --engine-lmstudio-soft: #ede7fe; --engine-gemini: #1a56c9; --engine-gemini-soft: #e2ebff;
  --focus: #2563eb; --selection: #dbe7ff; --focus-ring: 0 0 0 3px var(--accent-soft); --highlight: var(--live-soft);
  --scrim: rgba(17, 24, 39, .45);
  --media-backdrop: #0b0c0f; --on-media: #f2f4f7; --danger-on-media: #fca5a5;
  --shadow-xs: 0 1px 2px rgba(16, 24, 40, .05);
  --shadow-pop: 0 12px 32px -8px rgba(16, 24, 40, .18), 0 2px 6px rgba(16, 24, 40, .06);
  --scrollbar-thumb: #c4cad4; --scrollbar-thumb-hover: #a2aab7;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%235f6878' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");

  --font-sans: "Malgun Gothic", "Apple SD Gothic Neo", "Noto Sans KR", system-ui, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", Consolas, "D2Coding", monospace;
  --text-sm: 12px; --text-md: 13px; --text-lg: 15px; --text-xl: 18px;
  --leading-sm: 16px; --leading-md: 20px; --leading-lg: 22px; --leading-xl: 26px;

  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px;
  --radius-sm: 4px; --radius-md: 6px; --radius-lg: 10px; --radius-pill: 999px;
  --control-height: 32px; --control-height-sm: 26px; --icon-size: 16px; --indent: calc(var(--icon-size) + var(--space-1));
  --bar-height: 36px; --status-height: 26px; --gutter: 6px; --row-height: 28px;
  --chat-width: 36rem; /* 에이전트 대화창 한 칸의 펼친 폭(D-122). 여럿이면 이 폭으로 나란히 선다 */
  --chat-pill-width: 22rem; /* 접힌 대화창(알약)의 폭 */
  --duration-fast: .12s; --duration-slow: .24s; --disabled-opacity: .45; --track-thickness: 4px;
  --layer-pop: 50; --layer-dialog: 60; --layer-toast: 70;
}
[data-theme="light"] { color-scheme: light; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0b0c0f; --surface: #16181d; --surface-sunken: #101216; --surface-raised: #1d2027;
    --line: #262a31; --line-strong: #3a404b;
    --ink: #d0d5dd; --muted: #98a2b3; --faint: #667085;
    --accent: #2563eb; --on-accent: #ffffff; --accent-ink: #93c5fd; --accent-soft: #172554;
    --live: #fbbf24; --live-soft: #3a2a07; --done: #4ade80; --done-soft: #0e2a19;
    --danger: #f87171; --on-danger: #2a0a0a; --danger-soft: #3a1515;
    --engine-claude: #f2a582; --engine-claude-soft: #3b2218; --engine-codex: #ffffff; --engine-codex-soft: #000000;
    --engine-lmstudio: #c4b5fd; --engine-lmstudio-soft: #2a2143; --engine-gemini: #93b4ff; --engine-gemini-soft: #1b2747;
    --focus: #60a5fa; --selection: #1e3a8a; --focus-ring: 0 0 0 3px var(--accent-soft); --highlight: var(--live-soft);
    --scrim: rgba(0, 0, 0, .6);
    --shadow-xs: 0 1px 2px rgba(0, 0, 0, .4); --shadow-pop: 0 12px 32px -8px rgba(0, 0, 0, .7);
    --scrollbar-thumb: #3a404b; --scrollbar-thumb-hover: #545c6b;
    --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2398a2b3' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    color-scheme: dark;
  }
}
[data-theme="dark"] {
  --bg: #0b0c0f; --surface: #16181d; --surface-sunken: #101216; --surface-raised: #1d2027;
  --line: #262a31; --line-strong: #3a404b;
  --ink: #d0d5dd; --muted: #98a2b3; --faint: #667085;
  --accent: #2563eb; --on-accent: #ffffff; --accent-ink: #93c5fd; --accent-soft: #172554;
  --live: #fbbf24; --live-soft: #3a2a07; --done: #4ade80; --done-soft: #0e2a19;
  --danger: #f87171; --on-danger: #2a0a0a; --danger-soft: #3a1515;
  --engine-claude: #f2a582; --engine-claude-soft: #3b2218; --engine-codex: #ffffff; --engine-codex-soft: #000000;
  --engine-lmstudio: #c4b5fd; --engine-lmstudio-soft: #2a2143; --engine-gemini: #93b4ff; --engine-gemini-soft: #1b2747;
  --focus: #60a5fa; --selection: #1e3a8a; --focus-ring: 0 0 0 3px var(--accent-soft); --highlight: var(--live-soft);
  --scrim: rgba(0, 0, 0, .6);
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .4); --shadow-pop: 0 12px 32px -8px rgba(0, 0, 0, .7);
  --scrollbar-thumb: #3a404b; --scrollbar-thumb-hover: #545c6b;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2398a2b3' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  color-scheme: dark;
}

/* ui/base.css */
/* 부품 라이브러리의 바탕(D-115): 상자 크기, 글꼴, 초점 테두리, 스크롤 막대, 웹 컴포넌트가 정의되기 전의 깜빡임 막기.
   라이브러리를 쓰는 화면은 <html class="ui">를 달아 이 바탕을 받는다(아직 옮기지 않은 화면은 그대로 둔다). */
.ui, .ui *, .ui *::before, .ui *::after { box-sizing: border-box; }
.ui * { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) transparent; }
.ui *:hover { scrollbar-color: var(--scrollbar-thumb-hover) transparent; }
.ui body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--font-sans); font-size: var(--text-md); line-height: var(--leading-md); -webkit-font-smoothing: antialiased; }
.ui :focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.ui ::selection { background: var(--selection); }
.ui h1, .ui h2, .ui h3 { text-wrap: balance; margin: 0; }
.ui code, .ui kbd, .ui pre { font-family: var(--font-mono); font-size: var(--text-sm); }

/* 정의되기 전의 컴포넌트는 숨겨 둔다(정의되면 바로 그린다). */
.ui :not(:defined) { visibility: hidden; }

/* 화면 문구 도우미 */
.ui-muted { color: var(--muted); }
.ui-mono { font-family: var(--font-mono); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.ui-visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.ui-icon { width: var(--icon-size); height: var(--icon-size); flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; vertical-align: middle; }

/* ui/controls/badge/badge.css */
/* 표: <span class="ui-badge">. 상태·갈래·개수를 짧게 보인다. 색 data-tone(neutral·accent·live·done·danger, 없으면 neutral),
   앞에 점 data-dot, 점이 숨쉬듯 깜빡이게 data-busy(움직임을 줄인 설정이면 멈춘다), 숫자 동그라미 data-shape="count", 테두리만 data-variant="outline".
   색만으로 뜻을 전하지 않도록 글을 함께 쓴다. */
.ui-badge {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%; height: 20px; padding: 0 var(--space-2); vertical-align: middle;
  border: 1px solid transparent; border-radius: var(--radius-pill); background: var(--surface-sunken); color: var(--muted);
  font-size: var(--text-sm); line-height: var(--leading-sm); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ui-badge[data-tone="accent"] { background: var(--accent-soft); color: var(--accent-ink); }
.ui-badge[data-tone="live"] { background: var(--live-soft); color: var(--live); }
.ui-badge[data-tone="done"] { background: var(--done-soft); color: var(--done); }
.ui-badge[data-tone="danger"] { background: var(--danger-soft); color: var(--danger); }
.ui-badge[data-variant="outline"] { background: transparent; border-color: currentColor; }
.ui-badge[data-dot]::before { content: ""; flex: none; width: 6px; height: 6px; border-radius: var(--radius-pill); background: currentColor; }
.ui-badge[data-busy]::before { animation: ui-badge-pulse 1.4s ease-in-out infinite; }
.ui-badge[data-shape="count"] { justify-content: center; min-width: 20px; padding: 0 6px; font-variant-numeric: tabular-nums; }
.ui-badge[data-tone="accent"][data-shape="count"] { background: var(--accent); color: var(--on-accent); }
.ui-badge[data-tone="danger"][data-shape="count"] { background: var(--danger); color: var(--on-danger); }
.ui-badge > .ui-icon { width: 12px; height: 12px; }
@keyframes ui-badge-pulse { 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) { .ui-badge[data-busy]::before { animation: none; } }

/* ui/controls/button/button.css */
/* 단추: <button class="ui-button">. 변형 data-variant(primary·quiet·ghost·danger), 크기 data-size="sm", 아이콘만 data-shape="icon".
   위험 단추를 한 번 눌러 「정말」 상태가 되면 data-armed. 막힘은 disabled, 또는 풍선 도움말·초점을 남기려면 aria-disabled="true"(누름은 쓰는 쪽이 막는다). 아이콘 단추가 상태(바뀐 수 등)를 보이면 숫자 표(.ui-badge data-shape="count")를
   단추 안 끝에 넣는다: 오른쪽 위 모서리에 얹힌다(이름은 aria-label에 숫자까지 적는다). */
.ui-button {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  height: var(--control-height); padding: 0 var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink);
  font: inherit; font-weight: 500; white-space: nowrap; cursor: pointer; box-shadow: var(--shadow-xs);
  transition: background-color var(--duration-fast), border-color var(--duration-fast), color var(--duration-fast);
}
.ui-button:hover { background: var(--surface-sunken); }
.ui-button:active { transform: translateY(1px); }
.ui-button[data-variant="primary"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.ui-button[data-variant="primary"]:hover { filter: brightness(1.1); }
.ui-button[data-variant="quiet"] { border-color: transparent; background: transparent; color: var(--accent-ink); box-shadow: none; }
.ui-button[data-variant="quiet"]:hover { background: var(--accent-soft); }
.ui-button[data-variant="ghost"] { border-color: transparent; background: transparent; color: var(--muted); box-shadow: none; }
.ui-button[data-variant="ghost"]:hover { background: var(--surface-sunken); color: var(--ink); }
.ui-button[data-variant="danger"] { border-color: var(--danger); color: var(--danger); }
.ui-button[data-variant="danger"][data-armed] { background: var(--danger); color: var(--on-danger); }
.ui-button[data-size="sm"] { height: var(--control-height-sm); padding: 0 var(--space-2); font-size: var(--text-sm); }
.ui-button[data-shape="icon"] { position: relative; width: var(--control-height); padding: 0; }
.ui-button[data-shape="icon"] > .ui-badge[data-shape="count"] {
  position: absolute; top: -6px; right: -6px; min-width: 16px; height: 16px; padding: 0 4px;
  box-shadow: 0 0 0 2px var(--surface); font-size: 10px; pointer-events: none;
}
.ui-button[data-size="sm"][data-shape="icon"] { width: var(--control-height-sm); }
.ui-button[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-ink); }
.ui-button:disabled, .ui-button[aria-disabled="true"] { opacity: var(--disabled-opacity); cursor: not-allowed; filter: none; transform: none; }
.ui-button[aria-disabled="true"]:is(:not([data-variant]), [data-variant="danger"]):hover { background: var(--surface); }
.ui-button[aria-disabled="true"]:is([data-variant="quiet"], [data-variant="ghost"]):hover { background: transparent; }
.ui-button[aria-disabled="true"][data-variant="primary"]:hover { filter: none; }
@media (prefers-reduced-motion: reduce) { .ui-button { transition: none; } .ui-button:active { transform: none; } }

/* ui/controls/check/check.css */
/* 고르기 단추: <label class="ui-check"><input type="checkbox|radio">글</label>. 브라우저 기본 입력을 그대로 쓰고 모양만 바꾼다
   (키보드·폼 제출·읽어 주기는 기본 요소가 한다). 글 아래 설명은 <span class="ui-check__hint">, 오류는 입력에 aria-invalid="true",
   중간 상태는 입력의 indeterminate 속성(코드로 켠다), 이름표 없이 쓰면 입력에 aria-label을 단다. 작은 크기 data-size="sm". */
.ui-check { display: inline-grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--space-2); align-items: start; min-width: 0; color: var(--ink); font-size: var(--text-md); line-height: var(--leading-md); cursor: pointer; }
.ui-check > input {
  appearance: none; -webkit-appearance: none; position: relative; display: grid; place-content: center; flex: none;
  width: 16px; height: 16px; margin: 2px 0 0; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--surface); box-shadow: var(--shadow-xs); cursor: inherit;
  transition: background-color var(--duration-fast), border-color var(--duration-fast);
}
.ui-check > input[type="radio"] { border-radius: var(--radius-pill); }
.ui-check > input::before { content: ""; display: block; opacity: 0; transition: opacity var(--duration-fast); }
/* 체크 표시: 아래·오른쪽 테두리만 있는 상자를 돌려 그린다(그림 파일 없이 토큰 색만). */
.ui-check > input[type="checkbox"]::before { width: 5px; height: 9px; margin-top: -2px; border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.ui-check > input[type="checkbox"]:indeterminate::before { width: 8px; height: 0; margin-top: 0; border-width: 0 0 2px; transform: none; }
.ui-check > input[type="radio"]::before { width: 6px; height: 6px; border-radius: var(--radius-pill); background: var(--on-accent); }
.ui-check > input:checked, .ui-check > input[type="checkbox"]:indeterminate { background: var(--accent); border-color: var(--accent); }
.ui-check > input:checked::before, .ui-check > input[type="checkbox"]:indeterminate::before { opacity: 1; }
.ui-check:hover > input:not(:disabled):not(:checked) { border-color: var(--faint); }
.ui-check > input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.ui-check > input[aria-invalid="true"] { border-color: var(--danger); }
.ui-check > input:disabled { opacity: var(--disabled-opacity); }
.ui-check:has(> input:disabled) { color: var(--muted); cursor: not-allowed; }
.ui-check__hint { grid-column: 2; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); overflow-wrap: anywhere; }
.ui-check[data-size="sm"] { font-size: var(--text-sm); line-height: var(--leading-sm); column-gap: 6px; }
.ui-check[data-size="sm"] > input { width: 14px; height: 14px; margin-top: 1px; }
.ui-check[data-size="sm"] > input[type="checkbox"]::before { width: 4px; height: 8px; }
.ui-check[data-size="sm"] > input[type="radio"]::before { width: 5px; height: 5px; }
@media (prefers-reduced-motion: reduce) { .ui-check > input, .ui-check > input::before { transition: none; } }

/* ui/controls/drop/drop.css */
/* 파일 받는 자리: <ui-drop>. 점선 테두리 상자 전체가 단추다. 끌어 오는 동안 data-dragover, 거절하면 잠깐 data-state="rejected", 막힘 disabled.
   글쓴이 자식(미리 보기 등)을 넣으면 기본 글 대신 그 자식이 가운데 놓인다. 높이는 내용이 정하고, 더 크게 하려면 화면이 min-height를 준다. */
ui-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-1);
  position: relative; min-width: 0; min-height: 112px; padding: var(--space-4); text-align: center;
  border: 1.5px dashed var(--line-strong); border-radius: var(--radius-lg); background: var(--surface-sunken); color: var(--muted);
  cursor: pointer; overflow-wrap: anywhere;
  transition: border-color var(--duration-fast), background-color var(--duration-fast), color var(--duration-fast);
}
ui-drop:hover { border-color: var(--faint); color: var(--ink); }
ui-drop:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
ui-drop[data-dragover] { border-color: var(--accent); border-style: solid; background: var(--accent-soft); color: var(--accent-ink); }
ui-drop[data-state="rejected"] { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
ui-drop[disabled] { opacity: var(--disabled-opacity); cursor: not-allowed; }
ui-drop[disabled]:hover { border-color: var(--line-strong); color: var(--muted); }
ui-drop > .ui-drop__icon { display: grid; place-items: center; width: 36px; height: 36px; margin-bottom: var(--space-1); border-radius: var(--radius-pill); background: var(--surface); color: var(--accent-ink); box-shadow: var(--shadow-xs); }
ui-drop > .ui-drop__label { color: var(--ink); font-weight: 500; }
ui-drop > .ui-drop__hint, ui-drop > .ui-drop__paste { font-size: var(--text-sm); line-height: var(--leading-sm); }
ui-drop[data-dragover] > .ui-drop__label { color: var(--accent-ink); }
/* 알림 글은 평소에 숨겨 두고(읽어 주기만), 거절했을 때만 보인다. */
ui-drop > .ui-drop__status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
ui-drop[data-state="rejected"] > .ui-drop__status { position: static; width: auto; height: auto; overflow: visible; clip: auto; white-space: normal; font-size: var(--text-sm); font-weight: 500; }
ui-drop[data-state="rejected"] > .ui-drop__label { color: var(--danger); }
@media (max-width: 480px) { ui-drop > .ui-drop__paste { display: none; } }
@media (prefers-reduced-motion: reduce) { ui-drop { transition: none; } }

/* ui/controls/field/field.css */
/* 입력 묶음: <div class="ui-field">에 이름표(.ui-field__label, for로 칸과 잇는다), 칸, 도움말(.ui-field__hint), 오류 글(.ui-field__error)을 차례로 넣는다.
   칸은 aria-describedby로 도움말·오류 글을 가리키고, 오류면 칸에 aria-invalid="true"를 단다(칸 모양은 각 컨트롤이 정한다).
   이름표 옆 「필수」 표시는 .ui-field__label[data-required], 이름표를 왼쪽에 두는 줄 배치는 data-layout="row"(좁으면 위아래로 돌아간다). */
.ui-field { display: grid; gap: 6px; min-width: 0; align-content: start; margin: 0; padding: 0; border: 0; }
.ui-field__label { display: flex; align-items: baseline; gap: var(--space-1); min-width: 0; padding: 0; color: var(--ink); font-size: var(--text-md); line-height: var(--leading-sm); font-weight: 500; }
.ui-field__label[data-required]::after { content: "필수"; color: var(--danger); font-size: var(--text-sm); font-weight: 400; }
.ui-field__hint, .ui-field__error { margin: 0; font-size: var(--text-sm); line-height: var(--leading-sm); overflow-wrap: anywhere; }
.ui-field__hint { color: var(--muted); }
.ui-field__error { display: flex; align-items: flex-start; gap: var(--space-1); color: var(--danger); }
.ui-field__error:empty, .ui-field__error[hidden] { display: none; }
.ui-field__error > .ui-icon { margin-top: 0; }
.ui-field[aria-disabled="true"] .ui-field__label { color: var(--muted); }
.ui-field[data-layout="row"] { grid-template-columns: minmax(96px, 160px) minmax(0, 1fr); column-gap: var(--space-3); align-items: center; }
.ui-field[data-layout="row"] > :not(.ui-field__label) { grid-column: 2; }
.ui-field[data-layout="row"] > .ui-field__label { grid-column: 1; }
@media (max-width: 480px) { .ui-field[data-layout="row"] { grid-template-columns: minmax(0, 1fr); } .ui-field[data-layout="row"] > :not(.ui-field__label), .ui-field[data-layout="row"] > .ui-field__label { grid-column: 1; } }

/* ui/controls/input/input.css */
/* 글 칸: <input class="ui-input">, 여러 줄 <textarea class="ui-textarea">.
   크기 data-size="sm", 고정폭 글꼴 data-font="mono", 테두리 없는 칸(작성기 안 등) data-variant="bare", 오류 aria-invalid="true".
   <textarea data-grow>는 내용 따라 자란다(field-sizing: content, 최대 높이 --ui-textarea-max). 모르는 브라우저는 input.js가 맞춘다. */
.ui-input, .ui-textarea {
  display: block; width: 100%; min-width: 0; min-height: var(--control-height); margin: 0; padding: 0 var(--space-3);
  border: 1px solid var(--line-strong); border-radius: var(--radius-md); background: var(--surface); color: var(--ink);
  font: inherit; font-size: var(--text-md); line-height: var(--leading-md); box-shadow: var(--shadow-xs);
  transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
}
.ui-input { height: var(--control-height); text-overflow: ellipsis; }
.ui-textarea { min-height: 72px; padding: 5px var(--space-3); resize: vertical; overflow-wrap: anywhere; }
.ui-input::placeholder, .ui-textarea::placeholder { color: var(--faint); opacity: 1; }
.ui-input:hover, .ui-textarea:hover { border-color: var(--faint); }
.ui-input:focus, .ui-textarea:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.ui-input[aria-invalid="true"], .ui-textarea[aria-invalid="true"] { border-color: var(--danger); }
.ui-input[aria-invalid="true"]:focus, .ui-textarea[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px var(--danger-soft); }
.ui-input:read-only:not(:disabled), .ui-textarea:read-only:not(:disabled) { background: var(--surface-sunken); }
.ui-input:disabled, .ui-textarea:disabled { opacity: var(--disabled-opacity); cursor: not-allowed; background: var(--surface-sunken); }
.ui-input[data-size="sm"], .ui-textarea[data-size="sm"] { font-size: var(--text-sm); line-height: var(--leading-sm); padding-inline: var(--space-2); }
.ui-input[data-size="sm"] { height: var(--control-height-sm); min-height: var(--control-height-sm); }
.ui-textarea[data-size="sm"] { min-height: 56px; padding-block: 4px; }
.ui-input[data-font="mono"], .ui-textarea[data-font="mono"] { font-family: var(--font-mono); font-size: var(--text-sm); }
.ui-input[type="search"]::-webkit-search-cancel-button { cursor: pointer; }
.ui-input[data-variant="bare"], .ui-textarea[data-variant="bare"] { border-color: transparent; background: transparent; box-shadow: none; padding-inline: 0; }
.ui-input[data-variant="bare"]:focus, .ui-textarea[data-variant="bare"]:focus { border-color: transparent; box-shadow: none; }
/* 자라는 칸: 한 줄 높이에서 시작해 --ui-textarea-max까지 자라고 그 뒤로는 안에서 굴린다. */
.ui-textarea[data-grow] { min-height: var(--control-height); max-height: var(--ui-textarea-max, 40vh); resize: none; overflow-y: auto; }
.ui-textarea[data-grow][data-size="sm"] { min-height: var(--control-height-sm); }
@supports (field-sizing: content) { .ui-textarea[data-grow] { field-sizing: content; } }
@media (prefers-reduced-motion: reduce) { .ui-input, .ui-textarea { transition: none; } }

/* ui/controls/number/number.css */
/* 계단 숫자 칸: <ui-number>. 한 테두리 안에 − 단추 · 숫자 칸 · (단위) · + 단추가 늘어선다. 크기 size="sm", 막힘 disabled, 읽기만 readonly. */
ui-number {
  display: inline-flex; align-items: stretch; width: 140px; max-width: 100%; min-width: 0; height: var(--control-height); vertical-align: middle;
  border: 1px solid var(--line-strong); border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow-xs); overflow: hidden;
  transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
}
ui-number:hover { border-color: var(--faint); }
ui-number:focus-within { border-color: var(--accent); box-shadow: var(--focus-ring); }
ui-number[size="sm"] { height: var(--control-height-sm); width: 116px; }
ui-number[disabled] { opacity: var(--disabled-opacity); background: var(--surface-sunken); }
ui-number[readonly] { background: var(--surface-sunken); }
ui-number > .ui-number__input {
  flex: 1 1 0; width: 0; min-width: 2ch; margin: 0; padding: 0 var(--space-1); border: 0; background: transparent; color: var(--ink);
  font: inherit; font-size: var(--text-md); line-height: var(--leading-md); font-variant-numeric: tabular-nums; text-align: center;
}
ui-number > .ui-number__input:focus { outline: none; }
ui-number > .ui-number__input:disabled { cursor: not-allowed; }
ui-number[size="sm"] > .ui-number__input { font-size: var(--text-sm); line-height: var(--leading-sm); }
ui-number > .ui-number__unit { display: flex; align-items: center; padding-right: var(--space-1); color: var(--muted); font-size: var(--text-sm); white-space: nowrap; }
ui-number > .ui-number__step {
  display: grid; place-items: center; flex: none; width: var(--control-height); margin: 0; padding: 0; border: 0;
  background: transparent; color: var(--muted); cursor: pointer; touch-action: manipulation; user-select: none;
  transition: background-color var(--duration-fast), color var(--duration-fast);
}
ui-number[size="sm"] > .ui-number__step { width: var(--control-height-sm); }
ui-number > .ui-number__step:first-of-type { border-right: 1px solid var(--line); }
ui-number > .ui-number__step:last-of-type { border-left: 1px solid var(--line); }
ui-number > .ui-number__step:hover:not(:disabled) { background: var(--surface-sunken); color: var(--ink); }
ui-number > .ui-number__step:active:not(:disabled) { background: var(--accent-soft); color: var(--accent-ink); }
ui-number > .ui-number__step:disabled { color: var(--faint); cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { ui-number, ui-number > .ui-number__step { transition: none; } }

/* ui/controls/progress/progress.css */
/* 진행 막대: 브라우저 기본 <progress class="ui-progress" value="40" max="100">. value가 없으면 얼마나 남았는지 모르는 막대(왔다 갔다)다.
   이름은 aria-label이나 <label for>로 준다. 굵기 data-size="sm"(얇게)·"lg"(굵게), 색 data-tone(live·done·danger, 없으면 accent).
   움직임을 줄인 설정이면 모르는 막대는 멈춘 채 일부만 칠한다. */
.ui-progress {
  --ui-progress-color: var(--accent);
  display: block; width: 100%; min-width: 0; height: 6px; margin: 0; padding: 0; overflow: hidden;
  appearance: none; -webkit-appearance: none; border: 0; border-radius: var(--radius-pill);
  background: var(--surface-sunken); color: var(--ui-progress-color); box-shadow: inset 0 0 0 1px var(--line);
}
.ui-progress[data-size="sm"] { height: 3px; }
.ui-progress[data-size="lg"] { height: 10px; }
.ui-progress[data-tone="live"] { --ui-progress-color: var(--live); }
.ui-progress[data-tone="done"] { --ui-progress-color: var(--done); }
.ui-progress[data-tone="danger"] { --ui-progress-color: var(--danger); }
.ui-progress::-webkit-progress-bar { background: transparent; border-radius: var(--radius-pill); }
.ui-progress::-webkit-progress-value { background: var(--ui-progress-color); border-radius: var(--radius-pill); transition: width var(--duration-slow) linear; }
.ui-progress::-moz-progress-bar { background: var(--ui-progress-color); border-radius: var(--radius-pill); }
/* 모르는 막대: 칠한 조각 하나가 왼쪽에서 오른쪽으로 지나간다. */
.ui-progress:indeterminate {
  background: linear-gradient(var(--ui-progress-color), var(--ui-progress-color)) no-repeat, var(--surface-sunken);
  background-size: 35% 100%, auto; background-position: -55% 0, 0 0;
  animation: ui-progress-slide 1.3s ease-in-out infinite;
}
.ui-progress:indeterminate::-webkit-progress-bar { background: transparent; }
.ui-progress:indeterminate::-moz-progress-bar { background: transparent; }
@keyframes ui-progress-slide { from { background-position: -55% 0, 0 0; } to { background-position: 155% 0, 0 0; } }
@media (prefers-reduced-motion: reduce) {
  .ui-progress::-webkit-progress-value { transition: none; }
  .ui-progress:indeterminate { animation: none; background-position: 0 0, 0 0; }
}

/* ui/controls/segmented/segmented.css */
/* 마디 고르기: <ui-segmented>. 낮은 바탕 위에 선택지 단추가 늘어서고, 고른 것만 떠 보인다. 좁으면 줄을 바꾼다.
   크기 size="sm", 부모 폭 채우기 stretch, 막힘 disabled. */
ui-segmented {
  display: inline-flex; flex-wrap: wrap; gap: 2px; max-width: 100%; padding: 2px; vertical-align: middle;
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-sunken);
}
ui-segmented[stretch] { display: flex; }
ui-segmented[stretch] > .ui-segmented__option { flex: 1 1 0; }
ui-segmented > .ui-segmented__option {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 0;
  height: calc(var(--control-height) - 6px); padding: 0 10px; margin: 0;
  border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--muted);
  font: inherit; font-size: var(--text-md); line-height: var(--leading-md); font-weight: 500; white-space: nowrap; cursor: pointer;
  transition: background-color var(--duration-fast), color var(--duration-fast);
}
ui-segmented > .ui-segmented__option:hover:not(:disabled) { color: var(--ink); }
ui-segmented > .ui-segmented__option[aria-checked="true"] { background: var(--surface-raised); color: var(--ink); font-weight: 600; box-shadow: var(--shadow-xs), 0 0 0 1px var(--line); }
ui-segmented > .ui-segmented__option:focus-visible { outline: 2px solid var(--focus); outline-offset: 0; }
ui-segmented > .ui-segmented__option:disabled { opacity: var(--disabled-opacity); cursor: not-allowed; }
ui-segmented > .ui-segmented__option:not(:has(.ui-segmented__label)) { width: calc(var(--control-height) - 6px); padding: 0; }
ui-segmented .ui-segmented__label { overflow: hidden; text-overflow: ellipsis; }
ui-segmented[size="sm"] > .ui-segmented__option { height: calc(var(--control-height-sm) - 6px); padding: 0 var(--space-2); font-size: var(--text-sm); line-height: var(--leading-sm); }
ui-segmented[size="sm"] > .ui-segmented__option:not(:has(.ui-segmented__label)) { width: calc(var(--control-height-sm) - 6px); padding: 0; }
ui-segmented[disabled] { opacity: var(--disabled-opacity); }
@media (prefers-reduced-motion: reduce) { ui-segmented > .ui-segmented__option { transition: none; } }

/* ui/controls/select/select.css */
/* 고르기 칸: 브라우저 기본 <select class="ui-select">. 크기 data-size="sm", 오류 aria-invalid="true", 폭을 내용에 맞추려면 data-width="auto".
   사용자 정의 고르기 칸(appearance: base-select)을 아는 브라우저(Chromium 계열, WebView2 포함)는 펼친 목록까지 같은 모양으로 그린다.
   <select>를 그대로 두므로 키보드 조작과 읽어 주기는 같고, 모르는 브라우저는 운영체제 목록을 쓴다. */
.ui-select {
  display: block; width: 100%; min-width: 0; height: var(--control-height); margin: 0; padding: 0 36px 0 var(--space-3);
  border: 1px solid var(--line-strong); border-radius: var(--radius-md); color: var(--ink);
  background: var(--surface) var(--select-arrow) no-repeat right 10px center / 16px;
  font: inherit; font-size: var(--text-md); line-height: var(--leading-md); box-shadow: var(--shadow-xs);
  appearance: none; -webkit-appearance: none; cursor: pointer; text-overflow: ellipsis; white-space: nowrap;
  transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
}
.ui-select:hover { border-color: var(--faint); }
.ui-select:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.ui-select[aria-invalid="true"] { border-color: var(--danger); }
.ui-select[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px var(--danger-soft); }
.ui-select:disabled { opacity: var(--disabled-opacity); cursor: not-allowed; background-color: var(--surface-sunken); }
.ui-select[data-width="auto"] { display: inline-block; width: auto; max-width: 100%; }
.ui-select[data-size="sm"] {
  height: var(--control-height-sm); padding: 0 28px 0 var(--space-2); font-size: var(--text-sm); line-height: var(--leading-sm);
  background-position: right 7px center; background-size: 14px;
}
.ui-select option, .ui-select optgroup { background: var(--surface); color: var(--ink); }
@supports (appearance: base-select) {
  .ui-select, .ui-select::picker(select) { appearance: base-select; }
  /* 고른 글은 한 줄로 두고 내용 상자에서 자른다(화살표 밑으로 글이 들어가지 않게). 이 모드에서는 말줄임이 먹지 않는다. */
  .ui-select { display: flex; align-items: center; overflow: clip; overflow-clip-margin: content-box; }
  .ui-select[data-width="auto"] { display: inline-flex; }
  .ui-select::picker-icon { display: none; }
  .ui-select::picker(select) {
    margin-block: 4px; padding: 4px; min-width: anchor-size(width); max-height: min(320px, 60vh); overflow-y: auto;
    background: var(--surface-raised); color: var(--ink); border: 1px solid var(--line-strong); border-radius: var(--radius-md); box-shadow: var(--shadow-pop);
  }
  .ui-select option {
    display: flex; align-items: center; gap: var(--space-2); min-height: var(--row-height); padding: 4px 10px; border-radius: var(--radius-sm);
    background: transparent; color: var(--ink); font: inherit; white-space: nowrap; cursor: pointer;
  }
  .ui-select option:hover, .ui-select option:focus-visible { background: var(--surface-sunken); outline: none; }
  .ui-select option:checked { background: var(--accent-soft); color: var(--accent-ink); font-weight: 600; }
  .ui-select option:disabled { color: var(--faint); cursor: not-allowed; }
  .ui-select option::checkmark { display: none; }
  .ui-select optgroup { padding: 4px 0; color: var(--muted); font-size: var(--text-sm); font-weight: 600; }
}
@media (prefers-reduced-motion: reduce) { .ui-select { transition: none; } }

/* ui/controls/slider/slider.css */
/* 밀대: 브라우저 기본 <input type="range" class="ui-slider">. 키보드(←→·Home·End·PageUp·PageDown)와 읽어 주기는 기본 요소가 한다.
   채운 부분: Firefox는 스스로 그린다. 다른 브라우저는 글쓴이가 --ui-slider-fill(0%~100%)을 주면 그만큼 칠한다
   (Lit이면 style=${styleMap({ "--ui-slider-fill": `${비율}%` })}). 주지 않으면 길만 그린다. 크기 data-size="sm", 막힘 disabled. */
.ui-slider {
  --ui-slider-track: 4px; --ui-slider-thumb: 16px;
  display: block; width: 100%; min-width: 0; height: var(--control-height-sm); margin: 0; padding: 0;
  appearance: none; -webkit-appearance: none; background: transparent; cursor: pointer; accent-color: var(--accent);
}
.ui-slider[data-size="sm"] { --ui-slider-track: 3px; --ui-slider-thumb: 12px; height: 18px; }
.ui-slider:focus { outline: none; }
.ui-slider:disabled { opacity: var(--disabled-opacity); cursor: not-allowed; }

.ui-slider::-webkit-slider-runnable-track {
  height: var(--ui-slider-track); border-radius: var(--radius-pill);
  background: linear-gradient(to right, var(--accent) 0 var(--ui-slider-fill, 0%), var(--line-strong) var(--ui-slider-fill, 0%) 100%);
}
.ui-slider::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none; width: var(--ui-slider-thumb); height: var(--ui-slider-thumb);
  margin-top: calc((var(--ui-slider-track) - var(--ui-slider-thumb)) / 2);
  border: 2px solid var(--accent); border-radius: var(--radius-pill); background: var(--surface-raised); box-shadow: var(--shadow-xs);
  transition: box-shadow var(--duration-fast);
}
.ui-slider:hover::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--accent-soft); }
.ui-slider:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--focus); }
.ui-slider:disabled::-webkit-slider-thumb { border-color: var(--faint); box-shadow: none; }

.ui-slider::-moz-range-track { height: var(--ui-slider-track); border-radius: var(--radius-pill); background: var(--line-strong); }
.ui-slider::-moz-range-progress { height: var(--ui-slider-track); border-radius: var(--radius-pill); background: var(--accent); }
.ui-slider::-moz-range-thumb {
  box-sizing: border-box; width: var(--ui-slider-thumb); height: var(--ui-slider-thumb);
  border: 2px solid var(--accent); border-radius: var(--radius-pill); background: var(--surface-raised); box-shadow: var(--shadow-xs);
  transition: box-shadow var(--duration-fast);
}
.ui-slider:hover::-moz-range-thumb { box-shadow: 0 0 0 4px var(--accent-soft); }
.ui-slider:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--focus); }
.ui-slider:disabled::-moz-range-thumb { border-color: var(--faint); box-shadow: none; }
.ui-slider:disabled::-moz-range-progress { background: var(--faint); }
@media (prefers-reduced-motion: reduce) {
  .ui-slider::-webkit-slider-thumb { transition: none; }
  .ui-slider::-moz-range-thumb { transition: none; }
}

/* ui/controls/spinner/spinner.css */
/* 도는 표시: <span class="ui-spinner" role="status" aria-label="불러오는 중"></span>. 길이를 모르는 기다림에 쓴다(길이를 알면 .ui-progress).
   글자색을 따른다(단추 안에 넣으면 단추 글자색). 크기 data-size="sm"(12px)·"lg"(24px), 없으면 아이콘 크기.
   움직임을 줄인 설정이면 돌지 않고 천천히 흐려졌다 짙어진다(멈춘 고리는 끝난 것처럼 보이므로). */
.ui-spinner {
  display: inline-block; flex: none; width: var(--icon-size); height: var(--icon-size); vertical-align: middle;
  border: 2px solid currentColor; border-right-color: transparent; border-radius: var(--radius-pill);
  color: var(--accent); opacity: .9;
  animation: ui-spinner-turn .8s linear infinite;
}
.ui-spinner[data-tone="muted"] { color: var(--muted); }
.ui-spinner[data-tone="inherit"] { color: inherit; }
.ui-spinner[data-size="sm"] { width: 12px; height: 12px; border-width: 1.5px; }
.ui-spinner[data-size="lg"] { width: 24px; height: 24px; border-width: 2.5px; }
@keyframes ui-spinner-turn { to { transform: rotate(1turn); } }
@keyframes ui-spinner-fade { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .ui-spinner { animation: ui-spinner-fade 2s ease-in-out infinite; } }

/* ui/controls/switch/switch.css */
/* 켜고 끄기: <label class="ui-switch"><input type="checkbox" role="switch">글</label>. 바로 적용되는 설정에 쓴다(폼으로 모아 보낼 값은 .ui-check).
   글을 앞에 두려면 data-side="end"(단추가 끝으로 간다), 설명은 <span class="ui-switch__hint">, 작은 크기 data-size="sm". */
.ui-switch { display: inline-grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--space-2); align-items: start; min-width: 0; color: var(--ink); font-size: var(--text-md); line-height: var(--leading-md); cursor: pointer; }
.ui-switch > input {
  appearance: none; -webkit-appearance: none; position: relative; flex: none; width: 32px; height: 18px; margin: 1px 0 0;
  border: 1px solid var(--line-strong); border-radius: var(--radius-pill); background: var(--surface-sunken); cursor: inherit;
  transition: background-color var(--duration-fast), border-color var(--duration-fast);
}
.ui-switch > input::before {
  content: ""; position: absolute; top: 1px; left: 1px; width: 14px; height: 14px; border-radius: var(--radius-pill);
  background: var(--surface-raised); box-shadow: var(--shadow-xs); border: 1px solid var(--line-strong);
  transition: transform var(--duration-fast);
}
.ui-switch > input:checked { background: var(--accent); border-color: var(--accent); }
.ui-switch > input:checked::before { transform: translateX(14px); border-color: transparent; background: var(--on-accent); }
.ui-switch:hover > input:not(:disabled):not(:checked) { border-color: var(--faint); }
.ui-switch > input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.ui-switch > input:disabled { opacity: var(--disabled-opacity); }
.ui-switch:has(> input:disabled) { color: var(--muted); cursor: not-allowed; }
.ui-switch__hint { grid-column: 2; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); overflow-wrap: anywhere; }
.ui-switch[data-side="end"] { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
.ui-switch[data-side="end"] > input { grid-column: 2; grid-row: 1; }
.ui-switch[data-side="end"] > .ui-switch__hint { grid-column: 1; }
.ui-switch[data-size="sm"] { font-size: var(--text-sm); line-height: var(--leading-sm); column-gap: 6px; }
.ui-switch[data-size="sm"] > input { width: 26px; height: 14px; margin-top: 1px; }
.ui-switch[data-size="sm"] > input::before { width: 10px; height: 10px; }
.ui-switch[data-size="sm"] > input:checked::before { transform: translateX(12px); }
@media (prefers-reduced-motion: reduce) { .ui-switch > input, .ui-switch > input::before { transition: none; } }

/* ui/controls/tooltip/tooltip.css */
/* 풍선 도움말: <ui-tooltip text="…">대상</ui-tooltip>. 감싸개는 상자를 만들지 않아(display: contents) 대상의 배치를 바꾸지 않는다.
   풍선은 화면 기준 자리(position: fixed)에 놓이고, 자리는 tooltip.js가 정한다. 풍선이 위에 있으면 data-side="top", 아래면 "bottom". */
ui-tooltip { display: contents; }
ui-tooltip > .ui-tooltip__bubble {
  position: fixed; inset: auto; left: 0; top: 0; z-index: var(--layer-pop); margin: 0; overflow: visible;
  width: max-content; max-width: min(280px, calc(100vw - 16px)); padding: 4px var(--space-2);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm); background: var(--surface-raised); color: var(--ink);
  box-shadow: var(--shadow-pop); font-size: var(--text-sm); line-height: var(--leading-sm); font-weight: 400; text-align: start;
  white-space: normal; overflow-wrap: anywhere; pointer-events: auto;
  animation: ui-tooltip-in var(--duration-fast) ease-out;
}
ui-tooltip > .ui-tooltip__bubble[hidden] { display: none; }
@keyframes ui-tooltip-in { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { ui-tooltip > .ui-tooltip__bubble { animation: none; } }

/* ui/data/empty/empty.css */
/* 빈 상태: <div class="ui-empty" data-tone="neutral|info|warning|danger" data-size="compact">.
   안에 .ui-empty__icon(아이콘), .ui-empty__title(제목), .ui-empty__text(설명), .ui-empty__actions(단추들)을 필요한 것만 넣는다.
   목록·표·나무가 비었을 때 부품도 data-size="compact"로 쓴다. 불러오기 실패는 data-tone="danger"에 role="alert"를 단다. */
.ui-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2);
  min-width: 0; padding: var(--space-8) var(--space-4); text-align: center; color: var(--muted);
}
.ui-empty__icon {
  display: inline-flex; align-items: center; justify-content: center; width: var(--space-8); height: var(--space-8);
  border-radius: var(--radius-pill); background: var(--surface-sunken); color: var(--muted);
}
.ui-empty__icon .ui-icon { width: 18px; height: 18px; }
.ui-empty__title { margin: 0; color: var(--ink); font-size: var(--text-lg); line-height: var(--leading-lg); font-weight: 600; overflow-wrap: anywhere; }
.ui-empty__text { margin: 0; max-width: 46ch; font-size: var(--text-md); line-height: var(--leading-md); overflow-wrap: anywhere; }
.ui-empty__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin-top: var(--space-2); }
.ui-empty[data-tone="info"] .ui-empty__icon { background: var(--accent-soft); color: var(--accent-ink); }
.ui-empty[data-tone="warning"] .ui-empty__icon { background: var(--live-soft); color: var(--live); }
.ui-empty[data-tone="danger"] .ui-empty__icon { background: var(--danger-soft); color: var(--danger); }
.ui-empty[data-tone="danger"] .ui-empty__title { color: var(--danger); }
.ui-empty[data-size="compact"] { gap: var(--space-1); padding: var(--space-4) var(--space-3); }
.ui-empty[data-size="compact"] .ui-empty__title { font-size: var(--text-md); line-height: var(--leading-md); }
.ui-empty[data-size="compact"] .ui-empty__text { font-size: var(--text-sm); line-height: var(--leading-sm); }
.ui-empty[data-size="compact"] .ui-empty__icon { width: var(--control-height-sm); height: var(--control-height-sm); }
.ui-empty[data-size="compact"] .ui-empty__icon .ui-icon { width: var(--icon-size); height: var(--icon-size); }
.ui-empty[hidden] { display: none; }

/* ui/data/grid/grid.css */
/* 표: <ui-grid>. 스크롤 상자(.ui-grid__viewport, role="grid") 안에 붙어 있는 머리줄(.ui-grid__head)과 줄(.ui-grid__row)이 있다.
   열 배치는 부품이 --ui-grid-columns(grid-template-columns)와 --ui-grid-min(가장 작은 표 너비)으로 넘긴다. 좁으면 가로로 스크롤한다.
   200줄이 넘으면 가상 스크롤(data-virtual)이라 줄 높이가 --ui-rows-height로 고정된다. 표에 높이를 준다. */
ui-grid { display: flex; flex-direction: column; min-width: 0; min-height: 0; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); overflow: hidden; }
ui-grid:has(> .ui-grid__viewport:focus-visible) { border-color: var(--focus); box-shadow: 0 0 0 1px var(--focus); }
ui-grid .ui-grid__viewport { flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; }
ui-grid .ui-grid__viewport:focus-visible { outline: none; }
ui-grid .ui-grid__row {
  display: grid; grid-template-columns: var(--ui-grid-columns); width: max(100%, var(--ui-grid-min));
  min-height: var(--row-height); align-items: stretch;
}
ui-grid .ui-grid__head { position: sticky; top: 0; z-index: 1; width: max(100%, var(--ui-grid-min)); background: var(--surface-sunken); border-bottom: 1px solid var(--line); }
ui-grid .ui-grid__header {
  position: relative; display: flex; align-items: center; min-width: 0; padding: 0 var(--space-3);
  color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); font-weight: 600;
}
ui-grid .ui-grid__header[data-align="center"] { justify-content: center; }
ui-grid .ui-grid__header[data-align="end"] { justify-content: flex-end; }
ui-grid .ui-grid__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ui-grid .ui-grid__sort {
  display: inline-flex; align-items: center; gap: var(--space-1); min-width: 0; max-width: 100%; height: 100%;
  margin: 0; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer;
}
ui-grid .ui-grid__sort:hover { color: var(--ink); }
ui-grid .ui-grid__sort:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
ui-grid .ui-grid__sort-mark { display: inline-flex; flex: none; opacity: 0; transition: opacity var(--duration-fast); }
ui-grid .ui-grid__sort:hover .ui-grid__sort-mark { opacity: .5; }
ui-grid .ui-grid__sort-mark[data-on] { opacity: 1; color: var(--accent-ink); }
ui-grid .ui-grid__resize {
  position: absolute; top: 0; right: -4px; bottom: 0; z-index: 1; width: 8px; cursor: col-resize; touch-action: none;
}
ui-grid .ui-grid__resize::after { content: ""; position: absolute; top: 25%; bottom: 25%; left: 3.5px; width: 1px; background: var(--line-strong); }
ui-grid .ui-grid__header:last-child .ui-grid__resize { right: 0; }
ui-grid .ui-grid__resize:hover::after, ui-grid .ui-grid__resize:focus-visible::after, ui-grid[data-resizing] .ui-grid__resize:active::after { top: 0; bottom: 0; left: 2.5px; width: 3px; background: var(--accent); }
ui-grid .ui-grid__resize:focus-visible { outline: none; }
ui-grid[data-resizing] { cursor: col-resize; user-select: none; }
ui-grid .ui-grid__body { position: relative; }
ui-grid .ui-grid__window { will-change: transform; }
ui-grid .ui-grid__body .ui-grid__row { border-bottom: 1px solid var(--line); color: var(--ink); cursor: default; transition: background-color var(--duration-fast); }
ui-grid .ui-grid__viewport[data-virtual] .ui-grid__body .ui-grid__row { height: var(--ui-rows-height); min-height: 0; }
ui-grid .ui-grid__body .ui-grid__row:hover { background: var(--surface-sunken); }
ui-grid .ui-grid__body .ui-grid__row[aria-selected="true"] { background: var(--selection); }
ui-grid .ui-grid__viewport:focus-visible .ui-grid__body .ui-grid__row[data-active] { box-shadow: inset 0 0 0 2px var(--focus); }
ui-grid .ui-grid__cell {
  display: flex; align-items: center; min-width: 0; padding: 0 var(--space-3); overflow: hidden;
}
ui-grid .ui-grid__value { display: block; min-width: 0; max-width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
ui-grid .ui-grid__cell[data-align="center"] { justify-content: center; text-align: center; }
ui-grid .ui-grid__cell[data-align="end"] { justify-content: flex-end; text-align: end; font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) {
  ui-grid .ui-grid__body .ui-grid__row, ui-grid .ui-grid__sort-mark { transition: none; }
}

/* ui/data/list/list.css */
/* 목록: <ui-list>. 스크롤 상자(.ui-list__viewport)가 role="listbox"이고 줄은 .ui-list__option이다.
   200줄이 넘으면 가상 스크롤(data-virtual)이라 줄 높이가 --ui-rows-height(row-height 속성 또는 --row-height)로 고정된다. 목록에 높이를 준다. */
ui-list { display: flex; flex-direction: column; min-width: 0; min-height: 0; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); overflow: hidden; }
ui-list .ui-list__viewport { flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; }
ui-list .ui-list__viewport:focus-visible { outline: none; }
ui-list:has(> .ui-list__viewport:focus-visible) { border-color: var(--focus); box-shadow: 0 0 0 1px var(--focus); }
ui-list .ui-list__body { position: relative; }
ui-list .ui-list__window { will-change: transform; }
ui-list .ui-list__option {
  display: flex; align-items: center; gap: var(--space-2); min-width: 0; min-height: var(--row-height);
  padding: 0 var(--space-3);
  color: var(--ink); cursor: default; user-select: none;
  transition: background-color var(--duration-fast);
}
ui-list .ui-list__viewport[data-virtual] .ui-list__option { height: var(--ui-rows-height); min-height: 0; overflow: hidden; }
ui-list .ui-list__option:hover { background: var(--surface-sunken); }
ui-list .ui-list__option[aria-selected="true"] { background: var(--selection); }
ui-list .ui-list__viewport:focus-visible .ui-list__option[data-active] { box-shadow: inset 0 0 0 2px var(--focus); }
ui-list .ui-list__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) { ui-list .ui-list__option { transition: none; } }

/* ui/data/log/log.css */
/* 로그: <ui-log>. 스크롤 상자(.ui-log__viewport, role="log") 안에 200줄 묶음(.ui-log__chunk)이 쌓이고, 위로 올려 보는 중에 새 줄이 오면
   오른쪽 아래에 「새 줄 N개」 단추(.ui-log__jump)가 뜬다. 줄 갈래는 data-level(info·warn·error). 높이를 주면 그 안에서 스크롤한다. */
ui-log { position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-sunken); overflow: hidden; }
ui-log:has(> .ui-log__viewport:focus-visible) { border-color: var(--focus); box-shadow: 0 0 0 1px var(--focus); }
ui-log .ui-log__viewport { flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; }
ui-log .ui-log__viewport:focus-visible { outline: none; }
ui-log .ui-log__content { padding: var(--space-2) 0; font-family: var(--font-mono); font-size: var(--text-sm); line-height: var(--leading-sm); }
ui-log .ui-log__chunk { content-visibility: auto; contain-intrinsic-size: auto calc(var(--ui-log-lines, 200) * var(--leading-sm)); }
ui-log .ui-log__line { display: flex; gap: var(--space-2); min-width: 0; padding: 0 var(--space-3); color: var(--ink); border-left: 2px solid transparent; }
ui-log .ui-log__line[data-level="warn"] { color: var(--live); background: var(--live-soft); border-left-color: var(--live); }
ui-log .ui-log__line[data-level="error"] { color: var(--danger); background: var(--danger-soft); border-left-color: var(--danger); }
ui-log .ui-log__time { flex: none; color: var(--faint); font-variant-numeric: tabular-nums; user-select: none; }
ui-log .ui-log__level { flex: none; font-weight: 600; user-select: none; }
ui-log .ui-log__text { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
ui-log .ui-log__dropped, ui-log .ui-log__none { padding: var(--space-1) var(--space-3); color: var(--muted); font-family: var(--font-sans); }
ui-log .ui-log__dropped { border-bottom: 1px dashed var(--line-strong); margin-bottom: var(--space-1); }
ui-log .ui-log__jump {
  position: absolute; right: var(--space-3); bottom: var(--space-3); z-index: 1;
  display: inline-flex; align-items: center; gap: var(--space-1); height: var(--control-height-sm); padding: 0 var(--space-3);
  border: 1px solid var(--accent); border-radius: var(--radius-pill); background: var(--accent); color: var(--on-accent);
  font: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer; box-shadow: var(--shadow-pop);
}
ui-log .ui-log__jump:hover { filter: brightness(1.1); }

/* ui/data/notice/notice.css */
/* 알림 띠: <div class="ui-notice" data-tone="neutral|info|warning|danger" role="status|alert">. 글 사이나 판 위에 놓는 한 줄 띠다.
   안에 아이콘(.ui-icon), .ui-notice__body(.ui-notice__title 굵은 첫머리 + 글), .ui-notice__actions(단추), .ui-notice__close(닫기)를 둔다.
   급한 오류는 role="alert", 그 밖은 role="status". 좁으면 단추가 아래 줄로 내려간다. */
.ui-notice {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-2) var(--space-3); min-width: 0;
  padding: var(--space-2) var(--space-3); border: 1px solid var(--line); border-left-width: 3px; border-radius: var(--radius-md);
  background: var(--surface-sunken); color: var(--ink); font-size: var(--text-md); line-height: var(--leading-md);
}
.ui-notice > .ui-icon { margin-top: 2px; color: var(--muted); }
.ui-notice__body { flex: 1 1 180px; min-width: 0; overflow-wrap: anywhere; }
.ui-notice__title { font-weight: 600; margin-right: var(--space-1); }
.ui-notice__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-left: auto; }
.ui-notice__close {
  display: inline-flex; align-items: center; justify-content: center; flex: none; width: var(--control-height-sm); height: var(--control-height-sm);
  margin: -3px calc(var(--space-1) * -1) -3px 0; padding: 0; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--muted); cursor: pointer;
}
.ui-notice__close:hover { background: var(--surface); color: var(--ink); }
.ui-notice[data-tone="info"] { background: var(--accent-soft); border-color: var(--accent); }
.ui-notice[data-tone="info"] > .ui-icon { color: var(--accent-ink); }
.ui-notice[data-tone="warning"] { background: var(--live-soft); border-color: var(--live); }
.ui-notice[data-tone="warning"] > .ui-icon { color: var(--live); }
.ui-notice[data-tone="danger"] { background: var(--danger-soft); border-color: var(--danger); }
.ui-notice[data-tone="danger"] > .ui-icon, .ui-notice[data-tone="danger"] .ui-notice__title { color: var(--danger); }
.ui-notice[hidden] { display: none; }

/* ui/data/property/property.css */
/* 속성 판: <ui-property>. 묶음(.ui-property__group, 제목이 있으면 <details>)마다 이름–값 줄(.ui-property__row)이 두 칸으로 선다.
   폭이 좁으면(260px 아래) 이름이 값 위로 올라간다. 글쓴이 자식도 같은 클래스로 쓴다. */
ui-property { display: block; min-width: 0; container-type: inline-size; color: var(--ink); }
ui-property .ui-property__group { display: block; min-width: 0; border-bottom: 1px solid var(--line); }
ui-property .ui-property__group:last-child { border-bottom: 0; }
ui-property .ui-property__title {
  display: flex; align-items: center; gap: var(--space-1); min-height: var(--row-height); padding: 0 var(--space-1);
  color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); font-weight: 600; cursor: pointer; list-style: none; user-select: none;
  border-radius: var(--radius-sm);
}
ui-property .ui-property__title::-webkit-details-marker { display: none; }
ui-property .ui-property__title:hover { color: var(--ink); background: var(--surface-sunken); }
ui-property .ui-property__title .ui-icon { transition: transform var(--duration-fast); }
ui-property .ui-property__group[open] > .ui-property__title .ui-icon { transform: rotate(90deg); }
ui-property .ui-property__title-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ui-property .ui-property__rows { display: grid; gap: var(--space-1); margin: 0; padding: var(--space-1) 0 var(--space-2); }
ui-property .ui-property__row {
  display: grid; grid-template-columns: minmax(72px, 38%) minmax(0, 1fr); gap: var(--space-1) var(--space-3);
  align-items: start; min-width: 0; padding: var(--space-1) var(--space-1);
}
ui-property .ui-property__label {
  margin: 0; min-width: 0; padding-top: 2px; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); overflow-wrap: anywhere;
}
ui-property .ui-property__value { display: grid; gap: 2px; margin: 0; min-width: 0; overflow-wrap: anywhere; }
ui-property .ui-property__value[data-mono] { font-family: var(--font-mono); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
ui-property .ui-property__hint { color: var(--faint); font-family: var(--font-sans); font-size: var(--text-sm); line-height: var(--leading-sm); }
ui-property .ui-property__none { margin: 0; padding: var(--space-2) var(--space-1); font-size: var(--text-sm); }
@container (max-width: 260px) {
  ui-property .ui-property__row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  ui-property .ui-property__label { padding-top: 0; }
}
@media (prefers-reduced-motion: reduce) { ui-property .ui-property__title .ui-icon { transition: none; } }

/* ui/data/tree/tree.css */
/* 나무: <ui-tree>. .ui-tree__box(role="tree") 안에 펼친 차례대로 .ui-tree__row(role="treeitem")가 평평하게 놓이고,
   들여쓰기는 부품이 넘기는 --ui-tree-depth(1부터)로 정한다. 높이를 주면 그 안에서 스크롤한다. */
ui-tree { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
ui-tree .ui-tree__box { flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; padding: var(--space-1) 0; border-radius: var(--radius-md); }
ui-tree .ui-tree__box:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--focus); }
ui-tree .ui-tree__row {
  display: flex; align-items: center; gap: var(--space-1); min-width: 0; height: var(--row-height);
  padding: 0 var(--space-2) 0 calc(var(--space-1) + (var(--ui-tree-depth, 1) - 1) * var(--indent));
  border-radius: var(--radius-sm); color: var(--ink); cursor: default; user-select: none;
  transition: background-color var(--duration-fast);
}
ui-tree .ui-tree__row:hover { background: var(--surface-sunken); }
ui-tree .ui-tree__row[aria-selected="true"] { background: var(--selection); }
ui-tree .ui-tree__box:focus-visible .ui-tree__row[data-active] { box-shadow: inset 0 0 0 2px var(--focus); }
ui-tree .ui-tree__row[data-loading] { cursor: progress; }
ui-tree .ui-tree__row[data-loading]:hover { background: transparent; }
ui-tree .ui-tree__twisty { display: inline-flex; flex: none; width: var(--icon-size); height: var(--icon-size); color: var(--muted); cursor: pointer; }
ui-tree .ui-tree__icon { display: inline-flex; flex: none; color: var(--muted); }
ui-tree .ui-tree__row[aria-selected="true"] .ui-tree__icon { color: var(--accent-ink); }
ui-tree .ui-tree__image { flex: none; width: calc(var(--icon-size) + 4px); height: calc(var(--icon-size) + 4px); object-fit: cover; border-radius: 2px; background: var(--surface-sunken); }
ui-tree .ui-tree__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ui-tree .ui-tree__meta { flex: none; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); font-variant-numeric: tabular-nums; }
/* 흐린 줄(dim): 이름과 그림만 흐리게 해 알약·덧말은 그대로 읽힌다. */
ui-tree .ui-tree__row[data-dim] .ui-tree__label, ui-tree .ui-tree__row[data-dim] .ui-tree__image, ui-tree .ui-tree__row[data-dim] .ui-tree__icon { opacity: .55; }
ui-tree .ui-tree__row[draggable="true"] { cursor: grab; }
ui-tree .ui-tree__match { background: var(--highlight); color: inherit; border-radius: 2px; }
ui-tree .ui-tree__badge {
  flex: none; min-width: 20px; padding: 0 var(--space-1); border-radius: var(--radius-pill);
  background: var(--surface-sunken); color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); text-align: center; font-variant-numeric: tabular-nums;
}
ui-tree .ui-tree__badge[data-tone="accent"] { background: var(--accent-soft); color: var(--accent-ink); }
@media (prefers-reduced-motion: reduce) { ui-tree .ui-tree__row { transition: none; } }

/* ui/layout/app-frame/app-frame.css */
/* 앱 틀: <ui-app-frame viewport open-region>. 글쓴이 자식의 slot 속성이 영역이다(menubar·toolbar·start·main·end·status, slot이 없으면 main).
   빈 영역은 auto 줄·칸이라 접힌다. 좁은 폭(data-narrow, app-frame.js가 단다)에서는 옆 칸을 숨기고, 연 옆 칸(open-region)은
   본문 자리 위에 서랍으로 겹친다(.ui-app-frame__scrim이 그 아래 배경). 옆 칸의 테두리는 틀이 긋는다(<ui-dock>는 제 테두리가 있어 뺀다). */
ui-app-frame {
  position: relative; display: grid; box-sizing: border-box; height: 100%; min-width: 0; min-height: 0; overflow: hidden;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  grid-template-areas: "menubar menubar menubar" "toolbar toolbar toolbar" "start main end" "status status status";
  background: var(--bg); color: var(--ink);
}
ui-app-frame[viewport] { height: 100vh; height: 100dvh; }
ui-app-frame > [slot="menubar"] {
  grid-area: menubar; display: flex; align-items: center; gap: var(--space-2); min-width: 0; min-height: var(--bar-height);
  padding: 0 var(--space-2); background: var(--surface); border-bottom: 1px solid var(--line);
}
ui-app-frame > [slot="toolbar"] { grid-area: toolbar; min-width: 0; background: var(--surface); border-bottom: 1px solid var(--line); }
ui-app-frame > [slot="start"] { grid-area: start; min-width: 0; min-height: 0; overflow: auto; }
ui-app-frame > [slot="end"] { grid-area: end; min-width: 0; min-height: 0; overflow: auto; }
ui-app-frame > [slot="start"]:not(ui-dock) { background: var(--surface); border-inline-end: 1px solid var(--line); }
ui-app-frame > [slot="end"]:not(ui-dock) { background: var(--surface); border-inline-start: 1px solid var(--line); }
ui-app-frame > [slot="main"], ui-app-frame > :not([slot]) { grid-area: main; min-width: 0; min-height: 0; overflow: auto; }
ui-app-frame > [slot="status"] {
  grid-area: status; display: flex; align-items: center; gap: var(--space-3); min-width: 0; min-height: var(--status-height);
  padding: 0 var(--space-3); overflow: hidden; white-space: nowrap; background: var(--surface); border-top: 1px solid var(--line);
  color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm);
}
ui-app-frame > .ui-app-frame__scrim { grid-area: main; z-index: 1; overflow: hidden; background: var(--scrim); animation: ui-app-frame-fade var(--duration-fast) ease-out; }

/* 좁은 폭: 한 칸 격자, 옆 칸은 숨기고 연 쪽만 본문 위의 서랍으로 */
ui-app-frame[data-narrow] {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "menubar" "toolbar" "main" "status";
}
ui-app-frame[data-narrow]:not([open-region="start"]) > [slot="start"],
ui-app-frame[data-narrow]:not([open-region="end"]) > [slot="end"] { display: none; }
ui-app-frame[data-narrow][open-region="start"] > [slot="start"],
ui-app-frame[data-narrow][open-region="end"] > [slot="end"] {
  grid-area: main; z-index: 2; width: min(320px, 85%); max-width: none; box-shadow: var(--shadow-pop);
  animation: ui-app-frame-drawer var(--duration-slow) ease-out;
}
ui-app-frame[data-narrow] > ui-dock[collapsed][slot] { width: var(--bar-height); }
ui-app-frame[data-narrow][open-region="start"] > [slot="start"] { justify-self: start; }
ui-app-frame[data-narrow][open-region="end"] > [slot="end"] { justify-self: end; animation-name: ui-app-frame-drawer-end; }
ui-app-frame:not([data-narrow]) .ui-app-frame__narrow-only, ui-app-frame[data-narrow] .ui-app-frame__wide-only { display: none; }

@keyframes ui-app-frame-fade { from { opacity: 0; } }
@keyframes ui-app-frame-drawer { from { transform: translateX(-16px); opacity: 0; } }
@keyframes ui-app-frame-drawer-end { from { transform: translateX(16px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  ui-app-frame > .ui-app-frame__scrim,
  ui-app-frame[data-narrow][open-region="start"] > [slot="start"],
  ui-app-frame[data-narrow][open-region="end"] > [slot="end"] { animation: none; }
}

/* ui/layout/board/board.css */
/* 카드 판: <ui-board>. 격자 바탕 위에 카드(data-id가 있는 글쓴이 자식)를 판이 정한 transform으로 놓고, 그 아래에 잇는 선을 긋는다.
   판은 부모가 준 크기를 채우고 넘치는 것을 자른다. 카드 모양은 .ui-board-card(머리·제목·덧말·본문, 고름 테두리)와 그 변형(그림 카드 media, 타일 카드 tile)을 쓴다. */
ui-board {
  position: relative; display: block; min-width: 0; min-height: 0; overflow: hidden; touch-action: none;
  /* 판은 제 쌓임 층이다: 카드의 z-index(고름·초점·끌기)는 판 안에서만 겨루고, 판 밖의 떠 있는 창을 가리지 않는다. */
  isolation: isolate;
  /* 판 바탕은 카드(--surface)와 갈리게 들어간 면보다 한 단계 밝힌 색이다(어두움에서는 카드보다 밝고, 밝음에서는 흰 카드보다 어둡다). */
  background: color-mix(in srgb, var(--surface-sunken) 60%, var(--line-strong)); cursor: grab; outline: none;
  -webkit-user-select: none; user-select: none;
}
/* 판 위의 글은 끌기와 겹치지 않게 고르지 않는다. 입력 칸만 글을 고른다. */
ui-board input, ui-board textarea, ui-board [contenteditable] { -webkit-user-select: text; user-select: text; }
ui-board:focus-visible { box-shadow: inset 0 0 0 2px var(--focus); }
ui-board[data-panning] { cursor: grabbing; }

/* 바탕은 격자다: 한 칸(--board-step, 확대를 따라 커진다)마다 옅은 가는 선, 다섯 칸마다 조금 진한 선을 긋고, 판을 옮기면 함께 움직인다. */
ui-board > .ui-board__grid {
  position: absolute; inset: 0; pointer-events: none;
  --board-line: color-mix(in srgb, var(--line-strong) 45%, transparent);
  --board-line-major: color-mix(in srgb, var(--line-strong) 90%, transparent);
  background-image:
    linear-gradient(to right, var(--board-line-major) 1px, transparent 1px),
    linear-gradient(to bottom, var(--board-line-major) 1px, transparent 1px),
    linear-gradient(to right, var(--board-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--board-line) 1px, transparent 1px);
  background-size:
    calc(var(--board-step) * 5) calc(var(--board-step) * 5), calc(var(--board-step) * 5) calc(var(--board-step) * 5),
    var(--board-step) var(--board-step), var(--board-step) var(--board-step);
  background-position: var(--board-x) var(--board-y);
}
ui-board > .ui-board__links { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
ui-board > .ui-board__links path { fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; }
ui-board > .ui-board__links path[data-draft] { stroke: var(--accent); stroke-dasharray: 6 5; }

ui-board > [data-id] { position: absolute; left: 0; top: 0; z-index: 1; transform-origin: 0 0; cursor: default; }
ui-board > [data-id][data-selected] { z-index: 2; }
/* 초점이 안에 있는 카드(설정 칸·입력을 연 카드)는 고른 카드보다도 위로 올라와 이웃 카드(사진 등)에 가리지 않는다. */
ui-board > [data-id]:focus-within { z-index: 3; }
ui-board > [data-id][data-dragging] { z-index: 4; cursor: grabbing; }

ui-board > .ui-board__tools {
  position: absolute; top: var(--space-3); right: var(--space-3); z-index: 5; display: flex; align-items: center; gap: var(--space-1);
  padding: var(--space-1); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow-xs);
  cursor: default;
}
ui-board .ui-board__zoom { min-width: 3.5em; color: var(--muted); font-size: var(--text-sm); text-align: center; font-variant-numeric: tabular-nums; }

/* 판 카드의 모양(쓰지 않아도 된다). 크기는 카드 요소가 정한다(판이 크기를 정하지 않는다).
   카드 안에서 바로 고치는 칸은 테가 없는 .ui-board-card__input(제목은 data-role="title"), 올리거나 고른 카드에만 보이는 단추 줄은 .ui-board-card__actions다(카드 오른쪽 아래, 그림 카드도 같은 자리).
   카드 바깥 가장자리에서 안쪽 내용과 떠 있는 알약까지의 거리는 모든 갈래가 --board-card-inset 하나다(이름 알약이 카드마다 같은 자리에 선다, 테가 있으면 테 두께를 뺀 안쪽 여백). */
.ui-board-card {
  --board-card-inset: var(--space-3);
  box-sizing: border-box; display: flex; flex-direction: column; gap: var(--space-1); min-width: 0;
  padding: calc(var(--board-card-inset) - 1px); border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  background: var(--surface); color: var(--ink); box-shadow: var(--shadow-xs);
}
.ui-board-card:hover { border-color: var(--muted); }
.ui-board-card[data-selected] { border-color: var(--accent); }
.ui-board-card:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ui-board-card__head { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.ui-board-card__title {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-md); line-height: var(--leading-md); font-weight: 600;
}
/* 카드 섬네일: 카드 폭을 채우는 16:9 그림(넘치는 곳은 잘라 채운다). 끌기를 막지 않게 그림 끌기는 꺼 둔다(draggable="false"). */
.ui-board-card__thumb {
  display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius-sm);
  background: var(--surface-sunken); pointer-events: none;
}
.ui-board-card[data-dropping] { border-color: var(--accent); border-style: dashed; }
/* 카드 그림: 카드의 남은 자리를 채우고 그림 전체를 보인다(자르지 않는다). 끌기를 막지 않게 그림 끌기는 꺼 둔다. */
.ui-board-card__image {
  display: block; flex: 1 1 auto; width: 100%; min-height: 0; object-fit: contain; border-radius: var(--radius-sm);
  background: var(--surface-sunken); pointer-events: none;
}
/* 그림 카드(data-variant="media"): 테와 안쪽 여백 없이 그림이 카드 전체를 채우고, 머리 줄과 단추 줄은 그림 위에 알약으로 뜬다.
   카드 크기는 화면이 그림 비율로 정한다. 머리 알약은 다른 카드의 이름 알약과 같은 모양이고(아래 「이름 알약」), 단추 줄은 화면 테마와 관계없이 어두운 미디어 바탕 색을 쓴다. */
.ui-board-card[data-variant="media"] {
  padding: 0; border: 0; background: var(--media-backdrop);
  box-shadow: 0 0 0 1px var(--line-strong), var(--shadow-pop);
}
.ui-board-card[data-variant="media"]:hover { box-shadow: 0 0 0 1px var(--muted), var(--shadow-pop); }
.ui-board-card[data-variant="media"][data-selected] { box-shadow: 0 0 0 2px var(--accent), var(--shadow-pop); }
.ui-board-card[data-variant="media"]:focus-visible { box-shadow: 0 0 0 2px var(--focus), var(--shadow-pop); }
/* 카드를 자르지 않고(가장자리의 핀 점이 온전히 보이게) 그림만 카드 모서리를 따라 둥글린다. */
.ui-board-card[data-variant="media"] > .ui-board-card__image { position: absolute; inset: 0; height: 100%; border-radius: inherit; background: transparent; }
.ui-board-card[data-variant="media"] > .ui-board-card__actions {
  position: absolute; z-index: 1; border: 1px solid color-mix(in srgb, var(--on-media) 22%, transparent); border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--media-backdrop) 78%, transparent); color: var(--on-media); box-shadow: var(--shadow-xs);
}
.ui-board-card[data-variant="media"] > .ui-board-card__head {
  position: absolute; z-index: 1; top: var(--board-card-inset); left: var(--board-card-inset); max-width: calc(100% - 2 * var(--board-card-inset));
}
.ui-board-card[data-variant="media"] > .ui-board-card__head .ui-board-card__input { field-sizing: content; min-width: 4em; }
.ui-board-card[data-variant="media"] > .ui-board-card__actions { right: var(--board-card-inset); bottom: var(--board-card-inset); margin: 0; padding: 2px; }
.ui-board-card[data-variant="media"] > .ui-board-card__actions .ui-button[data-variant="ghost"] { color: var(--on-media); }
.ui-board-card[data-variant="media"] > .ui-board-card__actions .ui-button[data-variant="danger"] { border-color: transparent; color: var(--danger-on-media); background: transparent; }
/* 타일 카드(data-variant="tile"): 테 없는 판에 섬네일(.ui-board-card__thumb)이 카드 전체를 채우고, 머리 줄은 왼쪽 위, 단추 줄은 오른쪽 위에
   알약으로 늘 떠 있다(단추 하나하나가 둥근 알약). 섬네일이 없으면 가운데에 빈 상태(.ui-empty, 덧말과 등록 단추)를 두고,
   섬네일이 있으면 덧말(.ui-board-card__meta)이 왼쪽 아래 알약이다. 머리 줄과 단추 줄은 한 줄 격자라 단추가 많으면 제목이 줄어든다. 카드 크기는 화면이 정한다. */
.ui-board-card[data-variant="tile"] {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; align-content: start; gap: var(--space-1);
  padding: var(--board-card-inset); border: 0; overflow: hidden; isolation: isolate;
  background: radial-gradient(ellipse at 50% 35%, var(--surface-raised), var(--surface-sunken));
  box-shadow: 0 0 0 1px var(--line-strong), var(--shadow-pop);
}
.ui-board-card[data-variant="tile"]:hover { box-shadow: 0 0 0 1px var(--muted), var(--shadow-pop); }
.ui-board-card[data-variant="tile"][data-selected] { box-shadow: 0 0 0 2px var(--accent), var(--shadow-pop); }
.ui-board-card[data-variant="tile"]:focus-visible { box-shadow: 0 0 0 2px var(--focus), var(--shadow-pop); }
.ui-board-card[data-variant="tile"][data-dropping] { box-shadow: 0 0 0 2px var(--accent), var(--shadow-pop); outline: 2px dashed var(--accent); outline-offset: -6px; }
.ui-board-card[data-variant="tile"] > .ui-board-card__thumb { position: absolute; inset: 0; z-index: 0; height: 100%; aspect-ratio: auto; border-radius: 0; }
.ui-board-card[data-variant="tile"] > .ui-empty { position: absolute; inset: 0; z-index: 1; padding: var(--space-8) var(--space-3) var(--space-3); }
.ui-board-card[data-variant="tile"] > .ui-empty .ui-empty__icon { background: transparent; color: var(--accent-ink); }
.ui-board-card[data-variant="tile"] > .ui-empty .ui-empty__icon .ui-icon { width: 24px; height: 24px; }
.ui-board-card[data-variant="tile"] > .ui-board-card__meta,
.ui-board-card[data-variant="tile"] > .ui-board-card__actions .ui-button {
  border: 1px solid var(--line-strong); border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--surface) 88%, transparent); color: var(--ink); box-shadow: var(--shadow-pop);
}
.ui-board-card[data-variant="tile"] > .ui-board-card__head { position: relative; z-index: 2; grid-column: 1; justify-self: start; max-width: 100%; }
.ui-board-card[data-variant="tile"] > .ui-board-card__meta {
  position: absolute; z-index: 2; left: var(--board-card-inset); bottom: var(--board-card-inset); max-width: calc(100% - 2 * var(--board-card-inset));
}
.ui-board-card[data-variant="tile"] > .ui-board-card__actions { position: relative; z-index: 2; grid-column: 2; grid-row: 1; margin: 0; visibility: visible; }
.ui-board-card[data-variant="tile"] > .ui-board-card__actions .ui-button:hover { background: var(--surface-raised); border-color: var(--muted); }
.ui-board-card[data-variant="tile"] > .ui-board-card__actions .ui-button[data-variant="danger"] {
  border-color: var(--danger); background: color-mix(in srgb, var(--danger-soft) 88%, transparent); color: var(--danger);
}
/* 핀: 카드 가장자리에 붙는 이음 자리. 입력 핀 줄(.ui-board-pins[data-side="in"])은 왼쪽 가장자리, 출력 핀 줄(data-side="out")은 오른쪽 가장자리에
   세로로 서고, 점(.ui-board-pin__dot)이 가장자리에 걸친다. 점 색은 나르는 것의 갈래다: 그림·영상·둘 다(data-kind: image·video·media)는 한 색이라
   이을 수 있는 핀끼리 같은 색이고, 글(text)은 다른 색이다. 무엇을 받는지는 끄는 동안의 data-board-accepts가 보인다.
   끄는 출력 핀을 받을 수 있는 입력 핀은 판이 data-board-accepts를 단다. 핀 줄의 높이는 카드가 --board-pins-top으로 정한다. */
.ui-board-pins {
  position: absolute; z-index: 3; top: var(--board-pins-top, 44px); display: flex; flex-direction: column; gap: var(--space-2);
  pointer-events: none;
}
.ui-board-pins[data-side="in"] { left: 0; align-items: flex-start; }
.ui-board-pins[data-side="out"] { right: 0; align-items: flex-end; }
.ui-board-pin {
  --pin-color: var(--muted);
  display: inline-flex; align-items: center; gap: var(--space-1); max-width: 9rem; pointer-events: auto;
  font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--ink);
}
:is(.ui-board-pin, ui-board > .ui-board__links path):is([data-kind="image"], [data-kind="video"], [data-kind="media"]) { --pin-color: var(--accent); }
:is(.ui-board-pin, ui-board > .ui-board__links path)[data-kind="text"] { --pin-color: var(--done); }
/* 흐름(개발 워커 카드끼리, D-157)은 그림·글과 다른 색이다. */
:is(.ui-board-pin, ui-board > .ui-board__links path)[data-kind="flow"] { --pin-color: var(--live); }
/* 정본 작업본(설정집·스토리·집필 카드끼리, D-158)도 따로 칠한다. */
:is(.ui-board-pin, ui-board > .ui-board__links path)[data-kind="draft"] { --pin-color: var(--focus); }
.ui-board-pins[data-side="in"] .ui-board-pin { transform: translateX(-7px); }
.ui-board-pins[data-side="out"] .ui-board-pin { flex-direction: row-reverse; transform: translateX(7px); cursor: crosshair; }
.ui-board-pin__dot {
  flex: none; width: 14px; height: 14px; box-sizing: border-box; border: 3px solid var(--pin-color); border-radius: 50%; background: var(--surface);
}
.ui-board-pin[data-linked] .ui-board-pin__dot { background: var(--pin-color); cursor: pointer; }
.ui-board-pin[data-linked] .ui-board-pin__dot:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 45%, transparent); }
.ui-board-pin__label {
  min-width: 0; border: 1px solid var(--line-strong); border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--surface) 88%, transparent); box-shadow: var(--shadow-pop);
}
/* 작은 알약(핀 이름표, 타일 카드의 덧말 알약): 한 높이(20px)에 글이 가운데 오고, 좌우 여백은 이름 알약과 같다. 길면 말줄임. */
.ui-board-pin__label,
.ui-board-card[data-variant="tile"] > .ui-board-card__meta {
  box-sizing: border-box; display: block; height: 20px; padding: 0 var(--space-2); line-height: 18px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ui-board-pin[data-optional] .ui-board-pin__label::after { content: " (선택)"; color: var(--muted); }
.ui-board-pin[data-board-accepts] .ui-board-pin__dot { box-shadow: 0 0 0 4px color-mix(in srgb, var(--pin-color) 40%, transparent); }
/* 핀끼리 이은 선(끄는 중인 선도)은 나가는 핀의 갈래 색이다(위 핀 색 규칙을 함께 쓴다). */
ui-board > .ui-board__links path[data-pinned] { stroke: var(--pin-color, var(--accent)); }
ui-board > .ui-board__links path[data-draft][data-kind] { stroke: var(--pin-color); }
/* 파일을 판 위로 끌어 오는 동안(화면이 data-dropping을 단다). */
ui-board[data-dropping] { box-shadow: inset 0 0 0 2px var(--accent); }
.ui-board-card__meta { color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); }
.ui-board-card__body { flex: 1 1 auto; min-height: 0; overflow: hidden; font-size: var(--text-sm); line-height: var(--leading-sm); white-space: pre-wrap; overflow-wrap: anywhere; }
/* 이름 알약: 테가 있는 카드의 이름(.ui-board-card__name)과 그림·타일 카드의 머리 줄이 모두 이 한 모양이다(높이·색·간격·글자).
   바탕은 불투명이라 밝은 그림 위에서도 다른 카드의 알약과 같은 색이다. 높이는 작은 컨트롤 높이라 옆에 놓인 작은 단추와 한 줄에 맞고, 카드의 --board-card-inset 자리에 서므로 카드 갈래가 달라도 같은 곳에 보인다. */
.ui-board-card__name,
.ui-board-card[data-variant="media"] > .ui-board-card__head,
.ui-board-card[data-variant="tile"] > .ui-board-card__head {
  box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--space-1); flex: 0 1 auto; min-width: 0;
  height: var(--control-height-sm); padding: 0 var(--space-2);
  border: 1px solid var(--line-strong); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--ink); box-shadow: var(--shadow-pop);
}
.ui-board-card__name > .ui-board-card__title,
.ui-board-card[data-variant="media"] > .ui-board-card__head :is(.ui-board-card__title, .ui-board-card__input),
.ui-board-card[data-variant="tile"] > .ui-board-card__head .ui-board-card__title {
  font-size: var(--text-sm); line-height: var(--leading-sm); font-weight: 600;
}
/* 이름 글은 글꼴의 위아래 여백(맑은 고딕은 위가 크다) 대신 글자 획(대문자 높이~기준선)으로 알약 가운데에 선다. 줄인 상자 밖으로 나온 획이
   잘리지 않게 세로는 넘침을 두고 가로만 자른다(말줄임은 그대로). */
.ui-board-card__name > .ui-board-card__title,
.ui-board-card[data-variant="media"] > .ui-board-card__head .ui-board-card__title,
.ui-board-card[data-variant="tile"] > .ui-board-card__head .ui-board-card__title {
  text-box: trim-both cap alphabetic; overflow-x: clip; overflow-y: visible;
}
.ui-board-card__handle {
  flex: none; width: 14px; height: 14px; border: 2px solid var(--accent); border-radius: 50%; background: var(--surface); cursor: crosshair;
}
/* 단추 줄은 늘 자리를 차지하고(나타날 때 카드가 커지지 않아 두 번 누르기가 단추에 걸리지 않는다) 올리거나 고르거나 초점이 있을 때만 보인다. */
.ui-board-card__actions { display: flex; gap: var(--space-1); align-self: flex-end; margin-top: auto; visibility: hidden; }
.ui-board-card:hover .ui-board-card__actions, .ui-board-card:focus-within .ui-board-card__actions,
.ui-board-card[data-selected] .ui-board-card__actions { visibility: visible; }
.ui-board-card__input {
  flex: 1 1 auto; min-width: 0; min-height: 0; padding: 0; border: 0; border-radius: var(--radius-sm); outline: none;
  background: transparent; color: inherit; font: inherit; font-size: var(--text-sm); line-height: var(--leading-sm); resize: none;
}
.ui-board-card__input[data-role="title"] { font-size: var(--text-md); line-height: var(--leading-md); font-weight: 600; }
.ui-board-card__input::placeholder { color: var(--faint); }
.ui-board-card__input:focus-visible { box-shadow: var(--focus-ring); }

/* ui/layout/dialog/dialog.css */
/* 대화창: <ui-dialog heading="…" open wide>. 부품은 자리를 차지하지 않고, 그린 <dialog class="ui-dialog__box">가 맨 위 층에 뜬다.
   머리(제목·닫기), 본문(글쓴이 자식, 넘치면 넘김), 단추 줄(본문 안의 [slot="actions"], 아래에 붙음)차례다. 배경은 --scrim. */
ui-dialog { display: contents; }
ui-dialog .ui-dialog__box {
  box-sizing: border-box; width: min(480px, calc(100vw - 32px)); max-width: none; max-height: calc(100dvh - 32px);
  margin: auto; padding: 0; overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface-raised); color: var(--ink); box-shadow: var(--shadow-pop);
  font-family: var(--font-sans); font-size: var(--text-md); line-height: var(--leading-md);
}
ui-dialog[wide] .ui-dialog__box { width: min(880px, calc(100vw - 32px)); }
/* 전체 화면(full): 창 테·여백·둥근 모서리·그림자 없이 화면을 다 쓰고 본문이 남은 높이를 다 쓴다(크게 보기). 머리의 닫기 단추와 Esc로 닫는다. */
ui-dialog[full] .ui-dialog__box { width: 100vw; height: 100dvh; max-height: 100dvh; margin: 0; border: 0; border-radius: 0; box-shadow: none; }
ui-dialog[full] .ui-dialog__box[open] { animation: none; }
ui-dialog .ui-dialog__box[open] { display: flex; flex-direction: column; animation: ui-dialog-in var(--duration-slow) ease-out; }
ui-dialog .ui-dialog__box::backdrop { background: var(--scrim); }
ui-dialog .ui-dialog__head {
  flex: none; display: flex; align-items: flex-start; gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4); border-bottom: 1px solid var(--line);
}
ui-dialog .ui-dialog__title {
  flex: 1 1 auto; min-width: 0; margin: 0; padding-top: 2px;
  font-size: var(--text-lg); line-height: var(--leading-lg); font-weight: 600; overflow-wrap: anywhere;
}
ui-dialog .ui-dialog__body {
  flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4);
}
ui-dialog .ui-dialog__body > [slot="actions"] {
  order: 1; position: sticky; bottom: calc(-1 * var(--space-4)); z-index: 1;
  display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: var(--space-2);
  margin: auto calc(-1 * var(--space-4)) calc(-1 * var(--space-4)); padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--line); background: var(--surface-raised);
}
@keyframes ui-dialog-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
@media (prefers-reduced-motion: reduce) { ui-dialog .ui-dialog__box[open] { animation: none; } }

/* ui/layout/dock/dock.css */
/* 접는 옆 칸: <ui-dock heading side="start|end" collapsed>. 머리 줄(.ui-dock__head)은 부품이 그리고 그 밖의 글쓴이 자식이 본문이다.
   폭은 --ui-dock-width(처음 260px). 접히면 --bar-height 폭의 띠가 되어 세로 제목과 펴기 단추만 보인다. 앱 틀의 start·end 영역에 넣어 쓴다. */
ui-dock {
  display: flex; flex-direction: column; box-sizing: border-box; width: var(--ui-dock-width, 260px); max-width: 100%;
  min-width: 0; min-height: 0; overflow: hidden; background: var(--surface); color: var(--ink);
  border-inline-end: 1px solid var(--line); transition: width var(--duration-slow);
}
ui-dock[side="end"] { border-inline-end: 0; border-inline-start: 1px solid var(--line); }
ui-dock > .ui-dock__head {
  flex: none; display: flex; align-items: center; gap: var(--space-1); height: var(--bar-height);
  padding: 0 var(--space-1) 0 var(--space-3); border-bottom: 1px solid var(--line);
}
ui-dock[side="end"] > .ui-dock__head { flex-direction: row-reverse; padding: 0 var(--space-3) 0 var(--space-1); }
ui-dock .ui-dock__title {
  flex: 1 1 auto; min-width: 0; margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
ui-dock > :not(.ui-dock__head) { flex: 1 1 auto; min-height: 0; overflow: auto; }
ui-dock[collapsed] { width: var(--bar-height); }
ui-dock[collapsed] > :not(.ui-dock__head) { display: none; }
ui-dock[collapsed] > .ui-dock__head, ui-dock[collapsed][side="end"] > .ui-dock__head {
  flex: 1 1 auto; flex-direction: column-reverse; justify-content: flex-end; height: auto; gap: var(--space-2);
  padding: var(--space-1) 0; border-bottom: 0;
}
ui-dock[collapsed] .ui-dock__title { flex: 0 1 auto; writing-mode: vertical-rl; cursor: pointer; }
ui-dock[collapsed] .ui-dock__title:hover { color: var(--ink); }
/* 폭 손잡이(resizable): 안쪽 가장자리의 얇은 띠. 끄는 동안은 폭 바뀜이 손을 바로 따르게 움직임을 끈다. */
ui-dock[resizable] { position: relative; }
ui-dock[data-resizing] { transition: none; user-select: none; }
ui-dock > .ui-dock__grip { position: absolute; top: 0; bottom: 0; right: -3px; z-index: 2; width: 6px; flex: none; overflow: visible; cursor: col-resize; touch-action: none; }
ui-dock[side="end"] > .ui-dock__grip { right: auto; left: -3px; }
ui-dock > .ui-dock__grip:hover, ui-dock > .ui-dock__grip:focus-visible, ui-dock[data-resizing] > .ui-dock__grip { background: var(--accent); outline: none; }
@media (prefers-reduced-motion: reduce) { ui-dock { transition: none; } }

/* ui/layout/menu/menu.css */
/* 메뉴: <ui-menu>는 자리를 차지하지 않고, 열면 판(.ui-menu__panel, role="menu")을 맨 위 층에 띄운다. 하위 메뉴도 판 하나씩이다.
   항목은 아이콘 칸·이름·단축키·하위 표시 네 칸이고, 초점 받은 항목과 하위 메뉴가 열린 항목을 칠한다. 위험 항목은 data-variant="danger". */
ui-menu { display: contents; }
ui-menu .ui-menu__panel {
  position: fixed; inset: auto; left: 0; top: 0; margin: 0; z-index: var(--layer-pop);
  display: flex; flex-direction: column; box-sizing: border-box; min-width: 180px; width: max-content;
  max-width: min(320px, calc(100vw - 16px)); max-height: calc(100dvh - 16px); overflow: auto; overscroll-behavior: contain;
  padding: var(--space-1); border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--surface-raised); color: var(--ink); box-shadow: var(--shadow-pop);
  font-family: var(--font-sans); font-size: var(--text-md); line-height: var(--leading-md);
  animation: ui-menu-in var(--duration-fast) ease-out;
}
ui-menu .ui-menu__panel:focus { outline: none; }
ui-menu .ui-menu__item {
  display: grid; grid-template-columns: var(--icon-size) minmax(0, 1fr) auto auto; align-items: center; column-gap: var(--space-2);
  flex: none; min-height: var(--row-height); padding: 0 var(--space-2); border-radius: var(--radius-sm);
  color: var(--ink); cursor: default; user-select: none; white-space: nowrap; outline: none;
}
ui-menu .ui-menu__item:focus, ui-menu .ui-menu__item[aria-expanded="true"] { background: var(--accent-soft); color: var(--accent-ink); }
ui-menu .ui-menu__item:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--focus); }
ui-menu .ui-menu__item[data-variant="danger"] { color: var(--danger); }
ui-menu .ui-menu__item[data-variant="danger"]:focus { background: var(--danger-soft); color: var(--danger); }
ui-menu .ui-menu__item[aria-disabled="true"] { color: var(--faint); background: transparent; }
ui-menu .ui-menu__icon { display: inline-flex; width: var(--icon-size); height: var(--icon-size); color: var(--muted); }
ui-menu .ui-menu__item:focus .ui-menu__icon, ui-menu .ui-menu__item[data-variant="danger"] .ui-menu__icon { color: inherit; }
ui-menu .ui-menu__item[aria-disabled="true"] .ui-menu__icon { color: var(--faint); }
ui-menu .ui-menu__label { overflow: hidden; text-overflow: ellipsis; }
ui-menu .ui-menu__shortcut { padding-left: var(--space-4); color: var(--muted); font-family: var(--font-sans); font-size: var(--text-sm); }
ui-menu .ui-menu__item[aria-disabled="true"] .ui-menu__shortcut { color: var(--faint); }
ui-menu .ui-menu__more { display: inline-flex; color: var(--muted); }
ui-menu .ui-menu__sep { flex: none; height: 1px; margin: var(--space-1) var(--space-2); background: var(--line); }
@keyframes ui-menu-in { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { ui-menu .ui-menu__panel { animation: none; } }

/* ui/layout/popover/popover.css */
/* 떠 있는 판: <ui-popover open placement="bottom-start">. 글쓴이 자식이 내용이다. 자리(left·top)는 popover.js가 정한다.
   popover 속성(맨 위 층)의 브라우저 기본 모양(가운데 놓기, 테두리, 바탕)은 여기서 덮는다. 닫혀 있으면 그리지 않는다. */
ui-popover {
  display: none; position: fixed; inset: auto; left: 0; top: 0; margin: 0; z-index: var(--layer-pop);
  box-sizing: border-box; min-width: 160px; width: max-content; max-width: min(360px, calc(100vw - 16px)); max-height: calc(100dvh - 16px);
  overflow: auto; padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface-raised); color: var(--ink); box-shadow: var(--shadow-pop);
  font-family: var(--font-sans); font-size: var(--text-md); line-height: var(--leading-md);
}
ui-popover[open] { display: block; animation: ui-popover-in var(--duration-fast) ease-out; }
ui-popover:focus-visible { outline-offset: -2px; }
@keyframes ui-popover-in { from { opacity: 0; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { ui-popover[open] { animation: none; } }

/* ui/layout/split/split.css */
/* 나눔 막대: <ui-split direction="row|column">. 칸은 글쓴이 자식이고, 사이 손잡이는 부품이 그린다(order로 칸 사이에 놓인다). */
ui-split { display: flex; flex-direction: row; min-width: 0; min-height: 0; overflow: hidden; }
ui-split[direction="column"] { flex-direction: column; }
ui-split > .ui-split__pane { flex: 0 1 auto; min-width: 0; min-height: 0; overflow: auto; }
ui-split > .ui-split__handle {
  flex: none; position: relative; width: var(--gutter); cursor: col-resize; touch-action: none;
  background: transparent; transition: background-color var(--duration-fast);
}
ui-split[direction="column"] > .ui-split__handle { width: auto; height: var(--gutter); cursor: row-resize; }
ui-split > .ui-split__handle::after {
  content: ""; position: absolute; inset: 0 calc(50% - .5px); background: var(--line);
}
ui-split[direction="column"] > .ui-split__handle::after { inset: calc(50% - .5px) 0; }
ui-split > .ui-split__handle:hover, ui-split > .ui-split__handle:focus-visible, ui-split[data-dragging] > .ui-split__handle:active { background: var(--accent-soft); }
ui-split > .ui-split__handle:focus-visible { outline-offset: -2px; }
ui-split[data-dragging] { user-select: none; cursor: col-resize; }
ui-split[direction="column"][data-dragging] { cursor: row-resize; }
@media (prefers-reduced-motion: reduce) { ui-split > .ui-split__handle { transition: none; } }

/* ui/layout/tabs/tabs.css */
/* 탭: <ui-tabs value closable>. 탭 줄(.ui-tabs__strip)은 부품이 그리고, data-tab을 단 글쓴이 자식이 판(.ui-tabs__panel)이다.
   고른 탭은 아래 강조 선, 닫을 수 있는 탭(data-closable)은 오른쪽에 작은 닫기 단추. 탭이 많으면 탭 줄이 가로로 넘친다.
   iconic이면 아이콘이 있는 탭(data-icon-only)은 아이콘만 보이는 좁은 탭이다(이름은 aria-label과 풍선 도움말).
   slot="end"를 단 자식은 탭 줄 오른쪽 끝의 도구 자리다. */
ui-tabs { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
ui-tabs > .ui-tabs__strip {
  position: relative; flex: none; display: flex; align-items: stretch; gap: 2px; min-width: 0;
  padding: 0 var(--space-2); border-bottom: 1px solid var(--line); overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin; overscroll-behavior-x: contain;
}
ui-tabs .ui-tabs__tab {
  position: relative; flex: none; display: flex; align-items: center; max-width: 220px; height: var(--control-height);
  border-radius: var(--radius-md) var(--radius-md) 0 0; color: var(--muted);
  transition: background-color var(--duration-fast), color var(--duration-fast);
}
ui-tabs .ui-tabs__tab:hover { background: var(--surface-sunken); color: var(--ink); }
ui-tabs .ui-tabs__tab[data-selected] { color: var(--ink); box-shadow: inset 0 -2px 0 var(--accent); }
ui-tabs .ui-tabs__button {
  appearance: none; display: flex; align-items: center; gap: var(--space-2); min-width: 0; height: 100%;
  margin: 0; padding: 0 var(--space-3); border: 0; border-radius: inherit; background: transparent;
  color: inherit; font: inherit; font-weight: 500; cursor: pointer; white-space: nowrap;
}
ui-tabs .ui-tabs__tab[data-closable] .ui-tabs__button { padding-right: var(--space-1); }
ui-tabs .ui-tabs__tab[data-icon-only] .ui-tabs__button { padding: 0 var(--space-2); }
ui-tabs .ui-tabs__button:focus-visible { outline-offset: -2px; }
ui-tabs .ui-tabs__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
ui-tabs .ui-tabs__close {
  appearance: none; flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; margin: 0 var(--space-1) 0 0; padding: 0; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--faint); cursor: pointer;
}
ui-tabs .ui-tabs__close .ui-icon { width: 12px; height: 12px; }
ui-tabs .ui-tabs__close:hover { background: var(--line); color: var(--ink); }
ui-tabs .ui-tabs__tab:not([data-selected]):not(:hover) .ui-tabs__close { opacity: .6; }
ui-tabs > .ui-tabs__panel { flex: 1 1 auto; min-width: 0; min-height: 0; overflow: auto; }
/* 탭 줄 끝 자리: slot="end"를 단 자식은 탭 줄 오른쪽에 같은 줄로 놓고 아래 선을 이어 긋는다. 탭이 넘치면 탭 줄만 넘친다. */
ui-tabs:has(> [slot="end"]) { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto minmax(0, 1fr); }
ui-tabs:has(> [slot="end"]) > .ui-tabs__strip { grid-area: 1 / 1; }
ui-tabs > [slot="end"] { grid-area: 1 / 2; display: flex; align-items: center; gap: var(--space-1); min-width: 0; padding: 0 var(--space-2); border-bottom: 1px solid var(--line); }
ui-tabs:has(> [slot="end"]) > .ui-tabs__panel { grid-area: 2 / 1 / 3 / 3; }
ui-tabs > .ui-tabs__panel[hidden] { display: none; }
ui-tabs > .ui-tabs__panel:focus-visible { outline-offset: -2px; }
@media (prefers-reduced-motion: reduce) { ui-tabs .ui-tabs__tab { transition: none; } }

/* ui/layout/toast/toast.css */
/* 알림: <ui-toast-region inline>. 화면 오른쪽 아래에 알림(.ui-toast-region__item)을 아래로 쌓는다. 알림은 data-tone="neutral|done|danger".
   popover 속성(맨 위 층)의 브라우저 기본 모양은 여기서 덮는다. 영역 자체는 누름을 받지 않고 알림만 받는다. inline이면 위치가 정해진 부모 안에 그린다. */
ui-toast-region {
  position: fixed; inset: auto var(--space-4) var(--space-4) auto; z-index: var(--layer-toast); box-sizing: border-box;
  display: flex; flex-direction: column; align-items: stretch; gap: var(--space-2);
  width: min(360px, calc(100vw - 32px)); height: auto; max-height: calc(100dvh - 32px); margin: 0; padding: 0;
  overflow: visible; border: 0; background: transparent; color: var(--ink); pointer-events: none;
  font-family: var(--font-sans); font-size: var(--text-md); line-height: var(--leading-md);
}
ui-toast-region[inline] { position: absolute; inset: auto var(--space-3) var(--space-3) auto; width: min(320px, calc(100% - 24px)); max-height: calc(100% - 24px); }
ui-toast-region .ui-toast-region__item {
  pointer-events: auto; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  border: 1px solid var(--line); border-inline-start: 3px solid var(--line-strong); border-radius: var(--radius-md);
  background: var(--surface-raised); box-shadow: var(--shadow-pop);
  animation: ui-toast-region-in var(--duration-slow) ease-out;
}
ui-toast-region .ui-toast-region__item[data-tone="done"] { border-inline-start-color: var(--done); }
ui-toast-region .ui-toast-region__item[data-tone="danger"] { border-inline-start-color: var(--danger); background: var(--danger-soft); }
ui-toast-region .ui-toast-region__icon { display: inline-flex; padding-top: 5px; color: var(--muted); }
ui-toast-region .ui-toast-region__item[data-tone="done"] .ui-toast-region__icon { color: var(--done); }
ui-toast-region .ui-toast-region__item[data-tone="danger"] .ui-toast-region__icon { color: var(--danger); }
ui-toast-region .ui-toast-region__text { margin: 0; padding-top: 3px; white-space: pre-line; overflow-wrap: anywhere; }
@keyframes ui-toast-region-in { from { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) { ui-toast-region .ui-toast-region__item { animation: none; } }

/* ui/layout/toolbar/toolbar.css */
/* 도구 막대: <ui-toolbar> 또는 <div class="ui-toolbar" role="toolbar">. 단추를 한 줄로 늘어놓고, 좁으면 다음 줄로 넘긴다.
   구분선 .ui-toolbar__sep(role="separator"), 남는 폭 채우기 .ui-toolbar__spacer, 붙은 단추 묶음 .ui-toolbar__group, 작은 이름표 .ui-toolbar__label.
   앱 위쪽 띠로 쓸 때는 data-variant="bar"(바탕·아래 선), 세로는 orientation="vertical"(클래스만 쓰면 data-orientation="vertical"),
   넘치면 줄을 바꾸지 않고 가로로 넘기려면 data-wrap="none". */
ui-toolbar, .ui-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); min-width: 0;
  min-height: var(--bar-height); padding: 2px var(--space-2);
}
ui-toolbar[data-variant="bar"], .ui-toolbar[data-variant="bar"] { background: var(--surface); border-bottom: 1px solid var(--line); }
ui-toolbar[data-wrap="none"], .ui-toolbar[data-wrap="none"] { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; }
ui-toolbar[orientation="vertical"], .ui-toolbar[data-orientation="vertical"] {
  flex-direction: column; flex-wrap: nowrap; align-items: stretch; width: max-content; min-height: 0; padding: var(--space-2) 2px;
}
.ui-toolbar__sep { flex: none; align-self: stretch; width: 1px; margin: var(--space-1) var(--space-1); background: var(--line); }
ui-toolbar[orientation="vertical"] .ui-toolbar__sep, .ui-toolbar[data-orientation="vertical"] .ui-toolbar__sep { width: auto; height: 1px; margin: var(--space-1); }
.ui-toolbar__spacer { flex: 1 1 auto; min-width: 0; }
.ui-toolbar__group { display: inline-flex; align-items: center; gap: 2px; }
.ui-toolbar__label { padding: 0 var(--space-1); color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); white-space: nowrap; }

/* ui/layout/window/window.css */
/* 떠 있는 창: <ui-window heading="…" minimized contained>. 제목 줄(.ui-window__bar)과 크기 손잡이(.ui-window__grip)는 부품이 그리고,
   그 밖의 글쓴이 자식이 본문(넘치면 넘김)이다. 자리(left·top)·크기·쌓임(z-index)은 window.js가 정한다.
   contained면 위치가 정해진 부모 안에 absolute로, 아니면 화면에 fixed로 뜬다. 맨 앞 창은 data-active, 끄는 중은 data-dragging. */
ui-window {
  position: fixed; left: 0; top: 0; z-index: 1; box-sizing: border-box;
  display: flex; flex-direction: column; width: 360px; min-width: min(200px, 100%); max-width: 100%; max-height: 100%;
  overflow: hidden; border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  background: var(--surface-raised); color: var(--ink); box-shadow: var(--shadow-pop);
  font-family: var(--font-sans); font-size: var(--text-md); line-height: var(--leading-md);
}
ui-window[contained] { position: absolute; }
ui-window[hidden] { display: none; }
ui-window[data-dragging] { user-select: none; }
ui-window > .ui-window__bar {
  flex: none; display: flex; align-items: center; gap: 2px; height: var(--bar-height);
  padding: 0 var(--space-1) 0 var(--space-3); border-bottom: 1px solid var(--line);
  background: var(--surface-sunken); color: var(--muted); cursor: move; touch-action: none; user-select: none;
}
ui-window[data-active] > .ui-window__bar { background: var(--surface); color: var(--ink); }
ui-window > .ui-window__bar:focus-visible { outline-offset: -2px; }
ui-window .ui-window__title {
  flex: 1 1 auto; min-width: 0; margin: 0; font-size: var(--text-md); line-height: var(--leading-md); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
ui-window .ui-window__subheading { margin-left: var(--space-2); color: var(--muted); font-size: var(--text-sm); font-weight: 400; }
ui-window > .ui-window__bar .ui-button { cursor: pointer; }
ui-window > :not(.ui-window__bar):not(.ui-window__grip) { flex: 1 1 auto; min-height: 0; overflow: auto; }
ui-window[minimized] > :not(.ui-window__bar) { display: none; }
ui-window[minimized] > .ui-window__bar { border-bottom: 0; }
ui-window > .ui-window__grip {
  position: absolute; right: 0; bottom: 0; z-index: 1; width: 16px; height: 16px; cursor: nwse-resize; touch-action: none;
  background: linear-gradient(135deg, transparent 0 55%, var(--line-strong) 55% 62%, transparent 62% 72%, var(--line-strong) 72% 79%, transparent 79%);
  border-bottom-right-radius: var(--radius-lg);
}
ui-window > .ui-window__grip:hover, ui-window[data-dragging] > .ui-window__grip:active {
  background: linear-gradient(135deg, transparent 0 55%, var(--accent) 55% 62%, transparent 62% 72%, var(--accent) 72% 79%, transparent 79%);
}
ui-window > .ui-window__grip:focus-visible { outline-offset: -2px; }
ui-window > .ui-window__grip[hidden] { display: none; }

/* ui/media/player/player.css */
/* 재생 막대: <ui-player>. 표지 · 이름 · 단추 · 재생 위치 · 소리 크기를 한 줄에 놓고, 폭이 좁으면 재생 위치를 둘째 줄로 내린다.
   상태는 .ui-player__bar[data-state=idle|loading|ready|buffering|error]. 막대 칠은 --ui-player-fill(재생한 만큼)·--ui-player-buffer(받은 만큼). */
ui-player { display: block; min-width: 0; container-type: inline-size; }
ui-player .ui-player__bar {
  display: grid; align-items: center; gap: var(--space-2) var(--space-3);
  grid-template-columns: auto minmax(120px, 1fr) auto minmax(180px, 2fr) auto;
  grid-template-areas: "cover meta controls seek volume";
  padding: var(--space-2) var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); color: var(--ink);
}
ui-player .ui-player__bar:not([data-cover]) { grid-template-columns: minmax(120px, 1fr) auto minmax(180px, 2fr) auto; grid-template-areas: "meta controls seek volume"; }
ui-player .ui-player__audio { display: none; }
ui-player .ui-player__cover { grid-area: cover; width: 40px; height: 40px; border-radius: var(--radius-sm); object-fit: cover; background: var(--surface-sunken); }
ui-player .ui-player__meta { grid-area: meta; display: flex; flex-direction: column; min-width: 0; }
ui-player .ui-player__title { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 600; line-height: var(--leading-md); }
ui-player .ui-player__title[data-empty] { color: var(--muted); font-weight: 400; }
ui-player .ui-player__subtitle, ui-player .ui-player__note { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); }
ui-player .ui-player__note { display: flex; align-items: center; gap: var(--space-1); }
ui-player .ui-player__note[data-tone="danger"] { color: var(--danger); }
ui-player .ui-player__note .ui-icon { width: 14px; height: 14px; }
ui-player .ui-player__spinner {
  width: 12px; height: 12px; flex: none; border: 2px solid currentColor; border-right-color: transparent; border-radius: var(--radius-pill);
  animation: ui-player-spin .8s linear infinite;
}
@keyframes ui-player-spin { to { transform: rotate(360deg); } }

ui-player .ui-player__controls { grid-area: controls; display: flex; align-items: center; gap: var(--space-1); }
ui-player .ui-player__play { border-radius: var(--radius-pill); }
ui-player .ui-player__seek { grid-area: seek; display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
ui-player .ui-player__time { flex: none; min-width: 3.2em; color: var(--muted); text-align: center; }
ui-player .ui-player__volume { grid-area: volume; display: flex; align-items: center; gap: var(--space-1); }
ui-player .ui-player__volume-range { width: 88px; }

/* 막대(재생 위치·소리 크기): 브라우저 기본 모양을 지우고 토큰 색으로 칠한다. */
ui-player .ui-player__range {
  --ui-player-fill: 0%; --ui-player-buffer: 0%;
  flex: 1 1 auto; min-width: 0; height: 20px; margin: 0; background: transparent; cursor: pointer; appearance: none; -webkit-appearance: none;
}
ui-player .ui-player__range:disabled { cursor: default; opacity: var(--disabled-opacity); }
ui-player .ui-player__range::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--radius-pill);
  background: linear-gradient(to right, var(--accent) 0 var(--ui-player-fill), var(--line-strong) 0 var(--ui-player-buffer), var(--line) 0);
}
ui-player .ui-player__range::-moz-range-track {
  height: 4px; border-radius: var(--radius-pill);
  background: linear-gradient(to right, var(--accent) 0 var(--ui-player-fill), var(--line-strong) 0 var(--ui-player-buffer), var(--line) 0);
}
ui-player .ui-player__range::-webkit-slider-thumb {
  width: 12px; height: 12px; margin-top: -4px; border: 0; border-radius: var(--radius-pill); background: var(--accent); box-shadow: var(--shadow-xs); -webkit-appearance: none;
  transition: transform var(--duration-fast);
}
ui-player .ui-player__range::-moz-range-thumb { width: 12px; height: 12px; border: 0; border-radius: var(--radius-pill); background: var(--accent); box-shadow: var(--shadow-xs); }
ui-player .ui-player__range:hover::-webkit-slider-thumb, ui-player .ui-player__range:focus-visible::-webkit-slider-thumb { transform: scale(1.2); }
ui-player .ui-player__range:disabled::-webkit-slider-thumb { background: var(--faint); }
ui-player .ui-player__range:disabled::-moz-range-thumb { background: var(--faint); }

/* 좁은 폭: 둘째 줄에 재생 위치. 더 좁으면 소리 크기 막대를 빼고 끄기 단추만 둔다. */
@container (max-width: 640px) {
  ui-player .ui-player__bar { grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "cover meta controls" "seek seek volume"; }
  ui-player .ui-player__bar:not([data-cover]) { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "meta meta controls" "seek seek volume"; }
}
@container (max-width: 360px) {
  ui-player .ui-player__volume-range { display: none; }
  ui-player .ui-player__bar { padding: var(--space-2); gap: var(--space-1) var(--space-2); }
}
@media (prefers-reduced-motion: reduce) {
  ui-player .ui-player__spinner { animation: none; border-right-color: currentColor; opacity: .6; }
  ui-player .ui-player__range::-webkit-slider-thumb { transition: none; }
}

/* ui/media/thumbs/thumbs.css */
/* 작은 그림 모음: <ui-thumbs size="sm|md|lg" selection="single|multiple|none" fit="cover|contain">.
   열 수는 칸의 가장 작은 폭(--ui-thumbs-min)으로 저절로 정한다. 고른 칸은 aria-selected="true". */
ui-thumbs { --ui-thumbs-min: 128px; display: block; min-width: 0; }
ui-thumbs[size="sm"] { --ui-thumbs-min: 88px; }
ui-thumbs[size="lg"] { --ui-thumbs-min: 184px; }
ui-thumbs .ui-thumbs__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(var(--ui-thumbs-min), 100%), 1fr)); gap: var(--space-2); }
ui-thumbs[size="sm"] .ui-thumbs__grid { gap: var(--space-1); }

ui-thumbs .ui-thumbs__item {
  display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; padding: var(--space-1); border-radius: var(--radius-md);
  border: 1px solid transparent; cursor: pointer; user-select: none; transition: background-color var(--duration-fast), border-color var(--duration-fast);
}
ui-thumbs .ui-thumbs__item:hover { background: var(--surface-sunken); }
ui-thumbs .ui-thumbs__item:focus-visible { outline-offset: 0; }
ui-thumbs .ui-thumbs__item[aria-selected="true"] { background: var(--accent-soft); border-color: var(--accent); }

ui-thumbs .ui-thumbs__picture { position: relative; aspect-ratio: 1; overflow: hidden; border-radius: var(--radius-sm); background: var(--surface-sunken); }
ui-thumbs .ui-thumbs__picture[data-state="loading"] { animation: ui-thumbs-pulse 1.4s ease-in-out infinite; }
ui-thumbs .ui-thumbs__image { display: block; width: 100%; height: 100%; object-fit: cover; -webkit-user-drag: none; }
ui-thumbs[fit="contain"] .ui-thumbs__picture { background: var(--media-backdrop); }
ui-thumbs[fit="contain"] .ui-thumbs__image { object-fit: contain; }
ui-thumbs .ui-thumbs__picture[data-state="error"] .ui-thumbs__image { visibility: hidden; }
ui-thumbs .ui-thumbs__broken { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--faint); }
ui-thumbs .ui-thumbs__badge {
  position: absolute; top: var(--space-1); left: var(--space-1); max-width: calc(100% - 2 * var(--space-1)); padding: 0 var(--space-1);
  border-radius: var(--radius-sm); background: var(--scrim); color: var(--on-media); font-size: var(--text-sm); line-height: var(--leading-sm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
ui-thumbs .ui-thumbs__check {
  position: absolute; top: var(--space-1); right: var(--space-1); display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: var(--radius-pill); border: 1.5px solid var(--on-media); background: var(--scrim); color: var(--on-accent);
}
ui-thumbs .ui-thumbs__item[aria-selected="true"] .ui-thumbs__check { background: var(--accent); border-color: var(--accent); }
ui-thumbs .ui-thumbs__check .ui-icon { width: 12px; height: 12px; stroke-width: 2.4; }
ui-thumbs .ui-thumbs__label { min-width: 0; padding: 0 var(--space-1); color: var(--ink); font-size: var(--text-sm); line-height: var(--leading-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
ui-thumbs[size="sm"] .ui-thumbs__label { display: none; }

ui-thumbs .ui-thumbs__skeleton { aspect-ratio: 1; border-radius: var(--radius-md); background: var(--surface-sunken); animation: ui-thumbs-pulse 1.4s ease-in-out infinite; }
ui-thumbs .ui-thumbs__empty { display: flex; align-items: center; justify-content: center; gap: var(--space-2); margin: 0; padding: var(--space-6) var(--space-4); border: 1px dashed var(--line-strong); border-radius: var(--radius-lg); color: var(--muted); }
@keyframes ui-thumbs-pulse { 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) {
  ui-thumbs .ui-thumbs__item { transition: none; }
  ui-thumbs .ui-thumbs__picture[data-state="loading"], ui-thumbs .ui-thumbs__skeleton { animation: none; }
}

/* ui/media/video/video.css */
/* 영상: <ui-video>. 비율 상자(어두운 미디어 바탕) 안에 기본 <video controls>, 아래 설명. ratio="none"이면 상자가 부품의 높이를 채운다.
   담는 법(fit)은 영상 요소의 object-fit이고, cover에서 잘린 쪽을 옮길 수 있으면 끌기 손 모양이다.
   상태는 .ui-video__box[data-state=idle|loading|ready|buffering|error]. 여는 중은 가운데 작은 고리만 띄우고 기본 단추를 가리지 않는다. */
ui-video { display: block; min-width: 0; }
ui-video .ui-video__frame { margin: 0; }
ui-video .ui-video__frame[data-free] { display: flex; flex-direction: column; height: 100%; }
ui-video .ui-video__frame[data-free] > .ui-video__box { flex: 1 1 auto; min-height: 0; }
ui-video .ui-video__box[data-fit="cover"] { cursor: grab; touch-action: none; }
ui-video .ui-video__box { position: relative; width: 100%; overflow: hidden; border-radius: var(--radius-lg); background: var(--media-backdrop); color: var(--on-media); }
ui-video .ui-video__media { display: block; width: 100%; height: 100%; object-fit: contain; background: var(--media-backdrop); }
ui-video .ui-video__media[hidden] { display: none; }
ui-video .ui-video__state {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-4); background: var(--media-backdrop); color: var(--on-media); font-size: var(--text-md); line-height: var(--leading-md); text-align: center;
}
ui-video .ui-video__state[data-quiet] { background: transparent; pointer-events: none; }
/* 오류는 어두운 미디어 바탕에서 읽히는 위험 색(--danger-on-media)을 쓴다. */
ui-video .ui-video__state[data-tone="danger"] { font-weight: 600; color: var(--danger-on-media); }
ui-video .ui-video__spinner {
  width: 28px; height: 28px; border: 3px solid currentColor; border-right-color: transparent; border-radius: var(--radius-pill); opacity: .85;
  animation: ui-video-spin .8s linear infinite;
}
@keyframes ui-video-spin { to { transform: rotate(360deg); } }
ui-video .ui-video__caption { padding: var(--space-2) var(--space-1) 0; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); overflow-wrap: anywhere; }
@media (prefers-reduced-motion: reduce) { ui-video .ui-video__spinner { animation: none; border-right-color: currentColor; opacity: .5; } }

/* ui/media/viewer/viewer.css */
/* 그림 보기: <ui-viewer>. 위는 그림·영상 칸(어두운 미디어 바탕), 아래는 넘기기·재생·담는 법·배율 막대와 설명.
   높이는 쓰는 자리가 정한다(없으면 최소 240px). 상태는 .ui-viewer__view[data-status=empty|loading|ready|error]. */
ui-viewer { display: flex; flex-direction: column; min-width: 0; min-height: 240px; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); overflow: hidden; }
ui-viewer:fullscreen { border: 0; border-radius: 0; }
ui-viewer .ui-viewer__frame { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; margin: 0; }
ui-viewer .ui-viewer__view { position: relative; flex: 1 1 auto; min-height: 160px; background: var(--media-backdrop); color: var(--on-media); }
ui-viewer .ui-viewer__stage { position: absolute; inset: 0; overflow: hidden; touch-action: pan-x pan-y; user-select: none; }
ui-viewer .ui-viewer__stage:focus-visible { outline-offset: -2px; }
ui-viewer .ui-viewer__stage[data-pannable] { cursor: grab; touch-action: none; }
ui-viewer .ui-viewer__stage[data-dragging] { cursor: grabbing; }
ui-viewer .ui-viewer__image { position: absolute; left: 50%; top: 50%; max-width: none; max-height: none; transform: translate(-50%, -50%); pointer-events: none; -webkit-user-drag: none; }
ui-viewer .ui-viewer__image[hidden] { display: none; }
ui-viewer[pixelated] .ui-viewer__image { image-rendering: pixelated; }

/* 상태 층: 그림 위에 겹친다. 여는 중(data-over)은 이전 그림을 흐리게 남긴다. */
ui-viewer .ui-viewer__state {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-4); color: var(--on-media); font-size: var(--text-md); line-height: var(--leading-md); text-align: center; pointer-events: none;
}
ui-viewer .ui-viewer__state[data-over] { background: var(--scrim); }
/* 오류는 어두운 미디어 바탕에서 읽히는 위험 색(--danger-on-media)을 쓴다. */
ui-viewer .ui-viewer__state[data-tone="danger"] { font-weight: 600; color: var(--danger-on-media); }
ui-viewer .ui-viewer__spinner {
  width: var(--icon-size); height: var(--icon-size); flex: none; border: 2px solid currentColor; border-right-color: transparent; border-radius: var(--radius-pill);
  animation: ui-viewer-spin .8s linear infinite;
}
@keyframes ui-viewer-spin { to { transform: rotate(360deg); } }

ui-viewer .ui-viewer__bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-1) var(--space-3); padding: var(--space-1) var(--space-2); border-top: 1px solid var(--line); background: var(--surface); }
ui-viewer .ui-viewer__group { display: flex; align-items: center; gap: var(--space-1); min-width: 0; }
ui-viewer .ui-viewer__zoom { margin-left: auto; flex-wrap: wrap; justify-content: flex-end; }
/* 영상의 재생 줄: 재생·시간·위치 밀대(남는 폭)·소리. */
ui-viewer .ui-viewer__play { flex: 1 1 16rem; }
ui-viewer .ui-viewer__seek { flex: 1 1 auto; }
ui-viewer .ui-viewer__time { flex: none; color: var(--muted); font-size: var(--text-sm); }
ui-viewer .ui-viewer__count, ui-viewer .ui-viewer__percent { min-width: 4.5em; text-align: center; color: var(--muted); }
ui-viewer .ui-viewer__caption { padding: var(--space-2) var(--space-3); border-top: 1px solid var(--line); color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-sm); overflow-wrap: anywhere; }
@media (prefers-reduced-motion: reduce) { ui-viewer .ui-viewer__spinner { animation: none; border-right-color: currentColor; opacity: .6; } }

