/*! qufox-design v0.7.0 — components.css · https://design.qufox.com · 항상 최신 빌드(버전 핀은 벤더링) */
/* qufox · component library
 * Requires tokens.css. All components are plain CSS classes,
 * framework-agnostic (work with React, Vue, vanilla HTML).
 *
 * LAYER 분류 (범용/채팅 분리 · docs/adr/0002):
 *   - 섹션 배너에 `· layer: generic|chat` 태그를 붙여 둔다.
 *   - generic = 채팅 도메인 개념이 없어 어떤 앱에서도 동작. chat = 채팅 도메인
 *     개념(message·thread·channel·server·member·presence·mention·reaction·role·
 *     typing·voice)을 내포.
 *   - 컴포넌트별 권위 있는 layer/변형/필요토큰/접근성/예제는 packages/manifest/
 *     components.json(사람·AI 공용 SSOT)이 정본. 물리 파일 분리는 후속 태스크.
 */

/* ───── Button · layer: generic ───── */
.qf-btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s-3);
  height: 36px; padding: 0 var(--s-5);
  border: 1px solid transparent; border-radius: var(--r-md);
  font: 500 var(--fs-14)/1 var(--font-sans);
  letter-spacing: -0.005em; cursor: pointer; user-select: none; text-decoration: none;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard),
              transform var(--dur-fast) var(--ease-standard);
  background: transparent; color: var(--text);
}
.qf-btn:active { transform: scale(0.97); transition-duration: var(--dur-instant); }
.qf-btn[disabled], .qf-btn:disabled {
  opacity: 0.5; cursor: not-allowed;
}
.qf-btn svg { width: 16px; height: 16px; }

/* variants */
.qf-btn--primary {
  background: var(--accent-solid); color: var(--text-onAccent);
}
.qf-btn--primary:hover   { background: var(--accent-solid-hover); }
.qf-btn--primary:active  { background: var(--accent-solid-press); }

.qf-btn--secondary {
  background: var(--bg-elevated); color: var(--text);
  border-color: var(--border);
}
.qf-btn--secondary:hover  { background: var(--bg-hover); border-color: var(--border-strong); }
.qf-btn--secondary:active { background: var(--bg-selected); }

.qf-btn--ghost { background: transparent; color: var(--text-secondary); }
.qf-btn--ghost:hover  { background: var(--bg-hover); color: var(--text); }
.qf-btn--ghost:active { background: var(--bg-selected); }

.qf-btn--danger {
  background: var(--danger-600); color: #fff;
}
.qf-btn--danger:hover  { background: var(--danger-700); }

.qf-btn--link {
  background: transparent; color: var(--link); height: auto; padding: 0;
}
.qf-btn--link:hover { color: var(--link-hover); text-decoration: underline; }

/* sizes */
.qf-btn--sm { height: 28px; padding: 0 var(--s-4); font-size: var(--fs-13); border-radius: var(--r-sm); }
.qf-btn--md { /* default */ }
.qf-btn--lg { height: 44px; padding: 0 var(--s-6); font-size: var(--fs-15); }

.qf-btn--icon { width: 36px; padding: 0; }
.qf-btn--icon.qf-btn--sm { width: 28px; }
.qf-btn--icon.qf-btn--lg { width: 44px; }

/* Tight icon-only button for tight list rows (channel / category
   suffix). No rectangular shell at rest, just the icon in
   text-muted; hover colour-lifts to text-strong. Used where qf-btn
   (28-36px) would inflate row height. */
.qf-row-iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
  padding: 0; border: none; background: transparent;
  border-radius: var(--r-xs);
  color: var(--text-muted); cursor: pointer;
  flex-shrink: 0;
}
.qf-row-iconbtn:hover  { color: var(--text-strong); }
.qf-row-iconbtn:active { color: var(--accent); }
.qf-row-iconbtn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ───── Input · layer: generic ───── */
.qf-input {
  display: block; width: 100%; height: 40px;
  padding: 0 var(--s-4);
  background: var(--bg-input); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  font: 400 var(--fs-14) var(--font-sans);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.qf-input::placeholder { color: var(--text-muted); }
.qf-input:hover:not(:focus) { border-color: var(--border-strong); }
/* No focus halo on text inputs — the caret is the cue. Keeps border
   neutral so the typing experience feels calm. aria-invalid=true is
   the only state that changes the border (for error affordance). */
.qf-input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-accent); }
.qf-input[aria-invalid="true"] { border-color: var(--danger-400); box-shadow: var(--ring-danger); }
/* native date/time picker 아이콘 — dark-first 기본은 검은 아이콘이라 invert(흰색)로 가시화.
   라이트 테마는 원래 어두운 아이콘이 잘 보이므로 invert 해제. */
.qf-input::-webkit-calendar-picker-indicator { filter: invert(1); opacity: 0.55; cursor: pointer; }
.qf-input::-webkit-calendar-picker-indicator:hover { opacity: 0.85; }
[data-theme="light"] .qf-input::-webkit-calendar-picker-indicator { filter: none; opacity: 0.6; }
.qf-textarea { height: auto; min-height: 80px; padding: var(--s-3) var(--s-4); resize: vertical; }

.qf-field { display: flex; flex-direction: column; gap: var(--s-2); }
.qf-field__label {
  font-size: var(--fs-12); font-weight: 600;
  color: var(--text-secondary); text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
}
.qf-field__hint { font-size: var(--fs-12); color: var(--text-muted); }
.qf-field__error { font-size: var(--fs-12); color: var(--danger-400); }

/* ───── Avatar · layer: generic ───── */
.qf-avatar {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; border-radius: var(--r-pill); overflow: hidden;
  background: var(--bg-elevated); color: var(--text-secondary);
  font-family: var(--font-sans); font-weight: 600; line-height: 1;
  text-transform: uppercase; user-select: none;
}
.qf-avatar img { display: block; width: 100%; height: 100%; object-fit: cover; }
.qf-avatar--xs { width: 20px; height: 20px; font-size: 9px; }
.qf-avatar--sm { width: 24px; height: 24px; font-size: 11px; }
.qf-avatar--md { width: 40px; height: 40px; font-size: 16px; }
.qf-avatar--lg { width: 56px; height: 56px; font-size: 22px; }
.qf-avatar--xl { width: 80px; height: 80px; font-size: 30px; }
.qf-avatar__status {
  position: absolute; right: -2px; bottom: -2px;
  width: var(--sz-status-dot); height: var(--sz-status-dot); border-radius: var(--r-pill);
  box-shadow: 0 0 0 var(--sz-status-dot-ring) var(--avatar-ring);
}
.qf-avatar--sm .qf-avatar__status { width: var(--sz-status-dot-sm); height: var(--sz-status-dot-sm); box-shadow: 0 0 0 var(--sz-status-dot-ring) var(--avatar-ring); }
.qf-avatar--xs .qf-avatar__status { width: 8px; height: 8px; box-shadow: 0 0 0 var(--sz-status-dot-ring) var(--avatar-ring); }
.qf-avatar__status--online  { background: var(--status-online); }
.qf-avatar__status--idle    { background: var(--status-idle); }
.qf-avatar__status--dnd     { background: var(--status-dnd); }
.qf-avatar__status--offline { background: var(--status-offline); }

/* ───── Badge · layer: generic ───── */
.qf-badge {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: 2px 8px; border-radius: var(--r-xs);
  font: 600 var(--fs-11)/1.2 var(--font-sans);
  background: var(--bg-hover); color: var(--text-secondary);
}
.qf-badge--accent { background: var(--accent-subtle); color: var(--accent-on-subtle); }
.qf-badge--danger { background: var(--danger-bg); color: var(--text-danger); }
.qf-badge--success { background: var(--ok-bg); color: var(--text-ok); }
.qf-badge--dot { padding: 2px 6px 2px 4px; }
.qf-badge--dot::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
}

.qf-badge--count {
  min-width: 18px; height: 18px; padding: 0 5px;
  background: var(--danger-600); color: #fff;
  border-radius: var(--r-pill); font-size: 11px;
  justify-content: center;
}
/* 상태 배지 확장(010): --warn = 쉬는 중/지연, --muted = 멈춤/비활성(기본보다 한 단계 낮은 톤).
   --count(빨강)는 알림 개수 전용 — 중립 개수는 기본 배지나 qf-source__count 를 쓴다. */
.qf-badge--warn { background: var(--warn-bg); color: var(--text-warn); }
.qf-badge--muted { background: transparent; box-shadow: inset 0 0 0 1px var(--border); color: var(--text-muted); } /* 테두리를 inset ring 으로 → 다른 배지와 같은 높이 */

/* ───── Tooltip · layer: generic ───── */
.qf-tooltip {
  padding: 6px 10px; border-radius: var(--r-sm);
  background: var(--bg-inverse); color: var(--text-inverse);
  font: 500 var(--fs-12) var(--font-sans);
  box-shadow: var(--elev-2); pointer-events: none;
  white-space: nowrap;
}

/* ───── Modal · layer: generic ───── */
.qf-modal-backdrop {
  position: fixed; inset: 0; background: rgba(10,8,30,0.65);
  backdrop-filter: blur(4px);
  display: grid; place-items: center;
  z-index: var(--z-modal-bg);
}
.qf-modal {
  width: min(520px, calc(100vw - 32px));
  background: var(--bg-elevated); border-radius: var(--r-xl);
  box-shadow: var(--elev-4); border: 1px solid var(--border);
  display: flex; flex-direction: column; max-height: 85vh;
  position: relative; z-index: var(--z-modal);
}
.qf-modal__header {
  padding: var(--s-6) var(--s-7) var(--s-4);
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-4);
}
.qf-modal__title { font: 600 var(--fs-20)/1.2 var(--font-sans); letter-spacing: var(--tracking-tight); color: var(--text-strong); }
.qf-modal__body { padding: 0 var(--s-7); overflow: auto; flex: 1; }
.qf-modal__footer {
  padding: var(--s-5) var(--s-7);
  display: flex; justify-content: flex-end; gap: var(--s-3);
  border-top: 1px solid var(--divider); margin-top: var(--s-5);
}

/* ───── Dropdown / Menu · layer: generic ───── */
.qf-menu {
  min-width: 200px; padding: var(--s-2);
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--elev-3);
}
.qf-menu__item {
  display: flex; align-items: center; gap: var(--s-3);
  width: 100%; padding: var(--s-2) var(--s-3); border-radius: var(--r-sm);
  background: transparent; border: none; text-align: left;
  font: 500 var(--fs-14) var(--font-sans); color: var(--text);
  cursor: pointer; user-select: none;
}
.qf-menu__item:hover { background: var(--bg-hover); }
.qf-menu__item--danger { color: var(--danger-400); }
.qf-menu__item--danger:hover { background: var(--danger-bgHov); }
.qf-menu__item svg { width: 16px; height: 16px; opacity: 0.8; }
.qf-menu__separator { height: 1px; background: var(--divider); margin: var(--s-2) calc(var(--s-2) * -1); }
.qf-menu__kbd {
  margin-left: auto; font: 500 var(--fs-11) var(--font-mono);
  color: var(--text-secondary);
}

/* ───── Tabs · layer: generic ───── */
.qf-tabs { display: flex; gap: var(--s-2); border-bottom: 1px solid var(--divider); }
.qf-tabs__item {
  position: relative; padding: var(--s-3) var(--s-4);
  font: 500 var(--fs-14) var(--font-sans); color: var(--text-secondary);
  cursor: pointer; background: transparent; border: none;
}
.qf-tabs__item:hover { color: var(--text); }
.qf-tabs__item[aria-selected="true"] { color: var(--text-strong); }
.qf-tabs__item[aria-selected="true"]::after {
  content: ''; position: absolute; left: var(--s-4); right: var(--s-4); bottom: -1px;
  height: 2px; background: var(--accent); border-radius: 2px 2px 0 0;
}

/* ───── Toast · layer: generic ───── */
.qf-toast {
  display: flex; align-items: flex-start; gap: var(--s-3);
  padding: var(--s-4) var(--s-5); min-width: 320px; max-width: 420px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md); box-shadow: var(--elev-3);
}
.qf-toast--success { border-left-color: var(--ok-400); }
.qf-toast--danger  { border-left-color: var(--danger-400); }
.qf-toast--warn    { border-left-color: var(--warn-400); }
.qf-toast__title { font: 600 var(--fs-14) var(--font-sans); color: var(--text-strong); }
.qf-toast__body  { font-size: var(--fs-13); color: var(--text-secondary); margin-top: 2px; }

/* ───── Chat: Message · layer: chat ───── */
.qf-message {
  position: relative;
  padding: var(--s-2) var(--s-7) var(--s-2) var(--s-8);
  display: grid; grid-template-columns: auto 1fr; gap: 0 var(--s-4);
  margin-top: 0;
}
.qf-message:hover { background: var(--bg-row-hover); }
.qf-message--head { padding-top: var(--s-5); }
/* @mention / @here / @role highlight — violet tint + left accent bar (token-driven, not Discord amber). */
.qf-message--mention { background: var(--mention-bg); box-shadow: inset var(--s-1) 0 0 var(--accent); }
.qf-message--mention:hover { background: var(--mention-bgHov); }
.qf-message--cont { grid-template-columns: auto 1fr; }
.qf-message--cont .qf-message__avatar { visibility: hidden; height: 0; }
.qf-message--cont .qf-message__meta { display: none; }
.qf-message__avatar { grid-row: span 2; }
.qf-message__meta {
  display: flex; align-items: baseline; gap: var(--s-3);
  margin-bottom: var(--s-1);
}
.qf-message__author { font: 600 var(--fs-15) var(--font-sans); color: var(--text-strong); }
.qf-message__time { font-size: var(--fs-11); color: var(--text-muted); font-weight: 500; }
/* Continuation-row gutter timestamp — hidden until row hover (Slack/Discord left-gutter pattern).
   Sits in the avatar column on .qf-message--cont rows. */
.qf-message__gutter-time {
  grid-column: 1; align-self: center; justify-self: center;
  font: 500 var(--fs-11) var(--font-sans); color: var(--text-muted);
  opacity: 0; transition: opacity var(--dur-fast) var(--ease-standard);
  user-select: none; pointer-events: none;
}
.qf-message:hover .qf-message__gutter-time { opacity: 1; }
.qf-message__body {
  /* 072-N6-5 (D2): 채팅 폰트 크기 토큰. 미설정 시 base(--fs-15) 폴백. */
  font-size: var(--fs-chat, var(--fs-15)); line-height: var(--lh-normal); color: var(--text);
  word-wrap: break-word;
}
.qf-message__body p { margin: 0; }
.qf-message__body p + p { margin-top: var(--s-2); }
.qf-message__body a { color: var(--link); text-decoration: none; }
.qf-message__body a:hover { text-decoration: underline; }
.qf-message__toolbar {
  position: absolute; top: -14px; right: var(--s-5);
  display: flex; gap: 2px; padding: 2px;
  background: var(--bg-floating); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--elev-2);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(var(--s-1));
  transition: opacity var(--dur-instant) var(--ease-standard),
              transform var(--dur-fast) var(--ease-standard),
              visibility 0s linear var(--dur-fast);
}
.qf-message:hover .qf-message__toolbar {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0);
  transition: opacity var(--dur-fast) var(--ease-standard),
              transform var(--dur-fast) var(--ease-standard);
}

/* Mention pill */
.qf-mention {
  display: inline-block; padding: 0 4px; margin: 0 1px;
  background: var(--mention-bg); color: var(--accent-on-subtle);
  border-radius: var(--r-xs); font-weight: 500;
  cursor: pointer;
}
.qf-mention:hover { background: var(--mention-bgHov); }

/* Reactions */
.qf-reactions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.qf-reaction {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 8px; border-radius: var(--r-lg);
  background: var(--bg-hover); border: 1px solid transparent;
  font: 500 var(--fs-12) var(--font-sans); color: var(--text-secondary);
  cursor: pointer;
  transition: background var(--dur-fast);
}
.qf-reaction:hover { background: var(--bg-selected); border-color: var(--border-strong); }
.qf-reaction--me { background: var(--accent-bg); border-color: var(--accent); color: var(--accent-on-subtle); }

/* Reply bar */
.qf-replybar {
  display: flex; align-items: center; gap: var(--s-2);
  padding-left: var(--s-8); padding-top: 2px;
  font-size: var(--fs-13); color: var(--text-muted);
}
.qf-replybar__arrow { width: 24px; height: 10px; border: 2px solid var(--border-strong); border-right: none; border-bottom: none; border-radius: 6px 0 0 0; margin-left: -6px; }
.qf-replybar__author { color: var(--text-secondary); font-weight: 500; }

/* ───── Chat: Thread · layer: chat ───── */
/* Inline thread-preview chip attached under a message.
   Aligns with the message body (past avatar column): s-8 + avatar-md(40) + s-4 gap = 88px. */
.qf-thread-chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  margin: 2px var(--s-7) var(--s-2) 88px;
  padding: 4px 10px 4px 6px;
  background: var(--bg-panel); border: 1px solid transparent;
  border-radius: var(--r-md); cursor: pointer;
  font: 500 var(--fs-13) var(--font-sans); color: var(--text-secondary);
  white-space: nowrap; max-width: calc(100% - 88px - var(--s-7));
  transition: background var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard);
}
.qf-thread-chip:hover { background: var(--bg-elevated); border-color: var(--border); color: var(--text); }
.qf-thread-chip__avatars { display: flex; }
.qf-thread-chip__avatars > * { margin-left: -4px; border: 2px solid var(--bg-chat); border-radius: 50%; }
.qf-thread-chip__avatars > *:first-child { margin-left: 0; }
.qf-thread-chip__count { color: var(--accent); font-weight: 600; }
.qf-thread-chip__last { color: var(--text-muted); font-weight: 500; }
.qf-thread-chip__cta { color: var(--text-muted); font-size: var(--fs-11); }
.qf-thread-chip:hover .qf-thread-chip__cta { color: var(--accent); }

