{
  "$description": "qufox 컴포넌트 매니페스트 — 사람과 AI 공용. 각 항목은 클래스 사용 인터페이스를 요약한다.",
  "$note": "범용/채팅 분리(docs/tasks/001, docs/adr/0002) 완료. layer = generic | chat | mobile. 모바일 내부 도메인(generic/chat)은 ADR 0002 표 참조. qf-doc-*(문서 전용)은 제외. category 필드 추가(카탈로그 그룹, 페이지 생성 기준).",
  "version": "0.2.0",
  "tokensRef": "../tokens/dist/tokens.json",
  "components": [
    {
      "name": "Button",
      "class": "qf-btn",
      "layer": "generic",
      "category": "Actions",
      "purpose": "기본 액션 버튼입니다.",
      "variants": [
        "qf-btn--primary",
        "qf-btn--secondary",
        "qf-btn--ghost",
        "qf-btn--danger",
        "qf-btn--link",
        "qf-btn--sm",
        "qf-btn--lg",
        "qf-btn--icon",
        "qf-btn--touch"
      ],
      "requiredTokens": [
        "--accent",
        "--text",
        "--text-onAccent",
        "--border",
        "--r-md",
        "--s-3",
        "--s-5",
        "--dur-fast",
        "--bg-selected",
        "--text-strong",
        "--m-touch"
      ],
      "a11y": "키보드 포커스가 보이도록 하고, 비활성은 disabled 또는 aria-disabled 로 전달합니다. 아이콘 전용 버튼은 aria-label 로 의미를 보충하고, 텍스트 대비는 AA 4.5:1 을 지킵니다. 토글(aria-pressed)로 쓰는 ghost/icon 버튼은 눌린 상태가 selected 표면으로 표시됩니다 — 아이콘·라벨 전환과 함께 두어 색에만 기대지 않습니다. 버튼 라벨은 줄바꿈하지 않습니다(white-space: nowrap) — 긴 라벨은 줄이거나 너비를 주세요. 단독으로 선 아이콘 버튼(제목 옆 공유 등)에는 --touch 를 붙이면 터치 환경에서 보이는 크기는 그대로 두고 44px 히트 영역을 투명 ::after 로 넓힙니다(qf-navbar 안은 자동). 밀집 툴바(간격 8px 미만)에는 쓰지 않습니다 — 이웃 버튼의 탭을 가로챕니다.",
      "exampleHtml": "<button class=\"qf-btn qf-btn--primary\">Save</button>",
      "purposeEn": "A primary action button.",
      "a11yEn": "Make keyboard focus visible and convey the disabled state via disabled or aria-disabled. For icon-only buttons, add meaning with aria-label, and keep text contrast at AA 4.5:1. Ghost/icon buttons used as toggles (aria-pressed) show the pressed state as the selected surface; pair it with an icon or label change so state does not rely on color alone. Button labels do not wrap (white-space: nowrap); shorten long labels or give the button width. Add --touch to a standalone icon button (e.g. share next to a title) to extend a 44px hit area with a transparent ::after on touch devices while keeping its visual size (automatic inside qf-navbar). Do not use it in dense toolbars (gaps under 8px) — it would steal taps from neighbours."
    },
    {
      "name": "IconButton",
      "class": "qf-row-iconbtn",
      "layer": "generic",
      "category": "Actions",
      "purpose": "리스트 행에 들어가는 군더더기 없는 아이콘 전용 버튼입니다.",
      "variants": [],
      "requiredTokens": [
        "--text-muted",
        "--text-strong",
        "--accent",
        "--r-xs"
      ],
      "a11y": "아이콘만 표시하므로 aria-label 로 동작을 반드시 전달하고, 키보드 포커스를 보이게 하며 비활성은 disabled 로 알립니다. 색만으로 상태를 전하지 않도록 합니다.",
      "exampleHtml": "<button class=\"qf-row-iconbtn\" aria-label=\"Settings\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-settings\"/></svg></button>",
      "examples": [
        {
          "label": "Settings",
          "html": "<button class=\"qf-row-iconbtn\" aria-label=\"Settings\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-settings\"/></svg></button>"
        },
        {
          "label": "Search",
          "html": "<button class=\"qf-row-iconbtn\" aria-label=\"Search\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-search\"/></svg></button>"
        },
        {
          "label": "More",
          "html": "<button class=\"qf-row-iconbtn\" aria-label=\"More\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-more\"/></svg></button>"
        },
        {
          "label": "Edit",
          "html": "<button class=\"qf-row-iconbtn\" aria-label=\"Edit\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-edit\"/></svg></button>"
        },
        {
          "label": "Notifications",
          "html": "<button class=\"qf-row-iconbtn\" aria-label=\"Notifications\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-bell\"/></svg></button>"
        }
      ],
      "purposeEn": "A clean, icon-only button designed to sit in list rows.",
      "a11yEn": "Since only an icon is shown, the action must always be conveyed via aria-label; keep keyboard focus visible and signal the disabled state with disabled. Do not rely on color alone to convey state."
    },
    {
      "name": "Input",
      "class": "qf-input",
      "layer": "generic",
      "category": "Forms",
      "purpose": "한 줄 텍스트 입력 필드입니다.",
      "variants": [],
      "requiredTokens": [
        "--bg-input",
        "--text",
        "--border",
        "--border-strong",
        "--text-muted",
        "--r-lg",
        "--s-4",
        "--ring-danger"
      ],
      "a11y": "오류 상태는 aria-invalid=\"true\" 로 전달하며 보더와 링으로 함께 표시합니다. 라벨은 label 요소나 aria-label 로 연결하고, 안내문은 aria-describedby 로 묶습니다.",
      "exampleHtml": "<input class=\"qf-input\" type=\"text\" aria-label=\"Search\" placeholder=\"Search\">",
      "purposeEn": "A single-line text input field.",
      "a11yEn": "Convey the error state with aria-invalid=\"true\", reinforced by the border and focus ring. Associate the label via a label element or aria-label, and tie helper text together with aria-describedby."
    },
    {
      "name": "InputGroup",
      "class": "qf-input-group",
      "layer": "generic",
      "category": "Forms",
      "purpose": "입력과 양옆 아이콘 버튼을 한 덩어리로 묶습니다(줄바꿈 없음, 입력은 줄어들 수 있음). 예: 날짜 입력 양옆의 이전 날/다음 날 화살표(qf-btn--ghost qf-btn--icon qf-btn--sm qf-btn--touch + qf-i-chevron-left/right).",
      "purposeEn": "Groups an input with icon buttons on either side as one unit (no wrapping; the input may shrink). Example: previous/next-day arrows (qf-btn--ghost qf-btn--icon qf-btn--sm qf-btn--touch + qf-i-chevron-left/right) around a date input.",
      "variants": [],
      "requiredTokens": [
        "--s-1"
      ],
      "a11y": "화살표 버튼에는 aria-label(이전 날, 다음 날)을 두고 입력에는 label 이나 aria-label 을 둡니다. 범위 끝에서는 버튼을 disabled 로. 화살표는 28px 라 qf-btn--touch 를 붙여 터치 환경에서 44px 히트 영역을 받습니다(입력 위로 8px 겹치는 정도는 허용).",
      "a11yEn": "Give the arrow buttons an aria-label (previous day, next day) and the input a label or aria-label. Disable a button at the end of the range. The 28px arrows carry qf-btn--touch so they get a 44px hit area on touch devices (overlapping the input by 8px is acceptable).",
      "exampleHtml": "<div class=\"qf-input-group\"><button class=\"qf-btn qf-btn--ghost qf-btn--icon qf-btn--sm qf-btn--touch\" type=\"button\" aria-label=\"Previous day\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-chevron-left\"/></svg></button><input class=\"qf-input\" type=\"date\" value=\"2026-10-03\" aria-label=\"Go to date\"><button class=\"qf-btn qf-btn--ghost qf-btn--icon qf-btn--sm qf-btn--touch\" type=\"button\" aria-label=\"Next day\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-chevron-right\"/></svg></button></div>",
      "variantsAsChips": true
    },
    {
      "name": "Textarea",
      "class": "qf-textarea",
      "layer": "generic",
      "category": "Forms",
      "purpose": "여러 줄 텍스트를 입력하는 입력 필드입니다. qf-input 위에 높이, 세로 리사이즈를 더합니다. --mono 는 지시문·코드 편집용(--font-mono 12px).",
      "variants": [
        "qf-textarea--mono"
      ],
      "requiredTokens": [
        "--bg-input",
        "--border",
        "--text",
        "--r-lg",
        "--s-3",
        "--s-4",
        "--ring-accent",
        "--font-mono",
        "--fs-12",
        "--lh-normal"
      ],
      "a11y": "label 을 for/id 또는 감싼 라벨로 연결하고, 오류는 aria-invalid=true 로 표시합니다. 리사이즈는 세로만 허용해 레이아웃 깨짐을 막습니다.",
      "exampleHtml": "<textarea class=\"qf-input qf-textarea\" rows=\"3\" aria-label=\"Notes\" placeholder=\"Enter a note\"></textarea>",
      "purposeEn": "A multi-line text input field that adds height and vertical resizing on top of qf-input. --mono is for prompts and code (--font-mono, 12px).",
      "a11yEn": "Associate the label via for/id or a wrapping label, and indicate errors with aria-invalid=true. Allow vertical-only resizing to prevent layout breakage."
    },
    {
      "name": "Field",
      "class": "qf-field",
      "layer": "generic",
      "category": "Forms",
      "purpose": "라벨, 컨트롤, 도움말, 오류 메시지를 묶는 폼 필드 래퍼입니다.",
      "variants": [
        "qf-field__label",
        "qf-field__hint",
        "qf-field__error"
      ],
      "requiredTokens": [
        "--s-2",
        "--fs-12",
        "--text-secondary",
        "--text-muted",
        "--danger-400",
        "--tracking-caps"
      ],
      "a11y": "라벨을 컨트롤과 연결하고(감싸거나 for/id), 오류 텍스트는 aria-describedby 로 연결합니다. 색뿐 아니라 텍스트로 오류를 전달합니다.",
      "exampleHtml": "<label class=\"qf-field\"><span class=\"qf-field__label\">Email</span><input class=\"qf-input\" type=\"email\" placeholder=\"you@example.com\"><span class=\"qf-field__hint\">Enter your work email.</span></label>",
      "purposeEn": "A form field wrapper that groups the label, control, helper text, and error message.",
      "a11yEn": "Associate the label with the control (by wrapping it or via for/id), and link error text with aria-describedby. Convey errors with text, not color alone."
    },
    {
      "name": "DateRange",
      "class": "qf-daterange",
      "layer": "generic",
      "category": "Forms",
      "purpose": "시작, 종료 날짜를 묶는 범위 입력입니다. 달력 선택은 브라우저 native date picker 를 사용합니다.",
      "variants": [
        "qf-daterange__sep"
      ],
      "requiredTokens": [
        "--s-3",
        "--text-muted",
        "--bg-input",
        "--border",
        "--r-lg"
      ],
      "a11y": "각 input 에 from/to 라벨을 aria-label 로 연결하고, 범위 의미(시작~종료)를 함께 전달합니다.",
      "exampleHtml": "<div class=\"qf-daterange\"><input class=\"qf-input\" type=\"date\" aria-label=\"Start date\" value=\"2026-06-01\"><span class=\"qf-daterange__sep\">~</span><input class=\"qf-input\" type=\"date\" aria-label=\"End date\" value=\"2026-06-16\"></div>",
      "purposeEn": "A range input that groups a start and end date, using the browser's native date picker for calendar selection.",
      "a11yEn": "Associate each input with a from/to label via aria-label, and also convey the range meaning (start to end)."
    },
    {
      "name": "Avatar",
      "class": "qf-avatar",
      "layer": "generic",
      "category": "Display",
      "purpose": "사용자를 나타내는 원형 아바타입니다(상태 점은 옵션).",
      "variants": [
        "qf-avatar--xs",
        "qf-avatar--sm",
        "qf-avatar--md",
        "qf-avatar--lg",
        "qf-avatar--xl",
        "qf-avatar__status",
        "qf-avatar__status--online",
        "qf-avatar__status--idle",
        "qf-avatar__status--dnd",
        "qf-avatar__status--offline"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--r-pill",
        "--sz-status-dot",
        "--sz-status-dot-ring",
        "--status-online",
        "--status-offline",
        "--avatar-ring"
      ],
      "a11y": "이미지에는 의미 있는 alt 를 제공하고, 상태 점은 색만으로 전하지 않도록 온라인, 자리 비움 등 텍스트나 aria-label 로 보충합니다.",
      "exampleHtml": "<span class=\"qf-avatar qf-avatar--md\">M<span class=\"qf-avatar__status qf-avatar__status--online\"></span></span>",
      "examples": [
        {
          "label": "Online",
          "html": "<span class=\"qf-avatar qf-avatar--md\">M<span class=\"qf-avatar__status qf-avatar__status--online\"></span></span>"
        },
        {
          "label": "Idle",
          "html": "<span class=\"qf-avatar qf-avatar--md\">H<span class=\"qf-avatar__status qf-avatar__status--idle\"></span></span>"
        },
        {
          "label": "Do not disturb",
          "html": "<span class=\"qf-avatar qf-avatar--md\">J<span class=\"qf-avatar__status qf-avatar__status--dnd\"></span></span>"
        },
        {
          "label": "Offline",
          "html": "<span class=\"qf-avatar qf-avatar--md\">S<span class=\"qf-avatar__status qf-avatar__status--offline\"></span></span>"
        }
      ],
      "purposeEn": "A circular avatar that represents a user (with an optional status dot).",
      "a11yEn": "Provide meaningful alt text for the image, and do not convey the status dot by color alone, supplementing it with text or aria-label such as online or away."
    },
    {
      "name": "Badge",
      "class": "qf-badge",
      "layer": "generic",
      "category": "Display",
      "purpose": "라벨, 상태, 알림 개수를 나타내는 작은 배지입니다. 상태는 --success(정상) --warn(쉬는 중, 지연) --danger(중단, 실패) --muted(멈춤, 비활성)로 구분합니다. AI 가 쓴 글의 표시: 제목 위 kicker 줄에 기본 qf-badge + qf-i-sparkle 로 \"AI 요약\" 을 두고(중립 톤, 11px), 글 끝의 qf-notice--info 는 \"틀린 곳이 있으면 알려 주세요\" 한 줄로 줄입니다. 배지 안 svg 는 글자 크기에 맞춰집니다(qf-icon--sm 같은 크기 변형을 명시하면 그 크기 유지).",
      "variants": [
        "qf-badge--accent",
        "qf-badge--success",
        "qf-badge--warn",
        "qf-badge--danger",
        "qf-badge--muted",
        "qf-badge--dot",
        "qf-badge--count"
      ],
      "requiredTokens": [
        "--bg-hover",
        "--text-secondary",
        "--accent-subtle",
        "--ok-bg",
        "--text-ok",
        "--warn-bg",
        "--text-warn",
        "--danger-bg",
        "--text-danger",
        "--border",
        "--text-muted",
        "--danger-600",
        "--r-xs",
        "--r-pill",
        "--s-2",
        "--fs-11"
      ],
      "a11y": "색만으로 의미를 전하지 않도록 텍스트를 함께 둡니다. qf-badge--count(빨강)는 알림 개수 전용입니다. 글 수, 커뮤니티 수 같은 중립 개수에는 쓰지 말고 기본 qf-badge 나 qf-source__count(회색 숫자)를 쓰세요. 개수 배지는 읽지 않음 N건 처럼 aria-label 로 보충합니다.",
      "exampleHtml": "<span class=\"qf-badge qf-badge--count\" aria-label=\"3 new notifications\">3</span>",
      "purposeEn": "A small badge for a label, a status, or a notification count. Status tones: --success (healthy), --warn (resting, delayed), --danger (stopped, failed), --muted (paused, inactive). Marking AI-written text: put a default qf-badge with qf-i-sparkle reading \"AI summary\" in the kicker line above the title (neutral tone, 11px), and shorten the closing qf-notice--info to one line (\"Tell us if something is wrong\"). An svg inside a badge scales with the text (an explicit size modifier such as qf-icon--sm keeps its size).",
      "a11yEn": "Include text so meaning is not conveyed by color alone. qf-badge--count (red) is for notification counts only. Do not use it for neutral counts such as post or community totals; use the default qf-badge or qf-source__count (gray number) instead. Supplement count badges with an aria-label such as N unread.",
      "examples": [
        {
          "label": "Status set",
          "html": "<span class=\"qf-badge qf-badge--success\">Healthy</span> <span class=\"qf-badge qf-badge--warn\">Resting</span> <span class=\"qf-badge qf-badge--danger\">Stopped</span> <span class=\"qf-badge qf-badge--muted\">Paused</span> <span class=\"qf-badge\">Neutral</span>"
        },
        {
          "label": "Notification count (only)",
          "html": "<span class=\"qf-badge qf-badge--count\" aria-label=\"3 new notifications\">3</span>"
        },
        {
          "label": "Neutral count (not red)",
          "html": "<span class=\"qf-source\">Clien <span class=\"qf-source__count\">2</span></span> <span class=\"qf-badge\">2 posts</span>"
        },
        {
          "label": "AI-written marker (in a kicker line)",
          "html": "<div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Politics</span><span>4 communities · 9 posts · 9 min ago</span><span class=\"qf-badge\"><svg class=\"qf-icon\" aria-hidden=\"true\"><use href=\"#qf-i-sparkle\"/></svg>AI summary</span></div>"
        }
      ]
    },
    {
      "name": "Tooltip",
      "class": "qf-tooltip",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "요소를 가리킬 때 짧은 설명을 띄우는 말풍선입니다.",
      "variants": [],
      "requiredTokens": [
        "--bg-inverse",
        "--text-inverse",
        "--r-sm",
        "--fs-12",
        "--elev-2"
      ],
      "a11y": "role=\"tooltip\" 으로 표시하고 대상에 aria-describedby 로 연결합니다. pointer-events 가 없으므로 키보드 포커스로도 내용을 접근할 수 있게 합니다.",
      "exampleHtml": "<span class=\"qf-tooltip\" role=\"tooltip\" id=\"tt-1\">Open settings</span>",
      "purposeEn": "A bubble that shows a short description when an element is pointed at.",
      "a11yEn": "Mark it with role=\"tooltip\" and associate it with the target via aria-describedby. Since it has no pointer-events, ensure the content is also reachable via keyboard focus."
    },
    {
      "name": "Modal",
      "class": "qf-modal",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "화면 위에 띄우는 모달 대화상자입니다.",
      "variants": [
        "qf-modal-backdrop",
        "qf-modal__header",
        "qf-modal__title",
        "qf-modal__body",
        "qf-modal__footer"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--border",
        "--r-xl",
        "--elev-4",
        "--s-7",
        "--divider",
        "--z-modal",
        "--z-modal-bg"
      ],
      "a11y": "role=\"dialog\" 와 aria-modal=\"true\" 를 지정하고 aria-labelledby 로 제목과 연결합니다. 포커스를 모달 안에 가두고 Esc 로 닫으며, 열릴 때 포커스를 옮깁니다.",
      "exampleHtml": "<div class=\"qf-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"m-t\"><div class=\"qf-modal__header\"><h2 class=\"qf-modal__title\" id=\"m-t\">Delete project</h2><button class=\"qf-btn qf-btn--icon qf-btn--lg qf-btn--ghost\" aria-label=\"Close\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-x\"/></svg></button></div><div class=\"qf-modal__body\">This action cannot be undone. The project and all its data will be permanently deleted.</div><div class=\"qf-modal__footer\"><button class=\"qf-btn qf-btn--ghost\">Cancel</button><button class=\"qf-btn qf-btn--danger\">Delete</button></div></div>",
      "purposeEn": "A modal dialog presented over the screen.",
      "a11yEn": "Set role=\"dialog\" and aria-modal=\"true\", and associate the title via aria-labelledby. Trap focus within the modal, close on Esc, and move focus into it when it opens."
    },
    {
      "name": "Menu",
      "class": "qf-menu",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "동작 목록을 보여주는 드롭다운 메뉴입니다.",
      "variants": [
        "qf-menu__item",
        "qf-menu__item--danger",
        "qf-menu__separator",
        "qf-menu__kbd"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--border",
        "--bg-hover",
        "--r-lg",
        "--r-sm",
        "--divider",
        "--s-2",
        "--elev-3"
      ],
      "a11y": "role=\"menu\" 와 role=\"menuitem\" 을 지정하고, 위아래 화살표로 이동하며 Esc 로 닫습니다. 단축키는 텍스트로 함께 보여 색만으로 의미를 전하지 않습니다.",
      "exampleHtml": "<div class=\"qf-menu\" role=\"menu\"><button class=\"qf-menu__item\" role=\"menuitem\">Edit<kbd class=\"qf-menu__kbd\">⌘E</kbd></button><button class=\"qf-menu__item\" role=\"menuitem\">Copy<kbd class=\"qf-menu__kbd\">⌘C</kbd></button><div class=\"qf-menu__separator\" role=\"separator\"></div><button class=\"qf-menu__item qf-menu__item--danger\" role=\"menuitem\">Delete</button></div>",
      "purposeEn": "A dropdown menu that presents a list of actions.",
      "a11yEn": "Set role=\"menu\" and role=\"menuitem\", navigate with the up and down arrows, and close on Esc. Show shortcuts as text so meaning is not conveyed by color alone."
    },
    {
      "name": "Tabs",
      "class": "qf-tabs",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "여러 뷰를 전환하는 탭 모음입니다.",
      "variants": [
        "qf-tabs__item"
      ],
      "requiredTokens": [
        "--divider",
        "--text-secondary",
        "--text-strong",
        "--accent",
        "--s-3",
        "--s-4",
        "--fs-14"
      ],
      "a11y": "role=\"tablist\" 과 role=\"tab\" 을 지정하고 선택 탭은 aria-selected=\"true\" 로 알립니다. 좌우 화살표로 이동하며 밑줄 표시 외에 상태를 텍스트로도 전합니다.",
      "exampleHtml": "<div class=\"qf-tabs\" role=\"tablist\"><button class=\"qf-tabs__item\" role=\"tab\" aria-selected=\"true\">All</button><button class=\"qf-tabs__item\" role=\"tab\" aria-selected=\"false\">Mentions</button><button class=\"qf-tabs__item\" role=\"tab\" aria-selected=\"false\">Unread</button></div>",
      "purposeEn": "A set of tabs for switching between multiple views.",
      "a11yEn": "Set role=\"tablist\" and role=\"tab\", and mark the active tab with aria-selected=\"true\". Navigate with the left and right arrows, and convey state with text in addition to the underline indicator."
    },
    {
      "name": "Toast",
      "class": "qf-toast",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "화면 모서리에 잠깐 뜨는 알림 메시지입니다. --brief 는 짧게 떠 있다 사라집니다(--dur-toast-brief, 2.4초. 축소 모션에서는 모션 없이 2.4초 뒤 사라짐. 카탈로그에는 사라지는 라이브 예제를 두지 않습니다). 패턴: 공유 버튼(qf-btn--ghost qf-btn--icon + qf-i-share)은 navigator.share 를 쓰고, 없으면 링크를 복사한 뒤 qf-toast--brief 로 \"링크를 복사했습니다\" 를 띄웁니다.",
      "variants": [
        "qf-toast--success",
        "qf-toast--danger",
        "qf-toast--warn",
        "qf-toast__title",
        "qf-toast__body",
        "qf-toast--brief"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--border",
        "--accent",
        "--ok-400",
        "--danger-400",
        "--warn-400",
        "--r-md",
        "--elev-3",
        "--dur-toast-brief",
        "--ease-standard",
        "--s-2"
      ],
      "a11y": "role=\"status\" 와 aria-live=\"polite\" 로 알리고 오류는 role=\"alert\" 를 씁니다. 좌측 색 막대만으로 의미를 전하지 않도록 제목과 본문 텍스트를 함께 둡니다. --brief 는 animationend 에서 요소를 제거하고, 토스트 영역을 role=\"status\"(aria-live=polite) 로 두어 낭독되게 합니다.",
      "exampleHtml": "<div class=\"qf-toast qf-toast--success\" role=\"status\" aria-live=\"polite\"><div class=\"qf-toast__title\">Saved</div></div>",
      "purposeEn": "A notification message that appears briefly in a corner of the screen. --brief shows briefly and fades out (--dur-toast-brief, 2.4 s; under reduced motion it simply disappears after 2.4 s without motion. No live disappearing example is kept in the catalog). Pattern: a share button (qf-btn--ghost qf-btn--icon + qf-i-share) calls navigator.share, or falls back to copying the link and showing a qf-toast--brief \"Link copied\".",
      "a11yEn": "Announce with role=\"status\" and aria-live=\"polite\", and use role=\"alert\" for errors. Include title and body text so meaning is not conveyed by the left color bar alone. Remove a --brief toast on animationend, and give the toast region role=\"status\" (aria-live=polite) so it is announced.",
      "examples": []
    },
    {
      "name": "ToastRegion",
      "class": "qf-toast-region",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "여러 qf-toast 를 쌓아 올리는 고정 컨테이너입니다. 기본 우상단, --bottom/--left 로 위치를 바꿉니다.",
      "variants": [
        "qf-toast-region--bottom",
        "qf-toast-region--left"
      ],
      "requiredTokens": [
        "--z-toast",
        "--s-5",
        "--s-3"
      ],
      "a11y": "region 에 aria-live=polite(긴급은 assertive)를 두어 추가되는 토스트를 스크린리더가 읽게 합니다. pointer-events 는 토스트에만.",
      "exampleHtml": "<div class=\"qf-toast-region\" aria-live=\"polite\"><div class=\"qf-toast qf-toast--success\" role=\"status\"><div class=\"qf-toast__title\">Saved</div></div><div class=\"qf-toast qf-toast--warn\" role=\"status\"><div class=\"qf-toast__title\">Sync delayed</div></div></div>",
      "purposeEn": "A fixed container that stacks multiple qf-toast elements, defaulting to the top right and repositionable with --bottom/--left.",
      "a11yEn": "Set aria-live=polite (assertive for urgent) on the region so screen readers announce toasts as they are added. Apply pointer-events to the toasts only."
    },
    {
      "name": "CodeBlock",
      "class": "qf-codeblock",
      "layer": "generic",
      "category": "Display",
      "purpose": "코드를 모노스페이스로 표시하는 코드 블록입니다.",
      "variants": [
        "qf-codeblock--with-header",
        "qf-codeblock__lang",
        "qf-codeblock__header",
        "qf-codeblock__header-lang",
        "qf-codeblock__copy",
        "qf-code-inline"
      ],
      "requiredTokens": [
        "--bg-code",
        "--text-code",
        "--divider",
        "--r-md",
        "--s-4",
        "--fs-13",
        "--font-mono",
        "--ok-400"
      ],
      "a11y": "복사 버튼은 button 요소로 두고 복사 완료는 색만이 아니라 'Copied' 텍스트로 함께 알립니다. 코드는 pre, code 마크업으로 감싸 의미를 전달합니다.",
      "exampleHtml": "<pre class=\"qf-codeblock\"><span class=\"qf-codeblock__lang\">ts</span><code>const a = 1;</code></pre>",
      "purposeEn": "A code block that displays code in a monospace font.",
      "a11yEn": "Use a button element for the copy button and signal copy completion with 'Copied' text, not color alone. Wrap code in pre and code markup to convey its meaning."
    },
    {
      "name": "Prose",
      "class": "qf-prose",
      "layer": "generic",
      "category": "Display",
      "purpose": "마크다운 등 장문 문서를 디자인 시스템 톤으로 렌더하는 본문(prose) 스타일입니다. 본문 폭은 기본 가독성 measure(약 640px)이며, --qf-prose-max 로 오버라이드합니다(예: none, var(--container-w-wide)). --article 은 뉴스 전말처럼 3~6문장 문단이 이어지는 기사 본문용으로 본문 16px, 문단 간격 한 줄(--s-6), 한국어 어절 유지(keep-all)를 적용합니다. --article 안의 h2/h3 는 기사 소제목(17px, 600, 밑선 없음, 위 --s-6 아래 --s-2)입니다 — 전말이 네 문단 이상이면 흐름이 바뀌는 곳에 12자 안의 h2 를 둡니다.",
      "variants": [
        "qf-prose--article"
      ],
      "requiredTokens": [
        "--text",
        "--text-strong",
        "--link",
        "--accent",
        "--divider",
        "--bg-panel",
        "--font-mono",
        "--container-w-narrow",
        "--fs-16",
        "--s-6",
        "--fs-17",
        "--lh-snug"
      ],
      "a11y": "시맨틱 HTML 계층(h1→h6 순서, ul/ol/blockquote/table 구조)을 유지하고, 링크는 밑줄로 색 외 단서를 함께 줍니다. 본문 대비 AA, 코드/표는 토큰 색을 사용합니다.",
      "exampleHtml": "<article class=\"qf-prose\"><h2>Document title</h2><p>The body uses <strong>emphasis</strong>, a <a href=\"#prose\">link</a>, and <code>inline</code> code.</p><ul><li>First item</li><li>Second item</li></ul><blockquote>Blockquotes are highlighted with the accent bar.</blockquote></article>",
      "purposeEn": "A body (prose) style that renders long-form documents such as Markdown in the design system's tone. The body measure defaults to a readable ~640px and is overridable via --qf-prose-max (e.g., none or var(--container-w-wide)). --article is for article bodies of several 3–6 sentence paragraphs (news write-ups): 16px body, one-line paragraph spacing (--s-6), and keep-all word breaking for Korean. Inside --article, h2/h3 are article subheadings (17px, 600, no underline, --s-6 above and --s-2 below) — when the body runs four paragraphs or more, put a short h2 (about 12 characters) where the thread changes.",
      "a11yEn": "Maintain the semantic HTML hierarchy (h1→h6 order, ul/ol/blockquote/table structure), and underline links to provide a cue beyond color. Keep body contrast at AA, and use token colors for code and tables.",
      "examples": [
        {
          "label": "--article (news write-up)",
          "html": "<article class=\"qf-prose qf-prose--article\"><p>The Supreme Court acknowledged on Tuesday that expense reports filed by its chief justice were inaccurate, and issued a formal apology. The admission followed a week of reporting that traced dozens of receipts to restaurants closed on the dates listed.</p><p>Community reaction spread quickly across several Korean forums. Threads on Clien and Bobaedream questioned why the internal audit had taken years, while posters on 82cook focused on the apology itself.</p><p>The court said it would publish a corrected ledger and refer the matter to an outside review. No timeline was given.</p></article>"
        }
      ]
    },
    {
      "name": "Lede",
      "class": "qf-lede",
      "layer": "generic",
      "category": "Display",
      "purpose": "제목 아래 리드(standfirst) 문단입니다. 2~3문장 요약을 본문보다 한 단계 큰 글자(18px, 줄 간격도 따라 넓어짐)와 보조 톤(--text-secondary)으로 두어 본문(qf-prose--article, 16px)과 구분합니다. --rule 은 아래에 짧은 액센트 선을 그려 본문의 시작을 알립니다. 리드와 본문 사이에 그림이 없으면 --rule 을 쓰고, 그림이 사이를 가르면 기본형을 씁니다. 위 여백 --s-2, 아래 여백 --s-6(qf-stack 안에서는 stack 의 gap 이 양쪽에 더해집니다). 폭은 qf-prose 와 같은 measure 이며, --qf-prose-max 를 바꿀 때는 리드와 본문의 공통 조상에 둡니다.",
      "purposeEn": "A lede (standfirst) paragraph under the title. A two-to-three-sentence summary set one step larger than body text (18px, so the line pitch widens too) in a secondary tone (--text-secondary), apart from the article body (qf-prose--article, 16px). --rule draws a short accent line underneath to mark where the body begins. Use --rule when nothing sits between lede and body; use the plain form when a picture separates them. Margins: --s-2 above, --s-6 below (inside a qf-stack the stack gap is added on both sides). Same measure as qf-prose; when overriding --qf-prose-max, set it on a common ancestor of lede and body.",
      "variants": [
        "qf-lede--rule"
      ],
      "requiredTokens": [
        "--fs-18",
        "--lh-loose",
        "--text-secondary",
        "--font-sans",
        "--s-2",
        "--s-5",
        "--s-6",
        "--s-9",
        "--accent",
        "--container-w-narrow"
      ],
      "a11y": "p 로 두어 제목(h1) 바로 다음 문단으로 낭독되게 합니다. 본문과의 구분은 크기(18 vs 16px)와 --rule 의 선, 두 가지가 색과 무관한 단서이고 보조 톤은 덧붙는 단서입니다(본문색과의 대비는 1.3~1.5:1 로 약함). 그래서 리드와 본문 사이에 그림이 없으면 반드시 --rule 을 씁니다. 선은 ::after 장식(border 로 그려 강제 색상 모드에서도 남음)이라 보조기기에 노출되지 않습니다. 굵기는 400 으로 고정합니다(한국어 폴백 글꼴에서 500 은 400 과 같게 렌더될 수 있어 단서가 되지 못함). 대비: --text-secondary on --bg-app 10.7:1(다크) / 10.2:1(라이트), 선 --accent 4.49 / 4.07.",
      "a11yEn": "Use a p so it is read as the paragraph right after the heading (h1). Two cues separate it from the body independently of color: size (18 vs 16px) and the --rule line; the secondary tone is an extra cue (its contrast against body text is only 1.3–1.5:1). So when no picture sits between lede and body, always use --rule. The line is ::after decoration (drawn as a border so it survives forced-colors mode) and is not exposed to assistive technology. Weight is fixed at 400 (in Korean fallback fonts 500 may render like 400, so weight is not a reliable cue). Contrast: --text-secondary on --bg-app 10.7:1 (dark) / 10.2:1 (light); the --accent line 4.49 / 4.07.",
      "wideExamples": true,
      "exampleHtml": "<article><h1 class=\"qf-page-header__title\">Supreme Court admits chief justice filed false expense reports</h1><p class=\"qf-lede qf-lede--rule\">The court acknowledged on Tuesday that expense reports filed by its chief justice were inaccurate and issued a formal apology, after a week of reporting traced dozens of receipts to restaurants closed on the dates listed.</p><section class=\"qf-prose qf-prose--article\"><p>The admission followed an investigation by a nonprofit newsroom. Community reaction spread quickly across several Korean forums, with threads questioning why the internal audit had taken years.</p><p>The court said it would publish a corrected ledger and refer the matter to an outside review. No timeline was given.</p></section></article>",
      "examples": [
        {
          "label": "--rule (no picture between lede and body)",
          "html": "<article><h1 class=\"qf-page-header__title\">Supreme Court admits chief justice filed false expense reports</h1><p class=\"qf-lede qf-lede--rule\">The court acknowledged on Tuesday that expense reports filed by its chief justice were inaccurate and issued a formal apology.</p><section class=\"qf-prose qf-prose--article\"><p>The admission followed an investigation by a nonprofit newsroom. Community reaction spread quickly across several Korean forums.</p></section></article>"
        },
        {
          "label": "Plain (a figure separates lede and body)",
          "html": "<article><h1 class=\"qf-page-header__title\">Supreme Court admits chief justice filed false expense reports</h1><p class=\"qf-lede\">The court acknowledged on Tuesday that expense reports filed by its chief justice were inaccurate and issued a formal apology.</p><figure style=\"margin:0\"><img class=\"qf-story__media\" src=\"brand-assets/png/og-image.png\" alt=\"\" style=\"max-width:320px\"></figure><section class=\"qf-prose qf-prose--article\"><p>The admission followed an investigation by a nonprofit newsroom. Community reaction spread quickly across several Korean forums.</p></section></article>"
        }
      ]
    },
    {
      "name": "KeyPoints",
      "class": "qf-keypoints",
      "layer": "generic",
      "category": "Display",
      "purpose": "기사의 핵심 묶음입니다. 리드와 전말 사이(그림이 있으면 그림 다음)에 두는 상자로, 라벨(\"핵심\", 12px 대문자 톤)과 한 문장짜리 항목 2~4개(40자 안)를 담습니다. 글머리는 점이 아니라 액센트의 짧은 막대(순서가 의미가 아니라 번호를 쓰지 않습니다). 바탕 --bg-panel 과 테두리만 있고 왼쪽 선이 없어 qf-notice 와 같은 종류로 보이지 않습니다. 리드와 같은 부모의 바로 다음 형제로 오면 qf-lede--rule 의 선이 자동으로 숨습니다(:has 인접 형제). 리드가 다른 요소에 감싸여 있으면 발동하지 않으니 그때는 --rule 을 붙이지 마세요. 상자는 qf-prose 밖에 둡니다(안에 두면 qf-prose 의 h2 스타일이 라벨을 덮습니다). 폭은 qf-prose 와 같은 measure 입니다.",
      "purposeEn": "An article's key points. A box placed between the lede and the body (after the picture if there is one), holding a label (\"Key points\", 12px caps tone) and two to four one-sentence items (under about 40 characters). Bullets are short accent bars, not dots (order carries no meaning, so no numbers). It has a --bg-panel background and a border but no left rule, so it does not read as a qf-notice. When it is the lede's immediate next sibling under the same parent, the qf-lede--rule line hides automatically (:has adjacent sibling); if the lede is wrapped in another element this does not fire, so leave --rule off then. Place the box outside qf-prose (inside, qf-prose's h2 style would override the label). Same measure as qf-prose.",
      "variants": [
        "qf-keypoints__title",
        "qf-keypoints__list",
        "qf-keypoints__item"
      ],
      "requiredTokens": [
        "--s-5",
        "--s-6",
        "--s-4",
        "--s-3",
        "--s-2",
        "--container-w-narrow",
        "--bg-panel",
        "--border",
        "--r-lg",
        "--r-pill",
        "--fs-12",
        "--fs-15",
        "--lh-tight",
        "--lh-normal",
        "--tracking-caps",
        "--text-muted",
        "--text",
        "--accent",
        "--font-sans"
      ],
      "a11y": "section[aria-labelledby] 로 영역 이름을 주고 라벨을 h2 로 두어 \"핵심\" 이 문서 구조에 들어가게 하고, 항목은 ul/li 로 둡니다(막대는 ::before 장식). 막대 --accent on --bg-panel 다크 3.97 / 라이트 3.79(그래픽 3:1). 글자 --text on --bg-panel AA. 항목이 다섯 개를 넘으면 상자가 리드보다 무거워집니다 — 네 개까지.",
      "a11yEn": "Use section[aria-labelledby] to name the region and make the label an h2 so \"Key points\" enters the document outline, and mark items up as ul/li (the bar is ::before decoration). Bar --accent on --bg-panel 3.97 dark / 3.79 light (graphics 3:1). Text --text on --bg-panel passes AA. More than four items makes the box heavier than the lede — keep it to four.",
      "exampleHtml": "<section class=\"qf-keypoints\" aria-labelledby=\"kp-title\"><h2 class=\"qf-keypoints__title\" id=\"kp-title\">Key points</h2><ul class=\"qf-keypoints__list\"><li class=\"qf-keypoints__item\">The court admitted its expense reports were inaccurate and apologized.</li><li class=\"qf-keypoints__item\">Dozens of receipts named restaurants closed on the dates listed.</li><li class=\"qf-keypoints__item\">A corrected ledger and an outside review were promised, with no timeline.</li></ul></section>",
      "wideExamples": true,
      "examples": [
        {
          "label": "After a lede (lede rule hides)",
          "html": "<article><p class=\"qf-lede qf-lede--rule\">The court acknowledged on Tuesday that expense reports filed by its chief justice were inaccurate and issued a formal apology.</p><section class=\"qf-keypoints\" aria-labelledby=\"kp2\"><h2 class=\"qf-keypoints__title\" id=\"kp2\">Key points</h2><ul class=\"qf-keypoints__list\"><li class=\"qf-keypoints__item\">The court admitted its expense reports were inaccurate and apologized.</li><li class=\"qf-keypoints__item\">A corrected ledger and an outside review were promised.</li></ul></section><section class=\"qf-prose qf-prose--article\"><h2>What the reporting found</h2><p>The admission followed an investigation by a nonprofit newsroom that traced dozens of receipts to restaurants closed on the dates listed.</p><h2>Community reaction</h2><p>Threads on several Korean forums questioned why the internal audit had taken years.</p></section></article>"
        }
      ]
    },
    {
      "name": "Mark",
      "class": "qf-mark",
      "layer": "generic",
      "category": "Display",
      "purpose": "검색어 강조입니다. <mark class=\"qf-mark\"> 로 제목(qf-story__title)과 요약(qf-story__summary) 안에서 씁니다. 바탕은 액센트의 옅은 톤(--accent-subtle), 글자색은 상속이라 제목(600)과 요약(400) 어디서도 과하지 않습니다. 좌우 2px 패딩은 음수 margin 으로 상쇄되어 글자 흐름이 밀리지 않고, 두 줄에 걸치면 양 끝이 각각 둥급니다(box-decoration-break: clone).",
      "purposeEn": "Search-term highlight. Use <mark class=\"qf-mark\"> inside titles (qf-story__title) and summaries (qf-story__summary). Background is the subtle accent (--accent-subtle); the text color is inherited, so it stays calm in both 600-weight titles and 400-weight summaries. The 2px side padding is offset by a negative margin so text does not shift, and when it spans two lines each end is rounded (box-decoration-break: clone).",
      "variants": [],
      "requiredTokens": [
        "--accent-subtle",
        "--r-xs",
        "--s-1"
      ],
      "a11y": "mark 요소라 보조기기가 강조로 알립니다(일부는 낭독하지 않음). 글자색 상속: --text-strong on --accent-subtle 다크 13.1:1 / 라이트 14.6:1, --text-secondary 8.2 / 8.2 — AA. 강조가 의미를 바꾸지 않도록 검색어 외에는 쓰지 않습니다. 카드 hover 로 제목이 accent 색이 되어도 mark 안 글자는 --text-strong 을 유지합니다(라이트에서 accent 글자는 4.25:1). 음수 margin 때문에 줄 첫머리의 mark 는 왼쪽 2px 이 qf-clamp(overflow hidden) 안에서 잘릴 수 있습니다 — 장식 끝만 평평해집니다.",
      "a11yEn": "A mark element, which assistive technology may announce as highlighted (some do not). Inherited text color: --text-strong on --accent-subtle 13.1:1 dark / 14.6:1 light, --text-secondary 8.2 / 8.2 — AA. Use it only for matched search terms so the highlight does not change meaning. When a card hover turns the title accent-colored, text inside the mark stays --text-strong (accent text would be 4.25:1 in light). Because of the negative margin, a mark at the start of a line may lose its left 2px inside qf-clamp (overflow hidden) — only the decorative end flattens.",
      "exampleHtml": "<article class=\"qf-story\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Politics</span><span>4 communities · 9 posts</span></div><h2 class=\"qf-story__title\">Supreme <mark class=\"qf-mark\">Court</mark> admits chief justice filed false expense reports</h2><p class=\"qf-story__summary\">The <mark class=\"qf-mark\">court</mark> acknowledged on Tuesday that expense reports filed by its chief justice were inaccurate.</p></article>",
      "variantsAsChips": true
    },
    {
      "name": "Callout",
      "class": "qf-callout",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "Obsidian 스타일 콜아웃 — qf-prose 본문 안에 렌더하는 13종 안내 블록입니다. 접기(details/summary)를 지원합니다.",
      "variants": [
        "qf-callout--note",
        "qf-callout--abstract",
        "qf-callout--info",
        "qf-callout--todo",
        "qf-callout--tip",
        "qf-callout--success",
        "qf-callout--question",
        "qf-callout--warning",
        "qf-callout--failure",
        "qf-callout--danger",
        "qf-callout--bug",
        "qf-callout--example",
        "qf-callout--quote",
        "qf-callout__title",
        "qf-callout__icon",
        "qf-callout__body"
      ],
      "requiredTokens": [
        "--text-info",
        "--text-ok",
        "--text-warn",
        "--text-danger",
        "--accent-text",
        "--text-muted",
        "--border",
        "--border-strong",
        "--bg-elevated",
        "--text-strong",
        "--r-md",
        "--s-1",
        "--ring-focus"
      ],
      "a11y": "타입 의미를 좌측 강조선과 아이콘 색뿐 아니라 제목 텍스트로 함께 전달해 색 단독 의존을 피합니다(WCAG 1.4.1). 아이콘은 aria-hidden, 접기는 네이티브 details/summary 로 키보드, 스크린리더 동작을 보존하고 summary 에 포커스 링을 둡니다.",
      "exampleHtml": "<div class=\"qf-callout qf-callout--tip\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-sparkle\"/></svg></span>Tip</div><div class=\"qf-callout__body\">Use tokens and dark, light adapt automatically.</div></div>",
      "iconMap": {
        "note": "qf-i-edit",
        "abstract": "qf-i-clipboard",
        "info": "qf-i-info",
        "todo": "qf-i-check",
        "tip": "qf-i-sparkle",
        "success": "qf-i-check-double",
        "question": "qf-i-help",
        "warning": "qf-i-alert",
        "failure": "qf-i-x-circle",
        "danger": "qf-i-alert",
        "bug": "qf-i-bug",
        "example": "qf-i-file-text",
        "quote": "qf-i-quote"
      },
      "examples": [
        {
          "label": "note",
          "html": "<div class=\"qf-callout qf-callout--note\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-edit\"/></svg></span>Note</div><div class=\"qf-callout__body\">A general note in body flow.</div></div>"
        },
        {
          "label": "abstract",
          "html": "<div class=\"qf-callout qf-callout--abstract\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-clipboard\"/></svg></span>Abstract</div><div class=\"qf-callout__body\">A short summary of what follows.</div></div>"
        },
        {
          "label": "info",
          "html": "<div class=\"qf-callout qf-callout--info\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-info\"/></svg></span>Info</div><div class=\"qf-callout__body\">Contextual information.</div></div>"
        },
        {
          "label": "todo",
          "html": "<div class=\"qf-callout qf-callout--todo\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-check\"/></svg></span>Todo</div><div class=\"qf-callout__body\">Something still to do.</div></div>"
        },
        {
          "label": "tip",
          "html": "<div class=\"qf-callout qf-callout--tip\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-sparkle\"/></svg></span>Tip</div><div class=\"qf-callout__body\">Use tokens and dark, light adapt automatically.</div></div>"
        },
        {
          "label": "success",
          "html": "<div class=\"qf-callout qf-callout--success\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-check-double\"/></svg></span>Success</div><div class=\"qf-callout__body\">The step completed.</div></div>"
        },
        {
          "label": "question",
          "html": "<div class=\"qf-callout qf-callout--question\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-help\"/></svg></span>Question</div><div class=\"qf-callout__body\">Is this the right approach?</div></div>"
        },
        {
          "label": "warning",
          "html": "<div class=\"qf-callout qf-callout--warning\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-alert\"/></svg></span>Warning</div><div class=\"qf-callout__body\">Double-check before continuing.</div></div>"
        },
        {
          "label": "failure",
          "html": "<div class=\"qf-callout qf-callout--failure\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-x-circle\"/></svg></span>Failure</div><div class=\"qf-callout__body\">The check did not pass.</div></div>"
        },
        {
          "label": "danger",
          "html": "<div class=\"qf-callout qf-callout--danger\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-alert\"/></svg></span>Danger</div><div class=\"qf-callout__body\">This action cannot be undone.</div></div>"
        },
        {
          "label": "bug",
          "html": "<div class=\"qf-callout qf-callout--bug\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-bug\"/></svg></span>Bug</div><div class=\"qf-callout__body\">Known issue, tracked in #142.</div></div>"
        },
        {
          "label": "example",
          "html": "<div class=\"qf-callout qf-callout--example\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-file-text\"/></svg></span>Example</div><div class=\"qf-callout__body\">A worked example follows.</div></div>"
        },
        {
          "label": "quote",
          "html": "<div class=\"qf-callout qf-callout--quote\"><div class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-quote\"/></svg></span>Quote</div><div class=\"qf-callout__body\">Design is how it works.</div></div>"
        },
        {
          "label": "collapsible (details/summary)",
          "html": "<details class=\"qf-callout qf-callout--question\" open><summary class=\"qf-callout__title\"><span class=\"qf-callout__icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-help\"/></svg></span>Collapsible</summary><div class=\"qf-callout__body\">Click the summary to fold and unfold.</div></details>"
        }
      ],
      "purposeEn": "Obsidian-style callouts — 13 typed notice blocks rendered inside qf-prose body, with optional collapsing via details/summary.",
      "a11yEn": "Convey the type via the title text in addition to the left accent bar and icon color, avoiding reliance on color alone (WCAG 1.4.1). Icons are aria-hidden; collapsing uses native details/summary to preserve keyboard and screen reader behavior, with a focus ring on the summary."
    },
    {
      "name": "TableOfContents",
      "class": "qf-toc",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "블로그, 문서 우측(qf-app-shell__aside)에 sticky 로 붙는 '이 페이지에서' 목차입니다.",
      "variants": [
        "qf-toc__title",
        "qf-toc__item",
        "qf-toc__item--d2",
        "qf-toc__item--d3",
        "qf-toc__link",
        "qf-toc__link--active"
      ],
      "requiredTokens": [
        "--accent",
        "--text-strong",
        "--text-secondary",
        "--text-muted",
        "--fs-11",
        "--fs-13",
        "--tracking-caps",
        "--s-4",
        "--s-7",
        "--lh-snug"
      ],
      "a11y": "nav 에 aria-label 을 두어 랜드마크 의미를 전달하고, 활성 항목은 좌측 세로 인디케이터(--accent) + 텍스트 강조로 색 단독 의존을 피합니다. 링크는 44px 터치 타깃을 확보하고 긴 제목은 말줄임합니다.",
      "exampleHtml": "<nav class=\"qf-toc\" aria-label=\"On this page\"><div class=\"qf-toc__title\">On this page</div><ul><li class=\"qf-toc__item\"><a class=\"qf-toc__link qf-toc__link--active\" href=\"#intro\">Introduction</a></li><li class=\"qf-toc__item qf-toc__item--d2\"><a class=\"qf-toc__link\" href=\"#setup\">Setup</a></li><li class=\"qf-toc__item qf-toc__item--d3\"><a class=\"qf-toc__link\" href=\"#install\">Install the CLI</a></li></ul></nav>",
      "purposeEn": "An 'on this page' table of contents that sticks to the right (qf-app-shell__aside) of a blog or doc page.",
      "a11yEn": "Give the nav an aria-label to convey the landmark, and mark the active item with a left vertical indicator (--accent) plus text emphasis so meaning is not carried by color alone. Links meet a 44px touch target and long titles are truncated with an ellipsis."
    },
    {
      "name": "Footnotes",
      "class": "qf-footnotes",
      "layer": "generic",
      "category": "Display",
      "purpose": "마크다운 각주 목록 — 본문 하단 구분선 아래 각주와 역참조 링크입니다. 본문 내 참조 위첨자(sup>a)는 qf-prose 스코프에서 --accent-text 로 강조됩니다.",
      "variants": [
        "qf-footnotes__backref"
      ],
      "requiredTokens": [
        "--divider",
        "--fs-13",
        "--text-secondary",
        "--text-muted",
        "--accent-text",
        "--accent-bg-faint",
        "--r-xs",
        "--s-5",
        "--s-6",
        "--s-8"
      ],
      "a11y": "각주는 ol 로 순서를 전달하고, 역참조 링크에는 aria-label(예: 본문으로 돌아가기)을 두어 아이콘, 기호 단독 의존을 피합니다. 본문 참조는 실제 a[href] 앵커로 왕복 이동이 가능합니다.",
      "exampleHtml": "<section class=\"qf-footnotes\"><ol><li id=\"fn1\">디자인 시스템은 사람과 AI가 함께 씁니다. <a class=\"qf-footnotes__backref\" href=\"#fnref1\" aria-label=\"본문으로 돌아가기\">↩</a></li></ol></section>",
      "purposeEn": "A Markdown footnotes list — footnotes and back-reference links below a divider at the end of the body. In-body reference superscripts (sup>a) are accented via --accent-text within qf-prose.",
      "a11yEn": "Footnotes use an ol to convey order, and back-reference links carry an aria-label (e.g., back to content) so meaning is not carried by an icon or glyph alone. In-body references are real a[href] anchors enabling round-trip navigation."
    },
    {
      "name": "Transclude",
      "class": "qf-transclude",
      "layer": "generic",
      "category": "Display",
      "purpose": "다른 노트를 본문에 인라인 인용하는 트랜스클루전 카드입니다. 좌측 --border-strong 액센트 + --bg-panel 배경, 상단 __source 원문 링크. 채팅 임베드 qf-embed 와 이름 충돌을 피해 qf-transclude 로 명명했습니다.",
      "variants": [
        "qf-transclude__source"
      ],
      "requiredTokens": [
        "--border",
        "--border-strong",
        "--bg-panel",
        "--divider",
        "--text-secondary",
        "--text-muted",
        "--link",
        "--r-md",
        "--s-4",
        "--s-5",
        "--fs-12"
      ],
      "a11y": "인용 의미를 blockquote 시맨틱으로 전달하고, 원문 링크는 실제 a[href] 로 출처 이동을 보장합니다. 좌측 액센트는 장식이며 출처 텍스트로 맥락을 함께 제공합니다.",
      "exampleHtml": "<blockquote class=\"qf-transclude\"><div class=\"qf-transclude__source\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-link\"/></svg><a href=\"/notes/api\">notes/api</a></div><p>Embedded content from another note is quoted inline here.</p></blockquote>",
      "purposeEn": "A transclusion card that quotes another note inline within the body, with a left --border-strong accent, --bg-panel surface, and a __source link to the original at the top. Named qf-transclude to avoid colliding with the chat embed qf-embed.",
      "a11yEn": "Convey the quotation with blockquote semantics, and provide a real a[href] source link for navigation to the origin. The left accent is decorative, with source text supplying context."
    },
    {
      "name": "SettingsLayout",
      "class": "qf-settings",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "좌측 내비게이션과 본문 영역으로 나뉜 설정 레이아웃입니다.",
      "variants": [
        "qf-settings__nav",
        "qf-settings__nav-head",
        "qf-settings__nav-item",
        "qf-settings__main"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--divider",
        "--s-4",
        "--s-8",
        "--r-sm",
        "--text-secondary",
        "--bg-selected"
      ],
      "a11y": "내비 항목은 aria-selected로 현재 선택을 표현하고 키보드 포커스가 보이게 합니다. 선택 상태는 배경색뿐 아니라 aria 속성으로 함께 전달합니다.",
      "exampleHtml": "<div class=\"qf-settings\"><nav class=\"qf-settings__nav\"><a class=\"qf-settings__nav-item\" aria-selected=\"true\">General</a></nav><div class=\"qf-settings__main\">Content</div></div>",
      "purposeEn": "A settings layout split into a left navigation and a main content area.",
      "a11yEn": "Express the current selection on nav items with aria-selected and keep keyboard focus visible. Convey the selected state via aria attributes, not background color alone."
    },
    {
      "name": "ToggleRow",
      "class": "qf-toggle-row",
      "layer": "generic",
      "category": "Forms",
      "purpose": "제목과 설명, 우측 컨트롤을 한 줄로 묶는 설정 토글 행입니다.",
      "variants": [
        "qf-toggle-row__text",
        "qf-toggle-row__title",
        "qf-toggle-row__desc"
      ],
      "requiredTokens": [
        "--s-5",
        "--divider",
        "--fs-15",
        "--fs-13",
        "--text-strong",
        "--text-muted",
        "--font-sans"
      ],
      "a11y": "행의 제목 텍스트를 우측 스위치의 라벨로 연결해 무엇을 켜고 끄는지 화면 낭독기에 전달합니다.",
      "exampleHtml": "<div class=\"qf-toggle-row\"><div class=\"qf-toggle-row__text\"><div class=\"qf-toggle-row__title\" id=\"r1\">Notifications</div><div class=\"qf-toggle-row__desc\">Receive desktop notifications</div></div><button class=\"qf-switch\" role=\"switch\" aria-checked=\"true\" aria-labelledby=\"r1\"></button></div>",
      "purposeEn": "A settings toggle row that groups a title, description, and a control on the right into a single line.",
      "a11yEn": "Associate the row's title text as the label for the switch on the right so screen readers know what is being turned on and off."
    },
    {
      "name": "Switch",
      "class": "qf-switch",
      "layer": "generic",
      "category": "Forms",
      "purpose": "켜짐, 꺼짐 상태를 토글하는 스위치입니다.",
      "variants": [],
      "requiredTokens": [
        "--bg-track",
        "--accent",
        "--r-pill",
        "--dur-fast",
        "--dur-base",
        "--ease-spring"
      ],
      "a11y": "role=switch와 aria-checked로 상태를 전달하고 라벨을 연결합니다. 키보드로 토글 가능하며 포커스가 보이게 합니다.",
      "exampleHtml": "<button class=\"qf-switch\" role=\"switch\" aria-checked=\"true\" aria-label=\"Turn on notifications\"></button>",
      "purposeEn": "A switch that toggles between on and off states.",
      "a11yEn": "Convey state with role=switch and aria-checked, and associate a label. Make it togglable via keyboard and keep focus visible."
    },
    {
      "name": "EmptyState",
      "class": "qf-empty",
      "layer": "generic",
      "category": "Display",
      "purpose": "내용이 없을 때 안내하는 빈 상태 영역입니다. 404 같은 안내 화면도 제목, 본문, 찾기 입력(qf-search), 행동 버튼을 자식으로 두면 됩니다(변형 불필요).",
      "variants": [
        "qf-empty__title",
        "qf-empty__body"
      ],
      "requiredTokens": [
        "--s-12",
        "--s-7",
        "--s-4",
        "--text-muted",
        "--text-strong",
        "--fs-18",
        "--fs-14"
      ],
      "a11y": "안내 의미를 아이콘이 아닌 제목, 본문 텍스트로 전달합니다. 동적으로 나타날 때는 aria-live 영역에 넣는 것을 권장합니다.",
      "exampleHtml": "<div class=\"qf-empty\"><svg class=\"qf-icon qf-icon--xl\" aria-hidden=\"true\" style=\"color:var(--text-muted)\"><use href=\"#qf-i-inbox\"/></svg><div class=\"qf-empty__title\">No items yet</div><div class=\"qf-empty__body\">Add your first item to get started.</div><button class=\"qf-btn qf-btn--primary qf-btn--sm\">New item</button></div>",
      "purposeEn": "An empty-state area that provides guidance when there is no content. Pages such as a 404 are built from the same parts: title, body, a search form (qf-search), and an action button as children (no variant needed).",
      "a11yEn": "Convey the guidance through title and body text rather than the icon. When it appears dynamically, placing it in an aria-live region is recommended.",
      "examples": [
        {
          "label": "404 page (title + body + search + action)",
          "html": "<div class=\"qf-empty\"><svg class=\"qf-icon qf-icon--xl\" aria-hidden=\"true\" style=\"color:var(--text-muted)\"><use href=\"#qf-i-search\"/></svg><div class=\"qf-empty__title\">Page not found</div><div class=\"qf-empty__body\">The address may be wrong, or the issue may have been removed.</div><form class=\"qf-search\" role=\"search\" style=\"width:100%;max-width:calc(var(--s-12) * 4.5)\"><span class=\"qf-search__icon\" aria-hidden=\"true\"><svg class=\"qf-icon qf-icon--sm\"><use href=\"#qf-i-search\"/></svg></span><input class=\"qf-search__input\" type=\"search\" aria-label=\"Search issues\" placeholder=\"Search issues\"></form><a class=\"qf-btn qf-btn--secondary\" href=\"#home\">Back to the front page</a></div>"
        }
      ]
    },
    {
      "name": "Skeleton",
      "class": "qf-skel",
      "layer": "generic",
      "category": "Display",
      "purpose": "콘텐츠 로딩 중에 보여 주는 자리표시자 스켈레톤입니다.",
      "variants": [],
      "requiredTokens": [
        "--bg-hover",
        "--bg-selected",
        "--r-sm"
      ],
      "a11y": "로딩 영역을 aria-busy=\"true\"로 표시하고 의미 있는 콘텐츠가 아님을 화면 낭독기에 알립니다. 모션 민감 사용자를 위해 prefers-reduced-motion을 존중하는 것을 권장합니다.",
      "exampleHtml": "<div class=\"qf-skel\" aria-hidden=\"true\" style=\"width: 60%; height: var(--s-4)\"></div>",
      "purposeEn": "A placeholder skeleton shown while content is loading.",
      "a11yEn": "Mark the loading area with aria-busy=\"true\" to tell screen readers it is not meaningful content. Respecting prefers-reduced-motion for motion-sensitive users is recommended."
    },
    {
      "name": "Eyebrow",
      "class": "qf-eyebrow",
      "layer": "generic",
      "category": "Display",
      "purpose": "섹션 위에 올리는 작은 대문자 레이블입니다.",
      "variants": [],
      "requiredTokens": [
        "--fs-12",
        "--font-sans",
        "--tracking-caps",
        "--accent-text"
      ],
      "a11y": "대문자 변환은 시각 표현이며 실제 텍스트는 정상 표기로 두어 화면 낭독기가 올바르게 읽도록 합니다. 강조 색에만 의미를 싣지 않습니다.",
      "exampleHtml": "<span class=\"qf-eyebrow\">New feature</span>",
      "purposeEn": "A small uppercase label placed above a section.",
      "a11yEn": "The uppercase transform is visual presentation only, so keep the actual text in normal casing so screen readers read it correctly. Do not carry meaning in the accent color alone."
    },
    {
      "name": "SettingsOverlay",
      "class": "qf-settings-overlay",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "전체 화면 위에 띄우는 설정 오버레이입니다.",
      "variants": [
        "qf-settings-overlay__backdrop",
        "qf-settings-overlay__card",
        "qf-settings-overlay__close"
      ],
      "requiredTokens": [
        "--z-settings",
        "--z-settings-bg",
        "--bg-app",
        "--r-xl",
        "--elev-4",
        "--s-8",
        "--dur-base"
      ],
      "a11y": "카드는 role=dialog와 aria-modal=\"true\"로 모달임을 알리고 포커스를 가두며 Esc로 닫습니다. 닫기 버튼에는 aria-label을 둡니다.",
      "exampleHtml": "<div class=\"qf-settings-overlay__backdrop\"></div><div class=\"qf-settings-overlay__card\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Settings\"><button class=\"qf-settings-overlay__close\" aria-label=\"Close\">×</button><div class=\"qf-stack\" style=\"padding:var(--s-8)\"><h2>Settings</h2><p style=\"color:var(--text-secondary);max-width:560px\">Edit app-wide settings such as account, notifications, and theme in a full-screen overlay. Lay it out with navigation on the left and a content area on the right.</p></div></div>",
      "purposeEn": "A settings overlay presented over the full screen.",
      "a11yEn": "Mark the card as a modal with role=dialog and aria-modal=\"true\", trap focus, and close on Esc. Give the close button an aria-label."
    },
    {
      "name": "Banner",
      "class": "qf-banner",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "상단 또는 본문에 알림을 표시하는 배너입니다.",
      "variants": [
        "qf-banner--info",
        "qf-banner--warn",
        "qf-banner--danger",
        "qf-banner--accent",
        "qf-banner__icon",
        "qf-banner__msg",
        "qf-banner__actions",
        "qf-banner__close"
      ],
      "requiredTokens": [
        "--accent",
        "--accent-subtle",
        "--info-400",
        "--warn-400",
        "--danger-400",
        "--bg-panel",
        "--s-3",
        "--text-strong"
      ],
      "a11y": "성격에 맞춰 role=status 또는 role=alert를 부여하고 정보 의미를 색뿐 아니라 아이콘과 텍스트로 전달합니다. 닫기 버튼에는 aria-label을 둡니다.",
      "exampleHtml": "<div class=\"qf-banner qf-banner--warn\" role=\"status\"><span class=\"qf-banner__msg\">Your connection is unstable.</span><button class=\"qf-banner__close\" aria-label=\"Close\">×</button></div>",
      "purposeEn": "A banner that displays a notice at the top of the page or inline.",
      "a11yEn": "Assign role=status or role=alert to match its nature, and convey the informational meaning with an icon and text, not color alone. Give the close button an aria-label."
    },
    {
      "name": "KeyboardShortcutSheet",
      "class": "qf-kbd-sheet",
      "layer": "generic",
      "category": "Display",
      "purpose": "단축키 목록을 그룹별로 보여 주는 시트입니다.",
      "variants": [
        "qf-kbd-sheet__group",
        "qf-kbd-sheet__group-title",
        "qf-kbd-sheet__row",
        "qf-kbd-sheet__keys",
        "qf-kbd-sheet__plus",
        "qf-kbd"
      ],
      "requiredTokens": [
        "--s-6",
        "--s-8",
        "--s-2",
        "--fs-11",
        "--fs-14",
        "--text-muted",
        "--text-secondary",
        "--tracking-caps"
      ],
      "a11y": "각 키캡은 kbd 요소로 표기해 의미를 전달하고 모달 안에 둘 때는 dialog 컨텍스트에서 포커스를 관리합니다.",
      "exampleHtml": "<div class=\"qf-kbd-sheet\"><div class=\"qf-kbd-sheet__group\"><div class=\"qf-kbd-sheet__group-title\">Navigation</div><div class=\"qf-kbd-sheet__row\"><span>Open search</span><span class=\"qf-kbd-sheet__keys\"><kbd class=\"qf-kbd\">Ctrl</kbd><span class=\"qf-kbd-sheet__plus\">+</span><kbd class=\"qf-kbd\">K</kbd></span></div></div></div>",
      "purposeEn": "A sheet that presents a list of keyboard shortcuts grouped by category.",
      "a11yEn": "Mark each keycap with a kbd element to convey its meaning, and manage focus within the dialog context when placed inside a modal."
    },
    {
      "name": "CommandPalette",
      "class": "qf-cmd-palette",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "단축키로 여는 명령, 검색 팔레트입니다.",
      "variants": [
        "qf-cmd-palette-backdrop",
        "qf-cmd-palette__search",
        "qf-cmd-palette__input",
        "qf-cmd-palette__list",
        "qf-cmd-palette__section",
        "qf-cmd-palette__row",
        "qf-cmd-palette__row-icon",
        "qf-cmd-palette__label",
        "qf-cmd-palette__path",
        "qf-cmd-palette__kbd",
        "qf-cmd-palette__empty"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--border",
        "--r-xl",
        "--elev-4",
        "--accent",
        "--bg-selected",
        "--s-4"
      ],
      "a11y": "결과 행은 aria-selected로 현재 선택을 알리고 선택 행은 좌측 accent bar로 색 외 단서를 함께 제공합니다. 키보드 위아래 이동과 포커스 가시를 보장하고, 빈 결과는 텍스트로 안내합니다.",
      "exampleHtml": "<div class=\"qf-cmd-palette\" role=\"dialog\" aria-label=\"Command palette\"><div class=\"qf-cmd-palette__search\"><input class=\"qf-cmd-palette__input\" role=\"combobox\" aria-expanded=\"true\" aria-controls=\"cmd-list\" placeholder=\"Search commands\"></div><ul id=\"cmd-list\" class=\"qf-cmd-palette__list\" role=\"listbox\"><li class=\"qf-cmd-palette__row\" role=\"option\" aria-selected=\"true\"><span class=\"qf-cmd-palette__label\">New document</span></li></ul></div>",
      "purposeEn": "A command and search palette opened via a keyboard shortcut.",
      "a11yEn": "Indicate the current selection on result rows with aria-selected, and provide a cue beyond color via a left accent bar on the selected row. Ensure up and down keyboard navigation and visible focus, and convey empty results with text."
    },
    {
      "name": "EmojiPicker",
      "class": "qf-emoji-picker",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "이모지를 검색, 카테고리별로 골라 넣는 선택기입니다.",
      "variants": [
        "qf-emoji-picker__search",
        "qf-emoji-picker__search-field",
        "qf-emoji-picker__search-input",
        "qf-emoji-picker__tabs",
        "qf-emoji-picker__tab",
        "qf-emoji-picker__grid",
        "qf-emoji-picker__category-label",
        "qf-emoji-picker__row",
        "qf-emoji-picker__cell",
        "qf-emoji-picker__preview",
        "qf-emoji-picker__preview-shortcode",
        "qf-emoji-picker__preview--empty"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--border",
        "--r-xl",
        "--elev-4",
        "--accent",
        "--bg-hover",
        "--bg-selected",
        "--s-3"
      ],
      "a11y": "카테고리 탭과 이모지 셀은 aria-selected로 상태를 전하고, 선택 탭은 하단 accent 표시로 색 외 단서를 더합니다. 하단 미리보기에 shortcode 텍스트를 함께 노출하고 포커스 가시를 보장합니다.",
      "exampleHtml": "<div class=\"qf-emoji-picker\" role=\"dialog\" aria-label=\"Select emoji\"><div class=\"qf-emoji-picker__search\"><div class=\"qf-emoji-picker__search-field\"><span class=\"qf-emoji-picker__search-icon\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-search\"/></svg></span><input class=\"qf-emoji-picker__search-input\" type=\"search\" aria-label=\"Search emoji\" placeholder=\"Search\"></div></div><div class=\"qf-emoji-picker__tabs\" role=\"tablist\"><button class=\"qf-emoji-picker__tab\" role=\"tab\" aria-selected=\"true\" aria-label=\"Smileys\">😀</button><button class=\"qf-emoji-picker__tab\" role=\"tab\" aria-selected=\"false\" aria-label=\"Animals\">🐶</button><button class=\"qf-emoji-picker__tab\" role=\"tab\" aria-selected=\"false\" aria-label=\"Food\">🍔</button><button class=\"qf-emoji-picker__tab\" role=\"tab\" aria-selected=\"false\" aria-label=\"Activities\">⚽</button><button class=\"qf-emoji-picker__tab\" role=\"tab\" aria-selected=\"false\" aria-label=\"Symbols\">🎵</button></div><div class=\"qf-emoji-picker__grid\"><div class=\"qf-emoji-picker__category-label\">Smileys</div><div class=\"qf-emoji-picker__row\"><button class=\"qf-emoji-picker__cell\" aria-selected=\"true\" aria-label=\"Emoji\">😀</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😃</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😄</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😁</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😆</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😅</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😂</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">🤣</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😊</button></div><div class=\"qf-emoji-picker__row\"><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😇</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">🙂</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">🙃</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😉</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😌</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😍</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">🥰</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😘</button><button class=\"qf-emoji-picker__cell\" aria-label=\"Emoji\">😗</button></div></div><div class=\"qf-emoji-picker__preview\"><span class=\"qf-emoji-picker__preview-emoji\">😀</span><span class=\"qf-emoji-picker__preview-shortcode\">:grinning:</span></div></div>",
      "purposeEn": "A picker for searching emoji and choosing them by category.",
      "a11yEn": "Convey state on category tabs and emoji cells with aria-selected, and add a cue beyond color via a bottom accent indicator on the selected tab. Surface the shortcode text in the bottom preview and ensure visible focus."
    },
    {
      "name": "UploadOverlay",
      "class": "qf-upload-overlay",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "파일을 끌어다 놓는 동안 표시되는 드래그 앤 드롭 안내 오버레이입니다.",
      "variants": [
        "qf-upload-overlay__icon",
        "qf-upload-overlay__title",
        "qf-upload-overlay__hint"
      ],
      "requiredTokens": [
        "--accent-bg",
        "--accent",
        "--r-xl",
        "--s-3",
        "--s-4",
        "--text-strong",
        "--text-secondary"
      ],
      "a11y": "오버레이는 pointer-events 없이 시각 안내만 담당하며, 제목과 힌트 텍스트로 동작을 명확히 전달합니다. 점선 테두리와 함께 안내 문구를 두어 색 단독 의존을 피합니다.",
      "exampleHtml": "<div class=\"qf-upload-overlay\" role=\"status\" aria-live=\"polite\"><div class=\"qf-upload-overlay__icon\"><svg class=\"qf-icon\" aria-hidden=\"true\"><use href=\"#qf-i-upload\"/></svg></div><p class=\"qf-upload-overlay__title\">Drop here to upload</p><p class=\"qf-upload-overlay__hint\">Up to 50MB</p></div>",
      "purposeEn": "A drag-and-drop guidance overlay shown while files are being dragged over a drop target.",
      "a11yEn": "The overlay is purely visual with no pointer-events, conveying its action through a title and hint text. A dashed border is paired with guidance text so meaning is not carried by color alone."
    },
    {
      "name": "Notice",
      "class": "qf-notice",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "정보, 경고, 위험을 알리는 인라인 안내 박스입니다.",
      "variants": [
        "qf-notice--info",
        "qf-notice--warn",
        "qf-notice--danger",
        "qf-notice__icon",
        "qf-notice__body",
        "qf-notice__title"
      ],
      "requiredTokens": [
        "--border",
        "--s-1",
        "--info-400",
        "--warn-400",
        "--danger-400",
        "--r-md",
        "--bg-elevated",
        "--text-secondary"
      ],
      "a11y": "좌측 보더 색과 아이콘에 더해 제목, 본문 텍스트로 의미를 전달해 색 단독 의존을 피합니다. 경고, 위험은 role=alert로, 정보는 role=status로 스크린리더에 알립니다.",
      "exampleHtml": "<div class=\"qf-notice qf-notice--warn\" role=\"alert\"><span class=\"qf-notice__icon\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-alert\"/></svg></span><div class=\"qf-notice__body\"><p class=\"qf-notice__title\">Caution</p>You have unsaved changes.</div></div>",
      "purposeEn": "An inline message box that surfaces info, warning, or danger states.",
      "a11yEn": "Meaning is carried by a title and body text in addition to the left border color and icon, avoiding reliance on color alone. Warning and danger use role=alert, while info uses role=status to announce to screen readers.",
      "examples": [
        {
          "label": "Load failed + retry (end of an infinite list)",
          "html": "<div class=\"qf-notice qf-notice--warn\" role=\"alert\"><span class=\"qf-notice__icon\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-alert\"/></svg></span><div class=\"qf-notice__body qf-cluster\"><span>Could not load more issues.</span><button class=\"qf-btn qf-btn--secondary qf-btn--sm\" type=\"button\">Retry</button></div></div>"
        }
      ]
    },
    {
      "name": "OtpInput",
      "class": "qf-otp-input",
      "layer": "generic",
      "category": "Forms",
      "purpose": "인증 코드를 자리별로 입력하는 일회용 코드 입력란입니다.",
      "variants": [
        "qf-otp-input__digit"
      ],
      "requiredTokens": [
        "--s-2",
        "--s-10",
        "--fs-24",
        "--font-mono",
        "--accent",
        "--ring-accent",
        "--danger-400",
        "--ring-danger"
      ],
      "a11y": "각 자리 입력은 포커스 시 accent ring으로 가시하고, 오류는 aria-invalid와 danger ring으로 색 외 상태를 함께 전합니다. inputMode=numeric로 숫자 키패드를 제공하고 라벨로 용도를 안내합니다.",
      "exampleHtml": "<div class=\"qf-otp-input\" role=\"group\" aria-label=\"6-digit verification code\"><input class=\"qf-otp-input__digit\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 1\"><input class=\"qf-otp-input__digit\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 2\"><input class=\"qf-otp-input__digit\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 3\"><input class=\"qf-otp-input__digit\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 4\"><input class=\"qf-otp-input__digit\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 5\"><input class=\"qf-otp-input__digit\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 6\"></div>",
      "purposeEn": "A one-time code input field where a verification code is entered one digit per box.",
      "a11yEn": "Each digit box shows an accent ring on focus for visible focus, and errors are conveyed beyond color with aria-invalid and a danger ring. inputMode=numeric brings up the numeric keypad, and a label communicates the field's purpose."
    },
    {
      "name": "PasswordStrengthMeter",
      "class": "qf-strength-meter",
      "layer": "generic",
      "category": "Forms",
      "purpose": "비밀번호 강도를 막대와 라벨로 실시간 표시합니다.",
      "variants": [
        "qf-strength-meter__track",
        "qf-strength-meter__bar",
        "qf-strength-meter__label"
      ],
      "requiredTokens": [
        "--s-1",
        "--s-2",
        "--bg-hover",
        "--danger-400",
        "--warn-400",
        "--ok-400",
        "--ok-600",
        "--r-sm"
      ],
      "a11y": "강도는 data-strength 막대 색과 함께 라벨 텍스트(약함, 보통, 강함)로 전달해 색 단독 의존을 피합니다. aria-live로 강도 변화를 알립니다.",
      "exampleHtml": "<div class=\"qf-strength-meter\" data-strength=\"strong\"><div class=\"qf-strength-meter__track\"><span class=\"qf-strength-meter__bar is-on\"></span><span class=\"qf-strength-meter__bar is-on\"></span><span class=\"qf-strength-meter__bar is-on\"></span></div><p class=\"qf-strength-meter__label\" aria-live=\"polite\">Strong</p></div>",
      "examples": [
        {
          "label": "Weak",
          "html": "<div class=\"qf-strength-meter\" data-strength=\"weak\"><div class=\"qf-strength-meter__track\"><span class=\"qf-strength-meter__bar is-on\"></span><span class=\"qf-strength-meter__bar\"></span><span class=\"qf-strength-meter__bar\"></span></div><p class=\"qf-strength-meter__label\" aria-live=\"polite\">Weak</p></div>"
        },
        {
          "label": "Medium",
          "html": "<div class=\"qf-strength-meter\" data-strength=\"ok\"><div class=\"qf-strength-meter__track\"><span class=\"qf-strength-meter__bar is-on\"></span><span class=\"qf-strength-meter__bar is-on\"></span><span class=\"qf-strength-meter__bar\"></span></div><p class=\"qf-strength-meter__label\" aria-live=\"polite\">Medium</p></div>"
        },
        {
          "label": "Strong",
          "html": "<div class=\"qf-strength-meter\" data-strength=\"strong\"><div class=\"qf-strength-meter__track\"><span class=\"qf-strength-meter__bar is-on\"></span><span class=\"qf-strength-meter__bar is-on\"></span><span class=\"qf-strength-meter__bar is-on\"></span></div><p class=\"qf-strength-meter__label\" aria-live=\"polite\">Strong</p></div>"
        }
      ],
      "purposeEn": "Displays password strength in real time using a bar and a label.",
      "a11yEn": "Strength is conveyed through label text (weak, fair, strong) alongside the data-strength bar color, avoiding reliance on color alone. aria-live announces changes in strength."
    },
    {
      "name": "BackupCodes",
      "class": "qf-backup-codes",
      "layer": "generic",
      "category": "Forms",
      "purpose": "2단계 인증의 일회용 복구 코드를 보여주는 목록입니다.",
      "variants": [
        "qf-backup-codes__code"
      ],
      "requiredTokens": [
        "--s-2",
        "--s-4",
        "--bg-input",
        "--border",
        "--r-md",
        "--font-mono",
        "--text-strong",
        "--text-muted"
      ],
      "a11y": "사용한 코드는 취소선과 함께 흐린 색으로 구분하며, 상태를 시각만이 아니라 텍스트로 함께 안내하면 좋습니다. mono 정렬로 코드 가독성을 확보합니다.",
      "exampleHtml": "<ul class=\"qf-backup-codes\" aria-label=\"Backup codes\"><li class=\"qf-backup-codes__code\">A1B2-C3D4</li><li class=\"qf-backup-codes__code is-used\">E5F6-G7H8</li><li class=\"qf-backup-codes__code\">J9K0-L1M2</li><li class=\"qf-backup-codes__code\">N3P4-Q5R6</li><li class=\"qf-backup-codes__code\">S7T8-U9V0</li><li class=\"qf-backup-codes__code\">W1X2-Y3Z4</li><li class=\"qf-backup-codes__code\">B5C6-D7E8</li><li class=\"qf-backup-codes__code\">F9G0-H1J2</li></ul>",
      "purposeEn": "A list that displays one-time recovery codes for two-factor authentication.",
      "a11yEn": "Used codes are distinguished with a strikethrough and dimmed color; it is best to also convey their state through text and not visuals alone. A monospace alignment keeps the codes legible."
    },
    {
      "name": "Checkbox",
      "class": "qf-checkbox",
      "layer": "generic",
      "category": "Forms",
      "purpose": "약관 동의 등 확인을 받는 체크박스입니다.",
      "variants": [
        "qf-checkbox__input",
        "qf-checkbox__label"
      ],
      "requiredTokens": [
        "--s-3",
        "--s-5",
        "--accent",
        "--fs-13",
        "--text-secondary",
        "--lh-normal"
      ],
      "a11y": "네이티브 input 위에 accent-color만 얹어 focus-visible 포커스 가시를 보존합니다. label과 input을 연결해 라벨 클릭과 스크린리더 읽기를 보장합니다.",
      "exampleHtml": "<label class=\"qf-checkbox\"><input class=\"qf-checkbox__input\" type=\"checkbox\"><span class=\"qf-checkbox__label\">I agree to the terms</span></label>",
      "purposeEn": "A checkbox for confirmations such as agreeing to terms.",
      "a11yEn": "Only accent-color is layered onto the native input to preserve focus-visible focus visibility. The label and input are associated so that clicking the label works and screen readers read it correctly."
    },
    {
      "name": "Stack",
      "class": "qf-stack",
      "layer": "generic",
      "category": "Layout",
      "purpose": "자식 요소를 세로로 일정 간격으로 쌓는 레이아웃 프리미티브입니다.",
      "variants": [
        "qf-stack--tight",
        "qf-stack--loose"
      ],
      "requiredTokens": [
        "--s-4",
        "--s-2",
        "--s-6"
      ],
      "a11y": "레이아웃 전용이라 자체 시맨틱이 없습니다. 의미 있는 묶음이면 ul/section 등 적절한 요소에 적용하세요. 간격은 --qf-gap 으로 조절합니다.",
      "exampleHtml": "<div class=\"qf-stack\"><div>First</div><div>Second</div></div>",
      "purposeEn": "A layout primitive that stacks child elements vertically at a consistent spacing.",
      "a11yEn": "Layout-only, so it carries no semantics of its own. For a meaningful grouping, apply it to an appropriate element such as ul or section. Spacing is controlled via --qf-gap."
    },
    {
      "name": "Cluster",
      "class": "qf-cluster",
      "layer": "generic",
      "category": "Layout",
      "purpose": "요소를 가로로 묶고 넘치면 줄바꿈하는 레이아웃 프리미티브입니다(툴바, 태그군, 액션열).",
      "variants": [
        "qf-cluster--between",
        "qf-cluster--end",
        "qf-cluster--tight"
      ],
      "requiredTokens": [
        "--s-3",
        "--s-2"
      ],
      "a11y": "레이아웃 전용. 액션 묶음이면 의미에 맞는 컨테이너 요소를 쓰고, 시각 순서와 DOM 순서를 일치시킵니다.",
      "exampleHtml": "<div class=\"qf-cluster\"><button class=\"qf-btn qf-btn--primary\">Save</button><button class=\"qf-btn qf-btn--ghost\">Cancel</button></div>",
      "purposeEn": "A layout primitive that groups elements horizontally and wraps when they overflow (toolbars, tag groups, action rows).",
      "a11yEn": "Layout-only. For an action grouping, use a container element that fits its meaning, and keep visual order in sync with DOM order."
    },
    {
      "name": "Grid",
      "class": "qf-grid",
      "layer": "generic",
      "category": "Layout",
      "purpose": "셀이 자동으로 채워지는 반응형 그리드 레이아웃입니다.",
      "variants": [
        "qf-grid--cols-2",
        "qf-grid--cols-3",
        "qf-grid--cols-4"
      ],
      "requiredTokens": [
        "--s-4",
        "--grid-cell-min"
      ],
      "a11y": "레이아웃 전용. --qf-grid-min 으로 최소 셀폭을 조절하며, 콘텐츠 순서가 의미를 가지면 DOM 순서를 유지합니다.",
      "exampleHtml": "<div class=\"qf-grid\"><section class=\"qf-card\"><div class=\"qf-card__body\">A</div></section><section class=\"qf-card\"><div class=\"qf-card__body\">B</div></section></div>",
      "purposeEn": "A responsive grid layout whose cells fill in automatically.",
      "a11yEn": "Layout-only. Use --qf-grid-min to control the minimum cell width, and preserve DOM order when content order is meaningful."
    },
    {
      "name": "Container",
      "class": "qf-container",
      "layer": "generic",
      "category": "Layout",
      "purpose": "중앙 정렬 본문 컨테이너입니다(최대폭 + 좌우 여백). 언제 무엇을 쓰나: --narrow(640px)는 폼과 단일 문서 본문. --reading(720px)은 뉴스, 이슈, 블로그 같은 목록형 읽기 화면(제목 한 줄, 요약 3줄, 출처 칩 한 줄이 들어가는 폭). 기본(960px)은 대시보드, 표, 2열 레이아웃. --wide(1200px)는 넓은 표와 차트.",
      "variants": [
        "qf-container--narrow",
        "qf-container--wide",
        "qf-container--reading"
      ],
      "requiredTokens": [
        "--container-w",
        "--container-w-narrow",
        "--container-w-wide",
        "--s-5",
        "--container-w-reading"
      ],
      "a11y": "레이아웃 전용. 페이지 본문 폭과 좌우 여백을 일관되게 유지합니다. 보통 main/section 에 적용합니다.",
      "exampleHtml": "<main class=\"qf-container qf-container--narrow\"><h1>Title</h1></main>",
      "purposeEn": "A centered body container (max width + side padding). Which one when: --narrow (640px) for forms and a single document body; --reading (720px) for list-style reading pages such as news, issues, or a blog index (fits a one-line title, a three-line summary, and one row of source chips); default (960px) for dashboards, tables, and two-column layouts; --wide (1200px) for wide tables and charts.",
      "a11yEn": "Layout-only. Keeps page content width and horizontal padding consistent. Typically applied to main or section."
    },
    {
      "name": "Divider",
      "class": "qf-divider",
      "layer": "generic",
      "category": "Layout",
      "purpose": "콘텐츠를 구분하는 수평 또는 수직 선입니다.",
      "variants": [
        "qf-divider--vertical"
      ],
      "requiredTokens": [
        "--divider",
        "--s-5",
        "--s-4"
      ],
      "a11y": "의미 있는 구분은 hr 또는 role=separator 로 표현합니다. 순수 장식이면 aria-hidden 을 고려합니다.",
      "exampleHtml": "<hr class=\"qf-divider\">",
      "purposeEn": "A horizontal or vertical line that separates content.",
      "a11yEn": "Express a meaningful separation with hr or role=separator. If it is purely decorative, consider aria-hidden."
    },
    {
      "name": "Card",
      "class": "qf-card",
      "layer": "generic",
      "category": "Display",
      "purpose": "콘텐츠를 묶는 표면 컨테이너입니다(헤더, 본문, 푸터 슬롯).",
      "variants": [
        "qf-card__header",
        "qf-card__title",
        "qf-card__body",
        "qf-card__footer",
        "qf-card--interactive"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--border",
        "--divider",
        "--r-xl",
        "--s-6",
        "--text-strong",
        "--border-strong"
      ],
      "a11y": "상호작용 카드(--interactive)는 키보드 접근 가능한 요소(a/button)로 감싸거나 적절한 role 과 tabindex 를 부여하고 포커스를 보이게 합니다.",
      "exampleHtml": "<section class=\"qf-card\"><div class=\"qf-card__header\"><h3 class=\"qf-card__title\">Title</h3></div><div class=\"qf-card__body\">This is the content.</div></section>",
      "purposeEn": "A surface container that groups content (with header, body, and footer slots).",
      "a11yEn": "An interactive card (--interactive) should be wrapped in a keyboard-accessible element (a/button) or given an appropriate role and tabindex, with visible focus."
    },
    {
      "name": "Tag",
      "class": "qf-tag",
      "layer": "generic",
      "category": "Display",
      "purpose": "라벨이나 카테고리를 나타내는 칩입니다(카운트, 상태용 Badge 와 구분되는 텍스트 라벨). <a class=\"qf-tag\"> 로 두면 눌리는 태그가 됩니다(hover 는 selected 표면, 터치 환경 40px + 44px 히트). 글 수 같은 개수는 qf-tag__count(가는 숫자)로 — qf-badge--count(빨강)는 알림 전용입니다. qf-story--stretched 카드 안에서도 눌립니다.",
      "variants": [
        "qf-tag--accent",
        "qf-tag--outline",
        "qf-tag__remove",
        "qf-tag__count"
      ],
      "requiredTokens": [
        "--bg-hover",
        "--text-secondary",
        "--accent-subtle",
        "--accent-on-subtle",
        "--r-sm",
        "--s-3",
        "--fs-12",
        "--bg-selected",
        "--text",
        "--text-muted",
        "--ring-focus",
        "--s-9",
        "--m-touch",
        "--dur-fast",
        "--ease-standard",
        "--s-2",
        "--border-strong",
        "--r-xs"
      ],
      "a11y": "제거 버튼(qf-tag__remove)에는 aria-label(예: 디자인 태그 제거)을 둡니다. 색만으로 의미를 전하지 않고 라벨 텍스트를 함께 둡니다. 링크 태그는 a[href] 라 포커스 링이 보이고, 개수는 숫자 뒤에 단위를 aria-label 로 보충할 수 있습니다(예: \"powershell, 글 24건\"). 링크 태그에는 qf-tag__remove 를 넣지 않습니다(a 안의 button). 본문(qf-prose) 안 인라인 태그는 줄 높이를 지키며 터치 성장이 없습니다. 개수는 --text-secondary(굵기 400, 태그 글자는 500)라 바탕 --bg-hover 위 9.5 / 9.5, hover 표면 --bg-selected 위에서도 AA 입니다(--text-muted 는 라이트 4.48 로 미달이라 쓰지 않음).",
      "exampleHtml": "<span class=\"qf-tag qf-tag--accent\">Design<button class=\"qf-tag__remove\" aria-label=\"Remove Design tag\">×</button></span>",
      "purposeEn": "A chip that represents a label or category (a text label, distinct from Badge which is used for counts and status). As <a class=\"qf-tag\"> it becomes a clickable tag (hover = selected surface; 40px tall with a 44px hit area on touch). Show counts such as post totals with qf-tag__count (a lighter-weight number) — qf-badge--count (red) is for notifications only. It stays clickable inside a qf-story--stretched card.",
      "a11yEn": "Give the remove button (qf-tag__remove) an aria-label (e.g., Remove design tag). Pair label text with color so meaning is never conveyed by color alone. A link tag is an a[href] with a visible focus ring; supplement a count with an aria-label such as \"powershell, 24 posts\". Do not put qf-tag__remove inside a link tag (a button inside an a). Inline tags inside qf-prose keep the line height and do not grow on touch. The count uses --text-secondary at weight 400 (tag text is 500): 9.5 / 9.5 on --bg-hover and still AA on the --bg-selected hover surface (--text-muted would be 4.48 in light, so it is not used).",
      "examples": [
        {
          "label": "Link tags with counts (tag index)",
          "html": "<div class=\"qf-cluster qf-cluster--tight\"><a class=\"qf-tag\" href=\"#t1\">#powershell <span class=\"qf-tag__count\">24</span></a><a class=\"qf-tag\" href=\"#t2\">#drama <span class=\"qf-tag__count\">30</span></a><a class=\"qf-tag\" href=\"#t3\">#algorithm <span class=\"qf-tag__count\">12</span></a><a class=\"qf-tag qf-tag--accent\" href=\"#t4\" aria-current=\"page\">#travel <span class=\"qf-tag__count\">10</span></a></div>"
        }
      ]
    },
    {
      "name": "Stat",
      "class": "qf-stat",
      "layer": "generic",
      "category": "Display",
      "purpose": "대시보드 지표(KPI)를 큰 값, 라벨, 추세 델타로 표시합니다. 값 아래 보조 텍스트(qf-stat__hint, 예: 상한 300장)를 둘 수 있습니다. --sm 은 사이드·요약 줄의 짧은 숫자(값 18px 600, 라벨 12px)입니다.",
      "variants": [
        "qf-stat__label",
        "qf-stat__value",
        "qf-stat__delta",
        "qf-stat__delta--up",
        "qf-stat__delta--down",
        "qf-stat__hint",
        "qf-stat--sm"
      ],
      "requiredTokens": [
        "--text-strong",
        "--text-muted",
        "--fs-32",
        "--fs-13",
        "--tracking-tight",
        "--ok-600",
        "--danger-400",
        "--fs-12",
        "--fs-18",
        "--lh-snug",
        "--tracking-normal"
      ],
      "a11y": "추세는 색뿐 아니라 화살표/부호(▲▼ 또는 +/-)와 텍스트로 전달해 색 단독 의존을 피합니다. 값에는 단위/의미를 함께 둡니다.",
      "exampleHtml": "<div class=\"qf-stat\"><span class=\"qf-stat__label\">Monthly active users</span><span class=\"qf-stat__value\">12,840</span><span class=\"qf-stat__delta qf-stat__delta--up\">▲ 8.2%</span></div>",
      "examples": [
        {
          "label": "Increase",
          "html": "<div class=\"qf-stat\"><span class=\"qf-stat__label\">Monthly active users</span><span class=\"qf-stat__value\">12,840</span><span class=\"qf-stat__delta qf-stat__delta--up\">▲ 8.2%</span></div>"
        },
        {
          "label": "Decrease",
          "html": "<div class=\"qf-stat\"><span class=\"qf-stat__label\">Churn rate</span><span class=\"qf-stat__value\">3.1%</span><span class=\"qf-stat__delta qf-stat__delta--down\">▼ 1.4%</span></div>"
        },
        {
          "label": "No delta",
          "html": "<div class=\"qf-stat\"><span class=\"qf-stat__label\">Pending tasks</span><span class=\"qf-stat__value\">27</span></div>"
        },
        {
          "label": "With hint",
          "html": "<div class=\"qf-stat\"><span class=\"qf-stat__label\">Images kept</span><span class=\"qf-stat__value\">212</span><span class=\"qf-stat__hint\">limit 300</span></div>"
        }
      ],
      "purposeEn": "Displays a dashboard metric (KPI) with a large value, a label, and a trend delta. A hint line under the value (qf-stat__hint, e.g. \"limit 300\") is optional. --sm is for short figures in an aside or summary row (value 18px 600, label 12px).",
      "a11yEn": "Trend is conveyed not just by color but with an arrow/sign (▲▼ or +/-) and text, avoiding reliance on color alone. Pair the value with its unit and meaning."
    },
    {
      "name": "Sparkline",
      "class": "qf-sparkline",
      "layer": "generic",
      "category": "Display",
      "purpose": "시계열 추세를 작은 영역에 보여주는 미니 차트입니다. 데이터→points/path 는 소비처가 생성하고 DS 는 토큰 기반 스타일(선, 영역 색)을 제공합니다.",
      "variants": [
        "qf-sparkline__line",
        "qf-sparkline__area",
        "qf-sparkline--ok",
        "qf-sparkline--warn",
        "qf-sparkline--danger"
      ],
      "requiredTokens": [
        "--accent",
        "--ok-400",
        "--warn-400",
        "--danger-400"
      ],
      "a11y": "장식이 아니면 role=img + aria-label 로 추세 요약(예: \"최근 24시간 처리량, 상승\")을 제공하고, 색뿐 아니라 수치 라벨을 함께 둡니다.",
      "exampleHtml": "<span class=\"qf-sparkline qf-sparkline--ok\"><svg viewBox=\"0 0 100 32\" preserveAspectRatio=\"none\" role=\"img\" aria-label=\"Recent throughput trend, rising\"><polyline class=\"qf-sparkline__area\" points=\"0,28 12,24 24,26 36,18 48,20 60,12 72,14 84,6 100,8 100,32 0,32\"></polyline><polyline class=\"qf-sparkline__line\" points=\"0,28 12,24 24,26 36,18 48,20 60,12 72,14 84,6 100,8\"></polyline></svg></span>",
      "purposeEn": "A mini chart that shows a time-series trend within a small area. The consumer generates points/path from the data, and the DS provides token-based styling (line and area colors).",
      "a11yEn": "Unless decorative, provide a trend summary via role=img + aria-label (e.g., \"Throughput over the last 24 hours, rising\"), and pair color with numeric labels."
    },
    {
      "name": "Spinner",
      "class": "qf-spinner",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "작업이 진행 중임을 나타내는 회전 로더입니다.",
      "variants": [
        "qf-spinner--sm",
        "qf-spinner--lg"
      ],
      "requiredTokens": [
        "--bg-selected",
        "--accent",
        "--r-pill"
      ],
      "a11y": "role=status 와 aria-label(예: 불러오는 중)로 진행을 알립니다. prefers-reduced-motion 은 tokens.css 전역 가드가 존중합니다.",
      "exampleHtml": "<span class=\"qf-spinner\" role=\"status\" aria-label=\"Loading\"></span>",
      "purposeEn": "A spinning loader that indicates work is in progress.",
      "a11yEn": "Announce progress with role=status and an aria-label (e.g., Loading). prefers-reduced-motion is honored by the global guard in tokens.css."
    },
    {
      "name": "Progress",
      "class": "qf-progress",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "작업 진행률을 막대로 표시합니다.",
      "variants": [
        "qf-progress__bar",
        "qf-progress--success",
        "qf-progress--danger",
        "qf-progress--indeterminate"
      ],
      "requiredTokens": [
        "--bg-hover",
        "--accent",
        "--ok-400",
        "--danger-400",
        "--r-pill",
        "--s-2",
        "--dur-base"
      ],
      "a11y": "role=progressbar 와 aria-valuenow/valuemin/valuemax 로 값을 전달합니다. 진행률 미정 상태는 aria-busy=true 또는 값 속성 생략으로 표현합니다.",
      "exampleHtml": "<div class=\"qf-progress\" role=\"progressbar\" aria-valuenow=\"60\" aria-valuemin=\"0\" aria-valuemax=\"100\"><span class=\"qf-progress__bar\" style=\"width: 60%\"></span></div>",
      "purposeEn": "Displays task progress as a bar.",
      "a11yEn": "Convey the value with role=progressbar and aria-valuenow/valuemin/valuemax. Express an indeterminate state with aria-busy=true or by omitting the value attributes."
    },
    {
      "name": "Radial",
      "class": "qf-radial",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "용량, 사용률 등 단일 값을 방사형(게이지)으로 보여줍니다. --qf-radial-value(0~100) 를 인라인 style 로 전달합니다.",
      "variants": [
        "qf-radial__value",
        "qf-radial--ok",
        "qf-radial--warn",
        "qf-radial--danger"
      ],
      "requiredTokens": [
        "--accent",
        "--bg-hover",
        "--bg-elevated",
        "--text-strong",
        "--ok-400",
        "--warn-400",
        "--danger-400",
        "--fs-18"
      ],
      "a11y": "role=progressbar + aria-valuenow/min/max 로 값을 전달하고, 중앙 수치 텍스트로 색 단독 의존을 피합니다.",
      "exampleHtml": "<div class=\"qf-radial qf-radial--warn\" style=\"--qf-radial-value:72\" role=\"progressbar\" aria-valuenow=\"72\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-label=\"Disk usage\"><span class=\"qf-radial__value\">72%</span></div>",
      "purposeEn": "Shows a single value such as capacity or utilization as a radial gauge. Pass --qf-radial-value (0–100) via inline style.",
      "a11yEn": "Convey the value with role=progressbar + aria-valuenow/min/max, and use the centered numeric text to avoid reliance on color alone."
    },
    {
      "name": "Select",
      "class": "qf-select",
      "layer": "generic",
      "category": "Forms",
      "purpose": "네이티브 select 를 디자인에 맞춰 감싸는 드롭다운 선택 필드입니다.",
      "variants": [],
      "requiredTokens": [
        "--bg-input",
        "--border",
        "--text",
        "--r-lg",
        "--s-4",
        "--s-8",
        "--text-muted",
        "--ring-accent"
      ],
      "a11y": "내부는 네이티브 select 라 키보드/스크린리더 동작을 그대로 보존합니다. label 을 for/id 로 연결하고, 오류는 aria-invalid=true 로 표시합니다.",
      "exampleHtml": "<span class=\"qf-select\"><select aria-label=\"Country\"><option>Korea</option><option>Japan</option></select></span>",
      "purposeEn": "A dropdown select field that wraps a native select to match the design.",
      "a11yEn": "Because the core is a native select, keyboard and screen reader behavior is preserved as-is. Associate the label via for/id, and indicate errors with aria-invalid=true."
    },
    {
      "name": "Radio",
      "class": "qf-radio",
      "layer": "generic",
      "category": "Forms",
      "purpose": "단일 선택 라디오 버튼과 라벨을 한 행으로 묶습니다.",
      "variants": [
        "qf-radio__input",
        "qf-radio__label"
      ],
      "requiredTokens": [
        "--s-3",
        "--s-5",
        "--accent",
        "--fs-13",
        "--text-secondary"
      ],
      "a11y": "같은 name 으로 그룹을 묶고 qf-choice-list 로 감쌉니다. 네이티브 input 위에 accent-color 만 얹어 포커스 가시를 보존합니다.",
      "exampleHtml": "<div class=\"qf-stack\" style=\"gap:var(--s-2)\"><label class=\"qf-radio\"><input class=\"qf-radio__input\" type=\"radio\" name=\"plan\" checked><span class=\"qf-radio__label\">Free</span></label><label class=\"qf-radio\"><input class=\"qf-radio__input\" type=\"radio\" name=\"plan\"><span class=\"qf-radio__label\">Pro</span></label><label class=\"qf-radio\"><input class=\"qf-radio__input\" type=\"radio\" name=\"plan\"><span class=\"qf-radio__label\">Team</span></label></div>",
      "purposeEn": "Pairs a single-select radio button with its label on one row.",
      "a11yEn": "Group options under the same name and wrap them in qf-choice-list. Only accent-color is layered onto the native input to preserve focus visibility."
    },
    {
      "name": "ChoiceList",
      "class": "qf-choice-list",
      "layer": "generic",
      "category": "Forms",
      "purpose": "체크박스나 라디오 그룹을 제목과 함께 묶는 컨테이너입니다.",
      "variants": [
        "qf-choice-list__legend"
      ],
      "requiredTokens": [
        "--s-3",
        "--s-1",
        "--fs-12",
        "--text-secondary",
        "--tracking-caps"
      ],
      "a11y": "fieldset 으로 만들고 legend(qf-choice-list__legend)로 그룹 제목을 전달합니다. 단일 선택은 radio, 다중 선택은 checkbox 를 씁니다.",
      "exampleHtml": "<fieldset class=\"qf-choice-list\"><legend class=\"qf-choice-list__legend\">Notifications</legend><label class=\"qf-checkbox\"><input class=\"qf-checkbox__input\" type=\"checkbox\" checked><span class=\"qf-checkbox__label\">Email</span></label><label class=\"qf-checkbox\"><input class=\"qf-checkbox__input\" type=\"checkbox\"><span class=\"qf-checkbox__label\">Push</span></label><label class=\"qf-checkbox\"><input class=\"qf-checkbox__input\" type=\"checkbox\"><span class=\"qf-checkbox__label\">SMS</span></label></fieldset>",
      "purposeEn": "A container that groups a set of checkboxes or radios together with a title.",
      "a11yEn": "Build it as a fieldset and convey the group title with legend (qf-choice-list__legend). Use radio for single selection and checkbox for multiple selection."
    },
    {
      "name": "FormLayout",
      "class": "qf-form",
      "layer": "generic",
      "category": "Forms",
      "purpose": "폼 필드를 세로로 배치하고 가로 행과 액션열을 제공하는 레이아웃입니다.",
      "variants": [
        "qf-form__row",
        "qf-form__actions"
      ],
      "requiredTokens": [
        "--s-5",
        "--s-4",
        "--s-3",
        "--grid-cell-min"
      ],
      "a11y": "form 요소에 적용하고 각 필드는 qf-field 로 라벨/힌트/에러를 연결합니다. __row 는 좁은 폭에서 자동 줄바꿈됩니다.",
      "exampleHtml": "<form class=\"qf-form\"><div class=\"qf-form__row\"><label class=\"qf-field\"><span class=\"qf-field__label\">Name</span><input class=\"qf-input\"></label></div><div class=\"qf-form__actions\"><button class=\"qf-btn qf-btn--primary\">Save</button></div></form>",
      "purposeEn": "A layout that arranges form fields vertically and provides horizontal rows and an action row.",
      "a11yEn": "Apply it to a form element, and associate each field's label/hint/error via qf-field. __row wraps automatically at narrow widths."
    },
    {
      "name": "Link",
      "class": "qf-link",
      "layer": "generic",
      "category": "Actions",
      "purpose": "본문 안의 인라인 텍스트 링크입니다.",
      "variants": [
        "qf-link--muted",
        "qf-link--unresolved"
      ],
      "requiredTokens": [
        "--link",
        "--link-hover",
        "--r-xs",
        "--text-secondary",
        "--text-muted"
      ],
      "a11y": "실제 a[href] 로 만들어 키보드/스크린리더 탐색을 보존하고, hover 밑줄과 충분한 대비로 색 단독 의존을 피합니다. 포커스 ring 이 보입니다. 미해결 위키링크(qf-link--unresolved)는 href 를 비우고 aria-disabled=\"true\" 로 표시하며, 점선 밑줄과 색으로 미해결을 색 단독이 아니게 전달합니다.",
      "exampleHtml": "<a class=\"qf-link\" href=\"/docs\">View docs</a>",
      "examples": [
        {
          "label": "Default",
          "html": "<a class=\"qf-link\" href=\"/docs\">View docs</a>"
        },
        {
          "label": "Muted",
          "html": "<a class=\"qf-link qf-link--muted\" href=\"/docs\">View docs</a>"
        },
        {
          "label": "Unresolved wikilink",
          "html": "<a class=\"qf-link qf-link--unresolved\" aria-disabled=\"true\" role=\"link\">Missing note</a>"
        }
      ],
      "purposeEn": "An inline text link within body content.",
      "a11yEn": "Built as a real a[href] to preserve keyboard and screen reader navigation, with a hover underline and sufficient contrast so meaning is not carried by color alone. The focus ring is visible. An unresolved wikilink (qf-link--unresolved) has no href and is marked aria-disabled=\"true\", conveying the unresolved state with a dotted underline and color rather than color alone."
    },
    {
      "name": "ButtonGroup",
      "class": "qf-btn-group",
      "layer": "generic",
      "category": "Actions",
      "purpose": "인접한 버튼들을 한 덩어리로 결합한 그룹입니다(분할 액션, 세그먼트).",
      "variants": [],
      "requiredTokens": [
        "--r-md"
      ],
      "a11y": "role=group 과 aria-label 로 묶음의 의미를 전달합니다. 토글 세그먼트면 각 버튼에 aria-pressed 를 둡니다.",
      "exampleHtml": "<div class=\"qf-btn-group\" role=\"group\" aria-label=\"Sort\"><button class=\"qf-btn qf-btn--secondary\" aria-pressed=\"true\">List</button><button class=\"qf-btn qf-btn--secondary\">Grid</button></div>",
      "purposeEn": "A group that joins adjacent buttons into a single unit (split actions, segments).",
      "a11yEn": "Convey the unit's meaning with role=group and aria-label. For toggle segments, set aria-pressed on each button."
    },
    {
      "name": "CopyButton",
      "class": "qf-copy-btn",
      "layer": "generic",
      "category": "Actions",
      "purpose": "id, 경로, URL 등을 클립보드에 복사하는 독립 버튼입니다. 복사 완료는 data-copied=true 로 상태를 표시합니다(토글은 소비처).",
      "variants": [],
      "requiredTokens": [
        "--bg-elevated",
        "--border",
        "--text-secondary",
        "--bg-hover",
        "--ok-400",
        "--r-sm",
        "--font-mono"
      ],
      "a11y": "아이콘 옆 텍스트(복사/복사됨)로 동작과 결과를 전달하고, 완료는 색뿐 아니라 텍스트, aria-live 로 알립니다.",
      "exampleHtml": "<button class=\"qf-copy-btn\" type=\"button\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-copy\"/></svg>Copy</button>",
      "purposeEn": "A standalone button that copies an id, path, URL, or similar to the clipboard. Copy completion is reflected via data-copied=true (the consumer handles the toggle).",
      "a11yEn": "Convey the action and result with text next to the icon (Copy/Copied), and announce completion not just with color but with text and aria-live."
    },
    {
      "name": "Table",
      "class": "qf-table",
      "layer": "generic",
      "category": "Display",
      "purpose": "행과 열로 데이터를 보여주는 표입니다. 정렬 캐럿(aria-sort), sticky 헤더, 행 선택, 툴바 affordance 를 제공합니다(정렬/선택 로직은 소비처).",
      "variants": [
        "qf-table-wrap",
        "qf-table--striped",
        "qf-table__th--sortable",
        "qf-table__sort",
        "qf-table--sticky",
        "qf-table__toolbar",
        "qf-table__toolbar-title",
        "qf-table__toolbar-spacer"
      ],
      "requiredTokens": [
        "--divider",
        "--s-3",
        "--s-4",
        "--text-muted",
        "--text",
        "--bg-hover",
        "--fs-14",
        "--accent",
        "--accent-bg",
        "--bg-panel",
        "--z-sticky"
      ],
      "a11y": "thead/th(scope=col)/tbody 구조로 헤더 관계를 전달하고, 표 용도는 caption 또는 aria-label 로 설명합니다. 좁은 폭은 qf-table-wrap 으로 가로 스크롤합니다.",
      "exampleHtml": "<div class=\"qf-table-wrap\"><div class=\"qf-table__toolbar\"><span class=\"qf-table__toolbar-title\">Jobs</span><span class=\"qf-table__toolbar-spacer\"></span><button class=\"qf-btn qf-btn--secondary qf-btn--sm\">Retry</button></div><table class=\"qf-table qf-table--sticky\"><thead><tr><th><input type=\"checkbox\" class=\"qf-checkbox__input\" aria-label=\"Select all\"></th><th class=\"qf-table__th--sortable\" aria-sort=\"ascending\"><span class=\"qf-table__sort\">Name</span></th><th class=\"qf-table__th--sortable\"><span class=\"qf-table__sort\">Status</span></th></tr></thead><tbody><tr class=\"is-selected\"><td><input type=\"checkbox\" class=\"qf-checkbox__input\" checked aria-label=\"Select row\"></td><td>sync-skulk</td><td>Running</td></tr><tr><td><input type=\"checkbox\" class=\"qf-checkbox__input\" aria-label=\"Select row\"></td><td>backfill</td><td>Queued</td></tr></tbody></table></div>",
      "purposeEn": "A table that presents data in rows and columns, offering a sort caret (aria-sort), a sticky header, row selection, and toolbar affordances (the consumer handles sort and selection logic).",
      "a11yEn": "Convey header relationships with a thead/th(scope=col)/tbody structure, and describe the table's purpose with a caption or aria-label. At narrow widths, qf-table-wrap enables horizontal scrolling."
    },
    {
      "name": "DescriptionList",
      "class": "qf-dl",
      "layer": "generic",
      "category": "Display",
      "purpose": "용어와 설명(키-값) 쌍을 정렬해 보여주는 목록입니다.",
      "variants": [],
      "requiredTokens": [
        "--s-2",
        "--s-5",
        "--text-muted",
        "--text",
        "--fs-13",
        "--fs-14"
      ],
      "a11y": "dl/dt/dd 시맨틱으로 키-값 관계를 전달합니다. 시각적 2열 정렬은 grid 가 담당하고 의미는 마크업이 보존합니다.",
      "exampleHtml": "<dl class=\"qf-dl\"><dt>Plan</dt><dd>Pro</dd><dt>Joined</dt><dd>2026-01-01</dd></dl>",
      "purposeEn": "A list that presents term-and-description (key-value) pairs in an aligned layout.",
      "a11yEn": "Convey key-value relationships with dl/dt/dd semantics. The grid handles the visual two-column alignment while the markup preserves the meaning."
    },
    {
      "name": "AvatarGroup",
      "class": "qf-avatar-group",
      "layer": "generic",
      "category": "Display",
      "purpose": "여러 아바타를 겹쳐 쌓아 참여자를 압축 표시합니다.",
      "variants": [],
      "requiredTokens": [
        "--avatar-ring",
        "--r-pill",
        "--s-3"
      ],
      "a11y": "각 아바타 이미지에 alt(사용자명)를 제공하고, 그룹 전체 의미(예: 참여자 5명)는 aria-label 로 보충합니다. ring 은 --avatar-ring(표면) 으로 표면과 분리됩니다.",
      "exampleHtml": "<span class=\"qf-avatar-group\" aria-label=\"5 participants\"><span class=\"qf-avatar qf-avatar--sm\">M</span><span class=\"qf-avatar qf-avatar--sm\">H</span><span class=\"qf-avatar qf-avatar--sm\">J</span><span class=\"qf-avatar qf-avatar--sm\">+2</span></span>",
      "purposeEn": "Stacks multiple avatars in an overlapping cluster to compactly represent a set of participants.",
      "a11yEn": "Provide alt text (the user's name) on each avatar image, and supplement the group's overall meaning (e.g. \"5 participants\") with aria-label. The ring is separated from the surface via --avatar-ring (surface)."
    },
    {
      "name": "Breadcrumb",
      "class": "qf-breadcrumb",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "현재 위치까지의 경로를 보여주는 이동 경로 내비게이션입니다.",
      "variants": [
        "qf-breadcrumb__item",
        "qf-breadcrumb__sep"
      ],
      "requiredTokens": [
        "--s-2",
        "--link",
        "--text-muted",
        "--fs-13"
      ],
      "a11y": "nav[aria-label=경로] 안의 ol 로 만들고, 현재 페이지 항목에 aria-current=page 를 둡니다. 구분 기호(__sep)는 aria-hidden 으로 장식 처리합니다.",
      "exampleHtml": "<nav aria-label=\"Breadcrumb\"><ol class=\"qf-breadcrumb\"><li><a class=\"qf-breadcrumb__item\" href=\"/\">Home</a></li><li class=\"qf-breadcrumb__sep\" aria-hidden=\"true\">/</li><li><a class=\"qf-breadcrumb__item\" aria-current=\"page\">Settings</a></li></ol></nav>",
      "purposeEn": "A breadcrumb trail that shows the path to the current location.",
      "a11yEn": "Build it as an ol inside nav[aria-label=\"breadcrumb\"], and set aria-current=\"page\" on the current page's item. Mark the separator (__sep) as decorative with aria-hidden."
    },
    {
      "name": "Pagination",
      "class": "qf-pagination",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "페이지 사이를 이동하는 페이지네이션입니다.",
      "variants": [
        "qf-pagination__item"
      ],
      "requiredTokens": [
        "--s-1",
        "--r-sm",
        "--text-secondary",
        "--bg-hover",
        "--bg-selected",
        "--text-strong",
        "--fs-14"
      ],
      "a11y": "nav[aria-label=페이지] 로 감싸고 현재 페이지에 aria-current=page 를 둡니다. 이전/다음 버튼에는 aria-label 을 주고, 불가 상태는 disabled 로 알립니다.",
      "exampleHtml": "<nav aria-label=\"Pagination\"><ul class=\"qf-pagination\"><li><button class=\"qf-pagination__item\" aria-label=\"Previous\">‹</button></li><li><button class=\"qf-pagination__item\" aria-current=\"page\">1</button></li><li><button class=\"qf-pagination__item\">2</button></li></ul></nav>",
      "purposeEn": "Pagination controls for moving between pages.",
      "a11yEn": "Wrap it in nav[aria-label=\"pagination\"] and set aria-current=\"page\" on the current page. Give the previous/next buttons an aria-label, and signal unavailable states with disabled."
    },
    {
      "name": "Accordion",
      "class": "qf-accordion",
      "layer": "generic",
      "category": "Disclosure",
      "purpose": "제목을 눌러 내용을 펼치고 접는 디스클로저 목록입니다.",
      "variants": [
        "qf-accordion__item",
        "qf-accordion__trigger",
        "qf-accordion__panel"
      ],
      "requiredTokens": [
        "--border",
        "--divider",
        "--r-lg",
        "--s-4",
        "--s-5",
        "--text-strong",
        "--bg-hover"
      ],
      "a11y": "네이티브 details/summary 기반이라 키보드 토글과 펼침 상태 전달이 기본 제공됩니다. summary(qf-accordion__trigger)에 의미 있는 제목 텍스트를 둡니다.",
      "exampleHtml": "<div class=\"qf-accordion\"><details class=\"qf-accordion__item\" open><summary class=\"qf-accordion__trigger\">Shipping policy</summary><div class=\"qf-accordion__panel\">Ships within 3 days.</div></details></div>",
      "purposeEn": "A disclosure list whose sections expand and collapse when you click their header.",
      "a11yEn": "Built on native details/summary, so keyboard toggling and expanded-state communication come for free. Put meaningful header text in the summary (qf-accordion__trigger)."
    },
    {
      "name": "Popover",
      "class": "qf-popover",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "트리거 기준으로 떠 있는 임의 콘텐츠 패널입니다(메뉴와 달리 자유 콘텐츠).",
      "variants": [],
      "requiredTokens": [
        "--bg-floating",
        "--border",
        "--r-lg",
        "--elev-3",
        "--s-4",
        "--z-dropdown"
      ],
      "a11y": "트리거에 aria-haspopup/aria-expanded 를 두고 패널과 aria-controls 로 연결합니다. Esc 로 닫고 포커스를 트리거로 복원하며, 열릴 때 포커스를 패널로 옮깁니다.",
      "exampleHtml": "<div class=\"qf-popover\" role=\"dialog\" aria-label=\"Filter\"><p>This is floating content.</p></div>",
      "purposeEn": "A floating panel of arbitrary content anchored to a trigger (free-form content, unlike a menu).",
      "a11yEn": "Set aria-haspopup/aria-expanded on the trigger and link it to the panel with aria-controls. Close on Esc and restore focus to the trigger, and move focus into the panel when it opens."
    },
    {
      "name": "Drawer",
      "class": "qf-drawer",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "화면 측면에서 슬라이드되는 패널입니다(우측 기본, 좌측 변형).",
      "variants": [
        "qf-drawer-backdrop",
        "qf-drawer--left",
        "qf-drawer__header",
        "qf-drawer__title",
        "qf-drawer__body",
        "qf-drawer__footer"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--border",
        "--elev-4",
        "--z-modal",
        "--z-modal-bg",
        "--scrim",
        "--s-6",
        "--divider"
      ],
      "a11y": "role=dialog 와 aria-modal=true 로 알리고 포커스를 패널 안에 가둡니다. Esc 와 backdrop 클릭으로 닫고, 열기 전 포커스를 복원합니다.",
      "exampleHtml": "<div class=\"qf-drawer-backdrop\"></div><aside class=\"qf-drawer\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Settings\"><div class=\"qf-drawer__header\"><h2 class=\"qf-drawer__title\">Settings</h2></div><div class=\"qf-drawer__body\">Content</div></aside>",
      "purposeEn": "A panel that slides in from the edge of the screen (right by default, with a left variant).",
      "a11yEn": "Announce it with role=\"dialog\" and aria-modal=\"true\", and trap focus inside the panel. Close on Esc and backdrop click, and restore focus to where it was before opening."
    },
    {
      "name": "AppShell",
      "class": "qf-app-shell",
      "layer": "generic",
      "category": "Layout",
      "purpose": "헤더, 사이드바, 본문, 우측 보조 패널을 배치하는 범용 앱 셸입니다. 어떤 영역을 넣느냐로 1, 2, 3단과 헤더 유무가 자동 결정됩니다(별도 모디파이어 불필요). 컨테이너 쿼리로 셸 폭이 좁아지면(모바일) 사이드바, aside가 접혀 단일 컬럼이 됩니다. 폭은 --sidenav-w, --aside-w로 조절합니다.",
      "variants": [
        "qf-app-shell__navbar",
        "qf-app-shell__sidenav",
        "qf-app-shell__main",
        "qf-app-shell__aside",
        "qf-app-shell--locked",
        "qf-scroll-region"
      ],
      "requiredTokens": [
        "--sidenav-w",
        "--aside-w",
        "--shell-vh"
      ],
      "a11y": "레이아웃 컨테이너입니다. 내부에 landmark(header, nav, main)를 두어 보조기술이 영역을 인식하게 합니다. 사이드바 폭은 --sidenav-w 로 조절합니다.",
      "exampleHtml": "<div class=\"qf-app-shell\"><header class=\"qf-app-shell__navbar\"><nav class=\"qf-navbar\"><span class=\"qf-navbar__brand\">Acme</span><span class=\"qf-navbar__spacer\"></span><a class=\"qf-navbar__link\" aria-current=\"page\">Dashboard</a><a class=\"qf-navbar__link\">Reports</a></nav></header><nav class=\"qf-app-shell__sidenav qf-sidenav\"><div class=\"qf-sidenav__section\">Menu</div><a class=\"qf-sidenav__item\" aria-current=\"page\">Dashboard</a><a class=\"qf-sidenav__item\">Members</a><a class=\"qf-sidenav__item\">Settings</a></nav><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>A general app shell that lays out a header, sidebar, and main content. Columns derive from which regions you include.</p></main></div>",
      "wideExamples": true,
      "variantsAsChips": true,
      "examples": [
        {
          "label": "1 column, with header",
          "html": "<div class=\"qf-app-shell\"><header class=\"qf-app-shell__navbar\"><nav class=\"qf-navbar\"><span class=\"qf-navbar__brand\">Acme</span><span class=\"qf-navbar__spacer\"></span><a class=\"qf-navbar__link\" aria-current=\"page\">Dashboard</a><a class=\"qf-navbar__link\">Reports</a></nav></header><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>Header plus content — include only the navbar and main.</p></main></div>"
        },
        {
          "label": "1 column, no header",
          "html": "<div class=\"qf-app-shell\"><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>Just the content area — include only main.</p></main></div>"
        },
        {
          "label": "2 columns, with header",
          "html": "<div class=\"qf-app-shell\"><header class=\"qf-app-shell__navbar\"><nav class=\"qf-navbar\"><span class=\"qf-navbar__brand\">Acme</span><span class=\"qf-navbar__spacer\"></span><a class=\"qf-navbar__link\" aria-current=\"page\">Dashboard</a><a class=\"qf-navbar__link\">Reports</a></nav></header><nav class=\"qf-app-shell__sidenav qf-sidenav\"><div class=\"qf-sidenav__section\">Menu</div><a class=\"qf-sidenav__item\" aria-current=\"page\">Dashboard</a><a class=\"qf-sidenav__item\">Members</a><a class=\"qf-sidenav__item\">Settings</a></nav><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>Sidebar plus content under a header — the default layout.</p></main></div>"
        },
        {
          "label": "2 columns, no header",
          "html": "<div class=\"qf-app-shell\"><nav class=\"qf-app-shell__sidenav qf-sidenav\"><div class=\"qf-sidenav__section\">Menu</div><a class=\"qf-sidenav__item\" aria-current=\"page\">Dashboard</a><a class=\"qf-sidenav__item\">Members</a><a class=\"qf-sidenav__item\">Settings</a></nav><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>Sidebar plus content, with no top header.</p></main></div>"
        },
        {
          "label": "3 columns, with header",
          "html": "<div class=\"qf-app-shell\"><header class=\"qf-app-shell__navbar\"><nav class=\"qf-navbar\"><span class=\"qf-navbar__brand\">Acme</span><span class=\"qf-navbar__spacer\"></span><a class=\"qf-navbar__link\" aria-current=\"page\">Dashboard</a><a class=\"qf-navbar__link\">Reports</a></nav></header><nav class=\"qf-app-shell__sidenav qf-sidenav\"><div class=\"qf-sidenav__section\">Menu</div><a class=\"qf-sidenav__item\" aria-current=\"page\">Dashboard</a><a class=\"qf-sidenav__item\">Members</a><a class=\"qf-sidenav__item\">Settings</a></nav><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>Sidebar, content, and a right aside — a three-column workspace.</p></main><aside class=\"qf-app-shell__aside\"><div class=\"qf-eyebrow\">Activity</div><p>Recent updates and contextual details appear here.</p></aside></div>"
        },
        {
          "label": "3 columns, no header",
          "html": "<div class=\"qf-app-shell\"><nav class=\"qf-app-shell__sidenav qf-sidenav\"><div class=\"qf-sidenav__section\">Menu</div><a class=\"qf-sidenav__item\" aria-current=\"page\">Dashboard</a><a class=\"qf-sidenav__item\">Members</a><a class=\"qf-sidenav__item\">Settings</a></nav><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>Three columns without a header bar.</p></main><aside class=\"qf-app-shell__aside\"><div class=\"qf-eyebrow\">Activity</div><p>Recent updates and contextual details appear here.</p></aside></div>"
        },
        {
          "label": "Mobile (≤600px — sidebar & aside collapse)",
          "html": "<div style=\"max-width:380px;margin-inline:auto;border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden\"><div class=\"qf-app-shell\"><header class=\"qf-app-shell__navbar\"><nav class=\"qf-navbar\"><span class=\"qf-navbar__brand\">Acme</span><span class=\"qf-navbar__spacer\"></span><a class=\"qf-navbar__link\" aria-current=\"page\">Dashboard</a><a class=\"qf-navbar__link\">Reports</a></nav></header><nav class=\"qf-app-shell__sidenav qf-sidenav\"><div class=\"qf-sidenav__section\">Menu</div><a class=\"qf-sidenav__item\" aria-current=\"page\">Dashboard</a><a class=\"qf-sidenav__item\">Members</a><a class=\"qf-sidenav__item\">Settings</a></nav><main class=\"qf-app-shell__main\"><h1>Dashboard</h1><p>The same three-column markup: at a narrow width the sidebar and aside collapse to a single column. The app surfaces them via a drawer or tab bar.</p></main><aside class=\"qf-app-shell__aside\"><div class=\"qf-eyebrow\">Activity</div><p>Recent updates and contextual details appear here.</p></aside></div></div>"
        }
      ],
      "purposeEn": "A general app shell that lays out a header, sidebar, main content, and an optional right aside. Columns and the header derive from which regions you include — one, two, or three columns, with or without a header, no modifier needed. A container query collapses the sidebar and aside to a single column when the shell gets narrow (mobile). Tune widths with --sidenav-w and --aside-w.",
      "a11yEn": "This is a layout container. Place landmarks (header, nav, main) inside it so assistive technology can recognize each region. Control the sidebar width with --sidenav-w.",
      "deprecated": [
        "qf-app-shell--no-sidenav"
      ]
    },
    {
      "name": "Navbar",
      "class": "qf-navbar",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "브랜드, 내비 링크, 액션을 담는 상단 앱 바입니다. --sticky 는 위에 붙는 고정 상단 바입니다 — 긴 목록이나 읽기 페이지처럼 내려간 뒤에도 찾기와 맨 위로 가는 길이 필요할 때 씁니다. 클래스 계약: 소비처 JS 가 스크롤이 0 이 아니면 is-stuck(그림자·선), 아래로 스크롤 중이면 is-hidden(위로 숨음)을 붙였다 뗍니다. 좁은 폭 계약: 바는 한 줄을 지킵니다 — 브랜드는 말줄임, 링크 묶음(qf-navbar__nav)은 넘치면 가로 스크롤, 아이콘 버튼은 줄지 않음, ≤600px 에서 간격·여백 한 단계 축소. 폰에 둘 것은 브랜드 + 링크 2개 + 아이콘 버튼 3개까지이고 select·긴 링크는 소비처가 드로어나 메뉴로 옮깁니다(DS 는 아무것도 숨기지 않습니다).",
      "variants": [
        "qf-navbar__brand",
        "qf-navbar__nav",
        "qf-navbar__link",
        "qf-navbar__spacer",
        "qf-navbar--sticky"
      ],
      "requiredTokens": [
        "--h-navbar",
        "--bg-panel",
        "--divider",
        "--s-5",
        "--text-strong",
        "--bg-hover",
        "--bg-selected",
        "--z-header-sticky",
        "--elev-2",
        "--border",
        "--dur-base",
        "--ease-standard",
        "--dur-fast",
        "--m-touch",
        "--s-4",
        "--s-3",
        "--s-2",
        "--s-1",
        "--fs-16",
        "--fs-14",
        "--tracking-tight",
        "--text-secondary",
        "--r-sm",
        "--text"
      ],
      "a11y": "header > nav[aria-label] 로 감싸고, 현재 위치 링크에 aria-current=page 를 둡니다. 링크는 a[href], 키보드 포커스 가시. 밝기 토글은 qf-btn--ghost qf-btn--icon 에 aria-pressed(밝은 모드 = true)를 두고 해(qf-i-sun)/달(qf-i-moon) 아이콘을 소비처 JS 가 바꿉니다. 눌린 상태는 selected 표면으로 보입니다. 고정 바는 뷰포트를 가리므로 페이지 안 sticky(qf-dayrule)는 --qf-dayrule-top: var(--h-navbar) 로 그 아래에 두고, 포커스 이동 대상에는 scroll-margin-top 을 줍니다. 숨김 전환은 prefers-reduced-motion 에서 즉시 바뀝니다. --sticky 는 nav 를 body 직하(또는 header 자체)에 두어야 뷰포트에 붙습니다(header 안의 nav 는 header 박스 안에서만 붙음). 숨은 바 안으로 키보드 포커스가 들어오면 다시 보입니다. 바 안의 아이콘 버튼은 터치 환경에서 44px 히트 영역을 자동으로 받습니다. 브랜드가 아이콘 + 글자면 글자 span 에 말줄임 스타일을 두고(inline-flex 브랜드는 text-overflow 가 글자 요소에 걸려야 함) title 로 전체 이름을 줍니다. __nav 는 넘침 단서가 없으므로 폰에서 링크 2개까지(키보드 포커스는 자동 스크롤).",
      "exampleHtml": "<nav class=\"qf-navbar\" aria-label=\"Main menu\"><span class=\"qf-navbar__brand\">Acme</span><div class=\"qf-navbar__nav\"><a class=\"qf-navbar__link\" aria-current=\"page\">Dashboard</a><a class=\"qf-navbar__link\" href=\"/r\">Reports</a></div><span class=\"qf-navbar__spacer\"></span><button class=\"qf-btn qf-btn--ghost qf-btn--icon\" type=\"button\" aria-pressed=\"true\" aria-label=\"Light theme\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-sun\"/></svg></button><button class=\"qf-btn qf-btn--primary qf-btn--sm\">New</button></nav>",
      "purposeEn": "A top app bar that holds branding, navigation links, and actions. --sticky pins the bar to the top; use it on long lists and reading pages where search and a way back up must stay reachable. Class contract: consumer JS toggles is-stuck when scrolled past 0 (shadow and line) and is-hidden while scrolling down (slides up out of view). Narrow-width contract: the bar stays one line — the brand truncates with an ellipsis, the link group (qf-navbar__nav) scrolls horizontally when it overflows, icon buttons never shrink, and gaps/padding step down at ≤600px. On phones keep to brand + two links + three icon buttons; move a select or long links into a drawer or menu on the consumer side (the DS hides nothing).",
      "a11yEn": "Wrap it in header > nav[aria-label] and set aria-current=\"page\" on the link for the current location. Use a[href] for links, with a visible keyboard focus. A theme toggle is a qf-btn--ghost qf-btn--icon with aria-pressed (light mode = true); consumer JS swaps the sun (qf-i-sun) / moon (qf-i-moon) icon. The pressed state shows as the selected surface. A pinned bar covers part of the viewport: place in-page sticky elements (qf-dayrule) below it with --qf-dayrule-top: var(--h-navbar) and give focus targets a scroll-margin-top. The hide transition becomes instant under prefers-reduced-motion. For --sticky, put the nav directly under body (or make the header itself the bar); a nav inside a header only sticks within the header box. If keyboard focus enters a hidden bar it reappears. Icon buttons inside the bar get a 44px hit area on touch devices automatically. If the brand is icon + text, put the ellipsis styling on the text span (an inline-flex brand needs text-overflow on the text element) and give the full name via title. __nav shows no overflow hint, so keep to two links on phones (keyboard focus scrolls automatically)."
    },
    {
      "name": "ToTop",
      "class": "qf-totop",
      "layer": "generic",
      "category": "Actions",
      "purpose": "오른쪽 아래에 떠 있는 둥근 \"맨 위로\" 아이콘 버튼입니다(qf-i-chevron-up). 기본은 숨겨져 있고, 한 화면 넘게 내려갔을 때 소비처 JS 가 is-shown 을 붙이면 나타납니다. 44px 탭 대상이며 폰의 안전 영역을 더합니다. 바닥 광고나 모바일 탭바(qf-m-tabbar, z-index 가 더 높음) 위로 올릴 때는 --qf-totop-bottom 을 지정합니다(기본 --safe-bottom).",
      "purposeEn": "A round \"back to top\" icon button floating at the bottom right (qf-i-chevron-up). Hidden by default; consumer JS adds is-shown once the page is scrolled more than a screen. 44px tap target, with the phone safe area added. Set --qf-totop-bottom (default --safe-bottom) to lift it above a bottom ad or a mobile tab bar (qf-m-tabbar, which has a higher z-index).",
      "variants": [],
      "requiredTokens": [
        "--s-5",
        "--safe-bottom",
        "--z-sticky",
        "--m-touch",
        "--border",
        "--border-strong",
        "--r-pill",
        "--bg-elevated",
        "--bg-hover",
        "--text",
        "--elev-3",
        "--s-3",
        "--dur-fast",
        "--ease-standard",
        "--ring-focus"
      ],
      "a11y": "button 에 aria-label=\"맨 위로\" 를 둡니다. 숨김 상태는 visibility: hidden 이라 포커스 순서에서 빠지고, 보일 때만 탭·포커스됩니다. 포커스 링이 보입니다. 누르면 소비처가 window.scrollTo 로 올리고 포커스를 본문 첫 요소로 옮기면 좋습니다.",
      "a11yEn": "Give the button aria-label=\"Back to top\". While hidden it is visibility: hidden, so it leaves the focus order and is only tappable/focusable when shown. The focus ring is visible. On press the consumer scrolls to top and ideally moves focus to the first element of the content.",
      "exampleHtml": "<div style=\"position:relative;height:120px\"><button class=\"qf-totop is-shown\" type=\"button\" aria-label=\"Back to top\" style=\"position:absolute\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-chevron-up\"/></svg></button></div>",
      "variantsAsChips": true
    },
    {
      "name": "Nudge",
      "class": "qf-nudge",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "상단 바 아래 가운데에 떠서 \"새 이슈 3건\" 처럼 알리고, 누르면 맨 위로 가며 새로 고치는 알약 버튼입니다. 새 항목이 있을 때만 소비처가 DOM 에 넣고(위에서 내려오는 짧은 마운트 모션), 누르거나 처리되면 제거합니다 — 상태 클래스가 없습니다. 상단 바 높이가 기본(--h-navbar)과 다르면 --qf-nudge-top 으로 맞춥니다. 폰에서는 44px. button 또는 a 로 쓸 수 있습니다(밑줄 없음).",
      "purposeEn": "A pill button floating centered under the top bar that says something like \"3 new issues\" and, when pressed, scrolls to the top and refreshes. The consumer inserts it only when there is something new (short slide-down mount motion) and removes it on press or when handled — no state classes. If the top bar is not --h-navbar tall, set --qf-nudge-top. 44px on phones. It may be a button or an a (no underline).",
      "variants": [],
      "requiredTokens": [
        "--h-navbar",
        "--s-3",
        "--s-2",
        "--s-5",
        "--s-6",
        "--s-9",
        "--z-sticky",
        "--r-pill",
        "--accent-solid",
        "--accent-solid-hover",
        "--accent-solid-press",
        "--text-onAccent",
        "--elev-3",
        "--fs-13",
        "--dur-base",
        "--ease-emphasized",
        "--m-touch",
        "--ring-focus"
      ],
      "a11y": "button 이며 문구 자체가 이름입니다(아이콘은 aria-hidden). 새로 나타났음을 알리려면 aria-live=polite 영역 안에 넣습니다. 흰 글자는 solid accent 위에서 AA 를 넘습니다. 마운트 모션은 prefers-reduced-motion 에서 꺼집니다.",
      "a11yEn": "A button whose text is its name (icon aria-hidden). Place it inside an aria-live=polite region so its appearance is announced. White text on the solid accent passes AA. The mount motion is disabled under prefers-reduced-motion.",
      "exampleHtml": "<div style=\"position:relative;height:88px\"><button class=\"qf-nudge\" type=\"button\" style=\"position:absolute;top:var(--s-3)\"><svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-chevron-up\"/></svg>3 new issues</button></div>",
      "variantsAsChips": true
    },
    {
      "name": "SideNav",
      "class": "qf-sidenav",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "섹션과 항목으로 구성된 범용 세로 내비게이션입니다(채팅 채널 목록과 무관).",
      "variants": [
        "qf-sidenav__section",
        "qf-sidenav__item"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--divider",
        "--s-3",
        "--r-sm",
        "--text-secondary",
        "--bg-hover",
        "--bg-selected",
        "--text-muted"
      ],
      "a11y": "nav[aria-label] 로 감싸고, 현재 항목에 aria-current=page 를 둡니다. 섹션 제목(qf-sidenav__section)으로 그룹을 구분합니다.",
      "exampleHtml": "<nav class=\"qf-sidenav\" aria-label=\"Sections\"><div class=\"qf-sidenav__section\">Workspace</div><a class=\"qf-sidenav__item\" aria-current=\"page\">Overview</a><a class=\"qf-sidenav__item\" href=\"/m\">Members</a></nav>",
      "purposeEn": "A general-purpose vertical navigation organized into sections and items (unrelated to a chat channel list).",
      "a11yEn": "Wrap it in nav[aria-label] and set aria-current=\"page\" on the current item. Distinguish groups with section headings (qf-sidenav__section)."
    },
    {
      "name": "PageHeader",
      "class": "qf-page-header",
      "layer": "generic",
      "category": "Layout",
      "purpose": "페이지 제목, 부제, 액션을 한 줄로 묶는 페이지 헤더입니다. --hero 는 사이트 첫 화면용으로 제목을 한 단계 크게(32px), 부제를 본문 크기와 가독 폭으로 넉넉히 둡니다.",
      "variants": [
        "qf-page-header__title",
        "qf-page-header__subtitle",
        "qf-page-header__actions",
        "qf-page-header--hero"
      ],
      "requiredTokens": [
        "--s-7",
        "--fs-24",
        "--text-strong",
        "--text-muted",
        "--tracking-tight",
        "--fs-32",
        "--fs-16",
        "--text-secondary",
        "--container-w-narrow",
        "--s-8",
        "--s-5",
        "--s-2"
      ],
      "a11y": "제목은 h1(또는 페이지 계층에 맞는 heading)으로 두어 문서 구조를 전달합니다. 액션 묶음은 시각/DOM 순서를 일치시킵니다.",
      "exampleHtml": "<div class=\"qf-page-header\"><div><h1 class=\"qf-page-header__title\">Members</h1><p class=\"qf-page-header__subtitle\">12 members</p></div><div class=\"qf-page-header__actions\"><button class=\"qf-btn qf-btn--primary\">Invite</button></div></div>",
      "purposeEn": "A page header that groups the page title, subtitle, and actions into a single row. --hero is for a site's first screen: a larger title (32px) and a roomier subtitle at body size and reading width.",
      "a11yEn": "Make the title an h1 (or a heading appropriate to the page hierarchy) to convey document structure. Keep the action group's visual order in sync with its DOM order."
    },
    {
      "name": "TwoCol",
      "class": "qf-twocol",
      "layer": "generic",
      "category": "Layout",
      "purpose": "넓은 화면의 2열 틀입니다(본문 열 + 사이드 열). qf-container--wide(1200px) 안에 두면 1100px 이상에서 본문 열(최대 --container-w-reading 720px)과 사이드 열(--sz-aside 300px)이 간격 40px 로 가운데 정렬되어, 1100~1200 에서도 본문이 720 을 유지하고 사이드가 좁아지지 않습니다. 그 아래에서는 1열 — 본문 열이 지금의 reading 컨테이너처럼 가운데 오고 사이드는 본문 뒤로 내려갑니다(간격 --s-10). 2열에서 사이드는 고정 상단 바 아래에 sticky 로 붙고(--qf-aside-top 으로 바 높이 조정), 바가 is-hidden 으로 숨으면 맨 위로 미끄러집니다. 사이드는 뷰포트보다 짧게 두세요 — 768px 높이 노트북(실제 뷰포트 ≈ 650~680)을 기준으로 약 560px, 섹션 3개 이내. 길면 아래가 가려집니다.",
      "purposeEn": "A two-column frame for wide screens (main column + aside). Inside qf-container--wide (1200px), from 1100px up the main column (max --container-w-reading, 720px) and the aside (--sz-aside, 300px) sit 40px apart, centered as a pair, so between 1100 and 1200 the main stays at 720 and the aside does not shrink. Below that it is one column — the main column centers like today's reading container and the aside drops below it (gap --s-10). In two columns the aside sticks under the sticky top bar (adjust with --qf-aside-top) and slides to the top when the bar hides (is-hidden). Keep the aside shorter than the viewport — about 560px, up to three sections, sized for a 768px-tall laptop (real viewport ≈ 650–680); a taller aside gets its bottom cut off.",
      "variants": [
        "qf-twocol__main",
        "qf-twocol__aside"
      ],
      "requiredTokens": [
        "--container-w-reading",
        "--sz-aside",
        "--s-9",
        "--s-10",
        "--s-6",
        "--h-navbar",
        "--dur-base",
        "--ease-standard"
      ],
      "a11y": "본문(main 요소)을 먼저, 사이드(aside 요소)를 뒤에 두어 1열에서도 읽기 순서가 본문 → 사이드가 되게 합니다. 사이드의 섹션 제목은 h2 로 두어 문서 개요에 들어가게 합니다. sticky 사이드가 가리는 영역이 없도록 사이드 안에는 포커스 대상이 뷰포트 안에 다 보이는 길이만 둡니다.",
      "a11yEn": "Put the main element first and the aside after it so reading order stays main → aside in one column too. Make aside section titles h2 so they enter the outline. Keep the aside short enough that every focusable item inside it is visible while stuck.",
      "exampleHtml": "<div class=\"qf-twocol\" style=\"max-width:100%\"><main class=\"qf-twocol__main\"><article class=\"qf-story\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Politics</span><span>4 communities · 9 posts</span></div><h2 class=\"qf-story__title\">Supreme Court admits chief justice filed false expense reports</h2><p class=\"qf-story__summary\">The court acknowledged on Tuesday that expense reports filed by its chief justice were inaccurate.</p></article></main><aside class=\"qf-twocol__aside qf-aside\"><section class=\"qf-aside__section\"><h2 class=\"qf-aside__title\">Trending now</h2><ol class=\"qf-ranklist\" role=\"list\"><li class=\"qf-ranklist__item\"><a class=\"qf-ranklist__link\" href=\"#r1\"><span class=\"qf-ranklist__title\">Supreme Court admits false expense reports</span><span class=\"qf-ranklist__meta\">8 communities, 51 posts</span></a></li><li class=\"qf-ranklist__item\"><a class=\"qf-ranklist__link\" href=\"#r2\"><span class=\"qf-ranklist__title\">Central bank holds rates</span><span class=\"qf-ranklist__meta\">3 communities, 5 posts</span></a></li></ol></section></aside></div>",
      "wideExamples": true,
      "variantsAsChips": true
    },
    {
      "name": "Aside",
      "class": "qf-aside",
      "layer": "generic",
      "category": "Layout",
      "purpose": "사이드 열의 내용입니다: 섹션(qf-aside__section) 묶음, 섹션은 제목(qf-aside__title — 12px 600 대문자, --text-muted, qf-keypoints__title 과 같은 톤)과 본문. 본문보다 한 단계 조용한 톤(14px, --text-secondary)이고 섹션 사이 --s-6. 열 자체가 구분이므로 상자·선을 두르지 않습니다. 안에는 qf-ranklist(순위), qf-stat--sm 묶음(숫자), qf-chips + qf-chip__count(분류별 건수), qf-sources + qf-source--link(커뮤니티 링크) 를 둡니다.",
      "purposeEn": "Content of the aside column: a stack of sections (qf-aside__section), each with a title (qf-aside__title — 12px 600 caps, --text-muted, same tone as qf-keypoints__title) and a body. One step quieter than the main column (14px, --text-secondary), --s-6 between sections, no boxes or rules — the column itself is the separation. Inside go qf-ranklist (ranking), a group of qf-stat--sm (numbers), qf-chips with qf-chip__count (per-category counts), and qf-sources with qf-source--link (community links).",
      "variants": [
        "qf-aside__section",
        "qf-aside__title"
      ],
      "requiredTokens": [
        "--s-6",
        "--s-3",
        "--fs-14",
        "--fs-12",
        "--lh-tight",
        "--tracking-caps",
        "--text-secondary",
        "--text-muted"
      ],
      "a11y": "aside 요소에 두고 섹션 제목은 h2(또는 페이지 계층에 맞는 heading)로 둡니다. 섹션마다 aria-labelledby 로 제목을 연결하면 보조기기가 영역 이름을 읽습니다. 대문자 변환은 시각 표현입니다.",
      "a11yEn": "Use the aside element and make section titles h2 (or the heading level that fits the page). Link each section to its title with aria-labelledby so assistive technology names the region. The uppercase transform is visual only.",
      "exampleHtml": "<aside class=\"qf-aside\" style=\"max-width:300px\"><section class=\"qf-aside__section\" aria-labelledby=\"as1\"><h2 class=\"qf-aside__title\" id=\"as1\">Today in numbers</h2><div class=\"qf-cluster\" style=\"--qf-gap:var(--s-6)\"><div class=\"qf-stat qf-stat--sm\"><span class=\"qf-stat__value\">24</span><span class=\"qf-stat__label\">issues today</span></div><div class=\"qf-stat qf-stat--sm\"><span class=\"qf-stat__value\">12</span><span class=\"qf-stat__label\">communities</span></div><div class=\"qf-stat qf-stat--sm\"><span class=\"qf-stat__value\">3 min</span><span class=\"qf-stat__label\">since update</span></div></div></section><section class=\"qf-aside__section\" aria-labelledby=\"as2\"><h2 class=\"qf-aside__title\" id=\"as2\">By category</h2><div class=\"qf-chips\"><a class=\"qf-chip\" href=\"#c1\">Politics <span class=\"qf-chip__count\">7</span></a><a class=\"qf-chip\" href=\"#c2\">Society <span class=\"qf-chip__count\">5</span></a><a class=\"qf-chip\" href=\"#c3\">Sports <span class=\"qf-chip__count\">4</span></a></div></section><section class=\"qf-aside__section\" aria-labelledby=\"as3\"><h2 class=\"qf-aside__title\" id=\"as3\">Communities</h2><div class=\"qf-sources\"><a class=\"qf-source qf-source--link\" href=\"#s1\">Clien</a><a class=\"qf-source qf-source--link\" href=\"#s2\">Ruliweb</a><a class=\"qf-source qf-source--link\" href=\"#s3\">Ppomppu</a><a class=\"qf-source qf-source--link\" href=\"#s4\">82cook</a></div></section></aside>",
      "variantsAsChips": true
    },
    {
      "name": "Footer",
      "class": "qf-footer",
      "layer": "generic",
      "category": "Layout",
      "purpose": "읽기 폭 안의 바닥글입니다: 위 선, 안내 문단(qf-footer__note, 13px 보조 톤, 줄 길이 제한), 링크 줄(qf-footer__links). 안내는 한 문단으로 쓰기를 권합니다 — 둘 이상이면 작은 글씨가 길어져 읽히지 않습니다. 링크는 13px 에 세로 패딩으로 탭 대상 24px 이상이고 폰에서 두 줄로 감싸도 줄 사이 간격이 유지됩니다. 아래 여백은 안전 영역을 더합니다. 링크 앞에 아이콘(RSS 등)을 두면 간격 --s-1 로 붙습니다.",
      "purposeEn": "A footer within the reading width: a top rule, a note paragraph (qf-footer__note, 13px secondary tone, limited measure), and a link row (qf-footer__links). Keep the note to one paragraph — more small print stops being read. Links are 13px with vertical padding for a 24px+ tap target, and the row keeps its spacing when it wraps on phones. Bottom padding includes the safe area. A leading icon (e.g. RSS) inside a link sits with a --s-1 gap.",
      "variants": [
        "qf-footer__note",
        "qf-footer__links"
      ],
      "requiredTokens": [
        "--s-10",
        "--s-7",
        "--s-6",
        "--safe-bottom",
        "--border",
        "--text-muted",
        "--text-secondary",
        "--text",
        "--fs-13",
        "--lh-normal",
        "--container-w-narrow",
        "--s-2",
        "--s-4",
        "--s-5",
        "--s-1",
        "--ring-focus",
        "--r-xs"
      ],
      "a11y": "footer 요소(contentinfo)에 두고 링크 줄은 nav[aria-label] 또는 ul 로 묶습니다. 링크는 밑줄 대신 세로 패딩과 hover 밑줄을 쓰므로 바닥글 맥락(링크 줄)에서만 씁니다. 글자 13px 는 보조 정보의 바닥이며 안내 본문을 더 작게 두지 않습니다.",
      "a11yEn": "Use the footer element (contentinfo) and group the links in nav[aria-label] or a ul. Links rely on the link-row context plus hover underline rather than a permanent underline, so use this only in a footer link row. 13px is the floor for secondary text; do not make the note smaller.",
      "exampleHtml": "<footer class=\"qf-footer\"><p class=\"qf-footer__note\">Summaries are written by AI from community posts and may be inaccurate. Check the original posts for details.</p><nav class=\"qf-footer__links\" aria-label=\"Site\"><a href=\"#about\">About</a><a href=\"#removal\">Correction and removal requests</a><a href=\"#privacy\">Privacy policy</a></nav></footer>",
      "wideExamples": true
    },
    {
      "name": "PostNav",
      "class": "qf-postnav",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "글 끝의 이전 글 / 다음 글입니다(읽기 열 끝, 바닥글 앞 — \"읽은 다음\"의 자리). 두 칸이며 ≤600px 에서는 한 칸씩 세로로 쌓여 제목이 여러 줄로 감기지 않습니다. 각 칸은 카드 톤의 블록 링크(qf-postnav__link, --prev / --next)로 머리말(qf-postnav__eyebrow: \"← 이전 글\" / \"다음 글 →\")과 제목(qf-postnav__title, 15px 600, 두 줄 clamp)을 담고, 다음 글은 오른쪽 정렬입니다. 한쪽만 있으면 그 칸만 둡니다.",
      "purposeEn": "Previous / next post links at the end of an article (end of the reading column, before the footer — the \"after reading\" slot). Two cells; at ≤600px they stack so titles do not wrap into many lines. Each cell is a card-toned block link (qf-postnav__link, --prev / --next) holding an eyebrow (qf-postnav__eyebrow: \"← Previous\" / \"Next →\") and a title (qf-postnav__title, 15px 600, two-line clamp); the next cell is right-aligned. If only one neighbour exists, render just that cell.",
      "variants": [
        "qf-postnav__link",
        "qf-postnav__link--prev",
        "qf-postnav__link--next",
        "qf-postnav__eyebrow",
        "qf-postnav__title"
      ],
      "requiredTokens": [
        "--s-9",
        "--s-4",
        "--s-5",
        "--s-2",
        "--border",
        "--border-strong",
        "--r-lg",
        "--bg-panel",
        "--bg-elevated",
        "--fs-12",
        "--fs-15",
        "--lh-tight",
        "--lh-snug",
        "--tracking-caps",
        "--text-muted",
        "--text-strong",
        "--accent-text",
        "--ring-focus",
        "--dur-fast",
        "--ease-standard"
      ],
      "a11y": "nav[aria-label=\"앞뒤 글\"] 로 감싸고 각 칸은 a[href] 입니다. 머리말의 화살표는 텍스트라 낭독되며 링크 이름은 \"이전 글 제목\" 처럼 읽힙니다. 포커스 링이 칸 전체에 보입니다. 두 줄 clamp 로 잘린 제목은 title 속성으로 전체를 줄 수 있습니다.",
      "a11yEn": "Wrap in nav[aria-label=\"Adjacent posts\"]; each cell is an a[href]. The eyebrow arrows are text and are read, so the link name reads like \"Previous post, title\". The focus ring surrounds the whole cell. Provide clamped titles in full via a title attribute.",
      "exampleHtml": "<nav class=\"qf-postnav\" aria-label=\"Adjacent posts\"><a class=\"qf-postnav__link qf-postnav__link--prev\" href=\"#prev\"><span class=\"qf-postnav__eyebrow\">← Previous</span><span class=\"qf-postnav__title\">.NET - WPF JumpList</span></a><a class=\"qf-postnav__link qf-postnav__link--next\" href=\"#next\"><span class=\"qf-postnav__eyebrow\">Next →</span><span class=\"qf-postnav__title\">PowerShell - Creating a Credential for remote access</span></a></nav>",
      "wideExamples": true,
      "variantsAsChips": true
    },
    {
      "name": "Search",
      "class": "qf-search",
      "layer": "generic",
      "category": "Forms",
      "purpose": "아이콘과 클리어 버튼을 갖춘 검색 입력 필드입니다.",
      "variants": [
        "qf-search__icon",
        "qf-search__input",
        "qf-search__clear"
      ],
      "requiredTokens": [
        "--bg-input",
        "--border",
        "--accent",
        "--r-lg",
        "--s-4",
        "--text",
        "--text-muted"
      ],
      "a11y": "input[type=search] 에 aria-label 또는 연결된 label 을 둡니다. 아이콘은 aria-hidden, 클리어 버튼에는 aria-label(예: 검색어 지우기).",
      "exampleHtml": "<div class=\"qf-search\"><span class=\"qf-search__icon\" aria-hidden=\"true\"><svg class=\"qf-icon qf-icon--sm\"><use href=\"#qf-i-search\"/></svg></span><input class=\"qf-search__input\" type=\"search\" aria-label=\"Search\" placeholder=\"Search\"><button class=\"qf-search__clear\" aria-label=\"Clear search\">×</button></div>",
      "purposeEn": "A search input field with an icon and a clear button.",
      "a11yEn": "Give the input[type=\"search\"] an aria-label or an associated label. Mark the icon aria-hidden, and give the clear button an aria-label (e.g. \"Clear search\")."
    },
    {
      "name": "Slider",
      "class": "qf-slider",
      "layer": "generic",
      "category": "Forms",
      "purpose": "범위 값을 선택하는 슬라이더입니다.",
      "variants": [],
      "requiredTokens": [
        "--s-2",
        "--r-pill",
        "--bg-hover",
        "--accent",
        "--ring-focus"
      ],
      "a11y": "네이티브 input[type=range] 라 키보드 조작과 값 전달이 기본 제공됩니다. label 을 연결하고 필요 시 aria-valuetext 로 단위를 보충합니다.",
      "exampleHtml": "<input class=\"qf-slider\" type=\"range\" min=\"0\" max=\"100\" value=\"40\" aria-label=\"Volume\">",
      "purposeEn": "A slider for selecting a value from a range.",
      "a11yEn": "Because it's a native input[type=\"range\"], keyboard operation and value communication come for free. Associate a label, and use aria-valuetext to supplement units when needed."
    },
    {
      "name": "NumberInput",
      "class": "qf-number-input",
      "layer": "generic",
      "category": "Forms",
      "purpose": "증감 버튼이 있는 숫자 입력 필드입니다.",
      "variants": [
        "qf-number-input__field",
        "qf-number-input__btn"
      ],
      "requiredTokens": [
        "--bg-input",
        "--border",
        "--r-lg",
        "--text",
        "--font-mono",
        "--bg-hover",
        "--text-secondary"
      ],
      "a11y": "field 는 input[type=number, inputmode=numeric] 에 label 연결. 증감 버튼에는 aria-label(예: 1 증가/감소)을 두고, 값은 input 이 권위를 가집니다.",
      "exampleHtml": "<div class=\"qf-number-input\"><button class=\"qf-number-input__btn\" aria-label=\"Decrease\">−</button><input class=\"qf-number-input__field\" type=\"number\" value=\"1\" aria-label=\"Quantity\"><button class=\"qf-number-input__btn\" aria-label=\"Increase\">+</button></div>",
      "purposeEn": "A numeric input field with increment and decrement buttons.",
      "a11yEn": "The field is an input[type=\"number\", inputmode=\"numeric\"] with an associated label. Give the stepper buttons an aria-label (e.g. \"Increment\" / \"Decrement\"), and let the input remain the source of truth for the value."
    },
    {
      "name": "FileDrop",
      "class": "qf-file-drop",
      "layer": "generic",
      "category": "Forms",
      "purpose": "파일을 끌어다 놓거나 클릭해 올리는 범용 업로드 영역입니다.",
      "variants": [
        "qf-file-drop--active",
        "qf-file-drop__title"
      ],
      "requiredTokens": [
        "--border-strong",
        "--r-lg",
        "--bg-input",
        "--text-muted",
        "--accent",
        "--accent-bg",
        "--text-strong",
        "--s-8"
      ],
      "a11y": "실제 input[type=file] 을 영역에 연결(label 또는 클릭 위임)해 키보드 접근을 보존합니다. 드래그 상태는 --active 와 함께 텍스트로도 안내합니다.",
      "exampleHtml": "<label class=\"qf-file-drop\"><span class=\"qf-file-drop__title\">Drop files here</span><span>or click to browse</span><input type=\"file\" hidden></label>",
      "purposeEn": "A general-purpose upload zone for dropping files via drag-and-drop or clicking to browse.",
      "a11yEn": "Connect a real input[type=\"file\"] to the zone (via label or click delegation) to preserve keyboard access. Convey the drag state with text in addition to --active."
    },
    {
      "name": "Steps",
      "class": "qf-steps",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "다단계 흐름의 진행 단계를 표시합니다.",
      "variants": [
        "qf-steps__step",
        "qf-steps__marker",
        "qf-steps__bar",
        "qf-steps__step--done"
      ],
      "requiredTokens": [
        "--s-2",
        "--r-pill",
        "--bg-hover",
        "--accent",
        "--text-onAccent",
        "--ok-400",
        "--divider",
        "--text-muted"
      ],
      "a11y": "ol 로 순서를 전달하고 현재 단계에 aria-current=step 을 둡니다. 완료/현재 상태를 색뿐 아니라 마커 숫자, 텍스트로 함께 전달합니다.",
      "exampleHtml": "<ol class=\"qf-steps\"><li class=\"qf-steps__step qf-steps__step--done\"><span class=\"qf-steps__marker\">✓</span>Account</li><li class=\"qf-steps__bar\" aria-hidden=\"true\"></li><li class=\"qf-steps__step\" aria-current=\"step\"><span class=\"qf-steps__marker\">2</span>Profile</li></ol>",
      "purposeEn": "Displays the progress steps of a multi-step flow.",
      "a11yEn": "Convey order with an ol and set aria-current=\"step\" on the current step. Communicate the completed/current state with marker numbers and text, not color alone."
    },
    {
      "name": "List",
      "class": "qf-list",
      "layer": "generic",
      "category": "Display",
      "purpose": "구분선이 있는 범용 세로 목록입니다(설정 행, 항목 리스트).",
      "variants": [
        "qf-list__item",
        "qf-list__text",
        "qf-list__trailing",
        "qf-list__item--interactive"
      ],
      "requiredTokens": [
        "--s-3",
        "--s-4",
        "--divider",
        "--text",
        "--bg-hover",
        "--text-muted",
        "--s-9"
      ],
      "a11y": "ul/li 로 만들고, 상호작용 항목(--interactive)은 a/button 으로 두어 키보드 접근과 포커스 가시를 보존합니다. 상호작용 행은 최소 40px 이고, 행 안의 링크나 버튼(.qf-list__text)은 행 패딩까지 탭 대상으로 가져갑니다(약 37px, WCAG 2.5.8). 링크 안의 내용은 인라인 흐름 그대로 둘 수 있습니다(아이콘 + 텍스트, qf-clamp).",
      "exampleHtml": "<ul class=\"qf-list\"><li class=\"qf-list__item\"><span class=\"qf-list__text\">Notifications</span><span class=\"qf-list__trailing\">On</span></li><li class=\"qf-list__item\"><span class=\"qf-list__text\">Language</span><span class=\"qf-list__trailing\">English</span></li></ul>",
      "purposeEn": "A general-purpose vertical list with dividers (settings rows, item lists).",
      "a11yEn": "Build it with ul/li, and make interactive items (--interactive) an a/button to preserve keyboard access and visible focus. Interactive rows are at least 40px tall, and a link or button inside (.qf-list__text) extends its tap target into the row padding (about 37px, WCAG 2.5.8). Inline content inside the link (icon + text, qf-clamp) keeps flowing as usual."
    },
    {
      "name": "RankList",
      "class": "qf-ranklist",
      "layer": "generic",
      "category": "Display",
      "purpose": "순위 목록입니다(지금 뜨는 이슈 1~5). 번호는 CSS counter 로 그려 마크업에 숫자가 없고(순위가 의미라 번호, 액센트, tabular-nums), 항목 전체가 링크(qf-ranklist__link)이며 제목(qf-ranklist__title, 14px 600, 두 줄 clamp)과 메타(qf-ranklist__meta, 12px muted)로 이루어집니다. hover 틴트, 방문 톤, 포커스 링. 사이드 열에서 쓰지만 1열로 내려가도 그대로 동작합니다.",
      "purposeEn": "A ranked list (e.g. trending issues 1–5). Numbers come from a CSS counter so there is no digit in the markup (rank carries meaning: accent, tabular-nums). Each item is a link (qf-ranklist__link) holding a title (qf-ranklist__title, 14px 600, two-line clamp) and a meta line (qf-ranklist__meta, 12px muted), with hover tint, visited tone and focus ring. Meant for the aside but works unchanged when the layout drops to one column.",
      "variants": [
        "qf-ranklist__item",
        "qf-ranklist__link",
        "qf-ranklist__title",
        "qf-ranklist__meta"
      ],
      "requiredTokens": [
        "--s-7",
        "--s-3",
        "--s-2",
        "--s-1",
        "--r-md",
        "--fs-14",
        "--fs-12",
        "--lh-snug",
        "--accent-text",
        "--text-strong",
        "--text-secondary",
        "--text-muted",
        "--bg-row-hover",
        "--ring-focus",
        "--dur-fast",
        "--ease-standard"
      ],
      "a11y": "ol/li 로 두되 list-style: none 이라 WebKit 에서 목록 의미가 떨어질 수 있으니 ol 에 role=\"list\" 를 둡니다. CSS counter 번호는 ::before 생성 텍스트라 링크 이름에 포함되어 \"1 제목 메타\" 로 읽힙니다(순위가 전달됨). 두 줄 clamp 로 잘린 제목은 title 속성이나 aria-label 로 전체를 줄 수 있습니다. 번호 --accent-text on --bg-app 7.5 / 5.3 — AA.",
      "a11yEn": "Use ol/li, and add role=\"list\" to the ol because list-style: none can drop list semantics in WebKit. The CSS counter number is ::before generated text, so it becomes part of the link name and is read as \"1 title meta\" (the rank is conveyed). For titles cut by the two-line clamp, provide the full text via title or aria-label. Number --accent-text on --bg-app 7.5 / 5.3 — AA.",
      "exampleHtml": "<ol class=\"qf-ranklist\" role=\"list\" style=\"max-width:300px\"><li class=\"qf-ranklist__item\"><a class=\"qf-ranklist__link\" href=\"#r1\"><span class=\"qf-ranklist__title\">Supreme Court admits chief justice filed false expense reports, apologizes</span><span class=\"qf-ranklist__meta\">8 communities, 51 posts</span></a></li><li class=\"qf-ranklist__item\"><a class=\"qf-ranklist__link\" href=\"#r2\"><span class=\"qf-ranklist__title\">Central bank holds rates, signals a cut before year end</span><span class=\"qf-ranklist__meta\">3 communities, 5 posts</span></a></li><li class=\"qf-ranklist__item\"><a class=\"qf-ranklist__link\" href=\"#r3\"><span class=\"qf-ranklist__title\">City council reverses late-night bus cuts</span><span class=\"qf-ranklist__meta\">2 communities, 4 posts</span></a></li></ol>",
      "variantsAsChips": true
    },
    {
      "name": "Story",
      "class": "qf-story",
      "layer": "generic",
      "category": "Display",
      "purpose": "뉴스, 이슈, 블로그 같은 콘텐츠 목록의 한 항목입니다. 분류와 메타(kicker), 제목, 요약, 출처 칩, 4:3 썸네일의 위계를 구성요소로 고정합니다. 항목 전체가 링크(a.qf-story)이거나 제목만 링크일 수 있습니다. --pictured 는 텍스트 1열 + 썸네일 2열(좁은 폭에서는 크기만 줄고 자리는 유지), --lead 는 첫 항목 강조(제목 24px, 썸네일 확대. 좁은 폭에서는 16:9 썸네일이 텍스트 위로), --compact 는 제목과 kicker 만(요약, 출처는 마크업이 있어도 숨김). 방문한 항목은 제목이 한 톤 낮아집니다. 목록은 qf-container--reading(720px) 안에 두는 것을 권합니다(narrow 는 제목과 출처 칩이 넘치고 기본 폭은 요약 행이 깁니다). 항목 안의 출처 칩은 자동으로 한 단계 작아집니다(11px). --skeleton 은 다음 화면을 불러오는 동안 자리를 지키는 카드(빈 슬롯에 막대, 실제 카드와 같은 높이. 실제 카드가 --stretched 면 스켈레톤에도 --stretched 를 붙여 터치 환경의 출처 행 높이를 맞춥니다). --stretched 는 카드가 article 이고 제목만 링크일 때 제목 링크를 카드 전체로 늘려, 카드 안의 출처 칩(qf-source--link)과 태그 링크(a.qf-tag)를 눌리는 링크로 둘 수 있게 합니다 — 출처는 qf-source, 분류·주제는 qf-tag.",
      "purposeEn": "One item of a content list such as news, issues, or a blog. It fixes the hierarchy of kicker (category + meta), title, summary, source chips, and a 4:3 thumbnail. The whole item may be the link (a.qf-story) or only the title. --pictured places text in column 1 and the thumbnail in column 2 (on narrow screens it only shrinks, it does not move); --lead emphasizes the first item (24px title, wider thumbnail; on narrow screens a 16:9 thumbnail sits above the text); --compact shows only kicker and title (summary and sources are hidden even if present). Visited items get a quieter title. Place the list inside qf-container--reading (720px): narrow overflows titles and source chips, the default width makes summary lines too long. Source chips inside an item are automatically one step smaller (11px). --skeleton is a placeholder card shown while the next page loads (bars in the empty slots, same height as a real card; if real cards are --stretched, add --stretched to the skeleton too so the sources row matches on touch devices). --stretched stretches the title link over the whole card when the card is an article with only the title linked, so source chips (qf-source--link) and tag links (a.qf-tag) inside can be real links — sources use qf-source, categories and topics use qf-tag.",
      "variants": [
        "qf-story__kicker",
        "qf-story__title",
        "qf-story__summary",
        "qf-story__sources",
        "qf-story__media",
        "qf-story--pictured",
        "qf-story--lead",
        "qf-story--compact",
        "qf-story--skeleton",
        "qf-story--stretched"
      ],
      "requiredTokens": [
        "--text-strong",
        "--text-secondary",
        "--text-muted",
        "--accent-text",
        "--bg-row-hover",
        "--bg-media",
        "--aspect-4x3",
        "--aspect-16x9",
        "--sz-story-media",
        "--sz-story-media-sm",
        "--sz-story-media-lead",
        "--fs-18",
        "--fs-24",
        "--fs-14",
        "--r-md",
        "--s-5",
        "--ring-focus",
        "--fs-12",
        "--fs-15",
        "--s-1",
        "--s-2",
        "--s-3",
        "--s-4",
        "--lh-snug",
        "--lh-tight",
        "--lh-normal",
        "--tracking-tight",
        "--dur-fast",
        "--ease-standard",
        "--border",
        "--bg-hover",
        "--bg-selected",
        "--r-xs",
        "--r-pill",
        "--m-touch"
      ],
      "a11y": "제목은 h2/h3 로 두어 목록의 문서 구조를 전달합니다. 항목 전체를 링크로 만들 때는 안에 다른 링크(출처 칩 --link 등)를 넣지 않습니다(중첩 링크 금지). 장식 썸네일은 alt=\"\" 로 두고 width/height 를 적어 레이아웃 점프를 막습니다. 포커스는 항목 전체에 링으로 보입니다. 방문 톤은 보조 단서일 뿐 의미를 색에만 싣지 않습니다. 항목 전체가 링크면 kicker, 제목, 요약, 출처가 모두 링크 이름이 되어 길어집니다. 요약이 길면 제목만 링크로 두는 형태(Text only 예제)를 고려하세요. 링크 이름을 제목만으로 줄이려면 a.qf-story 에 aria-labelledby=\"제목 id\" 를 둘 수 있습니다. --stretched 에서는 카드 안 텍스트를 드래그로 선택할 수 없고 우클릭 메뉴가 제목 링크의 것입니다 — 그 대신 링크 이름이 제목만으로 짧습니다. 스켈레톤은 aria-hidden=\"true\" 로 두고 실제 카드가 오면 통째로 바꿉니다.",
      "a11yEn": "Use h2/h3 for the title to convey list structure. When the whole item is a link, do not nest other links inside it (no source --link chips). Give decorative thumbnails alt=\"\" and width/height to avoid layout shift. Focus is shown as a ring around the whole item. The visited tone is a secondary cue; meaning never relies on color alone. When the whole item is a link, the kicker, title, summary, and sources all become the link name and it gets long; if summaries are long, consider linking only the title (see the Text only example). To shorten the link name to the title only, aria-labelledby=\"<title id>\" can be set on a.qf-story. With --stretched, text inside the card cannot be selected by dragging and the context menu is the title link's — in return the link name is just the title. Give skeletons aria-hidden=\"true\" and replace them wholesale when real cards arrive.",
      "variantsAsChips": true,
      "wideExamples": true,
      "exampleHtml": "<a class=\"qf-story qf-story--pictured\" href=\"#story\"><img class=\"qf-story__media\" src=\"brand-assets/png/og-image.png\" alt=\"\" width=\"180\" height=\"135\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Politics</span><span>4 communities · 9 posts · 9 min ago</span></div><h2 class=\"qf-story__title\">Supreme Court admits chief justice filed false expense reports, apologizes</h2><p class=\"qf-story__summary qf-clamp qf-clamp--3\">Reporting by an investigative outlet traced dozens of receipts to restaurants that were closed on the dates listed. The court issued a formal apology and said a corrected ledger would follow.</p><div class=\"qf-sources qf-story__sources\"><span class=\"qf-source\">82cook</span><span class=\"qf-source\">Inven</span><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Bobaedream</span></div></a>",
      "examples": [
        {
          "label": "--pictured (default thumbnail, right column)",
          "html": "<a class=\"qf-story qf-story--pictured\" href=\"#story\"><img class=\"qf-story__media\" src=\"brand-assets/png/og-image.png\" alt=\"\" width=\"180\" height=\"135\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Politics</span><span>4 communities · 9 posts · 9 min ago</span></div><h2 class=\"qf-story__title\">Supreme Court admits chief justice filed false expense reports, apologizes</h2><p class=\"qf-story__summary qf-clamp qf-clamp--3\">Reporting by an investigative outlet traced dozens of receipts to restaurants that were closed on the dates listed. The court issued a formal apology and said a corrected ledger would follow.</p><div class=\"qf-sources qf-story__sources\"><span class=\"qf-source\">82cook</span><span class=\"qf-source\">Inven</span><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Bobaedream</span></div></a>"
        },
        {
          "label": "--lead --pictured (first item, larger)",
          "html": "<a class=\"qf-story qf-story--pictured qf-story--lead\" href=\"#story\"><img class=\"qf-story__media\" src=\"brand-assets/png/og-image.png\" alt=\"\" width=\"280\" height=\"210\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Politics</span><span>4 communities · 9 posts · 9 min ago</span></div><h2 class=\"qf-story__title\">Supreme Court admits chief justice filed false expense reports, apologizes</h2><p class=\"qf-story__summary qf-clamp qf-clamp--3\">Reporting by an investigative outlet traced dozens of receipts to restaurants that were closed on the dates listed. The court issued a formal apology and said a corrected ledger would follow.</p><div class=\"qf-sources qf-story__sources\"><span class=\"qf-source\">82cook</span><span class=\"qf-source\">Inven</span><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Bobaedream</span></div></a>"
        },
        {
          "label": "Text only, title is the link",
          "html": "<article class=\"qf-story\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Economy</span><span>3 communities · 5 posts · 2 h ago</span></div><h2 class=\"qf-story__title\"><a href=\"#story\">Central bank holds rates, signals a cut before year end</a></h2><p class=\"qf-story__summary\">The decision was unanimous. Forum threads focused on what the pause means for mortgage rates.</p><div class=\"qf-sources qf-story__sources\"><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Ppomppu</span><span class=\"qf-source\">Ruliweb</span></div></article>"
        },
        {
          "label": "--compact (kicker + title)",
          "html": "<a class=\"qf-story qf-story--compact\" href=\"#story\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Sports</span><span>2 communities · 3 posts · 30 min ago</span></div><h3 class=\"qf-story__title\">National team announces roster for the qualifiers</h3></a>"
        },
        {
          "label": "List with day rules (qf-stack + qf-dayrule)",
          "html": "<ol class=\"qf-stack qf-stack--tight\" style=\"list-style:none;margin:0;padding:0\"><li class=\"qf-dayrule qf-dayrule--sticky\" role=\"separator\" aria-label=\"Today, Wednesday, Sep 30\"><span class=\"qf-dayrule__label\">Today · Wednesday, Sep 30</span></li><li><a class=\"qf-story qf-story--pictured qf-story--lead\" href=\"#story\"><img class=\"qf-story__media\" src=\"brand-assets/png/og-image.png\" alt=\"\" width=\"180\" height=\"135\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Politics</span><span>4 communities · 9 posts · 9 min ago</span></div><h2 class=\"qf-story__title\">Supreme Court admits chief justice filed false expense reports, apologizes</h2><p class=\"qf-story__summary qf-clamp qf-clamp--3\">Reporting by an investigative outlet traced dozens of receipts to restaurants that were closed on the dates listed. The court issued a formal apology and said a corrected ledger would follow.</p><div class=\"qf-sources qf-story__sources\"><span class=\"qf-source\">82cook</span><span class=\"qf-source\">Inven</span><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Bobaedream</span></div></a></li><li><article class=\"qf-story\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Economy</span><span>3 communities · 5 posts · 2 h ago</span></div><h2 class=\"qf-story__title\"><a href=\"#story\">Central bank holds rates, signals a cut before year end</a></h2><p class=\"qf-story__summary\">The decision was unanimous. Forum threads focused on what the pause means for mortgage rates.</p><div class=\"qf-sources qf-story__sources\"><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Ppomppu</span><span class=\"qf-source\">Ruliweb</span></div></article></li><li><a class=\"qf-story qf-story--compact\" href=\"#story\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Sports</span><span>2 communities · 3 posts · 30 min ago</span></div><h3 class=\"qf-story__title\">National team announces roster for the qualifiers</h3></a></li><li class=\"qf-dayrule qf-dayrule--sticky\" role=\"separator\" aria-label=\"Yesterday, Tuesday, Sep 29\"><span class=\"qf-dayrule__label\">Yesterday · Tuesday, Sep 29</span></li><li><a class=\"qf-story qf-story--pictured\" href=\"#story\"><img class=\"qf-story__media\" src=\"brand-assets/png/og-image.png\" alt=\"\" width=\"180\" height=\"135\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Society</span><span>4 communities · 9 posts · 9 min ago</span></div><h2 class=\"qf-story__title\">City council reverses late-night bus cuts after a week of complaints</h2><p class=\"qf-story__summary qf-clamp qf-clamp--3\">Reporting by an investigative outlet traced dozens of receipts to restaurants that were closed on the dates listed. The court issued a formal apology and said a corrected ledger would follow.</p><div class=\"qf-sources qf-story__sources\"><span class=\"qf-source\">82cook</span><span class=\"qf-source\">Inven</span><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Bobaedream</span></div></a></li></ol>"
        },
        {
          "label": "--pictured without summary/sources (spacer rows keep kicker↔title tight)",
          "html": "<a class=\"qf-story qf-story--pictured\" href=\"#story\"><img class=\"qf-story__media\" src=\"brand-assets/png/og-image.png\" alt=\"\" width=\"180\" height=\"135\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Culture</span><span>2 communities · 4 posts · 1 h ago</span></div><h2 class=\"qf-story__title\">Film festival announces its opening title</h2></a>"
        },
        {
          "label": "--skeleton (loading placeholder)",
          "html": "<div class=\"qf-story qf-story--pictured qf-story--skeleton\" aria-hidden=\"true\"><div class=\"qf-story__media\"></div><div class=\"qf-story__kicker\"></div><div class=\"qf-story__title\"></div><div class=\"qf-story__summary\"></div><div class=\"qf-story__sources\"></div></div>"
        },
        {
          "label": "--stretched (article, title link stretched, source chips are links)",
          "html": "<article class=\"qf-story qf-story--stretched\"><div class=\"qf-story__kicker\"><span class=\"qf-eyebrow\">Economy</span><span>3 communities · 5 posts · 2 h ago</span></div><h2 class=\"qf-story__title\"><a href=\"#story\">Central bank holds rates, signals a cut before year end</a></h2><p class=\"qf-story__summary\">The decision was unanimous. Forum threads focused on what the pause means for mortgage rates.</p><div class=\"qf-sources qf-story__sources\"><a class=\"qf-source qf-source--link\" href=\"#site-clien\">Clien</a><a class=\"qf-source qf-source--link\" href=\"#site-ppomppu\">Ppomppu</a><a class=\"qf-source qf-source--link\" href=\"#site-ruliweb\">Ruliweb</a></div></article>"
        }
      ]
    },
    {
      "name": "Source",
      "class": "qf-source",
      "layer": "generic",
      "category": "Display",
      "purpose": "출처(커뮤니티, 매체) 이름 하나를 나타내는 중립 톤의 작은 칩입니다. 태그(라벨)나 배지(상태, 알림)와 구분되는 outline pill 입니다. qf-source__count 는 회색 숫자로 개수를(빨간 count 배지 대신), qf-source__mark 는 이니셜이나 파비콘을 넣습니다. --link 는 눌러서 원문 목록으로 가는 링크형, qf-sources 는 줄바꿈되는 묶음 컨테이너입니다. qf-story__sources 와 qf-timeline__body 안에서는 보조 정보라 자동으로 한 단계 작아집니다(11px, 본문 줄 높이와 같음).",
      "purposeEn": "A small, neutral chip naming one source (a community or outlet). An outline pill, distinct from Tag (labels) and Badge (status, notifications). qf-source__count shows a count as a gray number (instead of a red count badge); qf-source__mark holds an initial or favicon. --link makes it a link to the original list; qf-sources is the wrapping group container. Inside qf-story__sources and qf-timeline__body they are secondary information and shrink automatically by one step (11px, matching the body line height).",
      "variants": [
        "qf-sources",
        "qf-source__count",
        "qf-source__mark",
        "qf-source--link"
      ],
      "requiredTokens": [
        "--border",
        "--border-strong",
        "--text-secondary",
        "--text-muted",
        "--text",
        "--r-pill",
        "--fs-12",
        "--fs-11",
        "--s-2",
        "--s-3",
        "--s-5",
        "--m-touch",
        "--ring-focus",
        "--bg-selected",
        "--bg-hover",
        "--s-4",
        "--lh-snug",
        "--dur-fast",
        "--ease-standard"
      ],
      "a11y": "개수는 회색 숫자라 경고로 읽히지 않습니다. 필요하면 aria-label 로 뜻을 보충합니다(예: 보배드림 글 2건). --link 는 a 로 두고 포커스 링이 보입니다. 터치 환경(any-pointer: coarse, 터치 노트북 포함)에서는 --link 칩만 44px 로 커지고, 정적 칩과 카드 안 칩은 그대로입니다. 항목 전체가 링크인 qf-story 안에서는 --link 를 쓰지 않습니다.",
      "a11yEn": "Counts are gray numbers, so they do not read as warnings; add an aria-label when needed (e.g. \"2 posts on Bobaedream\"). --link is an anchor with a visible focus ring. On touch devices (any-pointer: coarse, including touch laptops) only --link chips grow to 44px; static chips and chips inside a linked qf-story stay as they are. Do not use --link inside a qf-story that is itself a link.",
      "exampleHtml": "<div class=\"qf-sources\"><span class=\"qf-source\">82cook</span><span class=\"qf-source\">Inven <span class=\"qf-source__count\">3</span></span><a class=\"qf-source qf-source--link\" href=\"#source\"><span class=\"qf-source__mark\" aria-hidden=\"true\">C</span>Clien <span class=\"qf-source__count\">2</span></a></div>",
      "examples": [
        {
          "label": "Plain",
          "html": "<span class=\"qf-source\">Bobaedream</span>"
        },
        {
          "label": "With count (gray, not red)",
          "html": "<span class=\"qf-source\">Bobaedream <span class=\"qf-source__count\">2</span></span>"
        },
        {
          "label": "With mark (initial)",
          "html": "<span class=\"qf-source\"><span class=\"qf-source__mark\" aria-hidden=\"true\">R</span>Ruliweb</span>"
        },
        {
          "label": "--link",
          "html": "<a class=\"qf-source qf-source--link\" href=\"#source\">Clien <span class=\"qf-source__count\">4</span></a>"
        },
        {
          "label": "Group (qf-sources, wraps)",
          "html": "<div class=\"qf-sources\"><span class=\"qf-source\">82cook</span><span class=\"qf-source\">Inven</span><span class=\"qf-source\">Clien</span><span class=\"qf-source\">Bobaedream</span><span class=\"qf-source\">Ppomppu</span><span class=\"qf-source\">Ruliweb</span><span class=\"qf-source\">DC Inside</span></div>"
        }
      ]
    },
    {
      "name": "Chip",
      "class": "qf-chip",
      "layer": "generic",
      "category": "Navigation",
      "purpose": "데스크톱 필터 칩과 그 묶음(qf-chips)입니다. 링크(a + aria-current=\"page\")와 버튼(button + aria-pressed=\"true\") 모두 지원하며 선택 상태는 accent-subtle 배경, accent 테두리, 굵은 라벨로 표시합니다. 기본은 줄바꿈(wrap), --scroll 은 좁은 폭에서 한 줄 가로 스크롤(스크롤바 숨김)입니다. 모바일 qf-m-filter-chip 과 같은 토큰(--r-pill, --accent-subtle, --accent)을 써서 시각이 일관되며, 라벨 색만 AA 를 위해 --accent-on-subtle 입니다. --scroll 은 오른쪽 끝을 배경으로 녹아들게 페이드해 더 있음을 암시하고, 넘칠 때만 생기며 끝까지 스크롤하면 걷힙니다(스크롤 타임라인 지원 브라우저는 자동, 그 밖은 소비처 JS 가 is-end 를 붙임). 터치 환경에서 칩은 40px 이고 투명 영역으로 44px 히트를 확보합니다. 같은 페이드를 qf-picks(이미지 선택 타일 줄)도 씁니다.",
      "purposeEn": "Desktop filter chips and their group (qf-chips). Works as links (a + aria-current=\"page\") or buttons (button + aria-pressed=\"true\"); the selected state uses an accent-subtle background, accent border, and a bold label. The group wraps by default; --scroll makes a single horizontally scrolling row on narrow screens (scrollbar hidden). Shares tokens with the mobile qf-m-filter-chip (--r-pill, --accent-subtle, --accent) for visual consistency; only the label color is --accent-on-subtle for AA. --scroll fades its right edge into the background to hint there is more, only when the row overflows, and the fade clears at the end (automatically where scroll-driven animations are supported; otherwise consumer JS adds is-end). On touch devices chips are 40px tall with a transparent 44px hit area. qf-picks (image pick rows) shares the same fade.",
      "variants": [
        "qf-chips",
        "qf-chips--scroll",
        "qf-chip__count"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--bg-hover",
        "--border",
        "--border-strong",
        "--text-secondary",
        "--text",
        "--text-muted",
        "--accent-subtle",
        "--accent",
        "--accent-on-subtle",
        "--r-pill",
        "--fs-13",
        "--s-2",
        "--s-3",
        "--s-4",
        "--m-touch",
        "--ring-focus",
        "--s-1",
        "--lh-tight",
        "--dur-fast",
        "--ease-standard",
        "--s-9",
        "--s-10"
      ],
      "a11y": "링크 칩은 nav[aria-label] 안의 a 로 두고 현재 항목에 aria-current=\"page\" 를, 버튼 칩은 aria-pressed 를 씁니다. 선택 상태는 색과 함께 테두리와 굵기로도 전달합니다. 포커스 링이 보이고(--scroll 은 halo 까지 보이도록 4px 여백을 안쪽에 두고 같은 크기의 음수 margin 으로 정렬을 유지하므로, 부모에 4px 이상 좌우 여백이 필요합니다), 터치 환경(any-pointer: coarse, 터치 노트북 포함)에서는 칩이 40px 이고 투명 영역으로 44px 히트를 확보합니다(인접 행의 히트 영역은 맞닿기만 함). --scroll 은 키보드로 Tab 이동 시 브라우저가 칩을 뷰로 스크롤합니다. --scroll 의 페이드는 넘칠 때만 생기고(스크롤 타임라인 지원 시), Tab 으로 칩에 가면 scroll-padding 으로 페이드 왼쪽에 멈춥니다. 미지원 브라우저는 소비처 JS 가 load·resize·scroll 에서 끝에 닿았거나 넘치지 않으면 is-end 를 붙입니다.",
      "a11yEn": "Link chips are anchors inside nav[aria-label] with aria-current=\"page\" on the current one; button chips use aria-pressed. The selected state is conveyed by border and weight as well as color. The focus ring stays visible (--scroll keeps a 4px inner padding for the halo and a matching negative margin to stay aligned, so the parent needs at least 4px of horizontal room), and on touch devices (any-pointer: coarse, including touch laptops) chips are 40px tall with a transparent 44px hit area (hit areas of adjacent rows touch but do not overlap). In --scroll, tabbing brings each chip into view. The --scroll fade appears only when the row overflows (with scroll-driven animation support), and tabbing to a chip stops left of the fade via scroll-padding. Without support, consumer JS adds is-end on load, resize and scroll when the row is at its end or does not overflow.",
      "variantsAsChips": true,
      "wideExamples": true,
      "exampleHtml": "<nav class=\"qf-chips\" aria-label=\"Category\"><a class=\"qf-chip\" href=\"#all\" aria-current=\"page\">All</a><a class=\"qf-chip\" href=\"#politics\">Politics</a><a class=\"qf-chip\" href=\"#society\">Society</a><a class=\"qf-chip\" href=\"#economy\">Economy</a><a class=\"qf-chip\" href=\"#culture\">Culture</a><a class=\"qf-chip\" href=\"#sports\">Sports</a><a class=\"qf-chip\" href=\"#tech\">Tech</a><a class=\"qf-chip\" href=\"#world\">World</a></nav>",
      "examples": [
        {
          "label": "Links (aria-current=\"page\")",
          "html": "<nav class=\"qf-chips\" aria-label=\"Category\"><a class=\"qf-chip\" href=\"#all\" aria-current=\"page\">All</a><a class=\"qf-chip\" href=\"#politics\">Politics</a><a class=\"qf-chip\" href=\"#society\">Society</a><a class=\"qf-chip\" href=\"#economy\">Economy</a><a class=\"qf-chip\" href=\"#culture\">Culture</a><a class=\"qf-chip\" href=\"#sports\">Sports</a><a class=\"qf-chip\" href=\"#tech\">Tech</a><a class=\"qf-chip\" href=\"#world\">World</a></nav>"
        },
        {
          "label": "Buttons (aria-pressed) with counts",
          "html": "<div class=\"qf-chips\" role=\"group\" aria-label=\"Sort\"><button class=\"qf-chip\" type=\"button\" aria-pressed=\"true\">Latest <span class=\"qf-chip__count\">128</span></button><button class=\"qf-chip\" type=\"button\" aria-pressed=\"false\">Trending <span class=\"qf-chip__count\">32</span></button><button class=\"qf-chip\" type=\"button\" aria-pressed=\"false\">Pictured <span class=\"qf-chip__count\">57</span></button></div>"
        },
        {
          "label": "--scroll (single row, scrolls sideways)",
          "html": "<div style=\"max-width:360px\"><nav class=\"qf-chips qf-chips--scroll\" aria-label=\"Category\"><a class=\"qf-chip\" href=\"#all\" aria-current=\"page\">All</a><a class=\"qf-chip\" href=\"#politics\">Politics</a><a class=\"qf-chip\" href=\"#society\">Society</a><a class=\"qf-chip\" href=\"#economy\">Economy</a><a class=\"qf-chip\" href=\"#culture\">Culture</a><a class=\"qf-chip\" href=\"#sports\">Sports</a><a class=\"qf-chip\" href=\"#tech\">Tech</a><a class=\"qf-chip\" href=\"#world\">World</a></nav></div>"
        }
      ]
    },
    {
      "name": "DayRule",
      "class": "qf-dayrule",
      "layer": "generic",
      "category": "Display",
      "purpose": "목록 사이에 끼우는 날짜, 구간 구분줄입니다. eyebrow 톤 라벨과 선으로 이루어지며 li 로 씁니다(role=\"separator\" 를 쓰면 aria-label 로 라벨을 함께 줍니다). --sticky 는 스크롤 시 상단에 붙고 앱 배경(--bg-app)을 깔아 아래 항목이 비치지 않습니다. 노브: --qf-dayrule-top(고정 헤더 아래 오프셋, 기본 0. z-index 가 --z-sticky 라 고정 헤더가 있으면 반드시 헤더 높이, 예: var(--h-navbar), 를 지정), --qf-dayrule-bg(패널 안에서 쓸 때 배경). sticky 구분줄이 포커스된 항목의 윗부분을 가리지 않게 항목에 scroll-margin-top 을 주는 것을 권합니다. 고정 상단 바(qf-navbar--sticky)가 is-hidden 으로 숨으면 날짜줄은 자동으로 맨 위(top: 0)로 올라붙습니다.",
      "purposeEn": "A date or section rule placed between list items: an eyebrow-toned label plus a line, used as an li (with role=\"separator\", also provide the label via aria-label). --sticky pins it to the top while scrolling and paints the app background (--bg-app) so items do not show through. Knobs: --qf-dayrule-top (offset under a fixed header, default 0; the rule sits at --z-sticky, so with a fixed header you must set this to the header height, e.g. var(--h-navbar)) and --qf-dayrule-bg (background when used inside a panel). Give items a scroll-margin-top so a sticky rule does not cover the top of a focused item. When a sticky top bar (qf-navbar--sticky) hides with is-hidden, the rule automatically moves up to top: 0.",
      "variants": [
        "qf-dayrule__label",
        "qf-dayrule--sticky"
      ],
      "requiredTokens": [
        "--accent-text",
        "--text-muted",
        "--bg-app",
        "--z-sticky",
        "--fs-12",
        "--tracking-caps",
        "--s-3",
        "--s-4",
        "--lh-tight",
        "--border",
        "--dur-base",
        "--ease-standard"
      ],
      "a11y": "ARIA 의 separator 는 자식 요소가 presentational 이라 안의 텍스트가 보조기기에 노출되지 않습니다. role=\"separator\" 를 쓰려면 같은 텍스트를 aria-label 로 반드시 함께 두고(예: aria-label=\"오늘, 9월 30일 수요일\"), 항목 수에 섞여도 괜찮으면 role 없이 일반 li 로 두어 라벨이 그대로 낭독되게 합니다. 라벨은 실제 텍스트로 두고 대문자 변환은 시각 표현입니다. --sticky 의 배경은 아래 텍스트가 겹쳐 보이지 않게 불투명해야 하므로 반투명 값을 넣지 않습니다.",
      "a11yEn": "In ARIA, separator has presentational children, so text inside it is not exposed to assistive technology. If you use role=\"separator\", always duplicate the label in aria-label (e.g. aria-label=\"Today, Wednesday, Sep 30\"); if counting it among the items is acceptable, use a plain li without the role so the label is read as-is. Keep the label as real text; the uppercase transform is visual only. The --sticky background must stay opaque so text does not show through; do not set a translucent value.",
      "exampleHtml": "<ul class=\"qf-stack qf-stack--tight\" style=\"list-style:none;margin:0;padding:0;width:100%\"><li class=\"qf-dayrule\" role=\"separator\" aria-label=\"Today, Wednesday, Sep 30\"><span class=\"qf-dayrule__label\">Today · Wednesday, Sep 30</span></li><li class=\"qf-list__item\" style=\"padding-inline:0\">First item of the day</li><li class=\"qf-list__item\" style=\"padding-inline:0\">Second item</li><li class=\"qf-dayrule\" role=\"separator\" aria-label=\"Yesterday, Tuesday, Sep 29\"><span class=\"qf-dayrule__label\">Yesterday · Tuesday, Sep 29</span></li><li class=\"qf-list__item\" style=\"padding-inline:0\">An older item</li></ul>",
      "wideExamples": true
    },
    {
      "name": "Timeline",
      "class": "qf-timeline",
      "layer": "generic",
      "category": "Display",
      "purpose": "시간순 기록을 세로로 보여 주는 타임라인입니다(진행 단계를 나타내는 qf-steps 와 다릅니다). 항목은 시각(qf-timeline__time), 마커(qf-timeline__marker), 본문(qf-timeline__body: 출처 칩 + 제목 링크 + 메타)로 이루어지고, 날짜가 바뀌는 곳에 qf-timeline__day 를 둡니다. 마커 색은 커스텀 프로퍼티 --qf-timeline-marker 하나로 열어 두어 소비처가 토큰(예: var(--viz-2))으로 출처를 구분합니다(기본 --text-muted). 좁은 폭에서도 시각 칸(--sz-timeline-time)이 유지되며, 더 좁게 써야 하면 --stacked 로 시각을 본문 위로 올립니다.",
      "purposeEn": "A vertical timeline of events in time order (unlike qf-steps, which shows progress through stages). Each item has a time (qf-timeline__time), a marker (qf-timeline__marker), and a body (qf-timeline__body: source chip + title link + meta); qf-timeline__day marks a date change. The marker color is exposed through one custom property, --qf-timeline-marker, which consumers set with a token (e.g. var(--viz-2)) to tell sources apart (default --text-muted). The time column (--sz-timeline-time) survives narrow widths; for very narrow layouts, --stacked moves the time above the body.",
      "variants": [
        "qf-timeline__item",
        "qf-timeline__time",
        "qf-timeline__marker",
        "qf-timeline__body",
        "qf-timeline__meta",
        "qf-timeline__day",
        "qf-timeline--stacked"
      ],
      "requiredTokens": [
        "--text",
        "--text-strong",
        "--text-secondary",
        "--text-muted",
        "--accent-text",
        "--sz-timeline-time",
        "--sz-timeline-marker",
        "--fs-14",
        "--fs-12",
        "--lh-normal",
        "--r-pill",
        "--s-3",
        "--s-4",
        "--border-strong",
        "--s-1",
        "--s-2",
        "--tracking-caps",
        "--lh-tight",
        "--border"
      ],
      "a11y": "ol 로 순서를 전달하고 시각은 time[datetime] 으로 둡니다. 마커는 장식(aria-hidden)이며 출처는 색이 아니라 qf-source 텍스트로 전달합니다. 제목은 qf-link 로 두어 포커스 링이 보이고, 같은 줄의 메타 텍스트와 구분되게 옅은 밑줄로 링크 단서를 함께 줍니다. 방문한 링크는 한 톤 낮아집니다. 날짜 구분(qf-timeline__day)은 li 텍스트로 낭독됩니다.",
      "a11yEn": "Convey order with an ol and mark times with time[datetime]. Markers are decorative (aria-hidden); the source is conveyed by the qf-source text, not by color. Titles are qf-link anchors with a visible focus ring and a light underline so they read as links next to the meta text on the same line, and visited links are toned down. Date dividers (qf-timeline__day) are read as list item text.",
      "wideExamples": true,
      "exampleHtml": "<ol class=\"qf-timeline\"><li class=\"qf-timeline__day\">Sep 30</li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-1)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T09:12\">09:12</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Clien</span> <a class=\"qf-link\" href=\"#post\">The chief justice's expense reports do not add up</a> <span class=\"qf-timeline__meta\">4 comments</span></div></li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-2)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T09:40\">09:40</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">82cook</span> <a class=\"qf-link\" href=\"#post\">So the Supreme Court apologized. Now what?</a> <span class=\"qf-timeline__meta\">31 comments</span></div></li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-3)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T11:05\">11:05</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Bobaedream</span> <a class=\"qf-link\" href=\"#post\">Court to publish a corrected ledger, refers case to outside review</a> <span class=\"qf-timeline__meta\">12 comments</span></div></li><li class=\"qf-timeline__day\">Sep 29</li><li class=\"qf-timeline__item\"><time class=\"qf-timeline__time\" datetime=\"2026-09-29T22:48\">22:48</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Inven</span> <a class=\"qf-link\" href=\"#post\">First report surfaces on the expense records</a> <span class=\"qf-timeline__meta\">2 comments</span></div></li></ol>",
      "examples": [
        {
          "label": "Default (time column)",
          "html": "<ol class=\"qf-timeline\"><li class=\"qf-timeline__day\">Sep 30</li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-1)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T09:12\">09:12</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Clien</span> <a class=\"qf-link\" href=\"#post\">The chief justice's expense reports do not add up</a> <span class=\"qf-timeline__meta\">4 comments</span></div></li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-2)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T09:40\">09:40</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">82cook</span> <a class=\"qf-link\" href=\"#post\">So the Supreme Court apologized. Now what?</a> <span class=\"qf-timeline__meta\">31 comments</span></div></li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-3)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T11:05\">11:05</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Bobaedream</span> <a class=\"qf-link\" href=\"#post\">Court to publish a corrected ledger, refers case to outside review</a> <span class=\"qf-timeline__meta\">12 comments</span></div></li><li class=\"qf-timeline__day\">Sep 29</li><li class=\"qf-timeline__item\"><time class=\"qf-timeline__time\" datetime=\"2026-09-29T22:48\">22:48</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Inven</span> <a class=\"qf-link\" href=\"#post\">First report surfaces on the expense records</a> <span class=\"qf-timeline__meta\">2 comments</span></div></li></ol>"
        },
        {
          "label": "--stacked (time above body)",
          "html": "<div style=\"max-width:320px\"><ol class=\"qf-timeline qf-timeline--stacked\"><li class=\"qf-timeline__day\">Sep 30</li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-1)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T09:12\">09:12</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Clien</span> <a class=\"qf-link\" href=\"#post\">The chief justice's expense reports do not add up</a> <span class=\"qf-timeline__meta\">4 comments</span></div></li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-2)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T09:40\">09:40</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">82cook</span> <a class=\"qf-link\" href=\"#post\">So the Supreme Court apologized. Now what?</a> <span class=\"qf-timeline__meta\">31 comments</span></div></li><li class=\"qf-timeline__item\" style=\"--qf-timeline-marker: var(--viz-3)\"><time class=\"qf-timeline__time\" datetime=\"2026-09-30T11:05\">11:05</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Bobaedream</span> <a class=\"qf-link\" href=\"#post\">Court to publish a corrected ledger, refers case to outside review</a> <span class=\"qf-timeline__meta\">12 comments</span></div></li><li class=\"qf-timeline__day\">Sep 29</li><li class=\"qf-timeline__item\"><time class=\"qf-timeline__time\" datetime=\"2026-09-29T22:48\">22:48</time><span class=\"qf-timeline__marker\" aria-hidden=\"true\"></span><div class=\"qf-timeline__body\"><span class=\"qf-source\">Inven</span> <a class=\"qf-link\" href=\"#post\">First report surfaces on the expense records</a> <span class=\"qf-timeline__meta\">2 comments</span></div></li></ol></div>"
        }
      ]
    },
    {
      "name": "Message",
      "class": "qf-message",
      "layer": "chat",
      "category": "Chat",
      "purpose": "아바타, 작성자, 본문을 한 행으로 묶는 채팅 메시지 행입니다.",
      "variants": [
        "qf-message--head",
        "qf-message--cont",
        "qf-message--mention",
        "qf-message__avatar",
        "qf-message__meta",
        "qf-message__author",
        "qf-message__time",
        "qf-message__gutter-time",
        "qf-message__body",
        "qf-message__toolbar"
      ],
      "requiredTokens": [
        "--s-2",
        "--s-7",
        "--s-8",
        "--s-4",
        "--mention-bg",
        "--accent",
        "--text",
        "--text-strong"
      ],
      "a11y": "mention 행은 색상 외에 좌측 accent 바와 텍스트로도 멘션을 전달해 색 단독 의존을 피합니다. 작성자, 시간 대비는 AA 4.5:1을 지킵니다. 본문 링크는 평상시 밑줄이 없고 링크색만으로는 본문과 대비가 3:1 미만이라(WCAG 1.4.1), 소비처에서 링크에 상시 밑줄을 주거나 대비 3:1 이상 링크색을 쓰기를 권장합니다.",
      "exampleHtml": "<article class=\"qf-message qf-message--head\"><img class=\"qf-message__avatar\" src=\"u.png\" alt=\"James Carter\"><div class=\"qf-message__meta\"><span class=\"qf-message__author\">James Carter</span><time class=\"qf-message__time\">2:30 PM</time></div><div class=\"qf-message__body\">Hey there!</div></article>",
      "purposeEn": "A chat message row that groups the avatar, author, and body into a single line.",
      "a11yEn": "Mention rows convey the mention with a left accent bar and text in addition to color, avoiding reliance on color alone. The author and timestamp meet AA 4.5:1 contrast. Body links are not underlined by default, and link color alone has less than 3:1 contrast against the body text (WCAG 1.4.1), so consumers are encouraged to give links a persistent underline or to use a link color with at least 3:1 contrast."
    },
    {
      "name": "MessageQuickReact",
      "class": "qf-msg-quickreact",
      "layer": "chat",
      "category": "Chat",
      "purpose": "메시지 hover 툴바 안에서 자주 쓰는 이모지 반응과 더보기를 한 줄로 제공합니다.",
      "variants": [
        "qf-msg-quickreact__slot",
        "qf-msg-quickreact__more"
      ],
      "requiredTokens": [
        "--r-sm",
        "--fs-15",
        "--text-secondary",
        "--text-muted",
        "--bg-hover",
        "--bg-selected",
        "--accent",
        "--dur-fast"
      ],
      "a11y": "각 슬롯은 button 요소로 키보드 포커스와 활성화가 가능하며, 이모지에는 aria-label로 의미를 텍스트로 전달합니다. 더보기 슬롯에도 aria-label을 부여합니다.",
      "exampleHtml": "<div class=\"qf-msg-quickreact\"><button class=\"qf-msg-quickreact__slot\" aria-label=\"Like reaction\">👍</button><button class=\"qf-msg-quickreact__slot qf-msg-quickreact__more\" aria-label=\"More reactions\">+</button></div>",
      "purposeEn": "Offers frequently used emoji reactions plus a \"more\" affordance in a single row inside the message hover toolbar.",
      "a11yEn": "Each slot is a button element so it can receive keyboard focus and be activated, and each emoji conveys its meaning as text via aria-label. Give the \"more\" slot an aria-label as well."
    },
    {
      "name": "Mention",
      "class": "qf-mention",
      "layer": "chat",
      "category": "Chat",
      "purpose": "본문 안에서 사용자나 역할을 가리키는 인라인 멘션 pill입니다.",
      "variants": [
        "qf-mention"
      ],
      "requiredTokens": [
        "--mention-bg",
        "--mention-bgHov",
        "--accent-on-subtle",
        "--accent-solid-hover",
        "--r-xs"
      ],
      "a11y": "멘션 대상은 색상에 더해 @이름 텍스트로 항상 명시해 색 단독 의존을 피합니다. 클릭 동작이 있으면 button이나 link role과 접근 가능한 이름을 부여합니다.",
      "exampleHtml": "<span class=\"qf-mention\" role=\"link\" tabindex=\"0\">@James Carter</span>",
      "purposeEn": "An inline mention pill that references a user or role within the message body.",
      "a11yEn": "Always state the mention target as @name text in addition to color, avoiding reliance on color alone. If it has a click action, give it a button or link role and an accessible name."
    },
    {
      "name": "Reactions",
      "class": "qf-reaction",
      "layer": "chat",
      "category": "Chat",
      "purpose": "메시지에 달린 이모지 반응을 개수와 함께 보여주는 pill입니다.",
      "variants": [
        "qf-reactions",
        "qf-reaction--me",
        "qf-reaction--add"
      ],
      "requiredTokens": [
        "--r-lg",
        "--bg-hover",
        "--bg-selected",
        "--accent",
        "--accent-subtle",
        "--border-strong",
        "--text-secondary",
        "--fs-12"
      ],
      "a11y": "각 반응은 button으로 토글하며, 내가 누른 반응은 색뿐 아니라 aria-pressed로 상태를 전달합니다. 반응 추가 버튼은 aria-label로 용도를 명시합니다.",
      "exampleHtml": "<div class=\"qf-reactions\"><button class=\"qf-reaction qf-reaction--me\" aria-pressed=\"true\">👍 3</button><button class=\"qf-reaction qf-reaction--add\" aria-label=\"Add reaction\">＋</button></div>",
      "purposeEn": "Pills that show the emoji reactions on a message along with their counts.",
      "a11yEn": "Each reaction toggles via a button, and reactions you've added convey their state with aria-pressed, not color alone. Give the add-reaction button an aria-label that states its purpose."
    },
    {
      "name": "ReplyBar",
      "class": "qf-replybar",
      "layer": "chat",
      "category": "Chat",
      "purpose": "메시지 위에 원본 답장 대상을 화살표와 작성자로 표시하는 줄입니다.",
      "variants": [
        "qf-replybar__arrow",
        "qf-replybar__author"
      ],
      "requiredTokens": [
        "--s-2",
        "--s-8",
        "--fs-13",
        "--text-muted",
        "--text-secondary",
        "--border-strong"
      ],
      "a11y": "답장 관계는 화살표 모양뿐 아니라 작성자 이름과 본문 미리보기 텍스트로 전달합니다. 클릭으로 원본 이동 시 접근 가능한 이름을 제공합니다.",
      "exampleHtml": "<div class=\"qf-replybar\"><span class=\"qf-replybar__arrow\" aria-hidden=\"true\"></span>Replying to <span class=\"qf-replybar__author\">James Carter</span></div>",
      "purposeEn": "A line above a message that indicates the original reply target with an arrow and the author.",
      "a11yEn": "Convey the reply relationship with the author's name and a body preview text, not the arrow shape alone. When clicking jumps to the original, provide an accessible name."
    },
    {
      "name": "Embed",
      "class": "qf-embed",
      "layer": "chat",
      "category": "Chat",
      "purpose": "링크, 이미지, 동영상, 봇 메시지 미리보기를 담는 임베드 카드입니다.",
      "variants": [
        "qf-embed--image",
        "qf-embed--video",
        "qf-embed--bot",
        "qf-embed__site",
        "qf-embed__title",
        "qf-embed__thumb",
        "qf-embed__play",
        "qf-embed__actions",
        "qf-embed__dismiss"
      ],
      "requiredTokens": [
        "--s-4",
        "--bg-panel",
        "--accent",
        "--r-md",
        "--link",
        "--info-400",
        "--text-muted",
        "--text-secondary"
      ],
      "a11y": "썸네일 이미지에는 의미 있는 alt를 제공하고, 재생, 닫기 버튼은 button과 aria-label로 용도를 전달합니다. 닫기 버튼은 focus-visible 시 항상 보이게 합니다.",
      "exampleHtml": "<div class=\"qf-embed\"><span class=\"qf-embed__site\">EXAMPLE.COM</span><a class=\"qf-embed__title\" href=\"/x\">Title</a><p class=\"qf-embed__desc\">A short description.</p><button class=\"qf-embed__dismiss\" aria-label=\"Dismiss embed\">✕</button></div>",
      "purposeEn": "An embed card that holds previews for links, images, videos, and bot messages.",
      "a11yEn": "Provide meaningful alt text on thumbnail images, and convey the purpose of the play and close buttons with button and aria-label. Keep the close button always visible on focus-visible."
    },
    {
      "name": "ThreadChip",
      "class": "qf-thread-chip",
      "layer": "chat",
      "category": "Chat",
      "purpose": "메시지 아래에 달린 스레드 답글 요약을 보여주고 패널을 여는 칩입니다.",
      "variants": [
        "qf-thread-chip__avatars",
        "qf-thread-chip__count",
        "qf-thread-chip__last",
        "qf-thread-chip__cta"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--bg-elevated",
        "--accent",
        "--text-secondary",
        "--r-md",
        "--s-2",
        "--dur-fast",
        "--ease-standard"
      ],
      "a11y": "버튼 또는 링크로 만들어 스레드 패널을 열고, 답글 수를 색뿐 아니라 텍스트로도 전달합니다. 키보드 포커스 ring 이 보여야 하고 호버 색 변화에만 의존하지 않습니다.",
      "exampleHtml": "<button class=\"qf-thread-chip\" aria-label=\"View 3 thread replies\"><span class=\"qf-thread-chip__count\">3</span><span class=\"qf-thread-chip__cta\">View replies</span></button>",
      "purposeEn": "A chip below a message that summarizes the thread replies and opens the thread panel.",
      "a11yEn": "Make it a button or link that opens the thread panel, and convey the reply count as text, not color alone. The keyboard focus ring must be visible, and don't rely on a hover color change alone."
    },
    {
      "name": "ThreadPanel",
      "class": "qf-thread-panel",
      "layer": "chat",
      "category": "Chat",
      "purpose": "우측에 붙는 스레드 사이드 패널의 컨테이너입니다.",
      "variants": [
        "qf-thread-panel__header",
        "qf-thread-panel__icon",
        "qf-thread-panel__title",
        "qf-thread-panel__sub",
        "qf-thread-panel__close"
      ],
      "requiredTokens": [
        "--bg-chat",
        "--divider",
        "--text-strong",
        "--text-muted",
        "--fs-15",
        "--h-topbar",
        "--s-5",
        "--elev-1"
      ],
      "a11y": "패널은 complementary 또는 dialog 역할로 알리고, 닫기 버튼에는 aria-label 을 둡니다. 닫기 버튼은 키보드로 도달, 포커스 가시여야 합니다.",
      "exampleHtml": "<aside class=\"qf-thread-panel\" role=\"complementary\" aria-label=\"Thread\"><header class=\"qf-thread-panel__header\"><h2 class=\"qf-thread-panel__title\">Thread</h2><button class=\"qf-thread-panel__close\" aria-label=\"Close thread\">×</button></header></aside>",
      "purposeEn": "The container for the thread side panel that docks on the right.",
      "a11yEn": "Announce the panel with a complementary or dialog role, and give the close button an aria-label. The close button must be reachable by keyboard with visible focus."
    },
    {
      "name": "ThreadOrigin",
      "class": "qf-thread-origin",
      "layer": "chat",
      "category": "Chat",
      "purpose": "스레드 패널 상단에 고정되는 원본 부모 메시지 카드입니다.",
      "variants": [
        "qf-thread-origin__meta",
        "qf-thread-origin__author",
        "qf-thread-origin__time",
        "qf-thread-origin__body"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--divider",
        "--accent",
        "--text-strong",
        "--text",
        "--r-md",
        "--s-4",
        "--fs-14"
      ],
      "a11y": "원본 메시지임을 텍스트로 분명히 하고, 왼쪽 accent 막대는 장식이므로 의미는 작성자, 시간 텍스트로 함께 전달합니다.",
      "exampleHtml": "<article class=\"qf-thread-origin\"><div class=\"qf-thread-origin__meta\"><span class=\"qf-thread-origin__author\">Mia</span><time class=\"qf-thread-origin__time\">2:14 PM</time></div><p class=\"qf-thread-origin__body\">This is the original message.</p></article>",
      "purposeEn": "The original parent message card that's pinned to the top of the thread panel.",
      "a11yEn": "Make it clear in text that this is the original message, and since the left accent bar is decorative, convey its meaning with the author and timestamp text as well."
    },
    {
      "name": "ThreadBody",
      "class": "qf-thread-body",
      "layer": "chat",
      "category": "Chat",
      "purpose": "스레드 답글 목록을 담아 세로로 스크롤하는 영역입니다.",
      "variants": [],
      "requiredTokens": [
        "--s-3"
      ],
      "a11y": "답글 목록은 list 또는 log 역할로 알리고, 새 답글이 도착하면 스크린 리더가 인지하도록 적절한 라이브 영역 처리를 권장합니다. 키보드 스크롤이 가능해야 합니다.",
      "exampleHtml": "<div class=\"qf-thread-body\" role=\"log\" aria-label=\"Thread replies\" tabindex=\"0\"></div>",
      "purposeEn": "A vertically scrolling region that holds the list of thread replies.",
      "a11yEn": "Announce the reply list with a list or log role, and use appropriate live-region handling so screen readers are aware when a new reply arrives. Keyboard scrolling must be possible."
    },
    {
      "name": "ThreadDivider",
      "class": "qf-thread-divider",
      "layer": "chat",
      "category": "Chat",
      "purpose": "스레드 내부에서 날짜 경계를 나누는 구분선입니다.",
      "variants": [],
      "requiredTokens": [
        "--divider",
        "--text-muted",
        "--fs-11",
        "--s-4",
        "--s-5",
        "--tracking-caps"
      ],
      "a11y": "단순 시각 구분이 아니라 separator 역할과 날짜 텍스트로 경계 의미를 전달합니다. 양옆 선은 장식 의사 요소입니다.",
      "exampleHtml": "<div class=\"qf-thread-divider\" role=\"separator\">June 16, 2026</div>",
      "purposeEn": "A divider that marks date boundaries within a thread.",
      "a11yEn": "Convey the boundary's meaning with a separator role and date text, not just a visual divider. The lines on either side are decorative pseudo-elements."
    },
    {
      "name": "ThreadMessage",
      "class": "qf-thread-msg",
      "layer": "chat",
      "category": "Chat",
      "purpose": "스레드 패널 안에서 보이는 컴팩트한 답글 메시지입니다.",
      "variants": [
        "qf-thread-msg--head",
        "qf-thread-msg--cont",
        "qf-thread-msg__avatar",
        "qf-thread-msg__meta",
        "qf-thread-msg__author",
        "qf-thread-msg__time",
        "qf-thread-msg__body"
      ],
      "requiredTokens": [
        "--text-strong",
        "--text",
        "--text-muted",
        "--fs-14",
        "--fs-chat",
        "--lh-normal",
        "--s-5",
        "--s-2"
      ],
      "a11y": "연속 메시지는 qf-thread-msg--cont 로 아바타, 메타를 숨겨도 작성자 정보는 그룹 첫 메시지나 aria 로 유지합니다. 호버 배경에만 의존하지 않습니다.",
      "exampleHtml": "<div class=\"qf-thread-msg qf-thread-msg--head\"><div class=\"qf-thread-msg__meta\"><span class=\"qf-thread-msg__author\">Skye</span><time class=\"qf-thread-msg__time\">2:15 PM</time></div><p class=\"qf-thread-msg__body\">Here's my reply.</p></div>",
      "purposeEn": "A compact reply message displayed inside the thread panel.",
      "a11yEn": "Consecutive messages use qf-thread-msg--cont to hide the avatar and metadata, but author information is still conveyed via the group's first message or aria. Do not rely on hover background alone."
    },
    {
      "name": "ThreadComposer",
      "class": "qf-thread-composer",
      "layer": "chat",
      "category": "Chat",
      "purpose": "스레드 패널 하단에서 답글을 작성하는 컴팩트 입력 영역입니다.",
      "variants": [
        "qf-thread-composer__options",
        "qf-thread-composer__checkbox"
      ],
      "requiredTokens": [
        "--divider",
        "--accent",
        "--text-muted",
        "--fs-12",
        "--s-5",
        "--s-3",
        "--s-4",
        "--s-2"
      ],
      "a11y": "입력 필드와 체크박스에 연결된 label 을 두고, 체크박스는 키보드로 토글, 포커스 가시여야 합니다. 모바일 터치 영역은 44px 를 권장합니다.",
      "exampleHtml": "<form class=\"qf-thread-composer\"><label class=\"qf-thread-composer__checkbox\"><input type=\"checkbox\"> Also send to channel</label></form>",
      "purposeEn": "A compact input area at the bottom of the thread panel for composing replies.",
      "a11yEn": "Associate a label with the input field and the checkbox; the checkbox must be toggleable by keyboard and have visible focus. A 44px touch target is recommended on mobile."
    },
    {
      "name": "ThreadFollowup",
      "class": "qf-thread-followup",
      "layer": "chat",
      "category": "Chat",
      "purpose": "메시지 아래에 답글 수와 참여자를 표시하는 스레드 시작 표시줄입니다.",
      "variants": [
        "qf-thread-followup__dot",
        "qf-thread-followup__last",
        "qf-thread-followup__avatars",
        "qf-thread-followup__count"
      ],
      "requiredTokens": [
        "--text-muted",
        "--accent",
        "--accent-hover",
        "--bg-chat",
        "--fs-12",
        "--r-pill",
        "--s-8",
        "--s-2"
      ],
      "a11y": "버튼 또는 링크로 스레드를 열고, 답글 수는 accent 색뿐 아니라 텍스트로 함께 전달합니다. 점, 아바타는 장식이며 키보드 포커스가 보여야 합니다.",
      "exampleHtml": "<button class=\"qf-thread-followup\" aria-label=\"View 5 replies\"><span class=\"qf-thread-followup__count\">5</span> replies</button>",
      "purposeEn": "A thread starter bar shown beneath a message that displays the reply count and participants.",
      "a11yEn": "Open the thread with a button or link, and convey the reply count as text in addition to the accent color. Dots and avatars are decorative; keyboard focus must be visible."
    },
    {
      "name": "ServerList",
      "class": "qf-serverlist",
      "layer": "chat",
      "category": "Chat",
      "purpose": "세로 서버 레일에 서버 아이콘 버튼을 모아 보여주는 좌측 네비게이션입니다.",
      "variants": [
        "qf-server-btn",
        "qf-server-btn__unread"
      ],
      "requiredTokens": [
        "--w-serverlist",
        "--bg-app",
        "--s-4",
        "--s-3",
        "--r-xl",
        "--accent"
      ],
      "a11y": "서버 버튼은 선택 상태를 aria-selected로 알리고 선택, 호버 시 좌측 필 인디케이터로 색 외 형태도 함께 전달합니다. 키보드 포커스가 보이며 읽지 않음 배지는 텍스트 수치를 노출합니다.",
      "exampleHtml": "<nav class=\"qf-serverlist\" aria-label=\"Servers\"><button class=\"qf-server-btn\" aria-selected=\"true\">Q</button></nav>",
      "purposeEn": "A left-hand navigation rail that groups server icon buttons in a vertical server rail.",
      "a11yEn": "Server buttons announce their selection state via aria-selected, and a left pill indicator on select/hover conveys shape in addition to color. Keyboard focus is visible, and unread badges expose a text count."
    },
    {
      "name": "ChannelList",
      "class": "qf-channellist",
      "layer": "chat",
      "category": "Chat",
      "purpose": "현재 서버의 카테고리와 채널 목록을 보여주는 사이드바입니다.",
      "variants": [
        "qf-channellist__head",
        "qf-channellist__body",
        "qf-channel--unread",
        "qf-channel__prefix",
        "qf-channel__suffix",
        "qf-category",
        "qf-channel"
      ],
      "requiredTokens": [
        "--w-channellist",
        "--bg-panel",
        "--divider",
        "--s-5",
        "--s-4",
        "--r-sm",
        "--bg-selected"
      ],
      "a11y": "채널 행은 aria-selected로 현재 채널을 알리고, 읽지 않음은 색과 굵기, 좌측 필을 함께 써 색 단독 의존을 피합니다. 키보드 포커스 ring이 보입니다.",
      "exampleHtml": "<div class=\"qf-channellist\"><div class=\"qf-channellist__body\"><div class=\"qf-channel\" aria-selected=\"true\"><span class=\"qf-channel__prefix\">#</span>general</div></div></div>",
      "purposeEn": "A sidebar that shows the current server's categories and channel list.",
      "a11yEn": "Channel rows announce the current channel via aria-selected, and unread state combines color, weight, and a left pill to avoid relying on color alone. The keyboard focus ring is visible."
    },
    {
      "name": "ChannelTopbar",
      "class": "qf-topbar",
      "layer": "chat",
      "category": "Chat",
      "purpose": "현재 채널 제목과 주제를 표시하는 상단 바입니다.",
      "variants": [
        "qf-topbar__title",
        "qf-topbar__topic"
      ],
      "requiredTokens": [
        "--h-topbar",
        "--s-5",
        "--bg-chat",
        "--divider",
        "--text-strong",
        "--text-muted",
        "--elev-1"
      ],
      "a11y": "제목과 주제를 텍스트로 함께 노출해 맥락을 명확히 전합니다. 주제 구분은 좌측 보더와 색차로 표현하되 의미는 텍스트로 전달합니다.",
      "exampleHtml": "<header class=\"qf-topbar\"><span class=\"qf-topbar__title\"># general</span><span class=\"qf-topbar__topic\">Team announcements channel</span></header>",
      "purposeEn": "A top bar that displays the current channel's title and topic.",
      "a11yEn": "Expose both the title and topic as text to clearly convey context. The topic separator is rendered with a left border and color contrast, but its meaning is carried by the text."
    },
    {
      "name": "MemberList",
      "class": "qf-memberlist",
      "layer": "chat",
      "category": "Chat",
      "purpose": "채널 참여 멤버를 역할 그룹별로 보여주는 우측 사이드바입니다.",
      "variants": [
        "qf-memberlist__group",
        "qf-member__name",
        "qf-member__role-owner",
        "qf-member__role-mod"
      ],
      "requiredTokens": [
        "--w-memberlist",
        "--bg-panel",
        "--s-4",
        "--s-3",
        "--r-sm",
        "--text-muted",
        "--bg-hover"
      ],
      "a11y": "역할 그룹 헤더를 텍스트로 노출하고 멤버 이름은 줄임 처리해도 전체 값을 접근성 텍스트로 전합니다. 행 호버, 포커스가 보입니다.",
      "exampleHtml": "<aside class=\"qf-memberlist\" aria-label=\"Members\"><div class=\"qf-memberlist__group\">Online</div><div class=\"qf-member\"><span class=\"qf-member__name\">Minsu</span></div></aside>",
      "purposeEn": "A right-hand sidebar that shows channel members grouped by role.",
      "a11yEn": "Expose role group headers as text, and even when member names are truncated, convey the full value as accessible text. Row hover and focus are visible."
    },
    {
      "name": "TypingIndicator",
      "class": "qf-typing",
      "layer": "chat",
      "category": "Chat",
      "purpose": "상대가 입력 중임을 점 애니메이션과 텍스트로 알리는 표시줄입니다.",
      "variants": [
        "qf-typing__dots"
      ],
      "requiredTokens": [
        "--s-5",
        "--h-typingbar",
        "--s-2",
        "--fs-13",
        "--font-sans",
        "--text-muted"
      ],
      "a11y": "입력 상태를 점 애니메이션만이 아니라 텍스트로도 전하며, 상태 영역은 aria-live로 보조기술에 변경을 알리길 권장합니다.",
      "exampleHtml": "<div class=\"qf-typing\" aria-live=\"polite\"><span class=\"qf-typing__dots\"><span></span><span></span><span></span></span>Minsu is typing</div>",
      "purposeEn": "A status bar that signals someone is typing using a dot animation and text.",
      "a11yEn": "Convey the typing state with text as well as the dot animation, and use aria-live on the status region so assistive technology is notified of changes."
    },
    {
      "name": "VoiceBadge",
      "class": "qf-voice",
      "layer": "chat",
      "category": "Chat",
      "purpose": "음성 채널 연결 상태를 작은 배지로 나타냅니다.",
      "variants": [],
      "requiredTokens": [
        "--ok-400",
        "--r-xs",
        "--fs-11",
        "--font-sans"
      ],
      "a11y": "색 외에 텍스트 라벨을 함께 표시해 음성 연결 의미를 색 단독에 의존하지 않고 전합니다. 텍스트 대비 AA를 유지합니다.",
      "exampleHtml": "<span class=\"qf-voice\">Voice connected</span>",
      "purposeEn": "A small badge that indicates the voice channel connection state.",
      "a11yEn": "Show a text label alongside color so the voice connection meaning does not rely on color alone. Maintain AA 4.5:1 text contrast."
    },
    {
      "name": "DropLine",
      "class": "qf-dropline",
      "layer": "chat",
      "category": "Chat",
      "purpose": "드래그한 채널, 카테고리가 놓일 위치를 가리키는 얇은 삽입 표시선입니다.",
      "variants": [],
      "requiredTokens": [
        "--s-3",
        "--r-pill",
        "--accent"
      ],
      "a11y": "드래그 앤 드롭 보조 시각 표시이며 키보드 재정렬 시에는 위치 변경을 텍스트, aria-live로 함께 안내하길 권장합니다.",
      "exampleHtml": "<div class=\"qf-dropline\" aria-hidden=\"true\"></div>",
      "purposeEn": "A thin insertion marker that indicates where a dragged channel or category will be dropped.",
      "a11yEn": "This is a supplementary visual cue for drag and drop; for keyboard reordering, also announce the position change via text and aria-live."
    },
    {
      "name": "RoleChip",
      "class": "qf-role-chip",
      "layer": "chat",
      "category": "Chat",
      "purpose": "멤버의 역할을 색 점과 역할명으로 함께 보여주는 칩입니다.",
      "variants": [
        "qf-role-chip__dot",
        "qf-role-chip--admin",
        "qf-role-chip--mod",
        "qf-role-chip--online",
        "qf-role-chip--bot"
      ],
      "requiredTokens": [
        "--s-2",
        "--s-3",
        "--accent-subtle",
        "--r-pill",
        "--fs-12",
        "--accent",
        "--text-secondary"
      ],
      "a11y": "역할 색 점과 함께 역할명 텍스트를 노출해 색 단독 의존을 피합니다. 텍스트 대비 AA를 유지합니다.",
      "exampleHtml": "<span class=\"qf-role-chip qf-role-chip--admin\"><span class=\"qf-role-chip__dot\"></span>Admin</span>",
      "purposeEn": "A chip that shows a member's role using a colored dot together with the role name.",
      "a11yEn": "Expose the role name text alongside the colored role dot to avoid relying on color alone. Maintain AA 4.5:1 text contrast."
    },
    {
      "name": "Composer",
      "class": "qf-composer",
      "layer": "generic",
      "category": "Forms",
      "purpose": "focus-within 필 안에 자동확장 textarea와 인라인 액션(전송 등)을 묶는 컴포저입니다. 선행 슬롯(__lead)과 크기 변형(--sm/--lg)을 지원합니다. 리치 텍스트 포맷바(__formatbar, --rich)는 챗 확장입니다.",
      "variants": [
        "qf-composer--rich",
        "qf-composer__formatbar",
        "qf-composer__format-btn",
        "qf-composer__format-sep",
        "qf-composer__row",
        "qf-composer__attach",
        "qf-composer__input",
        "qf-composer__actions",
        "qf-composer__send",
        "qf-composer__hint",
        "qf-composer__hint-count--over",
        "qf-composer__lead",
        "qf-composer--sm",
        "qf-composer--lg"
      ],
      "requiredTokens": [
        "--bg-input",
        "--border",
        "--accent",
        "--r-lg",
        "--s-3",
        "--s-5",
        "--text",
        "--text-onAccent",
        "--composer-max-h",
        "--composer-font-min",
        "--s-10"
      ],
      "a11y": "focus-within 시 accent 보더로 강조하므로 내부 textarea 는 자체 outline 을 끕니다. 포맷 버튼은 aria-pressed 로 토글 상태를 전합니다. 전송 버튼은 입력이 비면 disabled 로 두고, 힌트 글자수 초과는 색만이 아니라 텍스트로도 알립니다.",
      "exampleHtml": "<div class=\"qf-composer\"><div class=\"qf-composer__row\"><button class=\"qf-composer__attach\" aria-label=\"Attach\"><svg class=\"qf-icon\" aria-hidden=\"true\"><use href=\"#qf-i-plus\"/></svg></button><textarea class=\"qf-composer__input\" rows=\"1\" aria-label=\"Message\" placeholder=\"Type a message\"></textarea><button class=\"qf-composer__send\" aria-label=\"Send\"><svg class=\"qf-icon\" aria-hidden=\"true\"><use href=\"#qf-i-send\"/></svg></button></div></div>",
      "purposeEn": "A composer that wraps an auto-expanding textarea and inline actions (send, etc.) in a focus-within pill, with a leading slot (__lead) and size variants (--sm/--lg). The rich-text format bar (__formatbar, --rich) is a chat-layer extension.",
      "a11yEn": "Because focus-within highlights the field with an accent border, the inner textarea disables its own outline. Formatting buttons convey their toggle state via aria-pressed. Keep the send button disabled when the input is empty, and signal a hint character-count overflow with text as well as color."
    },
    {
      "name": "Autocomplete",
      "class": "qf-autocomplete",
      "layer": "chat",
      "category": "Chat",
      "purpose": "컴포저 위로 떠오르는 드롭업으로, 멘션, 채널, 이모지 후보를 같은 골격으로 제안합니다.",
      "variants": [
        "qf-autocomplete__header",
        "qf-autocomplete__item",
        "qf-autocomplete__avatar",
        "qf-autocomplete__label",
        "qf-autocomplete__sub",
        "qf-autocomplete__meta",
        "qf-autocomplete__meta-dot--online",
        "qf-autocomplete__item--mention",
        "qf-autocomplete__item--channel",
        "qf-autocomplete__item--emoji",
        "qf-autocomplete__shortcode"
      ],
      "requiredTokens": [
        "--bg-floating",
        "--border",
        "--r-lg",
        "--elev-3",
        "--z-dropdown",
        "--bg-selected",
        "--accent",
        "--s-2"
      ],
      "a11y": "항목은 role=option, 컨테이너는 role=listbox 를 가정하며, 키보드 선택 항목은 aria-selected=true 로 표시하고 좌측 accent 바를 함께 두어 색 단독 의존을 피합니다. 상태 dot 은 텍스트 라벨과 함께 의미를 전합니다.",
      "exampleHtml": "<ul class=\"qf-autocomplete\" role=\"listbox\" aria-label=\"Mention suggestions\"><li class=\"qf-autocomplete__item qf-autocomplete__item--mention\" role=\"option\" aria-selected=\"true\"><span class=\"qf-autocomplete__avatar\"></span><span class=\"qf-autocomplete__label\">Skye</span></li></ul>",
      "purposeEn": "A drop-up that floats above the composer and suggests mention, channel, and emoji candidates using a shared skeleton.",
      "a11yEn": "Items assume role=option and the container assumes role=listbox; the keyboard-selected item is marked with aria-selected=true and paired with a left accent bar to avoid relying on color alone. The status dot conveys meaning together with a text label."
    },
    {
      "name": "SlashMenu",
      "class": "qf-slash-menu",
      "layer": "chat",
      "category": "Chat",
      "purpose": "슬래시 커맨드 전용 드롭업으로, 좌측 아이콘, 모노스페이스 커맨드명, 설명을 위계 있게 보여줍니다.",
      "variants": [
        "qf-slash-menu__item",
        "qf-slash-menu__icon",
        "qf-slash-menu__text",
        "qf-slash-menu__cmd",
        "qf-slash-menu__arg",
        "qf-slash-menu__desc",
        "qf-slash-menu__kbd"
      ],
      "requiredTokens": [
        "--bg-selected",
        "--accent",
        "--accent-subtle",
        "--r-sm",
        "--s-3",
        "--mention-bg",
        "--text-strong",
        "--text-secondary"
      ],
      "a11y": "항목은 role=option 으로 두고 키보드 선택 항목에 aria-selected=true 와 좌측 accent 바를 함께 표시해 색 외 단서를 보장합니다. 단축키 힌트는 텍스트로 제공합니다.",
      "exampleHtml": "<ul class=\"qf-autocomplete qf-slash-menu\" role=\"listbox\" aria-label=\"Slash commands\"><li class=\"qf-slash-menu__item\" role=\"option\" aria-selected=\"true\"><span class=\"qf-slash-menu__icon\">/</span><span class=\"qf-slash-menu__text\"><span class=\"qf-slash-menu__cmd\">/giphy</span></span></li></ul>",
      "purposeEn": "A drop-up dedicated to slash commands that presents a left icon, monospace command name, and description in a clear hierarchy.",
      "a11yEn": "Give items role=option, and mark the keyboard-selected item with both aria-selected=true and a left accent bar to guarantee a cue beyond color. Provide shortcut hints as text."
    },
    {
      "name": "StatusPicker",
      "class": "qf-status-picker",
      "layer": "chat",
      "category": "Chat",
      "purpose": "온라인, 자리비움, 방해금지, 오프라인과 커스텀 상태를 고르는 팝오버입니다.",
      "variants": [
        "qf-status-picker__item",
        "qf-status-picker__dot--online",
        "qf-status-picker__dot--idle",
        "qf-status-picker__dot--dnd",
        "qf-status-picker__dot--offline",
        "qf-status-picker__label",
        "qf-status-picker__desc",
        "qf-status-picker__sep",
        "qf-status-picker__custom",
        "qf-status-picker__emoji",
        "qf-status-picker__custom-input"
      ],
      "requiredTokens": [
        "--bg-floating",
        "--border",
        "--r-lg",
        "--elev-3",
        "--bg-selected",
        "--status-online",
        "--s-3",
        "--sz-status-dot-sm"
      ],
      "a11y": "항목은 role=option 으로, 선택은 aria-selected=true 로 표시합니다. 상태 dot 은 색만으로 구분하지 않도록 라벨, 설명 텍스트와 함께 의미를 전하며, 커스텀 입력은 halo 를 끄는 대신 컨테이너 포커스로 보완합니다.",
      "exampleHtml": "<ul class=\"qf-status-picker\" role=\"listbox\" aria-label=\"Select status\"><li class=\"qf-status-picker__item\" role=\"option\" aria-selected=\"true\"><span class=\"qf-status-picker__dot qf-status-picker__dot--online\"></span><span class=\"qf-status-picker__label\">Online</span></li></ul>",
      "purposeEn": "A popover for choosing online, away, do-not-disturb, offline, and custom statuses.",
      "a11yEn": "Items use role=option and selection is marked with aria-selected=true. The status dot conveys meaning together with label and description text so it is not distinguished by color alone, and the custom input disables its halo, compensating with container focus instead."
    },
    {
      "name": "Hovercard",
      "class": "qf-hovercard",
      "layer": "chat",
      "category": "Chat",
      "purpose": "유저 프로필을 띄우는 카드로, 배너에 걸친 아바타와 이름, 상태, 역할, 소개를 보여줍니다.",
      "variants": [
        "qf-hovercard__banner",
        "qf-hovercard__banner--plain",
        "qf-hovercard__body",
        "qf-hovercard__avatar",
        "qf-hovercard__name",
        "qf-hovercard__handle",
        "qf-hovercard__status",
        "qf-hovercard__status-dot",
        "qf-hovercard__roles",
        "qf-hovercard__about",
        "qf-hovercard__actions"
      ],
      "requiredTokens": [
        "--bg-floating",
        "--border",
        "--r-xl",
        "--elev-4",
        "--accent",
        "--s-5",
        "--status-online",
        "--text-strong"
      ],
      "a11y": "카드는 role=dialog 또는 호버 트리거에 aria-describedby 로 연결합니다. 상태 dot 은 인접한 상태 텍스트와 함께 의미를 전하며, 액션 버튼은 qf-btn 으로 키보드 포커스 ring 을 갖춥니다.",
      "exampleHtml": "<div class=\"qf-hovercard\" role=\"dialog\" aria-label=\"Profile\"><div class=\"qf-hovercard__banner\"></div><div class=\"qf-hovercard__body\"><div class=\"qf-hovercard__name\">Skye</div></div></div>",
      "purposeEn": "A card that surfaces a user profile, showing an avatar overlapping a banner along with the name, status, roles, and bio.",
      "a11yEn": "The card uses role=dialog or is linked to its hover trigger via aria-describedby. The status dot conveys meaning together with the adjacent status text, and action buttons use qf-btn so they have a visible keyboard focus ring."
    },
    {
      "name": "SearchOverlay",
      "class": "qf-search-overlay",
      "layer": "chat",
      "category": "Chat",
      "purpose": "검색바, 필터칩, 결과 목록을 한 카드에 담아 메시지를 찾는 오버레이입니다.",
      "variants": [
        "qf-search-overlay__bar",
        "qf-search-overlay__input",
        "qf-search-overlay__filters",
        "qf-search-overlay__chip",
        "qf-search-overlay__chip-key",
        "qf-search-overlay__results",
        "qf-search-overlay__result",
        "qf-search-overlay__result-ctx",
        "qf-search-overlay__result-preview",
        "qf-search-overlay__jump"
      ],
      "requiredTokens": [
        "--bg-floating",
        "--border",
        "--r-xl",
        "--elev-4",
        "--divider",
        "--accent-subtle",
        "--accent",
        "--mention-bg"
      ],
      "a11y": "오버레이는 role=dialog 와 aria-label 을 두고, 검색 입력은 aria-label 로 명명합니다. 필터칩은 aria-pressed 로 활성 상태를 알리며, 검색어 강조 mark 는 색과 배경을 함께 써 본문과 구분합니다.",
      "exampleHtml": "<div class=\"qf-search-overlay\" role=\"dialog\" aria-label=\"Search\"><div class=\"qf-search-overlay__bar\"><input class=\"qf-search-overlay__input\" aria-label=\"Search term\" placeholder=\"Search\"></div><div class=\"qf-search-overlay__results\"></div></div>",
      "purposeEn": "An overlay for finding messages that combines a search bar, filter chips, and a results list in a single card.",
      "a11yEn": "The overlay has role=dialog and an aria-label, and the search input is named with aria-label. Filter chips announce their active state via aria-pressed, and the search-term highlight mark uses both color and background to distinguish it from body text."
    },
    {
      "name": "MobileSafeArea",
      "class": "qf-m-safe-top",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "기기 노치, 홈 인디케이터를 피하도록 안전 영역 여백을 적용합니다.",
      "variants": [
        "qf-m-safe-bottom"
      ],
      "requiredTokens": [
        "--m-statusbar",
        "--m-tabbar-h",
        "--m-gutter",
        "--s-3",
        "--m-keyboard-pad"
      ],
      "a11y": "env(safe-area-inset-*) 기반 여백으로 콘텐츠가 시스템 UI에 가려지지 않게 합니다. 시각 의미 전달 요소가 아니므로 별도 ARIA는 필요하지 않습니다.",
      "exampleHtml": "<div class=\"qf-m-safe-top\">Reserves padding for the top safe area.</div>",
      "purposeEn": "Applies safe-area padding so content avoids the device notch and home indicator.",
      "a11yEn": "Padding based on env(safe-area-inset-*) keeps content from being obscured by system UI. This element conveys no visual meaning, so no separate ARIA is needed."
    },
    {
      "name": "MobileScreen",
      "class": "qf-m-screen",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "상단바, 본문, 탭바로 구성되는 모바일 화면 셸의 루트 컨테이너입니다.",
      "variants": [
        "qf-m-screen--bare",
        "qf-m-screen--app"
      ],
      "requiredTokens": [
        "--bg-chat",
        "--m-statusbar",
        "--w-drawer-left",
        "--w-memberlist",
        "--s-3",
        "--divider"
      ],
      "a11y": "화면 전체 구조 컨테이너로 내부에 topbar, body, tabbar를 순서대로 배치합니다. 실제 앱 화면에는 qf-m-screen--app으로 동적 뷰포트 높이와 안전 영역을 적용합니다.",
      "exampleHtml": "<div class=\"qf-m-screen qf-m-screen--app\"><header class=\"qf-m-topbar\"></header><div class=\"qf-m-body\"></div></div>",
      "purposeEn": "The root container of the mobile screen shell, composed of a topbar, body, and tab bar.",
      "a11yEn": "As the full-screen structural container, it lays out topbar, body, and tabbar in order. For real app screens, apply qf-m-screen--app to use dynamic viewport height and the safe area."
    },
    {
      "name": "MobileTopbar",
      "class": "qf-m-topbar",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "모바일 화면 상단의 뒤로가기, 제목, 액션을 담는 상단바입니다.",
      "variants": [
        "qf-m-topbar__back",
        "qf-m-topbar__action",
        "qf-m-topbar__title",
        "qf-m-topbar__subtitle",
        "qf-m-topbar__actions"
      ],
      "requiredTokens": [
        "--m-topbar-h",
        "--m-touch",
        "--bg-chat",
        "--divider",
        "--text-strong",
        "--r-md"
      ],
      "a11y": "뒤로가기와 액션 버튼은 44px 터치 영역을 보장합니다. 아이콘만 있는 버튼에는 aria-label로 의미를 전달하고, 활성 상태는 bg-hover로 표시합니다.",
      "exampleHtml": "<header class=\"qf-m-topbar\"><button class=\"qf-m-topbar__back\" aria-label=\"Back\">←</button><div class=\"qf-m-topbar__title\">Title</div></header>",
      "purposeEn": "A top bar at the top of the mobile screen that holds the back button, title, and actions.",
      "a11yEn": "The back button and action buttons guarantee a 44px touch target. Give icon-only buttons meaning via aria-label, and indicate the active state with bg-hover."
    },
    {
      "name": "MobileTabBar",
      "class": "qf-m-tabbar",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "모바일 1차 내비게이션을 담는 하단 고정 탭바입니다.",
      "variants": [
        "qf-m-tab",
        "qf-m-tab__icon",
        "qf-m-tab__label",
        "qf-m-tab__pill",
        "qf-m-tab__badge",
        "qf-m-tab__badge--mention",
        "qf-m-tab__dot"
      ],
      "requiredTokens": [
        "--m-tabbar-h",
        "--bg-app",
        "--divider",
        "--accent",
        "--z-tabbar",
        "--badge-unread-bg"
      ],
      "a11y": "role=tablist과 각 탭의 aria-selected로 선택 상태를 전달합니다. 선택 탭은 색상에 더해 pill 형태와 아이콘 확대로 이중 인코딩해 색 단독 의존을 피합니다. 읽지 않음(violet)과 멘션(danger) 배지는 색만으로는 구분되므로(WCAG 1.4.1), aria-label로 '멘션 N건', '읽지 않음 N건'처럼 의미를 텍스트로도 전달하세요.",
      "exampleHtml": "<nav class=\"qf-m-tabbar\" role=\"tablist\"><button class=\"qf-m-tab\" role=\"tab\" aria-selected=\"true\"><span class=\"qf-m-tab__icon\">🏠</span><span class=\"qf-m-tab__label\">Home</span><span class=\"qf-m-tab__pill\"></span></button></nav>",
      "purposeEn": "A fixed bottom tab bar that holds the mobile primary navigation.",
      "a11yEn": "Convey selection state with role=tablist and each tab's aria-selected. The selected tab is dual-encoded with a pill shape and an enlarged icon in addition to color, avoiding reliance on color alone. Because the unread (violet) and mention (danger) badges are distinguished only by color (WCAG 1.4.1), also convey their meaning as text via aria-label, e.g. \"N mentions\" or \"N unread\"."
    },
    {
      "name": "MobileBody",
      "class": "qf-m-body",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "상단바와 탭바 사이에서 세로 스크롤되는 본문 영역입니다.",
      "variants": [],
      "requiredTokens": [
        "--bg-chat",
        "--m-gutter",
        "--divider",
        "--text",
        "--s-3"
      ],
      "a11y": "overscroll-behavior로 스크롤 체이닝을 막아 화면 셸의 의도치 않은 스크롤을 방지합니다. 본문 구조 컨테이너로 내부 콘텐츠의 의미 마크업을 따릅니다.",
      "exampleHtml": "<div class=\"qf-m-body\"><div class=\"qf-m-row\"></div></div>",
      "purposeEn": "A vertically scrolling body area between the topbar and tab bar.",
      "a11yEn": "overscroll-behavior prevents scroll chaining, avoiding unintended scrolling of the screen shell. As a structural body container, it follows the semantic markup of its inner content."
    },
    {
      "name": "MobileListRow",
      "class": "qf-m-row",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "다이렉트 메시지, 목록, 검색 결과를 표시하는 탭 가능한 리스트 행입니다.",
      "variants": [
        "qf-m-row--unread",
        "qf-m-row__primary",
        "qf-m-row__secondary",
        "qf-m-row__aside",
        "qf-m-row__time"
      ],
      "requiredTokens": [
        "--m-gutter",
        "--bg-hover",
        "--divider",
        "--text-strong",
        "--text-muted",
        "--fs-15"
      ],
      "a11y": "최소 높이 64px로 충분한 터치 영역을 제공합니다. button 요소로 쓸 때 배경, 테두리를 초기화해 화면 배경에 밀착시킵니다. 읽지 않음은 qf-m-row--unread로 글자 굵기와 색을 함께 강조합니다.",
      "exampleHtml": "<button class=\"qf-m-row qf-m-row--unread\"><div class=\"qf-m-row__primary\">Alex Carter</div><div class=\"qf-m-row__secondary\">You have a new message.</div></button>",
      "purposeEn": "A tappable list row for displaying direct messages, lists, and search results.",
      "a11yEn": "A minimum height of 64px provides a generous touch target. When used as a button element, reset its background and border so it sits flush against the screen background. Unread state uses qf-m-row--unread to emphasize both font weight and color."
    },
    {
      "name": "MobileSection",
      "class": "qf-m-section",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "리스트 안에서 그룹을 구분하는 섹션 머리글입니다.",
      "variants": [
        "qf-m-section__action"
      ],
      "requiredTokens": [
        "--s-5",
        "--m-gutter",
        "--text-muted",
        "--tracking-caps",
        "--link",
        "--fs-12"
      ],
      "a11y": "머리글 텍스트는 대문자 letter-spacing로 표시하고, 액션은 accent 대신 테마 인식 --link 색을 써서 패널 위 본문 텍스트 대비 AA를 충족합니다. 의미상 머리글 요소로 마크업하기를 권합니다.",
      "exampleHtml": "<h2 class=\"qf-m-section\">Direct Messages<a class=\"qf-m-section__action\" href=\"#\">See all</a></h2>",
      "purposeEn": "A section header that separates groups within a list.",
      "a11yEn": "Render the header text in uppercase with letter-spacing, and use the theme-aware --link color (instead of accent) for actions so they meet AA 4.5:1 contrast against body text over the panel. Marking it up as a semantic heading element is recommended."
    },
    {
      "name": "MobileSearch",
      "class": "qf-m-search",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "상단바에 인라인으로 배치하는 검색 입력 필드입니다.",
      "variants": [
        "qf-m-search__input"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--r-lg",
        "--m-gutter",
        "--text-muted",
        "--s-2",
        "--fs-14"
      ],
      "a11y": "입력 요소에는 aria-label 또는 연결된 라벨로 용도를 전달합니다. placeholder는 보조 정보로만 사용하고 단독 라벨로 의존하지 않습니다.",
      "exampleHtml": "<div class=\"qf-m-search\"><input class=\"qf-m-search__input\" type=\"search\" aria-label=\"Search\" placeholder=\"Search\"></div>",
      "purposeEn": "A search input field placed inline in the topbar.",
      "a11yEn": "Convey the input's purpose with aria-label or an associated label. Use the placeholder only as supplementary information and do not rely on it as the sole label."
    },
    {
      "name": "MobileSheet",
      "class": "qf-m-sheet",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "화면 하단에서 올라오는 액션, 옵션용 바텀 시트입니다.",
      "variants": [
        "qf-m-sheet__grab",
        "qf-m-sheet__title",
        "qf-m-sheet__item",
        "qf-m-sheet__item--danger",
        "qf-m-sheet__icon",
        "qf-m-sheet__divider",
        "qf-m-sheet-backdrop"
      ],
      "requiredTokens": [
        "--bg-elevated",
        "--m-sheet-r",
        "--elev-4",
        "--m-gutter",
        "--m-touch",
        "--bg-hover"
      ],
      "a11y": "role=dialog과 aria-modal로 시트를 모달로 알리고, 항목은 최소 높이 44px를 보장합니다. 위험 항목은 danger 색과 함께 텍스트로도 의미를 전달합니다. 백드롭 탭으로 닫힙니다.",
      "exampleHtml": "<div class=\"qf-m-sheet\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Options\"><div class=\"qf-m-sheet__grab\"></div><button class=\"qf-m-sheet__item\">Share</button></div>",
      "purposeEn": "A bottom sheet that rises from the bottom of the screen for actions and options.",
      "a11yEn": "Announce the sheet as modal with role=dialog and aria-modal, and guarantee a minimum height of 44px for items. Destructive items convey their meaning as text in addition to the danger color. It dismisses on a backdrop tap."
    },
    {
      "name": "MobileSegment",
      "class": "qf-m-segment",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "필터, 보기 전환을 위한 분절 선택 컨트롤입니다.",
      "variants": [
        "qf-m-segment__btn"
      ],
      "requiredTokens": [
        "--bg-panel",
        "--r-lg",
        "--s-3",
        "--m-gutter",
        "--bg-elevated",
        "--elev-1"
      ],
      "a11y": "role=tablist과 각 버튼의 aria-selected로 선택을 전달합니다. 선택 항목은 배경 채움과 그림자로 강조해 색 단독 의존을 피하고, 각 버튼은 텍스트 라벨을 갖습니다.",
      "exampleHtml": "<div class=\"qf-m-segment\" role=\"tablist\"><button class=\"qf-m-segment__btn\" role=\"tab\" aria-selected=\"true\">All</button><button class=\"qf-m-segment__btn\" role=\"tab\" aria-selected=\"false\">Requests</button></div>",
      "purposeEn": "A segmented control for switching filters and views.",
      "a11yEn": "Convey selection with role=tablist and each button's aria-selected. The selected item is emphasized with a filled background and shadow to avoid relying on color alone, and each button has a text label."
    },
    {
      "name": "MobileEmptyState",
      "class": "qf-m-empty",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "내용이 없을 때 안내 제목과 설명을 보여주는 빈 상태 영역입니다.",
      "variants": [
        "qf-m-empty__title",
        "qf-m-empty__body"
      ],
      "requiredTokens": [
        "--s-11",
        "--s-7",
        "--s-4",
        "--text-muted",
        "--text-strong",
        "--fs-16"
      ],
      "a11y": "제목과 본문 텍스트로 상황을 명확히 설명합니다. 장식 아이콘에는 aria-hidden을 적용하고 핵심 안내는 텍스트로 전달합니다.",
      "exampleHtml": "<div class=\"qf-m-empty\"><div class=\"qf-m-empty__title\">No messages yet</div><p class=\"qf-m-empty__body\">Start a conversation.</p></div>",
      "purposeEn": "An empty-state region that shows a title and description when there is no content to display.",
      "a11yEn": "Use the title and body text to clearly explain the situation. Apply aria-hidden to decorative icons and convey the essential message as text."
    },
    {
      "name": "MobileFab",
      "class": "qf-m-fab",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "화면 우하단에 떠 있는 주요 액션용 플로팅 버튼입니다.",
      "variants": [
        "qf-m-fab--search"
      ],
      "requiredTokens": [
        "--m-gutter",
        "--m-tabbar-h",
        "--r-pill",
        "--accent",
        "--elev-3",
        "--s-3"
      ],
      "a11y": "56px 크기로 44px 터치 기준을 넘으며, 아이콘만 있으므로 aria-label로 동작을 전달합니다. 탭바와 안전 영역 위에 배치되어 가려지지 않습니다.",
      "exampleHtml": "<button class=\"qf-m-fab\" aria-label=\"New message\">＋</button>",
      "purposeEn": "A floating action button anchored to the bottom-right of the screen for the primary action.",
      "a11yEn": "At 56px it exceeds the 44px touch-target minimum, and since it is icon-only, convey its action with aria-label. It sits above the tab bar and the safe area so it is never obscured."
    },
    {
      "name": "MobilePullToRefresh",
      "class": "qf-m-ptr",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "리스트 상단에서 당겨 새로고침할 때 표시하는 스피너 영역입니다.",
      "variants": [
        "qf-m-ptr__spin"
      ],
      "requiredTokens": [
        "--s-3",
        "--text-muted",
        "--divider",
        "--accent",
        "--fs-12",
        "--bg-chat"
      ],
      "a11y": "새로고침 진행은 aria-live 영역이나 상태 텍스트로 함께 알려 스피너 애니메이션에만 의존하지 않게 합니다. prefers-reduced-motion 환경을 고려합니다.",
      "exampleHtml": "<div class=\"qf-m-ptr\" role=\"status\" aria-live=\"polite\"><span class=\"qf-m-ptr__spin\" aria-hidden=\"true\"></span>Refreshing</div>",
      "purposeEn": "A spinner region shown when the user pulls down from the top of a list to refresh.",
      "a11yEn": "Announce refresh progress through an aria-live region or status text as well, so it does not rely on the spinner animation alone. Honor prefers-reduced-motion environments."
    },
    {
      "name": "MobileDrawerScrim",
      "class": "qf-m-drawer-scrim",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "드로어가 열렸을 때 본문 위에 깔려 탭하면 닫는 어두운 막입니다.",
      "variants": [
        "qf-m-panels--show-left",
        "qf-m-panels--show-right"
      ],
      "requiredTokens": [
        "--scrim",
        "--m-panel-dur",
        "--m-panel-ease",
        "--z-drawer",
        "--w-drawer-left",
        "--w-memberlist"
      ],
      "a11y": "드로어 열림 시에만 pointer-events가 활성화되어 막 탭으로 닫을 수 있습니다. 키보드 사용자를 위해 Escape 닫기도 함께 제공하기를 권합니다.",
      "exampleHtml": "<div class=\"qf-m-panels qf-m-panels--show-left\"><div class=\"qf-m-drawer-scrim\"></div></div>",
      "purposeEn": "A dark overlay that covers the main content while a drawer is open and dismisses it on tap.",
      "a11yEn": "pointer-events is enabled only while the drawer is open, so tapping the scrim closes it. For keyboard users, also provide Escape to close."
    },
    {
      "name": "MobileFilterChip",
      "class": "qf-m-filter-chip",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "활동, 검색 상단에서 결과를 거르는 가로 스크롤 필터 칩입니다. 데스크톱 대응은 qf-chip / qf-chips(같은 토큰 --r-pill, --accent-subtle, --accent 공유. 라벨 색만 AA 를 위해 --accent-on-subtle).",
      "variants": [
        "qf-m-filter-chip--solid",
        "qf-m-filter-chip__count",
        "qf-m-filter-bar"
      ],
      "requiredTokens": [
        "--m-touch",
        "--bg-panel",
        "--r-pill",
        "--accent",
        "--accent-subtle",
        "--text-secondary"
      ],
      "a11y": "최소 높이 44px로 터치 영역을 보장하고, 선택 상태는 aria-selected와 테두리로도 전달해 색 단독 의존을 피합니다. 선택 라벨에 --accent(a-500)를 쓰는 기본 변형은 본문 크기 텍스트 대비가 AA 4.5:1 미만일 수 있으니, 라벨 가독이 중요하면 더 진한 violet(a-600)의 솔리드 변형(qf-m-filter-chip--solid)을 쓰세요.",
      "exampleHtml": "<button class=\"qf-m-filter-chip\" aria-selected=\"true\">Mentions<span class=\"qf-m-filter-chip__count\">3</span></button>",
      "purposeEn": "A horizontally scrolling filter chip that narrows results at the top of activity and search views. Desktop counterpart: qf-chip / qf-chips (same tokens --r-pill, --accent-subtle, --accent; label color uses --accent-on-subtle for AA).",
      "a11yEn": "A minimum height of 44px guarantees the touch target, and the selected state is conveyed by aria-selected and a border as well, avoiding reliance on color alone. The default variant uses --accent (a-500) for the selected label, whose contrast may fall below AA 4.5:1 at body text size, so if label legibility matters, use the darker violet (a-600) solid variant (qf-m-filter-chip--solid)."
    },
    {
      "name": "MobileChannelRow",
      "class": "qf-m-channel",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "서버 드로어 안의 채널 목록 한 행입니다.",
      "variants": [
        "qf-m-channel--unread",
        "qf-m-channel__suffix"
      ],
      "requiredTokens": [
        "--s-3",
        "--bg-hover",
        "--bg-selected",
        "--text-muted",
        "--text-strong",
        "--fs-15"
      ],
      "a11y": "최소 높이 44px로 터치 영역을 확보합니다. 선택 상태는 aria-selected=true로 알리고, 읽지 않음은 색뿐 아니라 굵은 글꼴로도 구분합니다.",
      "exampleHtml": "<button class=\"qf-m-channel\" aria-selected=\"true\">general</button>",
      "purposeEn": "A single row in the channel list inside the server drawer.",
      "a11yEn": "A minimum height of 44px secures the touch target. The selected state is announced with aria-selected=true, and unread items are distinguished by bold weight as well as color."
    },
    {
      "name": "MobileComposer",
      "class": "qf-m-composer",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "채팅 입력창과 더하기, 보내기 버튼을 묶은 모바일 작성 영역입니다.",
      "variants": [
        "qf-m-composer__plus",
        "qf-m-composer__input",
        "qf-m-composer__send",
        "qf-m-composer__accessory",
        "qf-m-composer__accessory-btn"
      ],
      "requiredTokens": [
        "--s-3",
        "--s-2",
        "--bg-chat",
        "--bg-input",
        "--accent",
        "--m-touch"
      ],
      "a11y": "버튼은 44px 터치 영역을 지키고 입력은 포커스 시 accent 보더로 가시화합니다. 보낼 수 없을 때는 send 버튼에 disabled를 부여합니다.",
      "exampleHtml": "<form class=\"qf-m-composer\"><button class=\"qf-m-composer__plus\" aria-label=\"Attach\">+</button><textarea class=\"qf-m-composer__input\" aria-label=\"Message input\"></textarea><button class=\"qf-m-composer__send\" aria-label=\"Send\">↑</button></form>",
      "purposeEn": "A mobile composition area that groups the chat input with the add and send buttons.",
      "a11yEn": "Buttons maintain a 44px touch target, and the input is made visible on focus with an accent border. When sending is not possible, apply disabled to the send button."
    },
    {
      "name": "MobileMessage",
      "class": "qf-m-msg",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "호버 툴바 없이 조밀하게 표시하는 모바일 메시지 행입니다.",
      "variants": [
        "qf-m-msg--head",
        "qf-m-msg--cont",
        "qf-m-msg__avatar",
        "qf-m-msg__meta",
        "qf-m-msg__author",
        "qf-m-msg__time",
        "qf-m-msg__body"
      ],
      "requiredTokens": [
        "--m-gutter",
        "--s-4",
        "--fs-15",
        "--fs-chat",
        "--lh-snug",
        "--text-strong",
        "--text"
      ],
      "a11y": "연속 메시지는 qf-m-msg--cont로 아바타와 메타를 시각 생략하되 작성자 정보는 직전 head 메시지로 전달됩니다. 본문은 채팅 폰트 크기 설정을 존중합니다.",
      "exampleHtml": "<div class=\"qf-m-msg qf-m-msg--head\"><img class=\"qf-m-msg__avatar\" src=\"\" alt=\"Alex Carter\"><div><div class=\"qf-m-msg__meta\"><span class=\"qf-m-msg__author\">Alex Carter</span><time class=\"qf-m-msg__time\">3:14 PM</time></div><div class=\"qf-m-msg__body\">Hey there</div></div></div>",
      "purposeEn": "A compact mobile message row rendered without a hover toolbar.",
      "a11yEn": "Consecutive messages use qf-m-msg--cont to visually omit the avatar and metadata, while author information is conveyed by the preceding head message. The body respects the chat font-size setting."
    },
    {
      "name": "MobileSwipeReply",
      "class": "qf-m-swipe",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "메시지를 옆으로 밀 때 드러나는 답장 힌트 아이콘입니다.",
      "variants": [
        "qf-m-swipe"
      ],
      "requiredTokens": [
        "--s-4",
        "--m-swipe-icon-size",
        "--r-pill",
        "--accent",
        "--text-onAccent",
        "--dur-fast"
      ],
      "a11y": "스와이프 제스처의 보조 힌트이며 같은 동작은 메시지 메뉴 등 버튼 경로로도 제공해 제스처 단독 의존을 피합니다. 기본은 pointer-events:none으로 숨겨집니다.",
      "exampleHtml": "<span class=\"qf-m-swipe\" aria-hidden=\"true\">↩</span>",
      "purposeEn": "A reply hint icon revealed when a message is swiped sideways.",
      "a11yEn": "It is a supplementary hint for the swipe gesture; the same action is also available through a button path such as the message menu, avoiding reliance on the gesture alone. By default it is hidden with pointer-events:none."
    },
    {
      "name": "MobileReactionRow",
      "class": "qf-m-react-row",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "빠른 반응 이모지를 가로 스크롤로 고르는 칩 행입니다.",
      "variants": [
        "qf-m-react-chip",
        "qf-m-react-chip--more"
      ],
      "requiredTokens": [
        "--s-4",
        "--m-gutter",
        "--m-touch",
        "--r-pill",
        "--bg-panel",
        "--bg-hover"
      ],
      "a11y": "각 칩은 44px 터치 영역을 갖습니다. 더보기 칩은 추가 이모지 선택을 여는 용도로 aria-label을 부여하세요.",
      "exampleHtml": "<div class=\"qf-m-react-row\" role=\"group\" aria-label=\"Quick reactions\"><button class=\"qf-m-react-chip\">👍</button><button class=\"qf-m-react-chip qf-m-react-chip--more\" aria-label=\"More\">+</button></div>",
      "purposeEn": "A row of chips for picking quick-reaction emoji via horizontal scroll.",
      "a11yEn": "Each chip has a 44px touch target. Give the more chip an aria-label indicating that it opens additional emoji selection."
    },
    {
      "name": "MobileVoice",
      "class": "qf-m-voice",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "채널 안 음성, 통화 참여자 그리드와 제어 버튼을 담는 카드입니다.",
      "variants": [
        "qf-m-voice__head",
        "qf-m-voice__live",
        "qf-m-voice__grid",
        "qf-m-voice__tile",
        "qf-m-voice__tile--speaking",
        "qf-m-voice__tile-muted",
        "qf-m-voice__controls",
        "qf-m-voice__ctrl",
        "qf-m-voice__ctrl--leave"
      ],
      "requiredTokens": [
        "--s-4",
        "--s-5",
        "--m-gutter",
        "--r-xl",
        "--bg-elevated",
        "--border",
        "--ok-400"
      ],
      "a11y": "제어 버튼은 48px로 터치 영역을 확보하고 나가기는 위험 색과 함께 aria-label로 동작을 알립니다. 말하는 중 상태는 테두리 강조와 함께 텍스트로도 전달하세요.",
      "exampleHtml": "<section class=\"qf-m-voice\" aria-label=\"Voice channel\"><div class=\"qf-m-voice__controls\"><button class=\"qf-m-voice__ctrl\" aria-label=\"Mute\">🎤</button><button class=\"qf-m-voice__ctrl qf-m-voice__ctrl--leave\" aria-label=\"Leave\">✕</button></div></section>",
      "purposeEn": "A card holding the participant grid and control buttons for an in-channel voice or video call.",
      "a11yEn": "Control buttons secure a 48px touch target, and the leave button conveys its action with aria-label alongside a danger color. Convey the speaking state with text as well as a border emphasis."
    },
    {
      "name": "MobileNotification",
      "class": "qf-m-notif",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "활동 탭의 알림 한 건을 보여 주는 목록 항목입니다.",
      "variants": [
        "qf-m-notif--unread",
        "qf-m-notif__avatar",
        "qf-m-notif__head",
        "qf-m-notif__actor",
        "qf-m-notif__verb",
        "qf-m-notif__time",
        "qf-m-notif__preview"
      ],
      "requiredTokens": [
        "--m-gutter",
        "--s-2",
        "--accent",
        "--text-strong",
        "--text-secondary",
        "--text-muted",
        "--fs-14"
      ],
      "a11y": "읽지 않음은 좌측 accent 막대와 배경으로 구분하되 텍스트 라벨로도 알려 색 단독 의존을 피합니다.",
      "exampleHtml": "<a class=\"qf-m-notif qf-m-notif--unread\" href=\"#\"><img class=\"qf-m-notif__avatar\" src=\"\" alt=\"Alex Carter\"><div><div class=\"qf-m-notif__head\"><span class=\"qf-m-notif__actor\">Alex Carter</span><span class=\"qf-m-notif__verb\">mentioned you</span><time class=\"qf-m-notif__time\">5m</time></div><p class=\"qf-m-notif__preview\">Can you take a look?</p></div></a>",
      "purposeEn": "A list item that shows a single notification in the activity tab.",
      "a11yEn": "Unread items are distinguished by a left accent bar and background, but are also indicated with a text label to avoid reliance on color alone."
    },
    {
      "name": "MobileOverlappingPanels",
      "class": "qf-m-panels",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "좌측 채널, 중앙 채팅, 우측 멤버 3패널을 겹쳐 슬라이드하는 모바일 셸입니다.",
      "variants": [
        "qf-m-panels--show-left",
        "qf-m-panels--show-right",
        "qf-m-panels--show-center",
        "qf-m-panels--dragging",
        "qf-m-panels--snapping",
        "qf-m-panel-left",
        "qf-m-panel-center",
        "qf-m-panel-right"
      ],
      "requiredTokens": [
        "--m-panel-dur",
        "--m-panel-ease",
        "--w-drawer-left",
        "--w-memberlist",
        "--bg-panel",
        "--bg-chat",
        "--z-drawer"
      ],
      "a11y": "드래그 제스처로 패널을 여닫되 scrim 탭과 버튼 등 비제스처 경로도 함께 제공합니다. 드로어가 열리면 가려진 패널의 초점 이동을 막아 주세요.",
      "exampleHtml": "<div class=\"qf-m-panels qf-m-panels--show-left\"><aside class=\"qf-m-panel-left\"></aside><main class=\"qf-m-panel-center\"></main><aside class=\"qf-m-panel-right\"></aside></div>",
      "purposeEn": "A mobile shell that slides the three panels — channels on the left, chat in the center, members on the right — over one another.",
      "a11yEn": "Open and close panels with drag gestures, but also provide non-gesture paths such as a scrim tap and buttons. When a drawer is open, prevent focus from moving into the panels it covers."
    },
    {
      "name": "MobileServerHeader",
      "class": "qf-m-server-header",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "좌측 패널 채널 목록 상단의 서버명과 메뉴, 액션 버튼 행입니다.",
      "variants": [
        "qf-m-server-header__name",
        "qf-m-server-header__chevron",
        "qf-m-server-header__action"
      ],
      "requiredTokens": [
        "--s-2",
        "--m-gutter",
        "--m-topbar-h",
        "--m-touch",
        "--bg-panel",
        "--divider",
        "--text-strong"
      ],
      "a11y": "전체 헤더는 서버 메뉴 시트를 여는 버튼이며, 우측 액션은 44px 히트박스를 유지합니다. chevron 등 아이콘 버튼에는 aria-label을 부여하세요.",
      "exampleHtml": "<button class=\"qf-m-server-header\"><span class=\"qf-m-server-header__name\">Our Server</span><span class=\"qf-m-server-header__chevron\" aria-hidden=\"true\">▾</span></button>",
      "purposeEn": "The row at the top of the left panel's channel list showing the server name, menu, and action buttons.",
      "a11yEn": "The entire header is a button that opens the server menu sheet, and the right-side actions keep a 44px hit box. Give icon buttons such as the chevron an aria-label."
    },
    {
      "name": "MobileUnreadDivider",
      "class": "qf-m-unread-divider",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "읽지 않은 메시지 경계를 표시하는 구분선과 라벨입니다.",
      "variants": [
        "qf-m-unread-divider__label",
        "qf-m-unread-divider__pill"
      ],
      "requiredTokens": [
        "--s-3",
        "--m-gutter",
        "--unread-divider-accent",
        "--badge-unread-bg",
        "--text-onAccent",
        "--r-pill",
        "--fs-11"
      ],
      "a11y": "색 선뿐 아니라 읽지 않음 라벨 텍스트로도 경계를 알립니다. role=separator와 함께 안내 텍스트를 제공하세요.",
      "exampleHtml": "<div class=\"qf-m-unread-divider\" role=\"separator\"><span class=\"qf-m-unread-divider__label\">Unread</span><span class=\"qf-m-unread-divider__pill\">3</span></div>",
      "purposeEn": "A divider line and label that marks the boundary of unread messages.",
      "a11yEn": "Indicate the boundary with the unread label text as well as the colored line. Provide guidance text along with role=separator."
    },
    {
      "name": "MobileJumpButton",
      "class": "qf-m-jump-btn",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "목록 맨 아래로 이동하는 알약형 떠 있는 버튼입니다.",
      "variants": [
        "qf-m-jump-btn__badge"
      ],
      "requiredTokens": [
        "--m-gutter",
        "--m-touch",
        "--r-pill",
        "--bg-elevated",
        "--border",
        "--elev-3",
        "--badge-unread-bg"
      ],
      "a11y": "최소 44px 높이를 지키고 aria-label로 맨 아래 이동임을 알립니다. 배지의 읽지 않음 수는 텍스트로 전달됩니다.",
      "exampleHtml": "<button class=\"qf-m-jump-btn\" aria-label=\"Jump to bottom\">↓<span class=\"qf-m-jump-btn__badge\">5</span></button>",
      "purposeEn": "A pill-shaped floating button that jumps to the bottom of the list.",
      "a11yEn": "It keeps a minimum height of 44px and uses aria-label to indicate that it jumps to the bottom. The unread count in the badge is conveyed as text."
    },
    {
      "name": "MobileReactToast",
      "class": "qf-m-react-toast",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "두 번 탭으로 빠르게 반응했을 때 하단 중앙에 등장하는 알림 버블입니다.",
      "variants": [
        "qf-m-react-toast__emoji"
      ],
      "requiredTokens": [
        "--m-composer-h",
        "--m-tabbar-h",
        "--s-2",
        "--s-4",
        "--r-pill",
        "--bg-elevated",
        "--z-toast"
      ],
      "a11y": "동작 결과를 알리는 일시 토스트이므로 role=status로 보조 기술에 변화를 전달합니다. 스프링 등장 모션은 reduce-motion 환경을 존중하세요.",
      "exampleHtml": "<div class=\"qf-m-react-toast\" role=\"status\"><span class=\"qf-m-react-toast__emoji\">👍</span> Reaction added</div>",
      "purposeEn": "A notification bubble that appears at the bottom center after a double-tap quick reaction.",
      "a11yEn": "As a transient toast that reports the result of an action, it uses role=status to convey the change to assistive technology. Honor reduce-motion environments for the spring entrance animation."
    },
    {
      "name": "MobileImageGrid",
      "class": "qf-m-img-grid",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "메시지에 첨부된 여러 이미지를 격자로 배치하는 컨테이너입니다.",
      "variants": [
        "qf-m-img-grid--1",
        "qf-m-img-grid--3",
        "qf-m-img-grid--4",
        "qf-m-img-grid__tile",
        "qf-m-img-grid__more"
      ],
      "requiredTokens": [
        "--s-1",
        "--r-md",
        "--bg-panel",
        "--bg-floating",
        "--text-strong",
        "--fs-20"
      ],
      "a11y": "각 이미지에는 의미 있는 alt를 제공하고, 추가 매수 오버레이는 더 많은 이미지가 있음을 텍스트로 알립니다.",
      "exampleHtml": "<div class=\"qf-m-img-grid qf-m-img-grid--4\"><div class=\"qf-m-img-grid__tile\"><img src=\"\" alt=\"Photo 1\"></div><div class=\"qf-m-img-grid__tile\"><img src=\"\" alt=\"Photo 2\"><span class=\"qf-m-img-grid__more\">+3</span></div></div>",
      "purposeEn": "A container that arranges multiple images attached to a message in a grid.",
      "a11yEn": "Provide meaningful alt text for each image, and have the overflow-count overlay indicate as text that more images are available."
    },
    {
      "name": "MobileTile",
      "class": "qf-m-tile",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "홈 상단 퀵 타일 한 장으로 Catch Up, Threads 등 진입점을 보여 줍니다.",
      "variants": [
        "qf-m-tile--unread",
        "qf-m-tile__icon",
        "qf-m-tile__label",
        "qf-m-tile__count",
        "qf-m-tile-row"
      ],
      "requiredTokens": [
        "--s-3",
        "--s-4",
        "--m-tile-h",
        "--r-lg",
        "--bg-panel",
        "--accent",
        "--text-strong"
      ],
      "a11y": "읽지 않음은 accent 보더와 함께 카운트 텍스트로 알려 색 단독 의존을 피합니다. 타일은 버튼으로 키보드 접근이 가능합니다.",
      "exampleHtml": "<button class=\"qf-m-tile qf-m-tile--unread\"><span class=\"qf-m-tile__icon\" aria-hidden=\"true\">🧵</span><span class=\"qf-m-tile__label\">Threads</span><span class=\"qf-m-tile__count\">3</span></button>",
      "purposeEn": "A single quick tile at the top of the home view, surfacing entry points such as Catch Up and Threads.",
      "a11yEn": "Unread items are indicated with count text alongside an accent border to avoid reliance on color alone. Tiles are buttons and keyboard-accessible."
    },
    {
      "name": "MobileYou",
      "class": "qf-m-you-header",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "프로필 탭 상단의 아바타, 이름, 상태, 핸들을 보여 주는 헤더입니다.",
      "variants": [
        "qf-m-you-header--idle",
        "qf-m-you-header--dnd",
        "qf-m-you-header--offline",
        "qf-m-you-header__name",
        "qf-m-you-header__handle",
        "qf-m-you-header__state",
        "qf-m-you-header__meta",
        "qf-m-you-status",
        "qf-m-you-section",
        "qf-m-you-card"
      ],
      "requiredTokens": [
        "--s-4",
        "--s-6",
        "--m-gutter",
        "--fs-20",
        "--text-strong",
        "--text-muted",
        "--status-online",
        "--sz-status-dot"
      ],
      "a11y": "상태 점은 색만으로 의미를 주지 않도록 상태 텍스트를 함께 표기합니다. 이름과 핸들은 말줄임 처리되며 전체 값을 접근 가능하게 노출하세요.",
      "exampleHtml": "<header class=\"qf-m-you-header qf-m-you-header--dnd\"><img src=\"\" alt=\"My profile\"><div class=\"qf-m-you-header__meta\"><div class=\"qf-m-you-header__name\">Alex Carter</div><div class=\"qf-m-you-header__state\">Do Not Disturb</div></div></header>",
      "purposeEn": "A header at the top of the profile tab showing the avatar, name, status, and handle.",
      "a11yEn": "The status dot is accompanied by status text so it does not convey meaning by color alone. The name and handle are truncated with an ellipsis, so expose their full values accessibly."
    },
    {
      "name": "MobileThreadInbox",
      "class": "qf-m-thread-inbox",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "스레드 인박스 목록 항목으로 채널 맥락과 답글 미리보기, 읽지 않음 수를 보여 줍니다.",
      "variants": [
        "qf-m-thread-inbox__item",
        "qf-m-thread-inbox__item--unread",
        "qf-m-thread-inbox__avatars",
        "qf-m-thread-inbox__context",
        "qf-m-thread-inbox__preview",
        "qf-m-thread-inbox__count",
        "qf-m-thread-inbox__body"
      ],
      "requiredTokens": [
        "--s-3",
        "--m-gutter",
        "--m-dm-row-h",
        "--badge-unread-bg",
        "--text-onAccent",
        "--divider",
        "--text-secondary"
      ],
      "a11y": "항목은 버튼으로 64px 높이를 유지합니다. 읽지 않음은 텍스트 색 강조와 카운트 텍스트로 함께 전달합니다.",
      "exampleHtml": "<button class=\"qf-m-thread-inbox__item qf-m-thread-inbox__item--unread\"><span class=\"qf-m-thread-inbox__avatars\"></span><span class=\"qf-m-thread-inbox__body\"><span class=\"qf-m-thread-inbox__context\"># general</span><span class=\"qf-m-thread-inbox__preview\">New replies</span></span><span class=\"qf-m-thread-inbox__count\">2</span></button>",
      "purposeEn": "A thread inbox list item that shows the channel context, a reply preview, and the unread count.",
      "a11yEn": "Each item is a button that keeps a 64px height. Unread state is conveyed with both emphasized text color and count text."
    },
    {
      "name": "MobileChannelBrowser",
      "class": "qf-m-channel-browser",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "서버 채널 둘러보기 목록 항목으로 채널명, 설명, 멤버수와 참여 버튼을 제공합니다.",
      "variants": [
        "qf-m-channel-browser__item",
        "qf-m-channel-browser__name",
        "qf-m-channel-browser__desc",
        "qf-m-channel-browser__members",
        "qf-m-channel-browser__join"
      ],
      "requiredTokens": [
        "--s-3",
        "--m-gutter",
        "--m-touch",
        "--accent",
        "--accent-press",
        "--text-strong",
        "--text-muted",
        "--r-md"
      ],
      "a11y": "참여 버튼은 44px 히트박스를 지키고 이미 참여한 상태는 aria-pressed=true로 알립니다. 채널명 앞 # 표시 외에 의미는 텍스트로 전달합니다.",
      "exampleHtml": "<div class=\"qf-m-channel-browser__item\"><span class=\"qf-m-channel-browser__name\">general</span><span class=\"qf-m-channel-browser__desc\">Open chat</span><button class=\"qf-m-channel-browser__join\">Join</button></div>",
      "purposeEn": "A server channel browser list item that provides the channel name, description, member count, and a join button.",
      "a11yEn": "The join button keeps a 44px hit box, and the already-joined state is announced with aria-pressed=true. Beyond the leading # on the channel name, meaning is conveyed as text."
    },
    {
      "name": "MobileEmojiDrawer",
      "class": "qf-m-emoji-drawer",
      "layer": "mobile",
      "category": "Mobile",
      "purpose": "하단에서 올라오는 이모지 선택 드로어로 검색, 카테고리 탭, 격자 셀을 담습니다.",
      "variants": [
        "qf-m-emoji-drawer__grab",
        "qf-m-emoji-drawer__search",
        "qf-m-emoji-drawer__tabs",
        "qf-m-emoji-drawer__tab",
        "qf-m-emoji-drawer__grid",
        "qf-m-emoji-drawer__row",
        "qf-m-emoji-drawer__cell",
        "qf-m-emoji-drawer__category-label"
      ],
      "requiredTokens": [
        "--m-sheet-r",
        "--m-touch",
        "--m-gutter",
        "--bg-elevated",
        "--bg-input",
        "--accent",
        "--text-muted"
      ],
      "a11y": "카테고리 탭과 이모지 셀은 44px 터치 영역을 지킵니다. 선택된 탭은 aria-selected=true로 알리고 각 이모지 셀에는 이름 aria-label을 부여하세요.",
      "exampleHtml": "<div class=\"qf-m-emoji-drawer\" role=\"dialog\" aria-label=\"Pick an emoji\"><span class=\"qf-m-emoji-drawer__grab\"></span><div class=\"qf-m-emoji-drawer__tabs\" role=\"tablist\"><button class=\"qf-m-emoji-drawer__tab\" role=\"tab\" aria-selected=\"true\">😀</button></div><div class=\"qf-m-emoji-drawer__grid\"><div class=\"qf-m-emoji-drawer__row\"><button class=\"qf-m-emoji-drawer__cell\" aria-label=\"Grinning face\">😀</button></div></div></div>",
      "purposeEn": "An emoji picker drawer that slides up from the bottom, holding a search field, category tabs, and grid cells.",
      "a11yEn": "Category tabs and emoji cells keep a 44px touch target. Announce the selected tab with aria-selected=true and give each emoji cell a name via aria-label."
    },
    {
      "name": "MediaCard",
      "class": "qf-media-card",
      "layer": "generic",
      "category": "Media",
      "purpose": "종횡비 고정 미디어 슬롯(레터박스)에 오버레이 배지, 토글을 얹는 썸네일 카드입니다. 비디오, 이미지, 포스터 그리드에 씁니다.",
      "purposeEn": "A thumbnail card with an aspect-ratio-locked media slot (letterbox) plus overlay badges and toggles — for video, image, and poster grids.",
      "variants": [
        "qf-media-card__media",
        "qf-media-card__overlay",
        "qf-media-card__overlay--start",
        "qf-media-card__overlay--end",
        "qf-media-card__body",
        "qf-media-card__title",
        "qf-media-card__meta",
        "qf-media-card__skeleton",
        "qf-media-card__fallback",
        "qf-media-card--2x3",
        "qf-media-card--auto",
        "qf-media-card--interactive",
        "qf-media-card--error"
      ],
      "requiredTokens": [
        "--bg-media",
        "--media-overlay-scrim",
        "--aspect-16x9",
        "--aspect-2x3",
        "--r-lg",
        "--border",
        "--border-strong",
        "--bg-panel",
        "--elev-3",
        "--ring-accent",
        "--s-2",
        "--s-3",
        "--fs-13",
        "--fs-12",
        "--text",
        "--text-muted",
        "--dur-fast",
        "--ease-standard"
      ],
      "a11y": "활성화 가능한 미디어 슬롯은 role=button, tabindex=0, Enter/Space, aria-label 로 동작을 전달합니다. 오버레이 토글은 실제 button + aria-pressed + 44px, 배지 텍스트는 스크림 위에서 4.5:1, 스켈레톤은 aria-busy, 잘린 제목은 title 속성을 둡니다.",
      "a11yEn": "Make activatable media slots operable with role=button, tabindex=0, Enter/Space, and aria-label. Overlay toggles are real buttons with aria-pressed and a 44px target; badge text keeps 4.5:1 over the scrim; skeletons set aria-busy; truncated titles carry a title attribute.",
      "variantsAsChips": true,
      "exampleHtml": "<div class=\"qf-media-card qf-media-card--interactive\" style=\"max-width:260px\"><div class=\"qf-media-card__media\"><div class=\"qf-media-card__overlay qf-media-card__overlay--start\"><span class=\"qf-badge\">12:34</span></div><div class=\"qf-media-card__overlay qf-media-card__overlay--end\"><button class=\"qf-btn qf-btn--icon qf-btn--ghost\" aria-label=\"Favorite\" aria-pressed=\"false\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-star\"/></svg></button></div></div><div class=\"qf-media-card__body\"><div class=\"qf-media-card__title qf-clamp\">Sunset over the harbor, extended cut</div><div class=\"qf-media-card__meta\"><span>1080p</span><span>248 MB</span><span>2 days ago</span></div></div></div>",
      "examples": [
        {
          "label": "Poster (--2x3)",
          "html": "<div class=\"qf-media-card qf-media-card--2x3 qf-media-card--interactive\" style=\"max-width:180px\"><div class=\"qf-media-card__media\"><div class=\"qf-media-card__overlay qf-media-card__overlay--end\"><span class=\"qf-badge qf-badge--accent\">NEW</span></div></div><div class=\"qf-media-card__body\"><div class=\"qf-media-card__title qf-clamp\">The Lighthouse</div><div class=\"qf-media-card__meta\"><span>2019</span><span>Drama</span></div></div></div>"
        },
        {
          "label": "Loading (--loading + qf-skel)",
          "html": "<div class=\"qf-media-card\" style=\"max-width:260px\"><div class=\"qf-media-card__media\"><div class=\"qf-media-card__skeleton qf-skel\" aria-busy=\"true\"></div></div><div class=\"qf-media-card__body\"><div class=\"qf-skel\" style=\"height:var(--s-4);width:80%\"></div><div class=\"qf-skel\" style=\"height:var(--s-3);width:50%\"></div></div></div>"
        },
        {
          "label": "Error (--error + __fallback)",
          "html": "<div class=\"qf-media-card qf-media-card--error\" style=\"max-width:260px\"><div class=\"qf-media-card__media\"><div class=\"qf-media-card__fallback\"><svg class=\"qf-icon qf-icon--lg\" aria-hidden=\"true\"><use href=\"#qf-i-image\"/></svg><span>Couldn't load preview</span><button class=\"qf-btn qf-btn--secondary qf-btn--sm\" type=\"button\">Retry</button></div></div><div class=\"qf-media-card__body\"><div class=\"qf-media-card__title qf-clamp\">Broken asset</div></div></div>"
        }
      ]
    },
    {
      "name": "Figure",
      "class": "qf-figure",
      "layer": "generic",
      "category": "Media",
      "purpose": "그림(또는 영상)과 그 아래 한 줄 캡션입니다. 캡션은 13px 보조 톤이고, 안의 링크는 밑줄과 qf-i-external 아이콘으로 링크임이 색 외에도 드러납니다. \"이미지 출처: AI 생성\" 같은 비링크 문구도 같은 자리에 아이콘 없이 둡니다. 그림 최대 높이는 --qf-figure-max-h 로 제한할 수 있습니다(기본 없음).",
      "purposeEn": "A picture (or video) with a one-line caption below. The caption is 13px in a secondary tone; a link inside is underlined and carries a qf-i-external icon so it reads as a link beyond color. Non-link notes such as \"Image: AI generated\" sit in the same place without an icon. Limit the image height with --qf-figure-max-h (none by default).",
      "variants": [
        "qf-figure__caption"
      ],
      "requiredTokens": [
        "--s-2",
        "--s-1",
        "--r-lg",
        "--bg-media",
        "--fs-13",
        "--lh-snug",
        "--text-muted",
        "--text-secondary",
        "--text",
        "--border-strong"
      ],
      "a11y": "figure/figcaption 으로 두어 그림과 설명이 묶이게 하고, img 에는 내용을 설명하는 alt 를 둡니다(캡션은 출처, alt 는 내용). 외부 링크는 target=_blank 와 rel=noopener 를 쓰고 아이콘은 aria-hidden, 새 창 열림은 링크 텍스트나 aria-label 로 알립니다. 캡션 링크는 본문 안 인라인 링크라 세로 패딩만 더해 탭을 돕습니다.",
      "a11yEn": "Use figure/figcaption so image and caption are associated, and give the img an alt that describes the content (caption = source, alt = content). External links use target=_blank with rel=noopener, the icon is aria-hidden, and opening in a new window is conveyed in the link text or aria-label. Caption links are inline text links, so only vertical padding is added to help tapping.",
      "exampleHtml": "<figure class=\"qf-figure\" style=\"max-width:420px\"><img src=\"brand-assets/png/og-image.png\" alt=\"qufox brand card\"><figcaption class=\"qf-figure__caption\">Image: <a href=\"#source\" target=\"_blank\" rel=\"noopener\">Ruliweb post<svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-external\"/></svg></a></figcaption></figure>",
      "examples": [
        {
          "label": "Linked source",
          "html": "<figure class=\"qf-figure\" style=\"max-width:360px\"><img src=\"brand-assets/png/og-image.png\" alt=\"qufox brand card\"><figcaption class=\"qf-figure__caption\">Image: <a href=\"#source\" target=\"_blank\" rel=\"noopener\">Ruliweb post<svg class=\"qf-icon qf-icon--sm\" aria-hidden=\"true\"><use href=\"#qf-i-external\"/></svg></a></figcaption></figure>"
        },
        {
          "label": "AI generated (no link)",
          "html": "<figure class=\"qf-figure\" style=\"max-width:360px\"><img src=\"brand-assets/png/og-image.png\" alt=\"Illustration of the issue\"><figcaption class=\"qf-figure__caption\">Image: AI generated</figcaption></figure>"
        }
      ]
    },
    {
      "name": "Pick",
      "class": "qf-pick",
      "layer": "generic",
      "category": "Forms",
      "purpose": "축소 사본을 보고 하나를 고르는 이미지 선택 타일입니다(라디오 동작). <label class=\"qf-pick\"> 안에 input[type=radio].qf-pick__input(투명하게 깔려 타일 전체가 히트), 틀 qf-pick__frame(4:3, --bg-media, img 는 object-fit: contain — 이미지 전체를 봐야 하므로 cover 가 아님), 왼쪽 아래 태그 qf-pick__tag(미디어 위 글래스, 짧은 글자), 한 줄 라벨 qf-pick__label(12px, 말줄임)을 둡니다. 상태: checked 는 틀 바깥 액센트 외곽선(2px, offset 2), focus-visible 은 --ring-focus, disabled 는 흐림, hover 는 --border-strong 외곽선. 변형: qf-pick__frame--none(점선 틀에 글자 하나, \"없음\"), qf-pick--muted(후보가 아닌 것 — 이미지 채도·밝기 낮춤 + 태그 경고색), qf-pick--current(지금 보이는 것 — 라벨 진하게). 줄은 qf-picks: 가로 스크롤 + scroll-snap, 안쪽 여백으로 외곽선·링이 잘리지 않고, 오른쪽 페이드는 qf-chips--scroll 과 같습니다(넘칠 때만, 끝에서 걷힘, 미지원은 is-end). 타일 폭 --sz-pick(160px, 390px 에서 2.1장).",
      "purposeEn": "An image-choice tile that behaves like a radio: pick one from a row of thumbnails. A <label class=\"qf-pick\"> holds input[type=radio].qf-pick__input (laid transparently over the tile so the whole tile is the hit area), a frame qf-pick__frame (4:3, --bg-media, img uses object-fit: contain because the whole picture must be visible — not cover), a bottom-left tag qf-pick__tag (on-media glass, short text), and a one-line label qf-pick__label (12px, ellipsis). States: checked draws an accent outline outside the frame (2px, offset 2), focus-visible shows --ring-focus, disabled dims, hover shows a --border-strong outline. Variants: qf-pick__frame--none (dashed frame with a single word, \"None\"), qf-pick--muted (not a candidate — desaturated, darker image and a warning-colored tag), qf-pick--current (what is shown now — bolder label). The row is qf-picks: horizontal scroll with scroll-snap, inner padding so outlines and rings are not clipped, and the same right-edge fade as qf-chips--scroll (only when overflowing, clears at the end, is-end fallback). Tile width --sz-pick (160px; 2.1 tiles at 390px).",
      "variants": [
        "qf-pick__input",
        "qf-pick__frame",
        "qf-pick__frame--none",
        "qf-pick__tag",
        "qf-pick__label",
        "qf-pick--muted",
        "qf-pick--current",
        "qf-picks"
      ],
      "requiredTokens": [
        "--sz-pick",
        "--aspect-4x3",
        "--bg-media",
        "--r-md",
        "--r-xs",
        "--s-1",
        "--s-2",
        "--s-3",
        "--s-4",
        "--s-10",
        "--fs-12",
        "--fs-11",
        "--lh-normal",
        "--lh-snug",
        "--tracking-normal",
        "--text-muted",
        "--text-strong",
        "--border-strong",
        "--accent",
        "--ring-focus",
        "--on-media-glass-bg",
        "--on-media-text",
        "--warn-400",
        "--dur-fast",
        "--ease-standard",
        "--font-sans",
        "--on-media-glass-bg-strong",
        "--text-disabled"
      ],
      "a11y": "줄을 role=\"radiogroup\" + aria-label 로 묶고 타일은 label > input[type=radio] 라 클릭·Space·화살표 이동이 네이티브로 동작합니다(화살표로 옮기면 scroll-padding 으로 타일이 줄 안에 들어옵니다). 라디오의 접근 가능한 이름은 label 의 텍스트(태그 + 라벨)이며, 설명이 더 필요하면 label 의 title 이나 aria-describedby 로. img 는 장식이면 alt=\"\", 내용이 중요하면 설명. 태그는 미디어 위 글래스(--on-media-glass-bg + --on-media-text)라 어떤 이미지 위에서도 AA. muted 태그는 진한 글래스(--on-media-glass-bg-strong) 위 --warn-400 — 흰 이미지·라이트 레터박스 위에서도 6.8:1(.68 글래스는 4.1 로 미달). 타일 160×120 이라 터치 대상이 충분하고 터치 환경에서는 타일 간격이 --s-4 로 넓어집니다. checked 외곽선과 포커스 링이 겹치면 4px 액센트 띠로 보이며 둘 다 액센트라 \"선택된 것에 포커스\" 로 읽힙니다. disabled 타일은 이미지만 흐려지고 라벨은 --text-disabled 로 읽히며 hover 에 반응하지 않습니다. 선택·포커스·disabled 표시는 :has() 에 기대므로 미지원 브라우저(Firefox < 121, Safari < 15.4)에서는 상태가 보이지 않습니다.",
      "a11yEn": "Wrap the row in role=\"radiogroup\" with an aria-label; each tile is label > input[type=radio], so click, Space and arrow-key movement work natively (arrow focus scrolls the tile into view via scroll-padding). The radio's accessible name is the label text (tag + label); add a title or aria-describedby for more. Use alt=\"\" for decorative thumbnails, a description when the content matters. The tag sits on an on-media glass (--on-media-glass-bg + --on-media-text), AA on any image; the muted tag uses --warn-400 on the strong glass (--on-media-glass-bg-strong): 6.8:1 even over a white image or the light letterbox (the .68 glass would be 4.1). Tiles are 160×120, an ample touch target, and the gap widens to --s-4 on touch devices. When the checked outline and the focus ring overlap they read as a 4px accent band — both are accent, so it reads as \"the selected one is focused\". A disabled tile only dims its image; the label stays readable in --text-disabled and the tile does not react to hover. Checked, focus and disabled styling rely on :has(), so browsers without it (Firefox < 121, Safari < 15.4) show no state.",
      "exampleHtml": "<div class=\"qf-picks\" role=\"radiogroup\" aria-label=\"Image to show\"><label class=\"qf-pick qf-pick--current\"><input class=\"qf-pick__input\" type=\"radio\" name=\"pick\" value=\"1\" checked><span class=\"qf-pick__frame\"><img src=\"brand-assets/png/og-image.png\" alt=\"\"><span class=\"qf-pick__tag\">Shown</span></span><span class=\"qf-pick__label\">Clien, photo</span></label><label class=\"qf-pick\"><input class=\"qf-pick__input\" type=\"radio\" name=\"pick\" value=\"2\"><span class=\"qf-pick__frame\"><img src=\"brand-assets/png/icon-512.png\" alt=\"\"><span class=\"qf-pick__tag\">AI</span></span><span class=\"qf-pick__label\">AI image</span></label><label class=\"qf-pick qf-pick--muted\"><input class=\"qf-pick__input\" type=\"radio\" name=\"pick\" value=\"3\"><span class=\"qf-pick__frame\"><img src=\"brand-assets/png/og-image.png\" alt=\"\"><span class=\"qf-pick__tag\">Screenshot</span></span><span class=\"qf-pick__label\">Ruliweb, screenshot of an article</span></label><label class=\"qf-pick\"><input class=\"qf-pick__input\" type=\"radio\" name=\"pick\" value=\"none\"><span class=\"qf-pick__frame qf-pick__frame--none\">None</span><span class=\"qf-pick__label\">No image</span></label></div>",
      "wideExamples": true,
      "variantsAsChips": true
    },
    {
      "name": "Clamp",
      "class": "qf-clamp",
      "layer": "generic",
      "category": "Display",
      "purpose": "텍스트를 N줄에서 말줄임하는 유틸리티입니다. 카드 제목, 설명을 균일하게 자릅니다.",
      "purposeEn": "A utility that truncates text to N lines with an ellipsis — keeps card titles and descriptions uniform.",
      "variants": [
        "qf-clamp--1",
        "qf-clamp--3",
        "qf-clamp--reserve"
      ],
      "requiredTokens": [
        "--lh-snug"
      ],
      "a11y": "시각적 절단만이며 전체 텍스트는 접근성 트리에 유지합니다(클램프 노드에 aria-hidden 금지). 잘릴 수 있으면 title 이나 qf-tooltip 을 함께 제공합니다.",
      "a11yEn": "Visual truncation only — the full text stays in the accessibility tree (never aria-hidden the clamp node). When text may be cut, pair it with a title attribute or qf-tooltip.",
      "exampleHtml": "<p class=\"qf-clamp\" style=\"max-width:320px;margin:0\">This is a longer description that clamps to two lines with an ellipsis when it overflows its container width, keeping cards uniform.</p>",
      "examples": [
        {
          "label": "3 lines (--3)",
          "html": "<p class=\"qf-clamp qf-clamp--3\" style=\"max-width:320px;margin:0\">This variant clamps to three lines instead of two. Useful for denser cards where a little more context fits before the ellipsis truncates the remaining overflow text.</p>"
        },
        {
          "label": "1 line (--1)",
          "html": "<p class=\"qf-clamp qf-clamp--1\" style=\"max-width:320px;margin:0\">A single-line clamp behaves like a truncating ellipsis on overflow.</p>"
        }
      ]
    },
    {
      "name": "MediaOverlay",
      "class": "qf-media-overlay",
      "layer": "generic",
      "category": "Media",
      "purpose": "영상이나 이미지 스테이지 위에 떠서 컨트롤과 상태를 보여주는 글래스 크롬입니다. 테마와 무관하게 항상 밝고, safe-area를 피하며, pointer-events 패스스루로 네이티브 컨트롤을 가리지 않습니다.",
      "purposeEn": "Glass control and status chrome that floats over a video or image stage. Stays light regardless of theme, avoids the safe area, and passes pointer events through so it never blocks native controls.",
      "variants": [
        "qf-media-overlay__top",
        "qf-media-overlay__bottom",
        "qf-media-overlay__pill",
        "qf-media-overlay__pill--button",
        "qf-media-overlay__pill--danger",
        "qf-media-overlay__hud",
        "qf-media-overlay__hud--error",
        "qf-media-overlay__title",
        "qf-media-overlay__body"
      ],
      "requiredTokens": [
        "--on-media-text",
        "--on-media-text-muted",
        "--on-media-glass-bg",
        "--on-media-glass-bg-strong",
        "--on-media-border",
        "--on-media-border-hover",
        "--on-media-danger-text",
        "--on-media-danger-border",
        "--safe-top",
        "--safe-bottom",
        "--z-media-overlay",
        "--r-pill",
        "--r-lg",
        "--ring-focus",
        "--scrim-blur",
        "--fs-13",
        "--fs-15"
      ],
      "a11y": "__pill--button은 실제 button + 영상 위에서 보이는 포커스 링 + 44px 입니다. __hud는 버퍼링 시 role=status, aria-live=polite, 에러 시 role=alert 를 둡니다. 컨테이너 pointer-events:none이 포커스를 가두거나 네이티브 컨트롤 키보드 접근을 막지 않아야 합니다.",
      "a11yEn": "__pill--button is a real button with a focus ring visible over video and a 44px target. __hud uses role=status + aria-live=polite while buffering and role=alert on error. The container's pointer-events:none must not trap focus or block keyboard access to native controls.",
      "exampleHtml": "<div style=\"position:relative;aspect-ratio:var(--aspect-16x9);max-width:440px;background:var(--bg-media);border-radius:var(--r-lg);overflow:hidden\"><div class=\"qf-media-overlay\"><div class=\"qf-media-overlay__top\"><span class=\"qf-media-overlay__pill\">LIVE</span><span style=\"flex:1\"></span><button class=\"qf-media-overlay__pill qf-media-overlay__pill--button\" aria-label=\"Settings\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-settings\"/></svg></button></div><div class=\"qf-media-overlay__bottom\"><button class=\"qf-media-overlay__pill qf-media-overlay__pill--button\" aria-label=\"Play\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-video\"/></svg></button><span class=\"qf-media-overlay__pill\">12:34 / 48:02</span></div></div></div>",
      "examples": [
        {
          "label": "Buffering HUD (role=status)",
          "html": "<div style=\"position:relative;aspect-ratio:var(--aspect-16x9);max-width:440px;background:var(--bg-media);border-radius:var(--r-lg);overflow:hidden\"><div class=\"qf-media-overlay\"><div class=\"qf-media-overlay__hud\" role=\"status\" aria-live=\"polite\"><span class=\"qf-spinner\" aria-hidden=\"true\"></span><span class=\"qf-media-overlay__title\">Buffering…</span><span class=\"qf-media-overlay__body\">Reconnecting to stream</span></div></div></div>"
        },
        {
          "label": "Error HUD (role=alert)",
          "html": "<div style=\"position:relative;aspect-ratio:var(--aspect-16x9);max-width:440px;background:var(--bg-media);border-radius:var(--r-lg);overflow:hidden\"><div class=\"qf-media-overlay\"><div class=\"qf-media-overlay__hud qf-media-overlay__hud--error\" role=\"alert\"><span class=\"qf-media-overlay__title\">Playback failed</span><span class=\"qf-media-overlay__body\">The video could not be loaded.</span><button class=\"qf-media-overlay__pill qf-media-overlay__pill--button qf-media-overlay__pill--danger\" type=\"button\">Retry</button></div></div></div>"
        }
      ]
    },
    {
      "name": "Lightbox",
      "class": "qf-lightbox",
      "layer": "generic",
      "category": "Overlays",
      "purpose": "크롬 없는 풀스크린 contain-fit 미디어 뷰어(줌) 또는 풀블리드 스크롤 컨택트 시트입니다. 다이얼로그(qf-modal)와 달리 패널 크롬 없이 미디어만 담습니다.",
      "purposeEn": "A chrome-free fullscreen contain-fit media viewer (zoom) or a full-bleed scrolling contact sheet. Unlike a dialog (qf-modal), it holds only media — no panel chrome.",
      "variants": [
        "qf-lightbox__close",
        "qf-lightbox__stage",
        "qf-lightbox__media",
        "qf-lightbox__sheet",
        "qf-lightbox__frame",
        "qf-lightbox__caption",
        "qf-lightbox--dim",
        "qf-lightbox--sheet"
      ],
      "requiredTokens": [
        "--scrim",
        "--scrim-strong",
        "--scrim-blur",
        "--z-modal",
        "--on-media-glass-bg",
        "--on-media-border",
        "--on-media-text",
        "--r-md",
        "--safe-top",
        "--ring-focus",
        "--s-8"
      ],
      "variantsAsChips": true,
      "a11y": "role=dialog + aria-modal=true + aria-label. 포커스를 가두고 열 때 닫기 버튼으로 포커스 이동, 닫을 때 트리거로 복원. Esc와 스크림 클릭으로 닫고(미디어 클릭은 stopPropagation), 닫기 버튼은 44px, prefers-reduced-motion을 존중합니다.",
      "a11yEn": "role=dialog + aria-modal=true + aria-label. Trap focus, move focus to the close button on open, and restore it to the trigger on close. Close on Esc and scrim click (stop propagation on media clicks); the close button is 44px; respect prefers-reduced-motion.",
      "exampleHtml": "<div class=\"qf-lightbox\"><button class=\"qf-lightbox__close\" aria-label=\"Close\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-x\"/></svg></button><div class=\"qf-lightbox__stage\"><div class=\"qf-lightbox__media\" style=\"width:260px;aspect-ratio:3 / 2;background:var(--bg-media)\"></div></div></div>",
      "examples": [
        {
          "label": "Contact sheet (--sheet)",
          "html": "<div class=\"qf-lightbox qf-lightbox--sheet\"><button class=\"qf-lightbox__close\" aria-label=\"Close\"><svg class=\"qf-icon qf-icon--md\" aria-hidden=\"true\"><use href=\"#qf-i-x\"/></svg></button><div class=\"qf-lightbox__sheet\"><div class=\"qf-lightbox__frame\" style=\"width:240px;aspect-ratio:16 / 9;background:var(--bg-media);border-radius:var(--r-md)\"></div><div class=\"qf-lightbox__frame\" style=\"width:240px;aspect-ratio:16 / 9;background:var(--bg-media);border-radius:var(--r-md)\"></div></div></div>"
        }
      ]
    },
    {
      "name": "TypingDots",
      "class": "qf-typing-dots",
      "layer": "generic",
      "category": "Feedback",
      "purpose": "standalone 3점 staggered bounce로 '생성 중'/입력 중을 표시합니다. spinner(작업 중), progress(정량)와 의미가 다릅니다.",
      "purposeEn": "A three-dot staggered bounce that signals 'generating' or 'typing' — semantically distinct from a spinner (working) or progress (quantified).",
      "variants": [
        "qf-typing-dots__dot",
        "qf-typing-dots--onAccent",
        "qf-typing-dots--sm",
        "qf-typing-dots--lg"
      ],
      "requiredTokens": [
        "--size-typing-dot",
        "--motion-typing",
        "--text-muted",
        "--accent",
        "--s-1",
        "--r-pill",
        "--ease-standard"
      ],
      "a11y": "루트에 role=status, aria-live=polite, 접근명(예: aria-label='생성 중')을 두고 점은 aria-hidden 입니다. prefers-reduced-motion 에서 bounce를 opacity 페이드로 대체하고 scale(0)로 얼려 안 보이게 두지 않습니다.",
      "a11yEn": "Give the root role=status, aria-live=polite, and an accessible name (e.g. aria-label='Generating'); the dots are aria-hidden. Under prefers-reduced-motion, replace the bounce with an opacity fade — never freeze it at scale(0).",
      "exampleHtml": "<span class=\"qf-typing-dots\" role=\"status\" aria-label=\"Generating\"><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span></span>",
      "examples": [
        {
          "label": "On accent surface (--onAccent)",
          "html": "<span style=\"display:inline-flex;padding:var(--s-3) var(--s-4);background:var(--accent-subtle);border-radius:var(--r-lg)\"><span class=\"qf-typing-dots qf-typing-dots--onAccent\" role=\"status\" aria-label=\"Generating\"><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span></span></span>"
        },
        {
          "label": "Large (--lg)",
          "html": "<span class=\"qf-typing-dots qf-typing-dots--lg\" role=\"status\" aria-label=\"Generating\"><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span><span class=\"qf-typing-dots__dot\" aria-hidden=\"true\"></span></span>"
        }
      ]
    },
    {
      "name": "SortToggle",
      "class": "qf-sort-toggle",
      "layer": "generic",
      "category": "Display",
      "purpose": "오름차순↔내림차순↔off를 순환하는 3상태 정렬 토글입니다. 테이블 헤더(th[aria-sort]) 또는 버튼으로 쓰며 방향을 글리프로 표시합니다.",
      "purposeEn": "A three-state sort toggle that cycles ascending↔descending↔off — used as a table header (th[aria-sort]) or a button, with a direction glyph.",
      "variants": [
        "qf-sort-toggle__indicator",
        "qf-sort-toggle--lg"
      ],
      "requiredTokens": [
        "--text-secondary",
        "--text-strong",
        "--accent",
        "--bg-hover",
        "--sort-indicator-size",
        "--ring-accent",
        "--r-sm",
        "--s-1",
        "--s-2",
        "--fs-13"
      ],
      "a11y": "테이블 헤더는 th[aria-sort='ascending|descending|none'], 독립 버튼은 role=button + 접근명입니다. Enter/Space로 순환하고 방향은 색이 아니라 aria-sort/글리프로 전달하며 헤더는 44px 탭 타깃을 유지합니다.",
      "a11yEn": "Table headers use th[aria-sort='ascending|descending|none']; standalone buttons use role=button with an accessible name. Cycle with Enter/Space, convey direction via aria-sort/glyph (not color alone), and keep headers at a 44px tap target.",
      "exampleHtml": "<button class=\"qf-sort-toggle is-active\" type=\"button\" data-direction=\"asc\" aria-label=\"Sort by name, ascending\"><span>Name</span><span class=\"qf-sort-toggle__indicator\" aria-hidden=\"true\">▲</span></button>",
      "examples": [
        {
          "label": "Descending (active)",
          "html": "<button class=\"qf-sort-toggle is-active\" type=\"button\" data-direction=\"desc\" aria-label=\"Sort by size, descending\"><span>Size</span><span class=\"qf-sort-toggle__indicator\" aria-hidden=\"true\">▼</span></button>"
        },
        {
          "label": "Off (inactive)",
          "html": "<button class=\"qf-sort-toggle\" type=\"button\" data-direction=\"none\" aria-label=\"Sort by date\"><span>Modified</span><span class=\"qf-sort-toggle__indicator\" aria-hidden=\"true\">▲</span></button>"
        }
      ]
    },
    {
      "name": "Chart",
      "class": "qf-chart",
      "layer": "generic",
      "category": "Charts",
      "purpose": "멀티시리즈 시계열 라인/영역 차트입니다. 좌표(points/path)와 눈금 값은 소비처가 데이터에서 계산하고(sparkline 과 동일 계약), DS 는 축, 그리드, 시리즈, 호버 가이드, 값 툴팁의 토큰 기반 스타일을 제공합니다. 시리즈 색은 부모 그룹에 style=\"color:var(--viz-N)\"(1~8) 로 지정합니다.",
      "purposeEn": "A multi-series time-series line/area chart. The consumer computes coordinates (points/path) and tick values from the data (same contract as Sparkline); the DS provides token-based styling for axes, grid, series, hover guide, and value tooltip. Set series color on the parent group via style=\"color:var(--viz-N)\" (1–8).",
      "variants": [
        "qf-chart__svg",
        "qf-chart__grid",
        "qf-chart__axis",
        "qf-chart__label",
        "qf-chart__series",
        "qf-chart__area",
        "qf-chart__dot",
        "qf-chart__guide",
        "qf-chart__tooltip",
        "qf-chart__state",
        "qf-chart--loading",
        "qf-chart--empty"
      ],
      "variantsAsChips": true,
      "requiredTokens": [
        "--viz-1",
        "--viz-2",
        "--viz-3",
        "--chart-grid",
        "--chart-axis",
        "--chart-label",
        "--chart-tooltip-bg",
        "--chart-tooltip-text",
        "--border",
        "--bg-panel"
      ],
      "a11y": "svg 에 role=\"img\" + aria-label 로 추세를 요약합니다(예: \"최근 24시간 다운로드 대 실패, 상승\"). 호버 가이드/툴팁은 포인터 보조 수단이며, 색 단독 의존을 피하도록 범례와 수치 라벨을 함께 둡니다. 키보드/스크린리더 사용자에게는 동일 데이터의 표 대체본 제공을 권장합니다.",
      "a11yEn": "Summarize the trend with role=\"img\" + aria-label on the svg (e.g., \"Downloads vs failures over the last 24h, rising\"). The hover guide/tooltip is a pointer affordance; pair color with a legend and numeric labels to avoid color-only reliance. Offer an equivalent data table for keyboard/screen-reader users.",
      "exampleHtml": "<div class=\"qf-chart\" style=\"--chart-h:200px\"><svg class=\"qf-chart__svg\" viewBox=\"0 0 320 200\" role=\"img\" aria-label=\"Downloads vs failures over 24h, both rising\"><g class=\"qf-chart__grid\"><line x1=\"36\" y1=\"20\" x2=\"312\" y2=\"20\"></line><line x1=\"36\" y1=\"60\" x2=\"312\" y2=\"60\"></line><line x1=\"36\" y1=\"100\" x2=\"312\" y2=\"100\"></line><line x1=\"36\" y1=\"140\" x2=\"312\" y2=\"140\"></line></g><text class=\"qf-chart__label qf-chart__label--y\" x=\"30\" y=\"20\">300</text><text class=\"qf-chart__label qf-chart__label--y\" x=\"30\" y=\"60\">200</text><text class=\"qf-chart__label qf-chart__label--y\" x=\"30\" y=\"100\">100</text><text class=\"qf-chart__label qf-chart__label--y\" x=\"30\" y=\"140\">0</text><line class=\"qf-chart__axis\" x1=\"36\" y1=\"20\" x2=\"36\" y2=\"140\"></line><line class=\"qf-chart__axis\" x1=\"36\" y1=\"140\" x2=\"312\" y2=\"140\"></line><g style=\"color:var(--viz-1)\"><polygon class=\"qf-chart__area\" points=\"36,120 105,96 174,104 243,64 312,48 312,140 36,140\"></polygon><polyline class=\"qf-chart__series\" points=\"36,120 105,96 174,104 243,64 312,48\"></polyline></g><g style=\"color:var(--viz-3)\"><polyline class=\"qf-chart__series\" points=\"36,132 105,128 174,118 243,112 312,96\"></polyline></g><text class=\"qf-chart__label qf-chart__label--x\" x=\"36\" y=\"158\">00:00</text><text class=\"qf-chart__label qf-chart__label--x\" x=\"174\" y=\"158\">12:00</text><text class=\"qf-chart__label qf-chart__label--x\" x=\"312\" y=\"158\">24:00</text><line class=\"qf-chart__guide\" x1=\"243\" y1=\"20\" x2=\"243\" y2=\"140\"></line><circle class=\"qf-chart__dot\" cx=\"243\" cy=\"64\" r=\"3.5\" style=\"color:var(--viz-1)\"></circle></svg><div class=\"qf-chart__tooltip\" style=\"left:196px;top:24px\"><div class=\"qf-chart__tooltip-row\"><span class=\"qf-chart__tooltip-swatch\" style=\"color:var(--viz-1)\"></span>Downloaded 268</div><div class=\"qf-chart__tooltip-row\"><span class=\"qf-chart__tooltip-swatch\" style=\"color:var(--viz-3)\"></span>Failed 12</div></div><div class=\"qf-chart__state\">No data</div></div><ul class=\"qf-legend\" style=\"margin-top:var(--s-3)\"><li class=\"qf-legend__item\"><span class=\"qf-legend__swatch\" style=\"color:var(--viz-1)\"></span>Downloaded</li><li class=\"qf-legend__item\"><span class=\"qf-legend__swatch\" style=\"color:var(--viz-3)\"></span>Failed</li></ul>"
    },
    {
      "name": "BarChart",
      "class": "qf-barchart",
      "layer": "generic",
      "category": "Charts",
      "purpose": "그룹 또는 스택 막대 차트입니다. 카테고리축(사이트/카테고리) + 값, 막대 좌표/높이는 소비처가 계산합니다. 그룹은 막대를 나란히, 스택은 같은 x 에 쌓아 배치합니다(둘 다 좌표로 표현). --horizontal 로 가로 막대 레이블 정렬을 전환합니다. 막대 색은 style=\"color:var(--viz-N)\".",
      "purposeEn": "A grouped or stacked bar chart. The consumer computes bar coordinates/heights for a category axis (site/category) plus values. Groups place bars side by side; stacks pile them at the same x (both expressed via coordinates). Use --horizontal to switch label alignment for horizontal bars. Set bar color via style=\"color:var(--viz-N)\".",
      "variants": [
        "qf-barchart__svg",
        "qf-barchart__grid",
        "qf-barchart__axis",
        "qf-barchart__label",
        "qf-barchart__track",
        "qf-barchart__bar",
        "qf-barchart__value",
        "qf-barchart--horizontal"
      ],
      "variantsAsChips": true,
      "requiredTokens": [
        "--viz-1",
        "--viz-7",
        "--chart-grid",
        "--chart-axis",
        "--chart-label",
        "--chart-track",
        "--text-secondary"
      ],
      "a11y": "svg 에 role=\"img\" + aria-label 로 비교 요약을 제공하고, 값 레이블 텍스트를 함께 둬 색 단독 의존을 피합니다. 카테고리×시리즈 분해는 범례로 보강합니다.",
      "a11yEn": "Provide a comparison summary via role=\"img\" + aria-label, and include value-label text so the chart does not rely on color alone. Reinforce the category×series breakdown with a legend.",
      "exampleHtml": "<div class=\"qf-barchart\" style=\"--chart-h:200px\"><svg class=\"qf-barchart__svg\" viewBox=\"0 0 320 200\" role=\"img\" aria-label=\"Downloaded vs failed by site, grouped\"><g class=\"qf-barchart__grid\"><line x1=\"40\" y1=\"40\" x2=\"312\" y2=\"40\"></line><line x1=\"40\" y1=\"100\" x2=\"312\" y2=\"100\"></line><line x1=\"40\" y1=\"160\" x2=\"312\" y2=\"160\"></line></g><line class=\"qf-barchart__axis\" x1=\"40\" y1=\"20\" x2=\"40\" y2=\"160\"></line><line class=\"qf-barchart__axis\" x1=\"40\" y1=\"160\" x2=\"312\" y2=\"160\"></line><rect class=\"qf-barchart__bar\" x=\"60\" y=\"56\" width=\"22\" height=\"104\" style=\"color:var(--viz-1)\"></rect><rect class=\"qf-barchart__bar\" x=\"86\" y=\"132\" width=\"22\" height=\"28\" style=\"color:var(--viz-7)\"></rect><rect class=\"qf-barchart__bar\" x=\"150\" y=\"40\" width=\"22\" height=\"120\" style=\"color:var(--viz-1)\"></rect><rect class=\"qf-barchart__bar\" x=\"176\" y=\"120\" width=\"22\" height=\"40\" style=\"color:var(--viz-7)\"></rect><rect class=\"qf-barchart__bar\" x=\"240\" y=\"84\" width=\"22\" height=\"76\" style=\"color:var(--viz-1)\"></rect><rect class=\"qf-barchart__bar\" x=\"266\" y=\"146\" width=\"22\" height=\"14\" style=\"color:var(--viz-7)\"></rect><text class=\"qf-barchart__label\" x=\"84\" y=\"178\">api</text><text class=\"qf-barchart__label\" x=\"174\" y=\"178\">cdn</text><text class=\"qf-barchart__label\" x=\"264\" y=\"178\">web</text></svg></div>"
    },
    {
      "name": "Donut",
      "class": "qf-donut",
      "layer": "generic",
      "category": "Charts",
      "purpose": "다세그먼트 도넛 차트입니다(단일 게이지인 Radial 과 달리 N 세그먼트 분포). 각 세그먼트는 원 둘레 기준 stroke-dasharray/stroke-dashoffset 로 표현하며 길이는 소비처가 계산합니다. 중앙에 합계/라벨을 두고 분포는 범례로 보강합니다. 세그먼트 색은 style=\"color:var(--viz-N)\".",
      "purposeEn": "A multi-segment donut chart (an N-segment distribution, unlike the single-gauge Radial). Each segment is expressed with stroke-dasharray/stroke-dashoffset against the circle circumference, with lengths computed by the consumer. Place a total/label in the center and reinforce the distribution with a legend. Set segment color via style=\"color:var(--viz-N)\".",
      "variants": [
        "qf-donut__svg",
        "qf-donut__track",
        "qf-donut__segment",
        "qf-donut__center",
        "qf-donut__total",
        "qf-donut__label"
      ],
      "variantsAsChips": true,
      "requiredTokens": [
        "--viz-1",
        "--viz-5",
        "--viz-7",
        "--chart-track",
        "--text-strong",
        "--text-muted",
        "--fs-24"
      ],
      "a11y": "svg 에 role=\"img\" + aria-label 로 세그먼트 비율을 명시합니다(예: \"실패 사유: 타임아웃 60%, 인증 28%, 기타 12%\"). 중앙 합계 텍스트와 범례로 색 단독 의존을 피합니다.",
      "a11yEn": "State the segment proportions with role=\"img\" + aria-label on the svg (e.g., \"Failure reasons: timeout 60%, auth 28%, other 12%\"). Pair the centered total text and a legend to avoid color-only reliance.",
      "exampleHtml": "<div class=\"qf-donut\"><svg class=\"qf-donut__svg\" viewBox=\"0 0 128 128\" role=\"img\" aria-label=\"Failure reasons: timeout 60%, auth 28%, other 12%\"><circle class=\"qf-donut__track\" cx=\"64\" cy=\"64\" r=\"54\" stroke-width=\"16\"></circle><circle class=\"qf-donut__segment\" cx=\"64\" cy=\"64\" r=\"54\" stroke-width=\"16\" stroke-dasharray=\"203.58 135.71\" stroke-dashoffset=\"0\" style=\"color:var(--viz-1)\"></circle><circle class=\"qf-donut__segment\" cx=\"64\" cy=\"64\" r=\"54\" stroke-width=\"16\" stroke-dasharray=\"95 244.29\" stroke-dashoffset=\"-203.58\" style=\"color:var(--viz-5)\"></circle><circle class=\"qf-donut__segment\" cx=\"64\" cy=\"64\" r=\"54\" stroke-width=\"16\" stroke-dasharray=\"40.71 298.58\" stroke-dashoffset=\"-298.58\" style=\"color:var(--viz-7)\"></circle></svg><div class=\"qf-donut__center\"><span class=\"qf-donut__total\">412</span><span class=\"qf-donut__label\">failures</span></div></div>"
    },
    {
      "name": "Heatmap",
      "class": "qf-heatmap",
      "layer": "generic",
      "category": "Charts",
      "purpose": "사이트×시간 또는 사이트×카테고리 밀도(처리량/실패) 히트맵입니다. CSS 그리드 셀로, 강도는 셀마다 --qf-heat(0~1) 를 인라인 style 로 전달하면 색이 --heat-empty 와 currentColor 사이를 보간합니다. 그리드 열 수(grid-template-columns)와 셀 값은 소비처가 지정합니다. 색은 style=\"color:var(--viz-N)\".",
      "purposeEn": "A site×time or site×category density heatmap (throughput/failures). Cells are a CSS grid; pass per-cell intensity via inline style --qf-heat (0–1) and the color interpolates between --heat-empty and currentColor. The consumer sets the column count (grid-template-columns) and cell values. Set color via style=\"color:var(--viz-N)\".",
      "variants": [
        "qf-heatmap__row",
        "qf-heatmap__cell",
        "qf-heatmap__label"
      ],
      "variantsAsChips": true,
      "requiredTokens": [
        "--viz-2",
        "--heat-empty",
        "--text-muted"
      ],
      "a11y": "svg 가 아닌 그리드이므로 컨테이너에 role=\"img\" + aria-label 로 무엇의 밀도인지 요약하고, 각 셀에는 title(또는 aria-label)로 정확한 수치를 제공해 색/명도 단독 의존을 피합니다.",
      "a11yEn": "Because this is a grid (not an svg), give the container role=\"img\" + aria-label summarizing what the density represents, and provide the exact value per cell via title (or aria-label) so it does not rely on color/brightness alone.",
      "exampleHtml": "<div class=\"qf-heatmap\" style=\"grid-template-columns:auto repeat(6,1fr);max-width:300px\" role=\"img\" aria-label=\"Throughput density by site and 4-hour bucket\"><div class=\"qf-heatmap__row\"><span class=\"qf-heatmap__label\">api</span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.2\" title=\"api 00:00 — 21\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.5\" title=\"api 04:00 — 58\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.8\" title=\"api 08:00 — 92\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:1\" title=\"api 12:00 — 114\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.6\" title=\"api 16:00 — 67\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.3\" title=\"api 20:00 — 33\"></span></div><div class=\"qf-heatmap__row\"><span class=\"qf-heatmap__label\">cdn</span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.5\" title=\"cdn 00:00 — 54\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.7\" title=\"cdn 04:00 — 78\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.9\" title=\"cdn 08:00 — 101\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.6\" title=\"cdn 12:00 — 64\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.4\" title=\"cdn 16:00 — 41\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.2\" title=\"cdn 20:00 — 19\"></span></div><div class=\"qf-heatmap__row\"><span class=\"qf-heatmap__label\">web</span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.1\" title=\"web 00:00 — 9\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.3\" title=\"web 04:00 — 28\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.6\" title=\"web 08:00 — 63\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.8\" title=\"web 12:00 — 88\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.7\" title=\"web 16:00 — 71\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.5\" title=\"web 20:00 — 52\"></span></div><div class=\"qf-heatmap__row\"><span class=\"qf-heatmap__label\">db</span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.4\" title=\"db 00:00 — 44\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.5\" title=\"db 04:00 — 51\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.4\" title=\"db 08:00 — 47\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.6\" title=\"db 12:00 — 62\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:0.9\" title=\"db 16:00 — 103\"></span><span class=\"qf-heatmap__cell\" style=\"--qf-heat:1\" title=\"db 20:00 — 118\"></span></div></div>"
    },
    {
      "name": "Legend",
      "class": "qf-legend",
      "layer": "generic",
      "category": "Charts",
      "purpose": "차트들이 공유하는 범례입니다(색칩 + 라벨 + 선택적 수치). 색칩은 style=\"color:var(--viz-N)\" 로 시리즈 색과 맞춥니다. --toggle 변형은 항목을 button[aria-pressed] 로 만들어 시리즈 표시 on/off 를 전달합니다.",
      "purposeEn": "A legend shared by the charts (color swatch + label + optional value). Match the swatch to the series color via style=\"color:var(--viz-N)\". The --toggle variant turns items into button[aria-pressed] to convey series show/hide state.",
      "variants": [
        "qf-legend__item",
        "qf-legend__swatch",
        "qf-legend__value",
        "qf-legend--toggle"
      ],
      "variantsAsChips": true,
      "requiredTokens": [
        "--viz-1",
        "--viz-5",
        "--viz-7",
        "--text-secondary",
        "--text-muted",
        "--bg-hover",
        "--text-disabled"
      ],
      "a11y": "비토글형은 ul/li 로 의미를 전달합니다. 토글형은 각 항목을 button + aria-pressed(true/false) 로 만들어 표시 상태를 스크린리더에 알리고, 흐려진 항목도 색 외 상태(aria-pressed)로 구분되게 합니다.",
      "a11yEn": "Use ul/li for the non-toggle form. For the toggle form, make each item a button + aria-pressed (true/false) so screen readers announce the visibility state, and ensure dimmed items are distinguished by state (aria-pressed), not color alone.",
      "exampleHtml": "<ul class=\"qf-legend\"><li class=\"qf-legend__item\"><span class=\"qf-legend__swatch\" style=\"color:var(--viz-1)\"></span>Downloaded <span class=\"qf-legend__value\">3,248</span></li><li class=\"qf-legend__item\"><span class=\"qf-legend__swatch\" style=\"color:var(--viz-5)\"></span>Skipped <span class=\"qf-legend__value\">412</span></li><li class=\"qf-legend__item\"><span class=\"qf-legend__swatch\" style=\"color:var(--viz-7)\"></span>Failed <span class=\"qf-legend__value\">96</span></li></ul>",
      "examples": [
        {
          "label": "Toggle (show/hide series)",
          "html": "<div class=\"qf-legend qf-legend--toggle\"><button class=\"qf-legend__item\" type=\"button\" aria-pressed=\"true\"><span class=\"qf-legend__swatch\" style=\"color:var(--viz-1)\"></span>Downloaded</button><button class=\"qf-legend__item\" type=\"button\" aria-pressed=\"true\"><span class=\"qf-legend__swatch\" style=\"color:var(--viz-5)\"></span>Skipped</button><button class=\"qf-legend__item\" type=\"button\" aria-pressed=\"false\"><span class=\"qf-legend__swatch\"></span>Failed</button></div>"
        }
      ]
    }
  ]
}