/* Thread side panel (right rail, Discord style) */
.qf-thread-panel {
  display: flex; flex-direction: column;
  width: 420px; flex-shrink: 0; min-height: 0;
  background: var(--bg-chat);
  border-left: 1px solid var(--divider);
  align-self: stretch;
}
.qf-thread-panel__header {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 0 var(--s-5); height: var(--h-topbar);
  flex-shrink: 0;
  border-bottom: 1px solid var(--divider);
  box-shadow: var(--elev-1);
}
.qf-thread-panel__icon { color: var(--text-muted); font-size: 16px; }
.qf-thread-panel__title {
  font: 600 var(--fs-15) var(--font-sans); color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1; min-width: 0;
}
.qf-thread-panel__sub {
  font: 500 var(--fs-11) var(--font-sans);
  color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
.qf-thread-panel__close {
  margin-left: auto; border: none; background: transparent;
  color: var(--text-muted); cursor: pointer;
  width: 28px; height: 28px; border-radius: var(--r-sm);
  display: grid; place-items: center;
}
.qf-thread-panel__close:hover { background: var(--bg-hover); color: var(--text); }

/* Thread origin card (parent message pinned at top of panel) */
.qf-thread-origin {
  margin: var(--s-4) var(--s-5) var(--s-2);
  padding: var(--s-3) var(--s-4);
  background: var(--bg-panel);
  border: 1px solid var(--divider);
  border-radius: var(--r-md);
  position: relative;
}
.qf-thread-origin::before {
  content: ''; position: absolute; left: 0; top: var(--s-3); bottom: var(--s-3);
  width: 3px; background: var(--accent); border-radius: var(--r-xs);
}
.qf-thread-origin__meta {
  display: flex; align-items: baseline; gap: var(--s-2);
  font-size: var(--fs-13); margin-bottom: 2px;
}
.qf-thread-origin__author { font-weight: 600; color: var(--text-strong); }
.qf-thread-origin__time { color: var(--text-muted); font-size: var(--fs-11); }
.qf-thread-origin__body {
  font-size: var(--fs-14); color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Thread body (scrolls) */
.qf-thread-body { flex: 1; min-height: 0; overflow-y: auto; padding-bottom: var(--s-3); }

/* Day divider inside thread */
.qf-thread-divider {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5) var(--s-2);
  font: 500 var(--fs-11) var(--font-sans);
  color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
.qf-thread-divider::before,
.qf-thread-divider::after {
  content: ''; flex: 1; height: 1px; background: var(--divider);
}

/* Thread message (compact variant of qf-message) */
.qf-thread-msg {
  padding: var(--s-2) var(--s-5);
  display: grid; grid-template-columns: 32px 1fr; gap: 0 var(--s-3);
  position: relative;
}
.qf-thread-msg:hover { background: var(--bg-row-hover); }
.qf-thread-msg--head { padding-top: var(--s-4); }
.qf-thread-msg--cont .qf-thread-msg__avatar { visibility: hidden; height: 0; }
.qf-thread-msg--cont .qf-thread-msg__meta { display: none; }
.qf-thread-msg__avatar { grid-row: span 2; }
.qf-thread-msg__meta {
  display: flex; align-items: baseline; gap: var(--s-2);
  font-size: var(--fs-13); margin-bottom: 2px;
}
.qf-thread-msg__author { font: 600 var(--fs-14) var(--font-sans); color: var(--text-strong); }
.qf-thread-msg__time { font-size: var(--fs-11); color: var(--text-muted); }
/* 072-N6-5(리뷰 HIGH): 스레드 답글 본문도 채팅 폰트 크기(--fs-chat) 존중. 미설정 시 --fs-14. */
.qf-thread-msg__body { font-size: var(--fs-chat, var(--fs-14)); line-height: var(--lh-normal); color: var(--text); word-wrap: break-word; }

/* Thread composer (smaller than main) */
.qf-thread-composer {
  flex-shrink: 0;
  padding: var(--s-3) var(--s-5) var(--s-4);
  border-top: 1px solid var(--divider);
}
.qf-thread-composer__options {
  display: flex; align-items: center; gap: var(--s-2);
  margin-top: var(--s-2); font-size: var(--fs-12);
  color: var(--text-muted);
}
.qf-thread-composer__checkbox {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
}
.qf-thread-composer__checkbox input { accent-color: var(--accent); }

/* Thread-start indicator under a message (Slack-style "N replies") */
.qf-thread-followup {
  margin: var(--s-1) 0 0 var(--s-8);
  display: flex; align-items: center; gap: var(--s-2);
  background: transparent; border: none; padding: 0; cursor: pointer; text-align: left;
  font-size: var(--fs-12); color: var(--text-muted);
}
.qf-thread-followup__dot { width: 4px; height: 4px; border-radius: 50%; background: var(--text-muted); }
.qf-thread-followup__last { color: var(--text-muted); }

/* Code block */
.qf-code-inline {
  padding: 1px 5px; border-radius: var(--r-xs);
  background: var(--bg-panel); border: 1px solid var(--divider);
  font: 500 0.9em var(--font-mono); color: var(--text-strong);
}
.qf-codeblock {
  margin: 6px 0; padding: var(--s-4);
  background: var(--bg-code); border: 1px solid var(--divider);
  border-radius: var(--r-md); font: 400 var(--fs-13) var(--font-mono);
  color: var(--text-code); overflow-x: auto; line-height: 1.5;
}
.qf-codeblock__lang {
  display: block; font-size: 10px; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--text-muted);
  margin-bottom: var(--s-2); font-family: var(--font-sans); font-weight: 600;
}

/* 구문 강조 테마 — 코드 표면 안 span 색. 하이라이트 엔진(빌드/Prism 등)이 이 클래스를 출력. */
.qf-syntax__tag { color: var(--syntax-tag); }
.qf-syntax__attr { color: var(--syntax-attr); }
.qf-syntax__string { color: var(--syntax-string); }
.qf-syntax__comment { color: var(--syntax-comment); font-style: italic; }
.qf-syntax__punct { color: var(--syntax-punct); }

/* Embed / link preview */
.qf-embed {
  margin: 6px 0; padding: var(--s-4); max-width: 440px;
  background: var(--bg-panel); border-left: 4px solid var(--accent);
  border-radius: var(--r-md); display: flex; flex-direction: column; gap: 6px;
}
.qf-embed__site { font-size: var(--fs-11); color: var(--text-muted); text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.qf-embed__title { font: 600 var(--fs-15) var(--font-sans); color: var(--link); }
.qf-embed__desc { font-size: var(--fs-13); color: var(--text-secondary); line-height: var(--lh-snug); }

/* ───── Navigation · layer: chat ───── */
.qf-serverlist {
  width: var(--w-serverlist); background: var(--bg-app);
  padding: var(--s-4) 0; display: flex; flex-direction: column;
  gap: var(--s-3); align-items: center; overflow-y: auto;
}
.qf-server-btn {
  position: relative; width: 48px; height: 48px;
  border-radius: var(--r-xl);
  background: var(--bg-panel); display: grid; place-items: center;
  color: var(--text); cursor: pointer; border: none;
  transition: border-radius var(--dur-base) var(--ease-spring),
              background var(--dur-fast);
  font: 600 var(--fs-16) var(--font-sans);
}
/* Hover = shape morph only (squircle, neutral fill); selected = accent fill.
   Keeps hover and selected visually distinct (decision 050-1). */
.qf-server-btn:hover { border-radius: var(--r-md); background: var(--bg-hover); color: var(--text-strong); }
.qf-server-btn[aria-selected="true"] { border-radius: var(--r-md); background: var(--accent-solid); color: var(--text-onAccent); }
.qf-server-btn[aria-selected="true"]:hover { background: var(--accent-solid-hover); }
.qf-server-btn::before {
  content: ''; position: absolute; left: -16px; top: 50%;
  width: 4px; height: 0; background: var(--text-strong);
  border-radius: 0 4px 4px 0; transform: translateY(-50%);
  transition: height var(--dur-base) var(--ease-spring);
}
.qf-server-btn:hover::before { height: var(--nav-pill-h-hover); }
.qf-server-btn[aria-selected="true"]::before { height: var(--nav-pill-h-active); }
.qf-server-btn img { width: 100%; height: 100%; border-radius: inherit; transition: border-radius var(--dur-base) var(--ease-spring); }
.qf-server-btn__unread {
  position: absolute; top: -2px; right: -2px;
  min-width: 16px; height: 16px; padding: 0 4px;
  background: var(--danger-600); color: #fff;
  border-radius: var(--r-pill); font: 600 10px var(--font-sans);
  display: grid; place-items: center; border: 2px solid var(--bg-app);
}

.qf-channellist { width: var(--w-channellist); background: var(--bg-panel); display: flex; flex-direction: column; }
.qf-channellist__head {
  padding: var(--s-4) var(--s-5); height: var(--h-topbar);
  border-bottom: 1px solid var(--divider);
  font: 600 var(--fs-15) var(--font-sans); color: var(--text-strong);
  display: flex; align-items: center; justify-content: space-between;
  box-shadow: var(--elev-1);
}
.qf-channellist__body { padding: var(--s-4) var(--s-2); overflow-y: auto; flex: 1; }
.qf-category {
  display: flex; align-items: center; gap: 4px;
  padding: var(--s-4) var(--s-3) var(--s-2);
  font: 600 var(--fs-12) var(--font-sans); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
  cursor: pointer;
}
.qf-channel {
  position: relative;
  display: flex; align-items: center; gap: var(--s-3);
  padding: 0 var(--s-3); min-height: var(--h-channel-row-default); margin: 0;
  border-radius: var(--r-sm); color: var(--text-muted);
  font: 500 var(--fs-14) var(--font-sans); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.qf-channel:hover { background: var(--bg-hover); color: var(--text); }
.qf-channel[aria-selected="true"] { background: var(--bg-selected); color: var(--text-strong); }
.qf-channel--unread { color: var(--text-strong); font-weight: 700; }
/* Unread pill at the sidebar edge (color + weight dual-encode, Slack/Discord). */
.qf-channel--unread::before {
  content: ''; position: absolute; left: calc(-1 * var(--s-2)); top: 50%;
  transform: translateY(-50%);
  width: var(--s-1); height: var(--s-3);
  background: var(--text-strong); border-radius: 0 var(--r-pill) var(--r-pill) 0;
}
.qf-channel__prefix { opacity: 0.8; }
.qf-channel__suffix { margin-left: auto; display: flex; align-items: center; gap: 4px; }

.qf-topbar {
  height: var(--h-topbar); padding: 0 var(--s-5);
  background: var(--bg-chat); border-bottom: 1px solid var(--divider);
  display: flex; align-items: center; gap: var(--s-4);
  box-shadow: var(--elev-1);
}
.qf-topbar__title { font: 600 var(--fs-15) var(--font-sans); color: var(--text-strong); display: flex; align-items: center; gap: var(--s-2); }
.qf-topbar__topic { color: var(--text-muted); font-size: var(--fs-13); border-left: 1px solid var(--divider); padding-left: var(--s-4); }

.qf-memberlist { width: var(--w-memberlist); background: var(--bg-panel); padding: var(--s-4) var(--s-2); overflow-y: auto; }
.qf-memberlist__group { font: 600 var(--fs-12) var(--font-sans); color: var(--text-muted); text-transform: uppercase; letter-spacing: var(--tracking-caps); padding: var(--s-4) var(--s-3) var(--s-2); }
.qf-member {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 6px var(--s-3); border-radius: var(--r-sm); cursor: pointer;
}
.qf-member:hover { background: var(--bg-hover); }
.qf-member__name { font: 500 var(--fs-14) var(--font-sans); color: var(--text-secondary); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qf-member__role-owner { color: var(--text-warn); }
.qf-member__role-mod { color: var(--accent-text); }

/* Typing indicator */
.qf-typing { padding: 0 var(--s-5); height: var(--h-typingbar); display: flex; align-items: center; gap: var(--s-2); font: 400 var(--fs-13) var(--font-sans); color: var(--text-muted); }
.qf-typing__dots { display: inline-flex; gap: 2px; }
.qf-typing__dots span { width: 4px; height: 4px; border-radius: 50%; background: currentColor; animation: qf-typing 1.4s infinite cubic-bezier(0.4, 0, 0.6, 1); }
.qf-typing__dots span:nth-child(2) { animation-delay: 0.16s; }
.qf-typing__dots span:nth-child(3) { animation-delay: 0.32s; }
@keyframes qf-typing { 0%, 70%, 100% { opacity: 0.3; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-4px); } }

/* Voice badge */
.qf-voice { display: inline-flex; align-items: center; gap: 4px; padding: 2px 6px; background: var(--ok-bg); color: var(--text-ok); border-radius: var(--r-xs); font: 600 var(--fs-11) var(--font-sans); }

/* Settings layout */
.qf-settings { display: grid; grid-template-columns: 240px 1fr; gap: 0; height: 100%; }
.qf-settings__nav { background: var(--bg-panel); padding: var(--s-8) var(--s-4); border-right: 1px solid var(--divider); }
.qf-settings__nav-head { font: 600 var(--fs-11) var(--font-sans); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--text-muted); padding: 0 var(--s-3); margin-bottom: var(--s-2); }
.qf-settings__nav-item { display: block; padding: 6px var(--s-3); border-radius: var(--r-sm); color: var(--text-secondary); font: 500 var(--fs-14) var(--font-sans); cursor: pointer; margin-bottom: 2px; }
.qf-settings__nav-item:hover { background: var(--bg-hover); color: var(--text); }
.qf-settings__nav-item[aria-selected="true"] { background: var(--bg-selected); color: var(--text-strong); }
.qf-settings__main { padding: var(--s-9) var(--s-10); overflow-y: auto; max-width: 780px; }

.qf-toggle-row { display: flex; align-items: flex-start; gap: var(--s-5); padding: var(--s-5) 0; border-bottom: 1px solid var(--divider); }
.qf-toggle-row__text { flex: 1; }
.qf-toggle-row__title { font: 600 var(--fs-15) var(--font-sans); color: var(--text-strong); }
.qf-toggle-row__desc { font-size: var(--fs-13); color: var(--text-muted); margin-top: 2px; }

.qf-switch { --w:36px; --h:20px; position: relative; width: var(--w); height: var(--h); background: var(--bg-track); border-radius: var(--r-pill); cursor: pointer; transition: background var(--dur-fast); flex-shrink: 0; }
.qf-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: calc(var(--h) - 4px); height: calc(var(--h) - 4px); background: #fff; border-radius: 50%; transition: transform var(--dur-base) var(--ease-spring); }
.qf-switch[aria-checked="true"] { background: var(--accent); }
.qf-switch[aria-checked="true"]::after { transform: translateX(calc(var(--w) - var(--h))); }

/* Empty / Loading / Error */
.qf-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: var(--s-12) var(--s-7); text-align: center; color: var(--text-muted); gap: var(--s-4); }
.qf-empty__title { font: 600 var(--fs-18) var(--font-sans); color: var(--text-strong); }
.qf-empty__body { font-size: var(--fs-14); max-width: 360px; }

.qf-skel { background: linear-gradient(90deg, var(--bg-hover) 0%, var(--bg-selected) 50%, var(--bg-hover) 100%); background-size: 200% 100%; border-radius: var(--r-sm); animation: qf-skel 1.8s linear infinite; }
@keyframes qf-skel { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

/* Eyebrow label */
/* Eyebrow / UI uppercase labels use sans; mono is reserved for code contexts (decision 050-4). */
.qf-eyebrow { font: 600 var(--fs-11) var(--font-sans); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--accent-text); }

/* Drop insertion indicator — thin splitter-like line shown at the
   point where a dragged channel/category will land. Token-driven,
   2px tall, accent-colored. Rendered between rows/sections by
   ChannelList during an active drag. */
.qf-dropline { height: 2px; margin: 1px var(--s-3); border-radius: var(--r-pill); background: var(--accent); }

/* ───── Settings overlay · layer: generic ───── */
/* Full-viewport layered popup for app settings, channel settings,
   workspace settings, etc. Backdrop + card pattern shared across
   every settings surface — nav + main go inside the card. */
.qf-settings-overlay__backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: var(--z-settings-bg);
}
.qf-settings-overlay__card {
  position: fixed;
  top: var(--s-8); bottom: var(--s-8);
  left: 50%;
  transform: translateX(-50%);
  width: calc(100vw - 2 * var(--s-8));
  max-width: 1200px;
  background: var(--bg-app);
  border-radius: var(--r-xl);
  box-shadow: var(--elev-4);
  overflow: hidden;
  z-index: var(--z-settings);
  display: flex;
  flex-direction: column;
}
.qf-settings-overlay__close {
  position: absolute;
  top: var(--s-3); right: var(--s-3);
  width: 44px; height: 44px;   /* 44px 터치 타깃(a11y) */
  display: grid; place-items: center;
  background: transparent; border: none; border-radius: var(--r-sm);
  color: var(--text-muted); font-size: 20px; cursor: pointer;
  z-index: 1;
}
.qf-settings-overlay__close:hover { background: var(--bg-hover); color: var(--text); }


/* ═══════════════════════════════════════════════════════════════
   Task 050 — Slack/Discord-level upgrade: new component blocks.
   Authored against the 050 token vocabulary. qf-* desktop / qf-m-* mobile.
   ═══════════════════════════════════════════════════════════════ */


/* ───── Chat: Composer · layer: chat ───── */
/* 데스크톱 메인 채널/DM 컴포저. bg-input 셸 + 1px 하어라인,
   세로 스택(포맷바 → 입력행 → 힌트). focus-within 시 accent 보더로
   강조하므로 내부 textarea 는 자체 outline 을 끈다(halo opt-out).
   Slack rich-composer 의 정보밀도를 흡수하되 qufox 톤(미니멀). */
.qf-composer {
  display: flex; flex-direction: column;
  margin: 0 var(--s-5) var(--s-5);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  transition: border-color var(--dur-fast) var(--ease-standard);
}
.qf-composer:focus-within { border-color: var(--accent); }

/* 포맷 toolbar — 기본 접힘(max-height 0). rich 모디파이어 또는
   focus-within 시 펼쳐진다. overflow:hidden 으로 접힘 상태에서
   버튼이 새어나오지 않게 한다. */
.qf-composer__formatbar {
  display: flex; align-items: center; gap: var(--s-1);
  max-height: 0; overflow: hidden;
  padding: 0 var(--s-3);
  opacity: 0;
  transition: max-height var(--dur-base) var(--ease-standard),
              opacity var(--dur-fast) var(--ease-standard),
              padding var(--dur-base) var(--ease-standard);
}
.qf-composer--rich > .qf-composer__formatbar,
.qf-composer:focus-within > .qf-composer__formatbar {
  max-height: 44px; opacity: 1;
  padding: var(--s-2) var(--s-3) 0;
}
.qf-composer__format-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  padding: 0; border: none; background: transparent;
  border-radius: var(--r-sm);
  color: var(--text-muted); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.qf-composer__format-btn:hover { background: var(--bg-hover); color: var(--text); }
.qf-composer__format-btn[aria-pressed="true"] { color: var(--accent); }
.qf-composer__format-btn svg { width: 16px; height: 16px; }
.qf-composer__format-sep { width: 1px; height: 18px; background: var(--divider); margin: 0 var(--s-2); }

/* 입력행 — 첨부 / textarea / 우측 액션. textarea 가 늘어날 때
   첨부·전송이 하단에 정렬되도록 align-items:flex-end. */
.qf-composer__row {
  display: flex; align-items: flex-end; gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
}
.qf-composer__attach {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--s-9); height: var(--s-9); flex-shrink: 0;
  padding: 0; border: none; background: transparent;
  border-radius: var(--r-md);
  color: var(--text-muted); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.qf-composer__attach:hover { background: var(--bg-hover); color: var(--text); }
.qf-composer__attach svg { width: 20px; height: 20px; }

/* textarea — 부모가 bg-input 을 가지므로 투명 배경·보더 없음.
   min-height 는 단일행, max-height 120px 넘으면 스크롤. focus
   outline 은 부모 focus-within 강조로 대체(halo opt-out). */
.qf-composer__input {
  flex: 1; min-width: 0;
  min-height: var(--s-9); max-height: var(--composer-max-h);
  padding: var(--s-3) var(--s-2);
  background: transparent; border: none; resize: none;
  color: var(--text);
  font: 400 var(--fs-15)/var(--lh-snug) var(--font-sans);
  font-size: max(var(--fs-15), var(--composer-font-min)); /* iOS 줌 가드 */
  overflow-y: auto;
}
.qf-composer__input::placeholder { color: var(--text-muted); }
.qf-composer__input:focus { outline: none; }

/* 우측 액션 그룹(이모지 / GIF / 멘션 …) + 전송. */
.qf-composer__actions {
  display: flex; align-items: center; gap: var(--s-1);
  flex-shrink: 0;
}
.qf-composer__action {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--s-9); height: var(--s-9);
  padding: 0; border: none; background: transparent;
  border-radius: var(--r-md);
  color: var(--text-muted); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.qf-composer__action:hover { background: var(--bg-hover); color: var(--text); }
.qf-composer__action svg { width: 20px; height: 20px; }

/* 전송 — accent 배경 아이콘 버튼. 입력이 비면 disabled 로 두어
   n-5 표면 + text-muted 로 비활성 표현. */
.qf-composer__send {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--s-9); height: var(--s-9); flex-shrink: 0;
  margin-left: var(--s-1);
  padding: 0; border: none;
  background: var(--accent-solid); color: var(--text-onAccent);
  border-radius: var(--r-md); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard);
}
.qf-composer__send:hover { background: var(--accent-solid-hover); }
.qf-composer__send:active { background: var(--accent-solid-press); }
.qf-composer__send svg { width: 18px; height: 18px; }
.qf-composer__send:disabled,
.qf-composer__send[disabled] {
  background: var(--bg-track); color: var(--text-muted);
  cursor: not-allowed; pointer-events: none;
}

/* 힌트행(선택적) — 키 가이드 + 우측 글자수. */
.qf-composer__hint {
  display: flex; align-items: center; gap: var(--s-2);
  padding: 0 var(--s-4) var(--s-2);
  font: 500 var(--fs-11) var(--font-sans);
  color: var(--text-muted);
}
.qf-composer__hint kbd {
  padding: 0 var(--s-2);
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--r-xs);
  font: 500 var(--fs-11) var(--font-mono); color: var(--text-secondary);
}
.qf-composer__hint-count { margin-left: auto; font-variant-numeric: tabular-nums; }
.qf-composer__hint-count--over { color: var(--danger-400); }
/* generic 확장(005): 선행 슬롯 + 크기 변형. base/__input/__actions/__send 는 범용,
   __formatbar/--rich 는 챗(리치 텍스트) 확장. */
.qf-composer__lead { display: flex; align-items: center; gap: var(--s-1); flex-shrink: 0; }
.qf-composer--sm .qf-composer__input { min-height: var(--s-9); padding-block: var(--s-2); }
.qf-composer--lg .qf-composer__input { min-height: var(--s-11); }

/* ───── Autocomplete / Slash Menu · layer: chat ───── */
/* 컴포저 위로 뜨는 드롭업. @멘션 · #채널 · :이모지 · 슬래시 커맨드가
   같은 골격을 공유한다. 위치는 컴포저 래퍼 기준 absolute 가정:
   bottom = 컴포저 위 + s-2 여백, 좌우 0. 즉시성·위계는 elev-3 +
   bg-floating + 1px border로 띄워 잡는다. */
.qf-autocomplete {
  position: absolute;
  bottom: calc(100% + var(--s-2));
  left: 0; right: 0;
  max-height: 280px; overflow-y: auto;
  padding: var(--s-2);
  background: var(--bg-floating);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--elev-3);
  z-index: var(--z-dropdown);
}

/* 섹션 헤더 (예: "MEMBERS", "CHANNELS") */
.qf-autocomplete__header {
  padding: var(--s-3) var(--s-3) var(--s-2);
  font: 600 var(--fs-11)/var(--lh-snug) var(--font-sans);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
}

/* 항목: 아바타/아이콘 + 라벨(+서브) + 우측 보조 */
.qf-autocomplete__item {
  position: relative;
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  cursor: pointer; user-select: none;
  transition: background var(--dur-instant) var(--ease-standard);
}
.qf-autocomplete__item:hover { background: var(--bg-hover); }
/* 키보드 네비 강조 — 마우스 hover보다 강한 선택 상태 + 좌측 accent bar */
.qf-autocomplete__item[aria-selected="true"] { background: var(--bg-selected); }
.qf-autocomplete__item[aria-selected="true"]::before {
  content: ''; position: absolute; left: 0; top: 50%;
  width: 2px; height: calc(100% - var(--s-3));
  transform: translateY(-50%);
  background: var(--accent);
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
}

/* 좌측 아바타/아이콘 슬롯 — 작은 글리프는 가운데 정렬 */
.qf-autocomplete__avatar {
  flex-shrink: 0;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--bg-elevated);
  color: var(--text-muted);
  overflow: hidden;
}
.qf-autocomplete__avatar img { width: 100%; height: 100%; object-fit: cover; }

/* 이름 / 서브텍스트 */
.qf-autocomplete__label {
  font: 500 var(--fs-14)/var(--lh-snug) var(--font-sans);
  color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.qf-autocomplete__sub {
  font: 400 var(--fs-13)/var(--lh-snug) var(--font-sans);
  color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 라벨+서브를 한 컬럼으로 묶는 텍스트 본문 */
.qf-autocomplete__text { min-width: 0; flex: 1; display: flex; flex-direction: column; }
/* 라벨과 서브를 한 줄에 두는 인라인 배치 (멤버 핸들 등) */
.qf-autocomplete__text--inline { flex-direction: row; align-items: baseline; gap: var(--s-2); }

/* 우측 보조 (멘션 별칭 · 온라인 dot 등) */
.qf-autocomplete__meta {
  margin-left: auto; flex-shrink: 0;
  display: flex; align-items: center; gap: var(--s-2);
  font: 500 var(--fs-13)/1 var(--font-sans);
  color: var(--text-muted);
}
.qf-autocomplete__meta-dot {
  width: var(--sz-status-dot-sm); height: var(--sz-status-dot-sm);
  border-radius: var(--r-pill);
  background: var(--status-offline);
  flex-shrink: 0;
}
.qf-autocomplete__meta-dot--online  { background: var(--status-online); }
.qf-autocomplete__meta-dot--idle    { background: var(--status-idle); }
.qf-autocomplete__meta-dot--dnd     { background: var(--status-dnd); }

/* @멘션 변형 — 좌측 슬롯이 아바타로, 선택 시 멘션 토큰으로 물든다 */
.qf-autocomplete__item--mention[aria-selected="true"] { background: var(--mention-bg); }
.qf-autocomplete__item--mention[aria-selected="true"]:hover { background: var(--mention-bgHov); }

/* #채널 변형 — 좌측 슬롯은 # 글리프, 사각 라운드 */
.qf-autocomplete__item--channel .qf-autocomplete__avatar {
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-muted);
  font: 600 var(--fs-16)/1 var(--font-sans);
}
.qf-autocomplete__item--channel[aria-selected="true"] .qf-autocomplete__avatar { color: var(--text); }

/* :이모지 변형 — 큰 글리프 + shortcode */
.qf-autocomplete__item--emoji .qf-autocomplete__avatar {
  width: 28px; height: 28px;
  background: transparent;
  font-size: var(--fs-20);
  line-height: 1;
}
.qf-autocomplete__shortcode {
  font: 500 var(--fs-14)/var(--lh-snug) var(--font-mono);
  color: var(--text-secondary);
}
.qf-autocomplete__item--emoji[aria-selected="true"] .qf-autocomplete__shortcode { color: var(--text-strong); }

/* ───── Slash Menu · layer: chat ───── */
/* 슬래시 커맨드 전용 변형 — autocomplete 골격을 상속하되 좌측 슬래시
   아이콘 + monospace 커맨드명 + 설명 위계를 강조한다. */
.qf-slash-menu { /* qf-autocomplete 컨테이너 위에 함께 부착 */ }

.qf-slash-menu__item {
  position: relative;
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3);
  border-radius: var(--r-sm);
  cursor: pointer; user-select: none;
  transition: background var(--dur-instant) var(--ease-standard);
}
.qf-slash-menu__item:hover { background: var(--bg-hover); }
.qf-slash-menu__item[aria-selected="true"] { background: var(--bg-selected); }
.qf-slash-menu__item[aria-selected="true"]::before {
  content: ''; position: absolute; left: 0; top: 50%;
  width: 2px; height: calc(100% - var(--s-3));
  transform: translateY(-50%);
  background: var(--accent);
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
}

/* 좌측 슬래시 아이콘 슬롯 */
.qf-slash-menu__icon {
  flex-shrink: 0;
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--accent-subtle);
  color: var(--accent);
  font: 600 var(--fs-16)/1 var(--font-mono);
}
.qf-slash-menu__item[aria-selected="true"] .qf-slash-menu__icon { background: var(--mention-bg); }

/* 텍스트 컬럼 (커맨드 + 설명 스택) */
.qf-slash-menu__text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }

/* monospace 커맨드명 + 인자 힌트 */
.qf-slash-menu__cmd {
  font: 500 var(--fs-14)/var(--lh-snug) var(--font-mono);
  color: var(--text-strong);
}
.qf-slash-menu__arg {
  margin-left: var(--s-2);
  font: 400 var(--fs-13)/var(--lh-snug) var(--font-mono);
  color: var(--text-muted);
}
.qf-slash-menu__desc {
  font: 400 var(--fs-13)/var(--lh-snug) var(--font-sans);
  color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 우측 단축키 힌트 (선택) */
.qf-slash-menu__kbd {
  margin-left: auto; flex-shrink: 0;
  font: 500 var(--fs-11)/1 var(--font-mono);
  color: var(--text-muted);
}

/* ── 밀도: compact ── */
[data-density="compact"] .qf-autocomplete__item { padding: var(--s-1) var(--s-3); gap: var(--s-2); }
[data-density="compact"] .qf-autocomplete__avatar { width: 20px; height: 20px; }
[data-density="compact"] .qf-slash-menu__item { padding: var(--s-2) var(--s-3); }
[data-density="compact"] .qf-slash-menu__icon { width: 24px; height: 24px; font-size: var(--fs-14); }
[data-density="compact"] .qf-slash-menu__desc { display: none; }


/* ───── Command Palette (Cmd+K quick switcher) · layer: generic ───── */
.qf-cmd-palette-backdrop {
  position: fixed; inset: 0;
  background: rgba(10,8,30,0.65);
  backdrop-filter: blur(4px);
  display: flex; flex-direction: column; align-items: center;
  padding: 20vh var(--s-7) var(--s-7);
  z-index: var(--z-modal-bg);
}
.qf-cmd-palette {
  width: min(600px, calc(100vw - 2 * var(--s-7)));
  max-height: 60vh;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--elev-4);
  overflow: hidden;
  display: flex; flex-direction: column;
  z-index: var(--z-modal);
}

/* 상단 큰 검색 입력 — 보더 없이 하단 divider만, 좌측 search 아이콘 */
.qf-cmd-palette__search {
  display: flex; align-items: center; gap: var(--s-4);
  padding: 0 var(--s-5);
  height: 56px; flex-shrink: 0;
  border-bottom: 1px solid var(--divider);
}
.qf-cmd-palette__search-icon {
  width: 20px; height: 20px; flex-shrink: 0;
  display: grid; place-items: center;
  color: var(--text-muted);
}
.qf-cmd-palette__search-icon svg { width: 20px; height: 20px; }
.qf-cmd-palette__input {
  flex: 1; min-width: 0;
  background: transparent; border: none;
  color: var(--text-strong);
  font: 400 var(--fs-16)/var(--lh-snug) var(--font-sans);
}
.qf-cmd-palette__input::placeholder { color: var(--text-muted); }
/* 텍스트 입력 — focus halo opt-out (caret 가 cue) */
.qf-cmd-palette__input:focus { outline: none; }

/* 스크롤 결과 영역 */
.qf-cmd-palette__list {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: var(--s-2) var(--s-2) var(--s-3);
}
.qf-cmd-palette__section {
  padding: var(--s-4) var(--s-3) var(--s-2);
  font: 600 var(--fs-11)/var(--lh-tight) var(--font-sans);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
}

/* 결과 row — 좌측 아이콘/아바타 + 라벨 + 우측 kbd/경로 */
.qf-cmd-palette__row {
  position: relative;
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  color: var(--text-secondary);
  cursor: pointer; user-select: none;
}
.qf-cmd-palette__row:hover,
.qf-cmd-palette__row[aria-selected="true"] {
  background: var(--bg-selected);
  color: var(--text-strong);
}
/* 선택 행 좌측 accent bar */
.qf-cmd-palette__row[aria-selected="true"]::before {
  content: ''; position: absolute;
  left: 0; top: 50%; transform: translateY(-50%);
  width: 3px; height: 60%;
  background: var(--accent);
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
}
.qf-cmd-palette__row-icon {
  width: 20px; height: 20px; flex-shrink: 0;
  display: grid; place-items: center;
  color: var(--text-muted);
}
.qf-cmd-palette__row-icon svg { width: 18px; height: 18px; }
.qf-cmd-palette__row:hover .qf-cmd-palette__row-icon,
.qf-cmd-palette__row[aria-selected="true"] .qf-cmd-palette__row-icon {
  color: var(--text-secondary);
}
.qf-cmd-palette__label {
  flex: 1; min-width: 0;
  font: 500 var(--fs-14)/var(--lh-snug) var(--font-sans);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 우측 경로 텍스트 (예: "general · qufox") */
.qf-cmd-palette__path {
  flex-shrink: 0;
  font: 400 var(--fs-12) var(--font-sans);
  color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 45%;
}
/* 우측 mono kbd 힌트 */
.qf-cmd-palette__kbd {
  flex-shrink: 0;
  padding: 2px var(--s-2); min-width: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: var(--r-xs);
  font: 500 var(--fs-11)/1 var(--font-mono);
  color: var(--text-muted);
}
.qf-cmd-palette__empty {
  padding: var(--s-9) var(--s-5);
  text-align: center;
  font: 400 var(--fs-14) var(--font-sans);
  color: var(--text-muted);
}

/* ───── Emoji Picker · layer: generic ───── */
.qf-emoji-picker {
  width: 350px; height: 420px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--elev-4);
  display: flex; flex-direction: column;
  overflow: hidden;
}

/* 상단 검색 입력 */
.qf-emoji-picker__search {
  flex-shrink: 0;
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--divider);
}
.qf-emoji-picker__search-field {
  display: flex; align-items: center; gap: var(--s-3);
  height: 36px; padding: 0 var(--s-3);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.qf-emoji-picker__search-icon {
  width: 16px; height: 16px; flex-shrink: 0;
  display: grid; place-items: center;
  color: var(--text-muted);
}
.qf-emoji-picker__search-icon svg { width: 16px; height: 16px; }
.qf-emoji-picker__search-input {
  flex: 1; min-width: 0;
  background: transparent; border: none;
  color: var(--text);
  font: 400 var(--fs-14) var(--font-sans);
}
.qf-emoji-picker__search-input::placeholder { color: var(--text-muted); }
.qf-emoji-picker__search-input:focus { outline: none; }

/* 카테고리 가로 탭 */
.qf-emoji-picker__tabs {
  flex-shrink: 0;
  display: flex; align-items: stretch;
  padding: 0 var(--s-2);
  border-bottom: 1px solid var(--divider);
}
.qf-emoji-picker__tab {
  position: relative;
  flex: 1;
  height: 36px;
  display: grid; place-items: center;
  background: transparent; border: none;
  color: var(--text-muted); cursor: pointer;
  font-size: 18px; line-height: 1;
  border-radius: var(--r-sm);
}
.qf-emoji-picker__tab:hover { color: var(--text-secondary); background: var(--bg-hover); }
.qf-emoji-picker__tab[aria-selected="true"] { color: var(--text-strong); }
/* 선택 시 accent 하단 표시 */
.qf-emoji-picker__tab[aria-selected="true"]::after {
  content: ''; position: absolute;
  left: var(--s-4); right: var(--s-4); bottom: 0;
  height: 2px; background: var(--accent);
  border-radius: 2px 2px 0 0;
}

/* 9열 grid */
.qf-emoji-picker__grid {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: var(--s-2) var(--s-3) var(--s-3);
}
/* 섹션 라벨 sticky */
.qf-emoji-picker__category-label {
  position: sticky; top: 0; z-index: var(--z-sticky);
  padding: var(--s-3) var(--s-1) var(--s-2);
  background: var(--bg-elevated);
  font: 600 var(--fs-11)/var(--lh-tight) var(--font-sans);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
}
.qf-emoji-picker__row {
  display: grid; grid-template-columns: repeat(9, 1fr); gap: var(--s-1);
}
.qf-emoji-picker__cell {
  width: 100%; aspect-ratio: 1;
  display: grid; place-items: center;
  background: transparent; border: none;
  border-radius: var(--r-sm);
  font-size: 22px; line-height: 1;
  cursor: pointer; user-select: none;
}
.qf-emoji-picker__cell:hover { background: var(--bg-hover); }
.qf-emoji-picker__cell[aria-selected="true"] { background: var(--bg-selected); }

/* 하단 미리보기 + shortcode */
.qf-emoji-picker__preview {
  flex-shrink: 0;
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  height: 44px;
  border-top: 1px solid var(--divider);
}
.qf-emoji-picker__preview-emoji {
  font-size: 24px; line-height: 1; flex-shrink: 0;
}
.qf-emoji-picker__preview-shortcode {
  font: 500 var(--fs-13) var(--font-mono);
  color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.qf-emoji-picker__preview--empty .qf-emoji-picker__preview-shortcode {
  font-family: var(--font-sans);
  color: var(--text-muted);
}


/* ───── Status picker · layer: chat ───── */
/* 상태 선택 팝오버 — 온라인/자리비움/방해금지/오프라인 + 커스텀 상태.
   bg-floating(n-0) 위 떠 있는 면, elev-3. */
.qf-status-picker {
  min-width: 260px; padding: var(--s-2);
  background: var(--bg-floating); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--elev-3);
}
.qf-status-picker__item {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3); border-radius: var(--r-sm);
  cursor: pointer; user-select: none;
}
.qf-status-picker__item:hover { background: var(--bg-hover); }
.qf-status-picker__item[aria-selected="true"] { background: var(--bg-selected); }
.qf-status-picker__dot {
  flex-shrink: 0;
  width: var(--sz-status-dot-sm); height: var(--sz-status-dot-sm);
  border-radius: var(--r-pill); background: var(--status-offline);
}
.qf-status-picker__dot--online  { background: var(--status-online); }
.qf-status-picker__dot--idle    { background: var(--status-idle); }
.qf-status-picker__dot--dnd     { background: var(--status-dnd); }
.qf-status-picker__dot--offline { background: var(--status-offline); }
.qf-status-picker__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.qf-status-picker__label { font: 500 var(--fs-14) var(--font-sans); color: var(--text); }
.qf-status-picker__desc { font-size: var(--fs-12); color: var(--text-muted); line-height: var(--lh-snug); }
.qf-status-picker__sep { height: 1px; background: var(--divider); margin: var(--s-2) 0; }
/* 커스텀 상태 입력 행: 이모지 버튼 + 텍스트 입력 */
.qf-status-picker__custom {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
}
.qf-status-picker__emoji {
  flex-shrink: 0; width: 28px; height: 28px;
  display: grid; place-items: center;
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: var(--r-sm); cursor: pointer;
  font-size: var(--fs-15); color: var(--text-secondary);
}
.qf-status-picker__emoji:hover { background: var(--bg-hover); border-color: var(--border-strong); }
.qf-status-picker__custom-input {
  flex: 1; min-width: 0; height: 28px; padding: 0 var(--s-3);
  background: var(--bg-input); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  font: 400 var(--fs-13) var(--font-sans);
}
.qf-status-picker__custom-input::placeholder { color: var(--text-muted); }
.qf-status-picker__custom-input:focus { outline: none; } /* 텍스트 입력 halo opt-out */

/* ───── Hovercard · layer: chat ───── */
/* 유저 프로필 hovercard(280px) — 배너 + 걸친 아바타 + 프로필 메타.
   bg-floating, r-xl, elev-4. */
.qf-hovercard {
  width: 280px;
  background: var(--bg-floating); border: 1px solid var(--border);
  border-radius: var(--r-xl); box-shadow: var(--elev-4);
  overflow: hidden;
}
.qf-hovercard__banner {
  height: 60px;
  background: linear-gradient(120deg, var(--accent) 0%, var(--accent-solid-hover) 100%);
}
.qf-hovercard__banner--plain { background: var(--bg-panel); }
.qf-hovercard__body { padding: 0 var(--s-5) var(--s-5); }
/* 아바타 xl을 배너에 걸침 — 음수 마진 + bg-floating 링 */
.qf-hovercard__avatar {
  margin-top: calc(-1 * var(--s-9));
  margin-bottom: var(--s-3);
  border: var(--s-1) solid var(--bg-floating);
  border-radius: var(--r-pill);
}
.qf-hovercard__name { font: 700 var(--fs-18) var(--font-sans); color: var(--text-strong); letter-spacing: var(--tracking-tight); }
.qf-hovercard__handle { font: 500 var(--fs-13) var(--font-sans); color: var(--text-muted); margin-top: 1px; }
.qf-hovercard__status {
  display: flex; align-items: center; gap: var(--s-2);
  margin-top: var(--s-3);
  font-size: var(--fs-13); color: var(--text-secondary);
}
.qf-hovercard__status-dot {
  width: var(--sz-status-dot-sm); height: var(--sz-status-dot-sm);
  border-radius: var(--r-pill); background: var(--status-online); flex-shrink: 0;
}
.qf-hovercard__roles { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-4); }
.qf-hovercard__about {
  margin-top: var(--s-4); padding-top: var(--s-4);
  border-top: 1px solid var(--divider);
  font-size: var(--fs-13); color: var(--text-secondary); line-height: var(--lh-snug);
}
.qf-hovercard__actions { margin-top: var(--s-4); }
.qf-hovercard__actions .qf-btn { width: 100%; }

/* ───── Search overlay · layer: chat ───── */
/* 검색바 + 결과 + 필터칩. 2패널(좌 결과 / 우 옵션) 또는 적층.
   bg-floating 위 elev-4 카드. */
.qf-search-overlay {
  display: flex; flex-direction: column;
  width: min(760px, calc(100vw - var(--s-10)));
  max-height: 70vh;
  background: var(--bg-floating); border: 1px solid var(--border);
  border-radius: var(--r-xl); box-shadow: var(--elev-4);
  overflow: hidden;
}
.qf-search-overlay__bar {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--divider);
}
.qf-search-overlay__bar svg { width: 18px; height: 18px; color: var(--text-muted); flex-shrink: 0; }
.qf-search-overlay__input {
  flex: 1; min-width: 0; height: 28px; border: none; background: transparent;
  color: var(--text); font: 400 var(--fs-16) var(--font-sans);
}
.qf-search-overlay__input::placeholder { color: var(--text-muted); }
.qf-search-overlay__input:focus { outline: none; } /* 텍스트 입력 halo opt-out */
.qf-search-overlay__filters {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  padding: var(--s-3) var(--s-5);
  border-bottom: 1px solid var(--divider);
}
/* from:/in:/has: 필터 칩 */
.qf-search-overlay__chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-1) var(--s-3);
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: var(--r-sm); cursor: pointer;
  font: 500 var(--fs-12) var(--font-mono); color: var(--text-secondary);
}
.qf-search-overlay__chip:hover { background: var(--bg-hover); border-color: var(--border-strong); color: var(--text); }
.qf-search-overlay__chip[aria-pressed="true"] { background: var(--accent-subtle); border-color: var(--accent); color: var(--accent-on-subtle); }
.qf-search-overlay__chip-key { color: var(--text-muted); }
.qf-search-overlay__results { overflow-y: auto; padding: var(--s-2); flex: 1; min-height: 0; }
.qf-search-overlay__result {
  display: flex; flex-direction: column; gap: var(--s-1);
  padding: var(--s-3) var(--s-4); border-radius: var(--r-sm);
  cursor: pointer;
}
.qf-search-overlay__result:hover { background: var(--bg-hover); }
.qf-search-overlay__result-ctx {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-12); color: var(--text-muted);
}
.qf-search-overlay__result-ctx strong { color: var(--text-secondary); font-weight: 600; }
.qf-search-overlay__result-preview {
  font-size: var(--fs-14); color: var(--text); line-height: var(--lh-snug);
}
.qf-search-overlay__result-preview mark { background: var(--mention-bg); color: var(--accent-on-subtle); border-radius: var(--r-xs); padding: 0 2px; }
.qf-search-overlay__jump {
  align-self: flex-start; margin-top: var(--s-1);
  font: 600 var(--fs-11) var(--font-sans); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
.qf-search-overlay__result:hover .qf-search-overlay__jump { color: var(--accent); }

/* ───── Upload overlay · layer: generic ───── */
/* 첨부 드래그앤드롭 — 채팅 영역 위 점선 테두리 + 큰 아이콘 + 안내.
   accent 점선 border, 알파 딤(mention-bg 재사용). */
.qf-upload-overlay {
  position: absolute; inset: var(--s-3);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s-4);
  background: var(--accent-bg);
  border: 2px dashed var(--accent);
  border-radius: var(--r-xl);
  z-index: var(--z-dropdown);
  pointer-events: none;
}
.qf-upload-overlay__icon {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  color: var(--accent);
}
.qf-upload-overlay__icon svg { width: 48px; height: 48px; }
.qf-upload-overlay__title { font: 700 var(--fs-20) var(--font-sans); color: var(--text-strong); letter-spacing: var(--tracking-tight); }
.qf-upload-overlay__hint { font-size: var(--fs-13); color: var(--text-secondary); }

/* ───── Banner · layer: generic ───── */
/* 상단 notice/배너 — info/warn/danger/accent 4종.
   좌측 4px 상태색 바 + 배경 틴트, 좌 아이콘 + 메시지 + 우 액션/닫기. */
.qf-banner {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4) var(--s-3) var(--s-5);
  border-left: 4px solid var(--accent);
  background: var(--accent-subtle);
  font: 500 var(--fs-13) var(--font-sans); color: var(--text);
}
.qf-banner__icon { flex-shrink: 0; display: grid; place-items: center; color: var(--accent); }
.qf-banner__icon svg { width: 18px; height: 18px; }
.qf-banner__msg { flex: 1; min-width: 0; line-height: var(--lh-snug); }
.qf-banner__msg strong { color: var(--text-strong); font-weight: 600; }
.qf-banner__actions { display: flex; align-items: center; gap: var(--s-2); flex-shrink: 0; }
.qf-banner__close {
  flex-shrink: 0; width: 24px; height: 24px;
  display: grid; place-items: center;
  border: none; background: transparent; border-radius: var(--r-sm);
  color: var(--text-muted); cursor: pointer;
}
.qf-banner__close:hover { background: var(--bg-hover); color: var(--text); }
/* 변형 — 좌측 바 + 틴트 색상 */
.qf-banner--info   { border-left-color: var(--info-400); background: var(--bg-panel); }
.qf-banner--info   .qf-banner__icon { color: var(--info-400); }
.qf-banner--warn   { border-left-color: var(--warn-400); background: var(--bg-panel); }
.qf-banner--warn   .qf-banner__icon { color: var(--warn-400); }
.qf-banner--danger { border-left-color: var(--danger-400); background: var(--bg-panel); }
.qf-banner--danger .qf-banner__icon { color: var(--danger-400); }
.qf-banner--accent { border-left-color: var(--accent); background: var(--accent-subtle); }
.qf-banner--accent .qf-banner__icon { color: var(--accent); }

/* ───── Keyboard shortcut sheet · layer: generic ───── */
/* 단축키 시트 — grid 2열, 그룹별 행(설명 + 키캡). 모달 내부 가정. */
.qf-kbd-sheet {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--s-6) var(--s-8);
}
.qf-kbd-sheet__group { display: flex; flex-direction: column; gap: var(--s-2); }
.qf-kbd-sheet__group-title {
  font: 600 var(--fs-11) var(--font-sans); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
  margin-bottom: var(--s-2);
}
.qf-kbd-sheet__row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  padding: var(--s-2) 0;
  font-size: var(--fs-14); color: var(--text-secondary);
}
.qf-kbd-sheet__keys { display: inline-flex; align-items: center; gap: var(--s-1); flex-shrink: 0; }
.qf-kbd-sheet__plus { color: var(--text-muted); font-size: var(--fs-11); }
/* 키캡 — mono kbd */
.qf-kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 var(--s-2);
  background: var(--bg-input); border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: var(--r-sm);
  font: 600 var(--fs-11) var(--font-mono); color: var(--text-strong);
}

/* ───── Role chip · layer: chat ───── */
/* 역할 칩 — color dot + 역할명, pill, 역할색 알파 배경.
   데모는 토큰 화이트리스트(violet/status)만; 실제 역할색은
   --role-color 인라인 var 주입 가정(주석 참고). */
.qf-role-chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-1) var(--s-3);
  background: var(--accent-subtle);
  border-radius: var(--r-pill);
  font: 500 var(--fs-12) var(--font-sans); color: var(--text-secondary);
}
.qf-role-chip__dot {
  width: 8px; height: 8px; border-radius: var(--r-pill);
  background: var(--accent); flex-shrink: 0;
  /* 실제 역할색은 style="--role-color:…"로 인라인 주입,
     아래 변형이 var(--role-color)를 소비하는 형태를 권장 */
}
/* 토큰 화이트리스트 내 데모 변형 */
.qf-role-chip--admin   .qf-role-chip__dot { background: var(--danger-400); }
.qf-role-chip--mod     .qf-role-chip__dot { background: var(--accent-text); }
.qf-role-chip--online  .qf-role-chip__dot { background: var(--status-online); }
.qf-role-chip--bot     .qf-role-chip__dot { background: var(--info-400); }

/* ───── Embed variants · layer: chat ───── */
/* 기존 qf-embed 변형 — 이미지/비디오/봇 메시지 임베드. */
/* 이미지 썸네일 임베드 */
.qf-embed--image { padding: 0; border-left: none; overflow: hidden; max-width: 400px; }
.qf-embed--image .qf-embed__thumb { display: block; width: 100%; border-radius: var(--r-md); }
/* 비디오 — 플레이 오버레이 */
.qf-embed--video { padding: 0; border-left: none; overflow: hidden; max-width: 400px; position: relative; }
.qf-embed--video .qf-embed__thumb { display: block; width: 100%; border-radius: var(--r-md); }
.qf-embed__play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 56px; height: 56px; border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: var(--bg-floating); border: 1px solid var(--border-strong);
  box-shadow: var(--elev-3); color: var(--text-strong);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-standard), background var(--dur-fast);
}
.qf-embed__play:hover { background: var(--accent); color: var(--text-onAccent); transform: translate(-50%, -50%) scale(1.06); }
.qf-embed__play svg { width: 22px; height: 22px; margin-left: 2px; }
/* 봇/앱 메시지 임베드 — 액션 버튼 행 */
.qf-embed--bot { border-left-color: var(--info-400); }
.qf-embed--bot .qf-embed__app {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-11); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
.qf-embed--bot .qf-embed__app-badge {
  padding: 1px var(--s-2); border-radius: var(--r-xs);
  background: var(--accent-subtle); color: var(--accent-on-subtle);
  font: 600 var(--fs-11) var(--font-sans);
  text-transform: none; letter-spacing: var(--tracking-normal);
}
.qf-embed__actions {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  margin-top: var(--s-2); padding-top: var(--s-3);
  border-top: 1px solid var(--divider);
}


/* ───── Message quick-react bar · layer: chat ───── */
/* 메시지 hover 액션바(qf-message__toolbar) 내부의 빠른 반응 3슬롯.
   자주 쓰는 이모지 3개 + 더보기 아이콘. 작은 정사각 아이콘 버튼 행. */
.qf-msg-quickreact {
  display: inline-flex; align-items: center; gap: 2px;
}
.qf-msg-quickreact__slot {
  display: grid; place-items: center;
  width: 28px; height: 28px;
  padding: 0; border: none; background: transparent;
  border-radius: var(--r-sm);
  font: 400 var(--fs-15)/1 var(--font-sans);
  color: var(--text-secondary); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              transform var(--dur-fast) var(--ease-spring);
}
.qf-msg-quickreact__slot:hover { background: var(--bg-hover); transform: scale(1.12); }
.qf-msg-quickreact__slot:active { background: var(--bg-selected); transform: scale(0.96); }
.qf-msg-quickreact__slot svg { width: 16px; height: 16px; }
/* 더보기(+) 슬롯은 hover 시 accent 로 끌어올린다. */
.qf-msg-quickreact__more { color: var(--text-muted); }
.qf-msg-quickreact__more:hover { color: var(--accent); background: var(--bg-hover); }

/* ───── Reaction: add button · layer: chat ───── */
/* 기존 .qf-reaction 행 끝에 붙는 반응 추가 pill. dashed 외곽 + 아이콘. */
.qf-reaction--add {
  background: transparent;
  border: 1px dashed var(--border-strong);
  color: var(--text-muted);
  padding: 2px 8px;
}
.qf-reaction--add:hover {
  background: var(--accent-subtle);
  border-color: var(--accent);
  border-style: dashed;
  color: var(--accent);
}
.qf-reaction--add svg { width: 14px; height: 14px; }

/* ───── Code block: header + copy · layer: generic ───── */
/* 기존 .qf-codeblock 본문/.qf-codeblock__lang(블록 내부 인라인 라벨)은 그대로 두고,
   별도 상단 헤더 바를 옵션으로 얹는 element 들만 추가한다.
   __lang(인라인) 과 __header(상단 바)는 공존 가능 — 사용처에서 택일. */
.qf-codeblock__header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  background: var(--bg-panel);
  border: 1px solid var(--divider);
  border-bottom: none;
  border-radius: var(--r-md) var(--r-md) 0 0;
}
/* 헤더가 있을 때 본문(.qf-codeblock)은 위 모서리를 평평하게 — 사용처에서
   .qf-codeblock--with-header 를 함께 부여. */
.qf-codeblock--with-header {
  margin-top: 0;
  border-top-left-radius: 0; border-top-right-radius: 0;
}
.qf-codeblock__header-lang {
  font: 600 var(--fs-11)/1 var(--font-mono);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
}
.qf-codeblock__copy {
  display: inline-flex; align-items: center; gap: var(--s-2);
  height: 24px; padding: 0 var(--s-2);
  border: none; background: transparent;
  border-radius: var(--r-sm);
  font: 500 var(--fs-11) var(--font-sans);
  color: var(--text-muted); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.qf-codeblock__copy:hover { background: var(--bg-hover); color: var(--text); }
.qf-codeblock__copy:active { background: var(--bg-selected); }
.qf-codeblock__copy svg { width: 14px; height: 14px; }
/* 복사 직후 상태 — 사용처에서 data-copied="true" 토글, "Copied" 라벨 노출. */
.qf-codeblock__copy[data-copied="true"] { color: var(--ok-400); }

/* ───── Embed: dismiss · layer: chat ───── */
/* 링크 임베드(.qf-embed) 우상단 닫기(x). 임베드는 position:relative 필요 —
   사용처에서 .qf-embed 에 position:relative 부여(or 래퍼). */
.qf-embed__dismiss {
  position: absolute; top: var(--s-2); right: var(--s-2);
  display: grid; place-items: center;
  width: 20px; height: 20px;
  border: none; background: transparent;
  border-radius: var(--r-sm);
  color: var(--text-muted); cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.qf-embed:hover .qf-embed__dismiss { opacity: 1; }
.qf-embed__dismiss:hover { background: var(--bg-hover); color: var(--text); }
.qf-embed__dismiss:focus-visible { opacity: 1; }
.qf-embed__dismiss svg { width: 14px; height: 14px; }

/* ───── Thread follow-up: avatars + count · layer: chat ───── */
/* 기존 .qf-thread-followup("N replies") 보강 element. 참여자 아바타 스택과
   accent 카운트를 추가한다(기존 __dot/__last 와 공존). */
.qf-thread-followup__avatars { display: flex; align-items: center; }
.qf-thread-followup__avatars > * {
  margin-left: -6px;
  border: 2px solid var(--bg-chat); border-radius: var(--r-pill);
}
.qf-thread-followup__avatars > *:first-child { margin-left: 0; }
.qf-thread-followup__count { color: var(--accent); font-weight: 600; }
.qf-thread-followup:hover .qf-thread-followup__count { color: var(--accent-hover); }

/* ───── Density: cozy (default) / compact · layer: chat ───── */
/* cozy 는 기본값 — 토큰화된 값만 참조해 compact 와 의미를 맞춘다.
   spacious 토큰(--msg-group-gap-spacious 등)은 예약, 규칙 미작성. */
/* cozy continuation rows keep the original tight base (.qf-message padding-top
   = s-2); only the head row carries the group gap, re-expressed via the density
   token so the compact override below stays symmetric. */
.qf-message--head { padding-top: var(--msg-pt-head-cozy); }

/* compact: 그룹 간격을 좁히고, 연속 메시지는 아바타 폭만큼 인덴트.
   아바타는 head 에서 축소(32px), cont 에서 숨김. */
[data-density="compact"] .qf-message {
  padding-top: var(--msg-group-gap-compact);
}
[data-density="compact"] .qf-message--head {
  padding-top: var(--msg-pt-head-compact);
}
[data-density="compact"] .qf-message__avatar {
  width: var(--avatar-msg-compact); height: var(--avatar-msg-compact);
}
[data-density="compact"] .qf-message__author { font-size: var(--fs-14); }
/* 072-N6-5(리뷰 HIGH): compact 도 채팅 폰트 크기(--fs-chat) 존중. 미설정 시 compact 기본(--fs-14). */
[data-density="compact"] .qf-message__body { font-size: var(--fs-chat, var(--fs-14)); }
/* compact 연속 메시지: 아바타 자리 대신 인덴트로 본문 정렬 유지 */
[data-density="compact"] .qf-message--cont { padding-left: var(--msg-indent); }
[data-density="compact"] .qf-message--cont .qf-message__avatar { display: none; }

/* ───── Spring entrances: badge / reaction · layer: generic, chat (공유 keyframes) ───── */
/* 뱃지 등장(scale 0.6→1.1→1). 적용 예: .qf-badge--count, .qf-server-btn__unread. */
@keyframes qf-badge-in {
  0%   { transform: scale(0.6); opacity: 0; }
  60%  { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(1); }
}
/* 반응 추가 등장(살짝 약한 spring). 적용 예: 새로 추가된 .qf-reaction. */
@keyframes qf-react-in {
  0%   { transform: scale(0.7); opacity: 0; }
  55%  { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); }
}
/* 적용 클래스 예시 — 사용처에서 등장 시점에 부여:
   .qf-badge--count.is-enter { animation: qf-badge-in var(--dur-base) var(--ease-spring); will-change: transform; }
   .qf-reaction.is-enter     { animation: qf-react-in var(--dur-base) var(--ease-spring); will-change: transform; } */
.qf-badge--count.is-enter {
  animation: qf-badge-in var(--dur-base) var(--ease-spring);
  will-change: transform;
}
.qf-reaction.is-enter {
  animation: qf-react-in var(--dur-base) var(--ease-spring);
  will-change: transform;
}


/* ───── Overlay entrance motion ─────
   Subtle mount animations for the original floating surfaces. The global
   prefers-reduced-motion guard in tokens.css neutralizes these for users
   who opt out. (New 050 overlays carry their own entrance where needed.) */
@keyframes qf-pop-in {
  from { opacity: 0; transform: translateY(var(--s-1)) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes qf-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes qf-modal-in {
  from { opacity: 0; transform: translateY(var(--s-3)) scale(0.96); }
  to   { opacity: 1; transform: none; }
}
.qf-menu,
.qf-tooltip { animation: qf-pop-in var(--dur-fast) var(--ease-emphasized); }
.qf-modal { animation: qf-modal-in var(--dur-base) var(--ease-emphasized); }
.qf-modal-backdrop,
.qf-settings-overlay__backdrop { animation: qf-fade-in var(--dur-fast) var(--ease-standard); }
.qf-settings-overlay__card { animation: qf-modal-in var(--dur-base) var(--ease-emphasized); }

/* ============================================================
 * D18 — 인증 화면(로그인/가입/2FA/인증대기/비번재설정) 전용 컴포넌트.
 * 폼 기본은 .qf-field / .qf-input / .qf-btn 을 재사용하고, 아래는 인증
 * 화면이 추가로 필요로 하는 조각만 정의한다. 전부 DS 토큰 기반(raw hex/px
 * 금지 · 1px 하어라인 border 만 예외). 신규 도입분이라 기존 화면엔 dormant.
 * ============================================================ */

/* 알림 박스 — .qf-field__error(인라인 폼 에러)와 구분되는 정보/경고/위험
 * 등급 안내 블록. 좌측 강조선 + 아이콘 슬롯으로 색 단독 의존을 피한다
 * (WCAG 1.4.1). 계정 복구 안내·세션 만료 배너처럼 발견성이 중요한 안내용. */
.qf-notice {
  display: flex; gap: var(--s-3); align-items: flex-start;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--border);
  border-left-width: var(--s-1);
  border-left-color: var(--info-400);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
  color: var(--text-secondary);
  font-size: var(--fs-13); line-height: var(--lh-normal);
}
/* 아이콘 svg 가 inline-block(vertical-align)이라 baseline 여백으로 span 이 텍스트 줄보다 커지면,
   align-items:flex-start 에서 한 줄 본문이 위로 쏠려 보인다. flex 로 감싸 여백을 없애고 첫 줄에 맞춘다. */
.qf-notice__icon { flex-shrink: 0; color: var(--info-400); display: flex; }
.qf-notice__body { flex: 1; min-width: 0; }
/* 본문 안 <p>(특히 title)의 UA 기본 margin 이 살아 있으면 제목이 아이콘보다 아래로 밀리고
   notice 위쪽 여백이 두꺼워져 상하 정렬이 어긋나 보인다. 가장자리 margin 을 접어 균형을 맞춘다. */
.qf-notice__title { font-weight: 600; color: var(--text-strong); margin: 0 0 var(--s-1); }
.qf-notice__body > :first-child { margin-top: 0; }
.qf-notice__body > :last-child { margin-bottom: 0; }
.qf-notice--info   { border-left-color: var(--info-400); }
.qf-notice--info   .qf-notice__icon { color: var(--info-400); }
.qf-notice--warn   { border-left-color: var(--warn-400); }
.qf-notice--warn   .qf-notice__icon { color: var(--warn-400); }
.qf-notice--danger { border-left-color: var(--danger-400); }
.qf-notice--danger .qf-notice__icon { color: var(--danger-400); }

/* OTP/인증코드 입력 — 6자리 균등 박스. 자리별 포커스/붙여넣기 분배는
 * 컴포넌트(JS)가 담당하고, 여기서는 시각 레이아웃만 정의한다.
 * inputMode=numeric · type=text(스피너/로케일 회피) 전제. */
.qf-otp-input { display: flex; gap: var(--s-2); justify-content: center; }
.qf-otp-input__digit {
  width: var(--s-10); height: var(--s-10);
  text-align: center;
  font: 600 var(--fs-24) var(--font-mono);
  /* 단일 글자 세로 중앙 — height 와 같은 line-height. font 단축속성이 line-height 를
     normal 로 리셋하므로 반드시 그 *뒤*에 둔다. <input>(브라우저가 자동 중앙)·<div>
     (PRD 목업) 양쪽에 안전한 단일행 중앙 정렬 기법. */
  line-height: var(--s-10);
  color: var(--text-strong);
  background: var(--bg-input);
  border: 1px solid var(--border); border-radius: var(--r-md);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.qf-otp-input__digit:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-accent); }
.qf-otp-input__digit[aria-invalid="true"] { border-color: var(--danger-400); box-shadow: var(--ring-danger); }

/* 비밀번호 강도 미터 — 가입·재설정 시 실시간 피드백. 막대 4단 + 라벨.
 * 강도(약함/보통/강함)는 data-strength 로 받아 채워진 막대(.is-on) 색을 입힌다. */
.qf-strength-meter { display: flex; flex-direction: column; gap: var(--s-2); }
.qf-strength-meter__track { display: flex; gap: var(--s-1); }
.qf-strength-meter__bar {
  flex: 1; height: var(--s-1); border-radius: var(--r-sm);
  background: var(--bg-hover); transition: background var(--dur-fast);
}
.qf-strength-meter__label { font-size: var(--fs-12); color: var(--text-muted); }
.qf-strength-meter[data-strength="weak"]   .qf-strength-meter__bar.is-on { background: var(--danger-400); }
.qf-strength-meter[data-strength="weak"]   .qf-strength-meter__label { color: var(--danger-400); }
.qf-strength-meter[data-strength="ok"]     .qf-strength-meter__bar.is-on { background: var(--warn-400); }
.qf-strength-meter[data-strength="ok"]     .qf-strength-meter__label { color: var(--warn-600); }
.qf-strength-meter[data-strength="strong"] .qf-strength-meter__bar.is-on { background: var(--ok-400); }
.qf-strength-meter[data-strength="strong"] .qf-strength-meter__label { color: var(--ok-600); }

/* 백업 코드 표시 — 2FA 활성화 시 1회 노출되는 복구 코드 10개. mono 2열. */
.qf-backup-codes {
  list-style: none; margin: 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2);
  padding: var(--s-4);
  background: var(--bg-input);
  border: 1px solid var(--border); border-radius: var(--r-md);
}
.qf-backup-codes__code {
  font: 500 var(--fs-14) var(--font-mono);
  color: var(--text-strong); text-align: center;
  letter-spacing: var(--tracking-caps);
}
.qf-backup-codes__code.is-used { color: var(--text-muted); text-decoration: line-through; }

/* 체크박스 — 약관 동의·"백업코드를 저장했습니다" 등 확인용. 네이티브 input
 * 위에 accent-color 만 얹어 focus-visible 동작을 보존한다. */
.qf-checkbox { display: flex; gap: var(--s-3); align-items: flex-start; cursor: pointer; }
.qf-checkbox__input {
  flex-shrink: 0; width: var(--s-5); height: var(--s-5); margin: 0;
  accent-color: var(--accent);
}
.qf-checkbox__label { font-size: var(--fs-13); color: var(--text-secondary); line-height: var(--lh-normal); }


/* ═══════════════════════════════════════════════════════════════
   G-2 — 범용 기초 컴포넌트(레이아웃 프리미티브 + Display/Feedback).
   전부 layer: generic. 채팅/app-layout 토큰 미사용(드리프트 가드 통과).
   ═══════════════════════════════════════════════════════════════ */

/* ───── Layout: Stack · layer: generic ───── */
/* 세로 스택 — 자식 사이 일정 간격. --qf-gap 으로 간격 오버라이드(기본 --s-4). */
.qf-stack { display: flex; flex-direction: column; gap: var(--qf-gap, var(--s-4)); }
.qf-stack--tight { --qf-gap: var(--s-2); }
.qf-stack--loose { --qf-gap: var(--s-6); }

/* ───── Layout: Cluster · layer: generic ───── */
/* 가로 묶음 — wrap + 정렬. 툴바·태그군·액션열에 쓴다. */
.qf-cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--qf-gap, var(--s-3)); }
.qf-cluster--between { justify-content: space-between; }
.qf-cluster--end { justify-content: flex-end; }
.qf-cluster--tight { --qf-gap: var(--s-2); }

/* ───── Layout: Grid · layer: generic ───── */
/* 반응형 그리드 — auto-fill minmax. --qf-grid-min 으로 최소 셀폭(기본 --grid-cell-min). */
.qf-grid {
  display: grid; gap: var(--qf-gap, var(--s-4));
  grid-template-columns: repeat(auto-fill, minmax(var(--qf-grid-min, var(--grid-cell-min)), 1fr));
}
/* 고정 N-컬럼 균형 그리드 — auto-fill 기본과 달리 정확히 N열(마지막 행 고아 방지).
   minmax(0,1fr)로 min-content 오버플로 차단. 페이지 레벨 그리드라 뷰포트 기준 반응 붕괴. */
.qf-grid--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.qf-grid--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.qf-grid--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 960px) { .qf-grid--cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .qf-grid--cols-3, .qf-grid--cols-4 { grid-template-columns: 1fr; } }

/* ───── Layout: Container · layer: generic ───── */
/* 중앙 정렬 본문 컨테이너 — 최대폭 + 좌우 여백. */
.qf-container { width: 100%; max-width: var(--container-w); margin-inline: auto; padding-inline: var(--s-5); }
.qf-container--narrow { max-width: var(--container-w-narrow); }
.qf-container--wide { max-width: var(--container-w-wide); }

/* ───── Layout: Divider · layer: generic ───── */
.qf-divider { border: none; border-top: 1px solid var(--divider); margin: var(--s-5) 0; }
.qf-divider--vertical { border-top: none; border-left: 1px solid var(--divider); margin: 0 var(--s-4); align-self: stretch; }

/* ───── Display: Card · layer: generic ───── */
/* 콘텐츠 묶음 표면. header/body/footer 슬롯. interactive 변형은 hover 강조. */
.qf-card {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--r-xl); overflow: hidden;
}
.qf-card__header {
  padding: var(--s-5) var(--s-6); border-bottom: 1px solid var(--divider);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
}
.qf-card__title { font: 600 var(--fs-15) var(--font-sans); color: var(--text-strong); }
.qf-card__body { padding: var(--s-6); }
.qf-card__footer {
  padding: var(--s-4) var(--s-6); border-top: 1px solid var(--divider);
  display: flex; justify-content: flex-end; gap: var(--s-3);
}
.qf-card--interactive {
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard);
}
.qf-card--interactive:hover { border-color: var(--border-strong); background: var(--bg-elevated); }
/* 카드/리스트가 링크가 될 때(제목·행·카드 전체를 <a> 로 감쌈) 브라우저 기본 링크색·밑줄을
   상속으로 눌러 카드 톤을 유지하고, hover 는 accent 텍스트로 단서를 준다(예제가 정적 텍스트라
   드러나지 않던 실제 소비 패턴). */
.qf-card__title a, a.qf-card, .qf-list__item--interactive a { color: inherit; text-decoration: none; }
.qf-card__title a:hover, .qf-list__item--interactive a:hover { color: var(--accent-text); }

/* ───── Display: Tag · layer: generic ───── */
/* 라벨/카테고리 칩(배지와 구분: 배지=카운트·상태, 태그=라벨). 제거 가능 변형. */
.qf-tag {
  display: inline-flex; align-items: center; gap: var(--s-2);
  height: 24px; padding: 0 var(--s-3);
  border-radius: var(--r-sm); border: 1px solid transparent;
  background: var(--bg-hover); color: var(--text-secondary);
  font: 500 var(--fs-12) var(--font-sans);
}
.qf-tag--accent { background: var(--accent-subtle); color: var(--accent-on-subtle); }
.qf-tag--outline { background: transparent; border-color: var(--border-strong); }
.qf-tag__remove {
  display: inline-grid; place-items: center; width: 16px; height: 16px;
  margin-right: calc(-1 * var(--s-1));
  border: none; background: transparent; border-radius: var(--r-xs);
  color: var(--text-muted); cursor: pointer;
}
.qf-tag__remove:hover { background: var(--bg-selected); color: var(--text); }

/* ───── Stat · layer: generic ───── */
/* 대시보드 KPI: 큰 값 + 라벨 + 추세 델타. 추세는 색뿐 아니라 화살표/부호로도 전달(WCAG 1.4.1). */
.qf-stat { display: flex; flex-direction: column; gap: var(--s-1); }
.qf-stat__label { font-size: var(--fs-13); color: var(--text-muted); }
.qf-stat__value { font: 700 var(--fs-32)/1.1 var(--font-sans); color: var(--text-strong); letter-spacing: var(--tracking-tight); }
.qf-stat__delta { display: inline-flex; align-items: center; gap: var(--s-1); font: 500 var(--fs-13) var(--font-sans); color: var(--text-muted); }
.qf-stat__delta--up   { color: var(--ok-600); }
.qf-stat__delta--down { color: var(--danger-400); }
[data-theme="light"] .qf-stat__delta--down { color: var(--danger-600); }
/* 값 아래 보조 텍스트(010): 상한·단위·기준 등 ("상한 300장"). 델타와 같은 크기, 더 낮은 톤. */
.qf-stat__hint { font-size: var(--fs-12); color: var(--text-muted); }

/* ───── Feedback: Spinner · layer: generic ───── */
/* 무한 회전 로더. reduced-motion 은 tokens.css 전역 가드가 무력화. aria-label 권장. */
.qf-spinner {
  display: inline-block; width: var(--qf-spinner-size, 20px); height: var(--qf-spinner-size, 20px);
  border: 2px solid var(--bg-selected); border-top-color: var(--accent);
  border-radius: var(--r-pill); animation: qf-spin 0.7s linear infinite;
}
.qf-spinner--sm { --qf-spinner-size: 14px; }
.qf-spinner--lg { --qf-spinner-size: 32px; }
@keyframes qf-spin { to { transform: rotate(360deg); } }

/* ───── Feedback: Progress · layer: generic ───── */
/* 진행률 막대. role=progressbar + aria-valuenow 권장. indeterminate 는 width 미정. */
.qf-progress {
  display: block; width: 100%; height: var(--s-2);
  background: var(--bg-hover); border-radius: var(--r-pill); overflow: hidden;
}
.qf-progress__bar {
  display: block; height: 100%; width: 0;
  background: var(--accent); border-radius: inherit;
  transition: width var(--dur-base) var(--ease-standard);
}
.qf-progress--success .qf-progress__bar { background: var(--ok-400); }
.qf-progress--danger .qf-progress__bar { background: var(--danger-400); }
.qf-progress--indeterminate .qf-progress__bar { width: 40%; animation: qf-progress-indet 1.2s var(--ease-standard) infinite; }
@keyframes qf-progress-indet { 0% { margin-left: -40%; } 100% { margin-left: 100%; } }


/* ═══════════════════════════════════════════════════════════════
   G-2 Wave C — 범용 폼/액션 컴포넌트. 전부 layer: generic.
   ═══════════════════════════════════════════════════════════════ */

/* ───── Forms: Select · layer: generic ───── */
/* 네이티브 select 래퍼 + CSS chevron(currentColor 보더, raw hex 없음). */
.qf-select { position: relative; display: block; }
.qf-select select {
  appearance: none; -webkit-appearance: none;
  display: block; width: 100%; height: 40px;
  padding: 0 var(--s-8) 0 var(--s-4);
  background: var(--bg-input); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  font: 400 var(--fs-14) var(--font-sans); cursor: pointer;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.qf-select select:hover:not(:focus) { border-color: var(--border-strong); }
.qf-select select:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-accent); }
.qf-select select[aria-invalid="true"] { border-color: var(--danger-400); box-shadow: var(--ring-danger); }
.qf-select::after {
  content: ''; position: absolute; right: var(--s-4); top: 50%;
  width: 7px; height: 7px;
  border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}

/* ───── Forms: Radio · layer: generic ───── */
.qf-radio { display: flex; gap: var(--s-3); align-items: flex-start; cursor: pointer; }
.qf-radio__input { flex-shrink: 0; width: var(--s-5); height: var(--s-5); margin: 0; accent-color: var(--accent); }
.qf-radio__label { font-size: var(--fs-13); color: var(--text-secondary); line-height: var(--lh-normal); }

/* ───── Forms: Choice list · layer: generic ───── */
/* 체크박스/라디오 그룹을 fieldset/legend 로 묶는 컨테이너. */
.qf-choice-list { display: flex; flex-direction: column; gap: var(--s-3); border: none; margin: 0; padding: 0; min-width: 0; }
.qf-choice-list__legend {
  font-size: var(--fs-12); font-weight: 600; color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
  padding: 0; margin-bottom: var(--s-1);
}

/* ───── Forms: Form layout · layer: generic ───── */
/* 폼 필드 세로 배치 + 가로 행(__row) + 액션열(__actions). qf-field 와 조합. */
.qf-form { display: flex; flex-direction: column; gap: var(--s-5); }
.qf-form__row { display: flex; flex-wrap: wrap; gap: var(--s-4); }
.qf-form__row > * { flex: 1 1 var(--grid-cell-min); min-width: 0; }
.qf-form__actions { display: flex; justify-content: flex-end; gap: var(--s-3); margin-top: var(--s-2); }

/* ───── Actions: Link · layer: generic ───── */
.qf-link { color: var(--link); text-decoration: none; border-radius: var(--r-xs); }
.qf-link:hover { color: var(--link-hover); text-decoration: underline; }
.qf-link--muted { color: var(--text-secondary); }
.qf-link--muted:hover { color: var(--text); }

/* ───── Actions: Button group · layer: generic ───── */
/* 인접 버튼을 한 덩어리로 결합. role=group + aria-label 권장. */
.qf-btn-group { display: inline-flex; }
.qf-btn-group .qf-btn { border-radius: 0; }
.qf-btn-group .qf-btn:not(:first-child) { margin-left: -1px; }
.qf-btn-group .qf-btn:first-child { border-top-left-radius: var(--r-md); border-bottom-left-radius: var(--r-md); }
.qf-btn-group .qf-btn:last-child { border-top-right-radius: var(--r-md); border-bottom-right-radius: var(--r-md); }
.qf-btn-group .qf-btn:hover,
.qf-btn-group .qf-btn[aria-pressed="true"] { position: relative; z-index: 1; }


/* ═══════════════════════════════════════════════════════════════
   G-2 Wave D — 데이터/내비/공개/오버레이. 전부 layer: generic.
   ═══════════════════════════════════════════════════════════════ */

/* ───── Display: Table · layer: generic ───── */
.qf-table-wrap { width: 100%; overflow-x: auto; }
.qf-table { width: 100%; border-collapse: collapse; font: 400 var(--fs-14) var(--font-sans); }
/* .qf-table-wrap 로 감쌌을 때만 실제 가로 스크롤이 되도록: 테이블이 래퍼를 넘칠 수 있게 하고
   헤더는 줄바꿈 금지(긴/다열 표에서 셀이 세로로 쌓이는 것 방지). 래퍼 없는 .qf-table 는 불변. */
.qf-table-wrap .qf-table { width: auto; min-width: 100%; }
.qf-table-wrap .qf-table th { white-space: nowrap; }
.qf-table th, .qf-table td { padding: var(--s-3) var(--s-4); text-align: left; border-bottom: 1px solid var(--divider); }
.qf-table th {
  font: 600 var(--fs-12) var(--font-sans); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
.qf-table td { color: var(--text); }
.qf-table tbody tr:hover { background: var(--bg-hover); }
.qf-table--striped tbody tr:nth-child(2n) { background: var(--bg-panel); }
/* DataTable 강화 — 정렬 캐럿(aria-sort), sticky 헤더, 행 선택, 툴바. 정렬·선택 로직은 소비처. */
.qf-table__th--sortable { cursor: pointer; user-select: none; }
.qf-table__th--sortable:hover { color: var(--text-secondary); }
.qf-table__sort { display: inline-flex; align-items: center; gap: var(--s-2); }
.qf-table__sort::after { content: '↕'; opacity: 0.45; }
.qf-table th[aria-sort="ascending"] .qf-table__sort::after { content: '↑'; opacity: 1; color: var(--accent); }
.qf-table th[aria-sort="descending"] .qf-table__sort::after { content: '↓'; opacity: 1; color: var(--accent); }
.qf-table--sticky thead th { position: sticky; top: 0; z-index: var(--z-sticky); background: var(--bg-panel); }
.qf-table tbody tr.is-selected { background: var(--accent-bg); }
.qf-table tbody tr.is-selected:hover { background: var(--accent-bgHov); }
.qf-table__toolbar {
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
  padding: var(--s-3) var(--s-4); background: var(--bg-panel);
  border: 1px solid var(--divider); border-bottom: none; border-radius: var(--r-md) var(--r-md) 0 0;
}
.qf-table__toolbar-title { font: 600 var(--fs-14) var(--font-sans); color: var(--text-strong); }
.qf-table__toolbar-spacer { flex: 1; }

/* ═══ 운영/지표 콘솔 확장 (Skulk 피드백) · layer: generic ═══ */

/* Sparkline / mini time-series — 데이터→points/path 는 소비처가 생성, DS 는 토큰 스타일 셸.
   <svg viewBox="0 0 100 32" preserveAspectRatio="none"> 안에 polyline/path 를 넣는다. */
.qf-sparkline { display: block; width: 100%; height: 36px; color: var(--accent); }
.qf-sparkline svg { display: block; width: 100%; height: 100%; overflow: visible; }
.qf-sparkline__area { fill: currentColor; fill-opacity: 0.14; stroke: none; }
.qf-sparkline__line { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.qf-sparkline--ok     { color: var(--ok-400); }
.qf-sparkline--warn   { color: var(--warn-400); }
.qf-sparkline--danger { color: var(--danger-400); }

/* Radial / Gauge — conic-gradient. --qf-radial-value(0~100) 인라인 style 로 전달. */
.qf-radial {
  position: relative; width: 96px; height: 96px; border-radius: 50%;
  background: conic-gradient(var(--qf-radial-color, var(--accent)) calc(var(--qf-radial-value, 0) * 1%), var(--bg-hover) 0);
  display: grid; place-items: center;
}
.qf-radial::before { content: ''; position: absolute; inset: 11px; border-radius: 50%; background: var(--bg-elevated); }
.qf-radial__value { position: relative; font: 700 var(--fs-18) var(--font-sans); color: var(--text-strong); }
.qf-radial--ok     { --qf-radial-color: var(--ok-400); }
.qf-radial--warn   { --qf-radial-color: var(--warn-400); }
.qf-radial--danger { --qf-radial-color: var(--danger-400); }

/* ═══════════ Charts · layer: generic ═══════════
   sparkline·radial 과 동일 계약: 좌표/세그먼트 길이/셀 강도는 소비처가 데이터에서 계산하고
   (viewBox 기준), DS 는 축·그리드·시리즈·툴팁·범례의 토큰 기반 시각 스타일만 제공한다.
   범주 색은 부모/요소에 style="color:var(--viz-N)" (1~8), 자식은 currentColor 로 상속.
   접근성: 차트 svg 는 role="img"+aria-label 요약, 색 단독 의존 금지(범례/수치 라벨 동반). */

/* ── qf-chart — 멀티시리즈 시계열 라인/영역 ── */
.qf-chart { position: relative; display: block; width: 100%; color: var(--viz-1); }
.qf-chart__svg { display: block; width: 100%; height: var(--chart-h, 260px); overflow: visible; }
.qf-chart__grid line, .qf-chart__grid path { stroke: var(--chart-grid); stroke-width: 1; fill: none; vector-effect: non-scaling-stroke; }
.qf-chart__axis { stroke: var(--chart-axis); stroke-width: 1; fill: none; vector-effect: non-scaling-stroke; }
.qf-chart__label { fill: var(--chart-label); font: 500 var(--fs-11) var(--font-sans); }
.qf-chart__label--x { text-anchor: middle; }
.qf-chart__label--y { text-anchor: end; dominant-baseline: middle; }
.qf-chart__series { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.qf-chart__area { fill: currentColor; fill-opacity: 0.12; stroke: none; }
.qf-chart__dot { fill: currentColor; stroke: var(--bg-panel); stroke-width: 1.5; }
.qf-chart__guide { stroke: var(--chart-axis); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.qf-chart__tooltip {
  position: absolute; top: 0; left: 0; pointer-events: none; z-index: 1; min-width: 132px;
  padding: var(--s-2) var(--s-3); border-radius: var(--r-md);
  background: var(--chart-tooltip-bg); border: 1px solid var(--border); box-shadow: var(--elev-2);
  color: var(--chart-tooltip-text); font: 500 var(--fs-12) var(--font-sans);
}
.qf-chart__tooltip[hidden] { display: none; }
.qf-chart__tooltip-row { display: flex; align-items: center; gap: var(--s-2); white-space: nowrap; }
.qf-chart__tooltip-row + .qf-chart__tooltip-row { margin-top: var(--s-1); }
.qf-chart__tooltip-swatch { width: 8px; height: 8px; border-radius: 2px; background: currentColor; flex: none; }
/* 빈/로딩 — svg 위 중앙 상태 메시지 오버레이(소비처가 --empty/--loading 토글) */
.qf-chart__state { position: absolute; inset: 0; display: none; place-items: center; color: var(--text-muted); font-size: var(--fs-13); }
.qf-chart--empty .qf-chart__state, .qf-chart--loading .qf-chart__state { display: grid; }
.qf-chart--empty .qf-chart__svg, .qf-chart--loading .qf-chart__svg { opacity: 0; }

/* ── qf-barchart — 그룹/스택 막대 ── */
.qf-barchart { position: relative; display: block; width: 100%; color: var(--viz-1); }
.qf-barchart__svg { display: block; width: 100%; height: var(--chart-h, 260px); overflow: visible; }
.qf-barchart__grid line { stroke: var(--chart-grid); stroke-width: 1; vector-effect: non-scaling-stroke; }
.qf-barchart__axis { stroke: var(--chart-axis); stroke-width: 1; vector-effect: non-scaling-stroke; }
.qf-barchart__label { fill: var(--chart-label); font: 500 var(--fs-11) var(--font-sans); text-anchor: middle; }
.qf-barchart__track { fill: var(--chart-track); }
.qf-barchart__bar { fill: currentColor; rx: 2px; }
.qf-barchart__value { fill: var(--text-secondary); font: 600 var(--fs-11) var(--font-sans); text-anchor: middle; }
.qf-barchart--horizontal .qf-barchart__label { text-anchor: end; }
.qf-barchart--horizontal .qf-barchart__value { text-anchor: start; }

/* ── qf-donut — 다세그먼트 도넛 + 중앙 합계 ── */
.qf-donut { position: relative; display: inline-grid; place-items: center; }
.qf-donut > * { grid-area: 1 / 1; }
.qf-donut__svg { display: block; width: var(--donut-size, 160px); height: var(--donut-size, 160px); transform: rotate(-90deg); }
.qf-donut__track { fill: none; stroke: var(--chart-track); }
.qf-donut__segment { fill: none; stroke: currentColor; stroke-linecap: butt; transition: stroke-dasharray var(--dur-base) var(--ease-standard); }
.qf-donut__center { display: grid; place-items: center; text-align: center; line-height: var(--lh-snug); }
.qf-donut__total { font: 700 var(--fs-24) var(--font-sans); color: var(--text-strong); }
.qf-donut__label { font-size: var(--fs-12); color: var(--text-muted); }

/* ── qf-heatmap — 사이트×시간/카테고리 밀도. 셀 강도 --qf-heat(0~1), 색 color:var(--viz-N) ── */
.qf-heatmap { display: grid; gap: var(--s-1); }
.qf-heatmap__row { display: contents; }
.qf-heatmap__cell {
  aspect-ratio: 1; min-width: var(--s-5); border-radius: var(--r-xs);
  color: var(--viz-2);
  background: color-mix(in srgb, currentColor calc(var(--qf-heat, 0) * 100%), var(--heat-empty));
}
.qf-heatmap__label { font: 500 var(--fs-11) var(--font-sans); color: var(--text-muted); align-self: center; white-space: nowrap; }

/* ── qf-legend — 차트 공유 범례(색칩+라벨, 선택적 토글) ── */
.qf-legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-4); margin: 0; padding: 0; list-style: none; }
.qf-legend__item { display: inline-flex; align-items: center; gap: var(--s-2); font: 500 var(--fs-13) var(--font-sans); color: var(--text-secondary); }
.qf-legend__swatch { width: 12px; height: 12px; border-radius: var(--r-xs); background: currentColor; flex: none; }
.qf-legend__value { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.qf-legend--toggle .qf-legend__item { cursor: pointer; background: none; border: 0; padding: var(--s-1) var(--s-2); border-radius: var(--r-sm); }
.qf-legend--toggle .qf-legend__item:hover { background: var(--bg-hover); }
.qf-legend__item[aria-pressed="false"] { opacity: 0.45; }
.qf-legend__item[aria-pressed="false"] .qf-legend__swatch { background: var(--text-disabled); }

/* CopyButton — id/경로/URL 복사. data-copied="true" 로 완료 상태(앱이 토글). */
.qf-copy-btn {
  display: inline-flex; align-items: center; gap: var(--s-2);
  height: 28px; padding: 0 var(--s-3); border-radius: var(--r-sm);
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-secondary);
  font: 500 var(--fs-12) var(--font-mono); cursor: pointer;
}
.qf-copy-btn:hover { background: var(--bg-hover); color: var(--text); }
.qf-copy-btn svg { width: 14px; height: 14px; }
.qf-copy-btn[data-copied="true"] { color: var(--ok-400); border-color: var(--ok-400); }

/* Toast region — qf-toast 스택 컨테이너(고정). 우상단 기본, --bottom 으로 하단. */
.qf-toast-region {
  position: fixed; top: var(--s-5); right: var(--s-5); z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: var(--s-3);
  width: 360px; max-width: calc(100vw - 2 * var(--s-5)); pointer-events: none;
}
.qf-toast-region > * { pointer-events: auto; }
.qf-toast-region--bottom { top: auto; bottom: var(--s-5); }
.qf-toast-region--left { right: auto; left: var(--s-5); }

/* DateRange — native date input 2개 + 구분자(달력 picker 는 브라우저 native). */
.qf-daterange { display: inline-flex; align-items: center; gap: var(--s-3); }
.qf-daterange__sep { color: var(--text-muted); }

/* ───── Display: Description list · layer: generic ───── */
.qf-dl { display: grid; grid-template-columns: minmax(120px, auto) 1fr; gap: var(--s-2) var(--s-5); margin: 0; }
.qf-dl dt { color: var(--text-muted); font-size: var(--fs-13); }
.qf-dl dd { color: var(--text); font-size: var(--fs-14); margin: 0; }

/* ───── Display: Avatar group · layer: generic ───── */
/* 겹쳐진 아바타 스택. ring 은 --avatar-ring(표면) 사용 — generic 결합 해제 완료. */
.qf-avatar-group { display: inline-flex; }
.qf-avatar-group > * { box-shadow: 0 0 0 2px var(--avatar-ring); border-radius: var(--r-pill); }
.qf-avatar-group > * + * { margin-left: calc(-1 * var(--s-3)); }

/* ───── Navigation: Breadcrumb · layer: generic ───── */
.qf-breadcrumb {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  list-style: none; margin: 0; padding: 0; font: 500 var(--fs-13) var(--font-sans);
}
.qf-breadcrumb__item { color: var(--link); text-decoration: none; }
.qf-breadcrumb__item:hover { text-decoration: underline; }
.qf-breadcrumb__item[aria-current="page"] { color: var(--text-muted); pointer-events: none; }
.qf-breadcrumb__sep { color: var(--text-muted); }

/* ───── Navigation: Pagination · layer: generic ───── */
.qf-pagination { display: inline-flex; align-items: center; gap: var(--s-1); list-style: none; margin: 0; padding: 0; }
.qf-pagination__item {
  display: inline-grid; place-items: center; min-width: 32px; height: 32px; padding: 0 var(--s-2);
  border: none; background: transparent; border-radius: var(--r-sm);
  color: var(--text-secondary); font: 500 var(--fs-14) var(--font-sans); cursor: pointer;
}
.qf-pagination__item:hover { background: var(--bg-hover); color: var(--text); }
.qf-pagination__item[aria-current="page"] { background: var(--bg-selected); color: var(--text-strong); }
.qf-pagination__item:disabled { opacity: 0.5; cursor: not-allowed; }

/* ───── Disclosure: Accordion · layer: generic ───── */
/* 네이티브 details/summary 기반(키보드·스크린리더 동작 무료). */
.qf-accordion { border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.qf-accordion__item { border-bottom: 1px solid var(--divider); }
.qf-accordion__item:last-child { border-bottom: none; }
.qf-accordion__trigger {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-4) var(--s-5); cursor: pointer; list-style: none;
  font: 600 var(--fs-14) var(--font-sans); color: var(--text-strong);
}
.qf-accordion__trigger::-webkit-details-marker { display: none; }
.qf-accordion__trigger::after {
  content: ''; width: 7px; height: 7px;
  border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted);
  transform: rotate(45deg); transition: transform var(--dur-fast) var(--ease-standard);
}
.qf-accordion__item[open] .qf-accordion__trigger::after { transform: rotate(-135deg); }
.qf-accordion__trigger:hover { background: var(--bg-hover); }
.qf-accordion__panel { padding: 0 var(--s-5) var(--s-5); color: var(--text-secondary); font-size: var(--fs-14); line-height: var(--lh-normal); }

/* ───── Overlays: Popover · layer: generic ───── */
/* 트리거 기준 떠 있는 콘텐츠 패널(메뉴와 달리 임의 콘텐츠). 위치는 소비처가 잡는다. */
.qf-popover {
  min-width: 200px; max-width: 320px; padding: var(--s-4);
  background: var(--bg-floating); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--elev-3);
  color: var(--text); font-size: var(--fs-14); z-index: var(--z-dropdown);
}

/* ───── Overlays: Drawer · layer: generic ───── */
/* 측면 슬라이드 패널(backdrop + 우/좌). header/body/footer 슬롯. */
.qf-drawer-backdrop { position: fixed; inset: 0; background: var(--scrim); z-index: var(--z-modal-bg); }
.qf-drawer {
  position: fixed; top: 0; bottom: 0; right: 0; width: min(420px, 100vw);
  display: flex; flex-direction: column;
  background: var(--bg-elevated); border-left: 1px solid var(--border);
  box-shadow: var(--elev-4); z-index: var(--z-modal);
}
.qf-drawer--left { right: auto; left: 0; border-left: none; border-right: 1px solid var(--border); }
.qf-drawer__header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-5) var(--s-6); border-bottom: 1px solid var(--divider);
}
.qf-drawer__title { font: 600 var(--fs-16) var(--font-sans); color: var(--text-strong); }
.qf-drawer__body { flex: 1; overflow-y: auto; padding: var(--s-6); }
.qf-drawer__footer { padding: var(--s-4) var(--s-6); border-top: 1px solid var(--divider); display: flex; justify-content: flex-end; gap: var(--s-3); }
/* [hidden] 존중 — display:flex 가 UA [hidden]{display:none} 를 덮으므로 명시 가드(소비처가
   [hidden] 토글만으로 드로어/백드롭을 닫을 수 있게). */
.qf-drawer[hidden], .qf-drawer-backdrop[hidden] { display: none; }


/* ═══════════════════════════════════════════════════════════════
   G-2 Wave E — 범용 앱 셸 크롬 + 잔여 입력. 전부 layer: generic.
   (채팅 전용 serverlist/channellist/topbar 와 무관한 일반 앱 골격.)
   ═══════════════════════════════════════════════════════════════ */

/* ───── Layout: App shell · layer: generic ───── */
/* 헤더 + 사이드바 + 본문 그리드. 일반 앱(대시보드/SaaS) 골격. */
/* 레이아웃은 요소 유무로 자동 결정된다(presence-based). nav·aside 트랙은 auto 라 해당 요소가
   없거나(또는 모바일에서 숨겨지면) 0 으로 접힌다 → 1·2·3단과 헤더 유무가 마크업만으로 결정,
   별도 모디파이어 불필요. container-type 으로 뷰포트가 아닌 "셸 자신의 폭"에 반응(좁은 컨테이너·
   폰 프레임 안에서도 모바일 레이아웃이 그대로 동작). */
.qf-app-shell {
  display: grid; height: 100%;
  container-type: inline-size;
  grid-template-columns: auto 1fr auto;          /* nav | main | aside */
  grid-template-rows: auto 1fr;                  /* header | body */
  grid-template-areas: "nav header header" "nav main aside";
}
.qf-app-shell__navbar { grid-area: header; }
.qf-app-shell__sidenav { grid-area: nav; width: var(--sidenav-w); }
/* 본문은 앱 기본 surface(--bg-app) 위에 둔다 — navbar/sidenav/aside(--bg-panel)와 계층 구분.
   배경을 명시하지 않으면 부모 컨테이너 색이 비쳐 본문만 다른 색으로 보인다. */
.qf-app-shell__main { grid-area: main; overflow: auto; padding: var(--s-7); background: var(--bg-app); }
.qf-app-shell__aside {
  grid-area: aside; width: var(--aside-w); overflow: auto; padding: var(--s-5);
  background: var(--bg-panel); border-left: 1px solid var(--divider);
}
/* 모바일(셸 폭 ≤ 600px): 사이드바·aside 를 접어 단일 컬럼으로. 네비/보조 패널은 앱이 드로어나
   탭바로 surface 한다(헤더는 그대로 상단에 유지). */
@container (max-width: 600px) {
  /* .qf-sidenav { display:flex } 와 명시도가 같으면 소스 순서로 밀리므로 후손 결합자로 한 단계 올린다. */
  .qf-app-shell .qf-app-shell__sidenav,
  .qf-app-shell .qf-app-shell__aside { display: none; }
  .qf-app-shell__main { padding: var(--s-5); }
}
/* deprecated(ADR 0003): presence-based 레이아웃에선 사이드바를 빼면 자동 1단이라 불필요.
   v0.x 하위호환을 위해 "사이드바가 있어도 숨김" 의미를 유지하는 규칙으로 남긴다. 1.0 에서 제거. */
.qf-app-shell--no-sidenav > .qf-app-shell__sidenav { display: none; }

/* ───── Navigation: Navbar · layer: generic ───── */
.qf-navbar {
  display: flex; align-items: center; gap: var(--s-4);
  height: var(--h-navbar); padding: 0 var(--s-5);
  background: var(--bg-panel); border-bottom: 1px solid var(--divider);
}
.qf-navbar__brand { font: 700 var(--fs-16) var(--font-sans); color: var(--text-strong); letter-spacing: var(--tracking-tight); }
.qf-navbar__nav { display: flex; align-items: center; gap: var(--s-1); }
.qf-navbar__spacer { flex: 1; }
.qf-navbar__link {
  padding: var(--s-2) var(--s-3); border-radius: var(--r-sm);
  color: var(--text-secondary); font: 500 var(--fs-14) var(--font-sans); text-decoration: none;
}
.qf-navbar__link:hover { background: var(--bg-hover); color: var(--text); }
.qf-navbar__link[aria-current="page"] { background: var(--bg-selected); color: var(--text-strong); }

/* ───── Navigation: Side nav · layer: generic ───── */
.qf-sidenav {
  display: flex; flex-direction: column; gap: var(--s-1);
  padding: var(--s-4) var(--s-3); overflow-y: auto;
  background: var(--bg-panel); border-right: 1px solid var(--divider);
}
.qf-sidenav__section {
  font: 600 var(--fs-11) var(--font-sans); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
  padding: var(--s-4) var(--s-3) var(--s-2);
}
.qf-sidenav__item {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3); border-radius: var(--r-sm);
  color: var(--text-secondary); font: 500 var(--fs-14) var(--font-sans);
  text-decoration: none; cursor: pointer;
}
.qf-sidenav__item:hover { background: var(--bg-hover); color: var(--text); }
.qf-sidenav__item[aria-current="page"] { background: var(--bg-selected); color: var(--text-strong); }

/* ───── Layout: Page header · layer: generic ───── */
.qf-page-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap; padding: var(--s-7) 0 var(--s-5);
}
.qf-page-header__title { font: 600 var(--fs-24) var(--font-sans); color: var(--text-strong); letter-spacing: var(--tracking-tight); margin: 0; }
.qf-page-header__subtitle { font-size: var(--fs-14); color: var(--text-muted); margin-top: var(--s-1); }
.qf-page-header__actions { display: flex; gap: var(--s-3); flex-shrink: 0; }
/* 첫 화면 히어로(010): 제목 한 단계 크게(32px), 부제는 본문 크기 + 가독 폭. 사이트 첫 화면·랜딩 목록 상단용. */
.qf-page-header--hero { padding: var(--s-9) 0 var(--s-6); }
.qf-page-header--hero .qf-page-header__title { font-size: var(--fs-32); line-height: var(--lh-tight); }
.qf-page-header--hero .qf-page-header__subtitle {
  font-size: var(--fs-16); line-height: var(--lh-normal); color: var(--text-secondary);
  margin-top: var(--s-3); max-width: var(--container-w-narrow);
}

/* ───── Forms: Search · layer: generic ───── */
.qf-search {
  display: flex; align-items: center; gap: var(--s-3);
  height: 40px; padding: 0 var(--s-4);
  background: var(--bg-input); border: 1px solid var(--border); border-radius: var(--r-lg);
  color: var(--text-muted);
}
.qf-search:focus-within { border-color: var(--accent); }
.qf-search__icon { flex-shrink: 0; width: 16px; height: 16px; display: grid; place-items: center; }
.qf-search__input { flex: 1; min-width: 0; background: transparent; border: none; outline: none; color: var(--text); font: 400 var(--fs-14) var(--font-sans); }
.qf-search__input::placeholder { color: var(--text-muted); }
.qf-search__clear {
  flex-shrink: 0; width: 18px; height: 18px; display: grid; place-items: center;
  border: none; background: transparent; border-radius: var(--r-xs);
  color: var(--text-muted); cursor: pointer;
}
.qf-search__clear:hover { background: var(--bg-hover); color: var(--text); }

/* ───── Forms: Slider · layer: generic ───── */
.qf-slider {
  width: 100%; height: var(--s-2); appearance: none; -webkit-appearance: none;
  background: var(--bg-hover); border-radius: var(--r-pill); outline: none;
}
.qf-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border: none; border-radius: var(--r-pill); background: var(--accent); cursor: pointer; }
.qf-slider::-moz-range-thumb { width: 16px; height: 16px; border: none; border-radius: var(--r-pill); background: var(--accent); cursor: pointer; }
.qf-slider::-moz-range-progress { height: var(--s-2); background: var(--accent); border-radius: var(--r-pill); }
.qf-slider:focus-visible::-webkit-slider-thumb { box-shadow: var(--ring-focus); }

/* ───── Forms: Number input · layer: generic ───── */
.qf-number-input { display: inline-flex; align-items: center; overflow: hidden; background: var(--bg-input); border: 1px solid var(--border); border-radius: var(--r-lg); }
.qf-number-input__field { width: 64px; height: 38px; padding: 0 var(--s-3); background: transparent; border: none; outline: none; color: var(--text); text-align: center; font: 400 var(--fs-14) var(--font-mono); }
.qf-number-input__btn { width: 32px; height: 38px; border: none; background: transparent; color: var(--text-secondary); cursor: pointer; font-size: var(--fs-16); }
.qf-number-input__btn:hover { background: var(--bg-hover); color: var(--text); }

/* ───── Forms: File drop · layer: generic ───── */
/* 범용 드래그앤드롭 업로드 영역(채팅 upload-overlay 와 무관). */
.qf-file-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s-3); padding: var(--s-8) var(--s-6); text-align: center; cursor: pointer;
  background: var(--bg-input); border: 2px dashed var(--border-strong); border-radius: var(--r-lg);
  color: var(--text-muted);
  transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard);
}
.qf-file-drop:hover,
.qf-file-drop--active { border-color: var(--accent); background: var(--accent-bg); color: var(--text-secondary); }
.qf-file-drop__title { font: 600 var(--fs-14) var(--font-sans); color: var(--text-strong); }

/* ───── Navigation: Steps · layer: generic ───── */
.qf-steps { display: flex; align-items: center; gap: var(--s-2); list-style: none; margin: 0; padding: 0; }
.qf-steps__step { display: inline-flex; align-items: center; gap: var(--s-2); color: var(--text-muted); font: 500 var(--fs-13) var(--font-sans); }
.qf-steps__marker {
  display: grid; place-items: center; flex-shrink: 0; width: 24px; height: 24px;
  border-radius: var(--r-pill); background: var(--bg-hover); color: var(--text-secondary); font-size: var(--fs-12);
}
.qf-steps__step[aria-current="step"] { color: var(--text-strong); }
.qf-steps__step[aria-current="step"] .qf-steps__marker { background: var(--accent); color: var(--text-onAccent); }
.qf-steps__step--done { color: var(--text-secondary); }
.qf-steps__step--done .qf-steps__marker { background: var(--ok-400); color: var(--text-on-bright-fill); }
.qf-steps__bar { flex: 1; min-width: var(--s-6); height: 2px; background: var(--border-strong); }
.qf-steps__step--done .qf-steps__bar { background: var(--accent); }

/* ───── Display: List · layer: generic ───── */
.qf-list { display: flex; flex-direction: column; list-style: none; margin: 0; padding: 0; }
.qf-list__item { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); color: var(--text); }
.qf-list__item + .qf-list__item { border-top: 1px solid var(--divider); }
.qf-list__item--interactive { cursor: pointer; }
.qf-list__item--interactive:hover { background: var(--bg-hover); }
.qf-list__text { flex: 1; min-width: 0; }
.qf-list__trailing { flex-shrink: 0; color: var(--text-muted); }


/* ═══════════════════════════════════════════════════════════════
   Prose — 마크다운/장문 문서 렌더 스타일(layer: generic).
   <article class="qf-prose"> 안의 표준 HTML(마크다운 산출물)을 토큰 기반으로 스타일.
   소비 앱: 마크다운 → HTML 변환 후 .qf-prose 로 감싸면 문서가 DS 톤으로 렌더된다.
   ═══════════════════════════════════════════════════════════════ */
/* 본문 measure — 기본은 가독성 폭(narrow ≈ 640px, 60~75ch). 블로그/문서처럼 더 넓게 쓰려면
   소비처가 --qf-prose-max 로 오버라이드(예: none 또는 var(--container-w-wide)). 기본값 불변. */
.qf-prose { color: var(--text); font: 400 var(--fs-15)/var(--lh-loose) var(--font-sans); max-width: var(--qf-prose-max, var(--container-w-narrow)); }
.qf-prose > :first-child { margin-top: 0; }
.qf-prose > :last-child { margin-bottom: 0; }
.qf-prose h1, .qf-prose h2, .qf-prose h3, .qf-prose h4 { color: var(--text-strong); letter-spacing: var(--tracking-tight); line-height: var(--lh-tight); }
.qf-prose h1 { font-size: var(--fs-32); font-weight: 700; margin: var(--s-8) 0 var(--s-4); }
.qf-prose h2 { font-size: var(--fs-24); font-weight: 600; margin: var(--s-7) 0 var(--s-3); padding-bottom: var(--s-2); border-bottom: 1px solid var(--divider); }
.qf-prose h3 { font-size: var(--fs-20); font-weight: 600; margin: var(--s-6) 0 var(--s-2); }
.qf-prose h4 { font-size: var(--fs-16); font-weight: 600; margin: var(--s-5) 0 var(--s-2); }
.qf-prose p { margin: var(--s-4) 0; }
.qf-prose strong { color: var(--text-strong); font-weight: 600; }
.qf-prose em { font-style: italic; }
.qf-prose a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }
.qf-prose a:hover { color: var(--link-hover); }
.qf-prose ul, .qf-prose ol { margin: var(--s-4) 0; padding-left: var(--s-7); }
.qf-prose li { margin: var(--s-2) 0; }
.qf-prose li > ul, .qf-prose li > ol { margin: var(--s-2) 0; }
.qf-prose ul { list-style: disc; }
.qf-prose ol { list-style: decimal; }
.qf-prose blockquote { margin: var(--s-5) 0; padding: var(--s-2) var(--s-5); border-left: 3px solid var(--accent); background: var(--bg-panel); border-radius: 0 var(--r-md) var(--r-md) 0; color: var(--text-secondary); }
.qf-prose blockquote > :first-child { margin-top: 0; }
.qf-prose blockquote > :last-child { margin-bottom: 0; }
.qf-prose code { font: 500 0.9em var(--font-mono); background: var(--bg-panel); border: 1px solid var(--divider); border-radius: var(--r-xs); padding: 1px 5px; color: var(--text-strong); }
.qf-prose pre { margin: var(--s-5) 0; padding: var(--s-4); background: var(--bg-code); border: 1px solid var(--divider); border-radius: var(--r-md); overflow-x: auto; }
.qf-prose pre code { font-size: var(--fs-13); background: none; border: none; padding: 0; color: var(--text-code); }
.qf-prose hr { border: none; border-top: 1px solid var(--divider); margin: var(--s-7) 0; }
.qf-prose img { max-width: 100%; height: auto; border-radius: var(--r-md); }
.qf-prose table { width: 100%; border-collapse: collapse; margin: var(--s-5) 0; font-size: var(--fs-14); }
.qf-prose th, .qf-prose td { text-align: left; padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--divider); }
.qf-prose th { color: var(--text-muted); font-weight: 600; }
.qf-prose kbd { font: 600 var(--fs-12) var(--font-mono); background: var(--bg-input); border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: var(--r-sm); padding: 0 var(--s-2); color: var(--text-strong); }
/* 기사 본문(010): 3~6문장 문단이 3~5개 이어지는 뉴스 전말용. 본문 16px, 문단 사이 한 줄(--s-6)로 숨을 트고,
   한국어 어절이 줄 끝에서 쪼개지지 않게 keep-all(공백 없는 긴 URL 등은 anywhere 로 안전망). */
.qf-prose--article { font-size: var(--fs-16); word-break: keep-all; overflow-wrap: anywhere; }
.qf-prose--article p { margin: var(--s-6) 0; }
.qf-prose--article p:first-child { margin-top: 0; }

/* ═══════════════════════════════════════════════════════════════
   Media — 미디어/썸네일 레이어(layer: generic). 005 미디어 레이어.
   비-챗 미디어 앱(라이브러리/갤러리)용 제너릭 프리미티브.
   ═══════════════════════════════════════════════════════════════ */

/* ───── Utility: line clamp · layer: generic ───── */
/* N줄 후 말줄임. --qf-clamp-lines 노브로 줄 수 조절. --reserve는 줄 수만큼 높이 예약(레이아웃 점프 방지). */
.qf-clamp {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--qf-clamp-lines, 2); line-clamp: var(--qf-clamp-lines, 2);
  overflow: hidden; overflow-wrap: break-word;
}
.qf-clamp--1 { --qf-clamp-lines: 1; }
.qf-clamp--3 { --qf-clamp-lines: 3; }
.qf-clamp--reserve { min-height: calc(var(--qf-clamp-lines, 2) * 1em * var(--lh-snug)); }

/* ───── Media: Media card · layer: generic ───── */
/* 종횡비 고정 미디어 슬롯(레터박스) + 오버레이 배지/토글 앵커 + 본문. qf-card 로는 표현 불가한 프리미티브.
   배지=qf-badge, 토글=qf-btn--icon, 로딩=qf-skel 재사용. */
.qf-media-card {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--bg-panel); overflow: hidden;
}
  /* 빈 슬롯도 미디어 영역으로 읽히게 미세 inset 보더 */
.qf-media-card__media {
  position: relative; aspect-ratio: var(--aspect-16x9);
  background: var(--bg-media); overflow: hidden; box-shadow: inset 0 0 0 1px var(--border);
  display: grid; place-items: center;
}
.qf-media-card__media > img,
.qf-media-card__media > video { width: 100%; height: 100%; object-fit: cover; display: block; }
.qf-media-card--2x3 .qf-media-card__media { aspect-ratio: var(--aspect-2x3); }
.qf-media-card--auto .qf-media-card__media { aspect-ratio: auto; min-height: var(--s-12); }
/* 오버레이 앵커(상단 좌/우) + 밝은 썸네일 위 배지 AA용 스크림 */
.qf-media-card__media::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: var(--media-overlay-scrim); z-index: 0;
}
.qf-media-card__overlay { position: absolute; top: var(--s-2); z-index: 1; display: flex; gap: var(--s-1); align-items: center; }
.qf-media-card__overlay--start { left: var(--s-2); }
.qf-media-card__overlay--end { right: var(--s-2); }
/* interactive(hover-elevate + 키보드 포커스). 활성 미디어 슬롯은 role=button+tabindex=0. */
.qf-media-card--interactive { cursor: pointer; transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard); }
.qf-media-card--interactive:hover { border-color: var(--border-strong); box-shadow: var(--elev-3); }
.qf-media-card--interactive:focus-visible,
.qf-media-card__media:focus-visible { outline: none; box-shadow: var(--ring-accent); }
/* body */
.qf-media-card__body { padding: var(--s-3); display: flex; flex-direction: column; gap: var(--s-1); }
.qf-media-card__title { font: 600 var(--fs-13)/var(--lh-snug) var(--font-sans); color: var(--text); }
.qf-media-card__meta { display: flex; flex-wrap: wrap; gap: var(--s-2); font-size: var(--fs-12); color: var(--text-muted); }
/* loading(skeleton) / error(fallback) — 미디어 슬롯을 덮음 */
.qf-media-card__skeleton { position: absolute; inset: 0; z-index: 2; }
.qf-media-card__fallback {
  position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; align-content: center; gap: var(--s-2);
  padding: var(--s-4); text-align: center; color: var(--text-muted); font-size: var(--fs-13); background: var(--bg-media);
}
.qf-media-card--error .qf-media-card__media > img,
.qf-media-card--error .qf-media-card__media > video { display: none; }

/* ───── Media: On-media overlay · layer: generic ───── */
/* 영상/이미지 스테이지 위 글래스 컨트롤·상태 크롬. 테마 무관 고정색(--on-media-*) + safe-area +
   pointer-events 패스스루(컨테이너 none, 자식만 auto → 네이티브 video 컨트롤로 탭 통과). qf-modal 부적합. */
.qf-media-overlay {
  position: absolute; inset: 0; z-index: var(--z-media-overlay);
  display: flex; flex-direction: column; justify-content: space-between;
  padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
  pointer-events: none; color: var(--on-media-text);
}
.qf-media-overlay__top, .qf-media-overlay__bottom { display: flex; align-items: center; gap: var(--s-2); }
.qf-media-overlay__pill {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-3); border-radius: var(--r-pill);
  background: var(--on-media-glass-bg); color: var(--on-media-text);
  border: 1px solid var(--on-media-border); font: 500 var(--fs-13) var(--font-sans);
  -webkit-backdrop-filter: blur(var(--scrim-blur)); backdrop-filter: blur(var(--scrim-blur));
  pointer-events: auto;
}
.qf-media-overlay__pill--button { cursor: pointer; min-height: 44px; min-width: 44px; justify-content: center; transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard); }
.qf-media-overlay__pill--button:hover { background: var(--on-media-glass-bg-strong); border-color: var(--on-media-border-hover); }
.qf-media-overlay__pill--button:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.qf-media-overlay__pill--danger { color: var(--on-media-danger-text); border-color: var(--on-media-danger-border); }
.qf-media-overlay__hud {
  position: absolute; inset: 0; margin: auto; width: max-content; max-width: 80%; height: max-content;
  display: flex; flex-direction: column; align-items: center; gap: var(--s-3); text-align: center;
  padding: var(--s-6); border-radius: var(--r-lg);
  background: var(--on-media-glass-bg-strong); border: 1px solid var(--on-media-border); color: var(--on-media-text);
  pointer-events: auto;
}
.qf-media-overlay__hud--error { border-color: var(--on-media-danger-border); color: var(--on-media-danger-text); }
.qf-media-overlay__title { font: 600 var(--fs-15) var(--font-sans); }
.qf-media-overlay__body { font-size: var(--fs-13); color: var(--on-media-text-muted); }

/* ───── Media: Lightbox · layer: generic ───── */
/* 크롬 없는 풀스크린 contain-fit 뷰어(줌) 또는 풀블리드 스크롤 컨택트 시트. qf-modal(다이얼로그 콘텐츠 모델)과 다름. */
.qf-lightbox {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: flex; align-items: center; justify-content: center;
  background: var(--scrim); -webkit-backdrop-filter: blur(var(--scrim-blur)); backdrop-filter: blur(var(--scrim-blur));
}
.qf-lightbox--dim, .qf-lightbox--sheet { background: var(--scrim-strong); -webkit-backdrop-filter: none; backdrop-filter: none; }
.qf-lightbox__close {
  position: absolute; top: var(--safe-top); right: var(--safe-right); z-index: 1;
  display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px;
  border-radius: var(--r-pill); background: var(--on-media-glass-bg); border: 1px solid var(--on-media-border);
  color: var(--on-media-text); cursor: pointer;
}
.qf-lightbox__close:hover { background: var(--on-media-glass-bg-strong); border-color: var(--on-media-border-hover); }
.qf-lightbox__close:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.qf-lightbox__stage { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; padding: var(--s-8); }
.qf-lightbox__media { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--r-md); }
.qf-lightbox__sheet { width: 100%; height: 100%; overflow: auto; overscroll-behavior: contain; display: flex; flex-direction: column; align-items: center; gap: var(--s-2); padding: var(--s-6) var(--s-4); }
.qf-lightbox__frame { max-width: 100%; }
.qf-lightbox__caption { position: absolute; left: 0; right: 0; bottom: var(--safe-bottom); text-align: center; color: var(--on-media-text-muted); font-size: var(--fs-13); }

/* ───── Layout: App shell — locked (100dvh) mode · layer: generic ───── */
/* 풀뷰포트 락: 셸 전체 고정 높이 + overflow hidden, 내부 스크롤은 __main(또는 qf-scroll-region)만.
   호스트 html/body 에 overflow:hidden 필요(앱이 라우트/레이아웃 클래스로 적용 — 문서화된 호스트 요구). */
.qf-app-shell--locked { height: var(--shell-vh); overflow: hidden; }
.qf-scroll-region { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }

/* ───── Feedback: Typing dots · layer: generic ───── */
/* '생성 중'/타이핑 3점 staggered bounce(standalone). 챗 타이핑 바(.qf-typing, --h-typingbar)와
   클래스 충돌을 피해 -dots 루트. spinner(작업 중)·progress(정량)와 의미 구분.
   reduced-motion 에서 scale(0) 동결 금지 — opacity 페이드로 대체(현 앱 버그 수정). */
.qf-typing-dots { display: inline-flex; align-items: center; gap: var(--s-1); }
.qf-typing-dots__dot {
  width: var(--size-typing-dot); height: var(--size-typing-dot);
  border-radius: var(--r-pill); background: var(--text-muted);
  animation: qf-typing-bounce var(--motion-typing) var(--ease-standard) infinite;
}
.qf-typing-dots__dot:nth-child(1) { animation-delay: -0.32s; }
.qf-typing-dots__dot:nth-child(2) { animation-delay: -0.16s; }
.qf-typing-dots--onAccent .qf-typing-dots__dot { background: var(--accent); }
.qf-typing-dots--sm { --size-typing-dot: 5px; }
.qf-typing-dots--lg { --size-typing-dot: 10px; }
@keyframes qf-typing-bounce { 0%, 80%, 100% { transform: scale(0.5); opacity: 0.5; } 40% { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .qf-typing-dots__dot { animation-name: qf-typing-fade; }
  @keyframes qf-typing-fade { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
}

/* ───── Display: Sort toggle · layer: generic ───── */
/* 3상태(asc↔desc↔off) 정렬 토글. th[aria-sort] 또는 button. 방향은 색이 아니라 aria-sort/글리프로 전달.
   qf-table__sort 와 동일 의미(테이블 헤더용 프리미티브). */
.qf-sort-toggle {
  display: inline-flex; align-items: center; gap: var(--s-1);
  min-height: 44px; padding: var(--s-1) var(--s-2);
  background: none; border: none; border-radius: var(--r-sm);
  color: var(--text-secondary); font: 500 var(--fs-13) var(--font-sans);
  cursor: pointer; text-align: left;
}
.qf-sort-toggle:hover { background: var(--bg-hover); color: var(--text); }
.qf-sort-toggle:focus-visible { outline: none; box-shadow: var(--ring-accent); }
.qf-sort-toggle.is-active { color: var(--text-strong); }
.qf-sort-toggle__indicator { font-size: var(--sort-indicator-size); color: var(--text-muted); line-height: 1; }
.qf-sort-toggle.is-active .qf-sort-toggle__indicator { color: var(--accent); }
.qf-sort-toggle:not(.is-active) .qf-sort-toggle__indicator,
.qf-sort-toggle[data-direction="none"] .qf-sort-toggle__indicator { opacity: 0; }
.qf-sort-toggle--lg { font-size: var(--fs-14); }

/* ───── A11y: 44px 터치 타깃 보장(hit-area 확장) · layer: generic ───── */
/* 밀도가 빡빡해 시각 크기를 키울 수 없는 작은 컨트롤(배너 닫기 24px, 컴포저 행 버튼 40px)에
   투명 ::after 로 44px 클릭 영역만 확장한다 — 레이아웃·시각 불변, WCAG 2.5.5 / DS 44px 규칙 충족. */
.qf-banner__close, .qf-composer__send, .qf-composer__attach, .qf-composer__action { position: relative; }
.qf-banner__close::after, .qf-composer__send::after, .qf-composer__attach::after, .qf-composer__action::after {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 44px; height: 44px;
}

/* ═══════════════════════════════════════════════════════════════
   qufox-docs 엔진 컴포넌트(007) — Callout · ToC · Prose 확장.
   전부 layer: generic. qf-prose 본문/블로그 레이아웃에서 소비한다.
   타입 상태색은 테마 인지 텍스트 토큰(--text-info/-ok/-warn/-danger ·
   --accent-text)만 사용해 좌측 강조선 + 아이콘에 싣고, 제목/본문 텍스트는
   중립 토큰(--text-strong/-secondary)으로 두어 색 단독 의존을 피한다
   (WCAG 1.4.1). 다크 기본 + 라이트 자동 추종.
   ═══════════════════════════════════════════════════════════════ */

/* ───── Feedback: Callout · layer: generic ───── */
/* Obsidian 스타일 콜아웃 — qf-notice 를 13종 타입으로 확장한 리치 안내 블록.
   접기는 네이티브 <details class="qf-callout"> + <summary class="qf-callout__title">
   (qf-accordion 과 동일한 캐럿 패턴, 키보드·스크린리더 동작 무료). */
.qf-callout {
  margin: var(--s-5) 0;
  border: 1px solid var(--border);
  border-left-width: var(--s-1);
  border-left-color: var(--accent-text);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
  color: var(--text-secondary);
  font-size: var(--fs-14); line-height: var(--lh-normal);
}
.qf-callout__title {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  font: 600 var(--fs-15) var(--font-sans); color: var(--text-strong);
}
.qf-callout__icon { flex-shrink: 0; color: var(--accent-text); display: flex; }
/* 본문 — 제목이 있으면 상단 패딩을 접어 제목 바로 아래에서 시작. qf-prose 자식
   (p/ul/code…)은 prose 스타일을 그대로 상속하고, 가장자리 margin 만 접는다. */
.qf-callout__body { padding: 0 var(--s-4) var(--s-4); }
.qf-callout__body > :first-child { margin-top: 0; }
.qf-callout__body > :last-child { margin-bottom: 0; }
/* 콜아웃 중첩 시 안쪽 콜아웃 상하 마진 유지(body 자식 margin 접기와 무관하게). */
.qf-callout .qf-callout { margin: var(--s-4) 0; }

/* 접기(collapsible) — summary=__title. 네이티브 마커 제거 + 우측 캐럿. */
details.qf-callout > .qf-callout__title { cursor: pointer; list-style: none; }
details.qf-callout > .qf-callout__title::-webkit-details-marker { display: none; }
details.qf-callout > .qf-callout__title::after {
  content: ''; margin-left: auto; width: 7px; height: 7px;
  border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted);
  transform: rotate(45deg); transition: transform var(--dur-fast) var(--ease-standard);
}
details.qf-callout[open] > .qf-callout__title::after { transform: rotate(-135deg); }
details.qf-callout > .qf-callout__title:hover { background: var(--bg-hover); }
details.qf-callout > .qf-callout__title:focus-visible { outline: none; box-shadow: var(--ring-focus); }

/* 타입 13종 — 좌측 강조선 + 아이콘 색(테마 인지 토큰). 제목은 --text-strong 유지. */
.qf-callout--note     { border-left-color: var(--text-info); }
.qf-callout--note     .qf-callout__icon { color: var(--text-info); }
.qf-callout--abstract { border-left-color: var(--text-ok); }
.qf-callout--abstract .qf-callout__icon { color: var(--text-ok); }
.qf-callout--info     { border-left-color: var(--text-info); }
.qf-callout--info     .qf-callout__icon { color: var(--text-info); }
.qf-callout--todo     { border-left-color: var(--text-info); }
.qf-callout--todo     .qf-callout__icon { color: var(--text-info); }
.qf-callout--tip      { border-left-color: var(--text-ok); }
.qf-callout--tip      .qf-callout__icon { color: var(--text-ok); }
.qf-callout--success  { border-left-color: var(--text-ok); }
.qf-callout--success  .qf-callout__icon { color: var(--text-ok); }
.qf-callout--question { border-left-color: var(--text-warn); }
.qf-callout--question .qf-callout__icon { color: var(--text-warn); }
.qf-callout--warning  { border-left-color: var(--text-warn); }
.qf-callout--warning  .qf-callout__icon { color: var(--text-warn); }
.qf-callout--failure  { border-left-color: var(--text-danger); }
.qf-callout--failure  .qf-callout__icon { color: var(--text-danger); }
.qf-callout--danger   { border-left-color: var(--text-danger); }
.qf-callout--danger   .qf-callout__icon { color: var(--text-danger); }
.qf-callout--bug      { border-left-color: var(--text-danger); }
.qf-callout--bug      .qf-callout__icon { color: var(--text-danger); }
.qf-callout--example  { border-left-color: var(--accent-text); }
.qf-callout--example  .qf-callout__icon { color: var(--accent-text); }
.qf-callout--quote    { border-left-color: var(--border-strong); }
.qf-callout--quote    .qf-callout__icon { color: var(--text-muted); }

/* ───── Navigation: Table of contents · layer: generic ───── */
/* 블로그 글 우측(qf-app-shell__aside)에 sticky 로 붙는 "이 페이지에서" 목차.
   활성 항목 = 좌측 세로 인디케이터(--accent) + 텍스트 강조(색 단독 회피). */
.qf-toc {
  position: sticky; top: var(--s-5);
  max-height: calc(100vh - var(--s-9)); overflow-y: auto;
  font-size: var(--fs-13);
}
.qf-toc__title {
  font: 600 var(--fs-11) var(--font-sans); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
  margin: 0 0 var(--s-2);
}
.qf-toc ul { list-style: none; margin: 0; padding: 0; }
.qf-toc__item { margin: 0; }
.qf-toc__item--d2 { padding-left: var(--s-4); }
.qf-toc__item--d3 { padding-left: var(--s-7); }
.qf-toc__link {
  /* block(=flex 아님)이라야 text-overflow:ellipsis 가 직접 텍스트에 적용된다.
     padding(12*2) + line-height(20) = 44px 터치 타깃(WCAG 2.5.5), 단일 행 세로 중앙. */
  display: block;
  padding: var(--s-4) var(--s-3);
  line-height: var(--s-6);
  border-left: 2px solid transparent;
  color: var(--text-muted); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.qf-toc__link:hover { color: var(--text-secondary); }
.qf-toc__link--active {
  border-left-color: var(--accent);
  color: var(--text-strong); font-weight: 600;
}

/* ───── Display: Prose footnotes · layer: generic ───── */
/* 마크다운 각주 — 본문 하단 각주 목록(section) + 역참조 링크. 본문 내 참조
   위첨자(sup>a)는 --accent-text 로 강조(아래 qf-prose 스코프 규칙). */
.qf-footnotes {
  margin-top: var(--s-8); padding-top: var(--s-5);
  border-top: 1px solid var(--divider);
  font-size: var(--fs-13); color: var(--text-secondary);
}
.qf-footnotes ol { margin: 0; padding-left: var(--s-6); }
.qf-footnotes li { margin: var(--s-2) 0; }
.qf-footnotes li:target { background: var(--accent-bg-faint); border-radius: var(--r-xs); }
.qf-footnotes__backref { margin-left: var(--s-2); color: var(--text-muted); text-decoration: none; }
.qf-footnotes__backref:hover { color: var(--text-secondary); }
/* 본문 내 각주 참조 위첨자 링크(마크다운 sup>a). */
.qf-prose sup > a { color: var(--accent-text); text-decoration: none; font-weight: 600; }
.qf-prose sup > a:hover { text-decoration: underline; }

/* ───── Actions: Link — unresolved wikilink variant · layer: generic ───── */
/* 존재하지 않는 내부 문서로의 위키링크. href 없음 + aria-disabled="true".
   색 + 점선 밑줄 + 기본 커서로 "미해결" 을 색 단독이 아니게 전달. */
.qf-link--unresolved {
  color: var(--text-muted); text-decoration: underline dotted; text-underline-offset: 2px;
  cursor: default;
}
/* hover 에도 점선 유지 — 같은 특이도의 .qf-link:hover(실선)를 이기려 밑줄을 재선언
   (미해결의 색-독립 단서인 점선이 hover 에서 실선으로 회귀하지 않게). */
.qf-link--unresolved:hover { color: var(--text-muted); text-decoration: underline dotted; }

/* ───── Display: Transclusion embed · layer: generic ───── */
/* 다른 노트를 본문에 인라인 인용하는 카드(Obsidian 트랜스클루전). blockquote 로 마크업.
   주의: 채팅 링크/미디어 임베드 .qf-embed 와의 이름 충돌을 피해 .qf-transclude 로 명명. */
.qf-transclude {
  margin: var(--s-5) 0; padding: var(--s-4) var(--s-5);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  background: var(--bg-panel); color: var(--text-secondary);
}
.qf-transclude > :first-child { margin-top: 0; }
.qf-transclude > :last-child { margin-bottom: 0; }
.qf-transclude__source {
  display: flex; align-items: center; gap: var(--s-2);
  margin-bottom: var(--s-3); padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--divider);
  font-size: var(--fs-12); color: var(--text-muted);
}
.qf-transclude__source a { color: var(--link); text-decoration: none; }
.qf-transclude__source a:hover { text-decoration: underline; }
/* qf-prose 본문(1차 컨텍스트) 안에서 렌더될 때 .qf-prose blockquote(특이도 0,1,1)가
   border-left/padding 을 이긴다. 같은 스코프에서 트랜스클루전 스펙(중립 좌측선 +
   세로 패딩)을 재확정해, prose 의 --accent 인용 바와 구분되게 한다(특이도 0,2,0). */
.qf-prose .qf-transclude {
  border-left: 3px solid var(--border-strong);
  padding: var(--s-4) var(--s-5);
}

/* ═══════════════════════════════════════════════════════════════
   뉴스형 목록 구성요소(010 · issue-hub 요청) — Story · Source · Chip · DayRule · Timeline.
   전부 layer: generic. "목록형 콘텐츠"(뉴스/이슈/블로그/스트림) 일반의 요소로, 분류·메타·제목·요약·출처·
   썸네일의 관계를 구성요소로 고정한다. 색·치수는 토큰만 사용(--sz-story-*, --sz-timeline-*, --aspect-4x3).
   ═══════════════════════════════════════════════════════════════ */

/* ───── Display: Story · layer: generic ───── */
/* 콘텐츠 목록 항목(뉴스/이슈 카드). 카드 표면 없이 구분선 + 위계(kicker → title → summary → sources)로
   뉴스 목록의 리듬을 만든다. 전체가 링크(<a class="qf-story">)일 수도, 제목만 링크일 수도 있다.
   --pictured: 텍스트 1열 + 4:3 고정 프레임 썸네일 2열(좁은 폭에서는 크기만 축소, 자리 유지).
   --lead: 첫 항목 강조(제목 24px, 썸네일 확대. 좁은 폭에선 썸네일이 16:9 로 텍스트 위에 놓임).
   --compact: 제목 + kicker 만(요약·출처는 마크업이 있어도 숨김 → 같은 마크업을 밀도만 바꿔 재사용). */
.qf-story {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr);
  column-gap: var(--s-5); align-items: start;
  padding: var(--s-5) var(--s-3); border-radius: var(--r-md);
  color: inherit; text-decoration: none;
  transition: background var(--dur-fast) var(--ease-standard);
}
.qf-story > * { margin: 0; min-width: 0; }
/* 구분선은 ::after(직선) — border 로 두면 border-radius 를 따라 양끝이 휜다. */
.qf-story::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--divider); }
.qf-story__kicker {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-3);
  font-size: var(--fs-12); color: var(--text-muted); line-height: var(--lh-snug);
}
.qf-story__title {
  margin-top: var(--s-2);
  font: 600 var(--fs-18)/var(--lh-snug) var(--font-sans); color: var(--text-strong);
  letter-spacing: var(--tracking-tight); word-break: keep-all; overflow-wrap: anywhere;
  transition: color var(--dur-fast) var(--ease-standard);
}
.qf-story__title a { color: inherit; text-decoration: none; }
.qf-story__summary { margin-top: var(--s-2); font-size: var(--fs-14); line-height: var(--lh-normal); color: var(--text-secondary); }
.qf-story__sources { margin-top: var(--s-3); }
/* 썸네일: 4:3 고정 프레임 + cover. <img> 자체이거나 <img> 를 감싼 컨테이너 둘 다 허용. */
.qf-story__media {
  display: block; width: 100%; height: auto; aspect-ratio: var(--aspect-4x3); object-fit: cover;
  border-radius: var(--r-md); background: var(--bg-media); overflow: hidden;
}
.qf-story__media > img, .qf-story__media > video { display: block; width: 100%; height: 100%; object-fit: cover; }
.qf-story--pictured {
  grid-template-columns: minmax(0, 1fr) var(--sz-story-media);
  /* 행 = 스페이서 | kicker | title | summary | sources | 스페이서. 썸네일이 텍스트보다 높으면 초과분이
     양끝 1fr 로만 가서 슬롯 사이 간격이 벌어지지 않고(요약·출처 없음, --compact 포함) 텍스트가 세로 중앙에 온다. */
  grid-template-rows: 1fr auto auto auto auto 1fr;
}
.qf-story--pictured .qf-story__kicker { grid-column: 1; grid-row: 2; }
.qf-story--pictured .qf-story__title { grid-column: 1; grid-row: 3; }
.qf-story--pictured .qf-story__summary { grid-column: 1; grid-row: 4; }
.qf-story--pictured .qf-story__sources { grid-column: 1; grid-row: 5; }
.qf-story--pictured .qf-story__media { grid-column: 2; grid-row: 1 / -1; align-self: center; }
.qf-story--lead .qf-story__title { font-size: var(--fs-24); line-height: var(--lh-tight); }
.qf-story--lead .qf-story__summary { font-size: var(--fs-15); }
.qf-story--lead.qf-story--pictured { grid-template-columns: minmax(0, 1fr) var(--sz-story-media-lead); }
.qf-story--compact { padding-block: var(--s-3); }
.qf-story--compact .qf-story__title { font-size: var(--fs-15); margin-top: var(--s-1); }
.qf-story--compact .qf-story__summary, .qf-story--compact .qf-story__sources { display: none; }
.qf-story--compact.qf-story--pictured { grid-template-columns: minmax(0, 1fr) var(--sz-story-media-sm); }
/* 상호작용: 항목 전체가 링크면 행 hover 틴트 + 제목 accent. 제목만 링크면 제목 hover 만. */
a.qf-story:hover { background: var(--bg-row-hover); }
a.qf-story:hover .qf-story__title, .qf-story__title a:hover { color: var(--accent-text); }
a.qf-story:focus-visible { outline: none; box-shadow: var(--ring-focus); }
/* 방문 링크: 제목만 한 톤 낮춰 읽은 항목을 구분(뉴스 목록 관례). 색 외 단서 없음 = 보조 정보이므로 허용.
   :visited 는 색 계열 속성만 허용되어 구조 변화 없음. 원치 않으면 소비처가 재선언. */
a.qf-story:visited .qf-story__title, .qf-story__title a:visited { color: var(--text-secondary); }
a.qf-story:visited:hover .qf-story__title, .qf-story__title a:visited:hover { color: var(--accent-text); }
@media (max-width: 600px) {
  .qf-story { column-gap: var(--s-4); padding-inline: 0; }
  .qf-story--pictured, .qf-story--compact.qf-story--pictured { grid-template-columns: minmax(0, 1fr) var(--sz-story-media-sm); }
  .qf-story--lead.qf-story--pictured { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto auto auto; }
  .qf-story--lead.qf-story--pictured .qf-story__media {
    grid-column: 1; grid-row: 1; aspect-ratio: var(--aspect-16x9); margin-bottom: var(--s-3);
  }
}

/* ───── Display: Source · layer: generic ───── */
/* 출처 칩 — 커뮤니티/매체 이름 하나. 중립 톤(outline pill)으로 태그(라벨)·배지(상태/알림)와 구분한다.
   __count 는 회색 숫자(빨간 count 배지 금지), __mark 는 이니셜/파비콘. --link 는 눌러서 원문 목록으로.
   qf-sources 는 묶음 컨테이너(wrap, 간격 고정). */
.qf-sources { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.qf-source {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-pill);
  background: transparent; color: var(--text-secondary);
  font: 500 var(--fs-12)/var(--lh-snug) var(--font-sans); white-space: nowrap;
  text-decoration: none; vertical-align: middle;
}
.qf-source__mark {
  display: inline-grid; place-items: center; flex-shrink: 0;
  width: var(--s-5); height: var(--s-5); margin-left: calc(-1 * var(--s-2));
  border-radius: var(--r-pill); background: var(--bg-selected); color: var(--text-secondary);
  font: 600 var(--fs-11)/1 var(--font-sans); overflow: hidden;
}
.qf-source__mark > img, .qf-source__mark > svg { display: block; width: 100%; height: 100%; object-fit: cover; }
.qf-source__count { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.qf-source--link { cursor: pointer; transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard); }
.qf-source--link:hover { background: var(--bg-hover); border-color: var(--border-strong); color: var(--text); }
.qf-source--link:hover .qf-source__count { color: var(--text-secondary); }
.qf-source--link:focus-visible { outline: none; box-shadow: var(--ring-focus); }
/* 터치 환경(any-pointer: coarse — 터치 노트북 포함): 눌리는 칩만 44px 로 키운다(정적 칩·카드 안 칩은 그대로). */
@media (any-pointer: coarse) {
  .qf-source--link { min-height: var(--m-touch); padding-inline: var(--s-4); }
}

/* ───── Navigation: Chip · layer: generic ───── */
/* 데스크톱 필터 칩 그룹. 선택 상태 = aria-current="page"(링크) 또는 aria-pressed="true"(버튼).
   모바일 qf-m-filter-chip 과 같은 토큰(--r-pill · --accent-subtle · --accent)으로 시각 일관.
   라벨 색만 AA 를 위해 --accent-on-subtle(모바일 기본 변형의 --accent 는 4.5:1 미달 가능).
   qf-chips 는 묶음(wrap 기본), --scroll 은 좁은 폭에서 한 줄 가로 스크롤(스크롤바 숨김). */
.qf-chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.qf-chips--scroll {
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  padding: var(--s-2); margin: calc(-1 * var(--s-2)); /* 포커스 링(halo 포함)이 overflow 에 잘리지 않게 */
}
.qf-chips--scroll::-webkit-scrollbar { display: none; }
.qf-chip {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3) var(--s-4); border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--bg-panel); color: var(--text-secondary);
  font: 500 var(--fs-13)/var(--lh-tight) var(--font-sans); white-space: nowrap;
  text-decoration: none; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.qf-chip:hover { background: var(--bg-hover); border-color: var(--border-strong); color: var(--text); }
.qf-chip:hover .qf-chip__count { color: var(--text-secondary); }
.qf-chip:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.qf-chip__count { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.qf-chip[aria-current="page"], .qf-chip[aria-pressed="true"] {
  background: var(--accent-subtle); border-color: var(--accent); color: var(--accent-on-subtle); font-weight: 600;
}
.qf-chip[aria-current="page"] .qf-chip__count, .qf-chip[aria-pressed="true"] .qf-chip__count { color: var(--accent-on-subtle); }
@media (any-pointer: coarse) {
  .qf-chip { min-height: var(--m-touch); }
}

/* ───── Display: Day rule · layer: generic ───── */
/* 목록 사이 날짜/구간 구분줄: eyebrow 톤 라벨 + 선. <li role="separator"> 로 쓴다.
   --sticky 는 스크롤 시 상단에 붙고 배경을 앱 배경으로 깔아 아래 항목이 비치지 않는다.
   ARIA separator 는 자식이 presentational 이라 라벨이 낭독되지 않는다 → role="separator" 를 쓰면 aria-label 로 같은 텍스트를 함께 준다.
   노브: --qf-dayrule-top(고정 헤더 아래 오프셋, 기본 0) · --qf-dayrule-bg(패널 안에서 쓸 때 배경, 기본 --bg-app). */
.qf-dayrule {
  display: flex; align-items: center; gap: var(--s-3); list-style: none;
  padding-block: var(--s-3); margin: 0;
  font: 600 var(--fs-11)/var(--lh-tight) var(--font-sans); text-transform: uppercase; letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
}
.qf-dayrule__label { color: var(--accent-text); white-space: nowrap; }
.qf-dayrule::after { content: ""; flex: 1; height: 1px; background: var(--divider); }
.qf-dayrule--sticky {
  position: sticky; top: var(--qf-dayrule-top, 0); z-index: var(--z-sticky);
  background: var(--qf-dayrule-bg, var(--bg-app));
}
/* 스택 안에서 첫 구분줄이 아니면 앞 그룹과 한 칸 더 띈다. */
.qf-dayrule:not(:first-child) { margin-top: var(--s-4); }

/* ───── Display: Timeline · layer: generic ───── */
/* 세로 타임라인(시간순 기록. 진행 단계인 qf-steps 와 다름). 열 = 시각 | 마커 | 본문.
   마커 색은 --qf-timeline-marker 하나로 열어 둔다(소비처가 토큰으로 지정: var(--viz-2) 등). 기본 --text-muted
   (--border-strong 은 그래픽 대비 3:1 미달 — 다크 2.29, 라이트 1.65 — 이라 쓰지 않는다).
   __day 는 날짜 전환 구분(본문 열에 정렬). --stacked 는 시각을 본문 위로 올린다(아주 좁은 폭용 opt-in). */
.qf-timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.qf-timeline__item, .qf-timeline__day { position: relative; margin: 0; }
.qf-timeline__item {
  display: grid; grid-template-columns: var(--sz-timeline-time) var(--sz-timeline-marker) minmax(0, 1fr);
  column-gap: var(--s-3); align-items: start; padding-block: var(--s-3);
}
/* 세로선: 마커 열 중앙, 항목마다 이어 그려 연속선이 된다. 첫/끝 항목은 마커에서 시작/끝. */
.qf-timeline__item::before, .qf-timeline__day::before {
  content: ""; position: absolute; top: 0; bottom: 0; width: 2px; background: var(--divider);
  left: calc(var(--sz-timeline-time) + var(--s-3) + var(--sz-timeline-marker) / 2 - 1px);
}
.qf-timeline__item:first-child::before { top: calc(var(--s-3) + var(--fs-14) * var(--lh-normal) / 2); }
.qf-timeline__item:last-child::before { bottom: calc(100% - var(--s-3) - var(--fs-14) * var(--lh-normal) / 2); }
.qf-timeline__day:first-child::before { top: 50%; }
.qf-timeline__day:last-child::before { bottom: 50%; }
.qf-timeline__time {
  grid-column: 1; text-align: end; white-space: nowrap;
  font: 500 var(--fs-12)/var(--lh-normal) var(--font-sans); color: var(--text-muted); font-variant-numeric: tabular-nums;
  padding-top: calc((var(--fs-14) * var(--lh-normal) - var(--fs-12) * var(--lh-normal)) / 2);
}
.qf-timeline__marker {
  grid-column: 2; position: relative; z-index: 1;
  width: var(--sz-timeline-marker); height: var(--sz-timeline-marker); border-radius: var(--r-pill);
  background: var(--qf-timeline-marker, var(--text-muted));
  margin-top: calc((var(--fs-14) * var(--lh-normal) - var(--sz-timeline-marker)) / 2);
}
.qf-timeline__body { grid-column: 3; min-width: 0; font-size: var(--fs-14); line-height: var(--lh-normal); color: var(--text); overflow-wrap: anywhere; }
.qf-timeline__body > .qf-source { margin-right: var(--s-1); }
/* 본문 줄에 메타 텍스트가 함께 있어 "본문 안 링크" — 색 외 단서로 옅은 밑줄(WCAG 1.4.1). hover 시 accent + 진한 밑줄. */
.qf-timeline__body > .qf-link {
  color: var(--text-strong); font-weight: 500;
  text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 2px;
}
.qf-timeline__body > .qf-link:hover { color: var(--accent-text); text-decoration-color: currentColor; }
.qf-timeline__body > .qf-link:visited { color: var(--text-secondary); }
.qf-timeline__body > .qf-link:visited:hover { color: var(--accent-text); }
.qf-timeline__meta { color: var(--text-muted); font-size: var(--fs-12); white-space: nowrap; margin-left: var(--s-1); }
.qf-timeline__day {
  padding: var(--s-4) 0 var(--s-2);
  padding-left: calc(var(--sz-timeline-time) + var(--s-3) + var(--sz-timeline-marker) + var(--s-3));
  font: 600 var(--fs-11)/var(--lh-tight) var(--font-sans); text-transform: uppercase; letter-spacing: var(--tracking-caps);
  color: var(--text-secondary);
}
.qf-timeline__day:not(:first-child) { margin-top: var(--s-2); }
/* --stacked: 시각이 본문 위 줄로. 열 = 마커 | (시각 / 본문). */
.qf-timeline--stacked .qf-timeline__item { grid-template-columns: var(--sz-timeline-marker) minmax(0, 1fr); }
.qf-timeline--stacked .qf-timeline__time { grid-column: 2; grid-row: 1; text-align: start; padding-top: 0; }
.qf-timeline--stacked .qf-timeline__marker { grid-column: 1; grid-row: 1 / span 2; margin-top: calc((var(--fs-12) * var(--lh-normal) - var(--sz-timeline-marker)) / 2); }
.qf-timeline--stacked .qf-timeline__body { grid-column: 2; grid-row: 2; }
.qf-timeline--stacked .qf-timeline__item::before, .qf-timeline--stacked .qf-timeline__day::before { left: calc(var(--sz-timeline-marker) / 2 - 1px); }
.qf-timeline--stacked .qf-timeline__item:first-child::before { top: calc(var(--s-3) + var(--fs-12) * var(--lh-normal) / 2); }
.qf-timeline--stacked .qf-timeline__item:last-child::before { bottom: calc(100% - var(--s-3) - var(--fs-12) * var(--lh-normal) / 2); }
.qf-timeline--stacked .qf-timeline__day { padding-left: calc(var(--sz-timeline-marker) + var(--s-3)); }
