{
 "generated_at": "2026-10-21T02:30:12+00:00",
 "platforms": [
  {
   "id": "web",
   "name": "정비소 웹",
   "short": "웹",
   "stack": "React 19 · Vite · Ant Design",
   "src": "apps/admin-web/src/components/design-system/",
   "tokens": [
    "web"
   ]
  },
  {
   "id": "mechanic",
   "name": "정비사 앱",
   "short": "정비사",
   "stack": "Flutter · Riverpod",
   "src": "apps/mobile/lib/shared/widgets/",
   "tokens": [
    "mechanic"
   ]
  },
  {
   "id": "rider",
   "name": "라이더 앱",
   "short": "라이더",
   "stack": "Flutter · V1/V2 UI 공존",
   "src": "apps/rider/lib/shared/widgets/",
   "tokens": [
    "rider-v1",
    "rider-v2"
   ]
  }
 ],
 "categories": [
  "버튼",
  "입력",
  "배지",
  "카드",
  "표",
  "차트",
  "내비",
  "피드백",
  "오버레이",
  "레이아웃",
  "기타"
 ],
 "components": [
  {
   "id": "web-assistantfab",
   "platform": "web",
   "name": "AssistantFab",
   "category": "버튼",
   "purpose": "작은 흰 원형 부품 도우미 열기 버튼. 포커스 복원용 ref와 expanded 상태를 전달한다.",
   "variants": [
    "default"
   ],
   "props": [],
   "tokens": [
    "colors.surface",
    "colors.border",
    "radius.pill",
    "space.8xl"
   ],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/button/AssistantFab.tsx",
   "usage_count": null,
   "notes": "",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-button",
   "platform": "web",
   "name": "Button",
   "category": "버튼",
   "purpose": "크기·색상·좌우 아이콘을 지원하는 공통 액션 버튼",
   "variants": [
    "primary",
    "default",
    "danger",
    "text",
    "sm",
    "md",
    "lg",
    "xl",
    "hover",
    "active",
    "disabled",
    "fullWidth",
    "focus-visible"
   ],
   "props": [
    {
     "name": "variant",
     "type": "ButtonVariant",
     "required": false,
     "default": "'primary'"
    },
    {
     "name": "size",
     "type": "ButtonSize",
     "required": false,
     "default": "'md'"
    },
    {
     "name": "leftIcon",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "rightIcon",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "type",
     "type": "'button' | 'submit' | 'reset'",
     "required": false,
     "default": "'button'"
    },
    {
     "name": "onClick",
     "type": "(event: MouseEvent<HTMLButtonElement>) => void",
     "required": false,
     "default": null
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "fullWidth",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-primary-hover-bg",
    "--font-display",
    "buttonSize.lg.fontSize",
    "buttonSize.lg.height",
    "buttonSize.lg.iconSize",
    "buttonSize.lg.paddingX",
    "buttonSize.md.fontSize",
    "buttonSize.md.height",
    "buttonSize.md.iconSize",
    "buttonSize.md.paddingX",
    "buttonSize.sm.fontSize",
    "buttonSize.sm.height",
    "buttonSize.sm.iconSize",
    "buttonSize.sm.paddingX",
    "buttonSize.xl.fontSize",
    "buttonSize.xl.height",
    "buttonSize.xl.iconSize",
    "buttonSize.xl.paddingX",
    "colors.border",
    "colors.danger",
    "colors.primary",
    "colors.primaryGradient",
    "colors.primaryHoverBg",
    "colors.surface",
    "colors.textBodyStrong",
    "colors.textOnPrimary",
    "radius.md",
    "shadow.cardStrong",
    "space.sm"
   ],
   "sizes": "sm 32px/가로패딩12px/글자14px/아이콘18px; md 36/16/15/20; lg 40/20/15/22; xl 48/24/16/22. radius.md=8px, gap space.sm=6px, weight 500, lineHeight 1; disabled opacity 0.5; transition 120ms.",
   "file": "apps/admin-web/src/components/design-system/button/Button.tsx",
   "usage_count": 53,
   "notes": "기본 primary/md. native button + forwardRef로 AntD Popconfirm/Tooltip 트리거 ref를 지원하나 AntD Button 래퍼는 아니다. primary는 colors.primaryGradient를 사용하고 hover/active는 8%/16% 검정 오버레이. default는 그림자 없음. 마우스 hover/active는 React 상태로 관리한다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/button/Button.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons",
     "buwang-button"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".buwang-button:focus-visible,\n.buwang-icon-toggle:focus-visible",
      "css": ".buwang-button:focus-visible,\n.buwang-icon-toggle:focus-visible {\n  outline: none;\n  box-shadow: 0 0 0 3px var(--color-primary-hover-bg);\n}",
      "line": 426
     },
     {
      "selector": ".buwang-icon-toggle:disabled,\n.buwang-button:disabled",
      "css": ".buwang-icon-toggle:disabled,\n.buwang-button:disabled {\n  cursor: not-allowed;\n  opacity: 0.5;\n}",
      "line": 436
     },
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "DARKEN_OVERLAY",
      "scope": "module",
      "line": 35,
      "source": "'linear-gradient(rgba(0,0,0,0.08), rgba(0,0,0,0.08))'"
     },
     {
      "symbol": "DARKEN_OVERLAY_STRONGER",
      "scope": "module",
      "line": 37,
      "source": "'linear-gradient(rgba(0,0,0,0.16), rgba(0,0,0,0.16))'"
     },
     {
      "symbol": "variantPalettes",
      "scope": "module",
      "line": 40,
      "source": "{\n  primary: {\n    background: colors.primary,\n    color: colors.textOnPrimary,\n    border: 'none',\n    boxShadow: shadow.cardStrong,\n    hoverBackground: colors.primary,\n    hoverColor: colors.textOnPrimary,\n    hoverBorder: 'none',\n    activeBackground: colors.primary,\n  },\n  // Default variant renders without elevation per spec §5.2 — shadow-card-strong\n  // would conflict with the light border treatment shown in Pencil.\n  default: {\n    background: colors.surface,\n    color: colors.textBodyStrong,\n    border: colors.border,\n    boxShadow: 'none',\n    hoverBackground: colors.primaryHoverBg,\n    hoverColor: colors.textBodyStrong,\n    hoverBorder: colors.primary,\n    activeBackground: colors.primaryHoverBg,\n  },\n  danger: {\n    background: colors.danger,\n    color: colors.textOnPrimary,\n    border: 'transparent',\n    boxShadow: shadow.cardStrong,\n    hoverBackground: `${DARKEN_OVERLAY}, ${colors.danger}`,\n    hoverColor: colors.textOnPrimary,\n    hoverBorder: 'transparent',\n    activeBackground: `${DARKEN_OVERLAY_STRONGER}, ${colors.danger}`,\n  },\n  text: {\n    background: 'transparent',\n    color: colors.primary,\n    border: 'transparent',\n    boxShadow: 'none',\n    hoverBackground: colors.primaryHoverBg,\n    hoverColor: colors.primary,\n    hoverBorder: 'transparent',\n    activeBackground: colors.primaryHoverBg,\n  },\n}"
     },
     {
      "symbol": "buildBaseStyle",
      "scope": "module",
      "line": 85,
      "source": "function buildBaseStyle(\n  variant: ButtonVariant,\n  size: ButtonSize,\n  fullWidth: boolean,\n  disabled: boolean,\n): CSSProperties {\n  const preset = buttonSize[size];\n  const palette = variantPalettes[variant];\n  const isPrimary = variant === 'primary';\n  return {\n    display: 'inline-flex',\n    alignItems: 'center',\n    justifyContent: 'center',\n    gap: space.sm,\n    height: preset.height,\n    paddingInline: preset.paddingX,\n    paddingBlock: 0,\n    borderRadius: radius.md,\n    // border 축약형 + hover 의 borderColor 혼용은 React 스타일 경고/버그 유발 — 분해해서 지정.\n    borderWidth: palette.border === 'none' ? 0 : 1,\n    borderStyle: 'solid',\n    borderColor: palette.border === 'none' ? 'transparent' : palette.border,\n    background: palette.background,\n    backgroundImage: isPrimary ? colors.primaryGradient : undefined,\n    color: palette.color,\n    fontFamily: 'var(--font-display)',\n    fontSize: preset.fontSize,\n    fontWeight: 500,\n    lineHeight: 1,\n    boxShadow: palette.boxShadow,\n    cursor: disabled ? 'not-allowed' : 'pointer',\n    opacity: disabled ? 0.5 : 1,\n    width: fullWidth ? '100%' : undefined,\n    transition:\n      'background 120ms ease, color 120ms ease, border-color 120ms ease, box-shadow 120ms ease',\n    outline: 'none',\n    userSelect: 'none',\n    whiteSpace: 'nowrap',\n  };\n}"
     },
     {
      "symbol": "palette",
      "scope": "buildBaseStyle",
      "line": 92,
      "source": "variantPalettes[variant]"
     },
     {
      "symbol": "buildHoverStyle",
      "scope": "module",
      "line": 126,
      "source": "function buildHoverStyle(variant: ButtonVariant): CSSProperties {\n  const palette = variantPalettes[variant];\n  return {\n    background: palette.hoverBackground,\n    backgroundImage: variant === 'primary' ? `${DARKEN_OVERLAY}, ${colors.primaryGradient}` : undefined,\n    color: palette.hoverColor,\n    ...(palette.hoverBorder !== 'none' ? { borderColor: palette.hoverBorder } : null),\n  };\n}"
     },
     {
      "symbol": "palette",
      "scope": "buildHoverStyle",
      "line": 127,
      "source": "variantPalettes[variant]"
     },
     {
      "symbol": "buildActiveStyle",
      "scope": "module",
      "line": 136,
      "source": "function buildActiveStyle(variant: ButtonVariant): CSSProperties {\n  const palette = variantPalettes[variant];\n  return {\n    background: palette.activeBackground,\n    backgroundImage: variant === 'primary' ? `${DARKEN_OVERLAY_STRONGER}, ${colors.primaryGradient}` : undefined,\n  };\n}"
     },
     {
      "symbol": "palette",
      "scope": "buildActiveStyle",
      "line": 137,
      "source": "variantPalettes[variant]"
     },
     {
      "symbol": "baseStyle",
      "scope": "Button",
      "line": 174,
      "source": "buildBaseStyle(variant, size, fullWidth, disabled)"
     },
     {
      "symbol": "mergedStyle",
      "scope": "Button",
      "line": 175,
      "source": "{\n    ...baseStyle,\n    ...(hover && !disabled ? buildHoverStyle(variant) : null),\n    ...(active && !disabled ? buildActiveStyle(variant) : null),\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "span",
      "expression": "{{ fontSize: pixelSize, lineHeight: 1 }}",
      "line": 149
     },
     {
      "tag": "button",
      "expression": "{mergedStyle}",
      "line": 199
     }
    ],
    "rendered_tags": [
     "span",
     "button",
     "Icon"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [
     "--color-primary-hover-bg"
    ],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-button\"><div class=\"pv-web-row\"><span class=\"pv-web-button-control pv-web-button-primary\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M12 5v14M5 12h14\"/></svg>정비 접수</span><span class=\"pv-web-button-control\">취소</span></div><div class=\"pv-web-row pv-web-button-alternates\"><span class=\"pv-web-button-control pv-web-button-sm pv-web-button-danger\">삭제</span><span class=\"pv-web-button-control pv-web-button-sm pv-web-button-text\">상세 보기</span><span class=\"pv-web-button-control pv-web-button-sm pv-web-button-primary pv-web-disabled\">저장</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-icon-toggle",
   "platform": "web",
   "name": "IconToggle",
   "category": "버튼",
   "purpose": "활성 상태를 aria-pressed로 표현하는 원형 아이콘 버튼",
   "variants": [
    "inactive",
    "active",
    "hover-css",
    "focus-visible-css",
    "custom-size"
   ],
   "props": [
    {
     "name": "icon",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "active",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "onClick",
     "type": "(event: MouseEvent<HTMLButtonElement>) => void",
     "required": false,
     "default": null
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "number",
     "required": false,
     "default": "32"
    }
   ],
   "tokens": [
    "--color-primary",
    "--color-primary-hover-bg"
   ],
   "sizes": "기본32×32px; borderRadius=size/2; padding0; 아이콘20px; transition120ms.",
   "file": "apps/admin-web/src/components/design-system/input/IconToggle.tsx",
   "usage_count": 0,
   "notes": "disabled prop/DOM 전달은 없다. global.css에 :disabled 규칙은 존재한다. backgroundColor를 항상 inline으로 지정하므로 글로벌 :hover 규칙의 실제 적용은 CSS 우선순위를 고려해야 한다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/input/IconToggle.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "buwang-icon-toggle",
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".buwang-button:focus-visible,\n.buwang-icon-toggle:focus-visible",
      "css": ".buwang-button:focus-visible,\n.buwang-icon-toggle:focus-visible {\n  outline: none;\n  box-shadow: 0 0 0 3px var(--color-primary-hover-bg);\n}",
      "line": 426
     },
     {
      "selector": ".buwang-icon-toggle:hover",
      "css": ".buwang-icon-toggle:hover {\n  background-color: var(--color-primary-hover-bg);\n}",
      "line": 432
     },
     {
      "selector": ".buwang-icon-toggle:disabled,\n.buwang-button:disabled",
      "css": ".buwang-icon-toggle:disabled,\n.buwang-button:disabled {\n  cursor: not-allowed;\n  opacity: 0.5;\n}",
      "line": 436
     },
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "IconToggle",
      "line": 18,
      "source": "{\n    width: size,\n    height: size,\n    borderRadius: size / 2,\n    backgroundColor: active ? 'var(--color-primary-hover-bg)' : 'transparent',\n    display: 'inline-flex',\n    alignItems: 'center',\n    justifyContent: 'center',\n    border: 'none',\n    padding: 0,\n    cursor: 'pointer',\n    color: 'var(--color-primary)',\n    transition: 'background-color 120ms ease',\n    flexShrink: 0,\n  }"
     },
     {
      "symbol": "iconStyle",
      "scope": "IconToggle",
      "line": 34,
      "source": "{\n    fontSize: 20,\n    lineHeight: 1,\n    color: 'inherit',\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "button",
      "expression": "{rootStyle}",
      "line": 47
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 49
     }
    ],
    "rendered_tags": [
     "button",
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [
     "--color-primary-hover-bg"
    ],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-icon-toggle\"><div class=\"pv-web-column\"><span class=\"pv-web-icon-toggle-control pv-web-icon-toggle-active\" aria-label=\"즐겨찾기 선택됨\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"m12 3 2.8 5.7 6.3.9-4.6 4.5 1.1 6.3L12 17.4l-5.6 3 1.1-6.3L3 9.6l6.2-.9Z\"/></svg></span><span class=\"pv-web-caption\">선택됨</span></div><div class=\"pv-web-column\"><span class=\"pv-web-icon-toggle-control\" aria-label=\"즐겨찾기 해제됨\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"m12 3 2.8 5.7 6.3.9-4.6 4.5 1.1 6.3L12 17.4l-5.6 3 1.1-6.3L3 9.6l6.2-.9Z\"/></svg></span><span class=\"pv-web-caption\">선택 안 됨</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-mascot-helper-button",
   "platform": "web",
   "name": "MascotHelperButton",
   "category": "버튼",
   "purpose": "마우스 진입과 키보드 포커스에 반응하는 투명 너트·부부 캐릭터 버튼",
   "variants": [
    "nutty",
    "bubu",
    "idle",
    "hover",
    "focus",
    "disabled",
    "reduced-motion"
   ],
   "props": [
    {
     "name": "character",
     "type": "nutty | bubu",
     "required": false,
     "default": "nutty"
    },
    {
     "name": "label",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean attribute",
     "required": false,
     "default": "false"
    },
    {
     "name": "expanded",
     "type": "true | false",
     "required": false,
     "default": null
    },
    {
     "name": "--mascot-width",
     "type": "CSS length",
     "required": false,
     "default": "112px"
    }
   ],
   "tokens": [
    "--mascot-width",
    "--mascot-focus"
   ],
   "sizes": "기본 112×140px. --mascot-width로 폭 지정, 비율 4:5. 정비소 웹 우측 하단 사용.",
   "file": "apps/design-site/public/characters/v2/mascot-button.js",
   "usage_count": 0,
   "notes": "캐릭터별 GPT 원화 24프레임과 실제 투명 알파. 크기·발 위치 정렬. 60ms 간격 재생, 이탈 시 첫 자세 복귀. /characters/frames에서 48장과 기준선을 비교한다. 웹 표준 Custom Element이며 실제 정비소 도우미 연결 전이다.",
   "style_source": null,
   "preview_html": "<span class=\"pv-web-mascots\"><img src=\"/characters/v2/nutty-idle.webp\" alt=\"너트\"><img src=\"/characters/v2/bubu-idle.webp\" alt=\"부부\"></span>",
   "preview_variant": null
  },
  {
   "id": "web-three-d-accident-toolbar",
   "platform": "web",
   "name": "AccidentToolbar",
   "category": "입력",
   "purpose": "3D 사고 견적 툴바 — 사고 유형 프리셋 칩 + 부위 토글 버튼",
   "variants": [
    "accident-preset",
    "region-toggle",
    "active",
    "disabled",
    "clear"
   ],
   "props": [
    {
     "name": "active",
     "type": "Set<SpatialRegion>",
     "required": true,
     "default": null
    },
    {
     "name": "canClear",
     "type": "boolean",
     "required": false,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(regions: Set<SpatialRegion>, label: string, kind: 'accident' | 'region' | 'clear') => void",
     "required": true,
     "default": null
    },
    {
     "name": "disabledRegions",
     "type": "Set<SpatialRegion>",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "colors.danger",
    "colors.textMuted",
    "fontSize.caption",
    "fontWeight.semibold",
    "radius",
    "space"
   ],
   "sizes": "두 줄 칩 묶음(사고 유형 / 부위), gap space.md=8px·세로 gap space.sm=6px. 머리 라벨은 fontSize.caption/semibold, 사고 줄은 colors.danger·부위 줄은 colors.textMuted.",
   "file": "apps/admin-web/src/features/estimates/three3d/AccidentToolbar.tsx",
   "usage_count": 1,
   "notes": "부위 토글은 한 번에 한 부위만 — 다른 부위를 누르면 기존 선택이 풀리고, 같은 부위를 다시 누르면 해제된다. 사고 유형 프리셋은 여러 부위를 한꺼번에 잡는 것이 목적이라 그대로 둔다. 해당 부위의 부품을 전부 담으면 그 칩은 비활성으로 죽는다(disabledRegions).",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-toolbar\"><div class=\"pv-web-row\"><span class=\"pv-web-toolbar-label pv-web-toolbar-danger\">사고 유형</span><span class=\"pv-web-chip\">정면 추돌</span><span class=\"pv-web-chip\">측면</span></div><div class=\"pv-web-row\"><span class=\"pv-web-toolbar-label\">부위</span><span class=\"pv-web-chip pv-web-chip-active\">앞</span><span class=\"pv-web-chip\">뒤</span><span class=\"pv-web-chip pv-web-chip-off\">좌</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-cart-destination-picker",
   "platform": "web",
   "name": "CartDestinationPicker",
   "category": "입력",
   "purpose": "담은 부품을 어느 차량 묶음에 넣을지 고르는 선택기",
   "variants": [
    "with-groups",
    "new-vehicle",
    "with-label-input",
    "error"
   ],
   "props": [
    {
     "name": "destination",
     "type": "ReturnType<typeof useCartDestination>",
     "required": true,
     "default": null
    },
    {
     "name": "groupId",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "onChoose",
     "type": "(group?: VehicleGroup) => void",
     "required": true,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": ".pc-cart-destination 세로 배치 — 안내문(.pc-field-help) + 묶음 select(.pc-field) + 접이식 '차량 번호·이름 추가' + 장바구니 링크.",
   "file": "apps/admin-web/src/features/partsCommerce/CartDestination.tsx",
   "usage_count": 2,
   "notes": "장바구니는 차량별 묶음이 단위다 — 목적지를 고르지 않으면 부품이 어느 차 것인지 잃는다. 기존 묶음을 고르면 그 차량 조건을 불러와 이어 담고, '새 차량으로 부품 찾기'를 고르면 묶음을 새로 연다. 부품 도우미 독과 카탈로그 화면이 함께 쓴다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-cart-destination\"><p class=\"pv-web-caption\">장바구니는 수리할 차량별로 부품을 묶어 보관합니다.</p><span class=\"pv-web-form-field-label\">이어서 담을 차량별 주문 묶음</span><span class=\"pv-web-dropdown-control\"><span>서울 강남 가 1234 · PCX125 · 3품목</span></span><span class=\"pv-web-link\">차량별로 담아둔 부품 확인 →</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-checkbox",
   "platform": "web",
   "name": "Checkbox",
   "category": "입력",
   "purpose": "숨겨진 네이티브 입력과 시각 체크박스로 구성된 체크 컨트롤",
   "variants": [
    "checked",
    "unchecked",
    "disabled",
    "with-label"
   ],
   "props": [
    {
     "name": "checked",
     "type": "boolean",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(next: boolean) => void",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "id",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border-soft",
    "--color-primary",
    "--color-surface",
    "--color-text-body-strong",
    "--color-text-on-primary",
    "--font-display"
   ],
   "sizes": "박스18×18px/radius4px; 체크아이콘14px/top2px/left2px; gap8px; 라벨14px/400/1.428; 숨김 input1×1px/margin-1px; disabled opacity0.5.",
   "file": "apps/admin-web/src/components/design-system/input/Checkbox.tsx",
   "usage_count": 8,
   "notes": "label position:relative가 숨김 absolute input의 기준이다. id 미지정 시 useId 사용. indeterminate 또는 focus-visible 전용 스타일은 소스에 없다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/input/Checkbox.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "labelWrapStyle",
      "scope": "module",
      "line": 12,
      "source": "{\n  // relative 필수 — 숨김 input(absolute)의 containing block 을 라벨로 못박는다.\n  // 없으면 input 이 ICB(문서 루트) 기준으로 배치되어, 내부 스크롤 목록 안에서 쓰일 때\n  // 스크롤 클리핑을 탈출해 footer 아래로 문서 스크롤 영역을 수천 px 늘린다(유령 여백).\n  position: 'relative',\n  display: 'inline-flex',\n  alignItems: 'center',\n  gap: 8,\n  cursor: 'pointer',\n  userSelect: 'none',\n}"
     },
     {
      "symbol": "hiddenInputStyle",
      "scope": "module",
      "line": 24,
      "source": "{\n  position: 'absolute',\n  width: 1,\n  height: 1,\n  padding: 0,\n  margin: -1,\n  overflow: 'hidden',\n  clip: 'rect(0, 0, 0, 0)',\n  whiteSpace: 'nowrap',\n  border: 0,\n}"
     },
     {
      "symbol": "boxBaseStyle",
      "scope": "module",
      "line": 36,
      "source": "{\n  position: 'relative',\n  width: 18,\n  height: 18,\n  borderRadius: 4,\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  flexShrink: 0,\n  boxSizing: 'border-box',\n}"
     },
     {
      "symbol": "checkIconStyle",
      "scope": "module",
      "line": 48,
      "source": "{\n  fontSize: 14,\n  lineHeight: 1,\n  color: 'var(--color-text-on-primary)',\n  position: 'absolute',\n  top: 2,\n  left: 2,\n}"
     },
     {
      "symbol": "labelTextStyle",
      "scope": "module",
      "line": 57,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 14,\n  fontWeight: 400,\n  lineHeight: 1.428,\n  color: 'var(--color-text-body-strong)',\n}"
     },
     {
      "symbol": "boxStyle",
      "scope": "Checkbox",
      "line": 75,
      "source": "{\n    ...boxBaseStyle,\n    backgroundColor: checked ? 'var(--color-primary)' : 'var(--color-surface)',\n    border: checked ? 'none' : '1px solid var(--color-border-soft)',\n    cursor: disabled ? 'not-allowed' : 'pointer',\n  }"
     },
     {
      "symbol": "wrapStyle",
      "scope": "Checkbox",
      "line": 82,
      "source": "{\n    ...labelWrapStyle,\n    cursor: disabled ? 'not-allowed' : 'pointer',\n    opacity: disabled ? 0.5 : 1,\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "label",
      "expression": "{wrapStyle}",
      "line": 94
     },
     {
      "tag": "input",
      "expression": "{hiddenInputStyle}",
      "line": 101
     },
     {
      "tag": "span",
      "expression": "{boxStyle}",
      "line": 103
     },
     {
      "tag": "span",
      "expression": "{checkIconStyle}",
      "line": 105
     },
     {
      "tag": "span",
      "expression": "{labelTextStyle}",
      "line": 110
     }
    ],
    "rendered_tags": [
     "label",
     "input",
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-checkbox\"><span class=\"pv-web-checkbox-label\"><span class=\"pv-web-checkbox-box pv-web-checkbox-checked\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" aria-hidden=\"true\"><path d=\"m4 12 5 5L20 6\"/></svg></span>정비 완료 알림 보내기</span><div class=\"pv-web-row\"><span class=\"pv-web-checkbox-label\"><span class=\"pv-web-checkbox-box\"></span>전체 선택</span><span class=\"pv-web-checkbox-label pv-web-disabled\"><span class=\"pv-web-checkbox-box pv-web-checkbox-checked\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" aria-hidden=\"true\"><path d=\"m4 12 5 5L20 6\"/></svg></span>선택 불가</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-chip-group",
   "platform": "web",
   "name": "ChipGroup",
   "category": "입력",
   "purpose": "선택 칩 묶음 — 후보를 한 줄에 늘어놓고 하나를 고른다(소모품 바로 찾기·연식·내 차량)",
   "variants": [
    "selected",
    "with-detail",
    "sm",
    "md",
    "disabled",
    "nowrap"
   ],
   "props": [
    {
     "name": "items",
     "type": "ChipItem[]",
     "required": true,
     "default": null
    },
    {
     "name": "selectedKey",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "onSelect",
     "type": "(key: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "'sm' | 'md'",
     "required": false,
     "default": "'md'"
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "nowrap",
     "type": "boolean",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-primary-soft",
    "--color-primary-text",
    "--color-surface",
    "--color-text-body-strong",
    "--color-text-muted",
    "--font-display"
   ],
   "sizes": "칩 minHeight36px/padding6 14px/radius999/border1px; font14px 500(선택 700); detail 11px muted. sm 은 minHeight30/padding4 11/font13. 묶음 gap8px flex-wrap(nowrap 이면 가로 스크롤).",
   "file": "apps/admin-web/src/components/design-system/input/ChipGroup.tsx",
   "usage_count": 3,
   "notes": "div[role=group][aria-label] > button[aria-pressed]. 단일 선택이지만 해제(null) 를 허용해야 해서 radiogroup 이 아니다. 선택 = primary-soft 배경 + primary 보더 + primary-text.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/input/ChipGroup.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "notes": "스타일 숫자는 React CSSProperties 원문 기준. 포커스 링·hover 는 global.css 의 .buwang-* 규칙."
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-chip-group\"><span class=\"pv-web-chip pv-web-chip-selected\">연식 전체</span><span class=\"pv-web-chip\">2024</span><span class=\"pv-web-chip\">2021<small>많이 타는 연식</small></span><span class=\"pv-web-chip pv-web-disabled\">2018</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-colour-mark",
   "platform": "web",
   "name": "ColourMark",
   "category": "입력",
   "purpose": "읽기 전용 색 견본. 알려진 hex만 표시하고 미상은 빗금 처리",
   "variants": [],
   "props": [
    {
     "name": "color",
     "type": "string | null",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/layout/Workbench.tsx",
   "usage_count": null,
   "notes": "",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-dropdown",
   "platform": "web",
   "name": "Dropdown",
   "category": "입력",
   "purpose": "옵션 목록을 고르는 네이티브 셀렉트",
   "variants": [
    "default",
    "focus",
    "disabled",
    "placeholder"
   ],
   "props": [
    {
     "name": "value",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(next: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "options",
     "type": "DropdownOption[]",
     "required": true,
     "default": null
    },
    {
     "name": "placeholder",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "width",
     "type": "number | string",
     "required": false,
     "default": "'100%'"
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "id",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-surface",
    "--color-surface-muted",
    "--color-text-heading",
    "--color-text-muted",
    "--color-text-placeholder-faint",
    "--font-display"
   ],
   "sizes": "height36px; width 기본100%; border1px/radius8px; select padding0 36px 0 12px; font15px/400/1.428; 화살표20px/right10px.",
   "file": "apps/admin-web/src/components/design-system/input/Dropdown.tsx",
   "usage_count": 4,
   "notes": "AntD Select/Dropdown 래퍼가 아니다. placeholder 옵션은 value='' + disabled/hidden. 포커스 테두리는 React 상태로 전환한다. placeholderOptionStyle의 faint 색상은 실제 select에 적용된다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/input/Dropdown.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "wrapperBaseStyle",
      "scope": "module",
      "line": 23,
      "source": "{\n  position: 'relative',\n  display: 'inline-flex',\n  alignItems: 'center',\n  height: 36,\n  backgroundColor: 'var(--color-surface)',\n  // focus 시 borderColor 만 바꾸므로 축약형 대신 분해 지정 (React 스타일 경고 방지).\n  borderWidth: 1,\n  borderStyle: 'solid',\n  borderColor: 'var(--color-border)',\n  borderRadius: 8,\n  boxSizing: 'border-box',\n  transition: 'border-color 120ms ease',\n}"
     },
     {
      "symbol": "wrapperFocusStyle",
      "scope": "module",
      "line": 38,
      "source": "{\n  borderColor: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "wrapperDisabledStyle",
      "scope": "module",
      "line": 42,
      "source": "{\n  backgroundColor: 'var(--color-surface-muted)',\n  cursor: 'not-allowed',\n}"
     },
     {
      "symbol": "selectStyle",
      "scope": "module",
      "line": 47,
      "source": "{\n  appearance: 'none',\n  WebkitAppearance: 'none',\n  MozAppearance: 'none',\n  width: '100%',\n  height: '100%',\n  padding: '0 36px 0 12px',\n  border: 'none',\n  outline: 'none',\n  background: 'transparent',\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 400,\n  lineHeight: 1.428,\n  color: 'var(--color-text-heading)',\n  cursor: 'pointer',\n}"
     },
     {
      "symbol": "selectDisabledStyle",
      "scope": "module",
      "line": 65,
      "source": "{\n  cursor: 'not-allowed',\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "chevronStyle",
      "scope": "module",
      "line": 70,
      "source": "{\n  position: 'absolute',\n  right: 10,\n  top: '50%',\n  transform: 'translateY(-50%)',\n  fontSize: 20,\n  lineHeight: 1,\n  color: 'var(--color-text-muted)',\n  pointerEvents: 'none',\n}"
     },
     {
      "symbol": "placeholderOptionStyle",
      "scope": "module",
      "line": 81,
      "source": "{\n  color: 'var(--color-text-placeholder-faint)',\n}"
     },
     {
      "symbol": "wrapperStyle",
      "scope": "Dropdown",
      "line": 99,
      "source": "{\n    ...wrapperBaseStyle,\n    width,\n    ...(focused && !disabled ? wrapperFocusStyle : null),\n    ...(disabled ? wrapperDisabledStyle : null),\n  }"
     },
     {
      "symbol": "mergedSelectStyle",
      "scope": "Dropdown",
      "line": 106,
      "source": "{\n    ...selectStyle,\n    ...(disabled ? selectDisabledStyle : null),\n    ...(value === '' && placeholder ? placeholderOptionStyle : null),\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{wrapperStyle}",
      "line": 117
     },
     {
      "tag": "select",
      "expression": "{mergedSelectStyle}",
      "line": 126
     },
     {
      "tag": "span",
      "expression": "{chevronStyle}",
      "line": 139
     }
    ],
    "rendered_tags": [
     "div",
     "select",
     "option",
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-dropdown\"><div class=\"pv-web-dropdown-control pv-web-focused\"><span>엔진오일 교체</span><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"m7 10 5 5 5-5\"/></svg></div><div class=\"pv-web-row\"><div class=\"pv-web-dropdown-control pv-web-grow pv-web-placeholder\"><span>정비 항목</span><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"m7 10 5 5 5-5\"/></svg></div><div class=\"pv-web-dropdown-control pv-web-grow pv-web-input-disabled\"><span>선택 불가</span><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"m7 10 5 5 5-5\"/></svg></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-estimate-mode-switch",
   "platform": "web",
   "name": "EstimateModeSwitch",
   "category": "입력",
   "purpose": "견적 작성 표면 전환(일반 견적 ↔ 3D 견적) 세그먼트",
   "variants": [
    "general",
    "three_d",
    "proBadge",
    "disabled"
   ],
   "props": [
    {
     "name": "value",
     "type": "EstimateComposeMode",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(next: EstimateComposeMode) => void",
     "required": true,
     "default": null
    },
    {
     "name": "proBadge",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "colors.primary",
    "colors.textOnPrimary",
    "colors.textBodyStrong",
    "colors.primarySoft",
    "colors.primaryText",
    "radius",
    "space",
    "fontWeight"
   ],
   "sizes": "두 칸 세그먼트 — 선택 칸은 colors.primary 배경 + textOnPrimary, 비선택은 투명 + textBodyStrong. PRO 알약은 선택 시 rgba(255,255,255,.22), 비선택 시 primarySoft/primaryText.",
   "file": "apps/admin-web/src/features/estimates/EstimateModeSwitch.tsx",
   "usage_count": 2,
   "notes": "두 모드는 같은 견적 한 건의 다른 작업 표면이다 — 그래서 여기서 라우트를 옮기지 않는다(옮기면 폼이 새로 만들어지면서 적어 둔 항목·사진·메모가 사라진다). 부모의 mode 값만 바꾸고 무엇을 그릴지는 부모가 정한다. 3D 가 프로 전용이어도 버튼은 무료 계정에 보인다 — 눌러야 이유를 알 수 있다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame\"><div class=\"pv-web-segment\"><span class=\"pv-web-segment-item pv-web-segment-active\">일반 견적</span><span class=\"pv-web-segment-item\">3D 견적<i class=\"pv-web-pro-pill\">PRO</i></span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-estimate-photo-uploader",
   "platform": "web",
   "name": "EstimatePhotoUploader",
   "category": "입력",
   "purpose": "견적 사진 첨부 — 새로 고른 파일과 이미 저장된 사진을 한 자리에서 다룬다",
   "variants": [
    "empty",
    "with-photos",
    "saved-photos",
    "global-pdf-toggle",
    "per-photo-type"
   ],
   "props": [
    {
     "name": "photos",
     "type": "PhotoDraft[]",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(photos: PhotoDraft[]) => void",
     "required": true,
     "default": null
    },
    {
     "name": "savedPhotos",
     "type": "EstimatePhoto[]",
     "required": false,
     "default": "[]"
    },
    {
     "name": "onDeleteSavedPhoto",
     "type": "(photo: EstimatePhoto) => void | Promise<void>",
     "required": false,
     "default": null
    },
    {
     "name": "onSavedPhotoIncludeInPdfChange",
     "type": "(photo: EstimatePhoto, include: boolean) => void | Promise<void>",
     "required": false,
     "default": null
    },
    {
     "name": "showGlobalPdfToggle",
     "type": "boolean",
     "required": false,
     "default": "true"
    }
   ],
   "tokens": [
    "colors",
    "radius",
    "space",
    "fontWeight"
   ],
   "sizes": "업로드 영역 + 섬네일 격자. 사진 종류 셀렉트(손상/차량/기타), 파일당 최대 10MB.",
   "file": "apps/admin-web/src/features/estimates/EstimatePhotoUploader.tsx",
   "usage_count": 2,
   "notes": "위저드 Step3 와 통합 작성폼이 공유한다(W1-5). 업로드 자체는 상위가 저장 후 수행하고 여기서는 초안만 들고 있다 — 저장 전에 올리면 취소한 견적의 사진이 스토리지에 남는다. 저장된 사진과 새 File 을 분리해 재저장 시 중복 업로드를 막는다. 미리보기 blob 은 언마운트 때 해제한다. 상위 카드 머리에 전역 PDF 토글을 두는 통합폼은 showGlobalPdfToggle=false.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-uploader\"><div class=\"pv-web-uploader-drop\">사진을 끌어다 놓거나 눌러서 선택 · 1장당 10MB</div><div class=\"pv-web-row\"><span class=\"pv-web-part-thumb\">손상 사진</span><span class=\"pv-web-part-thumb\">차량 사진</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-parts-commerce-field",
   "platform": "web",
   "name": "Field",
   "category": "입력",
   "purpose": "라벨 + 입력 한 칸 — 부품 커머스 폼의 기본 필드",
   "variants": [
    "text",
    "number",
    "select",
    "disabled"
   ],
   "props": [
    {
     "name": "label",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "...props",
     "type": "InputHTMLAttributes<HTMLInputElement>",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "label(.pc-field) 세로 배치, 라벨 캡션 + 입력 한 줄. 치수는 features/partsCommerce/commerce.css 의 .pc-field 가 정한다.",
   "file": "apps/admin-web/src/features/partsCommerce/ui.tsx",
   "usage_count": 7,
   "notes": "input 속성을 그대로 펼쳐 넘긴다(type·value·maxLength·disabled…). 같은 클래스(.pc-field)를 select·combobox 도 쓰므로 부품 커머스 안에서는 폼 칸 모양이 하나로 유지된다. design-system 의 FormField 와 달리 라벨·입력만 있고 오류·도움말 슬롯은 Note 가 따로 맡는다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-form-field\"><span class=\"pv-web-form-field-label\">부품번호</span><span class=\"pv-web-text-input-control\">53100-K97-T00</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-filter-bar",
   "platform": "web",
   "name": "FilterBar",
   "category": "입력",
   "purpose": "필터 필드와 초기화·조회 버튼을 묶는 검색 영역",
   "variants": [
    "fields-only",
    "with-reset",
    "with-search",
    "loading"
   ],
   "props": [
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "onReset",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "onSearch",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "searchLabel",
     "type": "string",
     "required": false,
     "default": "'조회하기'"
    },
    {
     "name": "resetLabel",
     "type": "string",
     "required": false,
     "default": "'초기화'"
    },
    {
     "name": "loading",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary-hover-bg",
    "--color-surface",
    "--font-display",
    "--shadow-card-strong",
    "buttonSize.lg.fontSize",
    "buttonSize.lg.height",
    "buttonSize.lg.iconSize",
    "buttonSize.lg.paddingX",
    "buttonSize.md.fontSize",
    "buttonSize.md.height",
    "buttonSize.md.iconSize",
    "buttonSize.md.paddingX",
    "buttonSize.sm.fontSize",
    "buttonSize.sm.height",
    "buttonSize.sm.iconSize",
    "buttonSize.sm.paddingX",
    "buttonSize.xl.fontSize",
    "buttonSize.xl.height",
    "buttonSize.xl.iconSize",
    "buttonSize.xl.paddingX",
    "colors.border",
    "colors.danger",
    "colors.primary",
    "colors.primaryGradient",
    "colors.primaryHoverBg",
    "colors.surface",
    "colors.textBodyStrong",
    "colors.textOnPrimary",
    "radius.md",
    "shadow.cardStrong",
    "space.sm"
   ],
   "sizes": "padding12px; gap12px; radius16px; border1px; 필드 flex-wrap/gap12px; 버튼행 gap8px; DS Button md 높이36px.",
   "file": "apps/admin-web/src/components/design-system/filter/FilterBar.tsx",
   "usage_count": 0,
   "notes": "role=search인 section. 기본 searchLabel='조회하기'/resetLabel='초기화'. loading은 구성된 버튼을 disabled로 만들며 자체 스피너는 없다. 자식 필드와 Button의 토큰은 각 컴포넌트 항목 참조.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/filter/FilterBar.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 17,
      "source": "{\n  width: '100%',\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 12,\n  padding: 12,\n  borderRadius: 16,\n  backgroundColor: 'var(--color-surface)',\n  border: '1px solid var(--color-border)',\n  boxShadow: 'var(--shadow-card-strong)',\n  boxSizing: 'border-box',\n}"
     },
     {
      "symbol": "fieldRowStyle",
      "scope": "module",
      "line": 31,
      "source": "{\n  display: 'flex',\n  flexDirection: 'row',\n  flexWrap: 'wrap',\n  alignItems: 'flex-end',\n  gap: 12,\n  flex: 1,\n}"
     },
     {
      "symbol": "buttonRowStyle",
      "scope": "module",
      "line": 40,
      "source": "{\n  display: 'flex',\n  flexDirection: 'row',\n  justifyContent: 'flex-end',\n  alignItems: 'center',\n  gap: 8,\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "section",
      "expression": "{rootStyle}",
      "line": 61
     },
     {
      "tag": "div",
      "expression": "{fieldRowStyle}",
      "line": 65
     },
     {
      "tag": "div",
      "expression": "{buttonRowStyle}",
      "line": 67
     }
    ],
    "rendered_tags": [
     "section",
     "div",
     "Button"
    ],
    "composed_components": [
     {
      "file": "apps/admin-web/src/components/design-system/button/Button.tsx",
      "symbol": "Button"
     }
    ],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [
     "material-icons",
     "buwang-button"
    ],
    "global_token_names": [],
    "composed_token_names": [
     "--color-primary-hover-bg",
     "--font-display",
     "buttonSize.lg.fontSize",
     "buttonSize.lg.height",
     "buttonSize.lg.iconSize",
     "buttonSize.lg.paddingX",
     "buttonSize.md.fontSize",
     "buttonSize.md.height",
     "buttonSize.md.iconSize",
     "buttonSize.md.paddingX",
     "buttonSize.sm.fontSize",
     "buttonSize.sm.height",
     "buttonSize.sm.iconSize",
     "buttonSize.sm.paddingX",
     "buttonSize.xl.fontSize",
     "buttonSize.xl.height",
     "buttonSize.xl.iconSize",
     "buttonSize.xl.paddingX",
     "colors.border",
     "colors.danger",
     "colors.primary",
     "colors.primaryGradient",
     "colors.primaryHoverBg",
     "colors.surface",
     "colors.textBodyStrong",
     "colors.textOnPrimary",
     "radius.md",
     "shadow.cardStrong",
     "space.sm"
    ]
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-filter-bar\"><div class=\"pv-web-filter-bar-fields\"><div class=\"pv-web-dropdown-control pv-web-grow\"><span>전체 상태</span><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"m7 10 5 5 5-5\"/></svg></div><div class=\"pv-web-text-input-control pv-web-grow\">1가2 3456</div></div><div class=\"pv-web-filter-bar-actions\"><span class=\"pv-web-button-control\">초기화</span><span class=\"pv-web-button-control pv-web-button-primary\">조회하기</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-form-field",
   "platform": "web",
   "name": "FormField",
   "category": "입력",
   "purpose": "입력 라벨·필수 표시·도움말·오류 문구를 묶는 폼 필드",
   "variants": [
    "optional",
    "required",
    "helperText",
    "error",
    "custom-gap"
   ],
   "props": [
    {
     "name": "label",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "required",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "error",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "helperText",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "id",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "gap",
     "type": "number",
     "required": false,
     "default": "6"
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "--color-danger-strong",
    "--color-text-body-strong",
    "--color-text-muted",
    "--font-display"
   ],
   "sizes": "width100%; column gap 기본6px; 라벨행 gap2px; 라벨14px/500/1.428; 도움말13px/400/1.333; 오류13px/500/1.333.",
   "file": "apps/admin-web/src/components/design-system/input/FormField.tsx",
   "usage_count": 11,
   "notes": "단일 ReactElement 자식에 id와 aria-describedby/invalid/required를 주입한다. error가 helperText보다 우선한다. 자식의 기존 id가 별도 지정되면 유지되지만 label.htmlFor는 controlId를 사용하므로 호출부에서 id를 맞춰야 한다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/input/FormField.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "buildRootStyle",
      "scope": "module",
      "line": 14,
      "source": "function buildRootStyle(gap: number): CSSProperties {\n  return {\n    display: 'flex',\n    flexDirection: 'column',\n    gap,\n    width: '100%',\n  };\n}"
     },
     {
      "symbol": "labelRowStyle",
      "scope": "module",
      "line": 23,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  gap: 2,\n  // 라벨 클릭 시 입력 포커스(htmlFor)는 유지하되, 손가락 커서는 오해를 주므로 기본 커서.\n  cursor: 'default',\n}"
     },
     {
      "symbol": "labelTextStyle",
      "scope": "module",
      "line": 31,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 14,\n  fontWeight: 500,\n  lineHeight: 1.428,\n  color: 'var(--color-text-body-strong)',\n}"
     },
     {
      "symbol": "requiredMarkStyle",
      "scope": "module",
      "line": 39,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 14,\n  fontWeight: 500,\n  lineHeight: 1.428,\n  color: 'var(--color-danger-strong)',\n}"
     },
     {
      "symbol": "helperTextStyle",
      "scope": "module",
      "line": 47,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 13,\n  fontWeight: 400,\n  lineHeight: 1.333,\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "errorTextStyle",
      "scope": "module",
      "line": 55,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 13,\n  fontWeight: 500,\n  lineHeight: 1.333,\n  color: 'var(--color-danger-strong)',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{buildRootStyle(gap)}",
      "line": 95
     },
     {
      "tag": "label",
      "expression": "{labelRowStyle}",
      "line": 96
     },
     {
      "tag": "span",
      "expression": "{labelTextStyle}",
      "line": 97
     },
     {
      "tag": "span",
      "expression": "{requiredMarkStyle}",
      "line": 99
     },
     {
      "tag": "span",
      "expression": "{errorTextStyle}",
      "line": 106
     },
     {
      "tag": "span",
      "expression": "{helperTextStyle}",
      "line": 110
     }
    ],
    "rendered_tags": [
     "div",
     "label",
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-form-field\"><div class=\"pv-web-form-field-label\">차량 번호<span class=\"pv-web-form-field-required\">*</span></div><div class=\"pv-web-text-input-control pv-web-placeholder\">차량 번호를 입력하세요</div><div class=\"pv-web-form-field-error\">차량 번호를 입력해 주세요.</div></div>",
   "preview_variant": null
  },
  {
   "id": "web-search-bar",
   "platform": "web",
   "name": "SearchBar",
   "category": "입력",
   "purpose": "검색 입력과 선택적 추가 액션 버튼을 묶는 검색 폼",
   "variants": [
    "default",
    "focus",
    "with-action",
    "with-action-icon",
    "submit"
   ],
   "props": [
    {
     "name": "value",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(next: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "placeholder",
     "type": "string",
     "required": false,
     "default": "'번호판/소유자 검색'"
    },
    {
     "name": "onSubmit",
     "type": "(value: string) => void",
     "required": false,
     "default": null
    },
    {
     "name": "actionLabel",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "actionIcon",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "onAction",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "width",
     "type": "number | string",
     "required": false,
     "default": "'100%'"
    },
    {
     "name": "guideAnchor",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "actionGuideAnchor",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-surface",
    "--color-text-heading",
    "--color-text-muted",
    "--color-text-on-primary",
    "--color-text-placeholder",
    "--font-display",
    "--shadow-card-strong"
   ],
   "sizes": "height40px; width기본100%; gap12px; 입력 padding8px 12px/border1.25px/radius8px/gap8px; font15px/400/1.428; 아이콘20px. CTA height40px/padding0 16px/radius8px/font15px/500.",
   "file": "apps/admin-web/src/components/design-system/input/SearchBar.tsx",
   "usage_count": 4,
   "notes": "실제 루트는 form role=search이다(주석의 outer form 없음과 다름). Enter 또는 form submit으로 onSubmit(value) 호출. onAction이 있을 때만 별도 type=button 렌더. 기본 placeholder='번호판/소유자 검색'. guideAnchor/actionGuideAnchor 지원.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/input/SearchBar.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [
     "{uniqueClass}"
    ],
    "dynamic_class_definitions": [
     {
      "symbol": "uniqueClass",
      "scope": "SearchBar",
      "line": 119,
      "source": "`buwang-sb-${autoId.replace(/:/g, '')}`"
     }
    ],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 21,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  gap: 12,\n  height: 40,\n}"
     },
     {
      "symbol": "inputWrapperBaseStyle",
      "scope": "module",
      "line": 28,
      "source": "{\n  flex: 1,\n  minWidth: 0,\n  display: 'flex',\n  alignItems: 'center',\n  gap: 8,\n  height: 40,\n  padding: '8px 12px',\n  backgroundColor: 'var(--color-surface)',\n  // focus 시 borderColor 만 바꾸므로 축약형 대신 분해 지정 (React 스타일 경고 방지).\n  borderWidth: 1.25,\n  borderStyle: 'solid',\n  borderColor: 'var(--color-border)',\n  borderRadius: 8,\n  boxSizing: 'border-box',\n  transition: 'border-color 120ms ease',\n}"
     },
     {
      "symbol": "inputWrapperFocusStyle",
      "scope": "module",
      "line": 46,
      "source": "{\n  borderColor: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "searchIconStyle",
      "scope": "module",
      "line": 50,
      "source": "{\n  fontSize: 20,\n  lineHeight: 1,\n  color: 'var(--color-text-muted)',\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "inputStyle",
      "scope": "module",
      "line": 57,
      "source": "{\n  flex: 1,\n  minWidth: 0,\n  border: 'none',\n  outline: 'none',\n  background: 'transparent',\n  padding: 0,\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 400,\n  lineHeight: 1.428,\n  color: 'var(--color-text-heading)',\n}"
     },
     {
      "symbol": "actionButtonStyle",
      "scope": "module",
      "line": 71,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  gap: 6,\n  height: 40,\n  padding: '0 16px',\n  borderRadius: 8,\n  border: 'none',\n  backgroundColor: 'var(--color-primary)',\n  color: 'var(--color-text-on-primary)',\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 500,\n  lineHeight: 1.428,\n  cursor: 'pointer',\n  boxShadow: 'var(--shadow-card-strong)',\n  flexShrink: 0,\n  whiteSpace: 'nowrap',\n}"
     },
     {
      "symbol": "actionIconStyle",
      "scope": "module",
      "line": 91,
      "source": "{\n  fontSize: 20,\n  lineHeight: 1,\n  color: 'inherit',\n}"
     },
     {
      "symbol": "buildPlaceholderStyle",
      "scope": "module",
      "line": 99,
      "source": "function buildPlaceholderStyle(uniqueClass: string): string {\n  return `.${uniqueClass}::placeholder { color: var(--color-text-placeholder); opacity: 1; font-size: 15px; font-weight: 400; }`;\n}"
     },
     {
      "symbol": "uniqueClass",
      "scope": "SearchBar",
      "line": 119,
      "source": "`buwang-sb-${autoId.replace(/:/g, '')}`"
     },
     {
      "symbol": "rootMergedStyle",
      "scope": "SearchBar",
      "line": 121,
      "source": "{ ...rootStyle, width }"
     },
     {
      "symbol": "inputWrapperStyle",
      "scope": "SearchBar",
      "line": 122,
      "source": "{\n    ...inputWrapperBaseStyle,\n    ...(focused ? inputWrapperFocusStyle : null),\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "form",
      "expression": "{rootMergedStyle}",
      "line": 150
     },
     {
      "tag": "div",
      "expression": "{inputWrapperStyle}",
      "line": 153
     },
     {
      "tag": "span",
      "expression": "{searchIconStyle}",
      "line": 154
     },
     {
      "tag": "input",
      "expression": "{inputStyle}",
      "line": 167
     },
     {
      "tag": "button",
      "expression": "{actionButtonStyle}",
      "line": 174
     },
     {
      "tag": "span",
      "expression": "{actionIconStyle}",
      "line": 178
     }
    ],
    "rendered_tags": [
     "form",
     "style",
     "div",
     "span",
     "input",
     "button"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-search-bar\"><div class=\"pv-web-search-bar-row\"><div class=\"pv-web-search-bar-input\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m16 16 5 5\"/></svg><span class=\"pv-web-search-bar-placeholder\">번호판/소유자 검색</span></div></div><div class=\"pv-web-search-bar-row\"><div class=\"pv-web-search-bar-input pv-web-focused\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m16 16 5 5\"/></svg><span>1가2 3456</span></div><span class=\"pv-web-search-bar-action\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M12 5v14M5 12h14\"/></svg>등록</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-suggestion-listbox",
   "platform": "web",
   "name": "SuggestionListbox",
   "category": "입력",
   "purpose": "입력 아래에 뜨는 제안 목록 — 항목명 자동완성과 차량 후보가 함께 쓴다",
   "variants": [
    "floating",
    "inline",
    "loading",
    "error",
    "empty"
   ],
   "props": [
    {
     "name": "listId",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "options",
     "type": "T[]",
     "required": true,
     "default": null
    },
    {
     "name": "activeIndex",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "onActiveChange",
     "type": "(index: number) => void",
     "required": true,
     "default": null
    },
    {
     "name": "onSelect",
     "type": "(option: T, index: number) => void",
     "required": true,
     "default": null
    },
    {
     "name": "renderOption",
     "type": "(option: T, index: number) => ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "loading",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "error",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "emptyText",
     "type": "string",
     "required": false,
     "default": "'일치하는 항목이 없습니다'"
    },
    {
     "name": "floating",
     "type": "boolean",
     "required": false,
     "default": "true"
    },
    {
     "name": "maxHeight",
     "type": "number",
     "required": false,
     "default": "260"
    }
   ],
   "tokens": [
    "colors.surface",
    "colors.border",
    "colors.textMuted",
    "radius.md",
    "shadow.cardStrong",
    "space.lg",
    "space.xl",
    "fontSize.caption"
   ],
   "sizes": "패널 radius.md=8px · border 1px · shadow.cardStrong · maxHeight 기본 260px. 상태문 padding 10px 12px · fontSize.caption. 폭은 width 미지정 시 입력 폭을 따른다.",
   "file": "apps/admin-web/src/features/estimates/SuggestionListbox.tsx",
   "usage_count": 2,
   "notes": "디자인을 새로 만들지 않는다 — 치수·색은 styles/tokens 만, 필드 톤은 TextInput/Dropdown(높이 36·40, radius 8)에 맞춘다. a11y 구조(listbox+option+aria-activedescendant)는 partsCommerce 의 VehicleCombobox 패턴을 그대로 따랐다. 키보드로 옮긴 행이 스크롤 밖으로 나가지 않게 따라간다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-combobox\"><span class=\"pv-web-text-input-control\">브레이크 패</span><div class=\"pv-web-suggest\"><span class=\"pv-web-suggest-option pv-web-suggest-active\">브레이크 패드 앞<small>정비 이력 · 86,000원</small></span><span class=\"pv-web-suggest-option\">브레이크 패드 뒤<small>가격표 · 74,000원</small></span><div class=\"pv-web-suggest-status\">일치하는 항목이 없습니다</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-swatch-group",
   "platform": "web",
   "name": "SwatchGroup",
   "category": "입력",
   "purpose": "색 견본 묶음 — 차량 도장 색처럼 눈으로 고르는 선택지를 한 줄에(ChipGroup 의 자매). 원형 견본 + 이름·코드",
   "variants": [
    "selected",
    "unknown-colour",
    "sm",
    "md",
    "disabled"
   ],
   "props": [
    {
     "name": "items",
     "type": "SwatchItem[] ({key,label,detail?,color?,disabled?})",
     "required": true,
     "default": null
    },
    {
     "name": "selectedKey",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "onSelect",
     "type": "(key: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "'sm' | 'md'",
     "required": false,
     "default": "'md'"
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-primary-soft",
    "--color-primary-text",
    "--color-surface",
    "--color-text-body-strong",
    "--color-text-muted",
    "--font-display"
   ],
   "sizes": "항목 minHeight44/padding 5 12 5 6/radius999/border1px; 견본 원 md 26px·sm 20px, inset 1px 18% 검정 테두리(흰 견본도 보이게); 라벨14px 500(선택 700), detail 11px muted. 색을 모르면 회색 빗금. 묶음 gap8 flex-wrap.",
   "file": "apps/admin-web/src/components/design-system/input/SwatchGroup.tsx",
   "usage_count": 1,
   "notes": "div[role=group][aria-label] > button.buwang-chip.buwang-swatch[aria-pressed]. 단일 선택이지만 해제(null)를 허용해 radiogroup 이 아니다. 선택 = primary-soft 배경 + primary 보더. 부품 구매 도면(차량 색상 → 색상 품번 자동)에서 쓴다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-swatches\"><span class=\"pv-web-swatch\"><i style=\"background:#F1EFE8\"></i><b>펄 재스민 화이트<small>NHB35P</small></b></span><span class=\"pv-web-swatch pv-web-swatch-selected\"><i style=\"background:#B3121F\"></i><b>캔디 러스터 레드<small>R368C</small></b></span><span class=\"pv-web-swatch\"><i class=\"pv-web-swatch-unknown\"></i><b>색 미상</b></span></div>",
   "preview_variant": null
  },
  {
   "id": "web-text-input",
   "platform": "web",
   "name": "TextInput",
   "category": "입력",
   "purpose": "두 크기와 아이콘·비밀번호 표시 토글을 지원하는 텍스트 입력",
   "variants": [
    "sm",
    "md",
    "focus",
    "disabled",
    "prefixIcon",
    "suffixIcon",
    "password-hidden",
    "password-visible"
   ],
   "props": [
    {
     "name": "value",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(next: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "TextInputSize",
     "required": false,
     "default": "'sm'"
    },
    {
     "name": "type",
     "type": "'text' | 'number' | 'email' | 'password' | 'tel' | 'date'",
     "required": false,
     "default": "'text'"
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "prefixIcon",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "suffixIcon",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "passwordToggle",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "width",
     "type": "number | string",
     "required": false,
     "default": "'100%'"
    },
    {
     "name": "onPressEnter",
     "type": "() => void",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-surface",
    "--color-surface-muted",
    "--color-text-heading",
    "--color-text-muted",
    "--color-text-placeholder",
    "--color-text-placeholder-faint",
    "--font-display"
   ],
   "sizes": "기본sm height36px/border1px/placeholder13px; md height40px/border1.25px/placeholder15px. 공통padding8px 12px/radius8px/gap8px/input글자15px/400/1.428/아이콘18px; width기본100%.",
   "file": "apps/admin-web/src/components/design-system/input/TextInput.tsx",
   "usage_count": 16,
   "notes": "controlled input. passwordToggle과 type=password를 함께 주면 표시/숨김 버튼이 나온다. useId 기반 인스턴스별 placeholder CSS를 style 태그에 렌더한다. 주요 props 10개 외에 id/name/autoComplete/autoFocus/inputMode/pattern/maxLength/inputRef/aria 전달도 선언돼 있다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/input/TextInput.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [
     "{uniqueClass}"
    ],
    "dynamic_class_definitions": [
     {
      "symbol": "uniqueClass",
      "scope": "TextInput",
      "line": 168,
      "source": "`buwang-ti-${autoId.replace(/:/g, '')}`"
     }
    ],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "SIZE_PRESETS",
      "scope": "module",
      "line": 49,
      "source": "{\n  sm: {\n    height: 36,\n    borderWidth: 1,\n    placeholderFontSize: 13,\n    placeholderColor: 'var(--color-text-placeholder-faint)',\n  },\n  md: {\n    height: 40,\n    borderWidth: 1.25,\n    placeholderFontSize: 15,\n    placeholderColor: 'var(--color-text-placeholder)',\n  },\n}"
     },
     {
      "symbol": "buildWrapperBaseStyle",
      "scope": "module",
      "line": 64,
      "source": "function buildWrapperBaseStyle(preset: SizePreset): CSSProperties {\n  return {\n    display: 'inline-flex',\n    alignItems: 'center',\n    gap: 8,\n    height: preset.height,\n    padding: '8px 12px',\n    backgroundColor: 'var(--color-surface)',\n    // border 축약형 + focus 의 borderColor 혼용은 React 스타일 경고/버그 유발 — 분해 지정.\n    borderWidth: preset.borderWidth,\n    borderStyle: 'solid',\n    borderColor: 'var(--color-border)',\n    borderRadius: 8,\n    fontFamily: 'var(--font-display)',\n    fontSize: 15,\n    fontWeight: 400,\n    lineHeight: 1.428,\n    boxSizing: 'border-box',\n    transition: 'border-color 120ms ease',\n  };\n}"
     },
     {
      "symbol": "wrapperFocusStyle",
      "scope": "module",
      "line": 86,
      "source": "{\n  borderColor: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "wrapperDisabledStyle",
      "scope": "module",
      "line": 90,
      "source": "{\n  backgroundColor: 'var(--color-surface-muted)',\n  cursor: 'not-allowed',\n}"
     },
     {
      "symbol": "inputStyle",
      "scope": "module",
      "line": 95,
      "source": "{\n  flex: 1,\n  minWidth: 0,\n  border: 'none',\n  outline: 'none',\n  background: 'transparent',\n  padding: 0,\n  fontFamily: 'inherit',\n  fontSize: 'inherit',\n  fontWeight: 'inherit',\n  lineHeight: 'inherit',\n  color: 'var(--color-text-heading)',\n}"
     },
     {
      "symbol": "inputDisabledStyle",
      "scope": "module",
      "line": 109,
      "source": "{\n  cursor: 'not-allowed',\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "iconStyle",
      "scope": "module",
      "line": 114,
      "source": "{\n  fontSize: 18,\n  lineHeight: 1,\n  color: 'var(--color-text-muted)',\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "toggleButtonStyle",
      "scope": "module",
      "line": 121,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  border: 'none',\n  background: 'transparent',\n  padding: 0,\n  margin: 0,\n  cursor: 'pointer',\n  color: 'var(--color-text-muted)',\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "buildPlaceholderStyle",
      "scope": "module",
      "line": 137,
      "source": "function buildPlaceholderStyle(uniqueClass: string, preset: SizePreset): string {\n  return `.${uniqueClass}::placeholder { color: ${preset.placeholderColor}; opacity: 1; font-size: ${preset.placeholderFontSize}px; font-weight: 400; }`;\n}"
     },
     {
      "symbol": "uniqueClass",
      "scope": "TextInput",
      "line": 168,
      "source": "`buwang-ti-${autoId.replace(/:/g, '')}`"
     },
     {
      "symbol": "wrapperStyle",
      "scope": "TextInput",
      "line": 173,
      "source": "{\n    ...buildWrapperBaseStyle(preset),\n    width,\n    ...(focused && !disabled ? wrapperFocusStyle : null),\n    ...(disabled ? wrapperDisabledStyle : null),\n  }"
     },
     {
      "symbol": "mergedInputStyle",
      "scope": "TextInput",
      "line": 180,
      "source": "{\n    ...inputStyle,\n    ...(disabled ? inputDisabledStyle : null),\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{wrapperStyle}",
      "line": 190
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 193
     },
     {
      "tag": "input",
      "expression": "{mergedInputStyle}",
      "line": 221
     },
     {
      "tag": "button",
      "expression": "{toggleButtonStyle}",
      "line": 230
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 232
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 238
     }
    ],
    "rendered_tags": [
     "div",
     "style",
     "span",
     "input",
     "button"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-text-input\"><div class=\"pv-web-text-input-control pv-web-focused\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 21v-2a8 8 0 0 1 16 0v2\"/></svg><span>김부왕</span></div><div class=\"pv-web-row\"><div class=\"pv-web-text-input-control pv-web-grow\"><span class=\"pv-web-grow\">••••••••</span><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7S2 12 2 12Z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg></div><div class=\"pv-web-text-input-control pv-web-input-disabled pv-web-grow\">입력 불가</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-parts-commerce-vehicle-combobox",
   "platform": "web",
   "name": "VehicleCombobox",
   "category": "입력",
   "purpose": "브랜드·모델을 직접 치면서 고르는 자동완성 콤보박스(별칭 검색 포함)",
   "variants": [
    "closed",
    "open",
    "active-option",
    "disabled"
   ],
   "props": [
    {
     "name": "label",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "value",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "options",
     "type": "VehicleOption[]",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(value: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "placeholder",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "maxLength",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": ".pc-field 필드 한 칸 + 오른쪽 search 아이콘, 아래로 제안 패널(.pc-vehicle-suggestions)에 '브랜드/모델 선택 · N개' 머리글.",
   "file": "apps/admin-web/src/features/partsCommerce/VehicleCombobox.tsx",
   "usage_count": 1,
   "notes": "웹의 콤보박스 a11y 정본이다 — role=combobox + aria-expanded/aria-controls/aria-activedescendant + role=listbox/option, ↑↓ 순환·Enter 선택·Esc 닫기, IME 조합 중(isComposing)에는 키 처리를 건너뛴다. 견적의 SuggestionListbox 가 이 구조를 그대로 따랐다. 별칭(aliases)으로도 걸리게 해 'PCX'/'피씨엑스' 같은 입력을 받는다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-combobox\"><span class=\"pv-web-form-field-label\">모델</span><span class=\"pv-web-text-input-control\">PCX</span><div class=\"pv-web-suggest\"><div class=\"pv-web-suggest-head\"><span>모델 선택</span><span>3개</span></div><span class=\"pv-web-suggest-option pv-web-suggest-active\">PCX125</span><span class=\"pv-web-suggest-option\">PCX160</span><span class=\"pv-web-suggest-option\">PCX 하이브리드</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-parts-commerce-vehicle-editor",
   "platform": "web",
   "name": "VehicleEditor",
   "category": "입력",
   "purpose": "브랜드·모델·연식을 한 벌로 받는 차량 입력기 — 부품을 찾을 대상 차량을 정한다",
   "variants": [
    "default",
    "disabled",
    "custom-apply-label",
    "with-clear"
   ],
   "props": [
    {
     "name": "value",
     "type": "Selection",
     "required": true,
     "default": null
    },
    {
     "name": "onApply",
     "type": "(v: Selection) => void",
     "required": true,
     "default": null
    },
    {
     "name": "onClear",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "onDraftChange",
     "type": "(v: Selection) => void",
     "required": false,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "applyLabel",
     "type": "string",
     "required": false,
     "default": "'이 차량의 부품 보기'"
    },
    {
     "name": "clearLabel",
     "type": "string",
     "required": false,
     "default": "'입력 내용 비우기'"
    }
   ],
   "tokens": [],
   "sizes": "VehicleCombobox 두 칸(브랜드·모델) + 연식 칸 + 적용/비우기 버튼 줄. 적용 라벨 기본 '이 차량의 부품 보기'.",
   "file": "apps/admin-web/src/features/partsCommerce/VehicleSelection.tsx",
   "usage_count": 3,
   "notes": "부품 도우미 독·장바구니 묶음 편집·카탈로그 차량 선택 3곳이 같은 입력기를 쓴다 — 차량 조건이 화면마다 다른 모양이면 같은 부품 검색이 다른 결과로 보인다. onDraftChange 는 아직 적용하지 않은 입력을 바깥이 미리 보여 줄 때만 쓴다(검색은 onApply 에서만).",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-vehicle-editor\"><span class=\"pv-web-form-field-label\">브랜드</span><span class=\"pv-web-text-input-control\">혼다</span><span class=\"pv-web-form-field-label\">모델</span><span class=\"pv-web-text-input-control\">PCX125</span><div class=\"pv-web-row\"><span class=\"pv-web-button-control pv-web-button-primary pv-web-button-sm\">이 차량의 부품 보기</span><span class=\"pv-web-button-control pv-web-button-sm\">비우기</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-three-d-vehicle-picker",
   "platform": "web",
   "name": "VehiclePicker",
   "category": "입력",
   "purpose": "3D 견적 상단 차종 선택기 — 제조사 → 모델 → 연식",
   "variants": [
    "default",
    "loading-years",
    "years-error",
    "no-catalog",
    "unknown-year"
   ],
   "props": [
    {
     "name": "vehicleId",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "onVehicleChange",
     "type": "(id: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "modelYear",
     "type": "string | null",
     "required": true,
     "default": null
    },
    {
     "name": "onModelYearChange",
     "type": "(year: string | null) => void",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "colors",
    "fontSize",
    "fontWeight",
    "space"
   ],
   "sizes": "antd Select 세 칸 가로 배치(제조사·모델·연식). 연식 옵션은 '2021-2023 · 42종'처럼 부품 수를 함께 적는다.",
   "file": "apps/admin-web/src/features/estimates/three3d/VehiclePicker.tsx",
   "usage_count": 1,
   "notes": "연식 목록은 그 차종에 연결된 카탈로그 연식(GET /parts/oem/years)이고, 고른 연식은 아래 부품 목록의 필터로 내려간다. ★조회 실패를 '카탈로그 없음' 으로 뭉치지 않는다 — 토큰 만료·서버 오류인데 정비사는 '이 차종엔 연식 데이터가 없구나' 로 읽는다. 실패는 '연식 불러오기 실패' 로 말한다. 제조사→모델→연식 순서는 2026-09-20 결정(N)이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-vehicle-editor\"><span class=\"pv-web-dropdown-control\"><span>혼다</span></span><span class=\"pv-web-dropdown-control\"><span>PCX125</span></span><span class=\"pv-web-dropdown-control\"><span>2021-2023 · 42종</span></span></div>",
   "preview_variant": null
  },
  {
   "id": "web-view-mode-toggle",
   "platform": "web",
   "name": "ViewModeToggle",
   "category": "입력",
   "purpose": "목록 보기 전환 — 리스트형·앨범형·카드형 아이콘 세그먼트",
   "variants": [
    "list",
    "album",
    "card"
   ],
   "props": [
    {
     "name": "value",
     "type": "ViewMode",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(mode: ViewMode) => void",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "colors.surfaceNeutral",
    "colors.primary",
    "colors.textMuted"
   ],
   "sizes": "칸 36×32px · radius 6px · 묶음 radius 8px/padding 2px/gap 2px · 아이콘 16px. 선택 칸만 흰 배경 + 그림자 0 1px 2px rgba(0,0,0,.08), 색은 colors.primary.",
   "file": "apps/admin-web/src/features/community/components/ViewModeToggle.tsx",
   "usage_count": 2,
   "notes": "커뮤니티 피드와 장터 피드가 같은 전환을 쓴다. 라벨 대신 아이콘만 두고 title 로 이름을 말한다 — 피드 머리줄은 정렬·검색과 한 줄을 나눠 쓰기 때문. 묶음 배경은 colors.surfaceNeutral.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame\"><div class=\"pv-web-mode-toggle\"><span class=\"pv-web-mode-item pv-web-mode-active\">▤</span><span class=\"pv-web-mode-item\">▦</span><span class=\"pv-web-mode-item\">▥</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-estimate-status-tag",
   "platform": "web",
   "name": "EstimateStatusTag",
   "category": "배지",
   "purpose": "견적 상태(작성중·완료·발송됨·수락·거절·만료)를 같은 폭으로 보여 주는 배지",
   "variants": [
    "draft",
    "completed",
    "sent",
    "accepted",
    "rejected",
    "expired"
   ],
   "props": [
    {
     "name": "status",
     "type": "string",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "minWidth 48px · height 22px · paddingInline 8px · borderRadius 10px · fontSize 12px/600 · lineHeight 1 · whiteSpace nowrap.",
   "file": "apps/admin-web/src/features/estimates/EstimateStatusTag.tsx",
   "usage_count": 2,
   "notes": "minWidth 48 로 2글자(수락·만료)와 3글자(작성중·발송됨) 폭을 맞춘다 — antd Tag 는 내용 맞춤이라 같은 열에서 배지 크기가 들쭉날쭉했다. 색은 features/estimates/constants.ts 의 STATUS_COLORS(배경 휘도 한 밴드 + 글자 4.5:1 이상)이며 공용 StatusBadge 의 badgeTone(솔리드)과는 다른 계열이다 — 목록 표에서 연한 배경이 행을 덜 어지럽힌다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-estimate-status\"><span class=\"pv-web-est-draft\">작성중</span><span class=\"pv-web-est-completed\">완료</span><span class=\"pv-web-est-sent\">발송됨</span><span class=\"pv-web-est-completed\">수락</span><span class=\"pv-web-est-rejected\">거절</span><span class=\"pv-web-est-expired\">만료</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-pro-badge",
   "platform": "web",
   "name": "ProBadge",
   "category": "배지",
   "purpose": "프로 구독 전용 기능을 미리 표시하는 골드 배지",
   "variants": [
    "PRO",
    "custom-label"
   ],
   "props": [
    {
     "name": "label",
     "type": "string",
     "required": false,
     "default": "'PRO'"
    },
    {
     "name": "title",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--font-display"
   ],
   "sizes": "padding 1px 7px; borderRadius 6px; fontSize 10px; fontWeight 800; lineHeight 1.5; letterSpacing 0.04em.",
   "file": "apps/admin-web/src/components/design-system/badge/ProBadge.tsx",
   "usage_count": 3,
   "notes": "label 기본값 PRO, title 기본값 '프로 구독 전용 기능'. 골드 그라디언트(#f5b301 → #ff9500)와 글자색 #3a2600은 로컬 리터럴이다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/badge/ProBadge.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "style",
      "scope": "module",
      "line": 5,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  padding: '1px 7px',\n  borderRadius: 6,\n  background: 'linear-gradient(90deg, #f5b301, #ff9500)',\n  color: '#3a2600',\n  fontSize: 10,\n  fontWeight: 800,\n  lineHeight: 1.5,\n  letterSpacing: '0.04em',\n  fontFamily: 'var(--font-display)',\n  verticalAlign: 'middle',\n  whiteSpace: 'nowrap',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "span",
      "expression": "{style}",
      "line": 23
     }
    ],
    "rendered_tags": [
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-pro-badge\"><div class=\"pv-web-row\"><span>매출 분석</span><span class=\"pv-web-pro-badge-chip\" title=\"프로 구독 전용 기능\">PRO</span></div><div class=\"pv-web-row\"><span class=\"pv-web-caption\">고급 통계</span><span class=\"pv-web-pro-badge-chip\">PRO 전용</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-pro-switch",
   "platform": "web",
   "name": "ProSwitch",
   "category": "배지",
   "purpose": "프로 구독 중임을 '켜진 스위치'와 금관으로 알리는 상태 표시 버튼",
   "variants": [
    "on"
   ],
   "props": [
    {
     "name": "label",
     "type": "string",
     "required": false,
     "default": "'PRO'"
    },
    {
     "name": "title",
     "type": "string",
     "required": false,
     "default": "'프로 멤버십 이용 중 — 멤버십·결제 관리'"
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": "'프로 멤버십 이용 중 — 멤버십·결제 관리로 이동'"
    },
    {
     "name": "onClick",
     "type": "(event: MouseEvent<HTMLButtonElement>) => void",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--font-display",
    "--color-primary-hover-bg"
   ],
   "sizes": "트랙 padding 2px 3px 2px 9px; gap 5px; borderRadius 999px; 금관 18x18px(≤640px 16px, position:absolute left -2 top -12); 라벨 11px/800/0.04em; 손잡이 18x18px(≤640px 16px).",
   "file": "apps/admin-web/src/components/design-system/badge/ProSwitch.tsx",
   "usage_count": 1,
   "notes": "ON 전용이다 — OFF 자리는 상단바의 '프로 업그레이드' CTA 가 맡는다. role 은 switch 가 아니라 button: 눌러도 구독이 꺼지지 않고 /billing 으로 이동하므로 aria-checked 를 붙이지 않는다. 트랙 색은 ProBadge 골드 램프(#f5b301 → #ff9500 / 잉크 #3a2600)를 공유한다. 왕관은 아이콘 폰트에 글리프가 없어 3첨두 path 로 직접 그렸고 골드 채움(#ffc83d) + 잉크 테두리(2.4) + -10° 기울임이다 — 흰 상단바 위로 머리가 나가므로 테두리가 형체를 붙든다. ★크기·테두리·회전을 바꿀 때는 3배 확대가 아니라 원래 크기 스크린샷으로 확인할 것(16px 미만 + 가는 테두리 조합은 주황 얼룩이 된다). ★회전 각도는 crownStyle 과 buwang-pro-switch-crown-in 키프레임 두 곳에 있다 — 애니메이션이 인라인 transform 을 덮기 때문. ★알약 밖으로 나가는 배치라 상단바에 overflow:hidden 이 없어야 한다(PR #1498 회귀 → #1510 과 같은 규약). 마운트 시 손잡이가 왼→오로 미끄러지고 라벨·금관이 뒤따르는 키프레임은 prefers-reduced-motion 에서 꺼진다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/badge/ProSwitch.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "buwang-pro-switch",
     "buwang-pro-switch-crown",
     "buwang-pro-switch-label",
     "buwang-pro-switch-knob",
     "material-icons"
    ],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "rendered_tags": [
     "button",
     "svg",
     "path",
     "span"
    ],
    "composed_components": [],
    "portal_target": null
   },
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-parts-commerce-order-status",
   "platform": "web",
   "name": "Status",
   "category": "배지",
   "purpose": "부품 주문의 진행 상태 배지 한 개 + 보조 한 줄 — 정비소/판매자 관점을 같은 자리에서 바꿔 보여 준다",
   "variants": [
    "workshop",
    "dealer",
    "with-note"
   ],
   "props": [
    {
     "name": "order",
     "type": "Order",
     "required": true,
     "default": null
    },
    {
     "name": "dealer",
     "type": "boolean",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "badgeTone"
   ],
   "sizes": "공용 StatusBadge(높이 21px·paddingInline 8px·radius.sm) + 아래 보조문 small. 배지는 언제나 한 개다.",
   "file": "apps/admin-web/src/features/partsCommerce/ui.tsx",
   "usage_count": 2,
   "notes": "orderProgress(order, dealer) 하나가 라벨·톤·보조문을 정한다 — 화면마다 상태를 다시 해석하면 같은 주문이 목록과 상세에서 다르게 읽힌다. 2026-09-18 결정: 상태 배지는 주문당 한 개만(부분 입금·분할 출고 표기는 두지 않는다).",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-order-status\"><span class=\"pv-web-status-badge-chip pv-web-status-progress\">출고 준비 중</span><small class=\"pv-web-caption\">청구서 수락 완료 · 입금 확인 대기</small></div>",
   "preview_variant": null
  },
  {
   "id": "web-status-badge",
   "platform": "web",
   "name": "StatusBadge",
   "category": "배지",
   "purpose": "정비·예약 상태를 솔리드 색상 배지로 표시",
   "variants": [
    "pending",
    "confirmed",
    "inProgress",
    "completed",
    "urgent",
    "cancelled",
    "fill",
    "minWidth"
   ],
   "props": [
    {
     "name": "tone",
     "type": "StatusBadgeTone",
     "required": true,
     "default": null
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "fill",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "minWidth",
     "type": "number",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "badgeTone.cancelled.bg",
    "badgeTone.cancelled.fg",
    "badgeTone.completed.bg",
    "badgeTone.completed.fg",
    "badgeTone.confirmed.bg",
    "badgeTone.confirmed.fg",
    "badgeTone.inProgress.bg",
    "badgeTone.inProgress.fg",
    "badgeTone.pending",
    "badgeTone.pending.bg",
    "badgeTone.pending.fg",
    "badgeTone.urgent.bg",
    "badgeTone.urgent.fg",
    "fontFamily.mono",
    "fontSize.caption",
    "fontWeight.semibold",
    "radius.sm",
    "space.sm",
    "space.xs"
   ],
   "sizes": "fontSize.caption=13px; weight 600; lineHeight 1; paddingBlock 4px/paddingInline 8px; radius.sm=4px. fill: height 44px, width calc(100% + 32px), marginInline -16px/marginBlock -10px, paddingInline 6px, radius 0.",
   "file": "apps/admin-web/src/components/design-system/badge/StatusBadge.tsx",
   "usage_count": 18,
   "notes": "badgeTone[tone]을 직접 사용하므로 global.css의 --badge-* 팔레트와 다르다. 잘못된 tone은 pending으로 폴백한다. fill은 DataTable 기본 td(10px 16px)에 맞춘 값으로 dense 셀에는 맞지 않는다. fill=true일 때 minWidth를 무시한다. accepted는 TS 팔레트에 없다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/badge/StatusBadge.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "baseStyle",
      "scope": "module",
      "line": 22,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  width: 'fit-content',\n  borderRadius: radius.sm,\n  paddingBlock: space.xs,\n  // 2026-08-31: 업계 기본(Polaris 세로2·가로8, 내용 맞춤 폭)에 맞춰 컴팩트하게.\n  // 높이 21px 은 유지, 가로만 12→10. 폭 고정은 호출부 minWidth 로만(기본은 hug).\n  paddingInline: 10,\n  fontFamily: fontFamily.mono,\n  fontSize: fontSize.caption,\n  // 700은 솔리드 13px 에서 획이 뭉쳐 무겁게 읽힌다 — 업계는 medium~semibold.\n  fontWeight: fontWeight.semibold,\n  lineHeight: 1,\n  whiteSpace: 'nowrap',\n}"
     },
     {
      "symbol": "fillStyle",
      "scope": "module",
      "line": 41,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  boxSizing: 'border-box',\n  width: 'calc(100% + 32px)',\n  height: 44,\n  marginInline: -16,\n  marginBlock: -10,\n  borderRadius: 0,\n  paddingBlock: 0,\n  paddingInline: space.sm,\n}"
     },
     {
      "symbol": "palette",
      "scope": "StatusBadge",
      "line": 61,
      "source": "badgeTone[tone] ?? badgeTone.pending"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "span",
      "expression": "{{\n        ...baseStyle,\n        ...(fill ? fillStyle : null),\n        ...(minWidth != null && !fill ? { minWidth, boxSizing: 'border-box' } : null),\n        backgroundColor: palette.bg,\n        color: palette.fg,\n      }}",
      "line": 64
     }
    ],
    "rendered_tags": [
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-status-badge\"><div class=\"pv-web-row\"><span class=\"pv-web-status-badge-chip pv-web-status-confirmed\">예약 확정</span></div><div class=\"pv-web-status-badge-tones\"><span class=\"pv-web-status-badge-chip pv-web-status-pending\">대기</span><span class=\"pv-web-status-badge-chip pv-web-status-confirmed\">확정</span><span class=\"pv-web-status-badge-chip pv-web-status-progress\">정비 중</span><span class=\"pv-web-status-badge-chip pv-web-status-completed\">완료</span><span class=\"pv-web-status-badge-chip pv-web-status-urgent\">긴급</span><span class=\"pv-web-status-badge-chip pv-web-status-cancelled\">취소</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-brand-item",
   "platform": "web",
   "name": "BrandItem",
   "category": "카드",
   "purpose": "브랜드 이름을 작은 색상 점과 함께 표시하는 항목",
   "variants": [
    "static",
    "interactive",
    "custom-color"
   ],
   "props": [
    {
     "name": "label",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "color",
     "type": "string",
     "required": false,
     "default": "'#cbd5e1'"
    },
    {
     "name": "onClick",
     "type": "() => void",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-text-body-strong",
    "fontFamily.display",
    "fontSize.body",
    "fontWeight.regular",
    "space.sm"
   ],
   "sizes": "height 20px; gap space.sm=6px; 점 6×6px/radius 9999px; fontSize.body=15px/400/lineHeight 1.428.",
   "file": "apps/admin-web/src/components/design-system/card/BrandItem.tsx",
   "usage_count": 0,
   "notes": "onClick이 있으면 button, 없으면 div. 기본 점 색상 #cbd5e1은 공용 토큰이 없는 리터럴이다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/card/BrandItem.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 18,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  gap: space.sm,\n  height: 20,\n  padding: 0,\n  fontFamily: fontFamily.display,\n  background: 'transparent',\n  border: 'none',\n  textAlign: 'left',\n  appearance: 'none',\n}"
     },
     {
      "symbol": "bulletStyle",
      "scope": "module",
      "line": 31,
      "source": "{\n  width: 6,\n  height: 6,\n  borderRadius: 9999,\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "labelStyle",
      "scope": "module",
      "line": 38,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.body,\n  fontWeight: fontWeight.regular,\n  lineHeight: 1.428,\n  color: 'var(--color-text-body-strong)',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "button",
      "expression": "{{ ...rootStyle, cursor: 'pointer' }}",
      "line": 60
     },
     {
      "tag": "span",
      "expression": "{{ ...bulletStyle, backgroundColor: color }}",
      "line": 63
     },
     {
      "tag": "span",
      "expression": "{labelStyle}",
      "line": 66
     },
     {
      "tag": "div",
      "expression": "{rootStyle}",
      "line": 72
     },
     {
      "tag": "span",
      "expression": "{{ ...bulletStyle, backgroundColor: color }}",
      "line": 74
     },
     {
      "tag": "span",
      "expression": "{labelStyle}",
      "line": 77
     }
    ],
    "rendered_tags": [
     "button",
     "span",
     "div"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-brand-item\"><div class=\"pv-web-brand-item-control\"><span class=\"pv-web-brand-item-dot\"></span><span>혼다</span></div><div class=\"pv-web-row\"><div class=\"pv-web-brand-item-control\"><span class=\"pv-web-brand-item-dot pv-web-brand-item-blue\"></span><span>야마하</span></div><div class=\"pv-web-brand-item-control\"><span class=\"pv-web-brand-item-dot pv-web-brand-item-green\"></span><span>스즈키</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-callout-figure",
   "platform": "web",
   "name": "CalloutFigure",
   "category": "카드",
   "purpose": "도면(분해도) 위 콜아웃 — 카탈로그에 이미 인쇄된 번호 위에 투명한 링을 얹어 누르게 한다(숫자를 다시 그리지 않는다). 좌표는 도면 폭·높이 비율(0~1)",
   "variants": [
    "ring",
    "badge",
    "leader",
    "active",
    "hover",
    "colour"
   ],
   "props": [
    {
     "name": "src",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "width",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "height",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "alt",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "callouts",
     "type": "Callout[] ({key,label,x,y,color?})",
     "required": true,
     "default": null
    },
    {
     "name": "activeKeys",
     "type": "ReadonlySet<string>",
     "required": false,
     "default": null
    },
    {
     "name": "hoverKey",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "groupKeys",
     "type": "ReadonlySet<string>",
     "required": false,
     "default": null
    },
    {
     "name": "onActivate",
     "type": "(key: string) => void",
     "required": false,
     "default": null
    },
    {
     "name": "onHover",
     "type": "(key: string | null) => void",
     "required": false,
     "default": null
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "number",
     "required": false,
     "default": "24"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary"
   ],
   "sizes": "틀 border1px/radius12 흰 배경, aspect-ratio 는 도면 원본 비(921/460). 링 지름 24px(size) 원형, border2px(active 3px) primary 또는 color, 배경 투명; active = 35% 채움 + 흰 2px 외곽, hover = 16% 채움 + scale 1.12. 인쇄된 번호가 링 안에 그대로 보인다.",
   "file": "apps/admin-web/src/components/design-system/media/CalloutFigure.tsx",
   "usage_count": 1,
   "notes": "div[role=group][aria-label] > img + button.buwang-callout[aria-pressed][aria-label='도면 번호 N'][data-color]. 글자 없음(이름은 aria-label). 같은 번호가 여러 번(볼트 등) 나와도 한 key 로 함께 켜진다. color 가 있으면(색상 부품) 링·채움이 그 색. 포커스 링은 global.css, prefers-reduced-motion 이면 transition·scale 없음. variant='badge' 는 번호 없는 재구성 도면에 채움 원+흰 숫자 배지(26px)를 그리고, 콜아웃에 ax·ay(부품 위 점)가 있으면 svg[data-testid=callout-leaders](viewBox 1000, preserveAspectRatio none) 에 배지→부품 지시선(1.5px, 켜지면 2.5px, vector-effect non-scaling)과 끝점 점(3px)을 긋는다 — 카탈로그 지시선처럼 '이 번호 = 이 부품'(10/16).",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-callout-figure\"><svg viewBox=\"0 0 200 100\" class=\"pv-web-callout-art\" aria-hidden=\"true\"><path d=\"M20 70 C40 20, 90 15, 120 40 S170 75, 185 55\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><path d=\"M30 80 L120 82 L150 60\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><circle cx=\"96\" cy=\"52\" r=\"9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><text x=\"47\" y=\"33\" font-size=\"9\" fill=\"currentColor\">4</text><text x=\"119\" y=\"61\" font-size=\"9\" fill=\"currentColor\">10</text><text x=\"163\" y=\"29\" font-size=\"9\" fill=\"currentColor\">18</text></svg><span class=\"pv-web-callout pv-web-callout-active\" style=\"left:25%;top:30%\" aria-label=\"도면 번호 4 선택됨\"></span><span class=\"pv-web-callout pv-web-callout-colour\" style=\"left:62%;top:58%\" aria-label=\"도면 번호 10 색상 부품\"></span><span class=\"pv-web-callout\" style=\"left:84%;top:26%\" aria-label=\"도면 번호 18\"></span></div>",
   "preview_variant": null
  },
  {
   "id": "web-choice-tile",
   "platform": "web",
   "name": "ChoiceTile",
   "category": "카드",
   "purpose": "고르는 타일 — 사진(아이콘) + 이름 + 보조 글 한 장. 카테고리·모델·차량처럼 여럿 중 하나를 그림으로 고른다. ChoiceTileGrid 가 열 수에 맞춰 늘어놓는다",
   "variants": [
    "selected",
    "expanded",
    "row",
    "column",
    "sm",
    "md",
    "disabled",
    "grid"
   ],
   "props": [
    {
     "name": "label",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "caption",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "media",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "selected",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "onClick",
     "type": "() => void",
     "required": true,
     "default": null
    },
    {
     "name": "layout",
     "type": "'row' | 'column'",
     "required": false,
     "default": "'row'"
    },
    {
     "name": "size",
     "type": "'sm' | 'md'",
     "required": false,
     "default": "'md'"
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "expanded",
     "type": "boolean",
     "required": false,
     "default": null
    },
    {
     "name": "controls",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "mediaHeight",
     "type": "number",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-primary-soft",
    "--color-surface",
    "--color-surface-muted",
    "--color-text-body-strong",
    "--color-text-muted",
    "--font-display"
   ],
   "sizes": "타일 padding12/radius14/border1px/gap12; 미디어 md64px·sm48px radius10 surface-muted(column 이면 전폭 4:3); 라벨15px 700, 캡션12px muted. 선택 = primary 보더 + primary-soft 배경 + inset 1px 링. 격자 repeat(auto-fill, minmax(min(200px,100%),1fr)) gap12. column 은 mediaHeight 로 사진 칸 높이를 고정할 수 있다(부품 카테고리 56px).",
   "file": "apps/admin-web/src/components/design-system/card/ChoiceTile.tsx",
   "usage_count": 3,
   "notes": "button[aria-pressed=selected][aria-expanded][aria-controls]. 미디어는 aria-hidden 이라 이름은 라벨·캡션만. 눌린 타일 아래로 패널이 이어질 때 expanded/controls 로 연결한다(부품 카테고리 눌림 패널).",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/card/ChoiceTile.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "notes": "스타일 숫자는 React CSSProperties 원문 기준. 포커스 링·hover 는 global.css 의 .buwang-* 규칙."
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-choice-tiles\"><span class=\"pv-web-choice-tile pv-web-choice-tile-selected\"><span class=\"pv-web-choice-tile-media\"></span><span class=\"pv-web-choice-tile-text\"><b>브레이크</b><small>6종류</small></span></span><span class=\"pv-web-choice-tile\"><span class=\"pv-web-choice-tile-media\"></span><span class=\"pv-web-choice-tile-text\"><b>엔진·흡배기</b><small>12종류</small></span></span></div>",
   "preview_variant": null
  },
  {
   "id": "web-estimate-section-card",
   "platform": "web",
   "name": "EstimateSectionCard",
   "category": "카드",
   "purpose": "견적 작성 화면의 카드 섹션 — 제목·개수 배지·부제·오른쪽 슬롯",
   "variants": [
    "with-title",
    "with-count",
    "with-subtitle",
    "rightSlot",
    "headless",
    "collapse"
   ],
   "props": [
    {
     "name": "title",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "count",
     "type": "number",
     "required": false,
     "default": null
    },
    {
     "name": "subtitle",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "rightSlot",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "data-guide-anchor",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "colors",
    "fontWeight",
    "radius",
    "space"
   ],
   "sizes": "껍데기는 공용 SectionCard(border 1px·radius 16px·surface) + padding estSize.cardPadding. 머리글 gap space.lg=10px, 제목은 estFont 배율을 따른다(공용 카드의 18px 고정 title 슬롯은 쓰지 않는다).",
   "file": "apps/admin-web/src/features/estimates/EstimateSectionCard.tsx",
   "usage_count": 4,
   "notes": "카드 껍데기를 손으로 다시 그리지 않고 SectionCard 를 그대로 쓴다 — 같은 모양을 복제하면 DS 가 움직일 때 이 화면만 뒤처진다. 머리글만 children 으로 넣어 estimateScale(작성 화면 전용 글자 배율)을 따르게 했다. 같은 파일의 EstimateCollapseCard 는 접힌 상태에서도 내용을 한 줄로 말해 주는 접이식 변형이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-section-card\"><div class=\"pv-web-section-card-panel\"><div class=\"pv-web-section-card-header\"><span class=\"pv-web-row\"><span class=\"pv-web-section-card-title\">수리 항목</span><span class=\"pv-web-count-badge\">2개</span></span><span class=\"pv-web-link\">간편 입력</span></div><div class=\"pv-web-between\"><span class=\"pv-web-caption\">브레이크 패드 교체</span><span>86,000원</span></div><div class=\"pv-web-between\"><span class=\"pv-web-caption\">엔진오일 교환</span><span>38,000원</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-section-card",
   "platform": "web",
   "name": "SectionCard",
   "category": "카드",
   "purpose": "제목과 우측 슬롯을 갖춘 표준 흰색 섹션 카드",
   "variants": [
    "default",
    "flush",
    "custom-padding",
    "with-title",
    "rightSlot",
    "custom-className"
   ],
   "props": [
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "rightSlot",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "padding",
     "type": "number | string",
     "required": false,
     "default": "12"
    },
    {
     "name": "flush",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "className",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "data-guide-anchor",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-surface",
    "--color-text-heading",
    "--font-display",
    "--shadow-card-strong"
   ],
   "sizes": "width100%; 기본padding12px; border1px/radius16px; 제목18px/600/1.5. 헤더gap12px/marginBottom12px; flush는 padding0/overflow hidden, 헤더padding12px 16px 0.",
   "file": "apps/admin-web/src/components/design-system/layout/SectionCard.tsx",
   "usage_count": 12,
   "notes": "flush=true이면 padding prop 무시. 표/탭/페이지네이션을 카드 모서리에 붙일 때 사용. title 또는 rightSlot이 있을 때만 header 렌더. data-guide-anchor 전달. 기본 padding 은 16→12 로 줄었다(카드 하나당 좌우 8px 을 표·차트로 돌려준다).",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/layout/SectionCard.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [
     "{className}"
    ],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "titleStyle",
      "scope": "module",
      "line": 19,
      "source": "{\n  fontSize: 18, // heading3 유지 — 섹션 위계\n  fontWeight: 600,\n  lineHeight: 1.5,\n  color: 'var(--color-text-heading)',\n  margin: 0,\n}"
     },
     {
      "symbol": "rootStyle",
      "scope": "SectionCard",
      "line": 36,
      "source": "{\n    width: '100%',\n    backgroundColor: 'var(--color-surface)',\n    border: '1px solid var(--color-border)',\n    borderRadius: 16,\n    boxShadow: 'var(--shadow-card-strong)',\n    padding: flush ? 0 : padding,\n    overflow: flush ? 'hidden' : undefined,\n    boxSizing: 'border-box',\n    fontFamily: 'var(--font-display)',\n  }"
     },
     {
      "symbol": "headerStyle",
      "scope": "SectionCard",
      "line": 47,
      "source": "{\n    display: 'flex',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n    gap: 12,\n    marginBottom: 12,\n    ...(flush ? { padding: '12px 16px 0' } : null),\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "section",
      "expression": "{rootStyle}",
      "line": 59
     },
     {
      "tag": "header",
      "expression": "{headerStyle}",
      "line": 63
     },
     {
      "tag": "h3",
      "expression": "{titleStyle}",
      "line": 64
     }
    ],
    "rendered_tags": [
     "section",
     "header",
     "h3",
     "span",
     "div"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-section-card\"><div class=\"pv-web-section-card-panel\"><div class=\"pv-web-section-card-header\"><span class=\"pv-web-section-card-title\">차량 정보</span><span class=\"pv-web-link\">수정</span></div><div class=\"pv-web-between\"><span class=\"pv-web-caption\">차량</span><span>PCX125 · 1가2 3456</span></div><div class=\"pv-web-between\"><span class=\"pv-web-caption\">정비 항목</span><span>엔진오일 교체</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-staff-card",
   "platform": "web",
   "name": "StaffCard",
   "category": "카드",
   "purpose": "직원 프로필과 월 정비·매출 통계를 표시하는 카드",
   "variants": [
    "avatar-image",
    "avatar-initial",
    "permission-label",
    "kebab-action"
   ],
   "props": [
    {
     "name": "name",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "role",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "avatarUrl",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "avatarInitial",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "stats",
     "type": "StaffCardStats",
     "required": true,
     "default": null
    },
    {
     "name": "permissionLabel",
     "type": "string",
     "required": false,
     "default": "'관리 권한 보유'"
    },
    {
     "name": "onKebabClick",
     "type": "() => void",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-primary-soft",
    "--color-success-alt",
    "--color-surface",
    "--color-surface-muted",
    "--color-text-heading",
    "--color-text-muted",
    "--color-text-muted-2",
    "--color-text-on-primary",
    "--shadow-card-strong",
    "fontFamily.display",
    "fontSize.body",
    "fontSize.caption",
    "fontSize.heading4",
    "fontSize.micro",
    "fontWeight.medium",
    "fontWeight.semibold",
    "radius.lg",
    "radius.md",
    "space.md",
    "space['2xl']"
   ],
   "sizes": "minHeight 310px; padding/gap 16px; radius 16px; border 1.25px. 아바타 56px; 통계패널 minHeight 50px/padding 8px 12px/radius 8px; 권한칩 height 36px; 메뉴 버튼 24px/아이콘20px.",
   "file": "apps/admin-web/src/components/design-system/card/StaffCard.tsx",
   "usage_count": 0,
   "notes": "permissionLabel 기본 '관리 권한 보유'. 권한 표시는 비활성 버튼이 아닌 role=status. 메뉴 버튼은 콜백이 없어도 렌더된다. 아바타 이미지 실패 폴백은 구현하지 않는다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/card/StaffCard.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 28,
      "source": "{\n  position: 'relative',\n  width: '100%',\n  minHeight: 310,\n  borderRadius: radius.lg,\n  backgroundColor: 'var(--color-surface)',\n  border: '1.25px solid var(--color-border)',\n  boxShadow: 'var(--shadow-card-strong)',\n  padding: space['2xl'],\n  display: 'flex',\n  flexDirection: 'column',\n  gap: space['2xl'],\n  fontFamily: fontFamily.display,\n  boxSizing: 'border-box',\n}"
     },
     {
      "symbol": "topRowStyle",
      "scope": "module",
      "line": 44,
      "source": "{\n  display: 'flex',\n  alignItems: 'flex-start',\n  gap: space['2xl'],\n  position: 'relative',\n}"
     },
     {
      "symbol": "avatarStyle",
      "scope": "module",
      "line": 51,
      "source": "{\n  width: 56,\n  height: 56,\n  borderRadius: '50%',\n  backgroundColor: 'var(--color-primary)',\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  overflow: 'hidden',\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "avatarImgStyle",
      "scope": "module",
      "line": 63,
      "source": "{\n  width: '100%',\n  height: '100%',\n  objectFit: 'cover',\n}"
     },
     {
      "symbol": "avatarInitialStyle",
      "scope": "module",
      "line": 69,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: 22,\n  fontWeight: fontWeight.semibold,\n  color: 'var(--color-text-on-primary)',\n  lineHeight: 1,\n}"
     },
     {
      "symbol": "nameBlockStyle",
      "scope": "module",
      "line": 77,
      "source": "{\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 2,\n  flex: 1,\n  minWidth: 0,\n}"
     },
     {
      "symbol": "nameStyle",
      "scope": "module",
      "line": 85,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.heading4,\n  fontWeight: fontWeight.semibold,\n  lineHeight: 1.5,\n  color: 'var(--color-text-heading)',\n}"
     },
     {
      "symbol": "roleStyle",
      "scope": "module",
      "line": 93,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.caption,\n  fontWeight: fontWeight.medium,\n  lineHeight: 1.333,\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "kebabButtonStyle",
      "scope": "module",
      "line": 101,
      "source": "{\n  appearance: 'none',\n  background: 'transparent',\n  border: 'none',\n  padding: 0,\n  margin: 0,\n  cursor: 'pointer',\n  color: 'var(--color-text-muted)',\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  width: 24,\n  height: 24,\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "kebabIconStyle",
      "scope": "module",
      "line": 117,
      "source": "{\n  fontSize: 20,\n  lineHeight: 1,\n}"
     },
     {
      "symbol": "statsCardStyle",
      "scope": "module",
      "line": 122,
      "source": "{\n  borderRadius: radius.md,\n  backgroundColor: 'var(--color-primary-soft)',\n  minHeight: 50,\n  padding: '8px 12px',\n  display: 'grid',\n  gridTemplateColumns: '1fr 1fr',\n  gap: space.md,\n}"
     },
     {
      "symbol": "statColStyle",
      "scope": "module",
      "line": 132,
      "source": "{\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 2,\n  minWidth: 0,\n}"
     },
     {
      "symbol": "statLabelStyle",
      "scope": "module",
      "line": 141,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.micro,\n  fontWeight: fontWeight.medium,\n  lineHeight: 1.333,\n  color: 'var(--color-text-muted-2)',\n}"
     },
     {
      "symbol": "statValueWorkStyle",
      "scope": "module",
      "line": 151,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.body,\n  fontWeight: fontWeight.semibold,\n  lineHeight: 1.333,\n  color: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "statValueRevenueStyle",
      "scope": "module",
      "line": 159,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.body,\n  fontWeight: fontWeight.semibold,\n  lineHeight: 1.333,\n  color: 'var(--color-success-alt)',\n}"
     },
     {
      "symbol": "permissionStatusStyle",
      "scope": "module",
      "line": 170,
      "source": "{\n  marginTop: 'auto',\n  width: '100%',\n  height: 36,\n  borderRadius: radius.md,\n  backgroundColor: 'var(--color-surface-muted)',\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.body,\n  fontWeight: fontWeight.semibold,\n  color: 'var(--color-text-muted)',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{rootStyle}",
      "line": 206
     },
     {
      "tag": "div",
      "expression": "{topRowStyle}",
      "line": 207
     },
     {
      "tag": "span",
      "expression": "{avatarStyle}",
      "line": 208
     },
     {
      "tag": "img",
      "expression": "{avatarImgStyle}",
      "line": 210
     },
     {
      "tag": "span",
      "expression": "{avatarInitialStyle}",
      "line": 212
     },
     {
      "tag": "div",
      "expression": "{nameBlockStyle}",
      "line": 217
     },
     {
      "tag": "div",
      "expression": "{nameStyle}",
      "line": 218
     },
     {
      "tag": "div",
      "expression": "{roleStyle}",
      "line": 219
     },
     {
      "tag": "button",
      "expression": "{kebabButtonStyle}",
      "line": 224
     },
     {
      "tag": "span",
      "expression": "{kebabIconStyle}",
      "line": 229
     },
     {
      "tag": "div",
      "expression": "{statsCardStyle}",
      "line": 236
     },
     {
      "tag": "div",
      "expression": "{statColStyle}",
      "line": 237
     },
     {
      "tag": "span",
      "expression": "{statLabelStyle}",
      "line": 238
     },
     {
      "tag": "span",
      "expression": "{statValueWorkStyle}",
      "line": 239
     },
     {
      "tag": "div",
      "expression": "{statColStyle}",
      "line": 241
     },
     {
      "tag": "span",
      "expression": "{statLabelStyle}",
      "line": 242
     },
     {
      "tag": "span",
      "expression": "{statValueRevenueStyle}",
      "line": 243
     },
     {
      "tag": "div",
      "expression": "{permissionStatusStyle}",
      "line": 248
     }
    ],
    "rendered_tags": [
     "div",
     "span",
     "img",
     "button"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-staff-card\"><div class=\"pv-web-staff-card-scale\"><div class=\"pv-web-staff-card-panel\"><div class=\"pv-web-staff-card-top\"><span class=\"pv-web-staff-card-avatar\">김</span><div class=\"pv-web-staff-card-name-block\"><span class=\"pv-web-staff-card-name\">김부왕</span><span class=\"pv-web-staff-card-role\">책임 정비사</span></div><span class=\"pv-web-staff-card-menu\" aria-label=\"직원 메뉴\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"5\" r=\"2\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/><circle cx=\"12\" cy=\"19\" r=\"2\"/></svg></span></div><div class=\"pv-web-staff-card-stats\"><div><span class=\"pv-web-staff-card-stat-label\">이번 달 정비</span><span class=\"pv-web-staff-card-work\">42건</span></div><div><span class=\"pv-web-staff-card-stat-label\">매출 기여</span><span class=\"pv-web-staff-card-revenue\">₩3,240,000</span></div></div><div class=\"pv-web-staff-card-permission\">관리 권한 보유</div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-stat-card",
   "platform": "web",
   "name": "StatCard",
   "category": "카드",
   "purpose": "지표 값·단위와 증감 추세를 표시하는 요약 카드",
   "variants": [
    "none",
    "danger",
    "warning",
    "trend-up",
    "trend-down",
    "trend-flat",
    "with-icon"
   ],
   "props": [
    {
     "name": "label",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "value",
     "type": "string | number",
     "required": true,
     "default": null
    },
    {
     "name": "unit",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "icon",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "trend",
     "type": "StatCardTrend",
     "required": false,
     "default": null
    },
    {
     "name": "accent",
     "type": "StatCardAccent",
     "required": false,
     "default": "'none'"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-danger",
    "--color-danger-text",
    "--color-primary",
    "--color-success-alt",
    "--color-surface",
    "--color-text-body",
    "--color-text-heading",
    "--color-text-muted",
    "--color-warning-text",
    "--shadow-card-strong",
    "fontFamily.display",
    "fontSize.body",
    "fontSize.caption",
    "fontSize.statNumber",
    "fontWeight.bold",
    "fontWeight.medium",
    "fontWeight.regular",
    "radius.lg",
    "space.md",
    "space.xs",
    "space['2xl']"
   ],
   "sizes": "minHeight 96px; padding 16px; radius.lg=16px; border 1px; gap 8px. 값 28px/700/1.5; 라벨 15px/500/1.428; 추세 13px/500; 아이콘22px.",
   "file": "apps/admin-web/src/components/design-system/card/StatCard.tsx",
   "usage_count": 7,
   "notes": "accent는 buildLabelStyle에서 라벨 색에만 반영된다(값은 항상 textHeading). 좌측 강조선 없음. warning 라벨은 --color-warning-text 사용. 추세 up=success-alt, down=danger-text, flat=text-muted.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/card/StatCard.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "buildRootStyle",
      "scope": "module",
      "line": 31,
      "source": "function buildRootStyle(_accent: StatCardAccent): CSSProperties {\n  return {\n    position: 'relative',\n    width: '100%',\n    minHeight: 96,\n    borderRadius: radius.lg,\n    backgroundColor: 'var(--color-surface)',\n    border: '1px solid var(--color-border)',\n    boxShadow: 'var(--shadow-card-strong)',\n    padding: '16px',\n    display: 'flex',\n    flexDirection: 'column',\n    gap: space.md,\n    fontFamily: fontFamily.display,\n    boxSizing: 'border-box',\n  };\n}"
     },
     {
      "symbol": "buildLabelStyle",
      "scope": "module",
      "line": 49,
      "source": "function buildLabelStyle(accent: StatCardAccent): CSSProperties {\n  // warning(#f59e0b)은 흰 카드 위 2.15:1 — 카드의 유일한 식별 라벨이 제일 흐린\n  // 텍스트가 됐다(감사 HIGH). 텍스트는 전용 토큰(warningText, 4.5:1+)을 쓴다.\n  const baseColor =\n    accent === 'danger'\n      ? 'var(--color-danger)'\n      : accent === 'warning'\n        ? 'var(--color-warning-text)'\n        : 'var(--color-text-body)';\n  return {\n    fontFamily: fontFamily.display,\n    fontSize: fontSize.body,\n    fontWeight: fontWeight.medium,\n    lineHeight: 1.428,\n    color: baseColor,\n    margin: 0,\n  };\n}"
     },
     {
      "symbol": "valueRowStyle",
      "scope": "module",
      "line": 68,
      "source": "{\n  display: 'flex',\n  alignItems: 'baseline',\n  gap: space.xs,\n}"
     },
     {
      "symbol": "valueStyle",
      "scope": "module",
      "line": 74,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.statNumber,\n  fontWeight: fontWeight.bold,\n  lineHeight: 1.5, // spec §3.2 text-stat-number 28/700/1.5\n  color: 'var(--color-text-heading)',\n  fontVariantNumeric: 'tabular-nums',\n}"
     },
     {
      "symbol": "unitStyle",
      "scope": "module",
      "line": 83,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.body,\n  fontWeight: fontWeight.regular,\n  lineHeight: 1.428,\n  color: 'var(--color-text-body)',\n}"
     },
     {
      "symbol": "iconStyle",
      "scope": "module",
      "line": 91,
      "source": "{\n  position: 'absolute',\n  top: space['2xl'],\n  right: space['2xl'],\n  fontSize: 22,\n  lineHeight: 1,\n  color: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "trendRowStyle",
      "scope": "module",
      "line": 100,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  gap: space.xs,\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.caption,\n  fontWeight: fontWeight.medium,\n  lineHeight: 1,\n}"
     },
     {
      "symbol": "trendIconStyle",
      "scope": "module",
      "line": 110,
      "source": "{\n  fontSize: 14,\n  lineHeight: 1,\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{buildRootStyle(accent)}",
      "line": 137
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 141
     },
     {
      "tag": "div",
      "expression": "{buildLabelStyle(accent)}",
      "line": 147
     },
     {
      "tag": "div",
      "expression": "{valueRowStyle}",
      "line": 148
     },
     {
      "tag": "span",
      "expression": "{valueStyle}",
      "line": 149
     },
     {
      "tag": "span",
      "expression": "{unitStyle}",
      "line": 150
     },
     {
      "tag": "div",
      "expression": "{{ ...trendRowStyle, color: trendColor(trend.direction) }}",
      "line": 153
     },
     {
      "tag": "span",
      "expression": "{trendIconStyle}",
      "line": 156
     }
    ],
    "rendered_tags": [
     "div",
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-stat-card\"><div class=\"pv-web-stat-card-scale\"><div class=\"pv-web-stat-card-panel\"><span class=\"pv-web-stat-card-label\">오늘 정비</span><svg class=\"pv-web-icon pv-web-stat-card-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M4 20V4m0 16h17M8 16v-4m5 4V7m5 9V9\"/></svg><div class=\"pv-web-stat-card-value-row\"><span class=\"pv-web-stat-card-value\">24</span><span>건</span></div><div class=\"pv-web-stat-card-trend\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"m3 17 6-6 4 4 8-9m-6 0h6v6\"/></svg>전일 대비 12% 증가</div></div><div class=\"pv-web-stat-card-alternates\"><span class=\"pv-web-stat-card-warning\">입고 대기 3건</span><span class=\"pv-web-stat-card-danger\">긴급 정비 1건</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-task-card",
   "platform": "web",
   "name": "TaskCard",
   "category": "카드",
   "purpose": "번호판·정비 작업·담당자·진행 상태를 표시하는 작업 카드",
   "variants": [
    "normal",
    "urgent",
    "progress",
    "completed",
    "interactive",
    "static",
    "rightBadge"
   ],
   "props": [
    {
     "name": "plateNumber",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "urgency",
     "type": "'urgent' | 'normal'",
     "required": false,
     "default": "'normal'"
    },
    {
     "name": "model",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "serviceName",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "technician",
     "type": "TaskCardTechnician",
     "required": false,
     "default": null
    },
    {
     "name": "time",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "onClick",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "progress",
     "type": "number",
     "required": false,
     "default": null
    },
    {
     "name": "completed",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "rightBadge",
     "type": "ReactNode",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-surface",
    "--color-text-body-strong",
    "--color-text-heading",
    "--color-text-muted",
    "--shadow-card-soft",
    "fontFamily.sidebar",
    "fontSize.caption",
    "fontSize.heading4",
    "fontSize.micro",
    "fontWeight.bold",
    "fontWeight.medium",
    "fontWeight.regular",
    "fontWeight.semibold",
    "radius.lg",
    "space.md",
    "space.sm",
    "space.xl",
    "space.xs"
   ],
   "sizes": "minHeight 144px; padding 12px; gap 4px; radius 16px; border 1.25px. 긴급배지 padding 2px 6px/radius 8px/font12px; 담당자·완료칩20px; 진행막대 height6px/radius9999px; time 아이콘15px.",
   "file": "apps/admin-web/src/components/design-system/card/TaskCard.tsx",
   "usage_count": 1,
   "notes": "onClick에 따라 button/div. progress를 0~100으로 제한한다. formatPlateNumber로 번호판 표시를 정규화한다. 긴급배지 #fef2f2/#dc2626, 아바타 #dbeafe, 진행 #3b82f6, 완료체크 #10b981는 로컬 팔레트로 StatusBadge를 사용하지 않는다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/card/TaskCard.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 36,
      "source": "{\n  position: 'relative',\n  width: '100%',\n  minHeight: 144,\n  borderRadius: radius.lg,\n  backgroundColor: 'var(--color-surface)',\n  border: '1.25px solid var(--color-border)',\n  boxShadow: 'var(--shadow-card-soft)',\n  padding: space.xl, // 12 — spec §5.5 card internal padding scale\n  display: 'flex',\n  flexDirection: 'column',\n  gap: space.xs,\n  fontFamily: fontFamily.sidebar,\n  boxSizing: 'border-box',\n  textAlign: 'left',\n}"
     },
     {
      "symbol": "topRowStyle",
      "scope": "module",
      "line": 53,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'space-between',\n  gap: space.md,\n}"
     },
     {
      "symbol": "plateStyle",
      "scope": "module",
      "line": 60,
      "source": "{\n  fontFamily: fontFamily.sidebar,\n  fontSize: fontSize.heading4,\n  fontWeight: fontWeight.bold,\n  lineHeight: 1.25,\n  color: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "urgentBadgeStyle",
      "scope": "module",
      "line": 71,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  padding: '2px 6px',\n  borderRadius: 8,\n  backgroundColor: '#fef2f2',\n  color: '#dc2626',\n  fontFamily: fontFamily.sidebar,\n  fontSize: 12,\n  fontWeight: fontWeight.bold,\n  lineHeight: 1.2,\n  whiteSpace: 'nowrap',\n}"
     },
     {
      "symbol": "modelStyle",
      "scope": "module",
      "line": 86,
      "source": "{\n  fontFamily: fontFamily.sidebar,\n  fontSize: fontSize.heading4,\n  fontWeight: fontWeight.semibold,\n  lineHeight: 1.375,\n  color: 'var(--color-text-heading)',\n}"
     },
     {
      "symbol": "serviceStyle",
      "scope": "module",
      "line": 94,
      "source": "{\n  fontFamily: fontFamily.sidebar,\n  fontSize: fontSize.caption,\n  fontWeight: fontWeight.regular,\n  lineHeight: 1.333,\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "bottomRowStyle",
      "scope": "module",
      "line": 102,
      "source": "{\n  marginTop: 'auto',\n  paddingTop: space.md,\n  borderTop: '1px solid #f8fafc',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'space-between',\n  gap: space.md,\n}"
     },
     {
      "symbol": "avatarChipStyle",
      "scope": "module",
      "line": 112,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  gap: space.sm,\n}"
     },
     {
      "symbol": "avatarCircleStyle",
      "scope": "module",
      "line": 120,
      "source": "{\n  width: 20,\n  height: 20,\n  borderRadius: '50%',\n  backgroundColor: '#dbeafe',\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  overflow: 'hidden',\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "avatarInitialStyle",
      "scope": "module",
      "line": 132,
      "source": "{\n  fontFamily: fontFamily.sidebar,\n  fontSize: 13,\n  fontWeight: fontWeight.medium,\n  lineHeight: 1,\n  color: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "avatarImgStyle",
      "scope": "module",
      "line": 140,
      "source": "{\n  width: '100%',\n  height: '100%',\n  objectFit: 'cover',\n}"
     },
     {
      "symbol": "technicianNameStyle",
      "scope": "module",
      "line": 146,
      "source": "{\n  fontFamily: fontFamily.sidebar,\n  fontSize: fontSize.micro,\n  fontWeight: fontWeight.medium,\n  lineHeight: 1.2,\n  color: 'var(--color-text-body-strong)',\n}"
     },
     {
      "symbol": "timeChipStyle",
      "scope": "module",
      "line": 154,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  gap: space.xs,\n  fontFamily: fontFamily.sidebar,\n  fontSize: fontSize.micro,\n  fontWeight: fontWeight.regular,\n  lineHeight: 1.2,\n  // sidebarFgMuted(#f0f4fa)는 파란 사이드바 전용 — 흰 카드 위에선 대비 1.1:1 로\n  // 입고 시각이 사실상 안 보였다(2026-08-31 디자인 감사 HIGH).\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "timeIconStyle",
      "scope": "module",
      "line": 167,
      "source": "{\n  fontSize: 15,\n  lineHeight: 1,\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "progressBarWrapStyle",
      "scope": "module",
      "line": 173,
      "source": "{\n  width: '100%',\n  height: 6,\n  borderRadius: 9999,\n  backgroundColor: '#f1f5f9',\n  overflow: 'hidden',\n}"
     },
     {
      "symbol": "buildProgressFillStyle",
      "scope": "module",
      "line": 181,
      "source": "function buildProgressFillStyle(pct: number): CSSProperties {\n  return {\n    width: `${Math.min(100, Math.max(0, pct))}%`,\n    height: '100%',\n    borderRadius: 9999,\n    backgroundColor: '#3b82f6',\n    transition: 'width 300ms ease',\n  };\n}"
     },
     {
      "symbol": "completedCheckStyle",
      "scope": "module",
      "line": 191,
      "source": "{\n  width: 20,\n  height: 20,\n  borderRadius: '50%',\n  backgroundColor: '#ecfdf5',\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "completedCheckIconStyle",
      "scope": "module",
      "line": 202,
      "source": "{\n  fontSize: 14,\n  lineHeight: 1,\n  color: '#10b981',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{topRowStyle}",
      "line": 229
     },
     {
      "tag": "span",
      "expression": "{plateStyle}",
      "line": 230
     },
     {
      "tag": "span",
      "expression": "{urgentBadgeStyle}",
      "line": 233
     },
     {
      "tag": "span",
      "expression": "{completedCheckStyle}",
      "line": 236
     },
     {
      "tag": "span",
      "expression": "{completedCheckIconStyle}",
      "line": 237
     },
     {
      "tag": "div",
      "expression": "{modelStyle}",
      "line": 241
     },
     {
      "tag": "div",
      "expression": "{serviceStyle}",
      "line": 242
     },
     {
      "tag": "div",
      "expression": "{progressBarWrapStyle}",
      "line": 244
     },
     {
      "tag": "div",
      "expression": "{buildProgressFillStyle(progress)}",
      "line": 245
     },
     {
      "tag": "div",
      "expression": "{bottomRowStyle}",
      "line": 248
     },
     {
      "tag": "div",
      "expression": "{avatarChipStyle}",
      "line": 249
     },
     {
      "tag": "span",
      "expression": "{avatarCircleStyle}",
      "line": 252
     },
     {
      "tag": "img",
      "expression": "{avatarImgStyle}",
      "line": 257
     },
     {
      "tag": "span",
      "expression": "{avatarInitialStyle}",
      "line": 260
     },
     {
      "tag": "span",
      "expression": "{technicianNameStyle}",
      "line": 265
     },
     {
      "tag": "span",
      "expression": "{timeChipStyle}",
      "line": 269
     },
     {
      "tag": "span",
      "expression": "{timeIconStyle}",
      "line": 272
     },
     {
      "tag": "button",
      "expression": "{{ ...rootStyle, cursor: 'pointer' }}",
      "line": 288
     },
     {
      "tag": "div",
      "expression": "{rootStyle}",
      "line": 297
     }
    ],
    "rendered_tags": [
     "div",
     "span",
     "img",
     "button"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-task-card\"><div class=\"pv-web-task-card-scale\"><div class=\"pv-web-task-card-panel\"><div class=\"pv-web-between\"><span class=\"pv-web-task-card-plate\">1가2 3456</span><span class=\"pv-web-task-card-urgent\">긴급</span></div><div class=\"pv-web-task-card-model\">PCX125</div><div class=\"pv-web-task-card-service\">엔진오일 교체</div><div class=\"pv-web-task-card-progress\"><span></span></div><div class=\"pv-web-task-card-bottom\"><span class=\"pv-web-task-card-technician\"><span class=\"pv-web-task-card-avatar\">김</span>김부왕</span><span class=\"pv-web-task-card-time\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 6v6l4 2\"/></svg>14:30</span></div></div><div class=\"pv-web-task-card-alternates\"><span>일반 정비</span><span class=\"pv-web-task-card-completed\"><span>✓</span>정비 완료</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-vehicle-group-view",
   "platform": "web",
   "name": "VehicleGroupView",
   "category": "카드",
   "purpose": "차량 묶음 머리글과 대표 사진 — 장바구니·주문이 차량 단위로 갈리는 자리",
   "variants": [
    "heading",
    "heading-dealer",
    "image-actual",
    "image-generation",
    "image-representative",
    "image-placeholder",
    "stock"
   ],
   "props": [
    {
     "name": "group",
     "type": "VehicleGroup",
     "required": true,
     "default": null
    },
    {
     "name": "dealer",
     "type": "boolean",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [],
   "sizes": ".pc-group-heading = 사진(.pc-group-image) + 라벨(h3)·차량명(p)·번호판(small)·견적 링크. 사진 칸은 비어도 같은 크기를 지키고 two_wheeler/inventory_2 아이콘으로 대체한다.",
   "file": "apps/admin-web/src/features/partsCommerce/VehicleGroupView.tsx",
   "usage_count": 5,
   "notes": "사진은 실차 사진 → 해상(세대) 이미지 → 대표 이미지 순으로 고른다(priority actual<generation<representative). 로드 실패한 src 는 기억해 두고 다음 후보로 내려간다. http(s) 또는 / 로 시작하는 주소만 허용한다(//… 프로토콜 상대 주소는 막는다). 정비소 재고 묶음(kind=stock)은 차량이 없으므로 사진을 찾지 않는다. 판매자 화면에서는 번호판·견적 링크를 숨긴다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-group-heading\"><span class=\"pv-web-group-image\">차량 사진</span><div><strong>서울 강남 가 1234</strong><p class=\"pv-web-caption\">혼다 · PCX125 · 2023년</p><span class=\"pv-web-link\">견적서 #204</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-vehicle-header-card",
   "platform": "web",
   "name": "VehicleHeaderCard",
   "category": "카드",
   "purpose": "번호판·제조사 이미지·모델 정보를 표시하는 차량 헤더",
   "variants": [
    "backgroundOverlay",
    "plain-background",
    "with-year",
    "with-image"
   ],
   "props": [
    {
     "name": "plateNumber",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "model",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "brand",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "year",
     "type": "number",
     "required": false,
     "default": null
    },
    {
     "name": "imageUrl",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "backgroundOverlay",
     "type": "boolean",
     "required": false,
     "default": "true"
    }
   ],
   "tokens": [
    "--color-danger-strong",
    "--color-surface-neutral",
    "--color-text-heading",
    "--color-text-muted",
    "--color-text-on-primary",
    "--color-text-on-primary-muted",
    "--shadow-vehicle-plate",
    "fontFamily.display",
    "fontSize.caption",
    "fontSize.heading2",
    "fontWeight.bold",
    "fontWeight.regular",
    "letterSpacing.vehiclePlate",
    "radius.lg"
   ],
   "sizes": "minHeight 56px; gap16px. 번호판 padding10px 16px/radius16px/font24px/700/letterSpacing1.2px; 이미지50×40px/radius8px; 모델22px/700; 메타13px.",
   "file": "apps/admin-web/src/components/design-system/card/VehicleHeaderCard.tsx",
   "usage_count": 0,
   "notes": "backgroundOverlay 기본 true이며 배경 자체를 그리지 않고 글자색만 흰색 계열로 바꾼다. 번호판 배경 rgba(255,255,255,0.95), 그림자 --shadow-vehicle-plate. formatPlateNumber 적용.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/card/VehicleHeaderCard.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 23,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  gap: 16,\n  minHeight: 56,\n  width: 'fit-content',\n  maxWidth: '100%',\n  fontFamily: fontFamily.display,\n}"
     },
     {
      "symbol": "plateBoxStyle",
      "scope": "module",
      "line": 35,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  borderRadius: radius.lg,\n  backgroundColor: 'rgba(255, 255, 255, 0.95)',\n  boxShadow: 'var(--shadow-vehicle-plate)',\n  padding: '10px 16px',\n  whiteSpace: 'nowrap',\n}"
     },
     {
      "symbol": "plateTextStyle",
      "scope": "module",
      "line": 46,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: 24,\n  fontWeight: fontWeight.bold,\n  letterSpacing: letterSpacing.vehiclePlate,\n  lineHeight: 1.333,\n  color: 'var(--color-danger-strong)',\n}"
     },
     {
      "symbol": "brandImageBoxStyle",
      "scope": "module",
      "line": 55,
      "source": "{\n  width: 50,\n  height: 40,\n  borderRadius: 8,\n  backgroundColor: 'var(--color-surface-neutral)',\n  boxShadow: 'var(--shadow-vehicle-plate)',\n  overflow: 'hidden',\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "brandImgStyle",
      "scope": "module",
      "line": 65,
      "source": "{\n  width: '100%',\n  height: '100%',\n  objectFit: 'cover',\n  display: 'block',\n}"
     },
     {
      "symbol": "modelGroupStyle",
      "scope": "module",
      "line": 72,
      "source": "{\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 2,\n  minWidth: 0,\n}"
     },
     {
      "symbol": "modelStyleBase",
      "scope": "module",
      "line": 79,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.heading2,\n  fontWeight: fontWeight.bold,\n  lineHeight: 1.333,\n}"
     },
     {
      "symbol": "metaStyleBase",
      "scope": "module",
      "line": 86,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.caption,\n  fontWeight: fontWeight.regular,\n  lineHeight: 1.333,\n}"
     },
     {
      "symbol": "modelStyle",
      "scope": "VehicleHeaderCard",
      "line": 101,
      "source": "{\n    ...modelStyleBase,\n    color: backgroundOverlay\n      ? 'var(--color-text-on-primary)'\n      : 'var(--color-text-heading)',\n  }"
     },
     {
      "symbol": "metaStyle",
      "scope": "VehicleHeaderCard",
      "line": 107,
      "source": "{\n    ...metaStyleBase,\n    color: backgroundOverlay\n      ? 'var(--color-text-on-primary-muted)'\n      : 'var(--color-text-muted)',\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{rootStyle}",
      "line": 118
     },
     {
      "tag": "div",
      "expression": "{plateBoxStyle}",
      "line": 122
     },
     {
      "tag": "span",
      "expression": "{plateTextStyle}",
      "line": 123
     },
     {
      "tag": "div",
      "expression": "{brandImageBoxStyle}",
      "line": 125
     },
     {
      "tag": "img",
      "expression": "{brandImgStyle}",
      "line": 126
     },
     {
      "tag": "div",
      "expression": "{modelGroupStyle}",
      "line": 128
     },
     {
      "tag": "div",
      "expression": "{modelStyle}",
      "line": 129
     },
     {
      "tag": "div",
      "expression": "{metaStyle}",
      "line": 130
     }
    ],
    "rendered_tags": [
     "div",
     "span",
     "img"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-vehicle-header-card\"><div class=\"pv-web-vehicle-header-card-scale\"><div class=\"pv-web-vehicle-header-card-row\"><span class=\"pv-web-vehicle-header-card-plate\">1가2 3456</span><div class=\"pv-web-vehicle-header-card-model-group\"><span class=\"pv-web-vehicle-header-card-model\">PCX125</span><span class=\"pv-web-vehicle-header-card-meta\">혼다 · 2024년식</span></div></div><div class=\"pv-web-vehicle-header-card-backdrop\"><div class=\"pv-web-vehicle-header-card-row\"><span class=\"pv-web-vehicle-header-card-plate\">1가2 3456</span><div class=\"pv-web-vehicle-header-card-model-group\"><span class=\"pv-web-vehicle-header-card-model\">PCX125</span><span class=\"pv-web-vehicle-header-card-meta\">혼다 · 2024년식</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-cell-slot",
   "platform": "web",
   "name": "CellSlot",
   "category": "표",
   "purpose": "표 셀 안의 '버튼 자리' — 같은 칸이 상태에 따라 버튼 ⇄ 글자/배지로 바뀌어도 줄 높이가 안 튀게 양쪽을 같은 최소 높이 상자에 넣는다",
   "variants": [
    "height 32 (DS Button sm)",
    "height 24 (antd small)",
    "height 30 (직접 그린 버튼)",
    "align start/center/end"
   ],
   "props": [
    {
     "name": "height",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "align",
     "type": "'start' | 'center' | 'end'",
     "required": false,
     "default": "'start'"
    },
    {
     "name": "style",
     "type": "CSSProperties",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "inline-flex · align-items center · min-height = height prop · max-width 100% · vertical-align middle. 가로 정렬은 셀이 정하고 이 상자는 세로만 맞춘다.",
   "file": "apps/admin-web/src/components/design-system/table/CellSlot.tsx",
   "usage_count": 5,
   "notes": "span.buwang-cell-slot. 예약 '미방문' ⇄ '-', 신고 '처리/기각' ⇄ '-', SOS 수락/거절 ⇄ Tag, 차량 '명의 확인' ⇄ '—'. partsCommerce 표는 CSS :has(> .buwang-button) 규칙으로 같은 효과(commerce.css).",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-data-table",
   "platform": "web",
   "name": "DataTable",
   "category": "표",
   "purpose": "열 정의에 따라 데이터를 렌더하는 범용 HTML 테이블",
   "variants": [
    "default-fixed",
    "auto-layout",
    "loading",
    "empty",
    "clickable-row",
    "non-clickable-row",
    "hover",
    "stickyHeader",
    "bordered",
    "dense",
    "noClip"
   ],
   "props": [
    {
     "name": "columns",
     "type": "Array<DataTableColumn<T>>",
     "required": true,
     "default": null
    },
    {
     "name": "data",
     "type": "T[]",
     "required": true,
     "default": null
    },
    {
     "name": "rowKey",
     "type": "keyof T | ((row: T) => string | number)",
     "required": true,
     "default": null
    },
    {
     "name": "loading",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "onRowClick",
     "type": "(row: T) => void",
     "required": false,
     "default": null
    },
    {
     "name": "isRowClickable",
     "type": "(row: T) => boolean",
     "required": false,
     "default": null
    },
    {
     "name": "stickyHeader",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "stickyTop",
     "type": "number | string",
     "required": false,
     "default": "0"
    },
    {
     "name": "bordered",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "dense",
     "type": "boolean",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary-hover-bg",
    "--color-surface-muted",
    "--color-text-body-strong",
    "--color-text-heading",
    "--color-text-muted",
    "--font-display"
   ],
   "sizes": "width100%; font15px/lineHeight1.428; 기본 th height40px/padding8px 16px/weight600, td height44px/padding10px 16px/weight400. dense th32px/td36px/padding6px 12px; empty/loading height80px. sticky th top=stickyTop(기본0), zIndex1.",
   "file": "apps/admin-web/src/components/design-system/table/DataTable.tsx",
   "usage_count": 8,
   "notes": "AntD Table 래퍼가 아니다. columns[].render 호출, noClip은 셀 말줄임을 해제한다. onRowClick 및 isRowClickable이 허용한 행만 포커스/Enter/Space 클릭/hover 적용. layout 기본 fixed, auto 선택 가능. rowKey 값이 문자열·숫자가 아니면 rowIndex로 폴백. emptyText 기본 '데이터가 없습니다'.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/table/DataTable.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "tableStyle",
      "scope": "module",
      "line": 41,
      "source": "{\n  width: '100%',\n  borderCollapse: 'separate',\n  borderSpacing: 0,\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  lineHeight: 1.428,\n  // 'fixed' binds <colgroup> widths so spec-defined column widths stay exact\n  // even when a cell's content would otherwise push the column wider.\n  tableLayout: 'fixed',\n}"
     },
     {
      "symbol": "theadBaseStyle",
      "scope": "module",
      "line": 53,
      "source": "{\n  backgroundColor: 'var(--color-surface-muted)',\n}"
     },
     {
      "symbol": "thStyle",
      "scope": "module",
      "line": 57,
      "source": "{\n  height: 40,\n  padding: '8px 16px',\n  textAlign: 'left',\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 600,\n  lineHeight: 1.428,\n  color: 'var(--color-text-body-strong)',\n  backgroundColor: 'var(--color-surface-muted)',\n  borderBottom: '1px solid var(--color-border)',\n  boxSizing: 'border-box',\n  whiteSpace: 'nowrap',\n}"
     },
     {
      "symbol": "trBaseStyle",
      "scope": "module",
      "line": 72,
      "source": "{\n  transition: 'background-color 120ms ease',\n}"
     },
     {
      "symbol": "trHoverStyle",
      "scope": "module",
      "line": 76,
      "source": "{\n  backgroundColor: 'var(--color-primary-hover-bg)',\n}"
     },
     {
      "symbol": "trClickableStyle",
      "scope": "module",
      "line": 80,
      "source": "{\n  cursor: 'pointer',\n}"
     },
     {
      "symbol": "tdStyle",
      "scope": "module",
      "line": 84,
      "source": "{\n  height: 44,\n  padding: '10px 16px',\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 400,\n  lineHeight: 1.428,\n  color: 'var(--color-text-heading)',\n  borderBottom: '1px solid var(--color-border)',\n  boxSizing: 'border-box',\n  verticalAlign: 'middle',\n  whiteSpace: 'nowrap',\n  overflow: 'hidden',\n  textOverflow: 'ellipsis',\n}"
     },
     {
      "symbol": "emptyRowStyle",
      "scope": "module",
      "line": 100,
      "source": "{\n  height: 80,\n  padding: '10px 16px',\n  textAlign: 'center',\n  color: 'var(--color-text-muted)',\n  fontSize: 15,\n  fontWeight: 400,\n}"
     },
     {
      "symbol": "loadingRowStyle",
      "scope": "module",
      "line": 109,
      "source": "{\n  ...emptyRowStyle,\n}"
     },
     {
      "symbol": "theadStyle",
      "scope": "DataTable",
      "line": 140,
      "source": "theadBaseStyle"
     },
     {
      "symbol": "thExtra",
      "scope": "DataTable",
      "line": 145,
      "source": "{\n    ...(stickyHeader ? { position: 'sticky', top: stickyTop, zIndex: 1 } : null),\n    ...(bordered ? { border: '1px solid var(--color-border)' } : null),\n    ...(dense ? { padding: '6px 12px', height: 32 } : null),\n  }"
     },
     {
      "symbol": "tdExtra",
      "scope": "DataTable",
      "line": 150,
      "source": "{\n    ...(bordered ? { border: '1px solid var(--color-border)' } : null),\n    ...(dense ? { padding: '6px 12px', height: 36 } : null),\n  }"
     },
     {
      "symbol": "headerCellStyle",
      "scope": "DataTable",
      "line": 176,
      "source": "{\n              ...thStyle,\n              ...(col.align ? { textAlign: col.align } : null),\n              ...(col.width !== undefined ? { width: col.width } : null),\n              ...thExtra,\n            }"
     },
     {
      "symbol": "rowStyle",
      "scope": "DataTable",
      "line": 209,
      "source": "{\n              ...trBaseStyle,\n              ...(clickable ? trClickableStyle : null),\n              ...(isHover && clickable ? trHoverStyle : null),\n            }"
     },
     {
      "symbol": "cellStyle",
      "scope": "DataTable",
      "line": 230,
      "source": "{\n                    ...tdStyle,\n                    ...(col.align ? { textAlign: col.align } : null),\n                    ...(col.align === 'right'\n                      ? { fontVariantNumeric: 'tabular-nums' }\n                      : null),\n                    ...(isHover ? { backgroundColor: 'transparent' } : null),\n                    ...tdExtra,\n                    ...(col.noClip\n                      ? { overflow: 'visible', textOverflow: 'clip', whiteSpace: 'normal' }\n                      : null),\n                  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "table",
      "expression": "{{ ...tableStyle, ...(layout === 'auto' ? { tableLayout: 'auto' } : null) }}",
      "line": 164
     },
     {
      "tag": "col",
      "expression": "{col.width !== undefined ? { width: col.width } : undefined}",
      "line": 169
     },
     {
      "tag": "thead",
      "expression": "{theadStyle}",
      "line": 173
     },
     {
      "tag": "th",
      "expression": "{headerCellStyle}",
      "line": 183
     },
     {
      "tag": "td",
      "expression": "{loadingRowStyle}",
      "line": 193
     },
     {
      "tag": "td",
      "expression": "{emptyRowStyle}",
      "line": 199
     },
     {
      "tag": "tr",
      "expression": "{rowStyle}",
      "line": 217
     },
     {
      "tag": "td",
      "expression": "{cellStyle}",
      "line": 243
     }
    ],
    "rendered_tags": [
     "table",
     "colgroup",
     "col",
     "thead",
     "tr",
     "th",
     "tbody",
     "td"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-data-table\"><div class=\"pv-web-data-table-scale\"><table class=\"pv-web-data-table-table\" aria-label=\"정비 접수 목록\"><colgroup><col class=\"pv-web-data-table-vehicle-column\"/><col class=\"pv-web-data-table-status-column\"/><col/></colgroup><thead><tr><th>차량 번호</th><th>상태</th><th>정비 항목</th></tr></thead><tbody><tr class=\"pv-web-data-table-hover\"><td>1가2 3456</td><td><span class=\"pv-web-status-badge-chip pv-web-status-progress\">정비 중</span></td><td>엔진오일 교체</td></tr><tr><td>서울 강남 가 1234</td><td><span class=\"pv-web-status-badge-chip pv-web-status-pending\">대기</span></td><td>브레이크 점검</td></tr></tbody></table></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-estimate-item-rows",
   "platform": "web",
   "name": "EstimateItemRows",
   "category": "표",
   "purpose": "견적 수리 항목 입력 표 — 항목명·작업·부품비·공임·수량·소계를 인라인으로 적는다",
   "variants": [
    "combined",
    "split",
    "minimal",
    "with-totals",
    "with-suggestions",
    "linked-part-info"
   ],
   "props": [
    {
     "name": "items",
     "type": "SimpleItem[]",
     "required": true,
     "default": null
    },
    {
     "name": "costMode",
     "type": "'combined' | 'split'",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(items: SimpleItem[]) => void",
     "required": true,
     "default": null
    },
    {
     "name": "taxRate",
     "type": "number",
     "required": false,
     "default": "0"
    },
    {
     "name": "minimal",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "showTotals",
     "type": "boolean",
     "required": false,
     "default": "true"
    },
    {
     "name": "suggestionsEnabled",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "vehicleId",
     "type": "number | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "taxIncluded",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "showLinkInfo",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "focusRowId",
     "type": "string | null",
     "required": false,
     "default": "null"
    }
   ],
   "tokens": [
    "colors",
    "fontFamily",
    "fontSize",
    "fontWeight",
    "lineHeight",
    "radius",
    "space"
   ],
   "sizes": "열 배치는 estimateCompose.css 의 그리드. 금액 칸은 원/천원 단위 전환을 지원하고, 항목명 칸은 내용에 따라 자라는 textarea 다.",
   "file": "apps/admin-web/src/features/estimates/EstimateItemRows.tsx",
   "usage_count": 5,
   "notes": "작성 UI 의 작업 선택은 교체/수리 2값만 노출한다(W1-3) — DB enum·서버 CHECK·PDF 매핑은 도장·판금·탈착까지 5값을 유지하고 기존 값은 라벨로만 되살린다. 합계·세액은 estimateCalc(서버와 같은 규칙) 한 곳에서만 계산한다. Enter 로 끼워 넣은 빈 줄만 자동 정리하고 직접 추가한 줄은 비어 있어도 남긴다. 항목명 자동완성은 SuggestionListbox 로 띄운다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-item-rows\"><table><thead><tr><th>항목</th><th>작업</th><th>금액</th></tr></thead><tbody><tr><td>브레이크 패드</td><td>교체</td><td>86,000</td></tr><tr><td>엔진오일</td><td>교체</td><td>38,000</td></tr></tbody><tfoot><tr><td colspan=\"2\">합계</td><td>124,000</td></tr></tfoot></table></div>",
   "preview_variant": null
  },
  {
   "id": "web-grouped-order-lines",
   "platform": "web",
   "name": "GroupedOrderLines",
   "category": "표",
   "purpose": "차량별로 묶어 보여 주는 주문 품목 표",
   "variants": [
    "workshop",
    "dealer",
    "legacy-single-group",
    "no-stock"
   ],
   "props": [
    {
     "name": "order",
     "type": "Order",
     "required": true,
     "default": null
    },
    {
     "name": "dealer",
     "type": "boolean",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": ".pc-card 안에 묶음마다 VehicleGroupHeading + 가로 스크롤 표(.pc-table-wrap). 열: 품목·부품번호·수량·단가·할인액·품목 금액·재고·출고/수령. 묶음 끝에 'N품목 · N개' 요약.",
   "file": "apps/admin-web/src/features/partsCommerce/GroupedOrderLines.tsx",
   "usage_count": 1,
   "notes": "주문은 차량 묶음이 단위다 — 묶음 정보가 없는 옛 주문은 'legacy-order' 한 묶음으로 접어 같은 표로 그린다. 재고 없는 품목은 부품번호를 '—' 로 비우고 재고 칸에 '재고 없음' 을 적는다(2026-09-18 결정: 재고 없는 품목은 청구에서 뺀다). 요청 부품번호와 실제 공급 부품번호가 다를 수 있어 요청 값은 품목 칸 안에 따로 남긴다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-order-lines\"><div class=\"pv-web-group-heading\"><span class=\"pv-web-group-image\">차량 사진</span><div><strong>서울 강남 가 1234</strong><p class=\"pv-web-caption\">혼다 · PCX125</p></div></div><div class=\"pv-web-item-rows\"><table><thead><tr><th>품목</th><th>수량</th><th>금액</th></tr></thead><tbody><tr><td>프런트 커버</td><td>1</td><td>86,000</td></tr><tr><td>클립 세트<small>재고 없음</small></td><td>2</td><td>—</td></tr></tbody></table></div><span class=\"pv-web-caption\">2품목 · 3개</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-chart-card",
   "platform": "web",
   "name": "ChartCard",
   "category": "차트",
   "purpose": "제목과 우측 슬롯을 갖춘 차트·표 콘텐츠 카드",
   "variants": [
    "default",
    "custom-height",
    "custom-minHeight",
    "rightSlot"
   ],
   "props": [
    {
     "name": "title",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "guideAnchor",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "rightSlot",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "height",
     "type": "number | string",
     "required": false,
     "default": null
    },
    {
     "name": "minHeight",
     "type": "number",
     "required": false,
     "default": "288"
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-surface",
    "--color-text-heading",
    "--shadow-card-strong",
    "fontFamily.display",
    "fontSize.heading3",
    "fontWeight.semibold",
    "radius.lg"
   ],
   "sizes": "기본 minHeight 288px; width100%; padding16px; gap16px; radius.lg=16px; border1px. 제목18px/600/1.556; body flex1/minHeight0.",
   "file": "apps/admin-web/src/components/design-system/chart/ChartCard.tsx",
   "usage_count": 1,
   "notes": "차트를 직접 그리지 않고 children을 감싼다. 상단 주석의 radius48/padding25는 실제 코드의 16/16과 다르므로 렌더 코드를 기록했다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/chart/ChartCard.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "buildRootStyle",
      "scope": "module",
      "line": 17,
      "source": "function buildRootStyle(\n  height: number | string | undefined,\n  minHeight: number,\n): CSSProperties {\n  return {\n    width: '100%',\n    height,\n    minHeight,\n    borderRadius: radius.lg,\n    backgroundColor: 'var(--color-surface)',\n    border: '1px solid var(--color-border)',\n    boxShadow: 'var(--shadow-card-strong)',\n    padding: '16px',\n    display: 'flex',\n    flexDirection: 'column',\n    gap: 16,\n    fontFamily: fontFamily.display,\n    boxSizing: 'border-box',\n  };\n}"
     },
     {
      "symbol": "headerStyle",
      "scope": "module",
      "line": 38,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'space-between',\n  gap: 16,\n}"
     },
     {
      "symbol": "titleStyle",
      "scope": "module",
      "line": 45,
      "source": "{\n  fontFamily: fontFamily.display,\n  fontSize: fontSize.heading3,\n  fontWeight: fontWeight.semibold,\n  lineHeight: 1.556,\n  color: 'var(--color-text-heading)',\n  margin: 0,\n}"
     },
     {
      "symbol": "bodyStyle",
      "scope": "module",
      "line": 54,
      "source": "{\n  flex: 1,\n  minHeight: 0,\n  position: 'relative',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "section",
      "expression": "{buildRootStyle(height, minHeight)}",
      "line": 70
     },
     {
      "tag": "header",
      "expression": "{headerStyle}",
      "line": 74
     },
     {
      "tag": "h3",
      "expression": "{titleStyle}",
      "line": 75
     },
     {
      "tag": "div",
      "expression": "{bodyStyle}",
      "line": 78
     }
    ],
    "rendered_tags": [
     "section",
     "header",
     "h3",
     "div"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-chart-card\"><div class=\"pv-web-chart-card-scale\"><div class=\"pv-web-chart-card-panel\"><div class=\"pv-web-chart-card-header\"><span class=\"pv-web-chart-card-title\">주간 정비 건수</span><span class=\"pv-web-caption\">이번 주</span></div><div class=\"pv-web-chart-card-body\"><svg class=\"pv-web-chart-card-chart\" viewBox=\"0 0 330 80\" role=\"img\" aria-label=\"월요일부터 금요일까지 정비 12건, 18건, 15건, 22건, 24건\"><path d=\"M8 16h314M8 40h314M8 65h314\" fill=\"none\" stroke=\"var(--c-border)\"/><g fill=\"var(--c-primary)\"><rect x=\"24\" y=\"39\" width=\"28\" height=\"26\" rx=\"3\"/><rect x=\"86\" y=\"26\" width=\"28\" height=\"39\" rx=\"3\"/><rect x=\"148\" y=\"32\" width=\"28\" height=\"33\" rx=\"3\"/><rect x=\"210\" y=\"17\" width=\"28\" height=\"48\" rx=\"3\"/><rect x=\"272\" y=\"12\" width=\"28\" height=\"53\" rx=\"3\"/></g><g fill=\"var(--c-text-3)\" font-size=\"12\" text-anchor=\"middle\"><text x=\"38\" y=\"79\">월</text><text x=\"100\" y=\"79\">화</text><text x=\"162\" y=\"79\">수</text><text x=\"224\" y=\"79\">목</text><text x=\"286\" y=\"79\">금</text></g></svg></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-menu-bar",
   "platform": "web",
   "name": "MenuBar",
   "category": "내비",
   "purpose": "쇼핑몰형 가로 메뉴바 — 항목을 누르면 아래에 패널이 열린다. 눌린 항목(진열 축)과 열린 패널을 따로 둔다",
   "variants": [
    "active",
    "open",
    "with-icon",
    "rightSlot",
    "overlay-panel",
    "inline-panel",
    "leading-glyph"
   ],
   "props": [
    {
     "name": "items",
     "type": "MenuBarItem[]",
     "required": true,
     "default": null
    },
    {
     "name": "items[].leading",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "activeKey",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "openKey",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "onToggle",
     "type": "(key: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "onClose",
     "type": "() => void",
     "required": true,
     "default": null
    },
    {
     "name": "rightSlot",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "panel",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "panelLabel",
     "type": "string",
     "required": false,
     "default": "'메뉴 패널'"
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": "'메뉴'"
    },
    {
     "name": "overlay",
     "type": "boolean",
     "required": false,
     "default": "true"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-primary-hover-bg",
    "--color-primary-text",
    "--color-surface",
    "--color-text-body-strong",
    "--font-display",
    "--shadow-card-strong"
   ],
   "sizes": "항목 height48px/padding0 14px/밑줄3px(눌림 primary); font15px 600(눌림 700); 아이콘20px. 바 minHeight48px/하단 보더1px/radius12 12 0 0. 패널 absolute top100%/z30/radius0 0 12 12/그림자 card-strong(overlay=false 면 흐름 안).",
   "file": "apps/admin-web/src/components/design-system/filter/MenuBar.tsx",
   "usage_count": 1,
   "notes": "nav[aria-label] > button[aria-pressed=눌림][aria-expanded=열림][aria-controls=패널id]. 패널은 role=region[aria-label]. Escape 는 닫고 포커스를 연 항목으로 돌려보내며, 바깥 mousedown 도 닫는다. rightSlot 에 검색창을 둔다(부품 구매 제조사 메뉴). TabBar 와 달리 화면을 바꾸지 않고 같은 항목을 다시 누르면 패널만 닫힌다. 항목에 leading(제조사 로고 등 장식 노드)을 둘 수 있다. 항목이 한 줄에 다 안 들어가면 rightSlot(검색창)이 다음 줄로 내려간다 — 항목을 숨기지 않는다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/filter/MenuBar.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "notes": "스타일 숫자는 React CSSProperties 원문 기준. 포커스 링·hover 는 global.css 의 .buwang-* 규칙."
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-menu-bar\"><div class=\"pv-web-menu-bar-bar\"><span class=\"pv-web-menu-bar-item\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M4 7h16M4 12h16M4 17h16\"/></svg>전체 제조사</span><span class=\"pv-web-menu-bar-item pv-web-menu-bar-active\">혼다</span><span class=\"pv-web-menu-bar-item\">야마하</span><span class=\"pv-web-menu-bar-search\">품번 또는 부품 이름</span></div><div class=\"pv-web-menu-bar-panel\">모델 · 연식 · 부품 카테고리</div></div>",
   "preview_variant": null
  },
  {
   "id": "web-pagination",
   "platform": "web",
   "name": "Pagination",
   "category": "내비",
   "purpose": "페이지 번호·생략 기호·이전/다음 이동을 제공하는 페이지네이션",
   "variants": [
    "active",
    "inactive",
    "hover",
    "prev-disabled",
    "next-disabled",
    "ellipsis",
    "showFirstLast"
   ],
   "props": [
    {
     "name": "currentPage",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "totalPages",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "onPageChange",
     "type": "(page: number) => void",
     "required": true,
     "default": null
    },
    {
     "name": "siblingCount",
     "type": "number",
     "required": false,
     "default": "1"
    },
    {
     "name": "showFirstLast",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": "'페이지 이동'"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-surface-muted",
    "--color-text-body",
    "--color-text-body-alt",
    "--color-text-muted",
    "--color-text-on-primary",
    "--font-display"
   ],
   "sizes": "루트height56px/gap4px/상단border1px; 버튼32×36px/radius8px/font15px; 화살표22px/border1px; 생략32×36px/font16px/600; disabled opacity0.4.",
   "file": "apps/admin-web/src/components/design-system/table/Pagination.tsx",
   "usage_count": 7,
   "notes": "currentPage/totalPages를 유효 범위로 제한하고 totalPages<=1도 1페이지를 표시한다. siblingCount 기본1, showFirstLast 기본false. totalPages <= siblingCount*2+5이면 전체 번호 표시. 현재 페이지 aria-current=page.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/table/Pagination.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 20,
      "source": "{\n  height: 56,\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  gap: 4,\n  borderTop: '1px solid var(--color-border)',\n  width: '100%',\n  boxSizing: 'border-box',\n  fontFamily: 'var(--font-display)',\n}"
     },
     {
      "symbol": "baseButtonStyle",
      "scope": "module",
      "line": 32,
      "source": "{\n  width: 32,\n  height: 36,\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  border: 'none',\n  borderRadius: 8,\n  backgroundColor: 'transparent',\n  cursor: 'pointer',\n  padding: 0,\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  lineHeight: 1,\n  color: 'var(--color-text-body-alt)',\n  fontWeight: 400,\n  transition: 'background-color 120ms ease',\n}"
     },
     {
      "symbol": "activePageStyle",
      "scope": "module",
      "line": 51,
      "source": "{\n  backgroundColor: 'var(--color-primary)',\n  color: 'var(--color-text-on-primary)',\n  fontWeight: 500,\n}"
     },
     {
      "symbol": "hoverStyle",
      "scope": "module",
      "line": 57,
      "source": "{\n  backgroundColor: 'var(--color-surface-muted)',\n}"
     },
     {
      "symbol": "disabledStyle",
      "scope": "module",
      "line": 61,
      "source": "{\n  opacity: 0.4,\n  cursor: 'not-allowed',\n}"
     },
     {
      "symbol": "chevronIconStyle",
      "scope": "module",
      "line": 66,
      "source": "{\n  fontSize: 22,\n  lineHeight: 1,\n  color: 'var(--color-text-body)',\n}"
     },
     {
      "symbol": "ellipsisStyle",
      "scope": "module",
      "line": 72,
      "source": "{\n  width: 32,\n  height: 36,\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  fontFamily: 'var(--font-display)',\n  fontSize: 16,\n  fontWeight: 600,\n  color: 'var(--color-text-muted)',\n}"
     },
     {
      "symbol": "style",
      "scope": "renderIconButton",
      "line": 155,
      "source": "{\n      ...baseButtonStyle,\n      border: '1px solid var(--color-border)', // 화살표는 버튼 경계를 명확히\n      ...(isHover ? hoverStyle : null),\n      ...(disabled ? disabledStyle : null),\n    }"
     },
     {
      "symbol": "style",
      "scope": "renderPageButton",
      "line": 183,
      "source": "{\n      ...baseButtonStyle,\n      ...(isHover ? hoverStyle : null),\n      ...(active ? activePageStyle : null),\n    }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "button",
      "expression": "{style}",
      "line": 169
     },
     {
      "tag": "span",
      "expression": "{chevronIconStyle}",
      "line": 172
     },
     {
      "tag": "button",
      "expression": "{style}",
      "line": 195
     },
     {
      "tag": "nav",
      "expression": "{rootStyle}",
      "line": 205
     },
     {
      "tag": "span",
      "expression": "{ellipsisStyle}",
      "line": 226
     }
    ],
    "rendered_tags": [
     "button",
     "span",
     "nav"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-pagination\"><div class=\"pv-web-pagination-row\"><span class=\"pv-web-pagination-page pv-web-pagination-arrow pv-web-pagination-disabled\" aria-label=\"이전 페이지 비활성\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"m14 6-6 6 6 6\"/></svg></span><span class=\"pv-web-pagination-page pv-web-pagination-active\">1</span><span class=\"pv-web-pagination-page\">2</span><span class=\"pv-web-pagination-page\">3</span><span class=\"pv-web-pagination-ellipsis\">…</span><span class=\"pv-web-pagination-page\">12</span><span class=\"pv-web-pagination-page pv-web-pagination-arrow\" aria-label=\"다음 페이지\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"m10 6 6 6-6 6\"/></svg></span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-partnavigation",
   "platform": "web",
   "name": "PartNavigation",
   "category": "내비",
   "purpose": "고정 순서의 부위 목록. 선택은 primarySoft 배경과 왼쪽 표시선.",
   "variants": [],
   "props": [],
   "tokens": [
    "colors.primarySoft",
    "colors.surface",
    "space.md",
    "radius.md"
   ],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/filter/PartNavigation.tsx",
   "usage_count": null,
   "notes": "부품 구매 작업대에서 사용.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-segmentedcontrol",
   "platform": "web",
   "name": "SegmentedControl",
   "category": "내비",
   "purpose": "회색 바탕과 흰 선택 칸의 구매 대상·보기 전환. 방향키와 Home/End 지원.",
   "variants": [],
   "props": [],
   "tokens": [
    "colors.primarySoft",
    "colors.surface",
    "space.md",
    "radius.md"
   ],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/filter/SegmentedControl.tsx",
   "usage_count": null,
   "notes": "부품 구매 작업대에서 사용.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-tab-bar",
   "platform": "web",
   "name": "TabBar",
   "category": "내비",
   "purpose": "아이콘 탭과 우측 액션 슬롯을 제공하는 탭 내비게이션",
   "variants": [
    "workspace",
    "compact",
    "active",
    "inactive",
    "hover",
    "focus-visible",
    "with-icon",
    "rightSlot"
   ],
   "props": [
    {
     "name": "variant",
     "type": "'compact' | 'workspace'",
     "required": false,
     "default": "'compact'"
    },
    {
     "name": "tabs",
     "type": "TabItem[]",
     "required": true,
     "default": null
    },
    {
     "name": "activeKey",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(key: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "rightSlot",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "ariaLabel",
     "type": "string",
     "required": false,
     "default": "'탭'"
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-primary-hover-bg",
    "--color-surface",
    "--color-text-muted",
    "--color-text-on-primary",
    "--font-display",
    "--shadow-card-strong"
   ],
   "sizes": "compact(기본): 높이36px/paddingInline12px/radius8px/font15px/아이콘20px. workspace: 전체 너비 균등 배치, 높이72px/font22px/700/아이콘32px/gap16px, 선택 시 연파랑 배경과 4px 하단 강조선. 767px 이하 높이56px/font16px/아이콘24px/gap8px, 한 줄 가로 스크롤.",
   "file": "apps/admin-web/src/components/design-system/filter/TabBar.tsx",
   "usage_count": 6,
   "notes": "nav > role=tablist > button role=tab. 선택 탭 tabIndex0, 나머지 -1. 방향키/Home/End로 탭을 이동하는 핸들러는 없다. hover는 내부 비공개 Tab 함수의 React 상태가 담당.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/filter/TabBar.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "buwang-tab",
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".buwang-tab:focus",
      "css": ".buwang-tab:focus {\n  outline: none;\n}",
      "line": 443
     },
     {
      "selector": ".buwang-tab:focus-visible",
      "css": ".buwang-tab:focus-visible {\n  outline: none;\n  box-shadow: 0 0 0 3px var(--color-primary-hover-bg);\n}",
      "line": 446
     },
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 21,
      "source": "{\n  width: '100%',\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'flex-end',\n  justifyContent: 'space-between',\n  gap: 12,\n  flexWrap: 'wrap',\n}"
     },
     {
      "symbol": "tabListStyle",
      "scope": "module",
      "line": 31,
      "source": "{\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'center',\n  gap: 8,\n  flexWrap: 'wrap',\n  padding: 0,\n  margin: 0,\n  listStyle: 'none',\n}"
     },
     {
      "symbol": "rightSlotStyle",
      "scope": "module",
      "line": 42,
      "source": "{\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'center',\n  gap: 8,\n}"
     },
     {
      "symbol": "tabBaseStyle",
      "scope": "module",
      "line": 49,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  gap: 6,\n  height: 36,\n  paddingInline: 14,\n  paddingBlock: 0,\n  borderRadius: 8,\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  lineHeight: 1,\n  cursor: 'pointer',\n  transition:\n    'background-color 120ms ease, color 120ms ease, border-color 120ms ease, box-shadow 120ms ease',\n  outline: 'none',\n  whiteSpace: 'nowrap',\n  userSelect: 'none',\n}"
     },
     {
      "symbol": "tabInactiveStyle",
      "scope": "module",
      "line": 69,
      "source": "{\n  ...tabBaseStyle,\n  backgroundColor: 'var(--color-surface)',\n  color: 'var(--color-text-muted)',\n  // border 축약형 + hover 의 borderColor 혼용 금지(React 스타일 경고) — 분해 지정.\n  borderWidth: 1.25,\n  borderStyle: 'solid',\n  borderColor: 'var(--color-border)',\n  fontWeight: 600,\n  boxShadow: 'none',\n}"
     },
     {
      "symbol": "tabInactiveHoverStyle",
      "scope": "module",
      "line": 81,
      "source": "{\n  backgroundColor: 'var(--color-primary-hover-bg)', // 연파랑 채움\n  borderColor: 'var(--color-primary)',\n  color: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "tabActiveStyle",
      "scope": "module",
      "line": 87,
      "source": "{\n  ...tabBaseStyle,\n  backgroundColor: 'var(--color-primary)',\n  color: 'var(--color-text-on-primary)',\n  borderWidth: 1.25,\n  borderStyle: 'solid',\n  borderColor: 'var(--color-primary)',\n  fontWeight: 500,\n  boxShadow: 'var(--shadow-card-strong)',\n}"
     },
     {
      "symbol": "iconStyle",
      "scope": "module",
      "line": 98,
      "source": "{\n  fontSize: 20,\n  lineHeight: 1,\n}"
     },
     {
      "symbol": "mergedStyle",
      "scope": "Tab",
      "line": 116,
      "source": "{\n    ...(isActive ? tabActiveStyle : tabInactiveStyle),\n    ...(!isActive && hover ? tabInactiveHoverStyle : null),\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "button",
      "expression": "{mergedStyle}",
      "line": 131
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 137
     },
     {
      "tag": "nav",
      "expression": "{rootStyle}",
      "line": 155
     },
     {
      "tag": "div",
      "expression": "{tabListStyle}",
      "line": 156
     },
     {
      "tag": "div",
      "expression": "{rightSlotStyle}",
      "line": 166
     }
    ],
    "rendered_tags": [
     "button",
     "span",
     "nav",
     "div",
     "Tab"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [
     "--color-primary-hover-bg"
    ],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-tab-bar\"><div class=\"pv-web-tab-bar-list\"><span class=\"pv-web-tab-bar-tab\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z\"/></svg>대시보드</span><span class=\"pv-web-tab-bar-tab pv-web-tab-bar-active\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M4 4h16v16H4zM8 9v7M12 7v9M16 11v5\"/></svg>오늘 작업</span><span class=\"pv-web-tab-bar-tab\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M6 3h12v18H6zM9 7h6M9 11h6M9 15h4\"/></svg>정비 상세 입력</span><span class=\"pv-web-tab-bar-tab\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"m3 6 2 2 4-4M12 6h9m-18 9 2 2 4-4M12 15h9\"/></svg>담당자</span><span class=\"pv-web-tab-bar-tab\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M3 12a9 9 0 1 1 3 7M3 5v7h7M12 7v5l4 2\"/></svg>정비 이력</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-alert-banner",
   "platform": "web",
   "name": "AlertBanner",
   "category": "피드백",
   "purpose": "주요 안내 문구와 선택적 이동 버튼을 표시하는 배너",
   "variants": [
    "default-info-icon",
    "custom-icon",
    "with-action",
    "cta-hover"
   ],
   "props": [
    {
     "name": "icon",
     "type": "string",
     "required": false,
     "default": "'info'"
    },
    {
     "name": "title",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "actionLabel",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "onAction",
     "type": "() => void",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-primary",
    "--color-primary-deep",
    "--color-primary-hover-bg",
    "--color-surface",
    "--color-text-on-primary",
    "--color-text-on-primary-banner",
    "--font-display",
    "--shadow-card-strong"
   ],
   "sizes": "minHeight96px; padding0 16px; radius24px; gap16px; 아이콘 원48px/글리프20px. 제목18px/700/1.333, 본문15px/400/1.428. CTA minWidth144px/height40px/paddingInline16px/radius8px.",
   "file": "apps/admin-web/src/components/design-system/feedback/AlertBanner.tsx",
   "usage_count": 1,
   "notes": "role=status/aria-live=polite. --color-primary-deep 배경. onAction이 있을 때 CTA 렌더, actionLabel 기본 '바로가기'. CTA는 native button에 buwang-button 클래스만 붙인다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/feedback/AlertBanner.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons",
     "buwang-button"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".buwang-button:focus-visible,\n.buwang-icon-toggle:focus-visible",
      "css": ".buwang-button:focus-visible,\n.buwang-icon-toggle:focus-visible {\n  outline: none;\n  box-shadow: 0 0 0 3px var(--color-primary-hover-bg);\n}",
      "line": 426
     },
     {
      "selector": ".buwang-icon-toggle:disabled,\n.buwang-button:disabled",
      "css": ".buwang-icon-toggle:disabled,\n.buwang-button:disabled {\n  cursor: not-allowed;\n  opacity: 0.5;\n}",
      "line": 436
     },
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 15,
      "source": "{\n  width: '100%',\n  minHeight: 96,\n  borderRadius: 24,\n  // primary(#1E88E5) 위 흰 본문은 최대 3.68:1 — deep(#1565C0)이어야 15px 본문이 4.5:1 통과\n  backgroundColor: 'var(--color-primary-deep)',\n  boxShadow: 'var(--shadow-card-strong)',\n  padding: '0 16px', // spec §5.6 [0, 16] — min-height 96 + align center drives vertical room\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'center',\n  justifyContent: 'space-between',\n  gap: 16,\n  flexWrap: 'wrap',\n  boxSizing: 'border-box',\n}"
     },
     {
      "symbol": "leftClusterStyle",
      "scope": "module",
      "line": 32,
      "source": "{\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'center',\n  gap: 16,\n  flex: 1,\n  minWidth: 0,\n}"
     },
     {
      "symbol": "iconCircleStyle",
      "scope": "module",
      "line": 42,
      "source": "{\n  width: 48,\n  height: 48,\n  borderRadius: 9999,\n  backgroundColor: 'var(--color-text-on-primary-banner)',\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  flexShrink: 0,\n  color: 'var(--color-text-on-primary)',\n}"
     },
     {
      "symbol": "iconStyle",
      "scope": "module",
      "line": 54,
      "source": "{\n  fontSize: 20,\n  lineHeight: 1,\n}"
     },
     {
      "symbol": "textGroupStyle",
      "scope": "module",
      "line": 59,
      "source": "{\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 4,\n  minWidth: 0,\n}"
     },
     {
      "symbol": "titleStyle",
      "scope": "module",
      "line": 66,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 18,\n  fontWeight: 700,\n  lineHeight: 1.333,\n  color: 'var(--color-text-on-primary)',\n  margin: 0,\n}"
     },
     {
      "symbol": "descriptionStyle",
      "scope": "module",
      "line": 75,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 400,\n  lineHeight: 1.428,\n  color: 'var(--color-text-on-primary)',\n  margin: 0,\n}"
     },
     {
      "symbol": "ctaBaseStyle",
      "scope": "module",
      "line": 84,
      "source": "{\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  gap: 6,\n  minWidth: 144,\n  height: 40,\n  paddingInline: 16,\n  paddingBlock: 0,\n  borderRadius: 8,\n  border: 'none',\n  backgroundColor: 'var(--color-surface)',\n  color: 'var(--color-primary)',\n  fontFamily: 'var(--font-display)',\n  fontSize: 16,\n  fontWeight: 500,\n  lineHeight: 1,\n  boxShadow: 'var(--shadow-card-strong)',\n  cursor: 'pointer',\n  whiteSpace: 'nowrap',\n  flexShrink: 0,\n  transition:\n    'background-color 120ms ease, box-shadow 120ms ease, transform 120ms ease',\n}"
     },
     {
      "symbol": "ctaHoverStyle",
      "scope": "module",
      "line": 109,
      "source": "{\n  backgroundColor: 'var(--color-primary-hover-bg)',\n}"
     },
     {
      "symbol": "ctaIconStyle",
      "scope": "module",
      "line": 113,
      "source": "{\n  fontSize: 20,\n  lineHeight: 1,\n  color: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "ctaStyle",
      "scope": "AlertBanner",
      "line": 128,
      "source": "{\n    ...ctaBaseStyle,\n    ...(hover ? ctaHoverStyle : null),\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{rootStyle}",
      "line": 134
     },
     {
      "tag": "div",
      "expression": "{leftClusterStyle}",
      "line": 135
     },
     {
      "tag": "span",
      "expression": "{iconCircleStyle}",
      "line": 136
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 137
     },
     {
      "tag": "div",
      "expression": "{textGroupStyle}",
      "line": 141
     },
     {
      "tag": "div",
      "expression": "{titleStyle}",
      "line": 142
     },
     {
      "tag": "div",
      "expression": "{descriptionStyle}",
      "line": 143
     },
     {
      "tag": "button",
      "expression": "{ctaStyle}",
      "line": 153
     },
     {
      "tag": "span",
      "expression": "{ctaIconStyle}",
      "line": 156
     }
    ],
    "rendered_tags": [
     "div",
     "span",
     "button"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [
     "--color-primary-hover-bg"
    ],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-alert-banner\"><div class=\"pv-web-alert-banner-scale\"><div class=\"pv-web-alert-banner-panel\"><div class=\"pv-web-alert-banner-left\"><span class=\"pv-web-alert-banner-icon\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v6m0-10v1\"/></svg></span><div class=\"pv-web-alert-banner-text\"><span class=\"pv-web-alert-banner-title\">새 예약이 도착했어요</span><span class=\"pv-web-alert-banner-description\">입고 시간을 확인해 주세요.</span></div></div></div><div class=\"pv-web-alert-banner-variant\"><span class=\"pv-web-alert-banner-action\">예약 확인<svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M5 12h14m-5-5 5 5-5 5\"/></svg></span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-error-screen",
   "platform": "web",
   "name": "ErrorScreen",
   "category": "피드백",
   "purpose": "전체 화면 오류 상태 — 제목·설명·복구 버튼 한 벌",
   "variants": [
    "default",
    "custom-action-label"
   ],
   "props": [
    {
     "name": "title",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "actionLabel",
     "type": "string",
     "required": false,
     "default": "'새로고침'"
    },
    {
     "name": "onAction",
     "type": "() => void",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "--vh100",
    "--font-display",
    "--color-bg-canvas",
    "--color-text-heading",
    "--color-text-muted",
    "--color-primary"
   ],
   "sizes": "minHeight var(--vh100) · 세로 중앙 정렬 · gap 16px. 제목 20px/600, 설명 15px/muted, 버튼 padding 10px 16px·radius 8px·primary 배경.",
   "file": "apps/admin-web/src/components/ErrorScreen.tsx",
   "usage_count": 2,
   "notes": "루트 ErrorBoundary 와 라우터 RouteErrorPage 가 같은 화면을 그린다 — 문구·토큰이 두 곳으로 갈라지지 않게 표현만 여기 모았다. 값은 CSS 변수(--vh100·--font-display·--color-*)로만 읽는다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-error-screen\"><strong>문제가 발생했습니다</strong><span class=\"pv-web-caption\">잠시 후 다시 시도해 주세요.</span><span class=\"pv-web-button-control pv-web-button-primary pv-web-button-sm\">새로고침</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-parts-commerce-loading",
   "platform": "web",
   "name": "Loading",
   "category": "피드백",
   "purpose": "불러오는 중 / 실패 상태 한 벌 — 실패일 때만 다시 불러오기 버튼",
   "variants": [
    "loading",
    "error"
   ],
   "props": [
    {
     "name": "error",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "reload",
     "type": "() => void",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": ".pc 컨테이너 + Note 한 줄, 실패 시 아래에 default 버튼(leftIcon=refresh).",
   "file": "apps/admin-web/src/features/partsCommerce/ui.tsx",
   "usage_count": 6,
   "notes": "로딩과 실패를 한 컴포넌트로 묶어 화면마다 다른 문구가 생기지 않게 한다. 기본 문구는 '불러오는 중입니다…', 실패는 서버가 준 문장을 그대로 보여 주고 재시도 경로를 함께 준다 — 실패를 빈 화면으로 두면 정비사는 데이터가 없는 줄로 읽는다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-notes\"><p class=\"pv-web-note\">불러오는 중입니다…</p><p class=\"pv-web-note pv-web-note-error\">주문을 불러오지 못했습니다.</p><span class=\"pv-web-button-control pv-web-button-sm\">다시 불러오기</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-parts-commerce-note",
   "platform": "web",
   "name": "Note",
   "category": "피드백",
   "purpose": "부품 커머스의 한 줄 안내·오류문",
   "variants": [
    "status",
    "error"
   ],
   "props": [
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "error",
     "type": "boolean",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [],
   "sizes": ".pc-notice 한 줄(오류는 .pc-error 추가). 치수는 commerce.css.",
   "file": "apps/admin-web/src/features/partsCommerce/ui.tsx",
   "usage_count": 9,
   "notes": "안내는 role=status, 오류는 role=alert 로 읽힌다 — 같은 자리에 같은 모양으로 뜨더라도 보조기술에는 다르게 전달해야 한다. 부품 커머스에서 가장 많이 쓰이는 표시 요소(9곳)다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-notes\"><p class=\"pv-web-note\">장바구니에 담았습니다.</p><p class=\"pv-web-note pv-web-note-error\">청구서를 불러오지 못했습니다.</p></div>",
   "preview_variant": null
  },
  {
   "id": "web-notification-item",
   "platform": "web",
   "name": "NotificationItem",
   "category": "피드백",
   "purpose": "알림 심각도·내용·시간·읽음 여부를 표시하는 목록 행",
   "variants": [
    "info",
    "warning",
    "danger",
    "success",
    "read",
    "unread",
    "interactive",
    "hover",
    "rightExtra"
   ],
   "props": [
    {
     "name": "severity",
     "type": "NotificationSeverity",
     "required": false,
     "default": "'info'"
    },
    {
     "name": "title",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "time",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "unread",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "onClick",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "rightExtra",
     "type": "ReactNode",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-border",
    "--color-primary",
    "--color-primary-hover-bg",
    "--color-text-heading",
    "--color-text-muted",
    "--color-text-placeholder",
    "--font-display"
   ],
   "sizes": "minHeight60px; padding12px 18px; gap12px; 하단 border1px. 읽음점8px; 아이콘원28px/글리프16px. 제목15px/600 또는 unread700/1.4, 본문15px/400/1.4, 시간13px/400/1.5.",
   "file": "apps/admin-web/src/components/design-system/feedback/NotificationItem.tsx",
   "usage_count": 2,
   "notes": "기본 info/read. severityPalette는 공용 badgeTone과 별개다. unread 배경 rgba(30,136,229,0.04); onClick이 있을 때만 role=button/tabIndex=0 및 Enter/Space 동작과 hover 배경을 적용한다. 제목·설명은 한 줄 말줄임.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/feedback/NotificationItem.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "severityPalette",
      "scope": "module",
      "line": 27,
      "source": "{\n  info: { bg: '#eff6ff', fg: 'var(--color-primary)', icon: 'info' }, // spec §5.7\n  warning: { bg: '#fff5e3', fg: '#f59e0a', icon: 'warning' }, // spec §5.7\n  danger: { bg: '#fee2e2', fg: '#ba1a1a', icon: 'report' }, // spec §5.7\n  success: { bg: '#ebfaf2', fg: '#219661', icon: 'check_circle' }, // spec §5.7\n}"
     },
     {
      "symbol": "rootBaseStyle",
      "scope": "module",
      "line": 36,
      "source": "{\n  width: '100%',\n  minHeight: 60,\n  padding: '12px 18px',\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'center',\n  gap: 12,\n  borderBottom: '1px solid var(--color-border)',\n  backgroundColor: 'transparent',\n  boxSizing: 'border-box',\n  position: 'relative',\n  textAlign: 'left',\n  fontFamily: 'var(--font-display)',\n  transition: 'background-color 120ms ease',\n}"
     },
     {
      "symbol": "leadDotStyle",
      "scope": "module",
      "line": 54,
      "source": "(unread: boolean): CSSProperties => ({\n  width: 8,\n  height: 8,\n  borderRadius: 9999,\n  backgroundColor: unread ? 'var(--color-primary)' : 'transparent',\n  flexShrink: 0,\n})"
     },
     {
      "symbol": "rightGroupStyle",
      "scope": "module",
      "line": 63,
      "source": "{\n  display: 'flex',\n  flexDirection: 'column',\n  alignItems: 'flex-end',\n  gap: 4,\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "iconCircleStyle",
      "scope": "module",
      "line": 71,
      "source": "(bg: string): CSSProperties => ({\n  width: 28,\n  height: 28,\n  borderRadius: 9999,\n  backgroundColor: bg,\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  flexShrink: 0,\n})"
     },
     {
      "symbol": "iconStyle",
      "scope": "module",
      "line": 82,
      "source": "(fg: string): CSSProperties => ({\n  fontSize: 16,\n  lineHeight: 1,\n  color: fg,\n})"
     },
     {
      "symbol": "contentStyle",
      "scope": "module",
      "line": 88,
      "source": "{\n  flex: 1,\n  minWidth: 0,\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 2,\n}"
     },
     {
      "symbol": "titleStyle",
      "scope": "module",
      "line": 96,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 600,\n  lineHeight: 1.4,\n  color: 'var(--color-text-heading)',\n  margin: 0,\n  whiteSpace: 'nowrap',\n  overflow: 'hidden',\n  textOverflow: 'ellipsis',\n}"
     },
     {
      "symbol": "descriptionStyle",
      "scope": "module",
      "line": 108,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 400,\n  lineHeight: 1.4,\n  color: 'var(--color-text-muted)',\n  margin: 0,\n  whiteSpace: 'nowrap',\n  overflow: 'hidden',\n  textOverflow: 'ellipsis',\n}"
     },
     {
      "symbol": "timeStyle",
      "scope": "module",
      "line": 121,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 13,\n  fontWeight: 400,\n  lineHeight: 1.5,\n  color: 'var(--color-text-placeholder)',\n  flexShrink: 0,\n  whiteSpace: 'nowrap',\n}"
     },
     {
      "symbol": "palette",
      "scope": "NotificationItem",
      "line": 142,
      "source": "severityPalette[severity]"
     },
     {
      "symbol": "mergedStyle",
      "scope": "NotificationItem",
      "line": 145,
      "source": "{\n    ...rootBaseStyle,\n    // 안 읽음: 아주 옅은 파랑 틴트로 배경 구분(읽은 것과 대비). hover 시 더 진한 틴트.\n    ...(unread ? { backgroundColor: 'rgba(30,136,229,0.04)' } : null),\n    ...(interactive && hover\n      ? { backgroundColor: 'var(--color-primary-hover-bg)' }\n      : null),\n    cursor: interactive ? 'pointer' : 'default',\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{mergedStyle}",
      "line": 172
     },
     {
      "tag": "span",
      "expression": "{leadDotStyle(unread)}",
      "line": 174
     },
     {
      "tag": "span",
      "expression": "{iconCircleStyle(palette.bg)}",
      "line": 175
     },
     {
      "tag": "span",
      "expression": "{iconStyle(palette.fg)}",
      "line": 176
     },
     {
      "tag": "div",
      "expression": "{contentStyle}",
      "line": 180
     },
     {
      "tag": "div",
      "expression": "{{ ...titleStyle, fontWeight: unread ? 700 : 600 }}",
      "line": 181
     },
     {
      "tag": "div",
      "expression": "{descriptionStyle}",
      "line": 182
     },
     {
      "tag": "div",
      "expression": "{rightGroupStyle}",
      "line": 184
     },
     {
      "tag": "div",
      "expression": "{timeStyle}",
      "line": 185
     }
    ],
    "rendered_tags": [
     "div",
     "span"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-notification-item\"><div class=\"pv-web-notification-item-scale\"><div class=\"pv-web-notification-item-row pv-web-notification-item-unread\"><span class=\"pv-web-notification-item-dot\"></span><span class=\"pv-web-notification-item-icon pv-web-notification-item-info\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v6m0-10v1\"/></svg></span><div class=\"pv-web-notification-item-content\"><span class=\"pv-web-notification-item-title\">새 정비 예약</span><span class=\"pv-web-notification-item-description\">PCX125 · 엔진오일 교체</span></div><span class=\"pv-web-notification-item-time\">방금 전</span></div><div class=\"pv-web-notification-item-tones\"><span class=\"pv-web-notification-item-sample\"><span class=\"pv-web-notification-item-icon pv-web-notification-item-warning\">!</span>입고 지연</span><span class=\"pv-web-notification-item-sample\"><span class=\"pv-web-notification-item-icon pv-web-notification-item-danger\">!</span>결제 실패</span><span class=\"pv-web-notification-item-sample\"><span class=\"pv-web-notification-item-icon pv-web-notification-item-success\">✓</span>정비 완료</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-anchoredoverlay",
   "platform": "web",
   "name": "AnchoredOverlay",
   "category": "오버레이",
   "purpose": "작업대를 밀지 않는 포털 메뉴. 화면 경계 보정, 포커스 순환, Esc·바깥 클릭 닫기, 150ms 페이드.",
   "variants": [],
   "props": [],
   "tokens": [
    "colors.surface",
    "radius.md"
   ],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/overlay/AnchoredOverlay.tsx",
   "usage_count": null,
   "notes": "CascadeMenu와 SelectionPopover가 공유. 높이 min(60vh, 480px).",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-assistant-dock",
   "platform": "web",
   "name": "AssistantDock",
   "category": "오버레이",
   "purpose": "부품 도우미 독 — 화면 오른쪽 아래에 떠 있는 부품 찾기·담기 패널",
   "variants": [
    "closed",
    "open",
    "with-context",
    "estimate-context",
    "notice"
   ],
   "props": [],
   "tokens": [],
   "sizes": ".pc-dock 고정(fixed) 배치. 닫힌 상태는 진입 버튼(PurchaseEntry) 한 개, 열면 입력줄 + 후보 목록 + 장바구니 목적지 선택.",
   "file": "apps/admin-web/src/features/partsCommerce/AssistantDock.tsx",
   "usage_count": 4,
   "notes": "견적 목록·견적 작성·견적 상세·부품 재고 4개 화면에 함께 떠 있다 — 부품을 찾으러 화면을 떠나면 적던 견적이 끊기기 때문. 여는 순간에만 /capabilities·/catalog 를 부른다(닫혀 있으면 요청하지 않는다). assistantEvents 커스텀 이벤트로 바깥 화면이 차량·견적 맥락을 실어 열 수 있다. ★고정 배치라 화면 오른쪽 아래를 가린다 — 견적 하단 바를 카드로 바꾼 이유가 이 독을 피해 오른쪽 176px 을 비워 두고 있었기 때문이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-dock\"><div class=\"pv-web-dock-panel\"><div class=\"pv-web-between\"><strong>부품 도우미</strong><span class=\"pv-web-caption\">닫기</span></div><span class=\"pv-web-text-input-control pv-web-placeholder\">어떤 부품을 찾으세요?</span><span class=\"pv-web-caption\">서울 강남 가 1234 · PCX125 기준</span></div><span class=\"pv-web-dock-entry\">부품 찾기</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-cascademenu",
   "platform": "web",
   "name": "CascadeMenu",
   "category": "오버레이",
   "purpose": "글자 경로와 검색 가능한 단계별 캐스케이드 메뉴. hover 미리보기, 키보드 탐색, 1100px 이하 두 열.",
   "variants": [],
   "props": [],
   "tokens": [
    "colors.primarySoft",
    "colors.surface",
    "space.md",
    "radius.md"
   ],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/overlay/CascadeMenu.tsx",
   "usage_count": null,
   "notes": "부품 구매 작업대에서 사용.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-media-lightbox",
   "platform": "web",
   "name": "MediaLightbox",
   "category": "오버레이",
   "purpose": "이미지·동영상을 크게 보고 이전·다음 항목으로 이동하는 뷰어",
   "variants": [
    "image",
    "video",
    "empty",
    "invalid-index",
    "first",
    "middle",
    "last",
    "single-item"
   ],
   "props": [
    {
     "name": "items",
     "type": "MediaItem[]",
     "required": true,
     "default": null
    },
    {
     "name": "index",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "onClose",
     "type": "() => void",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(index: number) => void",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "--color-overlay",
    "--vh85",
    "--vw85"
   ],
   "sizes": "미디어 maxWidth var(--vw85)/maxHeight var(--vh85)/radius12px; 그림자0 8px 32px rgba(0,0,0,0.4). 닫기40×40px/top24px/right24px; 이전·다음44×44px/top50%/좌우24px; Overlay zIndex9999.",
   "file": "apps/admin-web/src/components/design-system/overlay/MediaLightbox.tsx",
   "usage_count": 6,
   "notes": "document.body 포털 + useScrollLock(items.length>0). video controls/autoPlay. items가 비거나 index가 유효하지 않으면 null; 잘못된 index여도 비어 있지 않으면 스크롤 잠금은 유지된다. Escape/방향키/포커스 트랩은 소스에 없다. MediaItem.thumbnail_url/duration은 선언되지만 이 뷰어에서 사용하지 않는다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/overlay/MediaLightbox.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "mediaStyle",
      "scope": "module",
      "line": 20,
      "source": "{\n  maxWidth: 'var(--vw85)',\n  maxHeight: 'var(--vh85)',\n  objectFit: 'contain',\n  borderRadius: 12,\n  boxShadow: '0 8px 32px rgba(0,0,0,0.4)',\n}"
     },
     {
      "symbol": "closeBtnStyle",
      "scope": "module",
      "line": 28,
      "source": "{\n  position: 'absolute',\n  top: 24,\n  right: 24,\n  width: 40,\n  height: 40,\n  borderRadius: '50%',\n  backgroundColor: 'rgba(0,0,0,0.5)',\n  color: '#fff',\n  border: 'none',\n  cursor: 'pointer',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  fontSize: 24,\n}"
     },
     {
      "symbol": "navBtnStyle",
      "scope": "module",
      "line": 45,
      "source": "{\n  position: 'absolute',\n  top: '50%',\n  transform: 'translateY(-50%)',\n  width: 44,\n  height: 44,\n  borderRadius: '50%',\n  backgroundColor: 'rgba(0,0,0,0.5)',\n  color: '#fff',\n  border: 'none',\n  cursor: 'pointer',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  fontSize: 28,\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "video",
      "expression": "{mediaStyle}",
      "line": 77
     },
     {
      "tag": "img",
      "expression": "{mediaStyle}",
      "line": 81
     },
     {
      "tag": "button",
      "expression": "{closeBtnStyle}",
      "line": 84
     },
     {
      "tag": "button",
      "expression": "{{ ...navBtnStyle, left: 24 }}",
      "line": 91
     },
     {
      "tag": "button",
      "expression": "{{ ...navBtnStyle, right: 24 }}",
      "line": 101
     }
    ],
    "rendered_tags": [
     "Overlay",
     "video",
     "img",
     "button",
     "span"
    ],
    "composed_components": [
     {
      "file": "apps/admin-web/src/components/design-system/overlay/Overlay.tsx",
      "symbol": "Overlay"
     }
    ],
    "portal_target": "document.body",
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": [
     "--color-overlay"
    ]
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-media-lightbox\"><div class=\"pv-web-lightbox-scale\"><div class=\"pv-web-lightbox-stage\"><div class=\"pv-web-lightbox-media\"><svg viewBox=\"0 0 260 150\" role=\"img\" aria-label=\"브레이크 점검 영상 예시\"><rect width=\"260\" height=\"150\" fill=\"var(--c-bg)\"/><circle cx=\"130\" cy=\"68\" r=\"47\" fill=\"var(--c-text-2)\"/><circle cx=\"130\" cy=\"68\" r=\"31\" fill=\"var(--c-border)\"/><circle cx=\"130\" cy=\"68\" r=\"20\" fill=\"var(--c-text-3)\"/><circle cx=\"130\" cy=\"68\" r=\"7\" fill=\"var(--c-surface)\"/><rect x=\"151\" y=\"42\" width=\"28\" height=\"42\" rx=\"6\" fill=\"var(--c-primary)\"/><text x=\"130\" y=\"138\" text-anchor=\"middle\" fill=\"var(--c-text-2)\" font-size=\"13\">브레이크 점검 · 1가2 3456</text></svg><div class=\"pv-web-lightbox-video-controls\"><span>Ⅱ</span><span class=\"pv-web-lightbox-video-track\"></span><span>0:12 / 0:30</span></div></div><span class=\"pv-web-lightbox-close\" aria-label=\"닫기\">×</span><span class=\"pv-web-lightbox-prev\" aria-label=\"이전 미디어\">‹</span><span class=\"pv-web-lightbox-next\" aria-label=\"다음 미디어\">›</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-modal",
   "platform": "web",
   "name": "Modal",
   "category": "오버레이",
   "purpose": "제목·본문·확인/취소 버튼을 제공하는 공통 대화상자",
   "variants": [
    "open",
    "closed",
    "default-footer",
    "custom-footer",
    "footer-null",
    "confirmLoading",
    "closeOnBackdropClick",
    "closeOnEsc"
   ],
   "props": [
    {
     "name": "open",
     "type": "boolean",
     "required": true,
     "default": null
    },
    {
     "name": "onClose",
     "type": "() => void",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "footer",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "width",
     "type": "number",
     "required": false,
     "default": "DEFAULT_WIDTH"
    },
    {
     "name": "onConfirm",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "confirmLoading",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "closeOnBackdropClick",
     "type": "boolean",
     "required": false,
     "default": "true"
    },
    {
     "name": "closeOnEsc",
     "type": "boolean",
     "required": false,
     "default": "true"
    }
   ],
   "tokens": [
    "--color-overlay",
    "--color-primary-hover-bg",
    "--color-surface",
    "--color-text-heading",
    "--color-text-muted",
    "--font-display",
    "--modal-max-w",
    "--vh90",
    "buttonSize.lg.fontSize",
    "buttonSize.lg.height",
    "buttonSize.lg.iconSize",
    "buttonSize.lg.paddingX",
    "buttonSize.md.fontSize",
    "buttonSize.md.height",
    "buttonSize.md.iconSize",
    "buttonSize.md.paddingX",
    "buttonSize.sm.fontSize",
    "buttonSize.sm.height",
    "buttonSize.sm.iconSize",
    "buttonSize.sm.paddingX",
    "buttonSize.xl.fontSize",
    "buttonSize.xl.height",
    "buttonSize.xl.iconSize",
    "buttonSize.xl.paddingX",
    "colors.border",
    "colors.danger",
    "colors.primary",
    "colors.primaryGradient",
    "colors.primaryHoverBg",
    "colors.surface",
    "colors.textBodyStrong",
    "colors.textOnPrimary",
    "radius.md",
    "shadow.cardStrong",
    "space.sm"
   ],
   "sizes": "기본width480px; maxWidth var(--modal-max-w); maxHeight var(--vh90); radius16px; header padding16px 24px 0 24px; body/footer16px 24px; footer gap10px. 닫기32×32px/top16px/right16px/아이콘24px; Overlay zIndex1000.",
   "file": "apps/admin-web/src/components/design-system/overlay/Modal.tsx",
   "usage_count": 20,
   "notes": "native dialog 역할의 div를 Overlay와 조합해 document.body에 포털한다. useScrollLock, 초기 필드 포커스, Tab 트랩, 닫힐 때 포커스 복원. footer=undefined일 때만 기본 버튼 생성; footer=null이면 비어 있는 footer 래퍼는 남는다. destroyOnClose는 타입에만 있고 구현에서 읽지 않으며 closed는 항상 null. 기본 확인/취소 라벨 '확인'/'취소'; confirmLoading은 확인 버튼만 비활성화.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/overlay/Modal.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "dialogStyle",
      "scope": "module",
      "line": 32,
      "source": "(width: number): CSSProperties => ({\n  width,\n  maxWidth: 'var(--modal-max-w)',\n  maxHeight: 'var(--vh90)',\n  backgroundColor: 'var(--color-surface)',\n  borderRadius: 16,\n  boxShadow:\n    '0 20px 60px rgba(0,0,0,0.24), 0 2px 8px rgba(0,0,0,0.12)',\n  display: 'flex',\n  flexDirection: 'column',\n  position: 'relative',\n  outline: 'none',\n})"
     },
     {
      "symbol": "headerStyle",
      "scope": "module",
      "line": 46,
      "source": "{\n  padding: '16px 24px 0 24px',\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 6,\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "titleStyle",
      "scope": "module",
      "line": 54,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 18,\n  fontWeight: 600,\n  lineHeight: 1.556,\n  color: 'var(--color-text-heading)',\n  margin: 0,\n  paddingRight: 32,\n}"
     },
     {
      "symbol": "descriptionStyle",
      "scope": "module",
      "line": 64,
      "source": "{\n  fontFamily: 'var(--font-display)',\n  fontSize: 15,\n  fontWeight: 400,\n  lineHeight: 1.428,\n  color: 'var(--color-text-muted)',\n  margin: 0,\n}"
     },
     {
      "symbol": "bodyStyle",
      "scope": "module",
      "line": 73,
      "source": "{\n  padding: '16px 24px',\n  flex: 1,\n  minHeight: 0,\n  overflowY: 'auto',\n}"
     },
     {
      "symbol": "footerStyle",
      "scope": "module",
      "line": 80,
      "source": "{\n  padding: '16px 24px',\n  display: 'flex',\n  flexDirection: 'row',\n  justifyContent: 'flex-end',\n  alignItems: 'center',\n  gap: 10,\n  flexShrink: 0,\n}"
     },
     {
      "symbol": "closeButtonStyle",
      "scope": "module",
      "line": 90,
      "source": "{\n  position: 'absolute',\n  top: 16,\n  right: 16,\n  width: 32,\n  height: 32,\n  borderRadius: 8,\n  border: 'none',\n  backgroundColor: 'transparent',\n  color: 'var(--color-text-muted)',\n  cursor: 'pointer',\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  padding: 0,\n  transition: 'background-color 120ms ease',\n}"
     },
     {
      "symbol": "closeIconStyle",
      "scope": "module",
      "line": 108,
      "source": "{\n  fontSize: 24,\n  lineHeight: 1,\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{dialogStyle(width)}",
      "line": 240
     },
     {
      "tag": "button",
      "expression": "{closeButtonStyle}",
      "line": 246
     },
     {
      "tag": "span",
      "expression": "{closeIconStyle}",
      "line": 248
     },
     {
      "tag": "header",
      "expression": "{headerStyle}",
      "line": 252
     },
     {
      "tag": "h2",
      "expression": "{titleStyle}",
      "line": 253
     },
     {
      "tag": "p",
      "expression": "{descriptionStyle}",
      "line": 256
     },
     {
      "tag": "div",
      "expression": "{bodyStyle}",
      "line": 258
     },
     {
      "tag": "div",
      "expression": "{footerStyle}",
      "line": 259
     }
    ],
    "rendered_tags": [
     "Button",
     "Overlay",
     "div",
     "button",
     "span",
     "header",
     "h2",
     "p"
    ],
    "composed_components": [
     {
      "file": "apps/admin-web/src/components/design-system/button/Button.tsx",
      "symbol": "Button"
     },
     {
      "file": "apps/admin-web/src/components/design-system/overlay/Overlay.tsx",
      "symbol": "Overlay"
     }
    ],
    "portal_target": "document.body",
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [
     "material-icons",
     "buwang-button"
    ],
    "global_token_names": [],
    "composed_token_names": [
     "--color-overlay",
     "--color-primary-hover-bg",
     "--font-display",
     "buttonSize.lg.fontSize",
     "buttonSize.lg.height",
     "buttonSize.lg.iconSize",
     "buttonSize.lg.paddingX",
     "buttonSize.md.fontSize",
     "buttonSize.md.height",
     "buttonSize.md.iconSize",
     "buttonSize.md.paddingX",
     "buttonSize.sm.fontSize",
     "buttonSize.sm.height",
     "buttonSize.sm.iconSize",
     "buttonSize.sm.paddingX",
     "buttonSize.xl.fontSize",
     "buttonSize.xl.height",
     "buttonSize.xl.iconSize",
     "buttonSize.xl.paddingX",
     "colors.border",
     "colors.danger",
     "colors.primary",
     "colors.primaryGradient",
     "colors.primaryHoverBg",
     "colors.surface",
     "colors.textBodyStrong",
     "colors.textOnPrimary",
     "radius.md",
     "shadow.cardStrong",
     "space.sm"
    ]
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-modal\"><div class=\"pv-web-modal-scale\"><div class=\"pv-web-modal-backdrop\"><div class=\"pv-web-modal-dialog\"><span class=\"pv-web-modal-close\" aria-label=\"닫기\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"m6 6 12 12M6 18 18 6\"/></svg></span><div class=\"pv-web-modal-header\">정비를 완료할까요?</div><div class=\"pv-web-modal-body\">PCX125 · 1가2 3456<br/>엔진오일 교체 내역을 저장합니다.</div><div class=\"pv-web-modal-footer\"><span class=\"pv-web-button-control\">취소</span><span class=\"pv-web-button-control pv-web-button-primary\">확인</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-overlay",
   "platform": "web",
   "name": "Overlay",
   "category": "오버레이",
   "purpose": "화면을 덮는 배경과 중앙 정렬 콘텐츠를 제공하는 오버레이",
   "variants": [
    "open",
    "closed",
    "custom-zIndex",
    "with-children"
   ],
   "props": [
    {
     "name": "open",
     "type": "boolean",
     "required": true,
     "default": null
    },
    {
     "name": "onClick",
     "type": "() => void",
     "required": false,
     "default": null
    },
    {
     "name": "zIndex",
     "type": "number",
     "required": false,
     "default": "100"
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-overlay"
   ],
   "sizes": "position fixed/inset0; flex center; 기본zIndex100; opacity transition200ms.",
   "file": "apps/admin-web/src/components/design-system/overlay/Overlay.tsx",
   "usage_count": 3,
   "notes": "closed여도 DOM은 유지하며 opacity0/pointerEvents none/visibility hidden/aria-hidden=true로 숨긴다. event.target===currentTarget인 배경 클릭만 콜백을 실행한다. 포털·스크롤 잠금은 직접 제공하지 않는다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/overlay/Overlay.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "baseStyle",
      "scope": "module",
      "line": 14,
      "source": "{\n  position: 'fixed',\n  inset: 0,\n  backgroundColor: 'var(--color-overlay)',\n  transition: 'opacity 200ms ease',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{{\n        ...baseStyle,\n        zIndex,\n        opacity: open ? 1 : 0,\n        pointerEvents: open ? 'auto' : 'none',\n        visibility: open ? 'visible' : 'hidden',\n      }}",
      "line": 42
     }
    ],
    "rendered_tags": [
     "div"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-overlay\"><div class=\"pv-web-overlay-page\"><span class=\"pv-web-overlay-page-title\">정비 접수 목록</span><span class=\"pv-web-overlay-page-line\">PCX125 · 1가2 3456</span><span class=\"pv-web-overlay-page-line\">엔진오일 교체</span><div class=\"pv-web-overlay-scrim\"><span class=\"pv-web-overlay-content\">정비 내역 확인</span></div></div><div class=\"pv-web-caption\">배경을 덮고 내용을 중앙에 표시</div></div>",
   "preview_variant": null
  },
  {
   "id": "web-photo-lightbox",
   "platform": "web",
   "name": "PhotoLightbox",
   "category": "오버레이",
   "purpose": "단일 사진을 화면 중앙에서 크게 보는 이미지 뷰어",
   "variants": [
    "open-src",
    "closed-null-or-empty-src"
   ],
   "props": [
    {
     "name": "src",
     "type": "string | null",
     "required": true,
     "default": null
    },
    {
     "name": "alt",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "onClose",
     "type": "() => void",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "--color-overlay",
    "--vh85",
    "--vw85"
   ],
   "sizes": "img maxWidth var(--vw85)/maxHeight var(--vh85)/radius12px; 그림자0 8px 32px rgba(0,0,0,0.4); 닫기40×40px/top24px/right24px/font24px; Overlay zIndex9999.",
   "file": "apps/admin-web/src/components/design-system/overlay/PhotoLightbox.tsx",
   "usage_count": 1,
   "notes": "src가 falsy이면 null. document.body 포털 및 useScrollLock(Boolean(src)). 배경 또는 × 버튼으로 닫기. Escape/포커스 트랩은 소스에 없다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/overlay/PhotoLightbox.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "imgStyle",
      "scope": "module",
      "line": 12,
      "source": "{\n  maxWidth: 'var(--vw85)',\n  maxHeight: 'var(--vh85)',\n  objectFit: 'contain',\n  borderRadius: 12,\n  boxShadow: '0 8px 32px rgba(0,0,0,0.4)',\n}"
     },
     {
      "symbol": "closeBtnStyle",
      "scope": "module",
      "line": 20,
      "source": "{\n  position: 'absolute',\n  top: 24,\n  right: 24,\n  width: 40,\n  height: 40,\n  borderRadius: '50%',\n  backgroundColor: 'rgba(0,0,0,0.5)',\n  color: '#fff',\n  border: 'none',\n  cursor: 'pointer',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  fontSize: 24,\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "img",
      "expression": "{imgStyle}",
      "line": 44
     },
     {
      "tag": "button",
      "expression": "{closeBtnStyle}",
      "line": 45
     }
    ],
    "rendered_tags": [
     "Overlay",
     "img",
     "button"
    ],
    "composed_components": [
     {
      "file": "apps/admin-web/src/components/design-system/overlay/Overlay.tsx",
      "symbol": "Overlay"
     }
    ],
    "portal_target": "document.body",
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": [
     "--color-overlay"
    ]
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-photo-lightbox\"><div class=\"pv-web-lightbox-scale\"><div class=\"pv-web-lightbox-stage\"><div class=\"pv-web-lightbox-media\"><svg viewBox=\"0 0 260 160\" role=\"img\" aria-label=\"엔진오일 교체 작업 기록 예시\"><rect width=\"260\" height=\"160\" fill=\"var(--c-bg)\"/><path d=\"M0 126h260\" stroke=\"var(--c-border)\"/><path d=\"M111 28h25v15l12 15v61H96V58l15-15Z\" fill=\"var(--c-primary)\"/><rect x=\"108\" y=\"24\" width=\"31\" height=\"12\" rx=\"3\" fill=\"var(--c-text-2)\"/><rect x=\"102\" y=\"68\" width=\"40\" height=\"32\" rx=\"3\" fill=\"var(--c-surface)\"/><text x=\"122\" y=\"88\" text-anchor=\"middle\" font-size=\"11\" fill=\"var(--c-primary-deep)\">4T OIL</text><path d=\"m162 105 20-36 7 4-20 36m13-40 2-13 12-3-5 10 5 6-11 4\" fill=\"var(--c-text-3)\"/><text x=\"130\" y=\"146\" text-anchor=\"middle\" fill=\"var(--c-text-2)\" font-size=\"13\">엔진오일 교체 · 1가2 3456</text></svg></div><span class=\"pv-web-lightbox-close\" aria-label=\"닫기\">×</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-chart-container",
   "platform": "web",
   "name": "ChartContainer",
   "category": "레이아웃",
   "purpose": "대시보드 차트 섹션을 세로로 쌓는 컨테이너",
   "variants": [
    "default",
    "custom-className"
   ],
   "props": [
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "className",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "width100%; flex column; gap16px; background transparent; 자체 padding 없음.",
   "file": "apps/admin-web/src/components/design-system/chart/ChartContainer.tsx",
   "usage_count": 7,
   "notes": "ChartContainer.Row 별칭과 named export ChartRow를 제공한다. ChartRow는 별도 항목으로 기록했다. 주석의 24px padding·패널 배경 설명과 실제 스타일이 다르다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/chart/ChartContainer.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [
     "{className}"
    ],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 25,
      "source": "{\n  display: 'flex',\n  flexDirection: 'column',\n  gap: 16,\n  width: '100%',\n  backgroundColor: 'transparent',\n  boxSizing: 'border-box',\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{rootStyle}",
      "line": 66
     }
    ],
    "rendered_tags": [
     "div"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-chart-container\"><div class=\"pv-web-chart-container-column\"><div class=\"pv-web-diagram-box pv-web-chart-container-summary\"><span>오늘 정비</span><strong>24건</strong><span>총 매출</span><strong>₩840,000</strong></div><div class=\"pv-web-diagram-box pv-web-chart-container-chart\"><span>주간 정비 추이</span><svg viewBox=\"0 0 100 26\" fill=\"none\" aria-hidden=\"true\"><path d=\"m2 24 17-6 18 3 18-13 18 4L98 2\" stroke=\"var(--c-primary)\" stroke-width=\"2\"/></svg></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-chart-container-row",
   "platform": "web",
   "name": "ChartRow",
   "category": "레이아웃",
   "purpose": "최소 아이템 너비를 기준으로 자동 줄바꿈하는 차트 그리드 행",
   "variants": [
    "auto-fit",
    "custom-minItemWidth",
    "custom-gap"
   ],
   "props": [
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "className",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "guideAnchor",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "minItemWidth",
     "type": "number",
     "required": false,
     "default": "220"
    },
    {
     "name": "gap",
     "type": "number",
     "required": false,
     "default": "16"
    }
   ],
   "tokens": [],
   "sizes": "기본 minItemWidth220px/gap16px; gridTemplateColumns repeat(auto-fit, minmax(${minItemWidth}px, 1fr)); width100%.",
   "file": "apps/admin-web/src/components/design-system/chart/ChartContainer.tsx",
   "usage_count": 7,
   "notes": "chart/ChartContainer.tsx의 named export이며 ChartContainer.Row와 동일 함수다. 파일 기반 id에 -row를 붙여 중복을 피했다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/chart/ChartContainer.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [
     "{className}"
    ],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "buildRowStyle",
      "scope": "module",
      "line": 34,
      "source": "function buildRowStyle(minItemWidth: number, gap: number): CSSProperties {\n  return {\n    display: 'grid',\n    gridTemplateColumns: `repeat(auto-fit, minmax(${minItemWidth}px, 1fr))`,\n    gap,\n    width: '100%',\n  };\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{buildRowStyle(minItemWidth, gap)}",
      "line": 54
     }
    ],
    "rendered_tags": [
     "div"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-chart-container-row\"><div class=\"pv-web-chart-container-row-scale\"><div class=\"pv-web-chart-container-row-grid\"><div class=\"pv-web-diagram-box pv-web-chart-container-row-panel\"><span>주간 정비</span><svg viewBox=\"0 0 180 46\" aria-hidden=\"true\"><g fill=\"var(--c-primary)\"><rect x=\"6\" y=\"26\" width=\"20\" height=\"20\" rx=\"3\"/><rect x=\"42\" y=\"14\" width=\"20\" height=\"32\" rx=\"3\"/><rect x=\"78\" y=\"20\" width=\"20\" height=\"26\" rx=\"3\"/><rect x=\"114\" y=\"5\" width=\"20\" height=\"41\" rx=\"3\"/><rect x=\"150\" y=\"0\" width=\"20\" height=\"46\" rx=\"3\"/></g></svg></div><div class=\"pv-web-diagram-box pv-web-chart-container-row-panel\"><span>주간 매출</span><svg viewBox=\"0 0 180 46\" fill=\"none\" aria-hidden=\"true\"><path d=\"m4 41 34-15 35 7 35-16 34 4 34-17\" stroke=\"var(--c-primary)\" stroke-width=\"3\"/></svg></div></div></div><div class=\"pv-web-caption\">가용 폭에 맞춰 차트를 나란히 배치</div></div>",
   "preview_variant": null
  },
  {
   "id": "web-three-d-estimate-form-panel",
   "platform": "web",
   "name": "EstimateFormPanel",
   "category": "레이아웃",
   "purpose": "3D 견적의 실제 견적서 패널 — 차량 선택 + 담긴 항목 인라인 편집 + 합계 + 저장",
   "variants": [
    "side",
    "bottom",
    "saving",
    "vehicle-search"
   ],
   "props": [
    {
     "name": "formData",
     "type": "WizardFormData",
     "required": true,
     "default": null
    },
    {
     "name": "onChange",
     "type": "(partial: Partial<WizardFormData>) => void",
     "required": true,
     "default": null
    },
    {
     "name": "onSave",
     "type": "(mode: 'draft' | 'pdf' | 'send') => void",
     "required": true,
     "default": null
    },
    {
     "name": "saving",
     "type": "boolean",
     "required": true,
     "default": null
    },
    {
     "name": "layout",
     "type": "'side' | 'bottom'",
     "required": false,
     "default": "'side'"
    }
   ],
   "tokens": [
    "colors.surface",
    "colors.border",
    "radius.lg",
    "space",
    "fontSize",
    "fontWeight"
   ],
   "sizes": "패널 border 1px·radius.lg=16px·surface. layout='bottom'(N7) 항목 한 줄 열 폭: 28(번호) + 항목명(신축) + 76(작업) + 130(부품비) + 112(공임) + 80(수량) + 96(소계) + 28(삭제) = 고정 550 + gap 8×7 = 606, 패딩 24·테두리 2 를 더해 632 가 항목명 밖의 예산이다.",
   "file": "apps/admin-web/src/features/estimates/three3d/EstimateFormPanel.tsx",
   "usage_count": 1,
   "notes": "layout='side'(기본)는 화면 오른쪽 세로 패널, 'bottom'(2026-09) 은 3D 화면 아래 전폭 — 헤더 한 줄, 항목은 열이 맞는 가로 한 줄, 높이는 자연스럽게. 폭 예산은 실측으로 잡았다: 1440 뷰포트(콘텐츠 1168)에서 항목명 536 / 1280 에서 376 / 1024 드로어에서 368 / 큰 글씨 zoom 1.15@1440 에서 348, zoom 1.3@1440(836)은 컨테이너 쿼리로 2줄 배치가 된다. ★미디어 쿼리가 아니라 컨테이너 쿼리를 쓴다 — 큰 글씨 모드는 :root zoom 이라 미디어 쿼리에 반영되지 않는다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-layout-stack\"><div class=\"pv-web-layout pv-web-layout-side\"><span class=\"pv-web-layout-cell pv-web-layout-viewer\">3D</span><span class=\"pv-web-layout-cell\">견적서<br>side</span></div><div class=\"pv-web-layout pv-web-layout-side\"><span class=\"pv-web-layout-cell pv-web-layout-viewer\">3D</span><span class=\"pv-web-layout-cell\">부품</span><span class=\"pv-web-layout-cell pv-web-layout-wide\">견적서 · bottom(N7)</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-three-d-oem-parts-panel",
   "platform": "web",
   "name": "OemPartsPanel",
   "category": "레이아웃",
   "purpose": "3D 견적 부품 패널(DB OEM) — 고른 부위의 순정 부품·파츠넘버를 눌러 견적에 담는다",
   "variants": [
    "default",
    "side-all",
    "side-left",
    "side-right",
    "loading",
    "error",
    "empty",
    "truncated",
    "show-hardware"
   ],
   "props": [
    {
     "name": "modelId",
     "type": "number",
     "required": true,
     "default": null
    },
    {
     "name": "group",
     "type": "string | null",
     "required": true,
     "default": null
    },
    {
     "name": "groupLabel",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "modelYear",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "sideHint",
     "type": "SideFilter | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "selectionKey",
     "type": "string | null",
     "required": false,
     "default": "null"
    },
    {
     "name": "focus",
     "type": "{ label: string; keys: string[] } | null",
     "required": false,
     "default": null
    },
    {
     "name": "addedCodes",
     "type": "ReadonlySet<string>",
     "required": true,
     "default": null
    },
    {
     "name": "onAdd",
     "type": "(part: OemPart) => void",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "colors.surface",
    "colors.border",
    "radius.lg",
    "space.2xl",
    "fontFamily",
    "fontSize",
    "fontWeight"
   ],
   "sizes": "패널 height 100% · overflowY auto · padding space['2xl']=16px · border 1px colors.border · radius.lg=16px · background colors.surface.",
   "file": "apps/admin-web/src/features/estimates/three3d/OemPartsPanel.tsx",
   "usage_count": 1,
   "notes": "좌/우 칩(전체·좌·우)은 클릭한 메쉬의 좌우를 초기값으로 받되 정비사가 바꿀 수 있고, 부위가 바뀌면 초기값으로 되돌아간다. 확대 뷰에서 고른 세부 부품의 키워드와 맞는 행을 위로 올린다. 이미 담은 파츠넘버(addedCodes)는 담기 버튼이 죽는다. 표시명은 utils/partDisplayName 한 곳에서만 만든다 — 색상 접미·좌우 표기는 부품별로 붙이고 전역 역채움을 하지 않는다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-panel\"><div class=\"pv-web-between\"><strong>앞 카울</strong><span class=\"pv-web-caption\">42종</span></div><div class=\"pv-web-row\"><span class=\"pv-web-chip pv-web-chip-active\">전체</span><span class=\"pv-web-chip\">좌</span><span class=\"pv-web-chip\">우</span></div><div class=\"pv-web-part-row\"><span>프런트 커버 · 좌</span><code>64301-K97-T00</code></div><div class=\"pv-web-part-row\"><span>프런트 커버 · 우</span><code>64302-K97-T00</code></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-page-container",
   "platform": "web",
   "name": "PageContainer",
   "category": "레이아웃",
   "purpose": "페이지 섹션을 공통 세로 간격으로 배치하는 루트 래퍼",
   "variants": [
    "default",
    "custom-gap",
    "custom-className"
   ],
   "props": [
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "gap",
     "type": "number",
     "required": false,
     "default": "16"
    },
    {
     "name": "className",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "data-guide-anchor",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "width100%; flex column; gap 기본16px; 자체 padding 없음.",
   "file": "apps/admin-web/src/components/design-system/layout/PageContainer.tsx",
   "usage_count": 23,
   "notes": "외곽 여백은 global.css의 .buwang-content-inner와 --layout-content-pad가 소유한다. data-guide-anchor를 루트에 전달한다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/layout/PageContainer.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [
     "{className}"
    ],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "style",
      "scope": "PageContainer",
      "line": 23,
      "source": "{\n    display: 'flex',\n    flexDirection: 'column',\n    gap,\n    width: '100%',\n    boxSizing: 'border-box',\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{style}",
      "line": 31
     }
    ],
    "rendered_tags": [
     "div"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-page-container\"><div class=\"pv-web-page-container-column\"><div class=\"pv-web-diagram-box pv-web-page-container-header\"><strong>정비 관리</strong><span class=\"pv-web-link\">+ 정비 접수</span></div><div class=\"pv-web-diagram-box pv-web-page-container-content\"><span>PCX125 · 1가2 3456</span><span class=\"pv-web-caption\">엔진오일 교체</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-page-header",
   "platform": "web",
   "name": "PageHeader",
   "category": "레이아웃",
   "purpose": "페이지 내부 제목·설명·아이콘과 우측 액션을 배치하는 헤더",
   "variants": [
    "title-only",
    "with-icon",
    "with-description",
    "with-actions"
   ],
   "props": [
    {
     "name": "title",
     "type": "ReactNode",
     "required": true,
     "default": null
    },
    {
     "name": "icon",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "description",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "actions",
     "type": "ReactNode",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-primary",
    "--color-text-heading",
    "--color-text-muted"
   ],
   "sizes": "width100%; gap12px/flex-wrap; 좌측gap10px; 액션gap8px; 아이콘22px; 제목18px/600/1.5; 설명13px/400/1.428.",
   "file": "apps/admin-web/src/components/design-system/layout/PageHeader.tsx",
   "usage_count": 0,
   "notes": "앱 셸 TopBar와 별개인 선택적 페이지 내부 헤더. title은 ReactNode이며 실제 태그 h2. description/actions는 null 여부로 표시한다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/layout/PageHeader.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [
     "material-icons"
    ],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [
     {
      "selector": ".material-icons",
      "css": ".material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 20px;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  font-feature-settings: 'liga';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  vertical-align: middle;\n  /* 폰트 로딩 실패 시 텍스트로 노출되지 않도록 fallback 없는 명시 */\n}",
      "line": 471
     },
     {
      "selector": ".material-icons:not(:lang(ko))",
      "css": ".material-icons:not(:lang(ko)) {\n  font-family: 'Material Icons', 'Material Icons Outlined';\n}",
      "line": 510
     }
    ],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "module",
      "line": 14,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'space-between',\n  gap: 12,\n  flexWrap: 'wrap',\n  width: '100%',\n}"
     },
     {
      "symbol": "leftGroupStyle",
      "scope": "module",
      "line": 23,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  gap: 10,\n  minWidth: 0,\n}"
     },
     {
      "symbol": "iconStyle",
      "scope": "module",
      "line": 30,
      "source": "{\n  fontSize: 22,\n  lineHeight: 1,\n  color: 'var(--color-primary)',\n}"
     },
     {
      "symbol": "titleStyle",
      "scope": "module",
      "line": 36,
      "source": "{\n  fontSize: 18, // heading3 유지\n  fontWeight: 600,\n  lineHeight: 1.5,\n  color: 'var(--color-text-heading)',\n  margin: 0,\n}"
     },
     {
      "symbol": "descriptionStyle",
      "scope": "module",
      "line": 44,
      "source": "{\n  fontSize: 13,\n  fontWeight: 400,\n  lineHeight: 1.428,\n  color: 'var(--color-text-muted)',\n  margin: 0,\n}"
     },
     {
      "symbol": "actionsStyle",
      "scope": "module",
      "line": 52,
      "source": "{\n  display: 'flex',\n  alignItems: 'center',\n  gap: 8,\n  flexShrink: 0,\n}"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "div",
      "expression": "{rootStyle}",
      "line": 66
     },
     {
      "tag": "div",
      "expression": "{leftGroupStyle}",
      "line": 67
     },
     {
      "tag": "span",
      "expression": "{iconStyle}",
      "line": 69
     },
     {
      "tag": "div",
      "expression": "{{ minWidth: 0 }}",
      "line": 73
     },
     {
      "tag": "h2",
      "expression": "{titleStyle}",
      "line": 74
     },
     {
      "tag": "p",
      "expression": "{descriptionStyle}",
      "line": 75
     },
     {
      "tag": "div",
      "expression": "{actionsStyle}",
      "line": 78
     }
    ],
    "rendered_tags": [
     "div",
     "span",
     "h2",
     "p"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-page-header\"><div class=\"pv-web-page-header-root\"><div class=\"pv-web-page-header-left\"><svg class=\"pv-web-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><rect x=\"4\" y=\"5\" width=\"16\" height=\"16\" rx=\"2\"/><path d=\"M8 3v4m8-4v4M4 11h16m-10 5 2 2 4-4\"/></svg><div><div class=\"pv-web-page-header-title\">예약 관리</div><div class=\"pv-web-page-header-description\">오늘의 정비 예약을 확인하세요.</div></div></div><div class=\"pv-web-page-header-actions\"><span class=\"pv-web-button-control pv-web-button-primary pv-web-button-sm\">+ 예약 등록</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-three-d-part-list-panel",
   "platform": "web",
   "name": "PartListPanel",
   "category": "레이아웃",
   "purpose": "3D 사고 견적의 부품 패널(정적 카탈로그) — 부위 → 부품 → 상세부품(번호·코드·가격)",
   "variants": [
    "default",
    "focused",
    "no-catalog",
    "raw-add",
    "just-added"
   ],
   "props": [
    {
     "name": "selectedMesh",
     "type": "string | null",
     "required": true,
     "default": null
    },
    {
     "name": "meshInfo",
     "type": "Record<string, MeshInfo>",
     "required": true,
     "default": null
    },
    {
     "name": "onAddPart",
     "type": "(detail: CatalogDetailPart) => void",
     "required": true,
     "default": null
    },
    {
     "name": "onAddRaw",
     "type": "(label: string) => void",
     "required": true,
     "default": null
    },
    {
     "name": "focus",
     "type": "{ label: string; keys: string[] } | null",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "colors.surface",
    "colors.border",
    "radius.lg",
    "space.xl",
    "fontSize",
    "fontWeight"
   ],
   "sizes": "패널 height 100% · overflowY auto · padding space.xl=12px · border 1px · radius.lg=16px. (본문 2분할이 50/50 이 되며 좌측이 좁아진 만큼 여백을 16→12 로 줄였다)",
   "file": "apps/admin-web/src/features/estimates/three3d/PartListPanel.tsx",
   "usage_count": 1,
   "notes": "카탈로그를 갖지 못한 차종에서는 고른 부위 이름을 그대로 견적에 담는 경로(onAddRaw)를 남긴다 — 못 담는 것보다 이름만이라도 담는 편이 낫다. 부위·세부 부품이 바뀌면 첫(매칭) 부품을 자동으로 펼친다. effect 대신 렌더 중 상태 조정(React 권장 패턴)을 쓴다. DB OEM 부품을 쓰는 OemPartsPanel 과 자리는 같고 출처가 다르다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-panel\"><div class=\"pv-web-between\"><strong>앞 휠</strong><span class=\"pv-web-caption\">카탈로그</span></div><div class=\"pv-web-part-row\"><span>휠 어셈블리 ▾</span><code>상세부품 3</code></div><div class=\"pv-web-part-row\"><span>타이어</span><code>110/70-13</code></div><span class=\"pv-web-button-control pv-web-button-sm\">부위 이름 그대로 담기</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-selectionpopover",
   "platform": "web",
   "name": "SelectionPopover",
   "category": "레이아웃",
   "purpose": "페이지를 밀지 않는 앵커 선택 목록. Escape·바깥 클릭 닫기",
   "variants": [],
   "props": [],
   "tokens": [],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/layout/Workbench.tsx",
   "usage_count": null,
   "notes": "색상·간격은 디자인 토큰을 사용합니다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-parts-commerce-shell",
   "platform": "web",
   "name": "Shell",
   "category": "레이아웃",
   "purpose": "부품 커머스 공통 셸 — 눈썹줄·제목·설명·역할 전환 링크 + 탭 내비",
   "variants": [
    "workshop",
    "dealer"
   ],
   "props": [
    {
     "name": "title",
     "type": "string",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "dealer",
     "type": "boolean",
     "required": false,
     "default": "false"
    },
    {
     "name": "children",
     "type": "ReactNode",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": ".pc 섹션(딜러는 .pc-dealer) — .pc-heading(eyebrow·h1·p + 역할 링크) + .pc-tabs NavLink 줄 + 본문.",
   "file": "apps/admin-web/src/features/partsCommerce/ui.tsx",
   "usage_count": 5,
   "notes": "정비소 구매 공간과 판매자 공간이 같은 셸을 dealer 한 값으로 갈라 쓴다 — 두 공간을 따로 그리면 탭 이름·경로가 조금씩 어긋난다. 탭은 정비소(부품 찾기·장바구니·구매 주문 + 내 부품 재고)와 딜러(판매 주문·상품·재고·사업자/정산)로 고정이고, 오른쪽 링크로 상대 공간을 오간다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-shell\"><div class=\"pv-web-between\"><div><div class=\"pv-web-eyebrow\">BUWANG · 정비소 부품 구매</div><strong>부품 찾기</strong></div><span class=\"pv-web-link\">판매자 공간 ↗</span></div><div class=\"pv-web-tab-bar-list\"><span class=\"pv-web-tab-bar-tab pv-web-tab-bar-active\">부품 찾기</span><span class=\"pv-web-tab-bar-tab\">장바구니</span><span class=\"pv-web-tab-bar-tab\">구매 주문</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-stack",
   "platform": "web",
   "name": "Stack",
   "category": "레이아웃",
   "purpose": "행·열 간격과 정렬 레이아웃",
   "variants": [],
   "props": [],
   "tokens": [],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/layout/Workbench.tsx",
   "usage_count": null,
   "notes": "색상·간격은 디자인 토큰을 사용합니다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-text",
   "platform": "web",
   "name": "Text",
   "category": "레이아웃",
   "purpose": "강조·보조·상태·오류 텍스트",
   "variants": [],
   "props": [],
   "tokens": [],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/layout/Workbench.tsx",
   "usage_count": null,
   "notes": "색상·간격은 디자인 토큰을 사용합니다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-three-d-estimate-workspace",
   "platform": "web",
   "name": "ThreeDEstimateWorkspace",
   "category": "레이아웃",
   "purpose": "3D 견적 작업대 — 참고 모델로 부위를 가리키고 실제 차량 기준 부품을 담는다",
   "variants": [
    "default",
    "hideSummary",
    "disabled",
    "not-entitled",
    "viewer-error"
   ],
   "props": [
    {
     "name": "vehicle",
     "type": "GeneralVehicleCandidate | null",
     "required": true,
     "default": null
    },
    {
     "name": "items",
     "type": "LinkedItem[]",
     "required": true,
     "default": null
    },
    {
     "name": "onItemsChange",
     "type": "(next: LinkedItem[]) => void",
     "required": true,
     "default": null
    },
    {
     "name": "onFocusItem",
     "type": "(rowId: string) => void",
     "required": false,
     "default": null
    },
    {
     "name": "referenceModelId",
     "type": "string | null",
     "required": false,
     "default": null
    },
    {
     "name": "onReferenceModelChange",
     "type": "(model: ReferenceModelInfo | null) => void",
     "required": false,
     "default": null
    },
    {
     "name": "disabled",
     "type": "boolean",
     "required": false,
     "default": null
    },
    {
     "name": "entitled",
     "type": "boolean",
     "required": false,
     "default": null
    },
    {
     "name": "vehicleAction",
     "type": "ReactNode",
     "required": false,
     "default": null
    },
    {
     "name": "hideSummary",
     "type": "boolean",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "colors",
    "fontWeight",
    "radius",
    "space"
   ],
   "sizes": "threeDWorkspace.css 의 컨테이너 쿼리 배치. 위: 3D 뷰어 + 부위/부품 패널, 아래: 담긴 항목. 카드 껍데기는 EstimateSectionCard.",
   "file": "apps/admin-web/src/features/estimates/three3d/ThreeDEstimateWorkspace.tsx",
   "usage_count": 1,
   "notes": "★이 화면에는 차가 두 대 있다 — 견적서에 적히는 실제 차량과 부위를 가리키려고 띄운 참고 모델. 부품은 언제나 실제 차량에서만 나온다(참고 모델에서 끌어오면 고객 차에 안 맞는 품번이 견적서에 박힌다). 프로 권한은 견적 전용 자격 API 로 판정하고 ★모르는 동안은 허용하지 않는다. 무거운 3D 번들은 ThreeDWorkspaceSlot 이 lazy 로 떼어 내 일반 견적만 쓰는 정비소가 three.js 를 받지 않는다. 사고 견적 전용 Estimate3DMode 와는 별개 표면이며 뷰어·어셈블리·부위 어휘만 함께 쓴다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-layout pv-web-layout-workspace\"><span class=\"pv-web-layout-cell pv-web-layout-viewer\">3D 뷰어<br>(참고 모델)</span><span class=\"pv-web-layout-cell\">부품 패널<br>(실제 차량)</span><span class=\"pv-web-layout-cell pv-web-layout-wide\">담긴 항목 · 견적</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-workbench",
   "platform": "web",
   "name": "Workbench",
   "category": "레이아웃",
   "purpose": "상단 선택 띠·부위·뷰어·관련 목록·하단 내역을 배치하는 반응형 작업대",
   "variants": [],
   "props": [],
   "tokens": [],
   "sizes": "null",
   "file": "apps/admin-web/src/components/design-system/layout/Workbench.tsx",
   "usage_count": null,
   "notes": "색상·간격은 디자인 토큰을 사용합니다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "web-avatar",
   "platform": "web",
   "name": "Avatar",
   "category": "기타",
   "purpose": "이미지 또는 이니셜을 표시하는 원형 아바타",
   "variants": [
    "image",
    "initials",
    "image-error-fallback"
   ],
   "props": [
    {
     "name": "src",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "initials",
     "type": "string",
     "required": false,
     "default": null
    },
    {
     "name": "size",
     "type": "number",
     "required": false,
     "default": "56"
    },
    {
     "name": "backgroundColor",
     "type": "string",
     "required": false,
     "default": "'var(--color-primary)'"
    },
    {
     "name": "alt",
     "type": "string",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "--color-primary",
    "--color-text-on-primary",
    "fontFamily.display",
    "fontWeight.semibold",
    "radius.pill"
   ],
   "sizes": "기본 width/height 56px; radius.pill=9999px; 글자 Math.round(size * 0.375)px.",
   "file": "apps/admin-web/src/components/design-system/avatar/Avatar.tsx",
   "usage_count": 0,
   "notes": "이미지 로드 실패 시 이니셜로 전환한다. imageFailed 상태를 src 변경 시 초기화하는 코드는 없다.",
   "style_source": {
    "file": "apps/admin-web/src/components/design-system/avatar/Avatar.tsx",
    "inline": true,
    "css_module_files": [],
    "antd_wrapper": false,
    "class_names": [],
    "dynamic_class_expressions": [],
    "dynamic_class_definitions": [],
    "global_css_files": [
     "apps/admin-web/src/styles/global.css"
    ],
    "global_rules": [],
    "baseline_rules": "tokens.css.baseline_rules",
    "inline_definitions": [
     {
      "symbol": "rootStyle",
      "scope": "Avatar",
      "line": 23,
      "source": "{\n    width: size,\n    height: size,\n    borderRadius: radius.pill,\n    backgroundColor: showImage ? 'transparent' : backgroundColor,\n    overflow: 'hidden',\n    display: 'inline-flex',\n    alignItems: 'center',\n    justifyContent: 'center',\n    flexShrink: 0,\n    color: 'var(--color-text-on-primary)',\n    fontFamily: fontFamily.display,\n    fontWeight: fontWeight.semibold,\n    fontSize: Math.round(size * 0.375),\n    lineHeight: 1,\n    userSelect: 'none',\n  }"
     },
     {
      "symbol": "imageStyle",
      "scope": "Avatar",
      "line": 41,
      "source": "{\n    width: '100%',\n    height: '100%',\n    objectFit: 'cover',\n    display: 'block',\n  }"
     }
    ],
    "inline_style_bindings": [
     {
      "tag": "span",
      "expression": "{rootStyle}",
      "line": 50
     },
     {
      "tag": "img",
      "expression": "{imageStyle}",
      "line": 54
     },
     {
      "tag": "span",
      "expression": "{rootStyle}",
      "line": 63
     }
    ],
    "rendered_tags": [
     "span",
     "img"
    ],
    "composed_components": [],
    "portal_target": null,
    "notes": "스타일 숫자는 React CSSProperties 원문 기준이다. 고정 class가 없는 요소는 inline 정의를 사용한다. global 규칙은 선택자 문맥과 inline 우선순위를 함께 적용한다.",
    "composed_class_names": [],
    "global_token_names": [],
    "composed_token_names": []
   },
   "preview_html": "<div class=\"pv-web-frame pv-web-avatar\"><div class=\"pv-web-column\"><span class=\"pv-web-avatar-circle\">김</span><span class=\"pv-web-caption\">김부왕</span></div><div class=\"pv-web-column\"><span class=\"pv-web-avatar-circle pv-web-avatar-small\">이</span><span class=\"pv-web-caption\">이정비</span></div><div class=\"pv-web-column\"><span class=\"pv-web-avatar-circle pv-web-avatar-fallback\">박</span><span class=\"pv-web-caption\">이미지 대체</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-comment-list",
   "platform": "web",
   "name": "CommentList",
   "category": "기타",
   "purpose": "댓글 목록 + 입력줄 — 커뮤니티 글과 장터 매물 상세가 함께 쓴다",
   "variants": [
    "default",
    "allowSecret",
    "loading",
    "has-more",
    "empty"
   ],
   "props": [
    {
     "name": "comments",
     "type": "CommentItem[]",
     "required": true,
     "default": null
    },
    {
     "name": "loading",
     "type": "boolean",
     "required": true,
     "default": null
    },
    {
     "name": "hasMore",
     "type": "boolean",
     "required": true,
     "default": null
    },
    {
     "name": "onLoadMore",
     "type": "() => void",
     "required": true,
     "default": null
    },
    {
     "name": "onAdd",
     "type": "(content: string, isSecret?: boolean) => Promise<void>",
     "required": true,
     "default": null
    },
    {
     "name": "onDelete",
     "type": "(id: number) => Promise<void>",
     "required": true,
     "default": null
    },
    {
     "name": "onAuthorClick",
     "type": "(userId: number) => void",
     "required": false,
     "default": null
    },
    {
     "name": "allowSecret",
     "type": "boolean",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "colors.border",
    "colors.textMuted",
    "colors.primary"
   ],
   "sizes": "입력줄 gap 8px·아래 여백 16px; 목록은 작성자·상대시간(dayjs ko)·본문 세로 배치. 비밀댓글 체크는 allowSecret 일 때만 나온다.",
   "file": "apps/admin-web/src/features/community/components/CommentList.tsx",
   "usage_count": 2,
   "notes": "커뮤니티(PostDetailPage)와 장터(ListingDetailPage) 두 표면이 같은 댓글 UI 를 쓴다 — 두 화면이 각자 그리면 시간 표기·삭제 동선이 갈라진다. 작성자 클릭은 onAuthorClick 으로 위임한다(공개 프로필 모달은 바깥이 소유).",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-comment-list\"><div class=\"pv-web-row\"><span class=\"pv-web-text-input-control pv-web-grow pv-web-placeholder\">댓글을 입력하세요</span><span class=\"pv-web-button-control pv-web-button-primary pv-web-button-sm\">등록</span></div><div class=\"pv-web-comment\"><b>김사장</b><span class=\"pv-web-caption\">3시간 전</span><p>부품 입고되면 알려주세요.</p></div><div class=\"pv-web-comment\"><b>부왕부품</b><span class=\"pv-web-caption\">1시간 전</span><p>내일 오전에 출고됩니다.</p></div></div>",
   "preview_variant": null
  },
  {
   "id": "web-part-image",
   "platform": "web",
   "name": "PartImage",
   "category": "기타",
   "purpose": "부품 사진 섬네일 — 사진이 없으면 '사진 준비 중' 자리표시로 대체",
   "variants": [
    "image",
    "placeholder"
   ],
   "props": [
    {
     "name": "part",
     "type": "PartType",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "폭은 부모(.pc-candidate 그리드)가 정한다. loading=lazy 고정. 자리표시는 같은 칸을 채우는 .pc-image-placeholder 회색 면.",
   "file": "apps/admin-web/src/features/partsCommerce/PartCandidates.tsx",
   "usage_count": 4,
   "notes": "부품 후보 카드·장바구니·주문 표가 같은 사진 칸을 쓴다(4곳). 사진 없음을 빈 칸으로 두면 표가 어긋나 보여서 항상 같은 크기의 자리표시를 그린다. 스타일은 features/partsCommerce/commerce.css.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame pv-web-row\"><span class=\"pv-web-part-thumb\">부품 사진</span><span class=\"pv-web-part-thumb pv-web-part-thumb-empty\">사진 준비 중</span></div>",
   "preview_variant": null
  },
  {
   "id": "web-sentence-text",
   "platform": "web",
   "name": "SentenceText",
   "category": "기타",
   "purpose": "문장 단위로 줄을 끊어 보여 주는 표시 전용 텍스트 래퍼",
   "variants": [
    "pre-line"
   ],
   "props": [
    {
     "name": "children",
     "type": "string",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "자체 치수 없음. white-space: pre-line + overflow-wrap: anywhere 만 건다 — 글자 크기·색·굵기는 부모를 그대로 따른다.",
   "file": "apps/admin-web/src/components/SentenceText.tsx",
   "usage_count": 22,
   "notes": "utils/sentenceLines 가 문장 경계에서 줄을 나눈 결과를 pre-line 으로 흘린다. 표시 경계에서만 쓰고 저장값·폼 입력값은 건드리지 않는다. AlertBanner·NotificationItem·FormField·Modal·DataTable·PageHeader 등 DS 프리미티브 안쪽이 이미 감싸고 있어 호출부가 또 감쌀 필요는 없다. 사용처 22곳으로 웹에서 가장 많이 재사용되는 표시 규약이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-web-frame\"><div class=\"pv-web-sentence\">신규 견적서가 도착했습니다.\n확인 후 고객에게 발송해 주세요.</div><span class=\"pv-web-caption\">문장 경계에서만 줄을 끊는다</span></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-accept-button",
   "platform": "mechanic",
   "name": "AcceptButton",
   "category": "버튼",
   "purpose": "예약 수락용 그라데이션 버튼.",
   "variants": [
    "enabled",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": false,
     "default": "'수락'"
    },
    {
     "name": "onPressed",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.ctaGradient",
    "AppColors.textMuted",
    "AppColors.textWhite",
    "AppSpacing.radiusMD",
    "AppTypography.buttonMD",
    "BwPressable.tintDark"
   ],
   "sizes": "전폭×44; radiusMD=12; enabled ctaGradient, disabled bgSurface; 라벨 buttonMD.copyWith(14px,w800,활성 textWhite/비활성 textMuted). | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=BorderRadius.circular(AppSpacing.radiusMD); width=double.infinity; height=44; color=isEnabled ? null : AppColors.bgSurface; gradient=isEnabled ? AppColors.ctaGradient : null; alignment=Alignment.center; style=AppTypography.buttonMD.copyWith( fontSize: 14, fontWeight: FontWeight.w800, color: isEnabled ? AppColors.textWhite : AppColors.textMuted, ); fontSize=14; fontWeight=FontWeight.w800; color=isEnabled ? AppColors.textWhite : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/buttons/accept_button.dart",
   "usage_count": 0,
   "notes": "BwPressable.button(tintDark, scale 0.98). onPressed=null이면 비활성.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-accept-button\"><span class=\"pv-mechanic-button pv-mechanic-primary\">수락</span><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-disabled\" aria-disabled=\"true\">수락</span><span class=\"pv-mechanic-button pv-mechanic-primary pv-mechanic-pressed\">수락</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-action-buttons",
   "platform": "mechanic",
   "name": "ActionButtons",
   "category": "버튼",
   "purpose": "보조·주 액션을 같은 폭의 가로 버튼 두 개로 표시한다.",
   "variants": [
    "secondary",
    "primary",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "secondaryLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "primaryLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onSecondary",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "onPrimary",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.borderDefault",
    "AppColors.ctaGradient",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppColors.textWhite",
    "AppSpacing.btnHeightLG",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppTypography.numericBody"
   ],
   "sizes": "각 Expanded 높이 btnHeightLG=50, radiusLG=18, 사이 md=12. 보조 borderDefault 배경/textSecondary; 주 ctaGradient, 비활성 borderDefault/textMuted. 라벨 numericBody=20px/w700/tabularFigures. | 코드 세부 표현식(하위 요소·상태 포함): width=AppSpacing.md; scale=_isSecondaryPressed ? 0.98 : 1.0; duration=const Duration(milliseconds: 100); height=AppSpacing.btnHeightLG; color=AppColors.borderDefault; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); alignment=Alignment.center; style=AppTypography.numericBody.copyWith( color: AppColors.textSecondary, ); color=AppColors.textSecondary; scale=_isPrimaryPressed ? 0.98 : 1.0; gradient=isEnabled ? AppColors.ctaGradient : null; color=isEnabled ? null : AppColors.borderDefault; style=AppTypography.numericBody.copyWith( color: isEnabled ? AppColors.textWhite : AppColors.textMuted, ); color=isEnabled ? AppColors.textWhite : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/buttons/action_buttons.dart",
   "usage_count": 1,
   "notes": "BwPressable 없이 GestureDetector+AnimatedScale 100ms/0.98. numericBody가 14px라는 주석과 달리 실제 토큰은 20px. 보조는 비활성 시 색상 변화 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-action-buttons\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-secondary\">이전</span><span class=\"pv-mechanic-button pv-mechanic-primary\">견적 전송</span></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-secondary\">이전</span><span class=\"pv-mechanic-button pv-mechanic-disabled\" aria-disabled=\"true\">견적 전송</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-dialog-actions",
   "platform": "mechanic",
   "name": "BwDialogActions",
   "category": "버튼",
   "purpose": "다이얼로그의 확인·취소 액션을 한 행 또는 전폭 버튼으로 배치한다.",
   "variants": [
    "single",
    "dual",
    "loading",
    "danger",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "primaryLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onPrimary",
     "type": "VoidCallback?",
     "required": true,
     "default": null
    },
    {
     "name": "secondaryLabel",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onSecondary",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "isLoading",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "danger",
     "type": "bool",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.statusDanger",
    "AppColors.textMuted",
    "AppColors.textTertiary",
    "AppColors.textWhite",
    "AppSpacing.btnHeightLG",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppTypography.buttonSM",
    "BwPressable.tintDark",
    "BwPressable.tintLight"
   ],
   "sizes": "높이50/radius18; 2버튼 Expanded 사이gap12; 보조 bgWhite/borderDefault1.5/buttonSM13/w700/textTertiary. 주 활성 brandPrimary 또는 statusDanger, 비활성 borderDefault; 13/w700 white 또는textMuted. 로딩18×18/stroke2/white. | 코드 세부 표현식(하위 요소·상태 포함): width=double.infinity; width=AppSpacing.md; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); height=AppSpacing.btnHeightLG; color=AppColors.bgWhite; border=Border.all(color: AppColors.borderDefault, width: 1.5); color=AppColors.borderDefault; width=1.5; alignment=Alignment.center; style=AppTypography.buttonSM.copyWith(color: AppColors.textTertiary); color=AppColors.textTertiary; pressedTint=BwPressable.tintLight; color=bg; width=18; height=18; strokeWidth=2; color=AppColors.textWhite; style=AppTypography.buttonSM.copyWith( color: enabled ? AppColors.textWhite : AppColors.textMuted, ); color=enabled ? AppColors.textWhite : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/overlays/bw_dialog_actions.dart",
   "usage_count": 22,
   "notes": "BwPressable.button, 주 tintLight/보조 tintDark. secondaryLabel=null이면 단일 전폭. loading 동안 양쪽 탭 비활성. onPrimary는 required nullable. AlertDialog.actions 대신 content 하단에 배치하는 규약.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-dialog-actions\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-dialog-secondary\">취소</span><span class=\"pv-mechanic-button pv-mechanic-dialog-primary\">예약 확정</span></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-dialog-danger\">삭제</span><span class=\"pv-mechanic-button pv-mechanic-dialog-primary\"><span class=\"pv-mechanic-spinner\" style=\"width:18px;height:18px\" aria-label=\"처리 중\"></span></span><span class=\"pv-mechanic-button pv-mechanic-dialog-disabled\" aria-disabled=\"true\">확인</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-large-text-toggle-button",
   "platform": "mechanic",
   "name": "BwLargeTextToggleButton",
   "category": "버튼",
   "purpose": "화면 상단에서 큰 글씨 모드를 즉시 켜고 끄는 텍스트형 퀵 액션 pill.",
   "variants": [
    "off",
    "on",
    "saving"
   ],
   "props": [
    {
     "name": "enabled",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "onChanged",
     "type": "ValueChanged<bool>",
     "required": true,
     "default": null
    },
    {
     "name": "isSaving",
     "type": "bool",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textWhite",
    "AppSpacing.iconMD",
    "AppSpacing.md",
    "AppTypography.bodySM"
   ],
   "sizes": "StadiumBorder pill; minHeight 48(터치 타깃), padding H md=12. off=bgWhite+borderDefault 테두리+brandPrimary 글자, on=brandPrimary 배경+같은 색 테두리+textWhite 글자. 라벨 bodySM/w700 1줄. isSaving이면 라벨 자리에 iconMD=20 CircularProgressIndicator(strokeWidth2, 상태색).",
   "file": "apps/mobile/lib/shared/widgets/accessibility/bw_large_text_toggle_button.dart",
   "usage_count": 1,
   "notes": "라벨이 상태가 아니라 다음 동작을 말한다 — 꺼짐이면 '큰 글씨 켜기', 켜짐이면 '큰 글씨 끄기'. 색만으로 상태를 전하지 않도록 Semantics(toggled/value)와 Tooltip을 함께 단다. 내부는 ExcludeSemantics. 컨트롤드 — 저장은 호출부. 설정 화면 헤더에서 쓰며, 같은 값을 아래 BwLargeTextSettingCard와 공유한다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "mechanic-outline-button",
   "platform": "mechanic",
   "name": "BwOutlineButton",
   "category": "버튼",
   "purpose": "브랜드 색상 테두리와 라벨의 아웃라인 버튼.",
   "variants": [
    "enabled",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onPressed",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderBrand",
    "AppColors.borderDefault",
    "AppColors.textBrand",
    "AppColors.textMuted",
    "AppSpacing.btnHeightLG",
    "AppSpacing.radiusLG",
    "AppTypography.numericBody",
    "BwPressable.tintDark"
   ],
   "sizes": "높이50, radius18, 전폭; 흰 배경; borderBrand 1.5(비활성 borderDefault 1.5); numericBody=20px/w700, textBrand/비활성 textMuted. | 코드 세부 표현식(하위 요소·상태 포함): height=AppSpacing.btnHeightLG; borderRadius=AppSpacing.radiusLG; backgroundColor=AppColors.bgWhite; border=Border.all( color: AppColors.borderBrand, width: 1.5, ); color=AppColors.borderBrand; width=1.5; color=AppColors.borderDefault; style=AppTypography.numericBody.copyWith( color: isEnabled ? AppColors.textBrand : AppColors.textMuted, ); color=isEnabled ? AppColors.textBrand : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/buttons/outline_button.dart",
   "usage_count": 4,
   "notes": "PressableButton → BwPressable.button. outlinePressedBg를 직접 쓰지 않고 공통 tintDark 사용.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-outline-button\"><span class=\"pv-mechanic-button pv-mechanic-outline\">정비 내역 보기</span><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-outline pv-mechanic-inactive\" aria-disabled=\"true\">내역 보기</span><span class=\"pv-mechanic-button pv-mechanic-outline pv-mechanic-pressed\">내역 보기</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-scan-button",
   "platform": "mechanic",
   "name": "BwScanButton",
   "category": "버튼",
   "purpose": "뷰파인더와 움직이는 스캔선을 갖춘 번호판 인식 진입 버튼.",
   "variants": [
    "idle",
    "pressed",
    "large-text"
   ],
   "props": [
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "height",
     "type": "double",
     "required": false,
     "default": "98"
    }
   ],
   "tokens": [
    "AppColors.bannerGradient",
    "AppColors.scanViewfinder"
   ],
   "sizes": "minHeight=height 기본98, radius20, bannerGradient. viewfinder width=min(availableWidth,210×textScale[1..3]), minHeight56; 내용폭=max(1,width-24); V padding=max(0,(height-56)/2). 코너19×19/선2.5/r5/scanViewfinder alpha0.9; 스캔선2/좌우9. SCAN Pretendard30/w700/ls3/height1, 설명11/w600/ls0.3/height1/white alpha0.7. 그림자 #1A56B0 alpha0.4→0.2, y12→6/blur26/spread-6. | 코드 세부 표현식(하위 요소·상태 포함): duration=const Duration(milliseconds: 2400); curve=Curves.easeInOut; fontFamily='Pretendard'; fontSize=30; fontWeight=FontWeight.w700; color=Colors.white; letterSpacing=3; height=1.0; fontSize=11; fontWeight=FontWeight.w600; letterSpacing=0.3; color=Colors.white.withValues(alpha: 0.7); duration=const Duration(milliseconds: 150); curve=Curves.easeOut; width=double.infinity; minHeight=widget.height; borderRadius=BorderRadius.circular(20); gradient=AppColors.bannerGradient; boxShadow=[ BoxShadow( color: const Color( 0xFF1A56B0, ).withValues(alpha: _pressed ? 0.2 : 0.4), offset: Offset(0, _pressed ? 6 : 12), blurRadius: 26, spreadRadius: -6, ), ]; color=const Color( 0xFF1A56B0, ).withValues(alpha: _pressed ? 0.2 : 0.4); offset=Offset(0, _pressed ? 6 : 12); blurRadius=26; spreadRadius=-6; alignment=Alignment.center; gradient=LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.white.withValues(alpha: 0.10), Colors.transparent, ], stops: const [0.0, 0.55], ); stops=const [0.0, 0.55]; padding=EdgeInsets.symmetric( vertical: math.max(0, (widget.height - 56) / 2), ); width=viewportWidth; minHeight=56; alignment=Alignment( 0, -1 + (_scanAnimation.value * 2), ); height=2; gradient=LinearGradient( colors: [ Colors.transparent, AppColors.scanViewfinder.withValues( alpha: 0.6, ), Colors.white, AppColors.scanViewfinder.withValues( alpha: 0.6, ), Colors.transparent, ], stops: const [0.0, 0.2, 0.5, 0.8, 1.0], ); stops=const [0.0, 0.2, 0.5, 0.8, 1.0]; width=contentWidth; style=scanStyle; textAlign=TextAlign.center; style=subtitleStyle; width=size; height=size; color=color; thickness=thickness",
   "file": "apps/mobile/lib/shared/widgets/cards/bw_scan_button.dart",
   "usage_count": 1,
   "notes": "GestureDetector, 150ms easeOut로 pressed translateY2+scale0.98. onTapUp에서 callback. onTap=null이어도 눌림 표현 유지. 스캔선 2400ms reverse repeat/easeInOut/0.1→0.9. 주석의 scanGradient 대신 실제 bannerGradient.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-scan-button\"><div class=\"pv-mechanic-scan-surface\"><div class=\"pv-mechanic-viewfinder\"><i></i><i></i><i></i><i></i><div class=\"pv-mechanic-scan-line\"></div><div class=\"pv-mechanic-scan-title\">SCAN</div><div class=\"pv-mechanic-scan-caption\">번호판을 스캔해 정비 시작</div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-fab-button",
   "platform": "mechanic",
   "name": "FabButton",
   "category": "버튼",
   "purpose": "추가 액션용 원형 플로팅 버튼.",
   "variants": [
    "enabled",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": false,
     "default": "Icons.add"
    },
    {
     "name": "onPressed",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.fabGradient",
    "AppColors.textMuted"
   ],
   "sizes": "64×64, radius=9999, 아이콘24. 활성 fabGradient; shadow brandPrimary alpha0.3 blur16 offset(0,6). 비활성 borderDefault/textMuted, 그림자 없음. | 코드 세부 표현식(하위 요소·상태 포함): scale=_isPressed ? 0.95 : 1.0; duration=const Duration(milliseconds: 100); width=64; height=64; gradient=_isEnabled ? AppColors.fabGradient : null; color=_isEnabled ? null : AppColors.borderDefault; borderRadius=BorderRadius.circular(9999); boxShadow=_isEnabled ? [ BoxShadow( color: AppColors.brandPrimary.withValues(alpha: 0.3), blurRadius: 16, offset: const Offset(0, 6), ), ] : null; color=AppColors.brandPrimary.withValues(alpha: 0.3); blurRadius=16; offset=const Offset(0, 6); alignment=Alignment.center; size=24; color=_isEnabled ? Colors.white : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/buttons/fab_button.dart",
   "usage_count": 0,
   "notes": "GestureDetector+AnimatedScale 100ms/0.95. 기본 Icons.add; BwPressable 미사용.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-fab-button\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-fab\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></span><span class=\"pv-mechanic-button pv-mechanic-fab pv-mechanic-fab-disabled\" aria-disabled=\"true\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></span><span class=\"pv-mechanic-button pv-mechanic-fab pv-mechanic-fab-pressed\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></span></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-ghost-button",
   "platform": "mechanic",
   "name": "GhostButton",
   "category": "버튼",
   "purpose": "중립 색상 테두리와 라벨의 보조 버튼.",
   "variants": [
    "enabled",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onPressed",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.textMuted",
    "AppColors.textTertiary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.radiusLG",
    "AppTypography.numericBody",
    "BwPressable.tintDark"
   ],
   "sizes": "높이50, radius18, 전폭; 흰 배경; borderDefault 1.5(비활성 bgSurface 1.5); numericBody=20px/w700, textTertiary/비활성 textMuted. | 코드 세부 표현식(하위 요소·상태 포함): height=AppSpacing.btnHeightLG; borderRadius=AppSpacing.radiusLG; backgroundColor=AppColors.bgWhite; border=Border.all( color: AppColors.borderDefault, width: 1.5, ); color=AppColors.borderDefault; width=1.5; color=AppColors.bgSurface; style=AppTypography.numericBody.copyWith( color: isEnabled ? AppColors.textTertiary : AppColors.textMuted, ); color=isEnabled ? AppColors.textTertiary : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/buttons/ghost_button.dart",
   "usage_count": 1,
   "notes": "PressableButton → BwPressable.button. 주석의 numericBody 14px 대신 실제 20px 사용. ghostPressedBg 토큰을 직접 쓰지 않음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-ghost-button\"><span class=\"pv-mechanic-button pv-mechanic-ghost\">나중에 입력</span><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-ghost pv-mechanic-inactive\" aria-disabled=\"true\">나중에 입력</span><span class=\"pv-mechanic-button pv-mechanic-ghost pv-mechanic-pressed\">나중에 입력</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-pressable-button",
   "platform": "mechanic",
   "name": "PressableButton",
   "category": "버튼",
   "purpose": "버튼 크기·배경·테두리·비활성 스타일을 받는 공용 베이스.",
   "variants": [
    "enabled",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "onPressed",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "width",
     "type": "double",
     "required": false,
     "default": "double.infinity"
    },
    {
     "name": "height",
     "type": "double",
     "required": true,
     "default": null
    },
    {
     "name": "borderRadius",
     "type": "double",
     "required": true,
     "default": null
    },
    {
     "name": "backgroundColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "disabledBackgroundColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "border",
     "type": "Border?",
     "required": false,
     "default": null
    },
    {
     "name": "disabledBorder",
     "type": "Border?",
     "required": false,
     "default": null
    },
    {
     "name": "child",
     "type": "Widget",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "BwPressable.tintDark"
   ],
   "sizes": "width 기본 double.infinity; height/borderRadius/backgroundColor/disabledBackgroundColor는 필수 주입. child 중앙 정렬. disabledBorder가 없으면 border 재사용. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=BorderRadius.circular(borderRadius); width=width; height=height; color=bg; border=b; alignment=Alignment.center",
   "file": "apps/mobile/lib/shared/widgets/buttons/pressable_button.dart",
   "usage_count": 3,
   "notes": "BwPressable.button으로 scale0.98+tintDark, 100ms easeOut. onPressed=null이면 비활성.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-pressable-button\"><div class=\"pv-mechanic-diagram\"><span class=\"pv-mechanic-muted\">버튼 영역 · 자식 중앙 정렬</span><span class=\"pv-mechanic-button pv-mechanic-primary\">정비카드 열기</span></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-disabled\" aria-disabled=\"true\">비활성</span><span class=\"pv-mechanic-button pv-mechanic-primary pv-mechanic-pressed\">누르는 중</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-primary-cta-button",
   "platform": "mechanic",
   "name": "PrimaryCtaButton",
   "category": "버튼",
   "purpose": "주요 제출·진행 액션을 표시하는 로딩 지원 CTA.",
   "variants": [
    "enabled",
    "disabled",
    "loading",
    "pressed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onPressed",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "isLoading",
     "type": "bool",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "AppColors.borderDefault",
    "AppColors.ctaGradient",
    "AppColors.shadowCtaColor",
    "AppColors.textMuted",
    "AppColors.textWhite",
    "AppSpacing.btnHeightXL",
    "AppSpacing.radiusCTA",
    "AppTypography.buttonLG",
    "BwPressable.tintDark"
   ],
   "sizes": "전폭×btnHeightXL=52; radiusCTA=16; ctaGradient; shadowCtaColor=Color(0x4D1A56B0), blur12 offset(0,4). 라벨 buttonLG=16/w800/ls0.5. 로딩 24×24 스피너 stroke2.5 white. 비활성 borderDefault/textMuted, 그림자 없음. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=BorderRadius.circular(AppSpacing.radiusCTA); width=double.infinity; height=AppSpacing.btnHeightXL; gradient=isEnabled ? AppColors.ctaGradient : null; color=isEnabled ? null : AppColors.borderDefault; boxShadow=isEnabled ? const [ BoxShadow( color: AppColors.shadowCtaColor, blurRadius: 12, offset: Offset(0, 4), ), ] : null; color=AppColors.shadowCtaColor; blurRadius=12; offset=Offset(0, 4); alignment=Alignment.center; width=24; height=24; strokeWidth=2.5; style=AppTypography.buttonLG.copyWith( color: isEnabled ? AppColors.textWhite : AppColors.textMuted, ); color=isEnabled ? AppColors.textWhite : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/buttons/primary_cta_button.dart",
   "usage_count": 18,
   "notes": "BwPressable.button 기본 tintDark. isLoading=true 또는 onPressed=null이면 비활성. 로딩도 비활성 배경을 사용.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-primary-cta-button\"><span class=\"pv-mechanic-button pv-mechanic-primary\">정비 시작하기</span><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-disabled\" aria-disabled=\"true\">정비 시작</span><span class=\"pv-mechanic-button pv-mechanic-primary\"><span class=\"pv-mechanic-spinner\" style=\"width:24px;height:24px\" aria-label=\"처리 중\"></span></span><span class=\"pv-mechanic-button pv-mechanic-primary pv-mechanic-pressed\">확정</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-reject-button",
   "platform": "mechanic",
   "name": "RejectButton",
   "category": "버튼",
   "purpose": "예약 거절용 빨간 라벨의 테두리 버튼.",
   "variants": [
    "enabled",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": false,
     "default": "'거절'"
    },
    {
     "name": "onPressed",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.borderDefault",
    "AppColors.statusError",
    "AppColors.textMuted",
    "AppColors.textWhite",
    "AppSpacing.radiusMD",
    "AppTypography.buttonMD",
    "BwPressable.tintDark"
   ],
   "sizes": "전폭×44, radius12, borderDefault 1.5, 활성 textWhite 배경/비활성 bgSurface. 라벨 buttonMD에서14px/w700, statusError/비활성 textMuted. | 코드 세부 표현식(하위 요소·상태 포함): height=44; borderRadius=AppSpacing.radiusMD; backgroundColor=AppColors.textWhite; border=Border.all(color: AppColors.borderDefault, width: 1.5); color=AppColors.borderDefault; width=1.5; style=AppTypography.buttonMD.copyWith( fontSize: 14, fontWeight: FontWeight.w700, color: isEnabled ? AppColors.statusError : AppColors.textMuted, ); fontSize=14; fontWeight=FontWeight.w700; color=isEnabled ? AppColors.statusError : AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/buttons/reject_button.dart",
   "usage_count": 0,
   "notes": "PressableButton → BwPressable.button. 비활성에서도 동일 border가 fallback으로 유지.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-reject-button\"><span class=\"pv-mechanic-button pv-mechanic-reject\">거절</span><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-reject pv-mechanic-inactive\" aria-disabled=\"true\">거절</span><span class=\"pv-mechanic-button pv-mechanic-reject pv-mechanic-pressed\">거절</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-boxed-text-field",
   "platform": "mechanic",
   "name": "BwBoxedTextField",
   "category": "입력",
   "purpose": "리스트 내부와 인라인 폼에 쓰는 박스형 텍스트 입력.",
   "variants": [
    "single-line",
    "multiline",
    "focused",
    "readOnly",
    "disabled"
   ],
   "props": [
    {
     "name": "controller",
     "type": "TextEditingController?",
     "required": false,
     "default": null
    },
    {
     "name": "hintText",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "height",
     "type": "double",
     "required": false,
     "default": "46"
    },
    {
     "name": "focusNode",
     "type": "FocusNode?",
     "required": false,
     "default": null
    },
    {
     "name": "onChanged",
     "type": "ValueChanged<String>?",
     "required": false,
     "default": null
    },
    {
     "name": "maxLines",
     "type": "int",
     "required": false,
     "default": "1"
    },
    {
     "name": "readOnly",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "enabled",
     "type": "bool",
     "required": false,
     "default": "true"
    },
    {
     "name": "style",
     "type": "TextStyle?",
     "required": false,
     "default": null
    },
    {
     "name": "borderColor",
     "type": "Color?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppTypography.inputPlaceholder",
    "AppTypography.inputValue"
   ],
   "sizes": "단일라인 높이46 기본(주입 가능), multiline 높이null; paddingH14/V0(멀티라인V12); radius12/border1.5. 편집 bgWhite, readOnly 또는 disabled bgSecondary. borderColor override → 편집 포커스 brandPrimary → borderDefault. 기본 inputValue/inputPlaceholder14/w400; scrollPadding bottom120. | 코드 세부 표현식(하위 요소·상태 포함): height=singleLine ? widget.height : null; padding=EdgeInsets.symmetric( horizontal: 14, vertical: singleLine ? 0 : 12, ); alignment=singleLine ? Alignment.centerLeft : null; color=inert ? AppColors.bgSecondary : AppColors.bgWhite; borderRadius=BorderRadius.circular(12); border=Border.all(color: borderColor, width: 1.5); color=borderColor; width=1.5; maxLines=widget.maxLines; scrollPadding=widget.scrollPadding; style=widget.style ?? AppTypography.inputValue; textAlignVertical=singleLine ? TextAlignVertical.center : null; hintStyle=widget.hintStyle ?? AppTypography.inputPlaceholder; border=InputBorder.none; contentPadding=EdgeInsets.zero",
   "file": "apps/mobile/lib/shared/widgets/forms/bw_boxed_text_field.dart",
   "usage_count": 4,
   "notes": "TextField, BwPressable 없음. 래퍼가 배경·테두리를 담당하고 내부 decoration은 filled=false/보더없음/zero padding. 외부 focusNode는 호출측 소유. 밖 탭은 unfocus. props 10개 제한으로 생략된 생성자 인자: keyboardType: TextInputType? (optional); inputFormatters: List<TextInputFormatter>? (optional); onSubmitted: ValueChanged<String>? (optional); textInputAction: TextInputAction? (optional); autofocus: bool (optional) = false; hintStyle: TextStyle? (optional); scrollPadding: EdgeInsets (optional) = const EdgeInsets.only(bottom: 120).",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-boxed-text-field\"><div class=\"pv-mechanic-field pv-mechanic-field-focused\">PCX125 · 1가2 3456</div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-field pv-mechanic-grow\">정비 메모 입력</div><div class=\"pv-mechanic-field pv-mechanic-field-inert pv-mechanic-grow\">읽기 전용</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-choice-chip",
   "platform": "mechanic",
   "name": "BwChoiceChip",
   "category": "입력",
   "purpose": "작업 유형·결제 수단 등을 선택하는 pill 칩.",
   "variants": [
    "selected",
    "unselected",
    "pressed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "selected",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textPrimary",
    "AppSpacing.lg",
    "AppSpacing.sm",
    "AppTypography.bodySM",
    "BwPressable.tintDark"
   ],
   "sizes": "paddingH16/V10/r9999; AnimatedContainer120ms. 선택 bg/border brandPrimary/흰 글자13/w700, 미선택 bgSecondary/borderDefault/textPrimary13/w500. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=BorderRadius.circular(9999); duration=const Duration(milliseconds: 120); padding=const EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.sm, ); color=selected ? AppColors.brandPrimary : AppColors.bgSecondary; border=Border.all( color: selected ? AppColors.brandPrimary : AppColors.borderDefault, ); color=selected ? AppColors.brandPrimary : AppColors.borderDefault; style=AppTypography.bodySM.copyWith( color: selected ? Colors.white : AppColors.textPrimary, fontWeight: selected ? FontWeight.w700 : FontWeight.w500, ); color=selected ? Colors.white : AppColors.textPrimary; fontWeight=selected ? FontWeight.w700 : FontWeight.w500",
   "file": "apps/mobile/lib/shared/widgets/chips/bw_choice_chip.dart",
   "usage_count": 0,
   "notes": "BwPressable.chip(scale0.97+tintDark). onTap은 필수로 disabled API 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-choice-chip\"><div class=\"pv-mechanic-chips\"><div class=\"pv-mechanic-choice pv-mechanic-choice-selected\">엔진오일</div><div class=\"pv-mechanic-choice\">타이어</div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-chips\"><div class=\"pv-mechanic-choice\">브레이크</div><div class=\"pv-mechanic-choice pv-mechanic-choice-selected pv-mechanic-pressed\">배터리</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-comment-input-bar",
   "platform": "mechanic",
   "name": "BwCommentInputBar",
   "category": "입력",
   "purpose": "화면 하단에 댓글·채팅 입력과 전송 버튼을 배치한다.",
   "variants": [
    "idle",
    "sending",
    "leading",
    "multiline"
   ],
   "props": [
    {
     "name": "controller",
     "type": "TextEditingController",
     "required": true,
     "default": null
    },
    {
     "name": "hintText",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onSubmit",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "sending",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "leading",
     "type": "Widget?",
     "required": false,
     "default": null
    },
    {
     "name": "maxLines",
     "type": "int",
     "required": false,
     "default": "3"
    },
    {
     "name": "maxLength",
     "type": "int",
     "required": false,
     "default": "1000"
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.brandPrimary",
    "AppTypography.bodySM"
   ],
   "sizes": "흰 배경/top border bgSecondary; padding L=leading있으면12/없으면16,R8,T8,B=viewPadding.bottom+8. 입력 bodySM13/w600, minLines1/maxLines3/maxLength1000 기본. sending 스피너20×20/stroke2. | 코드 세부 표현식(하위 요소·상태 포함): padding=EdgeInsets.only( left: leading != null ? 12 : 16, right: 8, top: 8, bottom: MediaQuery.of(context).viewPadding.bottom + 8, ); color=Colors.white; border=Border(top: BorderSide(color: AppColors.bgSecondary)); color=AppColors.bgSecondary; border=InputBorder.none; style=AppTypography.bodySM; maxLines=maxLines; minLines=1; width=20; height=20; strokeWidth=2; color=AppColors.brandPrimary",
   "file": "apps/mobile/lib/shared/widgets/forms/bw_comment_input_bar.dart",
   "usage_count": 2,
   "notes": "네이티브 IconButton; sending이면 onPressed=null. SafeArea 위젯 대신 viewPadding.bottom을 직접 합산. 길이 제한 enforced/카운터 숨김. 내부 필드 보더와 fill 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-comment-input-bar\"><div class=\"pv-mechanic-comment\"><div class=\"pv-mechanic-grow\">엔진오일 교체 완료했습니다.</div><div class=\"pv-mechanic-comment-send\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 3 19 9-19 9 4-9zM7 12h15\"/></svg></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-comment\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg><div class=\"pv-mechanic-grow\">정비 메모 전송 중</div><div class=\"pv-mechanic-comment-send\"><span class=\"pv-mechanic-spinner\" style=\"width:20px;height:20px\" aria-label=\"처리 중\"></span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-filter-tab",
   "platform": "mechanic",
   "name": "BwFilterTab",
   "category": "입력",
   "purpose": "목록 필터의 활성 상태를 pill 탭으로 표시한다.",
   "variants": [
    "active",
    "inactive",
    "disabled",
    "pressed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "isActive",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textSecondary",
    "AppColors.textWhite",
    "AppSpacing.radiusPill",
    "AppTypography.bodyXS",
    "BwPressable.tintDark"
   ],
   "sizes": "paddingH16/V9/radiusPill9999; border 활성brandPrimary/기본borderDefault. 활성 bg brandPrimary/textWhite/13w700, 비활성 bgWhite/textSecondary/13w600. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=BorderRadius.circular(AppSpacing.radiusPill); padding=const EdgeInsets.symmetric( vertical: 9, horizontal: 16, ); color=isActive ? AppColors.brandPrimary : AppColors.bgWhite; border=Border.all( color: isActive ? AppColors.brandPrimary : AppColors.borderDefault, ); color=isActive ? AppColors.brandPrimary : AppColors.borderDefault; style=AppTypography.bodyXS.copyWith( fontSize: 13, color: isActive ? AppColors.textWhite : AppColors.textSecondary, fontWeight: isActive ? FontWeight.w700 : FontWeight.w600, ); fontSize=13; color=isActive ? AppColors.textWhite : AppColors.textSecondary; fontWeight=isActive ? FontWeight.w700 : FontWeight.w600",
   "file": "apps/mobile/lib/shared/widgets/forms/filter_tab.dart",
   "usage_count": 4,
   "notes": "BwPressable.chip. onTap=null이면 눌림 비활성이나 선택 스타일은 isActive 그대로 유지.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-filter-tab\"><div class=\"pv-mechanic-chips\"><div class=\"pv-mechanic-filter pv-mechanic-filter-active\">전체</div><div class=\"pv-mechanic-filter\">대기</div><div class=\"pv-mechanic-filter\">완료</div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-chips\"><div class=\"pv-mechanic-filter pv-mechanic-filter-active pv-mechanic-pressed\">정비 중</div><div class=\"pv-mechanic-filter\">취소</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-input-field",
   "platform": "mechanic",
   "name": "BwInputField",
   "category": "입력",
   "purpose": "라벨·아이콘·검증 오류를 갖춘 폼 입력 필드.",
   "variants": [
    "normal",
    "focused",
    "error",
    "disabled",
    "obscured",
    "visible"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "hintText",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "controller",
     "type": "TextEditingController?",
     "required": false,
     "default": null
    },
    {
     "name": "validator",
     "type": "String? Function(String?)?",
     "required": false,
     "default": null
    },
    {
     "name": "errorText",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "enabled",
     "type": "bool",
     "required": false,
     "default": "true"
    },
    {
     "name": "obscureText",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "onChanged",
     "type": "ValueChanged<String>?",
     "required": false,
     "default": null
    },
    {
     "name": "leadingIcon",
     "type": "IconData?",
     "required": false,
     "default": null
    },
    {
     "name": "trailingIcon",
     "type": "IconData?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.statusError",
    "AppColors.textMuted",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppTypography.inputLabel",
    "AppTypography.inputPlaceholder",
    "AppTypography.inputValue",
    "BwPressable.tintDark"
   ],
   "sizes": "label inputLabel12/w700/ls1.2, gap6. radius12/paddingH16/V14, 높이 고정없음. 평상 borderDefault1.5; error statusError2; enabled focus brandPrimary2. 활성 bgWhite/disabled bgSecondary. 아이콘20/gap12; 값·힌트14/w400. 오류라벨 gap6/inputLabel/statusError. 150ms easeOut. | 코드 세부 표현식(하위 요소·상태 포함): style=AppTypography.inputLabel; height=6; duration=const Duration(milliseconds: 150); curve=Curves.easeOut; color=fillColor; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); border=Border.all( color: borderColor, width: borderWidth, ); color=borderColor; width=borderWidth; padding=const EdgeInsets.symmetric( vertical: 14, horizontal: AppSpacing.lg, ); size=AppSpacing.iconMD; color=leadingIconColor; width=AppSpacing.md; scrollPadding=widget.scrollPadding; style=AppTypography.inputValue; contentPadding=EdgeInsets.zero; border=InputBorder.none; hintStyle=AppTypography.inputPlaceholder; height=0; fontSize=0; style=AppTypography.inputLabel.copyWith( color: AppColors.statusError, ); color=AppColors.statusError",
   "file": "apps/mobile/lib/shared/widgets/forms/bw_input_field.dart",
   "usage_count": 4,
   "notes": "trailing icon만 BwPressable.icon(탭하면 _obscure 토글). TextFormField 기본 오류는 fontSize0/height0로 숨기고 외부 라벨 렌더. errorText가 validator보다 우선. scrollPadding bottom120. disabled 상태여도 trailing 래퍼 onTap은 별도로 비활성 처리하지 않음. props 10개 제한으로 생략된 생성자 인자: keyboardType: TextInputType? (optional); inputFormatters: List<TextInputFormatter>? (optional); onTapOutside: TapRegionCallback? (optional); textInputAction: TextInputAction? (optional); onFieldSubmitted: ValueChanged<String>? (optional); autofillHints: Iterable<String>? (optional); focusNode: FocusNode? (optional); scrollPadding: EdgeInsets (optional) = const EdgeInsets.only(bottom: 120).",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-input-field\"><span class=\"pv-mechanic-label\">차량 번호</span><div class=\"pv-mechanic-input pv-mechanic-field-error\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"5\" cy=\"17\" r=\"4\"/><circle cx=\"19\" cy=\"17\" r=\"4\"/><path d=\"M5 17 9 9h6l4 8M5 17h8l-4-8M13 5h3l2 6M6 9h4\"/></svg><span class=\"pv-mechanic-grow\">12가</span></div><div class=\"pv-mechanic-input-error\">차량 번호를 정확히 입력해 주세요.</div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-field pv-mechanic-field-focused pv-mechanic-grow\">1가2 3456</div><div class=\"pv-mechanic-field pv-mechanic-field-inert pv-mechanic-grow\">입력 불가</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-quantity-stepper",
   "platform": "mechanic",
   "name": "BwQuantityStepper",
   "category": "입력",
   "purpose": "최솟값·최댓값 범위에서 수량을 증감한다.",
   "variants": [
    "normal",
    "min-bound",
    "max-bound",
    "pressed"
   ],
   "props": [
    {
     "name": "value",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "onChanged",
     "type": "ValueChanged<int>",
     "required": true,
     "default": null
    },
    {
     "name": "min",
     "type": "int",
     "required": false,
     "default": "1"
    },
    {
     "name": "max",
     "type": "int",
     "required": false,
     "default": "99"
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.textPrimary",
    "AppSpacing.btnHeightMD",
    "AppSpacing.iconMD",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusSM",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppTypography.headingMD",
    "BwPressable.tintDark"
   ],
   "sizes": "paddingH10/V8, radius12, bgWhite/borderDefault1. 양 버튼40×40, tint radius16, 아이콘20; 값 headingMD16/w800. 경계 버튼 opacity0.4. | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.symmetric( horizontal: AppSpacing.sm, vertical: AppSpacing.xs, ); color=AppColors.bgWhite; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); border=Border.all(color: AppColors.borderDefault); color=AppColors.borderDefault; style=AppTypography.headingMD; opacity=enabled ? 1.0 : 0.4; borderRadius=BorderRadius.circular(AppSpacing.radiusSM); width=AppSpacing.btnHeightMD; height=AppSpacing.btnHeightMD; alignment=Alignment.center; size=AppSpacing.iconMD; color=AppColors.textPrimary",
   "file": "apps/mobile/lib/shared/widgets/forms/bw_quantity_stepper.dart",
   "usage_count": 0,
   "notes": "내부 BwPressable.icon. min1/max99 기본; 경계에서는 해당 onTap=null. value 자체 보정은 하지 않음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-quantity-stepper\"><div class=\"pv-mechanic-stepper\"><div class=\"pv-mechanic-step-control\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12h14\"/></svg></div><div class=\"pv-mechanic-step-value\">2</div><div class=\"pv-mechanic-step-control\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-stepper\"><div class=\"pv-mechanic-step-control pv-mechanic-bound\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12h14\"/></svg></div><div class=\"pv-mechanic-step-value\">1</div><div class=\"pv-mechanic-step-control\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></div></div><div class=\"pv-mechanic-stepper\"><div class=\"pv-mechanic-step-control\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12h14\"/></svg></div><div class=\"pv-mechanic-step-value\">99</div><div class=\"pv-mechanic-step-control pv-mechanic-bound\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-search-field",
   "platform": "mechanic",
   "name": "BwSearchField",
   "category": "입력",
   "purpose": "검색어 입력과 지우기 액션을 제공하는 검색 필드.",
   "variants": [
    "empty",
    "has-text",
    "disabled",
    "autofocus"
   ],
   "props": [
    {
     "name": "controller",
     "type": "TextEditingController",
     "required": true,
     "default": null
    },
    {
     "name": "hintText",
     "type": "String",
     "required": false,
     "default": "'검색'"
    },
    {
     "name": "onChanged",
     "type": "ValueChanged<String>?",
     "required": false,
     "default": null
    },
    {
     "name": "onClear",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "autofocus",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "enabled",
     "type": "bool",
     "required": false,
     "default": "true"
    },
    {
     "name": "onSubmitted",
     "type": "ValueChanged<String>?",
     "required": false,
     "default": null
    },
    {
     "name": "focusNode",
     "type": "FocusNode?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppSpacing.iconMD",
    "AppSpacing.radiusSM",
    "AppTypography.bodyMD"
   ],
   "sizes": "높이46/radius14/bgWhite/borderDefault1. 검색아이콘20/textMuted/L14/R9. 값14/w500, hint14/w500/textMuted; cursor brandPrimary. 지우기46×46/radius16/아이콘18; 빈 값 우측폭14. | 코드 세부 표현식(하위 요소·상태 포함): height=46; color=AppColors.bgWhite; borderRadius=BorderRadius.circular(14); border=Border.all(color: AppColors.borderDefault, width: 1); color=AppColors.borderDefault; width=1; padding=EdgeInsets.only(left: 14, right: 9); size=AppSpacing.iconMD; color=AppColors.textMuted; style=AppTypography.bodyMD.copyWith( fontWeight: FontWeight.w500, ); fontWeight=FontWeight.w500; border=InputBorder.none; hintStyle=AppTypography.bodyMD.copyWith( color: AppColors.textMuted, fontWeight: FontWeight.w500, ); contentPadding=EdgeInsets.zero; width=46; color=Colors.transparent; borderRadius=BorderRadius.circular(AppSpacing.radiusSM); size=18; width=14",
   "file": "apps/mobile/lib/shared/widgets/forms/bw_search_field.dart",
   "usage_count": 5,
   "notes": "clear는 Material+InkWell. 지우면 controller.clear + onChanged('') + onClear. textInputAction.search; Form 검증 없음. 바깥 탭 unfocus. onSubmitted=null은 코드상 직접 전달이며 별도 제출 핸들러 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-search-field\"><div class=\"pv-mechanic-search\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m16 16 5 5\"/></svg><div class=\"pv-mechanic-grow\">1가2 3456</div><div class=\"pv-mechanic-search-clear\"><svg aria-hidden=\"true\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 6 12 12M18 6 6 18\"/></svg></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-search\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m16 16 5 5\"/></svg><div class=\"pv-mechanic-grow\">차량 번호 검색</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-collapsible-price-input",
   "platform": "mechanic",
   "name": "CollapsiblePriceInput",
   "category": "입력",
   "purpose": "금액 한 필드와 부품비·공임비 상세 입력 사이를 전환한다.",
   "variants": [
    "collapsed",
    "expanded",
    "autofocus",
    "keyboard-toolbar"
   ],
   "props": [
    {
     "name": "partsController",
     "type": "TextEditingController",
     "required": true,
     "default": null
    },
    {
     "name": "laborController",
     "type": "TextEditingController",
     "required": true,
     "default": null
    },
    {
     "name": "partsLabel",
     "type": "String",
     "required": false,
     "default": "'부품비'"
    },
    {
     "name": "onChanged",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "externalTotalFocusNode",
     "type": "FocusNode?",
     "required": false,
     "default": null
    },
    {
     "name": "externalPartsFocusNode",
     "type": "FocusNode?",
     "required": false,
     "default": null
    },
    {
     "name": "externalLaborFocusNode",
     "type": "FocusNode?",
     "required": false,
     "default": null
    },
    {
     "name": "externalTotalController",
     "type": "TextEditingController?",
     "required": false,
     "default": null
    },
    {
     "name": "autofocus",
     "type": "bool",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppSpacing.md",
    "AppSpacing.xs",
    "AppTypography.caption",
    "AppTypography.inputValue"
   ],
   "sizes": "라벨 caption12/textMuted+gap6; BwBoxedTextField 기본높이46/r12/border1.5/H14; 값 inputValue16/w700/textPrimary. 토글 위8, 아이콘16/gap4/caption12 brandPrimary; 상세 간격12; 안내 위8. | 코드 세부 표현식(하위 요소·상태 포함): alignment=0.3; duration=const Duration(milliseconds: 300); style=AppTypography.caption.copyWith( color: AppColors.textMuted, fontSize: 12, ); color=AppColors.textMuted; fontSize=12; height=6; height=AppSpacing.xs; size=16; color=AppColors.brandPrimary; width=4; style=AppTypography.caption.copyWith( color: AppColors.brandPrimary, ); height=AppSpacing.md; style=AppTypography.caption.copyWith( color: AppColors.textMuted, ); fontSize=16; fontWeight=FontWeight.w700; color=AppColors.textPrimary; style=_inputTextStyle",
   "file": "apps/mobile/lib/shared/widgets/forms/collapsible_price_input.dart",
   "usage_count": 4,
   "notes": "토글 GestureDetector. 노동비>0이면 expanded로 초기화; 펼치면 합계 readOnly, 접으면 parts=합계/labor=0. ThousandsSeparatorFormatter와 NumberInputToolbar 오버레이 사용. 포커스 시200ms 후 ensureVisible(alignment0.3,duration300ms). 외부 노드/컨트롤러는 호출측 소유.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-collapsible-price-input\"><span class=\"pv-mechanic-price-label\">합계</span><div class=\"pv-mechanic-field pv-mechanic-price-value\">45,000</div><div class=\"pv-mechanic-price-toggle\"><svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m5 9 7 7 7-7\"/></svg>부품비 · 공임비 나눠 입력</div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-grow\"><span class=\"pv-mechanic-price-label\">부품비</span><div class=\"pv-mechanic-field pv-mechanic-price-value\">30,000</div></div><div class=\"pv-mechanic-grow\"><span class=\"pv-mechanic-price-label\">공임비</span><div class=\"pv-mechanic-field pv-mechanic-price-value\">15,000</div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-number-input-toolbar",
   "platform": "mechanic",
   "name": "NumberInputToolbar",
   "category": "입력",
   "purpose": "숫자 키보드 위에서 금액 추가와 필드 간 이동을 제공한다.",
   "variants": [
    "numeric",
    "non-numeric",
    "first-field",
    "last-field",
    "hidden"
   ],
   "props": [
    {
     "name": "focusNodes",
     "type": "List<FocusNode>",
     "required": true,
     "default": null
    },
    {
     "name": "controllers",
     "type": "List<TextEditingController>",
     "required": true,
     "default": null
    },
    {
     "name": "isNumericField",
     "type": "List<bool>?",
     "required": false,
     "default": null
    },
    {
     "name": "onFieldChanged",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppTypography.bodySM",
    "BwPressable.tintDark"
   ],
   "sizes": "높이60/H padding8/bgSecondary/top borderDefault0.5. 숫자버튼 H14/V11/r8/bgWhite/borderDefault1/bodySM17/w700; gap6 또는10. 이동버튼44×48/아이콘26; disabled textMuted. | 코드 세부 표현식(하위 요소·상태 포함): offset=ctrl.text.length; height=60; color=AppColors.bgSecondary; border=Border( top: BorderSide(color: AppColors.borderDefault, width: 0.5), ); color=AppColors.borderDefault; width=0.5; padding=const EdgeInsets.symmetric(horizontal: 8); alignment=Alignment.centerLeft; width=6; width=10; borderRadius=BorderRadius.circular(8); padding=const EdgeInsets.symmetric(horizontal: 14, vertical: 11); color=AppColors.bgWhite; border=Border.all(color: AppColors.borderDefault); style=AppTypography.bodySM.copyWith(fontSize: 17, fontWeight: FontWeight.w700); fontSize=17; fontWeight=FontWeight.w700; width=44; height=48; size=26; color=disabled ? AppColors.textMuted : AppColors.textPrimary",
   "file": "apps/mobile/lib/shared/widgets/forms/number_input_toolbar.dart",
   "usage_count": 4,
   "notes": "빠른 버튼 BwPressable.button, 이동 BwPressable.icon. TextFieldTapRegion. 포커스 없거나 키보드 viewInsets.bottom=0이면 숨김. 00/000은 커서 위치가 아니라 숫자 끝에 추가; +5천/+1만/+10만. 주석의 완료 버튼은 현재 build에 없음. 작은 화면 FittedBox.scaleDown.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-number-input-toolbar\"><div class=\"pv-mechanic-scale-frame\" style=\"height:30px\"><div class=\"pv-mechanic-scaled\" style=\"width:200%;transform:scale(0.5)\"><div class=\"pv-mechanic-toolbar\"><div class=\"pv-mechanic-quick-buttons\"><div class=\"pv-mechanic-quick\">00</div><div class=\"pv-mechanic-quick\">000</div><div class=\"pv-mechanic-quick\">+5천</div><div class=\"pv-mechanic-quick\">+1만</div><div class=\"pv-mechanic-quick\">+10만</div></div><div class=\"pv-mechanic-toolbar-nav pv-mechanic-muted\"><svg aria-hidden=\"true\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m15 5-7 7 7 7\"/></svg></div><div class=\"pv-mechanic-toolbar-nav\"><svg aria-hidden=\"true\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 5 7 7-7 7\"/></svg></div></div></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-toolbar\"><div class=\"pv-mechanic-grow\"></div><div class=\"pv-mechanic-toolbar-nav pv-mechanic-muted\"><svg aria-hidden=\"true\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m15 5-7 7 7 7\"/></svg></div><div class=\"pv-mechanic-toolbar-nav\"><svg aria-hidden=\"true\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 5 7 7-7 7\"/></svg></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-status-badge",
   "platform": "mechanic",
   "name": "BwStatusBadge",
   "category": "배지",
   "purpose": "예약·정비 상태를 색상과 라벨로 표시하는 pill 배지.",
   "variants": [
    "warning",
    "info",
    "success",
    "danger",
    "muted"
   ],
   "props": [
    {
     "name": "variant",
     "type": "BadgeVariant",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.statusDanger",
    "AppColors.statusDangerBg",
    "AppColors.statusInfo",
    "AppColors.statusInfoBg",
    "AppColors.statusMuted",
    "AppColors.statusMutedBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppSpacing.radiusPill",
    "AppTypography.captionBold"
   ],
   "sizes": "패딩 H11/V5; radiusPill=9999; captionBold를 11px/w800으로 변경. warning=WarningBg/Warning(대기), info=InfoBg/Info(정비 중), success=SuccessBg/Success(완료), danger=DangerBg/Danger(거절), muted=MutedBg/Muted(취소). | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.symmetric( vertical: 5, horizontal: 11, ); color=bg; borderRadius=BorderRadius.circular(AppSpacing.radiusPill); style=AppTypography.captionBold.copyWith( color: fg, fontSize: 11, fontWeight: FontWeight.w800, ); color=fg; fontSize=11; fontWeight=FontWeight.w800",
   "file": "apps/mobile/lib/shared/widgets/badges/status_badge.dart",
   "usage_count": 8,
   "notes": "표시 전용. label이 있으면 기본 상태명 대신 표시.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-status-badge\"><div class=\"pv-mechanic-chips\"><span class=\"pv-mechanic-badge pv-mechanic-tone-warning\">대기</span><span class=\"pv-mechanic-badge pv-mechanic-tone-info\">정비 중</span><span class=\"pv-mechanic-badge pv-mechanic-tone-success\">완료</span><span class=\"pv-mechanic-badge pv-mechanic-tone-danger\">거절</span><span class=\"pv-mechanic-badge pv-mechanic-tone-muted\">취소</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-title-rarity-badge",
   "platform": "mechanic",
   "name": "TitleRarityBadge",
   "category": "배지",
   "purpose": "칭호 희귀도와 표시 문맥에 맞춰 색·글로우·쉬머를 표시한다.",
   "variants": [
    "common",
    "rare",
    "epic",
    "legendary",
    "list",
    "detail"
   ],
   "props": [
    {
     "name": "titleName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "rarity",
     "type": "String",
     "required": false,
     "default": "'common'"
    },
    {
     "name": "mode",
     "type": "TitleDisplayMode",
     "required": false,
     "default": "TitleDisplayMode.list"
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.textMuted",
    "AppSpacing.md",
    "AppSpacing.radiusSM",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodySM",
    "AppTypography.caption"
   ],
   "sizes": "common 및 list: paddingH8/V2/r4/caption12w500; common bgSurface/textMuted; rare #60A5FA border alpha0.6, epic #8B5CF6 border alpha0.6; legendary #FFD700 border alpha0.8+아이콘12/gap2. detail(non-common): H12/V4/r16/bodySM13w600; rare shadow Color(0x8060A5FA)/blur8; epic purple→pink→purple; legendary 6색 gradient+shadow Color(0x99FFD700)/blur12/텍스트w700. | 코드 세부 표현식(하위 요소·상태 포함): duration=Duration( milliseconds: widget.rarity == 'legendary' ? 4000 : 3000); padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xs, vertical: 2); color=AppColors.bgSurface; borderRadius=BorderRadius.circular(4); style=AppTypography.caption.copyWith(color: AppColors.textMuted); color=AppColors.textMuted; border=Border.all(color: const Color(0xFF60A5FA).withValues(alpha: 0.6)); color=const Color(0xFF60A5FA).withValues(alpha: 0.6); style=AppTypography.caption.copyWith(color: const Color(0xFF60A5FA)); color=const Color(0xFF60A5FA); padding=const EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.xxs); borderRadius=BorderRadius.circular(AppSpacing.radiusSM); boxShadow=const [ BoxShadow( color: Color(0x8060A5FA), blurRadius: 8, spreadRadius: 0, ), ]; color=Color(0x8060A5FA); blurRadius=8; spreadRadius=0; style=AppTypography.bodySM.copyWith(color: const Color(0xFF60A5FA)); border=Border.all(color: const Color(0xFF8B5CF6).withValues(alpha: 0.6)); color=const Color(0xFF8B5CF6).withValues(alpha: 0.6); style=AppTypography.caption.copyWith(color: const Color(0xFF8B5CF6)); color=const Color(0xFF8B5CF6); gradient=LinearGradient( colors: const [ Color(0xFF8B5CF6), Color(0xFFEC4899), Color(0xFF8B5CF6), ], stops: [ (_shimmerCtrl!.value - 0.3).clamp(0.0, 1.0), _shimmerCtrl!.value, (_shimmerCtrl!.value + 0.3).clamp(0.0, 1.0), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ); stops=[ (_shimmerCtrl!.value - 0.3).clamp(0.0, 1.0), _shimmerCtrl!.value, (_shimmerCtrl!.value + 0.3).clamp(0.0, 1.0), ]; style=AppTypography.bodySM.copyWith(color: Colors.white); color=Colors.white; size=12; color=Color(0xFFFFD700); width=2; border=Border.all(color: const Color(0xFFFFD700).withValues(alpha: 0.8)); color=const Color(0xFFFFD700).withValues(alpha: 0.8); style=AppTypography.caption .copyWith(color: const Color(0xFFFFD700)); color=const Color(0xFFFFD700); gradient=LinearGradient( colors: const [ Color(0xFFFFD700), Color(0xFFFF6B6B), Color(0xFFC084FC), Color(0xFF60A5FA), Color(0xFF34D399), Color(0xFFFFD700), ], stops: List.generate(6, (i) { final base = _shimmerCtrl!.value + i / 6; return base > 1.0 ? base - 1.0 : base; }) ..sort(), ); stops=List.generate(6, (i) { final base = _shimmerCtrl!.value + i / 6; return base > 1.0 ? base - 1.0 : base; }) ..sort(); boxShadow=const [ BoxShadow( color: Color(0x99FFD700), blurRadius: 12, spreadRadius: 0, ), ]; color=Color(0x99FFD700); blurRadius=12; style=AppTypography.bodySM .copyWith(color: Colors.white, fontWeight: FontWeight.w700); fontWeight=FontWeight.w700",
   "file": "apps/mobile/lib/shared/widgets/profile/title_rarity_badge.dart",
   "usage_count": 2,
   "notes": "비인터랙티브. detail epic은3000ms repeat, legendary4000ms repeat; common은 detail도 동일 작은 배지. 애니메이션 컨트롤러는 initState에서만 조건부 생성하므로 기존 인스턴스의 rarity/mode 변경은 재초기화되지 않음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-title-rarity-badge\"><div class=\"pv-mechanic-rarity-stage\"><div class=\"pv-mechanic-chips\"><span class=\"pv-mechanic-rarity pv-mechanic-rarity-common\">첫 정비</span><span class=\"pv-mechanic-rarity pv-mechanic-rarity-rare pv-mechanic-rarity-detail\">꼼꼼한 손길</span></div><div class=\"pv-mechanic-chips\"><span class=\"pv-mechanic-rarity pv-mechanic-rarity-epic pv-mechanic-rarity-detail\">정비의 달인</span><span class=\"pv-mechanic-rarity pv-mechanic-rarity-legendary pv-mechanic-rarity-detail\">전설의 정비사</span></div><div class=\"pv-mechanic-chips\"><span class=\"pv-mechanic-rarity pv-mechanic-rarity-rare\">희귀</span><span class=\"pv-mechanic-rarity pv-mechanic-rarity-epic\">영웅</span><div class=\"pv-mechanic-legendary-list\"><svg aria-hidden=\"true\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m12 2 3 6 7 1-5 5 1 8-6-4-6 4 1-8-5-5 7-1z\"/></svg><span class=\"pv-mechanic-rarity pv-mechanic-rarity-legendary\">전설</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-action-card",
   "platform": "mechanic",
   "name": "BwActionCard",
   "category": "카드",
   "purpose": "큰 아이콘과 짧은 문구로 주요 액션을 강조하는 카드.",
   "variants": [
    "default",
    "custom-gradient",
    "tappable"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "gradient",
     "type": "LinearGradient?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandGradientEnd",
    "AppColors.brandGradientStart",
    "AppSpacing.lg",
    "AppSpacing.radiusLG",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppTypography.numericDisplay"
   ],
   "sizes": "높이107, radiusLG=18; padding L=xxl+xs=32,T20,R=xxl+lg=40,B20; 아이콘 자리65×67/아이콘64, gap15; numericDisplay 70px/w700/height1/ls-1.4/tabularFigures. 기본 gradient brandGradientStart→End. | 코드 세부 표현식(하위 요소·상태 포함): color=Colors.transparent; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); height=107; gradient=gradient ?? _defaultGradient; padding=const EdgeInsets.fromLTRB( AppSpacing.xxl + AppSpacing.xs, AppSpacing.xl, AppSpacing.xxl + AppSpacing.lg, AppSpacing.xl, ); width=65; height=67; size=64; color=Colors.white; width=15; style=AppTypography.numericDisplay.copyWith( fontSize: 70, color: Colors.white, height: 1.0, ); fontSize=70; height=1.0",
   "file": "apps/mobile/lib/shared/widgets/cards/bw_action_card.dart",
   "usage_count": 0,
   "notes": "Material+InkWell+Ink. BwPressable 미사용. 주석의 radius16/왼쪽30/IBMPlexMono는 실제 radius18/왼쪽32/Pretendard와 다름. 높이107 외 폭342는 주석일 뿐 고정하지 않음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-action-card\"><div class=\"pv-mechanic-scale-frame\" style=\"height:70px\"><div class=\"pv-mechanic-scaled\" style=\"width:153.84615384615384%;transform:scale(0.65)\"><div class=\"pv-mechanic-action-card\"><div class=\"pv-mechanic-action-icon\"><svg aria-hidden=\"true\" width=\"64\" height=\"64\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 3h6v6H3zM15 3h6v6h-6zM3 15h6v6H3zM15 15h3v3h3v3h-6zM12 3v9h9M3 12h6M12 15v6\"/></svg></div><div class=\"pv-mechanic-action-label\">시작!</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-history-card",
   "platform": "mechanic",
   "name": "BwHistoryCard",
   "category": "카드",
   "purpose": "차량 정보와 정비 상태·시간을 표시하는 대시보드 이력 카드.",
   "variants": [
    "default",
    "tappable",
    "badge-variants"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "iconColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "iconBgColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "vehicleName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "badgeVariant",
     "type": "BadgeVariant",
     "required": true,
     "default": null
    },
    {
     "name": "badgeLabel",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "statusLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "time",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.chevronColor",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusMD",
    "AppSpacing.xl",
    "AppSpacing.xxl",
    "AppSpacing.xxs",
    "AppTypography.bodyMD",
    "AppTypography.headingLG",
    "AppTypography.overline",
    "BwPressable.tintDark"
   ],
   "sizes": "radius18, bgWhite/borderDefault1, 왼쪽 색상줄 width4, padding20. 아이콘56×56/r12/size20, gap20. 차량 headingLG18/w800; 설명 bodyMD14/w600. 아래행 gap16; 상태 overline11/w800+4+BwStatusBadge; 상태-시간gap24; chevron20/상단padding12. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=BorderRadius.circular(AppSpacing.radiusLG); color=AppColors.bgWhite; border=Border.all(color: AppColors.borderDefault); color=AppColors.borderDefault; width=4; color=iconColor; padding=const EdgeInsets.all(AppSpacing.xl); width=56; height=56; color=iconBgColor; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); size=AppSpacing.xl; width=AppSpacing.xl; style=AppTypography.headingLG; style=AppTypography.bodyMD.copyWith( color: iconColor, ); height=AppSpacing.lg; style=AppTypography.overline; height=AppSpacing.xxs; width=AppSpacing.xxl; style=AppTypography.bodyMD; padding=const EdgeInsets.only(top: AppSpacing.md); size=AppSpacing.iconMD; color=AppColors.chevronColor",
   "file": "apps/mobile/lib/shared/widgets/cards/history_card.dart",
   "usage_count": 1,
   "notes": "BwPressable.card. statusLabel은 필수 파라미터지만 build에서 미사용. 왼쪽 색상줄의 명시 높이는 없고 실제 렌더는 부모 제약에 의존.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-history-card\"><div class=\"pv-mechanic-scale-frame\" style=\"height:110px\"><div class=\"pv-mechanic-scaled\" style=\"width:153.84615384615384%;transform:scale(0.65)\"><div class=\"pv-mechanic-card pv-mechanic-history\"><div class=\"pv-mechanic-history-leading\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-title\">PCX125 · 1가2 3456</div><div class=\"pv-mechanic-history-description\">엔진오일 교체</div><div class=\"pv-mechanic-history-footer\"><div class=\"pv-mechanic-stack\"><div class=\"pv-mechanic-overline\">정비 상태</div><span class=\"pv-mechanic-badge pv-mechanic-tone-success\">완료</span></div><div class=\"pv-mechanic-stack\"><div class=\"pv-mechanic-overline\">완료 시간</div><span class=\"pv-mechanic-muted\">오늘 14:30</span></div></div></div><div class=\"pv-mechanic-history-chevron\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 5 7 7-7 7\"/></svg></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-large-text-setting-card",
   "platform": "mechanic",
   "name": "BwLargeTextSettingCard",
   "category": "카드",
   "purpose": "큰 글씨 모드를 설명하고 스위치로 켜고 끄는 설정 카드.",
   "variants": [
    "off",
    "on",
    "saving"
   ],
   "props": [
    {
     "name": "enabled",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "onChanged",
     "type": "ValueChanged<bool>",
     "required": true,
     "default": null
    },
    {
     "name": "isSaving",
     "type": "bool",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusMD",
    "AppSpacing.sm",
    "AppSpacing.xxs",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.headingLG"
   ],
   "sizes": "Material bgWhite/radiusLG=18/borderDefault 1; padding lg=16. 좌측 아이콘칸 40×40 bgSurface/radiusMD=12, '가' headingLG brandPrimary(textScaler noScaling — 큰 글씨에서도 칸을 안 넘김); gap md=12. 제목 bodyMD, 설명 gap xxs=4 bodySM/w400/height1.5/textSecondary. 우측 gap sm=10, isSaving일 때 padding md + 20×20 CircularProgressIndicator(strokeWidth2, brandPrimary), 아니면 Switch.adaptive(activeTrackColor brandPrimary).",
   "file": "apps/mobile/lib/shared/widgets/accessibility/bw_large_text_setting_card.dart",
   "usage_count": 1,
   "notes": "컨트롤드 컴포넌트 — 저장과 전역 배율 적용은 호출부(BwTextScaleScope)가 한다. 고정 높이를 쓰지 않아 200% 확대·접히는 화면에서도 설명이 잘리지 않는다. Semantics(container/button/toggled/value/hint)를 바깥에 두고 내부는 ExcludeSemantics로 감싸 스크린리더가 한 덩어리로 읽게 한다. 행 전체와 스위치가 같은 토글.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-list-tile-card",
   "platform": "mechanic",
   "name": "BwListTileCard",
   "category": "카드",
   "purpose": "제목·설명과 선택적 앞뒤 콘텐츠를 배치하는 리스트 카드.",
   "variants": [
    "title-only",
    "subtitle",
    "leading",
    "trailing",
    "tappable"
   ],
   "props": [
    {
     "name": "leading",
     "type": "Widget?",
     "required": false,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "subtitle",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "trailing",
     "type": "Widget?",
     "required": false,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.xs",
    "AppTypography.bodyLG",
    "AppTypography.bodySM",
    "BwPressable.tintDark"
   ],
   "sizes": "minHeight64, padding16, radius12, bgWhite, borderDefault1.25. leading gap12/trailing gap8; title bodyLG15/w600 최대2줄; subtitle gap2/bodySM13/w400/textSecondary 최대2줄. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=borderRadius; minHeight=64; color=AppColors.bgWhite; border=Border.all( color: AppColors.borderDefault, width: 1.25, ); color=AppColors.borderDefault; width=1.25; padding=const EdgeInsets.all(AppSpacing.lg); width=AppSpacing.md; style=AppTypography.bodyLG.copyWith( fontWeight: FontWeight.w600, ); fontWeight=FontWeight.w600; maxLines=2; height=2; style=AppTypography.bodySM.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ); color=AppColors.textSecondary; fontWeight=FontWeight.normal; width=AppSpacing.xs",
   "file": "apps/mobile/lib/shared/widgets/cards/bw_list_tile_card.dart",
   "usage_count": 1,
   "notes": "BwPressable.card로 같은 radius의 틴트. trailing 자동 셰브론 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-list-tile-card\"><div class=\"pv-mechanic-list-tile\"><div class=\"pv-mechanic-icon-tile\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-list-title\">엔진오일 교체</div><div class=\"pv-mechanic-list-subtitle\">PCX125 · 1가2 3456</div></div><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 5 7 7-7 7\"/></svg></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-list-tile\"><div class=\"pv-mechanic-list-title\">정비 예약 확인</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-maintenance-timeline",
   "platform": "mechanic",
   "name": "BwMaintenanceTimeline",
   "category": "카드",
   "purpose": "정비 기록을 상태별 카드 목록과 선택적 날짜 구분으로 표시한다.",
   "variants": [
    "overdue",
    "pending",
    "inProgress",
    "done",
    "cancelled",
    "accident-done",
    "date-dividers"
   ],
   "props": [
    {
     "name": "records",
     "type": "List<MaintenanceListItem>",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "void Function(dynamic recordId)?",
     "required": false,
     "default": null
    },
    {
     "name": "showDateDividers",
     "type": "bool",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "AppColors.bgSurfaceNeutral",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.statusAccidentCompleted",
    "AppColors.statusDanger",
    "AppColors.statusDangerBg",
    "AppColors.statusInfo",
    "AppColors.statusInfoBg",
    "AppColors.statusMuted",
    "AppColors.statusMutedBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppColors.textMuted",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusPill",
    "AppTypography.bodyLG",
    "AppTypography.bodyMD",
    "AppTypography.numericSmall"
   ],
   "sizes": "카드간gap12/padding18/r18/bgWhite/borderDefault1/그림자없음. 타일44×44/r12/아이콘22/gap12; 제목15w800, 별명 아래번호판12.5w400/gap2; pillH11/V5/r9999/11w800. 구분선bgSurfaceNeutral1/위아래14; 항목14w700, 날짜11.5w400/top3, 금액16w700 brandPrimary. 날짜헤더12.5w800/textMuted/간격10/선1, top첫2/다음20/bottom8. | 코드 세부 표현식(하위 요소·상태 포함): padding=EdgeInsets.only(top: i == 0 ? 2 : 20, bottom: 8); height=12; style=AppTypography.numericSmall.copyWith( fontSize: 12.5, fontWeight: FontWeight.w800, color: AppColors.textMuted, ); fontSize=12.5; fontWeight=FontWeight.w800; color=AppColors.textMuted; width=10; height=1; color=AppColors.borderDefault; color=Colors.transparent; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); color=AppColors.bgWhite; border=Border.all(color: AppColors.borderDefault); padding=const EdgeInsets.all(18); width=44; height=44; alignment=Alignment.center; color=tileBg; borderRadius=BorderRadius.circular(12); size=22; color=tileFg; width=12; maxLines=1; style=AppTypography.bodyLG.copyWith( fontWeight: FontWeight.w800, ); height=2; style=AppTypography.numericSmall.copyWith( fontSize: 12.5, fontWeight: FontWeight.w400, color: AppColors.textMuted, ); fontWeight=FontWeight.w400; padding=const EdgeInsets.symmetric( vertical: 5, horizontal: 11, ); color=pillBg; borderRadius=BorderRadius.circular(AppSpacing.radiusPill); style=TextStyle( fontFamily: 'Pretendard', fontSize: 11, fontWeight: FontWeight.w800, color: pillFg, ); fontFamily='Pretendard'; fontSize=11; color=pillFg; height=14; color=AppColors.bgSurfaceNeutral; style=AppTypography.bodyMD.copyWith( fontWeight: FontWeight.w700, ); fontWeight=FontWeight.w700; height=3; style=AppTypography.numericSmall.copyWith( fontSize: 11.5, fontWeight: FontWeight.w400, color: AppColors.textMuted, ); fontSize=11.5; width=8; style=AppTypography.numericSmall.copyWith( fontSize: 16, color: AppColors.brandPrimary, ); fontSize=16; color=AppColors.brandPrimary",
   "file": "apps/mobile/lib/shared/widgets/sections/bw_maintenance_timeline.dart",
   "usage_count": 1,
   "notes": "Material+InkWell+Ink. completed→done/in_progress→inProgress/pending/cancelled, 다른값→overdue(긴급). 완료 사고수리는 타일만 statusAccidentCompleted alpha0.12/전경동일, pill은 성공. records는 DTO; onTap은 record.id. 날짜 헤더는 인접 createdAt 기준.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-maintenance-timeline\"><div class=\"pv-mechanic-scale-frame\" style=\"height:128px\"><div class=\"pv-mechanic-scaled\" style=\"width:142.85714285714286%;transform:scale(0.7)\"><div class=\"pv-mechanic-date-divider\">2026.09.08<div class=\"pv-mechanic-date-line\"></div></div><div class=\"pv-mechanic-card pv-mechanic-maintenance-card\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-timeline-tile pv-mechanic-tone-success\"><svg aria-hidden=\"true\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-timeline-name\">출근용 PCX</div><div class=\"pv-mechanic-timeline-plate\">1가2 3456</div></div><span class=\"pv-mechanic-badge pv-mechanic-tone-success\">완료</span></div><div class=\"pv-mechanic-divider\"></div><div class=\"pv-mechanic-timeline-footer\"><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-timeline-item\">엔진오일 교체</div><div class=\"pv-mechanic-timeline-date\">09.08 14:30 · 12,800km</div></div><div class=\"pv-mechanic-timeline-price\">45,000원</div></div></div></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-chips\"><span class=\"pv-mechanic-badge pv-mechanic-tone-warning\">대기</span><span class=\"pv-mechanic-badge pv-mechanic-tone-info\">정비 중</span><span class=\"pv-mechanic-badge pv-mechanic-tone-success\">완료</span><span class=\"pv-mechanic-badge pv-mechanic-tone-danger\">거절</span><span class=\"pv-mechanic-badge pv-mechanic-tone-muted\">취소</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-profile-card",
   "platform": "mechanic",
   "name": "BwProfileCard",
   "category": "카드",
   "purpose": "프로필 사진·레벨·칭호·활동 통계를 세 가지 크기로 표시한다.",
   "variants": [
    "mini",
    "compact",
    "full",
    "avatar-fallback",
    "equipped-title",
    "tappable"
   ],
   "props": [
    {
     "name": "profile",
     "type": "ProfileCard",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "ProfileCardSize",
     "required": false,
     "default": "ProfileCardSize.mini"
    },
    {
     "name": "equippedTitleName",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "equippedTitleRarity",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusSM",
    "AppSpacing.radiusXL",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodyLG",
    "AppTypography.bodySM",
    "AppTypography.caption",
    "AppTypography.headingMD",
    "AppTypography.numericSmall",
    "BwPressable.tintDark"
   ],
   "sizes": "mini: width100/padding12/r12/avatar radius24/name13w600. compact: padding16/r18/avatar r28/name15w700/level numericSmall12w700/배지16(최대3). mini·compact bgWhite/borderDefault1, 칩H8/V2/r4/caption12. full: padding20/r20/avatar r32/name16w800/tier helmet24/배지18, tier gradient/shadow tierColor alpha0.2 blur16 y4; story padding12/r16/black alpha0.15/bodySM13 italic. 세 모드 모두 고정 높이 없음. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=BorderRadius.circular(switch (size) { ProfileCardSize.mini => AppSpacing.radiusMD, ProfileCardSize.compact => AppSpacing.radiusLG, ProfileCardSize.full => AppSpacing.radiusXL, }); width=100; padding=const EdgeInsets.all(AppSpacing.md); color=AppColors.bgWhite; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); border=Border.all(color: AppColors.borderDefault); color=AppColors.borderDefault; radius=24; backgroundColor=AppColors.bgSurface; size=24; color=AppColors.textMuted; height=AppSpacing.xs; style=AppTypography.bodySM; maxLines=1; style=AppTypography.caption.copyWith( color: _tierColor(profile.tierName), ); color=_tierColor(profile.tierName); width=4; size=10; color=Color(0xFFF59E0B); style=AppTypography.caption.copyWith( color: const Color(0xFFF59E0B), fontSize: 10, ); color=const Color(0xFFF59E0B); fontSize=10; height=AppSpacing.xxs; padding=const EdgeInsets.all(AppSpacing.lg); borderRadius=BorderRadius.circular(AppSpacing.radiusLG); radius=28; size=28; width=AppSpacing.md; style=AppTypography.bodyLG; width=AppSpacing.xs; style=AppTypography.numericSmall.copyWith( color: _tierColor(profile.tierName), ); height=AppSpacing.md; padding=const EdgeInsets.only(right: 4); size=16; color=AppColors.brandPrimary; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xs, vertical: 2); color=AppColors.bgSurface; borderRadius=BorderRadius.circular(4); style=AppTypography.caption; borderRadius=BorderRadius.circular(AppSpacing.radiusXL); gradient=bgGradient; boxShadow=[ BoxShadow( color: tierColor.withValues(alpha: 0.2), blurRadius: 16, offset: const Offset(0, 4), ), ]; color=tierColor.withValues(alpha: 0.2); blurRadius=16; offset=const Offset(0, 4); padding=const EdgeInsets.all(AppSpacing.xl); radius=32; backgroundColor=Colors.white.withValues(alpha: 0.3); size=32; color=subTextColor; width=AppSpacing.lg; style=AppTypography.headingMD .copyWith(color: textColor); color=textColor; padding=const EdgeInsets.symmetric( horizontal: 6, vertical: 2); color=Colors.white.withValues(alpha: 0.2); style=AppTypography.numericSmall .copyWith(color: textColor); height=AppSpacing.lg; width=AppSpacing.xxs; style=AppTypography.bodySM .copyWith(color: subTextColor); size=18; color=Colors.white.withValues(alpha: 0.8); width=double.infinity; color=(isLightBg ? Colors.black : Colors.black).withValues(alpha: 0.15); borderRadius=BorderRadius.circular(AppSpacing.radiusSM); style=AppTypography.bodySM.copyWith( color: textColor.withValues(alpha: 0.9), fontStyle: FontStyle.italic, ); color=textColor.withValues(alpha: 0.9); style=AppTypography.caption.copyWith(color: color); color=color; padding=const EdgeInsets.symmetric(horizontal: AppSpacing.xs); style=AppTypography.caption .copyWith(color: color.withValues(alpha: 0.4)); color=color.withValues(alpha: 0.4)",
   "file": "apps/mobile/lib/shared/widgets/profile/bw_profile_card.dart",
   "usage_count": 1,
   "notes": "BwPressable.card. full TitleRarityBadge.detail, 나머지 list. 주석의 ~100/~200/~320은 코드상 높이 제약이 아님. 매너온도≥36.5일 때 표시. tier 색/gradient 로컬값은 tokens.local_profile에 수록.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-profile-card\"><div class=\"pv-mechanic-scale-frame\" style=\"height:100px\"><div class=\"pv-mechanic-scaled\" style=\"width:125%;transform:scale(0.8)\"><div class=\"pv-mechanic-card pv-mechanic-profile-compact\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-avatar\"><svg aria-hidden=\"true\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-profile-name\">김부왕</div><div class=\"pv-mechanic-profile-level\">Lv.24</div></div><span class=\"pv-mechanic-rarity pv-mechanic-rarity-rare\">꼼꼼한 손길</span></div></div><div class=\"pv-mechanic-profile-stats\"><div class=\"pv-mechanic-stat-chip\">정비 128건</div><div class=\"pv-mechanic-stat-chip\">경력 5년</div></div></div></div></div><div class=\"pv-mechanic-profile-variants\"><div class=\"pv-mechanic-scale-frame\" style=\"height:68px\"><div class=\"pv-mechanic-scaled\" style=\"width:238.0952380952381%;transform:scale(0.42)\"><div class=\"pv-mechanic-profile-mini\"><div class=\"pv-mechanic-avatar\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg></div><div class=\"pv-mechanic-profile-mini-name\">김부왕</div><span class=\"pv-mechanic-muted\">Lv.24 · ★ 4.9</span><span class=\"pv-mechanic-rarity pv-mechanic-rarity-common\">첫 정비</span></div></div></div><div class=\"pv-mechanic-scale-frame\" style=\"height:68px\"><div class=\"pv-mechanic-scaled\" style=\"width:238.0952380952381%;transform:scale(0.42)\"><div class=\"pv-mechanic-profile-full\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-avatar\"><svg aria-hidden=\"true\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-profile-name\">김부왕</div><div class=\"pv-mechanic-profile-level\">Lv.24</div></div><span class=\"pv-mechanic-rarity pv-mechanic-rarity-rare pv-mechanic-rarity-detail\">꼼꼼한 손길</span></div></div><div class=\"pv-mechanic-profile-stats\">정비 128건 · 경력 5년</div><div class=\"pv-mechanic-profile-story\">“정성을 담아 한 대 한 대 정비합니다.”</div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-reservation-card",
   "platform": "mechanic",
   "name": "BwReservationCard",
   "category": "카드",
   "purpose": "예약 시간·상태·번호판·차종·요청 정비항목·고객 정보를 위계 순으로 보여주는 목록 카드.",
   "variants": [
    "warning",
    "info",
    "success",
    "danger",
    "muted",
    "nickname"
   ],
   "props": [
    {
     "name": "time",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "date",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "badgeVariant",
     "type": "BadgeVariant",
     "required": true,
     "default": null
    },
    {
     "name": "badgeLabel",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "vehicleName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "plateNumber",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "nickname",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "mileageLabel",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "items",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "customerName",
     "type": "String?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.statusDanger",
    "AppColors.statusDangerBg",
    "AppColors.statusInfo",
    "AppColors.statusInfoBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusPill",
    "AppSpacing.sm",
    "AppTypography.bodyLG",
    "AppTypography.bodyMD",
    "AppTypography.bodyXS",
    "AppTypography.numericSmall"
   ],
   "sizes": "padding16/radius18/bgWhite/borderDefault1. 시간16/w700 brandPrimary; 날짜11/w400/textMuted/top3/gap10. pill H10/V5/r9999/11/w800. 제목18/w800; 별명 옆 번호판13/w600/gap8; 차량설명12/w400/top3; 항목13/w600 gap10; 고객12/w500 gap10.| 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.all(AppSpacing.lg); color=AppColors.bgWhite; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); border=Border.all(color: AppColors.borderDefault); color=AppColors.borderDefault; style=AppTypography.numericSmall.copyWith( fontSize: 16, color: AppColors.brandPrimary, ); fontSize=16; color=AppColors.brandPrimary; width=AppSpacing.sm; padding=const EdgeInsets.only(top: 3); style=AppTypography.numericSmall.copyWith( fontSize: 11, fontWeight: FontWeight.w400, color: AppColors.textMuted, ); fontSize=11; fontWeight=FontWeight.w400; color=AppColors.textMuted; padding=const EdgeInsets.symmetric( vertical: 5, horizontal: 10, ); color=chipBg; borderRadius=BorderRadius.circular(AppSpacing.radiusPill); maxLines=1; style=AppTypography.bodyXS.copyWith( fontSize: 11, fontWeight: FontWeight.w800, color: chipFg, ); fontWeight=FontWeight.w800; color=chipFg; height=AppSpacing.md; style=AppTypography.bodyLG.copyWith( fontSize: 18, fontWeight: FontWeight.w800, ); fontSize=18; width=8; style=AppTypography.numericSmall.copyWith( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textMuted, ); fontSize=13; fontWeight=FontWeight.w600; style=AppTypography.numericSmall.copyWith( fontSize: 18, fontWeight: FontWeight.w800, ); height=3; style=AppTypography.bodyXS.copyWith( fontSize: 12, fontWeight: FontWeight.w400, color: AppColors.textSecondary, ); fontSize=12; color=AppColors.textSecondary; height=AppSpacing.sm; maxLines=2; style=AppTypography.bodyMD.copyWith( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ); color=AppColors.textPrimary; style=AppTypography.bodyXS.copyWith( fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.textSecondary, ); fontWeight=FontWeight.w500; color=AppColors.statusDanger",
   "file": "apps/mobile/lib/shared/widgets/cards/reservation_card.dart",
   "usage_count": 2,
   "notes": "카드 자체 탭 API 없음 — 상세는 호출부가 리스트에서 감싸 연다. 수락·거절 액션은 카드에서 빠졌다(2026-09, onReject/onAccept 제거 · 예약 액션은 상세 시트로 이동). warning 기본 라벨은 \"대기\"에서 \"확인 필요\"로 바뀌었다. muted 배지 배경은 별도 Color(0xFFEEF2F8), 전경 textSecondary. props 10개 제한으로 생략된 생성자 인자: customerPhone: String? (optional).",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-reservation-card\"><div class=\"pv-mechanic-scale-frame\" style=\"height:133px\"><div class=\"pv-mechanic-scaled\" style=\"width:181.8181818181818%;transform:scale(0.55)\"><div class=\"pv-mechanic-card pv-mechanic-reservation\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-reservation-time\">14:30</div><div class=\"pv-mechanic-reservation-date\">오늘 · 9월 8일</div></div><span class=\"pv-mechanic-badge pv-mechanic-tone-warning\">예약 대기</span></div><div class=\"pv-mechanic-reservation-vehicle\">출근용 PCX <span class=\"pv-mechanic-reservation-plate\">1가2 3456</span></div><div class=\"pv-mechanic-reservation-model\">혼다 PCX125 · 2023년</div><div class=\"pv-mechanic-reservation-item\"><svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg>엔진오일 교체</div><div class=\"pv-mechanic-reservation-customer\"><svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg>김민수 고객님</div><div class=\"pv-mechanic-reservation-actions\"><span class=\"pv-mechanic-button pv-mechanic-reject\">거절</span><span class=\"pv-mechanic-button pv-mechanic-primary\">수락</span></div></div></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-chips\"><span class=\"pv-mechanic-badge pv-mechanic-tone-warning\">대기</span><span class=\"pv-mechanic-badge pv-mechanic-tone-info\">정비 중</span><span class=\"pv-mechanic-badge pv-mechanic-tone-success\">완료</span><span class=\"pv-mechanic-badge pv-mechanic-tone-danger\">거절</span><span class=\"pv-mechanic-badge pv-mechanic-tone-muted\">취소</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-timeline-history-card",
   "platform": "mechanic",
   "name": "BwTimelineHistoryCard",
   "category": "카드",
   "purpose": "정비 날짜·제목·금액·상태를 좌측 레일과 함께 표시하는 타임라인 한 행.",
   "variants": [
    "done",
    "pending",
    "inProgress",
    "cancelled",
    "overdue",
    "first",
    "last",
    "tappable"
   ],
   "props": [
    {
     "name": "date",
     "type": "DateTime",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "price",
     "type": "int?",
     "required": true,
     "default": null
    },
    {
     "name": "status",
     "type": "TimelineEntryStatus",
     "required": true,
     "default": null
    },
    {
     "name": "isFirst",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "isLast",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandGradientEnd",
    "AppColors.brandGradientStart",
    "AppColors.brandPrimary",
    "AppColors.statusError",
    "AppColors.statusErrorBg",
    "AppColors.statusInfo",
    "AppColors.statusInfoBg",
    "AppColors.statusMuted",
    "AppColors.statusMutedBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusPill",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.bodyXS",
    "BwPressable.tintDark"
   ],
   "sizes": "레일폭20/원20×20/흰 border2; 연결선폭2, 상단높이10; 레일-카드gap12. 카드 radius12, borderDefault1.25, padding H16/V12. 날짜 bodyXS12/w700; title14/w700 최대2줄; 가격13/w600/textMuted. pill paddingH10/V4/r9999/bodyXS12/w700. 원 그림자 상태색 alpha0.25/blur4/y1. | 코드 세부 표현식(하위 요소·상태 포함): width=20; width=2; height=AppSpacing.sm; color=AppColors.borderDefault; height=20; shape=BoxShape.circle; gradient=palette.dotGradient; color=palette.dotSolid; border=Border.all( color: AppColors.bgWhite, width: 2, ); color=AppColors.bgWhite; boxShadow=[ BoxShadow( color: (palette.dotSolid ?? AppColors.brandPrimary) .withValues(alpha: 0.25), blurRadius: 4, offset: const Offset(0, 1), ), ]; color=(palette.dotSolid ?? AppColors.brandPrimary) .withValues(alpha: 0.25); blurRadius=4; offset=const Offset(0, 1); width=AppSpacing.md; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); border=Border.all( color: AppColors.borderDefault, width: 1.25, ); width=1.25; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.md, ); style=AppTypography.bodyXS.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.w700, ); color=AppColors.textSecondary; fontWeight=FontWeight.w700; height=AppSpacing.xxs; style=AppTypography.bodyMD.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w700, ); color=AppColors.textPrimary; maxLines=2; height=AppSpacing.xs; style=AppTypography.bodySM.copyWith( color: AppColors.textMuted, ); color=AppColors.textMuted; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.sm, vertical: AppSpacing.xxs, ); color=palette.pillBg; borderRadius=BorderRadius.circular(AppSpacing.radiusPill); style=AppTypography.bodyXS.copyWith( color: palette.pillFg, fontWeight: FontWeight.w700, ); color=palette.pillFg",
   "file": "apps/mobile/lib/shared/widgets/cards/bw_timeline_history_card.dart",
   "usage_count": 0,
   "notes": "onTap이 있을 때만 내부 카드 BwPressable.card. 첫/끝 연결선은 공간 유지하며 숨김. price는 required int?이고 null은 '- · '; epoch0 날짜는 '-'. done은 브랜드 그라데이션 원+성공 pill, pending warning, inProgress info, cancelled muted, overdue error.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-timeline-history-card\"><div class=\"pv-mechanic-scale-frame\" style=\"height:133px\"><div class=\"pv-mechanic-scaled\" style=\"width:161.29032258064515%;transform:scale(0.62)\"><div class=\"pv-mechanic-rail-entry\"><div class=\"pv-mechanic-rail\"><div class=\"pv-mechanic-rail-dot pv-mechanic-tone-success\"></div></div><div class=\"pv-mechanic-rail-card\"><div class=\"pv-mechanic-rail-date\">2026.09.08</div><div class=\"pv-mechanic-rail-title\">엔진오일 교체</div><div class=\"pv-mechanic-rail-bottom\"><div class=\"pv-mechanic-rail-price\">45,000원 ·</div><span class=\"pv-mechanic-badge pv-mechanic-tone-success\">완료</span></div></div></div><div class=\"pv-mechanic-rail-entry\"><div class=\"pv-mechanic-rail pv-mechanic-rail-last\"><div class=\"pv-mechanic-rail-dot pv-mechanic-tone-info\"></div></div><div class=\"pv-mechanic-rail-card\"><div class=\"pv-mechanic-rail-date\">2026.09.08</div><div class=\"pv-mechanic-rail-title\">엔진오일 교체</div><div class=\"pv-mechanic-rail-bottom\"><div class=\"pv-mechanic-rail-price\">45,000원 ·</div><span class=\"pv-mechanic-badge pv-mechanic-tone-info\">정비 중</span></div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-vehicle-banner",
   "platform": "mechanic",
   "name": "BwVehicleBanner",
   "category": "카드",
   "purpose": "차량 사진·번호판·모델·주행거리를 브랜드 배너로 표시한다.",
   "variants": [
    "photo",
    "manufacturer-logo",
    "initial",
    "bike-fallback",
    "subtitle"
   ],
   "props": [
    {
     "name": "plateNumber",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "mileage",
     "type": "int?",
     "required": false,
     "default": null
    },
    {
     "name": "manufacturerName",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "manufacturerNameEn",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "modelName",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "logoUrl",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "vehiclePhotoUrl",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "year",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "nickname",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "showIcon",
     "type": "bool",
     "required": false,
     "default": "true"
    }
   ],
   "tokens": [
    "AppColors.bannerGradient",
    "AppTypography.bodySM"
   ],
   "sizes": "전폭, paddingH18/V16, radius16, bannerGradient; shadow Color(0x471A56B0) blur20 offset(0,8). 로고 박스40×40/r12/white alpha0.18; 이미지34×34/r8, gap14. 번호판15/w700 white; 보조 gap2/12px/w600 Color(0xD9F1F5F9); 이니셜22/w800. | 코드 세부 표현식(하위 요소·상태 포함): width=double.infinity; padding=const EdgeInsets.symmetric(vertical: 16, horizontal: 18); borderRadius=BorderRadius.circular(16); gradient=AppColors.bannerGradient; boxShadow=const [ BoxShadow( color: Color(0x471A56B0), offset: Offset(0, 8), blurRadius: 20, ), ]; color=Color(0x471A56B0); offset=Offset(0, 8); blurRadius=20; width=40; height=40; color=Colors.white.withValues(alpha: 0.18); borderRadius=BorderRadius.circular(12); alignment=Alignment.center; width=14; style=AppTypography.bodySM.copyWith( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 15, ); color=Colors.white; fontWeight=FontWeight.w700; fontSize=15; maxLines=1; height=2; style=AppTypography.bodySM.copyWith( color: const Color(0xD9F1F5F9), fontSize: 12, ); color=const Color(0xD9F1F5F9); fontSize=12; borderRadius=BorderRadius.circular(8); width=34; height=34; style=const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.w800, ); fontSize=22; fontWeight=FontWeight.w800; size=22",
   "file": "apps/mobile/lib/shared/widgets/cards/bw_vehicle_banner.dart",
   "usage_count": 2,
   "notes": "비인터랙티브. 차량사진→제조사 로고→영문/한글 이니셜→Lucide bike. showIcon과 brandColor는 선언되지만 렌더에서 사용하지 않음; showIcon=false도 아이콘 박스 유지. nickname이 모델명보다 우선. props 10개 제한으로 생략된 생성자 인자: brandColor: String? (optional).",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-vehicle-banner\"><div class=\"pv-mechanic-vehicle-banner\"><div class=\"pv-mechanic-vehicle-initial\">H</div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-vehicle-plate\">1가2 3456</div><div class=\"pv-mechanic-vehicle-subtitle\">PCX125 · 12,800km</div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-maintenance-item-card-maintenance-collapsed-card",
   "platform": "mechanic",
   "name": "MaintenanceCollapsedCard",
   "category": "카드",
   "purpose": "정비 항목을 접힌 그라데이션 카드와 완료 표시로 보여준다.",
   "variants": [
    "collapsed",
    "completed",
    "tappable"
   ],
   "props": [
    {
     "name": "categoryName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "brandName",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "productName",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "completed",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandGradientEnd",
    "AppColors.brandPrimary",
    "AppSpacing.btnHeightMD",
    "AppSpacing.iconMD",
    "AppSpacing.iconSM",
    "AppSpacing.lg",
    "AppSpacing.radiusLG",
    "AppSpacing.xl",
    "AppTypography.bodySM",
    "AppTypography.headingMD",
    "BwPressable.tintLight"
   ],
   "sizes": "아래 margin14; paddingH20/V16; radius18; brandPrimary→brandGradientEnd gradient. 원40×40(white alpha0.15)/아이콘18; gap16. 제목 headingMD16/w800 white; 완료 브랜드 설명13/w600/white alpha0.7; 체크20, 펼침 chevron20/white70. | 코드 세부 표현식(하위 요소·상태 포함): margin=const EdgeInsets.only(bottom: 14); borderRadius=BorderRadius.circular(AppSpacing.radiusLG); pressedTint=BwPressable.tintLight; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xl, vertical: AppSpacing.lg, ); gradient=const LinearGradient( colors: [AppColors.brandPrimary, AppColors.brandGradientEnd], ); size=AppSpacing.btnHeightMD; iconSize=AppSpacing.iconSM; width=AppSpacing.lg; style=AppTypography.headingMD.copyWith(color: Colors.white); color=Colors.white; style=AppTypography.bodySM.copyWith( color: Colors.white.withValues(alpha: 0.7), ); color=Colors.white.withValues(alpha: 0.7); size=AppSpacing.iconMD; width=8; color=Colors.white70; size=20",
   "file": "apps/mobile/lib/shared/widgets/cards/maintenance_item_card.dart",
   "usage_count": 1,
   "notes": "BwPressable.card + tintLight. MaintenanceIcons.whiteCircleIcon 사용. 완료+brandName 있을 때 설명, onTap 있을 때 펼침 화살표 표시.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-maintenance-item-card-maintenance-collapsed-card\"><div class=\"pv-mechanic-scale-frame\" style=\"height:72px\"><div class=\"pv-mechanic-scaled\" style=\"width:125%;transform:scale(0.8)\"><div class=\"pv-mechanic-maintenance-collapsed\"><div class=\"pv-mechanic-maintenance-circle\"><svg aria-hidden=\"true\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 7h11v13H3V9h2zM6 3h8v4M16 10l5-3 1 4-6 5M8 14h4\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-maintenance-title\">엔진오일</div><div class=\"pv-mechanic-maintenance-brand\">모튤 · 7100</div></div><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"m7 12 3 3 7-7\"/></svg><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m5 9 7 7 7-7\"/></svg></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-maintenance-item-card-maintenance-expanded-header",
   "platform": "mechanic",
   "name": "MaintenanceExpandedHeader",
   "category": "카드",
   "purpose": "펼친 정비 항목 카드의 접기 가능한 상단 헤더.",
   "variants": [
    "expanded",
    "collapsible"
   ],
   "props": [
    {
     "name": "categoryName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onCollapse",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandGradientEnd",
    "AppColors.brandGradientStart",
    "AppColors.brandPrimary",
    "AppSpacing.btnHeightMD",
    "AppSpacing.iconSM",
    "AppSpacing.lg",
    "AppSpacing.radiusLG",
    "AppSpacing.xl",
    "AppSpacing.xxs",
    "AppTypography.caption",
    "AppTypography.headingLG",
    "BwPressable.tintLight"
   ],
   "sizes": "상단 radius18만 적용; paddingH20/V16; brandPrimary→brandGradientEnd. 원40/아이콘18/white alpha0.15; gap16; 설명 caption12/w500/brandGradientStart, gap4, 제목 headingLG18/w800 white. | 코드 세부 표현식(하위 요소·상태 포함): borderRadius=const BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusLG), ); pressedTint=BwPressable.tintLight; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xl, vertical: AppSpacing.lg, ); gradient=LinearGradient( colors: [AppColors.brandPrimary, AppColors.brandGradientEnd], ); borderRadius=BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusLG), ); size=AppSpacing.btnHeightMD; iconSize=AppSpacing.iconSM; width=AppSpacing.lg; style=AppTypography.caption.copyWith( color: AppColors.brandGradientStart, ); color=AppColors.brandGradientStart; height=AppSpacing.xxs; style=AppTypography.headingLG.copyWith(color: Colors.white); color=Colors.white",
   "file": "apps/mobile/lib/shared/widgets/cards/maintenance_item_card.dart",
   "usage_count": 0,
   "notes": "BwPressable.card + tintLight. body는 호출측 조합. onCollapse=null이면 눌림 비활성.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-maintenance-item-card-maintenance-expanded-header\"><div class=\"pv-mechanic-maintenance-expanded\"><div class=\"pv-mechanic-maintenance-circle\"><svg aria-hidden=\"true\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 7h11v13H3V9h2zM6 3h8v4M16 10l5-3 1 4-6 5M8 14h4\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-maintenance-eyebrow\">정비 항목</div><div class=\"pv-mechanic-title\">엔진오일</div></div><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m5 15 7-7 7 7\"/></svg></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-back-header",
   "platform": "mechanic",
   "name": "BwBackHeader",
   "category": "내비",
   "purpose": "뒤로가기와 제목·선택적 우측 콘텐츠를 표시하는 헤더.",
   "variants": [
    "default",
    "trailing",
    "custom-back"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "trailing",
     "type": "Widget?",
     "required": false,
     "default": null
    },
    {
     "name": "onBack",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.textPrimary",
    "BwPressable.tintDark"
   ],
   "sizes": "padding L12/T6/R12/B10; 뒤로가기40×40/아이콘20; 제목 TextStyle18/w800/textPrimary(폰트패밀리 미지정); trailing right padding10. | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.fromLTRB(12, 6, 12, 10); width=40; height=40; size=20; color=AppColors.textPrimary; style=const TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: AppColors.textPrimary, ); fontSize=18; fontWeight=FontWeight.w800; padding=const EdgeInsets.only(right: 10)",
   "file": "apps/mobile/lib/shared/widgets/headers/back_header.dart",
   "usage_count": 37,
   "notes": "BwPressable.icon. 기본 뒤로가기: canPop이면 pop, 아니면 /dashboard로 go.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-back-header\"><div class=\"pv-mechanic-back-bar\"><div class=\"pv-mechanic-back-hit\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m15 5-7 7 7 7\"/></svg></div><div class=\"pv-mechanic-back-title\">정비카드</div><div class=\"pv-mechanic-back-trailing\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bottom-nav-bar",
   "platform": "mechanic",
   "name": "BwBottomNavBar",
   "category": "내비",
   "purpose": "홈·정비 이력·차량 관리·부왕장터·설정의 하단 탐색 탭.",
   "variants": [
    "active",
    "inactive",
    "compact-large-text"
   ],
   "props": [
    {
     "name": "currentIndex",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "ValueChanged<int>",
     "required": true,
     "default": null
    },
    {
     "name": "itemKeys",
     "type": "List<GlobalKey?>",
     "required": false,
     "default": "const []"
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.bgSurfaceNeutral",
    "AppColors.bgWhite",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppSpacing.md",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.displayNavActive",
    "AppTypography.displayNavInactive"
   ],
   "sizes": "높이=max(80,ceil(2×Vpadding+22+4+실측라벨높이)+2), SafeArea bottom 별도. 상단radius20/border bgSurfaceNeutral1/bgWhite. 탭 paddingH12/V8(폭<600 및 textScale>1.3이면H4/V4), radius16; 활성bgSurface/brandPrimary, 비활성transparent/textMuted. 아이콘20(차량22), gap4; 라벨10/w700/ls0.5/height1.5. | 코드 세부 표현식(하위 요소·상태 포함): height=navigationHeight; color=AppColors.bgWhite; border=Border( top: BorderSide(color: AppColors.bgSurfaceNeutral, width: 1), ); color=AppColors.bgSurfaceNeutral; width=1; borderRadius=BorderRadius.vertical(top: Radius.circular(20)); color=Colors.transparent; letterSpacing=0.5; style=labelStyle; textAlign=TextAlign.center; maxWidth=labelWidth.clamp(1.0, double.infinity); padding=EdgeInsets.symmetric( vertical: compactLargeText ? AppSpacing.xxs : AppSpacing.xs, horizontal: compactLargeText ? AppSpacing.xxs : AppSpacing.md, ); color=isActive ? AppColors.bgSurface : Colors.transparent; borderRadius=BorderRadius.circular(16); size=iconSize; color=color; height=AppSpacing.xxs; style=(isActive ? AppTypography.displayNavActive : AppTypography.displayNavInactive) .copyWith(letterSpacing: 0.5)",
   "file": "apps/mobile/lib/shared/widgets/navigation/bottom_nav_bar.dart",
   "usage_count": 1,
   "notes": "Material+InkWell, BwPressable 없음. TextPainter로 줄바꿈 높이 실측; itemKeys는 탭 Material에 적용.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bottom-nav-bar\"><div class=\"pv-mechanic-scale-frame\" style=\"height:48px\"><div class=\"pv-mechanic-scaled\" style=\"width:166.66666666666669%;transform:scale(0.6)\"><div class=\"pv-mechanic-nav\"><div class=\"pv-mechanic-nav-cell\"><div class=\"pv-mechanic-nav-tab pv-mechanic-nav-active\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 10 9-8 9 8v11h-6v-7H9v7H3z\"/></svg><div class=\"pv-mechanic-nav-label\">홈</div></div></div><div class=\"pv-mechanic-nav-cell\"><div class=\"pv-mechanic-nav-tab\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 6v6l4 2\"/></svg><div class=\"pv-mechanic-nav-label\">정비 이력</div></div></div><div class=\"pv-mechanic-nav-cell\"><div class=\"pv-mechanic-nav-tab\"><svg aria-hidden=\"true\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"5\" cy=\"17\" r=\"4\"/><circle cx=\"19\" cy=\"17\" r=\"4\"/><path d=\"M5 17 9 9h6l4 8M5 17h8l-4-8M13 5h3l2 6M6 9h4\"/></svg><div class=\"pv-mechanic-nav-label\">차량 관리</div></div></div><div class=\"pv-mechanic-nav-cell\"><div class=\"pv-mechanic-nav-tab\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 10v11h18V10M2 10l2-7h16l2 7M8 21v-7h8v7M2 10q3 4 5 0 2 4 5 0 2 4 5 0 2 4 5 0\"/></svg><div class=\"pv-mechanic-nav-label\">부왕장터</div></div></div><div class=\"pv-mechanic-nav-cell\"><div class=\"pv-mechanic-nav-tab\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"m9 2 6 0 1 3 3 1 3 4-2 3 0 3-4 4-3-1-3 1-4-4 0-3-2-3 3-4 3-1z\"/></svg><div class=\"pv-mechanic-nav-label\">설정</div></div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-settings-menu-item",
   "platform": "mechanic",
   "name": "BwSettingsMenuItem",
   "category": "내비",
   "purpose": "설정 메뉴의 아이콘·라벨·보조 콘텐츠·셰브론 행.",
   "variants": [
    "default",
    "no-border",
    "trailing",
    "custom-icon-color",
    "disabled"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "showBorder",
     "type": "bool",
     "required": false,
     "default": "true"
    },
    {
     "name": "trailing",
     "type": "Widget?",
     "required": false,
     "default": null
    },
    {
     "name": "iconColor",
     "type": "Color?",
     "required": false,
     "default": null
    },
    {
     "name": "iconBgColor",
     "type": "Color?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.bgSurfaceNeutral",
    "AppColors.brandPrimary",
    "AppSpacing.iconMD",
    "AppSpacing.iconSM",
    "AppTypography.displayMenuItem",
    "BwPressable.tintDark"
   ],
   "sizes": "paddingH16/V15, 각 gap13; 아이콘박스34×34/r10/bgSurface/아이콘20 brandPrimary. 라벨 displayMenuItem14/w700/height1.5; 하단 bgSurfaceNeutral border; 셰브론18 Color(0xFFCBD5E1). | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.symmetric(horizontal: 16, vertical: 15); border=Border( bottom: BorderSide(color: AppColors.bgSurfaceNeutral), ); color=AppColors.bgSurfaceNeutral; width=34; height=34; color=iconBgColor ?? AppColors.bgSurface; borderRadius=BorderRadius.circular(10); size=AppSpacing.iconMD; color=iconColor ?? AppColors.brandPrimary; width=13; style=AppTypography.displayMenuItem.copyWith(fontSize: 14); fontSize=14; size=AppSpacing.iconSM; color=Color(0xFFCBD5E1)",
   "file": "apps/mobile/lib/shared/widgets/forms/settings_menu_item.dart",
   "usage_count": 2,
   "notes": "BwPressable.card. showBorder=false는 구분선 제거; trailing이 있어도 셰브론은 항상 표시.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-settings-menu-item\"><div class=\"pv-mechanic-settings-row\"><div class=\"pv-mechanic-settings-icon\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8a6 6 0 0 0-12 0c0 8-3 8-3 10h18c0-2-3-2-3-10M10 21h4\"/></svg></div><div class=\"pv-mechanic-grow\">알림 설정</div><svg aria-hidden=\"true\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 5 7 7-7 7\"/></svg></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-settings-row\"><div class=\"pv-mechanic-settings-icon\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 10v11h18V10M2 10l2-7h16l2 7M8 21v-7h8v7M2 10q3 4 5 0 2 4 5 0 2 4 5 0 2 4 5 0\"/></svg></div><div class=\"pv-mechanic-grow\">정비소 정보</div><span class=\"pv-mechanic-muted\">부왕 정비소</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-top-app-bar-logo",
   "platform": "mechanic",
   "name": "BwTopAppBarLogo",
   "category": "내비",
   "purpose": "BUWANG 텍스트 로고와 알림·프로필 액션을 표시한다.",
   "variants": [
    "avatar",
    "fallback-avatar",
    "disabled-actions"
   ],
   "props": [
    {
     "name": "avatarUrl",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onProfileTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "onNotificationTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgPrimary",
    "AppColors.bgSurface",
    "AppColors.borderDefault",
    "AppColors.borderLight",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.sm",
    "AppTypography.displayLogo",
    "BwPressable.tintDark"
   ],
   "sizes": "전폭/paddingH16/V12/bgPrimary/bottom borderLight1. two_wheeler28+gap10+displayLogo20/w900/ls-1/height1.4. 알림24, gap10, avatar radius18(지름36); shadow borderDefault blur4, fallback person20. | 코드 세부 표현식(하위 요소·상태 포함): width=double.infinity; padding=EdgeInsets.symmetric( vertical: AppSpacing.md, horizontal: AppSpacing.lg, ); color=AppColors.bgPrimary; border=Border( bottom: BorderSide(color: AppColors.borderLight, width: 1), ); color=AppColors.borderLight; width=1; size=28; color=AppColors.brandPrimary; width=AppSpacing.sm; style=AppTypography.displayLogo; color=AppColors.textSecondary; size=24; shape=BoxShape.circle; color=AppColors.bgSurface; boxShadow=[ BoxShadow(color: AppColors.borderDefault, blurRadius: 4), ]; color=AppColors.borderDefault; blurRadius=4; radius=18; backgroundColor=AppColors.bgSurface; color=AppColors.textMuted; size=20",
   "file": "apps/mobile/lib/shared/widgets/headers/top_app_bar_logo.dart",
   "usage_count": 1,
   "notes": "알림/프로필 BwPressable.icon. 상단은 로고 이미지 대신 Material Symbols.two_wheeler 사용. avatarUrl이 null일 때만 person 표시.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-top-app-bar-logo\"><div class=\"pv-mechanic-scale-frame\" style=\"height:50px\"><div class=\"pv-mechanic-scaled\" style=\"width:125%;transform:scale(0.8)\"><div class=\"pv-mechanic-logo-bar\"><div class=\"pv-mechanic-logo-group\"><svg aria-hidden=\"true\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"5\" cy=\"17\" r=\"4\"/><circle cx=\"19\" cy=\"17\" r=\"4\"/><path d=\"M5 17 9 9h6l4 8M5 17h8l-4-8M13 5h3l2 6M6 9h4\"/></svg><div class=\"pv-mechanic-wordmark\">BUWANG</div></div><div class=\"pv-mechanic-logo-group\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8a6 6 0 0 0-12 0c0 8-3 8-3 10h18c0-2-3-2-3-10M10 21h4\"/></svg><div class=\"pv-mechanic-avatar\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg></div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-empty-state",
   "platform": "mechanic",
   "name": "BwEmptyState",
   "category": "피드백",
   "purpose": "데이터가 없는 화면에 안내와 선택적 액션을 표시한다.",
   "variants": [
    "basic",
    "description",
    "with-action"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "primaryActionLabel",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onPrimaryAction",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.lg",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppTypography.bodyMD",
    "AppTypography.headingMD",
    "BwPressable.tintDark"
   ],
   "sizes": "중앙/padding24; 아이콘원72/bgSurface/아이콘48textMuted; gap16/title headingMD16w800 최대2줄; 설명gap8/bodyMD14w400textSecondary 최대2줄; CTA위20/minWidth160/minHeight50, BwOutlineButton 실제높이50/r18/라벨20w700. | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, vertical: AppSpacing.xxl, ); width=72; height=72; color=AppColors.bgSurface; shape=BoxShape.circle; alignment=Alignment.center; size=48; color=AppColors.textMuted; height=AppSpacing.lg; style=AppTypography.headingMD; textAlign=TextAlign.center; maxLines=2; height=AppSpacing.xs; style=AppTypography.bodyMD.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ); color=AppColors.textSecondary; fontWeight=FontWeight.normal; height=AppSpacing.xl; minHeight=AppSpacing.btnHeightLG; minWidth=160",
   "file": "apps/mobile/lib/shared/widgets/states/bw_empty_state.dart",
   "usage_count": 10,
   "notes": "CTA는 label과 callback이 모두 있을 때만 표시. BwOutlineButton→PressableButton→BwPressable.button. 주석의48높이 대신 실제50.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-empty-state\"><div class=\"pv-mechanic-scale-frame\" style=\"height:137px\"><div class=\"pv-mechanic-scaled\" style=\"width:208.33333333333334%;transform:scale(0.48)\"><div class=\"pv-mechanic-state\"><div class=\"pv-mechanic-empty-icon\"><svg aria-hidden=\"true\" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg></div><div class=\"pv-mechanic-state-title\">아직 정비 내역이 없어요</div><div class=\"pv-mechanic-state-description\">첫 정비를 기록해 보세요.</div><div class=\"pv-mechanic-state-action\"><span class=\"pv-mechanic-button pv-mechanic-outline\">정비 등록</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-error-state",
   "platform": "mechanic",
   "name": "BwErrorState",
   "category": "피드백",
   "purpose": "로드 실패 메시지와 필수 재시도 버튼을 표시한다.",
   "variants": [
    "basic",
    "description",
    "retry"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": false,
     "default": "Icons.error_outline"
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onRetry",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "retryLabel",
     "type": "String",
     "required": false,
     "default": "'다시 시도'"
    }
   ],
   "tokens": [
    "AppColors.statusError",
    "AppColors.textSecondary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.lg",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppTypography.bodyMD",
    "AppTypography.headingMD",
    "BwPressable.tintDark"
   ],
   "sizes": "중앙/padding24; 에러아이콘48statusError; gap16/title headingMD16w800 최대2줄; 설명gap8/bodyMD14w400textSecondary 최대3줄; CTA위20/maxWidth240/minHeight50, 내부 PrimaryCtaButton 실제52/r16. | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, vertical: AppSpacing.xxl, ); size=48; color=AppColors.statusError; height=AppSpacing.lg; style=AppTypography.headingMD; textAlign=TextAlign.center; maxLines=2; height=AppSpacing.xs; style=AppTypography.bodyMD.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ); color=AppColors.textSecondary; fontWeight=FontWeight.normal; maxLines=3; height=AppSpacing.xl; minHeight=AppSpacing.btnHeightLG; maxWidth=240",
   "file": "apps/mobile/lib/shared/widgets/states/bw_error_state.dart",
   "usage_count": 15,
   "notes": "onRetry 필수. CTA는 BwPressable.button을 간접 사용. 주석의48높이 대신 실제52.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-error-state\"><div class=\"pv-mechanic-scale-frame\" style=\"height:130px\"><div class=\"pv-mechanic-scaled\" style=\"width:200%;transform:scale(0.5)\"><div class=\"pv-mechanic-state pv-mechanic-error-state\"><div class=\"pv-mechanic-error-icon\"><svg aria-hidden=\"true\" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m12 3 10 18H2zM12 9v5M12 17h.01\"/></svg></div><div class=\"pv-mechanic-state-title\">정비 내역을 불러오지 못했어요</div><div class=\"pv-mechanic-state-description\">연결을 확인하고 다시 시도해 주세요.</div><div class=\"pv-mechanic-state-action\"><span class=\"pv-mechanic-button pv-mechanic-primary\">다시 시도</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-info-banner",
   "platform": "mechanic",
   "name": "BwInfoBanner",
   "category": "피드백",
   "purpose": "상태별 안내 메시지와 선택적 액션·닫기를 표시한다.",
   "variants": [
    "info",
    "success",
    "warning",
    "error",
    "action",
    "dismiss"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "actionLabel",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onAction",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "onDismiss",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "variant",
     "type": "BwInfoBannerVariant",
     "required": false,
     "default": "BwInfoBannerVariant.info"
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.brandPrimary",
    "AppColors.statusError",
    "AppColors.statusErrorBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.iconLG",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusSM",
    "AppSpacing.xs",
    "AppTypography.bodyMD",
    "AppTypography.bodySM"
   ],
   "sizes": "padding L16/T12/R8/B12/r12; variant 색 border alpha0.3/width1. info bgSurface+brandPrimary, 그외 status*Bg+status*. 아이콘24/gap12; title14w600 최대2줄, description gap2/13w400textSecondary 최대3줄. 액션·닫기50×50/r16, 닫기아이콘20. | 코드 세부 표현식(하위 요소·상태 포함): border=AppColors.brandPrimary; border=AppColors.statusSuccess; border=AppColors.statusWarning; border=AppColors.statusError; color=palette.background; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); border=Border.all( color: palette.border.withValues(alpha: 0.3), width: 1, ); color=palette.border.withValues(alpha: 0.3); width=1; padding=const EdgeInsets.fromLTRB( AppSpacing.lg, AppSpacing.md, AppSpacing.xs, AppSpacing.md, ); size=AppSpacing.iconLG; color=palette.icon; width=AppSpacing.md; style=AppTypography.bodyMD; maxLines=2; height=2; style=AppTypography.bodySM.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ); color=AppColors.textSecondary; fontWeight=FontWeight.normal; maxLines=3; width=AppSpacing.btnHeightLG; height=AppSpacing.btnHeightLG; color=Colors.transparent; borderRadius=BorderRadius.circular(AppSpacing.radiusSM); style=AppTypography.bodySM.copyWith( color: palette.border, ); color=palette.border; maxLines=1; size=AppSpacing.iconMD; color=AppColors.textMuted",
   "file": "apps/mobile/lib/shared/widgets/states/bw_info_banner.dart",
   "usage_count": 1,
   "notes": "Material+InkWell, Semantics(button:true). action은 label+callback 모두 있어야 표시. 주석의48×48 대신 btnHeightLG=50 적용. description·닫기는 각각 optional.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-info-banner\"><div class=\"pv-mechanic-info-banner pv-mechanic-info-info\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v6M12 7h.01\"/></svg><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-info-title\">예약이 확정됐어요</div><div class=\"pv-mechanic-info-description\">오늘 14:30 · PCX125</div></div><div class=\"pv-mechanic-info-dismiss\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 6 12 12M18 6 6 18\"/></svg></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-info-variants\"><div class=\"pv-mechanic-info-banner pv-mechanic-info-success\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"m7 12 3 3 7-7\"/></svg><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-info-title\">정비 완료</div></div></div><div class=\"pv-mechanic-info-banner pv-mechanic-info-warning\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m12 3 10 18H2zM12 9v5M12 17h.01\"/></svg><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-info-title\">교체 임박</div></div></div><div class=\"pv-mechanic-info-banner pv-mechanic-info-error\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v6M12 7h.01\"/></svg><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-info-title\">저장 실패</div></div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-loading-state",
   "platform": "mechanic",
   "name": "BwLoadingState",
   "category": "피드백",
   "purpose": "화면 중앙에 로딩 표시와 선택적 메시지를 보여준다.",
   "variants": [
    "spinner-only",
    "message"
   ],
   "props": [
    {
     "name": "message",
     "type": "String?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppSpacing.lg",
    "AppSpacing.xxl",
    "AppTypography.bodySM"
   ],
   "sizes": "padding24/중앙정렬; CircularProgressIndicator brandPrimary/stroke3; 메시지위16/bodySM13w600textMuted/중앙텍스트. | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.all(AppSpacing.xxl); color=AppColors.brandPrimary; strokeWidth=3; height=AppSpacing.lg; style=AppTypography.bodySM.copyWith( color: AppColors.textMuted, ); color=AppColors.textMuted; textAlign=TextAlign.center",
   "file": "apps/mobile/lib/shared/widgets/states/bw_loading_state.dart",
   "usage_count": 3,
   "notes": "비인터랙티브. 스피너 지름은 코드에 미지정.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-loading-state\"><div class=\"pv-mechanic-loading-state\"><span class=\"pv-mechanic-spinner\" style=\"width:36px;height:36px\" aria-label=\"처리 중\"></span><div class=\"pv-mechanic-loading-message\">정비 내역을 불러오는 중이에요</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-offline-banner",
   "platform": "mechanic",
   "name": "BwOfflineBanner",
   "category": "피드백",
   "purpose": "네트워크가 끊겼을 때 빨간 연결 없음 배너를 표시한다.",
   "variants": [
    "online-hidden",
    "offline"
   ],
   "props": [
    {
     "name": "topPadding",
     "type": "double",
     "required": false,
     "default": "0"
    }
   ],
   "tokens": [
    "AppColors.statusError",
    "AppSpacing.md",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppTypography.bodySM"
   ],
   "sizes": "전폭/statusError; paddingL12/R12/T(topPadding+10)/B10, 기본 topPadding0. wifi_off16/white/gap8; bodySM13w600white. | 코드 세부 표현식(하위 요소·상태 포함): width=double.infinity; padding=EdgeInsets.only( left: AppSpacing.md, right: AppSpacing.md, top: topPadding + AppSpacing.sm, bottom: AppSpacing.sm, ); color=AppColors.statusError; size=16; color=Colors.white; width=AppSpacing.xs; style=AppTypography.bodySM.copyWith( color: Colors.white, fontWeight: FontWeight.w600, ); fontWeight=FontWeight.w600",
   "file": "apps/mobile/lib/shared/widgets/states/bw_offline_banner.dart",
   "usage_count": 1,
   "notes": "ConsumerWidget가 connectivityProvider 관찰; 상태 미확정은 online으로 취급하여 숨김. 탭 액션 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-offline-banner\"><div class=\"pv-mechanic-offline\"><svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 3 18 18M2 9a16 16 0 0 1 4-3M10 5a16 16 0 0 1 12 4M5 12a11 11 0 0 1 4-2M14 10a11 11 0 0 1 5 2M8 15a6 6 0 0 1 8 0M12 19h.01\"/></svg>인터넷 연결을 확인해 주세요</div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-scroll-more-hint",
   "platform": "mechanic",
   "name": "BwScrollMoreHint",
   "category": "피드백",
   "purpose": "스크롤 아래쪽에 남은 콘텐츠가 있음을 화살표 배지로 알린다.",
   "variants": [
    "has-more",
    "at-bottom",
    "no-scroll",
    "reduced-motion"
   ],
   "props": [
    {
     "name": "builder",
     "type": "Widget Function(BuildContext context, ScrollController controller)",
     "required": true,
     "default": null
    },
    {
     "name": "bottomInset",
     "type": "double",
     "required": false,
     "default": "10"
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary"
   ],
   "sizes": "배지30×30/원형/bgWhite/borderDefault1; 화살표22 brandPrimary; shadow Color(0x2608132A)/blur10/y3. bottomInset10 기본; 끝 여유12. opacity0↔1/180ms(애니메이션 비활성 설정시0). | 코드 세부 표현식(하위 요소·상태 포함): duration=duration; opacity=_hasMore ? 1.0 : 0.0; width=30; height=30; color=AppColors.bgWhite; shape=BoxShape.circle; border=Border.all(color: AppColors.borderDefault); color=AppColors.borderDefault; boxShadow=const [ BoxShadow( color: Color(0x2608132A), blurRadius: 10, offset: Offset(0, 3), ), ]; color=Color(0x2608132A); blurRadius=10; offset=Offset(0, 3); size=22; color=AppColors.brandPrimary",
   "file": "apps/mobile/lib/shared/widgets/indicators/bw_scroll_more_hint.dart",
   "usage_count": 1,
   "notes": "IgnorePointer로 탭 통과. builder가 전달받은 ScrollController를 반드시 연결해야 함. ScrollMetricsNotification으로 크기 변경 반영.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-scroll-more-hint\"><div class=\"pv-mechanic-scroll-window\"><div class=\"pv-mechanic-scroll-content\"><div class=\"pv-mechanic-scroll-item\">엔진오일 교체 · 45,000원</div><div class=\"pv-mechanic-scroll-item\">브레이크 패드 · 32,000원</div><div class=\"pv-mechanic-scroll-item\">타이어 교체 · 78,000원</div></div><div class=\"pv-mechanic-scroll-hint\"><svg aria-hidden=\"true\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m5 9 7 7 7-7\"/></svg></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-toast-message",
   "platform": "mechanic",
   "name": "BwToastMessage",
   "category": "피드백",
   "purpose": "성공·오류·경고 메시지를 하단 플로팅 토스트로 표시한다.",
   "variants": [
    "success",
    "error",
    "warning"
   ],
   "props": [
    {
     "name": "context",
     "type": "BuildContext",
     "required": true,
     "default": null
    },
    {
     "name": "message",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "variant",
     "type": "ToastVariant",
     "required": false,
     "default": "ToastVariant.success"
    }
   ],
   "tokens": [
    "AppColors.statusError",
    "AppColors.statusErrorBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppSpacing.iconMD",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.xl",
    "AppTypography.bodyMD"
   ],
   "sizes": "위치bottom100/left16/right16; paddingH20/V14/radius12/border1; variant별 status*Bg 배경/status* 테두리·아이콘·텍스트. 아이콘20/gap12/bodyMD14w600. 진입 offset(1,0)→0/300ms easeOutCubic. | 코드 세부 표현식(하위 요소·상태 포함): border=border; color=color; duration=const Duration(milliseconds: 300); curve=Curves.easeOutCubic; color=Colors.transparent; padding=const EdgeInsets.symmetric( vertical: 14, horizontal: AppSpacing.xl, ); color=widget.bg; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); border=Border.all(color: widget.border); color=widget.border; size=AppSpacing.iconMD; color=widget.color; width=AppSpacing.md; style=AppTypography.bodyMD.copyWith( fontWeight: FontWeight.w600, color: widget.color, ); fontWeight=FontWeight.w600",
   "file": "apps/mobile/lib/shared/widgets/overlays/toast_message.dart",
   "usage_count": 34,
   "notes": "private 생성자; props=static show. OverlayEntry 직접 삽입, 기존 토스트 즉시 제거. 2초 후 reverse, 3초 failsafe 제거. 클릭 액션/BwPressable 없음. 메시지는 sentenceLines()로 문장 경계에서 줄을 나눠 렌더한다(고아 어절 방지). props 배열은 Widget 생성자가 아닌 static show API를 기록한 예외이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-toast-message\"><div class=\"pv-mechanic-toast pv-mechanic-tone-success\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"m7 12 3 3 7-7\"/></svg><div class=\"pv-mechanic-grow\">정비 내역이 저장됐어요</div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-toast pv-mechanic-tone-danger\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v6M12 7h.01\"/></svg><div class=\"pv-mechanic-grow\">저장 실패</div></div><div class=\"pv-mechanic-toast pv-mechanic-tone-warning\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m12 3 10 18H2zM12 9v5M12 17h.01\"/></svg><div class=\"pv-mechanic-grow\">입력 확인</div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-xp-toast",
   "platform": "mechanic",
   "name": "XpToast",
   "category": "피드백",
   "purpose": "XP 획득과 레벨 상승을 상단 애니메이션 토스트로 알린다.",
   "variants": [
    "xp",
    "level-up"
   ],
   "props": [
    {
     "name": "context",
     "type": "BuildContext",
     "required": true,
     "default": null
    },
    {
     "name": "xp",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "levelUp",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "newLevel",
     "type": "int?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandGradientEnd",
    "AppColors.brandGradientStart"
   ],
   "sizes": "top=안전영역padding.top+60/좌우0/중앙. XP pill H16/V8/r20/black87, bolt20/gap4/라벨16w800 #FFD700; shadow gold alpha0.4 blur12 spread1. 레벨배너 H24/V12/r16/브랜드gradient; shadowEnd alpha0.5 blur20 spread2; LEVEL UP 20w900ls2, level28w900 gold, XP13 white alpha0.8. | 코드 세부 표현식(하위 요소·상태 포함): duration=Duration(milliseconds: widget.levelUp ? 2500 : 1500); curve=Curves.easeOut; opacity=_opacity; padding=const EdgeInsets.symmetric(horizontal: 16, vertical: 8); color=Colors.black87; borderRadius=BorderRadius.circular(20); boxShadow=[ BoxShadow( color: const Color(0xFFFFD700).withValues(alpha: 0.4), blurRadius: 12, spreadRadius: 1, ), ]; color=const Color(0xFFFFD700).withValues(alpha: 0.4); blurRadius=12; spreadRadius=1; color=Color(0xFFFFD700); size=20; width=4; style=const TextStyle( color: Color(0xFFFFD700), fontSize: 16, fontWeight: FontWeight.w800, ); fontSize=16; fontWeight=FontWeight.w800; padding=const EdgeInsets.symmetric(horizontal: 24, vertical: 12); gradient=const LinearGradient( colors: [AppColors.brandGradientStart, AppColors.brandGradientEnd], ); borderRadius=BorderRadius.circular(16); boxShadow=[ BoxShadow( color: AppColors.brandGradientEnd.withValues(alpha: 0.5), blurRadius: 20, spreadRadius: 2, ), ]; color=AppColors.brandGradientEnd.withValues(alpha: 0.5); blurRadius=20; spreadRadius=2; style=TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 2, ); color=Colors.white; fontSize=20; fontWeight=FontWeight.w900; letterSpacing=2; height=4; style=const TextStyle( color: Color(0xFFFFD700), fontSize: 28, fontWeight: FontWeight.w900, ); fontSize=28; style=TextStyle( color: Colors.white.withValues(alpha: 0.8), fontSize: 13, ); color=Colors.white.withValues(alpha: 0.8); fontSize=13",
   "file": "apps/mobile/lib/shared/widgets/overlays/xp_toast.dart",
   "usage_count": 1,
   "notes": "props는 실사용 static show 인자(클래스는 기본 암묵 생성자만 존재). OverlayEntry; 일반1500ms/레벨2500ms, opacity 단계weight20/50/30, slide(0,0.3)→(0,-0.2), easeOut. 레벨상승 heavyImpact; newLevel=null이면0. 중복 토스트 제거 로직 없음. props 배열은 Widget 생성자가 아닌 static show API를 기록한 예외이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-xp-toast\"><div class=\"pv-mechanic-xp-stage\"><div class=\"pv-mechanic-xp-pill\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m13 2-9 12h7l-1 8 10-13h-7z\"/></svg>+50 XP</div><div class=\"pv-mechanic-xp-level\"><div class=\"pv-mechanic-xp-heading\">LEVEL UP!</div><div class=\"pv-mechanic-xp-number\">Lv. 24</div><div class=\"pv-mechanic-xp-caption\">정비 완료 · +50 XP</div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bottom-sheet-menu-bw-bottom-sheet-menu",
   "platform": "mechanic",
   "name": "BwBottomSheetMenu",
   "category": "오버레이",
   "purpose": "제목과 아이콘 메뉴 목록을 모달 하단 시트로 표시한다.",
   "variants": [
    "with-title",
    "without-title",
    "menu-items"
   ],
   "props": [
    {
     "name": "context",
     "type": "BuildContext",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "items",
     "type": "List<BwBottomSheetMenuItem>",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderLight",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppSpacing.iconLG",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusXL",
    "AppSpacing.xl",
    "AppTypography.bodyLG",
    "AppTypography.headingLG",
    "BwPressable.tintDark"
   ],
   "sizes": "bgWhite/상단radius20/SafeArea; 상단gap12; 제목행H20/headingLG18/w800/닫기24; 제목 있으면 뒤gap20. 항목 padding16/bottom borderLight1; 아이콘20/textSecondary; gap12/라벨bodyLG15/w500/textPrimary. | 코드 세부 표현식(하위 요소·상태 포함): backgroundColor=AppColors.bgWhite; shape=const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusXL), ), ); borderRadius=BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusXL), ); height=AppSpacing.md; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xl, ); style=AppTypography.headingLG; size=AppSpacing.iconLG; color=AppColors.textMuted; height=AppSpacing.xl; padding=const EdgeInsets.all(AppSpacing.lg); border=Border( bottom: BorderSide(color: AppColors.borderLight), ); color=AppColors.borderLight; size=AppSpacing.iconMD; color=item.iconColor ?? AppColors.textSecondary; width=AppSpacing.md; style=AppTypography.bodyLG.copyWith( fontWeight: FontWeight.w500, color: item.textColor ?? AppColors.textPrimary, ); fontWeight=FontWeight.w500; color=item.textColor ?? AppColors.textPrimary",
   "file": "apps/mobile/lib/shared/widgets/overlays/bottom_sheet_menu.dart",
   "usage_count": 1,
   "notes": "생성자는 private이고 props는 static show의 인자. showModalBottomSheet; 닫기는 BwPressable.icon, 항목은 InkWell. 항목 탭은 먼저 Navigator.pop 후 callback; callback=null도 시트 닫힘. ListView shrinkWrap+NeverScrollable. props 배열은 Widget 생성자가 아닌 static show API를 기록한 예외이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bottom-sheet-menu-bw-bottom-sheet-menu\"><div class=\"pv-mechanic-scale-frame\" style=\"height:139px\"><div class=\"pv-mechanic-scaled\" style=\"width:153.84615384615384%;transform:scale(0.65)\"><div class=\"pv-mechanic-sheet\"><div class=\"pv-mechanic-sheet-header\"><div class=\"pv-mechanic-title pv-mechanic-grow\">정비카드 관리</div><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 6 12 12M18 6 6 18\"/></svg></div><div class=\"pv-mechanic-menu-item\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg><div class=\"pv-mechanic-grow\">정비 내역 수정</div></div><div class=\"pv-mechanic-menu-item\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 6v6l4 2\"/></svg><div class=\"pv-mechanic-grow\">예약 정보 보기</div></div><div class=\"pv-mechanic-menu-item pv-mechanic-menu-danger\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 6 12 12M18 6 6 18\"/></svg><div class=\"pv-mechanic-grow\">정비카드 삭제</div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-confirm-dialog",
   "platform": "mechanic",
   "name": "BwConfirmDialog",
   "category": "오버레이",
   "purpose": "질문 아이콘과 설명·확인·취소를 표시하는 공용 확인 팝업.",
   "variants": [
    "title-only",
    "description",
    "confirm-disabled"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "confirmLabel",
     "type": "String",
     "required": false,
     "default": "'확인'"
    },
    {
     "name": "cancelLabel",
     "type": "String",
     "required": false,
     "default": "'취소'"
    },
    {
     "name": "onConfirm",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "onCancel",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textTertiary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.iconLG",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusXL",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppTypography.bodyMD",
    "AppTypography.buttonSM",
    "AppTypography.headingLG",
    "BwPressable.tintDark",
    "BwPressable.tintLight"
   ],
   "sizes": "Dialog transparent; 본체padding24/bgWhite/radius20. 질문원56×56/bgSecondary/아이콘24 brandPrimary; gap20/제목headingLG18w800/설명gap8/bodyMD14w400textMuted/액션위20. 버튼높이50/radius18/gap12, 취소borderDefault1.5, 주brandPrimary/buttonSM13w700. | 코드 세부 표현식(하위 요소·상태 포함): backgroundColor=Colors.transparent; padding=const EdgeInsets.all(AppSpacing.xxl); color=AppColors.bgWhite; borderRadius=BorderRadius.circular(AppSpacing.radiusXL); width=56; height=56; color=AppColors.bgSecondary; shape=BoxShape.circle; size=AppSpacing.iconLG; color=AppColors.brandPrimary; height=AppSpacing.xl; style=AppTypography.headingLG; textAlign=TextAlign.center; height=AppSpacing.xs; style=AppTypography.bodyMD.copyWith( fontWeight: FontWeight.normal, color: AppColors.textMuted, ); fontWeight=FontWeight.normal; color=AppColors.textMuted; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); height=AppSpacing.btnHeightLG; border=Border.all( color: AppColors.borderDefault, width: 1.5, ); color=AppColors.borderDefault; width=1.5; alignment=Alignment.center; style=AppTypography.buttonSM.copyWith( color: AppColors.textTertiary, ); color=AppColors.textTertiary; width=AppSpacing.md; pressedTint=BwPressable.tintLight; style=AppTypography.buttonSM",
   "file": "apps/mobile/lib/shared/widgets/overlays/confirm_dialog.dart",
   "usage_count": 9,
   "notes": "static show returns Future<void> via showDialog, barrier 옵션은 지정하지 않음. 버튼 BwPressable.button(확인 tintLight). onCancel 없으면 pop; custom onCancel 및 onConfirm은 자동 pop하지 않으므로 호출측에서 닫아야 함. onConfirm=null은 탭만 비활성, 파란 배경 유지.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-confirm-dialog\"><div class=\"pv-mechanic-scale-frame\" style=\"height:137px\"><div class=\"pv-mechanic-scaled\" style=\"width:181.8181818181818%;transform:scale(0.55)\"><div class=\"pv-mechanic-confirm\"><div class=\"pv-mechanic-confirm-icon\"><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M9 9a3 3 0 1 1 4 3l-1 2M12 17h.01\"/></svg></div><div class=\"pv-mechanic-confirm-title\">정비를 완료할까요?</div><div class=\"pv-mechanic-confirm-description\">고객에게 완료 알림을 보내요.</div><div class=\"pv-mechanic-confirm-actions\"><span class=\"pv-mechanic-button pv-mechanic-dialog-secondary\">취소</span><span class=\"pv-mechanic-button pv-mechanic-dialog-primary\">완료</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-loading-overlay",
   "platform": "mechanic",
   "name": "BwLoadingOverlay",
   "category": "오버레이",
   "purpose": "중복 표시를 막으며 루트 내비게이터에 로딩 모달을 띄운다.",
   "variants": [
    "showing",
    "hidden",
    "custom-message"
   ],
   "props": [
    {
     "name": "context",
     "type": "BuildContext",
     "required": true,
     "default": null
    },
    {
     "name": "message",
     "type": "String?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderLight",
    "AppColors.brandPrimary",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.radiusLG",
    "AppTypography.bodySM"
   ],
   "sizes": "중앙160×160/bgWhite/radius18/borderLight1. 브랜드 스피너stroke3, 메시지위16/bodySM13w500textSecondary. | 코드 세부 표현식(하위 요소·상태 포함): width=160; height=160; color=AppColors.bgWhite; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); border=Border.all(color: AppColors.borderLight); color=AppColors.borderLight; color=AppColors.brandPrimary; strokeWidth=3; height=AppSpacing.lg; style=AppTypography.bodySM.copyWith( fontWeight: FontWeight.w500, color: AppColors.textSecondary, ); fontWeight=FontWeight.w500; color=AppColors.textSecondary",
   "file": "apps/mobile/lib/shared/widgets/overlays/loading_overlay.dart",
   "usage_count": 9,
   "notes": "private 생성자; props=static show. showDialog(barrierDismissible:false), 루트 Navigator 캡처. hide()는 context 없이 pop, 캡처 Navigator 불가시 rootNavigatorKey fallback. hideAndThen(callback)은 hide 후 다음 프레임 호출. 메시지는 liveRegion semantics. props 배열은 Widget 생성자가 아닌 static show API를 기록한 예외이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-loading-overlay\"><div class=\"pv-mechanic-overlay-scene\"><div class=\"pv-mechanic-overlay-underlay\">정비카드 · PCX125<div class=\"pv-mechanic-field\">엔진오일 교체</div></div><div class=\"pv-mechanic-overlay-dim\"></div><div class=\"pv-mechanic-overlay-box\"><span class=\"pv-mechanic-spinner\" style=\"width:36px;height:36px\" aria-label=\"처리 중\"></span><div class=\"pv-mechanic-overlay-message\">견적을 저장하고 있어요</div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-reject-reason-sheet",
   "platform": "mechanic",
   "name": "BwRejectReasonSheet",
   "category": "오버레이",
   "purpose": "예약 거절·취소 사유를 프리셋 또는 직접 입력으로 선택한다.",
   "variants": [
    "unselected",
    "preset-selected",
    "custom-empty",
    "custom-valid"
   ],
   "props": [
    {
     "name": "context",
     "type": "BuildContext",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": false,
     "default": "'거절 사유를 선택해 주세요'"
    },
    {
     "name": "confirmLabel",
     "type": "String",
     "required": false,
     "default": "'거절 확정'"
    },
    {
     "name": "presets",
     "type": "List<String>",
     "required": false,
     "default": "_presets"
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.borderLight",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppColors.textTertiary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.iconLG",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusXL",
    "AppSpacing.xl",
    "AppTypography.bodyLG",
    "AppTypography.bodyMD",
    "AppTypography.buttonSM",
    "AppTypography.headingLG",
    "BwPressable.tintDark",
    "BwPressable.tintLight"
   ],
   "sizes": "bgWhite/상단radius20; 상단12/제목행H20/headingLG18w800/닫기24/아래16. 사유행H20/V16/bottom borderLight1/radio20/gap12/15w500(선택700). 직접입력padding L20/T12/R20/B0, bodyMD14, maxLines2/maxLength100. 하단버튼50/radius18/gap12/H20/위아래20. | 코드 세부 표현식(하위 요소·상태 포함): backgroundColor=AppColors.bgWhite; shape=const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusXL), ), ); borderRadius=BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusXL), ); height=AppSpacing.md; padding=const EdgeInsets.symmetric(horizontal: AppSpacing.xl); style=AppTypography.headingLG; size=AppSpacing.iconLG; color=AppColors.textMuted; height=AppSpacing.lg; padding=const EdgeInsets.fromLTRB( AppSpacing.xl, AppSpacing.md, AppSpacing.xl, 0, ); maxLines=2; style=AppTypography.bodyMD; hintStyle=AppTypography.bodyMD.copyWith( color: AppColors.textMuted, ); height=AppSpacing.xl; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xl, ); borderRadius=BorderRadius.circular(AppSpacing.radiusLG); height=AppSpacing.btnHeightLG; color=AppColors.bgWhite; border=Border.all( color: AppColors.borderDefault, width: 1.5, ); color=AppColors.borderDefault; width=1.5; alignment=Alignment.center; style=AppTypography.buttonSM.copyWith( color: AppColors.textTertiary, ); color=AppColors.textTertiary; width=AppSpacing.md; pressedTint=BwPressable.tintLight; color=_canConfirm ? AppColors.brandPrimary : AppColors.borderDefault; style=AppTypography.buttonSM; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xl, vertical: AppSpacing.lg, ); border=Border( bottom: BorderSide(color: AppColors.borderLight), ); color=AppColors.borderLight; size=AppSpacing.iconMD; color=selected ? AppColors.brandPrimary : AppColors.textMuted; style=AppTypography.bodyLG.copyWith( fontWeight: selected ? FontWeight.w700 : FontWeight.w500, color: selected ? AppColors.textPrimary : AppColors.textSecondary, ); fontWeight=selected ? FontWeight.w700 : FontWeight.w500; color=selected ? AppColors.textPrimary : AppColors.textSecondary",
   "file": "apps/mobile/lib/shared/widgets/overlays/reject_reason_sheet.dart",
   "usage_count": 1,
   "notes": "private 생성자; props=static show, Future<String?>. showModalBottomSheet(isScrollControlled:true)+SafeArea(top:false); viewInsets.bottom 추가 padding은 없음. 기본 시간대 충돌/차종 미지원/부품 재고 없음/정비 일정 초과+기타. 취소null, 확정선택문자 또는 trim된 직접입력. 행 InkWell, 닫기 icon/버튼 button BwPressable. 유효사유 없으면 확정 비활성. props 배열은 Widget 생성자가 아닌 static show API를 기록한 예외이다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-reject-reason-sheet\"><div class=\"pv-mechanic-scale-frame\" style=\"height:211px\"><div class=\"pv-mechanic-scaled\" style=\"width:200%;transform:scale(0.5)\"><div class=\"pv-mechanic-sheet pv-mechanic-reason-sheet\"><div class=\"pv-mechanic-sheet-header\"><div class=\"pv-mechanic-title pv-mechanic-grow\">거절 사유를 선택해 주세요</div><svg aria-hidden=\"true\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 6 12 12M18 6 6 18\"/></svg></div><div class=\"pv-mechanic-reason\"><div class=\"pv-mechanic-radio\"></div><div class=\"pv-mechanic-grow\">시간대 충돌</div></div><div class=\"pv-mechanic-reason\"><div class=\"pv-mechanic-radio\"></div><div class=\"pv-mechanic-grow\">차종 미지원</div></div><div class=\"pv-mechanic-reason pv-mechanic-reason-selected\"><div class=\"pv-mechanic-radio\"></div><div class=\"pv-mechanic-grow\">부품 재고 없음</div></div><div class=\"pv-mechanic-reason\"><div class=\"pv-mechanic-radio\"></div><div class=\"pv-mechanic-grow\">정비 일정 초과</div></div><div class=\"pv-mechanic-reason\"><div class=\"pv-mechanic-radio\"></div><div class=\"pv-mechanic-grow\">기타 (직접 입력)</div></div><div class=\"pv-mechanic-reason-actions\"><span class=\"pv-mechanic-button pv-mechanic-dialog-secondary\">취소</span><span class=\"pv-mechanic-button pv-mechanic-dialog-primary\">거절 확정</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-profile-detail-sheet",
   "platform": "mechanic",
   "name": "ProfileDetailSheet",
   "category": "오버레이",
   "purpose": "공개 프로필·매너온도·거래 통계·후기를 하단 시트에서 보여준다.",
   "variants": [
    "loading",
    "error",
    "loaded",
    "trust-unavailable",
    "reviews-empty",
    "reviews-present"
   ],
   "props": [
    {
     "name": "userId",
     "type": "int",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgPrimary",
    "AppColors.bgSurface",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.statusSuccess",
    "AppColors.statusWarning",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusPill",
    "AppSpacing.radiusXL",
    "AppSpacing.sm",
    "AppSpacing.xl",
    "AppSpacing.xxl",
    "AppSpacing.xxs",
    "AppSpacing.xxxl",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.caption",
    "AppTypography.numericBody"
   ],
   "sizes": "maxHeight=화면높이×0.85(고정85% 아님); bgPrimary/상단r20/상단gap20; 로딩·에러padding32; 스크롤H24/섹션gap16. 통계·후기카드 padding20/r18/bgWhite/borderDefault1. 게이지높이8/r9999, 온도18w700; 후기별점14 #F5C842. | 코드 세부 표현식(하위 요소·상태 포함): backgroundColor=Colors.transparent; maxHeight=maxHeight; color=AppColors.bgPrimary; borderRadius=BorderRadius.vertical(top: Radius.circular(AppSpacing.radiusXL)); height=AppSpacing.xl; padding=EdgeInsets.all(AppSpacing.xxxl); color=AppColors.brandPrimary; padding=const EdgeInsets.all(AppSpacing.xxxl); style=AppTypography.bodyMD .copyWith(color: AppColors.textMuted); color=AppColors.textMuted; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xxl); size=ProfileCardSize.full; height=AppSpacing.lg; style=AppTypography.caption .copyWith(color: AppColors.textMuted); width=double.infinity; padding=const EdgeInsets.all(AppSpacing.xl); color=AppColors.bgWhite; borderRadius=BorderRadius.circular(AppSpacing.radiusLG); border=Border.all(color: AppColors.borderDefault); color=AppColors.borderDefault; style=AppTypography.bodyMD .copyWith(fontWeight: FontWeight.w700); fontWeight=FontWeight.w700; style=AppTypography.numericBody.copyWith( fontSize: 18, fontWeight: FontWeight.w700, color: tempColor, ); fontSize=18; color=tempColor; height=AppSpacing.sm; borderRadius=BorderRadius.circular(AppSpacing.radiusPill); minHeight=8; backgroundColor=AppColors.bgSurface; height=AppSpacing.md; width=AppSpacing.lg; style=AppTypography.caption.copyWith(color: AppColors.textMuted); width=AppSpacing.xxs; style=AppTypography.bodySM .copyWith(fontWeight: FontWeight.w700); style=AppTypography.bodyMD.copyWith(fontWeight: FontWeight.w700); style=AppTypography.bodySM .copyWith(color: AppColors.textMuted); size=14; color=i < r.rating ? const Color(0xFFF5C842) : AppColors.borderDefault; width=AppSpacing.sm; maxLines=1; height=AppSpacing.xxs; maxLines=2; style=AppTypography.bodySM .copyWith(color: AppColors.textSecondary); color=AppColors.textSecondary",
   "file": "apps/mobile/lib/shared/widgets/profile/profile_detail_sheet.dart",
   "usage_count": 2,
   "notes": "static show: isScrollControlled=true/background transparent. 프로필 fetch; trust/reviews 로딩·실패 시 해당 섹션 숨김. 온도ratio=(temp-30)/20 clamp0..1; ≥40 success/≥36.5 brandPrimary/나머지 warning. 최근2개 후기. 사용자 차단 TextButton, 성공시 pop. 자체 닫기 아이콘·SafeArea·drag handle 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-profile-detail-sheet\"><div class=\"pv-mechanic-scale-frame\" style=\"height:195px\"><div class=\"pv-mechanic-scaled\" style=\"width:200%;transform:scale(0.5)\"><div class=\"pv-mechanic-profile-sheet\"><div class=\"pv-mechanic-profile-full\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-avatar\"><svg aria-hidden=\"true\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg></div><div class=\"pv-mechanic-grow\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-profile-name\">김부왕</div><div class=\"pv-mechanic-profile-level\">Lv.24</div></div><span class=\"pv-mechanic-rarity pv-mechanic-rarity-rare pv-mechanic-rarity-detail\">꼼꼼한 손길</span></div></div><div class=\"pv-mechanic-profile-stats\">정비 128건 · 경력 5년</div><div class=\"pv-mechanic-profile-story\">“정성을 담아 한 대 한 대 정비합니다.”</div></div><div class=\"pv-mechanic-profile-metric\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-grow\">매너 온도</div><div class=\"pv-mechanic-temperature\">42.5°C</div></div><div class=\"pv-mechanic-gauge\"><div class=\"pv-mechanic-gauge-fill\"></div></div><div class=\"pv-mechanic-profile-stats\">정비 128건 · 후기 36개</div></div><div class=\"pv-mechanic-profile-review\"><div class=\"pv-mechanic-row\"><div class=\"pv-mechanic-grow\">최근 후기</div><div class=\"pv-mechanic-review-stars\">★★★★★</div></div><div class=\"pv-mechanic-review-text\">엔진오일 교체를 꼼꼼하게 해주셨어요.</div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-title-unlock-popup",
   "platform": "mechanic",
   "name": "TitleUnlockPopup",
   "category": "오버레이",
   "purpose": "칭호 해제를 축하하고 즉시 장착 또는 나중에 선택하게 한다.",
   "variants": [
    "common",
    "rare",
    "epic",
    "legendary",
    "description"
   ],
   "props": [
    {
     "name": "titleName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "rarity",
     "type": "String",
     "required": false,
     "default": "'common'"
    },
    {
     "name": "description",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onEquipNow",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "onLater",
     "type": "VoidCallback",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusXL",
    "AppSpacing.xxl",
    "AppSpacing.xxxl",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.buttonMD",
    "AppTypography.headingMD"
   ],
   "sizes": "Dialog transparent/barrier black54; 본체width280/paddingH24/V32/r20/bg#1A1A2E/rarity border alpha0.3. 파티클캔버스300×400. 제목headingMD16/칭호 detail/설명bodySM13 white70. CTA전폭×50/r12/buttonMD15w700; 나중에 bodyMD14 white54. rarity particles6/12/20/30. | 코드 세부 표현식(하위 요소·상태 포함): duration=const Duration(milliseconds: 500); duration=const Duration(milliseconds: 400); duration=const Duration(milliseconds: 300); duration=const Duration(milliseconds: 2000); curve=Curves.elasticOut; curve=Curves.easeOutBack; curve=Curves.easeOut; backgroundColor=Colors.transparent; alignment=Alignment.center; size=const Size(300, 400); width=280; padding=const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, vertical: AppSpacing.xxxl, ); color=const Color(0xFF1A1A2E); borderRadius=BorderRadius.circular(AppSpacing.radiusXL); border=Border.all( color: _rarityAccent(widget.rarity).withValues(alpha: 0.3), ); color=_rarityAccent(widget.rarity).withValues(alpha: 0.3); scale=_starScale; style=AppTypography.headingMD.copyWith( color: _rarityAccent(widget.rarity), ); color=_rarityAccent(widget.rarity); height=AppSpacing.xxl; opacity=_titleOpacity; scale=_titleScale; height=AppSpacing.lg; opacity=_descOpacity; style=AppTypography.bodySM .copyWith(color: Colors.white70); color=Colors.white70; textAlign=TextAlign.center; opacity=_buttonOpacity; width=double.infinity; height=AppSpacing.btnHeightLG; borderRadius=BorderRadius.circular(AppSpacing.radiusMD); style=AppTypography.buttonMD; height=AppSpacing.md; style=AppTypography.bodyMD .copyWith(color: Colors.white54); color=Colors.white54; size=size",
   "file": "apps/mobile/lib/shared/widgets/profile/title_unlock_popup.dart",
   "usage_count": 0,
   "notes": "showDialog(barrierDismissible:false). 장착 Material+InkWell, 나중에 TextButton; callbacks 자동 pop 없음. 실제 sequence:200ms 별(500ms elasticOut)+mediumImpact,500ms 칭호(400ms),700ms 설명(300ms)+파티클(2000ms),1000ms 버튼(300ms). 주석의 파티클900/버튼1200과 다름. Random(42) 고정.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-title-unlock-popup\"><div class=\"pv-mechanic-scale-frame\" style=\"height:178px\"><div class=\"pv-mechanic-scaled\" style=\"width:181.8181818181818%;transform:scale(0.55)\"><div class=\"pv-mechanic-unlock-scene\"><div class=\"pv-mechanic-unlock-particles\">✦ · ✧ · ✦</div><div class=\"pv-mechanic-unlock\"><div class=\"pv-mechanic-unlock-heading\">★ 새로운 칭호 ★</div><div class=\"pv-mechanic-unlock-badge\"><span class=\"pv-mechanic-rarity pv-mechanic-rarity-legendary pv-mechanic-rarity-detail\">전설의 정비사</span></div><div class=\"pv-mechanic-unlock-description\">정비 1,000건 완료</div><span class=\"pv-mechanic-button pv-mechanic-unlock-cta\">지금 장착하기</span><div class=\"pv-mechanic-unlock-later\">나중에</div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-amount-row",
   "platform": "mechanic",
   "name": "BwAmountRow",
   "category": "레이아웃",
   "purpose": "라벨과 원화 금액을 양끝 정렬하고 합계를 강조한다.",
   "variants": [
    "normal",
    "highlight"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "amount",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "highlight",
     "type": "bool",
     "required": false,
     "default": "false"
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textSecondary",
    "AppTypography.bodyMD",
    "AppTypography.headingLG",
    "AppTypography.headingMD"
   ],
   "sizes": "별도 크기·패딩없음. normal label bodyMD14w600textSecondary/amount14w600textPrimary; highlight label headingMD16w700/amount headingLG18w700brandPrimary(headingLG height1.56/ls-0.3 상속). | 코드 세부 표현식(하위 요소·상태 포함): style=highlight ? AppTypography.headingMD.copyWith(fontWeight: FontWeight.w700) : AppTypography.bodyMD.copyWith(color: AppColors.textSecondary); fontWeight=FontWeight.w700; color=AppColors.textSecondary; style=highlight ? AppTypography.headingLG.copyWith( color: AppColors.brandPrimary, fontWeight: FontWeight.w700) : AppTypography.bodyMD.copyWith(fontWeight: FontWeight.w600); color=AppColors.brandPrimary; fontWeight=FontWeight.w600",
   "file": "apps/mobile/lib/shared/widgets/text/bw_amount_row.dart",
   "usage_count": 0,
   "notes": "비인터랙티브. 천단위 쉼표+원. numericTypography를 직접 사용하지 않음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-amount-row\"><div class=\"pv-mechanic-amount-row\"><div class=\"pv-mechanic-amount-label\">부품비</div><div class=\"pv-mechanic-amount-value\">30,000원</div></div><div class=\"pv-mechanic-amount-row\"><div class=\"pv-mechanic-amount-label\">공임비</div><div class=\"pv-mechanic-amount-value\">15,000원</div></div><div class=\"pv-mechanic-amount-row pv-mechanic-amount-highlight\"><div class=\"pv-mechanic-amount-label\">합계</div><div class=\"pv-mechanic-amount-value\">45,000원</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-reservation-row",
   "platform": "mechanic",
   "name": "BwReservationRow",
   "category": "레이아웃",
   "purpose": "대시보드 예약 요약의 시간·차량·서비스를 한 행에 표시한다.",
   "variants": [
    "default"
   ],
   "props": [
    {
     "name": "time",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "vehicleName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "serviceType",
     "type": "String",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textPrimary",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodyXS",
    "AppTypography.caption"
   ],
   "sizes": "paddingV4; 시간폭64/bodyXS12w600 brandPrimary; gap8; 차량폭120/bodyXS12w600; gap8; 서비스Expanded/caption12w500textMuted. | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.symmetric(vertical: AppSpacing.xxs); width=64; maxLines=1; style=AppTypography.bodyXS.copyWith( color: AppColors.brandPrimary, ); color=AppColors.brandPrimary; width=AppSpacing.xs; width=120; style=AppTypography.bodyXS.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w600, ); color=AppColors.textPrimary; fontWeight=FontWeight.w600; style=AppTypography.caption",
   "file": "apps/mobile/lib/shared/widgets/sections/reservation_row.dart",
   "usage_count": 0,
   "notes": "비인터랙티브. 주석의 시간폭약50/시간w700/서비스11px 대신 실제64/w600/12px.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-reservation-row\"><div class=\"pv-mechanic-scale-frame\" style=\"height:40px\"><div class=\"pv-mechanic-scaled\" style=\"width:133.33333333333334%;transform:scale(0.75)\"><div class=\"pv-mechanic-reservation-row-item\"><div class=\"pv-mechanic-row-time\">14:30</div><div class=\"pv-mechanic-row-vehicle\">PCX125 · 1가2 3456</div><div class=\"pv-mechanic-row-service\">엔진오일</div></div><div class=\"pv-mechanic-reservation-row-item\"><div class=\"pv-mechanic-row-time\">15:00</div><div class=\"pv-mechanic-row-vehicle\">NMAX · 서울 강남 가 1234</div><div class=\"pv-mechanic-row-service\">타이어</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-section-header",
   "platform": "mechanic",
   "name": "BwSectionHeader",
   "category": "레이아웃",
   "purpose": "왼쪽 브랜드 막대와 우측 텍스트 액션이 있는 섹션 헤더.",
   "variants": [
    "title-only",
    "with-action",
    "disabled-action"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "actionLabel",
     "type": "String?",
     "required": false,
     "default": null
    },
    {
     "name": "onAction",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textBrand",
    "AppSpacing.md",
    "AppSpacing.radiusPill",
    "AppSpacing.xxl",
    "AppTypography.bodyMD",
    "AppTypography.headingXL"
   ],
   "sizes": "막대6×24/r9999/brandPrimary, 제목gap12/headingXL22w800/height1.4. 우측 action bodyMD14w600/textBrand; 양끝정렬. | 코드 세부 표현식(하위 요소·상태 포함): width=6; height=AppSpacing.xxl; color=AppColors.brandPrimary; borderRadius=BorderRadius.circular(AppSpacing.radiusPill); width=AppSpacing.md; style=AppTypography.headingXL; style=AppTypography.bodyMD.copyWith( color: AppColors.textBrand, ); color=AppColors.textBrand",
   "file": "apps/mobile/lib/shared/widgets/sections/section_header.dart",
   "usage_count": 1,
   "notes": "액션은 BwPressable.text(opacity0.55), actionLabel만 있어도 표시. text/bw_section_header.dart의 동일 이름 클래스와 API·스타일이 다르므로 id/file로 구분.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-section-header\"><div class=\"pv-mechanic-section-heading\"><div class=\"pv-mechanic-section-marker\"></div><div class=\"pv-mechanic-section-heading-title\">오늘 예약</div><div class=\"pv-mechanic-section-action\">전체 보기</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-section-header",
   "platform": "mechanic",
   "name": "BwSectionHeader",
   "category": "레이아웃",
   "purpose": "정비 요약 등에서 단순 섹션 제목 텍스트를 표시한다.",
   "variants": [
    "default"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppTypography.headingMD"
   ],
   "sizes": "별도 크기·패딩없음; headingMD.copyWith(w700)=16px/w700/Pretendard/textPrimary. | 코드 세부 표현식(하위 요소·상태 포함): style=AppTypography.headingMD.copyWith(fontWeight: FontWeight.w700); fontWeight=FontWeight.w700",
   "file": "apps/mobile/lib/shared/widgets/text/bw_section_header.dart",
   "usage_count": 0,
   "notes": "title은 필수 위치 인자(BwSectionHeader(this.title,{key})). sections/section_header.dart의 동명 클래스와 다른 위젯. 눌림·액션 없음.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-section-header\"><div class=\"pv-mechanic-simple-heading\">정비 견적 내역</div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-section-label",
   "platform": "mechanic",
   "name": "BwSectionLabel",
   "category": "레이아웃",
   "purpose": "섹션의 작은 디스플레이 라벨을 표시한다.",
   "variants": [
    "default"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppSpacing.xs",
    "AppTypography.displayLabel"
   ],
   "sizes": "paddingH8; displayLabel11w800/ls1.2/height1.3/textLabel. | 코드 세부 표현식(하위 요소·상태 포함): padding=const EdgeInsets.symmetric(horizontal: AppSpacing.xs); style=AppTypography.displayLabel",
   "file": "apps/mobile/lib/shared/widgets/sections/section_label.dart",
   "usage_count": 1,
   "notes": "비인터랙티브.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-section-label\"><div class=\"pv-mechanic-section-label-text\">정비 예약 관리</div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bottom-sheet-menu-bw-bottom-sheet-menu-item",
   "platform": "mechanic",
   "name": "BwBottomSheetMenuItem",
   "category": "기타",
   "purpose": "하단 메뉴 항목의 아이콘·라벨·색·콜백을 전달하는 데이터 객체.",
   "variants": [
    "default",
    "custom-colors"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "iconColor",
     "type": "Color?",
     "required": false,
     "default": null
    },
    {
     "name": "textColor",
     "type": "Color?",
     "required": false,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "자체 렌더 없음. BwBottomSheetMenu에서 항목 padding16, 아이콘20, gap12, 라벨15/w500으로 렌더.",
   "file": "apps/mobile/lib/shared/widgets/overlays/bottom_sheet_menu.dart",
   "usage_count": 1,
   "notes": "Widget이 아닌 public 모델 클래스이므로 별도 수록. iconColor/textColor 기본 null이며 실제 기본색은 메뉴가 결정.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bottom-sheet-menu-bw-bottom-sheet-menu-item\"><div class=\"pv-mechanic-diagram\"><span class=\"pv-mechanic-muted\">메뉴 항목</span><div class=\"pv-mechanic-menu-item\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg><div class=\"pv-mechanic-grow\">정비 내역 수정</div></div><div class=\"pv-mechanic-menu-item pv-mechanic-menu-danger\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 6 12 12M18 6 6 18\"/></svg><div class=\"pv-mechanic-grow\">정비카드 삭제</div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "mechanic-bw-pressable",
   "platform": "mechanic",
   "name": "BwPressable",
   "category": "기타",
   "purpose": "앱 공통 탭·길게누르기 눌림 피드백을 적용하는 래퍼.",
   "variants": [
    "default",
    "card",
    "button",
    "chip",
    "icon",
    "text",
    "pressed",
    "disabled",
    "long-press"
   ],
   "props": [
    {
     "name": "child",
     "type": "Widget",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "onLongPress",
     "type": "VoidCallback?",
     "required": false,
     "default": null
    },
    {
     "name": "pressedScale",
     "type": "double",
     "required": false,
     "default": "1.0"
    },
    {
     "name": "pressedTint",
     "type": "Color?",
     "required": false,
     "default": null
    },
    {
     "name": "borderRadius",
     "type": "BorderRadius?",
     "required": false,
     "default": null
    },
    {
     "name": "circleTint",
     "type": "bool",
     "required": false,
     "default": "false"
    },
    {
     "name": "pressedOpacity",
     "type": "double",
     "required": false,
     "default": "1.0"
    },
    {
     "name": "behavior",
     "type": "HitTestBehavior",
     "required": false,
     "default": "HitTestBehavior.opaque"
    }
   ],
   "tokens": [
    "BwPressable.tintDark",
    "BwPressable.tintLight"
   ],
   "sizes": "크기·패딩은 child 의존. 기본 scale1/opacity1/tint없음; card scale1+tintDark; button0.98+tintDark; chip0.97+tintDark; icon1+tintDark(기본원형); text opacity0.55. tintDark Color(0x0D000000), tintLight Color(0x1FFFFFFF). 100ms/easeOut. | 코드 세부 표현식(하위 요소·상태 포함): duration=_duration; curve=Curves.easeOut; color=Color.lerp(tint.withAlpha(0), tint, t); shape=isCircle ? BoxShape.circle : BoxShape.rectangle; borderRadius=isCircle ? null : widget.borderRadius; opacity=1.0 - (1.0 - widget.pressedOpacity) * t; scale=1.0 - (1.0 - widget.pressedScale) * t",
   "file": "apps/mobile/lib/shared/widgets/interaction/bw_pressable.dart",
   "usage_count": 70,
   "notes": "GestureDetector+foreground DecoratedBox+Transform.scale/Opacity. onTap 또는 onLongPress가 있어야 enabled. 짧은 탭도 forward 완료 후 reverse; 길게누름 동안 유지. borderRadius는 child와 일치시켜야 함. preset별 정확한 생성자는 tokens.pressed에 수록.",
   "style_source": null,
   "preview_html": "<div class=\"pv-mechanic-root pv-mechanic-bw-pressable\"><div class=\"pv-mechanic-diagram\"><span class=\"pv-mechanic-muted\">정비카드 · 눌림 피드백</span><div class=\"pv-mechanic-pressable-main\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 6a5 5 0 0 0-6 6l-5 5a2.8 2.8 0 0 0 4 4l5-5a5 5 0 0 0 6-6l-3 3-4-4z\"/></svg><div class=\"pv-mechanic-grow\">엔진오일 교체</div><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 5 7 7-7 7\"/></svg></div></div><div class=\"pv-mechanic-variants\"><div class=\"pv-mechanic-variants-inner\"><div class=\"pv-mechanic-row\"><span class=\"pv-mechanic-button pv-mechanic-dialog-primary pv-mechanic-pressed\">버튼</span><div class=\"pv-mechanic-choice pv-mechanic-choice-selected pv-mechanic-chip-pressed\">칩</div><div class=\"pv-mechanic-pressable-icon\"><svg aria-hidden=\"true\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg></div><div class=\"pv-mechanic-pressable-text\">자세히</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-primary-cta-v2-bw-outline-cta-v2",
   "platform": "rider",
   "name": "BwOutlineCtaV2",
   "category": "버튼",
   "purpose": "V2 링크색 테두리와 흰 배경의 보조 동작 버튼이다.",
   "variants": [
    "enabled",
    "disabled"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onPressed",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "height",
     "type": "double (default: 50)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.bgWhite",
    "AppColorsV2.linkAccent",
    "AppSpacingV2.radiusShopCard",
    "AppTypographyV2.body"
   ],
   "sizes": "기본 높이 50, 전체 폭, border 1.5, radius 14, body 14/w800. 토큰 값: AppSpacingV2.radiusShopCard=14.0; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L131 BorderSide(color: AppColorsV2.linkAccent, width: 1.5) | L133 BorderRadius.circular(AppSpacingV2.radiusShopCard) | L135 AppTypographyV2.body.copyWith(fontWeight: FontWeight.w800) | L124 width: double.infinity | L125 height: height | L129 foregroundColor: AppColorsV2.linkAccent | L130 backgroundColor: AppColorsV2.bgWhite | L131 color: AppColorsV2.linkAccent | L131 width: 1.5 | L135 fontWeight: FontWeight.w800",
   "file": "apps/rider/lib/shared/widgets/buttons/bw_primary_cta_v2.dart",
   "usage_count": 47,
   "notes": "V2 전용 — BwPrimaryCtaV2와 같은 파일에 정의된다. 파일 import 수와 실제 심볼 사용 수를 구분해야 한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(47)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 6개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-primary-cta-v2-bw-outline-cta-v2 \"><span class=\"pv-rider-cta pv-rider-outline\">정비소 찾아보기</span><div class=\"pv-rider-mini\"><div class=\"pv-rider-stage \" style=\"height:33px\"><div class=\"pv-rider-scaled\" style=\"width:153.84615%;transform:scale(0.65)\"><span class=\"pv-rider-cta pv-rider-outline pv-rider-disabled\">예약 시간 선택</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-primary-cta-v2",
   "platform": "rider",
   "name": "BwPrimaryCtaV2",
   "category": "버튼",
   "purpose": "그라데이션과 로딩 표시를 제공하는 전체 폭 기본 동작 버튼이다.",
   "variants": [
    "enabled",
    "disabled",
    "loading",
    "icon",
    "large-text"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onPressed",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "isLoading",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "height",
     "type": "double (default: AppSpacingV2.ctaHeight)",
     "required": false,
     "default": null
    },
    {
     "name": "icon",
     "type": "IconData? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "adaptToLargeText",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.border",
    "AppColorsV2.ctaGradient",
    "AppColorsV2.textMuted",
    "AppSpacingV2.ctaHeight",
    "AppSpacingV2.radiusShopCard",
    "AppTypographyV2.body"
   ],
   "sizes": "기본 높이 52, radius 14; adaptToLargeText && textScale>1.3이면 높이 최소 104·라벨 2줄; 라벨 16/w800, 아이콘 18·간격 8, 로더 22×22·stroke 2.5. 토큰 값: AppSpacingV2.ctaHeight=52.0; AppSpacingV2.radiusShopCard=14.0; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L44 AppTypographyV2.body.copyWith( color: enabled ? Colors.white : AppColorsV2.textMuted, fontWeight: FontWeight.w800, fontSize: 16, ) | L58 BorderRadius.circular(AppSpacingV2.radiusShopCard) | L45 color: enabled ? Colors.white : AppColorsV2.textMuted | L46 fontWeight: FontWeight.w800 | L47 fontSize: 16 | L52 width: double.infinity | L53 height: effectiveHeight | L56 gradient: enabled ? AppColorsV2.ctaGradient : null | L57 color: enabled ? null : AppColorsV2.border | L61 color: Colors.transparent | L74 width: 22 | L75 height: 22 | L77 strokeWidth: 2.5 | L87 size: 18 | L92 width: 8",
   "file": "apps/rider/lib/shared/widgets/buttons/bw_primary_cta_v2.dart",
   "usage_count": 47,
   "notes": "V2 전용 — V2 디자인을 비버전 경로 login_screen.dart 등에서도 사용한다. onPressed!=null && !isLoading일 때 활성화하며 탭 시 lightImpact를 실행한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(47)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 46개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-primary-cta-v2 \"><span class=\"pv-rider-cta \"><svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"M7 2v6m10-6v6M3 10h18m-13 5h2m4 0h2\"/></svg>예약하기</span><div class=\"pv-rider-mini\"><div class=\"pv-rider-stage \" style=\"height:34px\"><div class=\"pv-rider-scaled\" style=\"width:153.84615%;transform:scale(0.65)\"><div class=\"pv-rider-row \"><span class=\"pv-rider-cta pv-rider-disabled\">예약하기</span><span class=\"pv-rider-cta pv-rider-disabled\"><span class=\"pv-rider-spinner\"></span></span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-primary-cta-button",
   "platform": "rider",
   "name": "PrimaryCtaButton",
   "category": "버튼",
   "purpose": "누름 축소 효과와 로딩 표시가 있는 기본 CTA를 표시한다.",
   "variants": [
    "enabled",
    "disabled",
    "pressed",
    "loading"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onPressed",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "isLoading",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.ctaGradient",
    "AppColors.textMuted",
    "AppColors.textWhite",
    "AppSpacing.btnHeightXL",
    "AppTypography.buttonLG"
   ],
   "sizes": "높이 52, radius 14; pressed scale 0.98·100ms; shadow alpha 0.3·blur 12·offset(0,4); 로더 24×24·stroke 2.5; buttonLG 16/w700. 토큰 값: AppSpacing.btnHeightXL=52.0; AppTypography.buttonLG(size=16, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L55 BorderRadius.circular(14) | L58 BoxShadow( color: AppColors.brandPrimary.withValues(alpha: 0.3), blurRadius: 12, offset: const Offset(0, 4), ) | L78 AppTypography.buttonLG.copyWith( color: _isEnabled ? AppColors.textWhite : AppColors.textMuted, ) | L47 scale: _isPressed ? 0.98 : 1.0 | L50 width: double.infinity | L51 height: AppSpacing.btnHeightXL | L53 gradient: _isEnabled ? AppColors.ctaGradient : null | L54 color: _isEnabled ? null : AppColors.borderDefault | L59 color: AppColors.brandPrimary.withValues(alpha: 0.3) | L60 blurRadius: 12 | L69 width: 24 | L70 height: 24 | L72 strokeWidth: 2.5 | L79 color: _isEnabled ? AppColors.textWhite : AppColors.textMuted",
   "file": "apps/rider/lib/shared/widgets/buttons/primary_cta_button.dart",
   "usage_count": 25,
   "notes": "공용 — V1 AppColors/AppTypography 기반. V1 화면에서 직접 사용하며 공용 BwErrorState를 통해 V2 오류 화면에서도 재사용된다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(25)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 25개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-primary-cta-button pv-rider-v1\"><span class=\"pv-rider-cta \">내 차량 등록하기</span><div class=\"pv-rider-mini\"><div class=\"pv-rider-stage \" style=\"height:34px\"><div class=\"pv-rider-scaled\" style=\"width:153.84615%;transform:scale(0.65)\"><div class=\"pv-rider-row \"><span class=\"pv-rider-cta pv-rider-disabled\">등록하기</span><span class=\"pv-rider-cta \"><span class=\"pv-rider-spinner\"></span></span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-brand-model-cascade",
   "platform": "rider",
   "name": "BwBrandModelCascade",
   "category": "입력",
   "purpose": "제조사 선택에 따라 모델 선택을 연결하는 비동기 드롭다운을 표시한다.",
   "variants": [
    "loading",
    "error",
    "data",
    "model-disabled",
    "displacement"
   ],
   "props": [
    {
     "name": "brands",
     "type": "AsyncValue<List<BwCascadeOption>>",
     "required": true,
     "default": null
    },
    {
     "name": "selectedBrandId",
     "type": "int?",
     "required": true,
     "default": null
    },
    {
     "name": "onBrandChanged",
     "type": "ValueChanged<BwCascadeOption?>",
     "required": true,
     "default": null
    },
    {
     "name": "models",
     "type": "AsyncValue<List<BwCascadeOption>>?",
     "required": true,
     "default": null
    },
    {
     "name": "selectedModelId",
     "type": "int?",
     "required": true,
     "default": null
    },
    {
     "name": "onModelChanged",
     "type": "ValueChanged<BwCascadeOption?>",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.inputFocusAccent",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusSM",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.caption"
   ],
   "sizes": "필드 간격 12, radius 12, contentPadding H16/V16; 로더 20×20·stroke 2; 배기량 배지 H8/V4·radius 10·caption 11/w700. 토큰 값: AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusSM=10.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L100 EdgeInsets.symmetric( horizontal: AppSpacing.xs, vertical: AppSpacing.xxs, ) | L106 BorderRadius.circular( AppSpacing.radiusSM, ) | L112 AppTypography.caption.copyWith( color: AppColors.brandPrimary, fontWeight: FontWeight.w700, ) | L148 AppTypography.bodySM.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L153 BorderRadius.circular(AppSpacing.radiusMD) | L154 BorderSide(color: AppColors.borderDefault) | L162 BorderSide(color: AppColors.inputFocusAccent) | L164 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.lg, ) | L74 height: AppSpacing.md | L105 color: AppColors.bgSurface | L113 color: AppColors.brandPrimary | L114 fontWeight: FontWeight.w700 | L149 color: AppColors.textSecondary | L150 fontWeight: FontWeight.normal | L154 color: AppColors.borderDefault | L162 color: AppColors.inputFocusAccent | L175 height: 20 | L176 width: 20 | L178 strokeWidth: 2",
   "file": "apps/rider/lib/shared/widgets/forms/bw_brand_model_cascade.dart",
   "usage_count": 1,
   "notes": "V1 전용 — models==null이면 모델 드롭다운을 비활성화한다. 데이터 조회·선택 변경은 외부 AsyncValue와 콜백이 담당한다. 앱 lib 내 직접 import는 없다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다. 앱 lib 내 직접 import 없음(테스트만 있거나 사용처 없음).",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-brand-model-cascade pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:112px\"><div class=\"pv-rider-scaled\" style=\"width:142.85714%;transform:scale(0.7)\"><div class=\"pv-rider-column\" style=\"gap:12px\"><div class=\"pv-rider-field pv-rider-between\" style=\"padding:16px\">혼다<svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></div><div class=\"pv-rider-field pv-rider-between\" style=\"padding:16px\">PCX125<svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></div><div class=\"pv-rider-row \"><span class=\"pv-rider-chip pv-rider-tone-neutral \">125cc</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-filter-tab",
   "platform": "rider",
   "name": "BwFilterTab",
   "category": "입력",
   "purpose": "목록 필터의 활성 상태를 캡슐 탭으로 표시한다.",
   "variants": [
    "active",
    "inactive"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "isActive",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textTertiary",
    "AppColors.textWhite",
    "AppSpacing.md",
    "AppSpacing.radiusPill",
    "AppTypography.bodyXS"
   ],
   "sizes": "패딩 H18/V12, radius 9999, 텍스트 12·active w700/inactive w500. 토큰 값: AppSpacing.md=12.0; AppSpacing.radiusPill=9999.0; AppTypography.bodyXS(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L23 EdgeInsets.symmetric( vertical: AppSpacing.md, horizontal: 18, ) | L29 BorderRadius.circular(AppSpacing.radiusPill) | L32 Border.all(color: AppColors.borderDefault) | L36 AppTypography.bodyXS.copyWith( color: isActive ? AppColors.textWhite : AppColors.textTertiary, fontWeight: isActive ? FontWeight.w700 : FontWeight.w500, ) | L28 color: isActive ? AppColors.brandPrimary : AppColors.bgWhite | L32 color: AppColors.borderDefault | L37 color: isActive ? AppColors.textWhite : AppColors.textTertiary | L38 fontWeight: isActive ? FontWeight.w700 : FontWeight.w500",
   "file": "apps/rider/lib/shared/widgets/forms/filter_tab.dart",
   "usage_count": 1,
   "notes": "V1 전용 — 활성일 때 테두리를 생략한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-filter-tab pv-rider-v1\"><div class=\"pv-rider-row \"><span class=\"pv-rider-filter-active\">전체</span><span>예약</span><span>완료</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-input-field",
   "platform": "rider",
   "name": "BwInputField",
   "category": "입력",
   "purpose": "라벨·아이콘·오류·비밀번호 가림 기능을 갖춘 입력 필드다.",
   "variants": [
    "default",
    "focused",
    "error",
    "disabled",
    "obscured",
    "leading-icon",
    "trailing-icon"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "hintText",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "leadingIcon",
     "type": "IconData? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "trailingIcon",
     "type": "IconData? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "obscureText",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "controller",
     "type": "TextEditingController? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "validator",
     "type": "String? Function(String?)? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "keyboardType",
     "type": "TextInputType? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "errorText",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "enabled",
     "type": "bool (default: true)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.borderDefault",
    "AppColors.inputFocusAccent",
    "AppColors.statusError",
    "AppColors.textMuted",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppTypography.inputLabel",
    "AppTypography.inputPlaceholder",
    "AppTypography.inputValue"
   ],
   "sizes": "radius 12, border 기본 1.5/포커스·오류 2.0, padding H16/V14, 라벨 gap 6, 아이콘 20·gap 12, 입력 fontSize14, scrollPadding bottom120, transition150ms. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppTypography.inputLabel(size=12, weight=FontWeight.w700, height=미지정); AppTypography.inputPlaceholder(size=14, weight=FontWeight.normal, height=미지정); AppTypography.inputValue(size=14, weight=FontWeight.normal, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L140 BorderRadius.circular(AppSpacing.radiusMD) | L141 Border.all(color: borderColor, width: borderWidth) | L143 EdgeInsets.symmetric( vertical: 14, horizontal: AppSpacing.lg, ) | L179 EdgeInsets.only(bottom: 120) | L202 TextStyle(height: 0, fontSize: 0) | L222 AppTypography.inputLabel.copyWith( color: AppColors.statusError, ) | L134 height: 6 | L139 color: fillColor | L141 color: borderColor | L141 width: borderWidth | L154 size: AppSpacing.iconMD | L155 color: leadingIconColor | L157 width: AppSpacing.md | L202 height: 0 | L202 fontSize: 0 | L223 color: AppColors.statusError",
   "file": "apps/rider/lib/shared/widgets/forms/bw_input_field.dart",
   "usage_count": 5,
   "notes": "V1 전용 — 포커스 강조는 AppColors.inputFocusAccent를 통해 V2 linkAccent와 공유한다. errorText가 validator 오류보다 우선한다. 제출 시 onFieldSubmitted와 onSubmitted를 모두 호출한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. props 10개 제한으로 남은 생성자 파라미터: textInputAction: TextInputAction? (default: null); onFieldSubmitted: ValueChanged<String>? (default: null); onSubmitted: ValueChanged<String>? (default: null); focusNode: FocusNode? (default: null); autofillHints: Iterable<String>? (default: null); autofocus: bool (default: false); inputFormatters: List<TextInputFormatter>? (default: null). usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 5개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-input-field pv-rider-v1\"><span class=\"pv-rider-field-label\">차량 번호</span><div class=\"pv-rider-field pv-rider-focused\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"17\" r=\"4\"/><circle cx=\"19\" cy=\"17\" r=\"4\"/><path d=\"M5 17h9l-4-9H5m7-4h4l3 13M8 8l-3 9\"/></svg>1가2 3456<svg class=\"pv-rider-icon pv-rider-link\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-stage \" style=\"height:32px\"><div class=\"pv-rider-scaled\" style=\"width:153.84615%;transform:scale(0.65)\"><div class=\"pv-rider-field pv-rider-error\">번호판을 확인해 주세요</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-ocr-result-editor",
   "platform": "rider",
   "name": "BwOcrResultEditor",
   "category": "입력",
   "purpose": "OCR 결과를 수정하고 저신뢰 항목 확인·재촬영·확정을 제공한다.",
   "variants": [
    "high-confidence",
    "low-confidence",
    "focused",
    "all-filled",
    "incomplete",
    "numeric"
   ],
   "props": [
    {
     "name": "fields",
     "type": "List<BwOcrField>",
     "required": true,
     "default": null
    },
    {
     "name": "onConfirm",
     "type": "void Function(Map<String, String> values)",
     "required": true,
     "default": null
    },
    {
     "name": "onRetake",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "confirmLabel",
     "type": "String (default: '정보가 맞아요, 다음')",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgSurface",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.inputFocusAccent",
    "AppColors.statusSuccess",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppColors.textWhite",
    "AppSpacing.btnHeightXL",
    "AppSpacing.iconMD",
    "AppSpacing.iconSM",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusSM",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodySM",
    "AppTypography.buttonMD",
    "AppTypography.caption",
    "AppTypography.headingLG",
    "AppTypography.inputLabel",
    "AppTypography.inputValue",
    "AppTypography.numericBody"
   ],
   "sizes": "프리뷰 아이콘44×44·radius10; 카드 padding12·radius12·border1.25; 입력 radius12·border1.5/2·padding H16/V14·scroll bottom120; CTA52·radius14; 제목18. 토큰 값: AppSpacing.btnHeightXL=52.0; AppSpacing.iconMD=20.0; AppSpacing.iconSM=18.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusSM=10.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.buttonMD(size=15, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.headingLG(size=18, weight=FontWeight.w700, height=1.56); AppTypography.inputLabel(size=12, weight=FontWeight.w700, height=미지정); AppTypography.inputValue(size=14, weight=FontWeight.normal, height=미지정); AppTypography.numericBody(size=14, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L104 AppTypography.bodySM.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L116 EdgeInsets.all(AppSpacing.md) | L119 BorderRadius.circular(AppSpacing.radiusMD) | L120 Border.all(color: AppColors.borderDefault, width: 1.25) | L129 BorderRadius.circular(AppSpacing.radiusSM) | L146 AppTypography.caption.copyWith( color: AppColors.textMuted, ) | L183 AppTypography.bodySM.copyWith( color: AppColors.statusWarning, ) | L225 BorderRadius.circular(14) | L231 AppTypography.buttonMD.copyWith( color: _allFilled ? AppColors.textWhite : AppColors.textMuted, ) | L243 AppTypography.bodySM.copyWith(color: AppColors.textMuted) | L101 height: AppSpacing.xs | L105 color: AppColors.textSecondary | L106 fontWeight: FontWeight.normal | L109 height: AppSpacing.lg | L118 color: AppColors.bgWhite | L120 color: AppColors.borderDefault | L120 width: 1.25 | L125 width: 44 | L126 height: 44 | L128 color: AppColors.bgSurface | L133 size: AppSpacing.iconMD | L137 width: AppSpacing.md | L143 height: AppSpacing.xxs | L147 color: AppColors.textMuted | L155 size: AppSpacing.iconSM | L169 color: AppColors.statusWarningBg | L177 color: AppColors.statusWarning | L179 width: AppSpacing.xs | L206 height: AppSpacing.md | L212 width: double.infinity | L213 height: AppSpacing.btnHeightXL | L221 backgroundColor: AppColors.brandPrimary | L222 foregroundColor: AppColors.textWhite | L227 elevation: 0 | L232 color: _allFilled ? AppColors.textWhite : AppColors.textMuted | L325 Border.all(color: borderColor, width: borderWidth) | L333 EdgeInsets.only(bottom: 120) | L354 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: 14, ) | L313 size: 12 | L317 color: AppColors.statusSuccess | L323 color: AppColors.bgSecondary | L325 color: borderColor | L325 width: borderWidth",
   "file": "apps/rider/lib/shared/widgets/ocr/bw_ocr_result_editor.dart",
   "usage_count": 1,
   "notes": "V1 전용 — fields로 컨트롤러를 initState에서 만들고 값이 모두 비어 있지 않을 때 확정한다. 숫자 키보드 항목에는 digitsOnly를 적용한다. 앱 lib 내 직접 import는 없다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다. 앱 lib 내 직접 import 없음(테스트만 있거나 사용처 없음).",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-ocr-result-editor pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:178.57143%;transform:scale(0.56)\"><div class=\"pv-rider-column\" style=\"gap:12px\"><strong style=\"font-size:18px\">인식된 정보를 확인해 주세요</strong><div class=\"pv-rider-card\" style=\"padding:12px;border-radius:12px;border-width:1.25px\"><div class=\"pv-rider-row \"><span class=\"pv-rider-circle\" style=\"width:44px;height:44px;border-radius:10px;background:#F1F5F9\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 2h10l5 5v15H5zM14 2v6h6M8 12h9m-9 4h9\"/></svg></span><div>이륜자동차 사용신고필증<br><span class=\"pv-rider-muted\">차량 정보 인식 완료</span></div></div></div><div class=\"pv-rider-field\" style=\"border-color:#F59E0A\">차량 번호　1가2 3456 <svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 3 10 18H2zM12 9v5m0 3v1\"/></svg></div><span class=\"pv-rider-cta \">정보가 맞아요, 다음</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-rating-stars",
   "platform": "rider",
   "name": "BwRatingStars",
   "category": "입력",
   "purpose": "별점 값을 별 아이콘으로 보여주거나 탭으로 선택한다.",
   "variants": [
    "interactive",
    "read-only",
    "filled",
    "outline"
   ],
   "props": [
    {
     "name": "rating",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "onRatingChanged",
     "type": "ValueChanged<int>? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "count",
     "type": "int (default: 5)",
     "required": false,
     "default": null
    },
    {
     "name": "size",
     "type": "double (default: 44)",
     "required": false,
     "default": null
    },
    {
     "name": "activeColor",
     "type": "Color (default: AppColors.statusWarning)",
     "required": false,
     "default": null
    },
    {
     "name": "inactiveColor",
     "type": "Color (default: AppColors.borderDefault)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.borderDefault",
    "AppColors.statusWarning",
    "AppSpacing.xxs"
   ],
   "sizes": "기본 count5, size44, 각 별 padding H4, 기본 active statusWarning/inactive borderDefault. 토큰 값: AppSpacing.xxs=4.0. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L42 EdgeInsets.symmetric(horizontal: AppSpacing.xxs) | L45 size: size | L46 color: filled ? activeColor : inactiveColor",
   "file": "apps/rider/lib/shared/widgets/ratings/bw_rating_stars.dart",
   "usage_count": 5,
   "notes": "공용 — V1 AppColors 기본값이며 V1 리뷰와 V2 shop_detail_screen에서 공유한다. onRatingChanged가 null이면 GestureDetector를 만들지 않는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 5개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-rating-stars pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:42px\"><div class=\"pv-rider-scaled\" style=\"width:111.11111%;transform:scale(0.9)\"><div class=\"pv-rider-row \"><span style=\"padding:0 4px;color:#F59E0A\"><svg class=\"pv-rider-icon pv-rider-star-filled\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 2 3 6.5 7 1-5 5 1.2 7L12 18l-6.2 3.5 1.2-7-5-5 7-1z\"/></svg></span><span style=\"padding:0 4px;color:#F59E0A\"><svg class=\"pv-rider-icon pv-rider-star-filled\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 2 3 6.5 7 1-5 5 1.2 7L12 18l-6.2 3.5 1.2-7-5-5 7-1z\"/></svg></span><span style=\"padding:0 4px;color:#F59E0A\"><svg class=\"pv-rider-icon pv-rider-star-filled\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 2 3 6.5 7 1-5 5 1.2 7L12 18l-6.2 3.5 1.2-7-5-5 7-1z\"/></svg></span><span style=\"padding:0 4px;color:#F59E0A\"><svg class=\"pv-rider-icon pv-rider-star-filled\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 2 3 6.5 7 1-5 5 1.2 7L12 18l-6.2 3.5 1.2-7-5-5 7-1z\"/></svg></span><span style=\"padding:0 4px;color:#E2E8F0\"><svg class=\"pv-rider-icon \" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 2 3 6.5 7 1-5 5 1.2 7L12 18l-6.2 3.5 1.2-7-5-5 7-1z\"/></svg></span></div></div></div><div class=\"pv-rider-mini\"><span class=\"pv-rider-muted\">정비 만족도 · 4점</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-sos-symptom-tile",
   "platform": "rider",
   "name": "BwSosSymptomTile",
   "category": "입력",
   "purpose": "선택 상태와 위험 아이콘을 표시하는 SOS 증상 타일이다.",
   "variants": [
    "selected",
    "unselected",
    "danger"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "selected",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "danger",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.statusError",
    "AppColors.statusErrorBg",
    "AppColors.statusInfoBg",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.radiusMD",
    "AppTypography.bodyLG"
   ],
   "sizes": "minHeight 100, 패딩 16, radius 12, border 1.25, 아이콘 원 40×40·아이콘 20, 라벨 bodyLG 15/w700·최대 2줄. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.radiusMD=12.0; AppTypography.bodyLG(size=15, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L37 BoxConstraints(minHeight: 100) | L38 EdgeInsets.all(AppSpacing.lg) | L41 BorderRadius.circular(AppSpacing.radiusMD) | L42 Border.all( color: selected ? AppColors.brandPrimary : AppColors.borderDefault, width: 1.25, ) | L37 minHeight: 100 | L40 color: selected ? AppColors.statusInfoBg : AppColors.bgWhite | L43 color: selected ? AppColors.brandPrimary : AppColors.borderDefault | L44 width: 1.25 | L52 width: 40 | L53 height: 40 | L57 color: danger ? AppColors.statusErrorBg : AppColors.brandPrimary.withValues(alpha: 0.10) | L63 size: AppSpacing.iconMD | L64 color: danger ? AppColors.statusError : AppColors.brandPrimary",
   "file": "apps/rider/lib/shared/widgets/bw_sos_symptom_tile.dart",
   "usage_count": 4,
   "notes": "V1 전용 — selected는 타일 배경·테두리, danger는 아이콘 원의 색만 바꾼다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(4)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-sos-symptom-tile pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:100px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-row \"><div class=\"pv-rider-symptom pv-rider-selected\"><span class=\"pv-rider-circle \" style=\"width:40px;height:40px\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m14 2-9 12h6l-1 8 9-13h-6z\"/></svg></span><span>시동 불가</span></div><div class=\"pv-rider-symptom\"><span class=\"pv-rider-circle pv-rider-danger-circle\" style=\"width:40px;height:40px\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 3 10 18H2zM12 9v5m0 3v1\"/></svg></span><span>사고 발생</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-terms-agreement-list",
   "platform": "rider",
   "name": "BwTermsAgreementList",
   "category": "입력",
   "purpose": "전체동의와 개별 약관 체크·본문 펼치기를 제공한다.",
   "variants": [
    "all-checked",
    "partially-checked",
    "unchecked",
    "expanded",
    "collapsed"
   ],
   "props": [
    {
     "name": "items",
     "type": "List<BwTermsItem>",
     "required": true,
     "default": null
    },
    {
     "name": "onItemChanged",
     "type": "void Function(String key, bool checked)",
     "required": true,
     "default": null
    },
    {
     "name": "onAllChanged",
     "type": "void Function(bool checked)",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppSpacing.iconLG",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusMD",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.caption",
    "AppTypography.headingMD"
   ],
   "sizes": "전체 카드 padding20·radius16·shadow blur12/offset(0,4)/alpha .04; 항목 H16/V12·radius12·체크22; 펼침 maxHeight220·본문13·height1.8·animation200ms. 토큰 값: AppSpacing.iconLG=24.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusMD=12.0; AppSpacing.xl=20.0; AppSpacing.xs=8.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.headingMD(size=16, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L64 EdgeInsets.all(AppSpacing.xl) | L67 BorderRadius.circular(AppSpacing.radiusLG) | L68 Border.all(color: AppColors.borderDefault) | L70 BoxShadow( color: AppColors.brandPrimary.withValues(alpha: 0.04), blurRadius: 12, offset: const Offset(0, 4), ) | L63 width: double.infinity | L66 color: AppColors.bgWhite | L68 color: AppColors.borderDefault | L71 color: AppColors.brandPrimary.withValues(alpha: 0.04) | L72 blurRadius: 12 | L84 color: _allChecked ? AppColors.brandPrimary : AppColors.textMuted | L87 size: AppSpacing.iconLG | L89 width: AppSpacing.md | L95 height: AppSpacing.md | L99 height: AppSpacing.xs | L134 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.md, ) | L140 BorderRadius.circular(AppSpacing.radiusMD) | L165 AppTypography.caption.copyWith( color: AppColors.brandPrimary, ) | L177 EdgeInsets.only(top: AppSpacing.md) | L188 BoxConstraints(maxHeight: 220) | L192 AppTypography.bodySM.copyWith( color: AppColors.textSecondary, height: 1.8, ) | L152 color: item.isChecked ? AppColors.brandPrimary : AppColors.textMuted | L155 size: 22 | L166 color: AppColors.brandPrimary | L183 height: 1 | L188 maxHeight: 220 | L193 color: AppColors.textSecondary | L194 height: 1.8",
   "file": "apps/rider/lib/shared/widgets/progress/bw_terms_agreement_list.dart",
   "usage_count": 2,
   "notes": "V1 전용 — 체크 상태는 콜백으로 외부 관리하고 펼침 키 집합만 내부에서 관리한다. 빈 목록은 전체동의=false다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-terms-agreement-list pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:153.84615%;transform:scale(0.65)\"><div class=\"pv-rider-terms\"><div class=\"pv-rider-row\" style=\"gap:12px\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg><strong>전체 동의</strong></div><div class=\"pv-rider-rule\"></div><div class=\"pv-rider-term\"><svg class=\"pv-rider-icon pv-rider-link\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg><span class=\"pv-rider-grow\">[필수] 서비스 이용약관</span><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></div><div class=\"pv-rider-term\"><svg class=\"pv-rider-icon pv-rider-link\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg><span class=\"pv-rider-grow\">[필수] 개인정보 수집·이용</span><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 5 7 7-7 7\"/></svg></div><div class=\"pv-rider-term\" style=\"color:#6B7280\"><svg class=\"pv-rider-icon \" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg>[선택] 혜택 알림 동의</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-text-scale-setting-v2",
   "platform": "rider",
   "name": "BwTextScaleSettingV2",
   "category": "입력",
   "purpose": "앱 설정에서 큰 글씨 모드를 즉시 켜고 끄는 설명형 스위치 행이다.",
   "variants": [
    "standard",
    "large"
   ],
   "props": [
    {
     "name": "value",
     "type": "RiderTextScaleMode",
     "required": true,
     "default": null
    },
    {
     "name": "onChanged",
     "type": "ValueChanged<RiderTextScaleMode>",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.bgWhite",
    "AppColorsV2.headingNavy",
    "AppColorsV2.linkAccent",
    "AppColorsV2.textMuted",
    "AppColorsV2.textSecondary",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.xs",
    "AppTypographyV2.body",
    "AppTypographyV2.cardSubtitle"
   ],
   "sizes": "padding H16/V8, 아이콘20, standard 1.0배, large 1.2배. 제목·설명 줄바꿈에 따라 행 높이가 늘어난다.",
   "file": "apps/rider/lib/shared/widgets/settings/bw_text_scale_setting_v2.dart",
   "usage_count": 1,
   "notes": "V2 전용 설정 컴포넌트 — 전체 행 탭과 SwitchListTile semantics를 제공한다. 저장과 전역 적용은 호출부의 textScaleModeProvider가 담당한다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-text-scale-setting-v2\"><div class=\"pv-rider-settings-card\"><div class=\"pv-rider-text-scale-row\"><span class=\"pv-rider-text-icon\">가</span><span class=\"pv-rider-grow\"><b>큰 글씨</b><small>앱의 모든 글자를 20% 크게 표시하고<br>기기 글자 크기도 함께 반영해요</small></span><span class=\"pv-rider-switch pv-rider-switch-on\"><i></i></span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-time-slot-picker",
   "platform": "rider",
   "name": "BwTimeSlotPicker",
   "category": "입력",
   "purpose": "예약 시간의 선택·마감 상태를 V1 또는 V2 스타일로 표시한다.",
   "variants": [
    "v1",
    "v2",
    "selected",
    "closed",
    "available"
   ],
   "props": [
    {
     "name": "time",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "isClosed",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "isSelected",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "useV2Style",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppColors.textWhite",
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.headingNavy",
    "AppColorsV2.linkAccent",
    "AppColorsV2.textMuted",
    "AppColorsV2.trackLight",
    "AppSpacing.radiusSM",
    "AppTypography.numericSmall",
    "AppTypographyV2.bodyNumeric"
   ],
   "sizes": "radius 10; 고정 높이·패딩 없음(부모 제약); V1 numericSmall 12/w700, V2 bodyNumeric.copyWith(fontSize:13)/w800. 토큰 값: AppSpacing.radiusSM=10.0; AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정); AppTypographyV2.bodyNumeric(size=14, weight=FontWeight.w800, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L71 BorderRadius.circular(AppSpacing.radiusSM) | L72 Border.all( color: borderColor, ) | L80 AppTypographyV2.bodyNumeric.copyWith(fontSize: 13) | L70 color: bgColor | L73 color: borderColor | L80 fontSize: 13 | L83 color: textColor",
   "file": "apps/rider/lib/shared/widgets/forms/bw_time_slot_picker.dart",
   "usage_count": 5,
   "notes": "공용 — useV2Style로 두 버전 토큰을 직접 분기한다. isClosed는 색과 취소선만 바꾸고 onTap을 차단하지 않는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 5개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-time-slot-picker \"><div class=\"pv-rider-row \"><span class=\"pv-rider-slot pv-rider-slot-selected\">10:00</span><span class=\"pv-rider-slot\">10:30</span><span class=\"pv-rider-slot pv-rider-slot-closed\">11:00</span></div><div class=\"pv-rider-mini\"><span class=\"pv-rider-muted\">오전 예약 · 선택 가능 시간</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-delivery-components-auto-badge",
   "platform": "rider",
   "name": "AutoBadge",
   "category": "배지",
   "purpose": "정비비가 자동 입력된 항목임을 경고색 소형 배지로 표시한다.",
   "variants": [
    "default"
   ],
   "props": [],
   "tokens": [
    "AppColors.statusWarning",
    "AppColors.statusWarningBg"
   ],
   "sizes": "패딩 H5/V1, radius 4, 라벨 9/w700. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L180 EdgeInsets.symmetric(horizontal: 5, vertical: 1) | L182 color: AppColors.statusWarningBg | L183 BorderRadius.circular(4) | L187 kr(size: 9, color: AppColors.statusWarning)",
   "file": "apps/rider/lib/features/delivery_ledger/presentation/widgets/delivery_components.dart",
   "usage_count": 5,
   "notes": "V1 토큰 — 라벨은 '자동' 고정이라 props 가 없다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count 는 파일 단위 값이다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다. 담기는 근거: 순수익 대시보드·정산 2곳에서 같은 배지를 쓴다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "rider-bw-shop-card-v2-bw-buwang-badge",
   "platform": "rider",
   "name": "BwBuwangBadge",
   "category": "배지",
   "purpose": "파란 그라데이션 위에 BUWANG 파트너 워드마크를 표시한다.",
   "variants": [
    "default"
   ],
   "props": [],
   "tokens": [],
   "sizes": "패딩 H6/V2, radius 4, Pretendard 미지정 TextStyle 9/w800·letterSpacing .5, gradient Color(0xFF42A5F5)→Color(0xFF1E88E5). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L552 EdgeInsets.symmetric(horizontal: 6, vertical: 2) | L555 Color(0xFF42A5F5) | L555 Color(0xFF1E88E5) | L557 BorderRadius.circular(4) | L561 TextStyle( color: Colors.white, fontSize: 9, fontWeight: FontWeight.w800, letterSpacing: 0.5, ) | L554 gradient: const LinearGradient( colors: [Color(0xFF42A5F5), Color(0xFF1E88E5)], ) | L562 color: Colors.white | L563 fontSize: 9 | L564 fontWeight: FontWeight.w800 | L565 letterSpacing: 0.5",
   "file": "apps/rider/lib/shared/widgets/cards/bw_shop_card_v2.dart",
   "usage_count": 10,
   "notes": "V2 전용 — bw_shop_card_v2.dart의 공개 보조 위젯. AppColorsV2.partnerBadgeGradient와 유사하지만 구현은 직접 Color 리터럴을 사용한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(10)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-shop-card-v2-bw-buwang-badge \"><span class=\"pv-rider-partner\">BUWANG</span></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-completed-step-chip",
   "platform": "rider",
   "name": "BwCompletedStepChip",
   "category": "배지",
   "purpose": "완료한 입력 단계의 이름·값과 수정 동작을 표시한다.",
   "variants": [
    "completed"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "value",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.textMuted",
    "AppSpacing.authFormCardBorderWidth",
    "AppSpacing.radiusMD",
    "AppSpacing.sm",
    "AppTypography.bodySM",
    "AppTypography.inputLabel"
   ],
   "sizes": "padding H14/V10, radius12, border1.25; 체크 원22×22·아이콘12, 편집14; gap10, label12·value13. 토큰 값: AppSpacing.authFormCardBorderWidth=1.25; AppSpacing.radiusMD=12.0; AppSpacing.sm=10.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.inputLabel(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L28 EdgeInsets.symmetric( horizontal: 14, vertical: AppSpacing.sm, ) | L34 BorderRadius.circular(AppSpacing.radiusMD) | L35 Border.all( color: AppColors.borderDefault, width: AppSpacing.authFormCardBorderWidth, ) | L33 color: AppColors.bgWhite | L36 color: AppColors.borderDefault | L37 width: AppSpacing.authFormCardBorderWidth | L43 width: 22 | L44 height: 22 | L47 color: AppColors.statusSuccessBg | L52 size: 12 | L53 color: AppColors.statusSuccess | L56 width: AppSpacing.sm | L68 size: 14 | L69 color: AppColors.textMuted",
   "file": "apps/rider/lib/shared/widgets/progress/bw_completed_step_chip.dart",
   "usage_count": 3,
   "notes": "V1 전용 — 완료 체크와 수정 아이콘을 항상 표시하며 탭하면 onTap을 호출한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-completed-step-chip pv-rider-v1\"><div class=\"pv-rider-completed\"><div class=\"pv-rider-row \"><span class=\"pv-rider-check\" style=\"background:#EBFAF2;color:#219661\"><svg class=\"pv-rider-icon \" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg></span><span style=\"font-size:12px;font-weight:700\">내 차량</span><b class=\"pv-rider-grow\" style=\"font-size:13px\">PCX125</b><svg class=\"pv-rider-icon \" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m15 4 5 5M4 20l5-1L20 8l-5-5L4 15z\"/></svg></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-reservation-status-badge",
   "platform": "rider",
   "name": "BwReservationStatusBadge",
   "category": "배지",
   "purpose": "예약 상태 문구를 호출부가 지정한 배경색과 글자색으로 표시한다.",
   "variants": [
    "색상 주입"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "backgroundColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "textColor",
     "type": "Color",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppSpacing.radiusSM",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.caption"
   ],
   "sizes": "패딩 H8/V4, radius 10, caption 11/w700. 토큰 값: AppSpacing.radiusSM=10.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L28 EdgeInsets.symmetric( horizontal: AppSpacing.xs, vertical: AppSpacing.xxs, ) | L34 BorderRadius.circular(AppSpacing.radiusSM) | L38 AppTypography.caption.copyWith( color: textColor, fontWeight: FontWeight.w700, ) | L33 color: backgroundColor | L39 color: textColor | L40 fontWeight: FontWeight.w700",
   "file": "apps/rider/lib/shared/widgets/badges/bw_reservation_status_badge.dart",
   "usage_count": 2,
   "notes": "V1 전용 — 상태 매핑은 호출부가 담당한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-reservation-status-badge pv-rider-v1\"><div class=\"pv-rider-wrap\"><span class=\"pv-rider-chip pv-rider-tone-warning \">예약 대기</span><span class=\"pv-rider-chip pv-rider-tone-info \">예약 확정</span><span class=\"pv-rider-chip pv-rider-tone-info \">정비 중</span><span class=\"pv-rider-chip pv-rider-tone-success \">정비 완료</span><span class=\"pv-rider-chip pv-rider-tone-error \">예약 취소</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-sos-status-card",
   "platform": "rider",
   "name": "BwSosStatusCard",
   "category": "배지",
   "purpose": "SOS 진행 상태를 캡슐 형태로 표시한다.",
   "variants": [
    "색상 주입"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "backgroundColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "textColor",
     "type": "Color",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppSpacing.md",
    "AppSpacing.radiusPill",
    "AppSpacing.xs",
    "AppTypography.bodySM"
   ],
   "sizes": "패딩 H12/V8, radius 9999, bodySM 13/w700. 토큰 값: AppSpacing.md=12.0; AppSpacing.radiusPill=9999.0; AppSpacing.xs=8.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L33 EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.xs, ) | L39 BorderRadius.circular(AppSpacing.radiusPill) | L43 AppTypography.bodySM.copyWith( color: textColor, fontWeight: FontWeight.w700, ) | L38 color: backgroundColor | L44 color: textColor | L45 fontWeight: FontWeight.w700",
   "file": "apps/rider/lib/shared/widgets/badges/bw_sos_status_card.dart",
   "usage_count": 3,
   "notes": "V1 전용 — 배경색·글자색과 상태 문구를 생성자로 받는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-sos-status-card pv-rider-v1\"><div class=\"pv-rider-wrap\"><span class=\"pv-rider-chip pv-rider-tone-warning \">정비사 매칭 중</span><span class=\"pv-rider-chip pv-rider-tone-info \">출동 중</span><span class=\"pv-rider-chip pv-rider-tone-success \">도착 완료</span><span class=\"pv-rider-chip pv-rider-tone-error \">요청 취소</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-delivery-components-platform-chip",
   "platform": "rider",
   "name": "PlatformChip",
   "category": "배지",
   "purpose": "배달 플랫폼을 플랫폼색 점과 라벨의 알약 칩으로 표시한다.",
   "variants": [
    "baemin",
    "coupang",
    "yogiyo",
    "general",
    "미등록 키 fallback"
   ],
   "props": [
    {
     "name": "platform",
     "type": "String",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppSpacing.radiusPill"
   ],
   "sizes": "패딩 L7/T3/R9/B3, radius 9999, 배경 플랫폼색 8%(0x14), 보더 플랫폼색 16%(0x2A), 점 7 원형, gap 6, 라벨 12/w700(시스템 한글). 토큰 값: AppSpacing.radiusPill=9999.0. 플랫폼색: 배민 #2AC1BC · 쿠팡이츠 #111111 · 요기요 #FA0050 · 직접배달 #005792. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L107 EdgeInsets.fromLTRB(7, 3, 9, 3) | L109 color: p.color.withValues(alpha: 0x14 / 255) | L110 BorderRadius.circular(AppSpacing.radiusPill) | L111 Border.all(color: p.color.withValues(alpha: 0x2A / 255)) | L117 width: 7 | L118 height: 7 | L121 SizedBox(width: 6) | L122 kr(size: 12, color: p.labelColor) | L81 kDeliveryPlatforms | L77 labelColor — 쿠팡이츠(#111111)만 AppColors.textPrimary",
   "file": "apps/rider/lib/features/delivery_ledger/presentation/widgets/delivery_components.dart",
   "usage_count": 5,
   "notes": "V1 토큰 — 배달 장부 공용 칩. 플랫폼 키는 서버 enum(baemin|coupang|yogiyo|general)이 SSOT 이고, 모르는 키는 키 문자열을 라벨로 쓰고 AppColors.textMuted 로 칠한다(kDeliveryPlatforms·platformOf·platformLabel 이 같은 파일에 있다). 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count 는 파일 단위라 같은 파일의 DeliverySectionHeader·AutoBadge 와 값이 같다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다. 담기는 근거: 배달 장부 화면 2곳(장부 목록·달력)에서 재사용 — shared/widgets 밖이지만 화면 전용 컨테이너가 아니다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "rider-bw-home-bike-card",
   "platform": "rider",
   "name": "BwHomeBikeCard",
   "category": "카드",
   "purpose": "차량 별칭·브랜드·누적 주행거리와 업데이트 동작을 표시한다.",
   "variants": [
    "primary",
    "logo",
    "initial-fallback",
    "mileage-null",
    "tappable"
   ],
   "props": [
    {
     "name": "nickname",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "subtitle",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "brandInitial",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "mileage",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "isPrimary",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "onUpdateMileage",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "brandColor",
     "type": "Color? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "logoUrl",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.brandPrimary",
    "AppColors.textWhite",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusPill",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppSpacing.xxs",
    "AppTypography.bodySM",
    "AppTypography.bodyXS",
    "AppTypography.displayLogo",
    "AppTypography.headingXL",
    "AppTypography.numericBody",
    "AppTypography.numericDisplay",
    "AppTypography.overline"
   ],
   "sizes": "패딩 24, radius 16, 로고 40×40·radius 12, 제목 20/w800·height 1, 주행거리 36/w800·height .95, 기본 배지 9/w800·radius 6, shadow blur 16·offset(0,6). 토큰 값: AppSpacing.md=12.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusPill=9999.0; AppSpacing.xs=8.0; AppSpacing.xxl=24.0; AppSpacing.xxs=4.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.bodyXS(size=12, weight=FontWeight.w700, height=미지정); AppTypography.displayLogo(size=20, weight=FontWeight.w900, height=1.4); AppTypography.headingXL(size=20, weight=FontWeight.w700, height=1.4); AppTypography.numericBody(size=14, weight=FontWeight.w700, height=미지정); AppTypography.numericDisplay(size=20, weight=FontWeight.w700, height=1.4); AppTypography.overline(size=10, weight=FontWeight.w700, height=1.5). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L67 BorderRadius.circular(AppSpacing.radiusLG) | L69 BoxShadow( color: baseColor.withValues(alpha: 0.3), blurRadius: 16, offset: const Offset(0, 6), ) | L76 EdgeInsets.all(AppSpacing.xxl) | L90 BorderRadius.circular(AppSpacing.radiusMD) | L112 AppTypography.headingXL.copyWith( color: AppColors.textWhite, fontWeight: FontWeight.w800, letterSpacing: -0.4, height: 1, ) | L125 EdgeInsets.symmetric( horizontal: AppSpacing.xs, vertical: 2, ) | L131 BorderRadius.circular(6) | L135 AppTypography.overline.copyWith( fontSize: 9, letterSpacing: 0.5, fontWeight: FontWeight.w800, color: AppColors.textWhite, ) | L149 AppTypography.bodySM.copyWith( color: AppColors.textWhite.withValues(alpha: 0.79), fontWeight: FontWeight.w400, ) | L176 AppTypography.overline.copyWith( color: AppColors.textWhite.withValues(alpha: 0.65), ) | L192 AppTypography.numericDisplay.copyWith( color: AppColors.textWhite, fontSize: 36, fontWeight: FontWeight.w800, letterSpacing: -1, height: 0.95, ) | L203 AppTypography.numericBody.copyWith( color: AppColors.textWhite, ) | L217 EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.xs, ) | L223 BorderRadius.circular(AppSpacing.radiusPill) | L236 AppTypography.bodyXS.copyWith( color: AppColors.textWhite, ) | L261 AppTypography.displayLogo.copyWith( color: AppColors.textWhite, fontSize: 22, height: 1, letterSpacing: 0, ) | L62 gradient: LinearGradient( colors: [baseColor, baseColor.withValues(alpha: 0.45)], begin: Alignment.topLeft, end: Alignment.bottomRight, ) | L70 color: baseColor.withValues(alpha: 0.3) | L71 blurRadius: 16 | L84 width: 40 | L85 height: 40 | L89 color: AppColors.bgWhite.withValues(alpha: 0.18) | L102 width: 14 | L113 color: AppColors.textWhite | L114 fontWeight: FontWeight.w800 | L115 letterSpacing: -0.4 | L116 height: 1 | L123 width: AppSpacing.xs | L130 color: AppColors.bgWhite.withValues(alpha: 0.2) | L136 fontSize: 9 | L137 letterSpacing: 0.5 | L146 height: AppSpacing.xxs | L150 color: AppColors.textWhite.withValues(alpha: 0.79) | L151 fontWeight: FontWeight.w400 | L162 height: 18 | L177 color: AppColors.textWhite.withValues(alpha: 0.65) | L194 fontSize: 36 | L196 letterSpacing: -1 | L197 height: 0.95 | L200 width: AppSpacing.xxs | L213 width: AppSpacing.md | L230 size: 12 | L263 fontSize: 22 | L265 letterSpacing: 0",
   "file": "apps/rider/lib/shared/widgets/cards/bw_home_bike_card.dart",
   "usage_count": 3,
   "notes": "V1 전용 — brandColor가 없으면 brandPrimary를 사용하고 네트워크 로고 오류 시 이니셜을 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-home-bike-card pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:112px\"><div class=\"pv-rider-scaled\" style=\"width:156.25000%;transform:scale(0.64)\"><div class=\"pv-rider-home-bike\"><div class=\"pv-rider-row \"><span class=\"pv-rider-home-avatar\">H</span><div class=\"pv-rider-grow\"><div class=\"pv-rider-row \"><strong style=\"font-size:20px;line-height:1\">내 PCX</strong><span class=\"pv-rider-home-primary\">기본</span></div><span style=\"font-size:13px;opacity:.79\">PCX125 · 1가2 3456</span></div></div><div class=\"pv-rider-row pv-rider-between\" style=\"margin-top:18px;align-items:flex-end\"><div><span style=\"font-size:10px;opacity:.65;letter-spacing:1.2px\">TOTAL MILEAGE</span><div style=\"margin-top:4px\"><strong style=\"font-size:36px;line-height:.95;letter-spacing:-1px\">12,840</strong> <b>KM</b></div></div><span class=\"pv-rider-update\"><svg class=\"pv-rider-icon \" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m15 4 5 5M4 20l5-1L20 8l-5-5L4 15z\"/></svg>주행거리 업데이트</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-maintenance-item-card",
   "platform": "rider",
   "name": "BwMaintenanceItemCard",
   "category": "카드",
   "purpose": "정비 항목과 제품 정보·부품비·공임비를 표시한다.",
   "variants": [
    "separated-cost",
    "total-cost",
    "no-cost"
   ],
   "props": [
    {
     "name": "category",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "brandName",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "productName",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "partsCost",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "laborCost",
     "type": "int",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.borderLight",
    "AppColors.brandPrimary",
    "AppColors.textSecondary",
    "AppSpacing.iconLG",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodyLG",
    "AppTypography.bodySM",
    "AppTypography.caption",
    "AppTypography.numericSmall"
   ],
   "sizes": "외부 bottom 12, 패딩 16, radius 12, 아이콘 24; 간격 8/12/4, 구분선 height 1, 제목 bodyLG 15. 토큰 값: AppSpacing.iconLG=24.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.bodyLG(size=15, weight=FontWeight.w700, height=미지정); AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L43 EdgeInsets.only(bottom: AppSpacing.md) | L45 EdgeInsets.all(AppSpacing.lg) | L48 BorderRadius.circular(AppSpacing.radiusMD) | L49 Border.all(color: AppColors.borderDefault) | L78 AppTypography.bodySM.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L47 color: AppColors.bgWhite | L49 color: AppColors.borderDefault | L59 size: AppSpacing.iconLG | L60 color: AppColors.brandPrimary | L62 width: AppSpacing.xs | L75 height: AppSpacing.xs | L79 color: AppColors.textSecondary | L80 fontWeight: FontWeight.normal | L86 height: AppSpacing.md | L87 color: AppColors.borderLight | L87 height: 1 | L94 height: AppSpacing.xxs | L137 AppTypography.caption.copyWith(color: AppColors.textSecondary)",
   "file": "apps/rider/lib/shared/widgets/cards/bw_maintenance_item_card.dart",
   "usage_count": 3,
   "notes": "V1 전용 — 부품비와 공임비가 모두 0이면 비용 영역을 생략한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-maintenance-item-card pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:116px\"><div class=\"pv-rider-scaled\" style=\"width:133.33333%;transform:scale(0.75)\"><div class=\"pv-rider-card\" style=\"border-radius:12px;margin-bottom:12px\"><div class=\"pv-rider-row \"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M7 6h8v14H3V9h4zm8 3 5-3 2 3-7 5M6 3h7M20 15s-2 2-2 4a2 2 0 0 0 4 0c0-2-2-4-2-4Z\"/></svg><b style=\"font-size:15px\">엔진오일 교체</b></div><div style=\"margin-top:8px\"><span class=\"pv-rider-muted\">모튤 · 스쿠터 파워 5W-40</span></div><div class=\"pv-rider-rule\"></div><div class=\"pv-rider-row pv-rider-between\"><span class=\"pv-rider-muted\">부품비</span><b>18,000원</b></div><div class=\"pv-rider-row pv-rider-between\"><span class=\"pv-rider-muted\">공임비</span><b>7,000원</b></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-maintenance-item-row",
   "platform": "rider",
   "name": "BwMaintenanceItemRow",
   "category": "카드",
   "purpose": "정비 내역의 항목·차량 번호·날짜·비용·상태를 한 행으로 표시한다.",
   "variants": [
    "completed",
    "pending",
    "in_progress",
    "accidentCompleted",
    "extraCount"
   ],
   "props": [
    {
     "name": "date",
     "type": "DateTime",
     "required": true,
     "default": null
    },
    {
     "name": "category",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "vehiclePlate",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "totalCost",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "extraCount",
     "type": "int (default: 0)",
     "required": false,
     "default": null
    },
    {
     "name": "accidentCompleted",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "status",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.chevronColor",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusPill",
    "AppSpacing.xs",
    "AppTypography.bodyMD",
    "AppTypography.caption",
    "AppTypography.numericBody",
    "AppTypography.numericSmall"
   ],
   "sizes": "외부 bottom 8, 패딩 16, radius 12; 아이콘 원 40·아이콘 20, 상태 배지 H8/V2·pill, 날짜 numericSmall 12, 비용 numericBody 14. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusPill=9999.0; AppSpacing.xs=8.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.numericBody(size=14, weight=FontWeight.w700, height=미지정); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L52 EdgeInsets.only(bottom: AppSpacing.xs) | L56 EdgeInsets.all(AppSpacing.lg) | L59 BorderRadius.circular(AppSpacing.radiusMD) | L60 Border.all(color: AppColors.borderDefault) | L71 Color(0xFFFFCC00) | L100 EdgeInsets.symmetric( horizontal: AppSpacing.xs, vertical: 2, ) | L108 BorderRadius.circular( AppSpacing.radiusPill, ) | L114 AppTypography.caption.copyWith( color: _isCompleted ? AppColors.statusSuccess : AppColors.statusWarning, fontWeight: FontWeight.w700, ) | L142 AppTypography.numericSmall.copyWith( color: AppColors.textSecondary, ) | L58 color: AppColors.bgWhite | L60 color: AppColors.borderDefault | L68 size: 40 | L69 iconSize: 20 | L70 backgroundColor: accidentCompleted ? const Color(0xFFFFCC00) : null | L73 iconColor: accidentCompleted ? AppColors.textPrimary : null | L75 width: AppSpacing.md | L98 width: AppSpacing.xs | L105 color: _isCompleted ? AppColors.statusSuccessBg : AppColors.statusWarningBg | L115 color: _isCompleted ? AppColors.statusSuccess : AppColors.statusWarning | L118 fontWeight: FontWeight.w700 | L143 color: AppColors.textSecondary | L146 height: 2 | L159 color: AppColors.chevronColor | L160 size: AppSpacing.iconMD",
   "file": "apps/rider/lib/shared/widgets/cards/bw_maintenance_item_row.dart",
   "usage_count": 3,
   "notes": "V1 전용 — status가 completed이면 완료, pending/in_progress이면 정비 중 배지를 표시한다. 다른 값은 배지를 생략한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-maintenance-item-row pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:84px\"><div class=\"pv-rider-scaled\" style=\"width:156.25000%;transform:scale(0.64)\"><div class=\"pv-rider-maint-item\"><div class=\"pv-rider-row \"><span class=\"pv-rider-circle \" style=\"width:40px;height:40px\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M7 6h8v14H3V9h4zm8 3 5-3 2 3-7 5M6 3h7M20 15s-2 2-2 4a2 2 0 0 0 4 0c0-2-2-4-2-4Z\"/></svg></span><div class=\"pv-rider-grow\"><div class=\"pv-rider-row \"><b style=\"font-size:14px\">엔진오일 교체</b><span class=\"pv-rider-chip pv-rider-tone-success \">완료</span></div><span class=\"pv-rider-caption\">1가2 3456</span></div><div style=\"text-align:right\"><span style=\"font-size:12px;color:#596370\">9/8</span><br><b style=\"font-size:14px\">25,000원</b></div><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 5 7 7-7 7\"/></svg></div></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-row \"><span class=\"pv-rider-chip pv-rider-tone-warning \">정비 중</span><span class=\"pv-rider-chip pv-rider-tone-success \">사고 수리 완료</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-maintenance-row-v2",
   "platform": "rider",
   "name": "BwMaintenanceRowV2",
   "category": "카드",
   "purpose": "소모품의 잔여 거리·교체 상태와 사용률 막대를 표시한다.",
   "variants": [
    "needReplace",
    "soon",
    "normal",
    "divider",
    "info-action",
    "recommendation"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "name",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "remainKmLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "fillUsed",
     "type": "double",
     "required": true,
     "default": null
    },
    {
     "name": "status",
     "type": "MaintV2Status",
     "required": true,
     "default": null
    },
    {
     "name": "recommendLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "showDivider",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "onInfoTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.amberValue",
    "AppColorsV2.dangerStrong",
    "AppColorsV2.headingNavy",
    "AppColorsV2.normalFill",
    "AppColorsV2.slate",
    "AppColorsV2.textMuted",
    "AppColorsV2.trackLight",
    "AppSpacing.lg",
    "AppSpacing.radiusPill",
    "AppSpacing.sm",
    "AppTypographyV2.badge",
    "AppTypographyV2.body",
    "AppTypographyV2.bodyNumeric"
   ],
   "sizes": "패딩 H16/V14, 아이콘 원 30×30·아이콘 16, 정보 아이콘 padding 10·size 16, 막대 minHeight 7·pill, 값 15/w800, 상태 11/w800, divider 1. 토큰 값: AppSpacing.lg=16.0; AppSpacing.radiusPill=9999.0; AppSpacing.sm=10.0; AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1); AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45); AppTypographyV2.bodyNumeric(size=14, weight=FontWeight.w800, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L64 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: 14, ) | L98 EdgeInsets.all(10) | L108 AppTypographyV2.bodyNumeric.copyWith( fontSize: 15, color: remainColor, ) | L117 BorderRadius.circular(AppSpacing.radiusPill) | L131 AppTypographyV2.badge.copyWith( fontSize: 11, color: remainColor, ) | L139 AppTypographyV2.badge.copyWith( fontSize: 10, fontWeight: FontWeight.w500, color: AppColorsV2.textMuted, ) | L74 width: 30 | L75 height: 30 | L78 color: AppColorsV2.trackLight | L81 size: 16 | L81 color: AppColorsV2.slate | L83 width: AppSpacing.sm | L88 fontWeight: FontWeight.w700 | L102 color: AppColorsV2.textMuted | L109 fontSize: 15 | L110 color: remainColor | L115 height: AppSpacing.sm | L120 minHeight: 7 | L121 backgroundColor: AppColorsV2.trackLight | L125 height: 6 | L132 fontSize: 11 | L140 fontSize: 10 | L141 fontWeight: FontWeight.w500 | L151 height: 1 | L151 thickness: 1",
   "file": "apps/rider/lib/shared/widgets/cards/bw_maintenance_row_v2.dart",
   "usage_count": 6,
   "notes": "V2 전용 — fillUsed는 0~1로 clamp한다. 정상 막대는 normalFill 회색이며 긴급/임박은 dangerStrong/amberValue를 사용한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(6)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-maintenance-row-v2 \"><div class=\"pv-rider-stage \" style=\"height:115px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-maint-v2\"><div class=\"pv-rider-row \"><span class=\"pv-rider-circle \" style=\"width:30px;height:30px\"><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M7 6h8v14H3V9h4zm8 3 5-3 2 3-7 5M6 3h7M20 15s-2 2-2 4a2 2 0 0 0 4 0c0-2-2-4-2-4Z\"/></svg></span><b class=\"pv-rider-grow\">엔진오일</b><span style=\"padding:10px\"><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 10v7m0-11v1\"/></svg></span><strong style=\"font-size:15px;color:#E03131\">120km 초과</strong></div><div class=\"pv-rider-track\" style=\"margin-top:10px\"><span style=\"width:100%;background:#E03131\"></span></div><div class=\"pv-rider-row pv-rider-between\"><b style=\"font-size:11px;color:#E03131\">교체 필요</b><span class=\"pv-rider-caption\" style=\"font-size:10px\">권장 2,000km</span></div></div><div class=\"pv-rider-maint-tones\"><span style=\"color:#E8870B\">교체 임박 <i style=\"background:#E8870B\"></i></span><span style=\"color:#475569\">정상 <i style=\"background:#94A3B8\"></i></span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-mechanic-card",
   "platform": "rider",
   "name": "BwMechanicCard",
   "category": "카드",
   "purpose": "배정 정비소·정비사·전화번호·거리·도착 예상과 전화 동작을 표시한다.",
   "variants": [
    "mechanicName",
    "phone",
    "distanceKm",
    "estimatedMinutes",
    "call-action"
   ],
   "props": [
    {
     "name": "name",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "mechanicName",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "phone",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "distanceKm",
     "type": "double? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "estimatedMinutes",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onCall",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.iconSM",
    "AppSpacing.lg",
    "AppSpacing.radiusMD",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodySM",
    "AppTypography.buttonSM",
    "AppTypography.headingMD",
    "AppTypography.numericSmall"
   ],
   "sizes": "정보 아이콘 16, 간격 8/4/10/16, 전화 버튼 높이 48·radius 12·아이콘 18·라벨 buttonSM 13. 토큰 값: AppSpacing.btnHeightLG=48.0; AppSpacing.iconSM=18.0; AppSpacing.lg=16.0; AppSpacing.radiusMD=12.0; AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.buttonSM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.headingMD(size=16, weight=FontWeight.w700, height=미지정); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L56 AppTypography.bodySM.copyWith( color: AppColors.textSecondary, ) | L89 AppTypography.numericSmall.copyWith( color: AppColors.textSecondary, ) | L116 BorderRadius.circular(AppSpacing.radiusMD) | L117 Border.all(color: AppColors.brandPrimary) | L131 AppTypography.buttonSM.copyWith( color: AppColors.brandPrimary, ) | L53 height: AppSpacing.xs | L57 color: AppColors.textSecondary | L68 size: 16 | L69 color: AppColors.textMuted | L71 width: AppSpacing.xxs | L81 height: AppSpacing.sm | L93 width: AppSpacing.lg | L107 height: AppSpacing.lg | L112 width: double.infinity | L113 height: AppSpacing.btnHeightLG | L115 color: AppColors.bgWhite | L117 color: AppColors.brandPrimary | L125 size: AppSpacing.iconSM | L128 width: AppSpacing.xs",
   "file": "apps/rider/lib/shared/widgets/cards/bw_mechanic_card.dart",
   "usage_count": 4,
   "notes": "V1 전용 — 카드라는 이름이지만 루트는 Column이며 외부 배경·테두리는 지정하지 않는다. onCall이 있어야 전화 버튼을 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(4)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-mechanic-card pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:118px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-column\" style=\"gap:8px\"><b style=\"font-size:16px\">부왕 모터스</b><div class=\"pv-rider-row \"><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg><span class=\"pv-rider-muted\">김정비 정비사</span></div><div class=\"pv-rider-row \"><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg><span class=\"pv-rider-muted\">1.2km</span><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 6v6l4 2\"/></svg><span class=\"pv-rider-muted\">약 8분 후 도착</span></div><span class=\"pv-rider-cta pv-rider-call-v1\"><svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 3 4 4-2 3a16 16 0 0 0 7 7l3-2 4 4-3 3C9 22 2 15 2 6z\"/></svg>전화하기</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-next-check-row-v2",
   "platform": "rider",
   "name": "BwNextCheckRowV2",
   "category": "카드",
   "purpose": "다음 점검 소모품과 상태·잔여 거리를 간결한 행으로 표시한다.",
   "variants": [
    "divider",
    "tappable"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "name",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "statusLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "remainKmLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "showDivider",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.linkAccent",
    "AppColorsV2.navActivePillBg",
    "AppColorsV2.trackLight",
    "AppSpacing.lg",
    "AppTypographyV2.body",
    "AppTypographyV2.bodyNumeric",
    "AppTypographyV2.cardSubtitle"
   ],
   "sizes": "패딩 H16/V15, 아이콘 타일 38×38·radius 11·아이콘 20, gap 13/1, 잔여 거리 13/w700, divider height/thickness 1. 토큰 값: AppSpacing.lg=16.0; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45); AppTypographyV2.bodyNumeric(size=14, weight=FontWeight.w800, height=미지정); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L36 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: 15, ) | L48 BorderRadius.circular(11) | L60 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w700, ) | L71 AppTypographyV2.bodyNumeric.copyWith( fontSize: 13, fontWeight: FontWeight.w700, ) | L43 width: 38 | L44 height: 38 | L47 color: AppColorsV2.navActivePillBg | L50 size: 20 | L50 color: AppColorsV2.linkAccent | L52 width: 13 | L61 fontWeight: FontWeight.w700 | L64 height: 1 | L72 fontSize: 13 | L81 thickness: 1 | L81 color: AppColorsV2.trackLight",
   "file": "apps/rider/lib/shared/widgets/cards/bw_next_check_row_v2.dart",
   "usage_count": 1,
   "notes": "V2 전용 — 색상 상태 매핑 없이 statusLabel/remainKmLabel을 그대로 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-next-check-row-v2 \"><div class=\"pv-rider-next-check\"><div class=\"pv-rider-row \"><span class=\"pv-rider-next-icon\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M7 6h8v14H3V9h4zm8 3 5-3 2 3-7 5M6 3h7M20 15s-2 2-2 4a2 2 0 0 0 4 0c0-2-2-4-2-4Z\"/></svg></span><div class=\"pv-rider-grow\"><b>엔진오일</b><br><span class=\"pv-rider-muted\">다음 점검</span></div><b style=\"font-size:13px;color:#16243A\">1,200km</b></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-notification-card",
   "platform": "rider",
   "name": "BwNotificationCard",
   "category": "카드",
   "purpose": "알림 아이콘·제목·본문·상대 시각과 읽음 상태를 표시한다.",
   "variants": [
    "read",
    "unread"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "body",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "timestamp",
     "type": "DateTime",
     "required": true,
     "default": null
    },
    {
     "name": "isRead",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "iconColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "iconBgColor",
     "type": "Color",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgPrimary",
    "AppColors.bgWhite",
    "AppColors.brandPrimary",
    "AppColors.chevronColor",
    "AppColors.textSecondary",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusSM",
    "AppSpacing.screenPadding",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.caption"
   ],
   "sizes": "패딩 H24/V16; 미읽음 점 8×8; 아이콘 타일 40×40·radius 10·아이콘 20; 제목 14·1줄, 본문 13·2줄, 시각 caption 11. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusSM=10.0; AppSpacing.screenPadding=24.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L57 EdgeInsets.symmetric( horizontal: AppSpacing.screenPadding, vertical: AppSpacing.lg, ) | L69 EdgeInsets.only( top: AppSpacing.xs, right: AppSpacing.xs, ) | L85 EdgeInsets.only(right: AppSpacing.md) | L88 BorderRadius.circular(AppSpacing.radiusSM) | L101 AppTypography.bodyMD.copyWith( fontWeight: isRead ? FontWeight.normal : FontWeight.w700, ) | L110 AppTypography.bodySM.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L56 color: isRead ? AppColors.bgPrimary : AppColors.bgWhite | L67 width: 8 | L68 height: 8 | L74 color: AppColors.brandPrimary | L79 width: AppSpacing.lg | L83 width: 40 | L84 height: 40 | L87 color: iconBgColor | L91 size: AppSpacing.iconMD | L91 color: iconColor | L102 fontWeight: isRead ? FontWeight.normal : FontWeight.w700 | L107 height: AppSpacing.xxs | L111 color: AppColors.textSecondary | L112 fontWeight: FontWeight.normal | L129 color: AppColors.chevronColor",
   "file": "apps/rider/lib/shared/widgets/cards/bw_notification_card.dart",
   "usage_count": 2,
   "notes": "V1 전용 — 미읽음이면 점과 굵은 제목을 표시한다. 아이콘 색상은 호출부에서 받는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-notification-card pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:118px\"><div class=\"pv-rider-scaled\" style=\"width:133.33333%;transform:scale(0.75)\"><div class=\"pv-rider-notification\"><div class=\"pv-rider-row \"><span class=\"pv-rider-unread-dot\"></span><span class=\"pv-rider-notification-icon\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"M7 2v6m10-6v6M3 10h18m-13 5h2m4 0h2\"/></svg></span><div class=\"pv-rider-grow\"><b style=\"font-size:14px\">예약이 확정되었어요</b><p style=\"font-size:13px;margin-top:4px\">오늘 오후 2시, 부왕 모터스에서 만나요.</p><span class=\"pv-rider-caption\">10분 전</span></div></div></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-row \"><span class=\"pv-rider-muted\">읽음</span><span style=\"font-size:14px;font-weight:400\">정비가 완료되었어요</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-push-permission-card-v2",
   "platform": "rider",
   "name": "BwPushPermissionCardV2",
   "category": "카드",
   "purpose": "기기 알림이 꺼진 상태를 설명하고 알림 활성화 동작을 제공한다.",
   "variants": [
    "permission-disabled"
   ],
   "props": [
    {
     "name": "onEnable",
     "type": "VoidCallback",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.badgeSoonBg",
    "AppColorsV2.badgeSoonText",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppTypographyV2.body",
    "AppTypographyV2.cardSubtitle"
   ],
   "sizes": "패딩 16, radius 16, 아이콘 20, 간격 10/8/12, 본문 14/w800, CTA 높이 40. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusLG=16.0; AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L21 EdgeInsets.all(AppSpacing.lg) | L24 BorderRadius.circular(AppSpacing.radiusLG) | L40 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w800, color: AppColorsV2.badgeSoonText, ) | L23 color: AppColorsV2.badgeSoonBg | L33 size: AppSpacing.iconMD | L34 color: AppColorsV2.badgeSoonText | L36 width: AppSpacing.sm | L41 fontWeight: FontWeight.w800 | L48 height: AppSpacing.xs | L53 height: AppSpacing.md | L56 height: 40",
   "file": "apps/rider/lib/shared/widgets/cards/bw_push_permission_card_v2.dart",
   "usage_count": 2,
   "notes": "V2 전용 — 알림 권한 상태 판정은 호출부가 담당하며 onEnable을 그대로 전달한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-push-permission-card-v2 \"><div class=\"pv-rider-stage \" style=\"height:112px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-card pv-rider-push\"><div class=\"pv-rider-row \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 16h14l-2-3V9A5 5 0 0 0 7 9v4zm5 4h4M12 2v2\"/></svg><b>알림이 꺼져 있어요</b></div><p style=\"margin:8px 0 12px\"><span class=\"pv-rider-muted\">예약·정비 소식을 놓치지 마세요.</span></p><span class=\"pv-rider-cta \">알림 켜기</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-score-card",
   "platform": "rider",
   "name": "BwScoreCard",
   "category": "카드",
   "purpose": "정비 점수·등급·설명을 짙은 브랜드 그라데이션 카드에 표시한다.",
   "variants": [
    "grade",
    "message"
   ],
   "props": [
    {
     "name": "score",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "grade",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "message",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandGradientStart",
    "AppColors.brandPrimary",
    "AppColors.brandPrimaryDark",
    "AppColors.textWhite",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusPill",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppSpacing.xxs",
    "AppTypography.bodySM",
    "AppTypography.captionBold",
    "AppTypography.numericDisplay",
    "AppTypography.overline"
   ],
   "sizes": "전체 폭, 패딩 24, radius 16; 점수 56/w800·letterSpacing -2·height 1, 분모 18, 등급 H10/V4·pill. 토큰 값: AppSpacing.md=12.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusPill=9999.0; AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppSpacing.xxl=24.0; AppSpacing.xxs=4.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.captionBold(size=11, weight=FontWeight.w700, height=미지정); AppTypography.numericDisplay(size=20, weight=FontWeight.w700, height=1.4); AppTypography.overline(size=10, weight=FontWeight.w700, height=1.5). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L35 BorderRadius.circular(AppSpacing.radiusLG) | L37 EdgeInsets.all(AppSpacing.xxl) | L48 AppTypography.overline.copyWith( color: AppColors.textWhite.withValues(alpha: 0.65), ) | L57 EdgeInsets.symmetric( horizontal: AppSpacing.sm, vertical: AppSpacing.xxs, ) | L63 BorderRadius.circular(AppSpacing.radiusPill) | L67 AppTypography.captionBold.copyWith( color: AppColors.textWhite, letterSpacing: 0.5, ) | L85 AppTypography.numericDisplay.copyWith( color: AppColors.textWhite, fontSize: 56, fontWeight: FontWeight.w800, letterSpacing: -2, height: 1, ) | L96 AppTypography.numericDisplay.copyWith( color: AppColors.textWhite.withValues(alpha: 0.6), fontSize: 18, ) | L109 AppTypography.bodySM.copyWith( color: AppColors.textWhite.withValues(alpha: 0.85), fontWeight: FontWeight.w400, ) | L28 width: double.infinity | L30 gradient: const LinearGradient( colors: [AppColors.brandPrimaryDark, AppColors.brandPrimary], begin: Alignment.topLeft, end: Alignment.bottomRight, ) | L49 color: AppColors.textWhite.withValues(alpha: 0.65) | L62 color: AppColors.brandGradientStart.withValues(alpha: 0.25) | L68 color: AppColors.textWhite | L69 letterSpacing: 0.5 | L76 height: AppSpacing.md | L87 fontSize: 56 | L88 fontWeight: FontWeight.w800 | L89 letterSpacing: -2 | L90 height: 1 | L93 width: AppSpacing.xs | L97 color: AppColors.textWhite.withValues(alpha: 0.6) | L98 fontSize: 18 | L110 color: AppColors.textWhite.withValues(alpha: 0.85) | L111 fontWeight: FontWeight.w400",
   "file": "apps/rider/lib/shared/widgets/cards/bw_score_card.dart",
   "usage_count": 3,
   "notes": "V1 전용 — score 값 자체를 clamp하지 않고 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-score-card pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:149.25373%;transform:scale(0.67)\"><div class=\"pv-rider-score-v1\"><div class=\"pv-rider-row pv-rider-between\"><span style=\"font-size:10px;letter-spacing:1.2px;opacity:.65\">MAINTENANCE SCORE</span><span class=\"pv-rider-grade\">우수</span></div><div style=\"margin-top:12px\"><strong style=\"font-size:56px;letter-spacing:-2px;line-height:1\">86</strong> <span style=\"font-size:18px;opacity:.6\">/ 100</span></div><p style=\"margin-top:12px;font-size:13px;opacity:.85\">내 차량, 건강하게 관리하고 있어요.</p></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-score-card-v2",
   "platform": "rider",
   "name": "BwScoreCardV2",
   "category": "카드",
   "purpose": "원형 정비 점수와 등급·백분위·지난달 대비 변화를 표시한다.",
   "variants": [
    "percentile",
    "previous-score",
    "positive-delta",
    "negative-delta",
    "zero-delta"
   ],
   "props": [
    {
     "name": "score",
     "type": "double",
     "required": true,
     "default": null
    },
    {
     "name": "grade",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "percentileLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "prevScore",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "delta",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "gaugeSize",
     "type": "double (default: 72)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.badgeNormalBg",
    "AppColorsV2.badgeNormalText",
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppSpacing.lg",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusPill",
    "AppSpacing.xs",
    "AppTypographyV2.badge",
    "AppTypographyV2.cardSubtitle",
    "AppTypographyV2.cardTitle"
   ],
   "sizes": "패딩 16, radius 16, 게이지 기본 72·stroke 6, 제목 18, 보조 12/w600, delta H6/V2·pill·아이콘 14. 토큰 값: AppSpacing.lg=16.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusPill=9999.0; AppSpacing.xs=8.0; AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4); AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L33 EdgeInsets.all(AppSpacing.lg) | L36 BorderRadius.circular(AppSpacing.radiusLG) | L37 Border.all(color: AppColorsV2.border) | L50 AppTypographyV2.cardTitle.copyWith(fontSize: 18) | L56 AppTypographyV2.cardSubtitle.copyWith( fontWeight: FontWeight.w600, ) | L35 color: AppColorsV2.bgWhite | L37 color: AppColorsV2.border | L41 size: gaugeSize | L41 strokeWidth: 6 | L42 width: AppSpacing.lg | L50 fontSize: 18 | L53 height: 2 | L57 fontWeight: FontWeight.w600 | L62 height: AppSpacing.xs | L91 TextStyle(fontWeight: FontWeight.w700) | L96 TextStyle(fontWeight: FontWeight.w800) | L100 AppTypographyV2.cardSubtitle.copyWith( fontFeatures: const [FontFeature.tabularFigures()], ) | L91 fontWeight: FontWeight.w700 | L96 fontWeight: FontWeight.w800 | L105 width: AppSpacing.xs | L122 EdgeInsets.symmetric(horizontal: 6, vertical: 2) | L125 BorderRadius.circular(AppSpacing.radiusPill) | L137 AppTypographyV2.badge.copyWith( color: AppColorsV2.badgeNormalText, fontFeatures: const [FontFeature.tabularFigures()], ) | L124 color: AppColorsV2.badgeNormalBg | L132 size: 14 | L133 color: AppColorsV2.badgeNormalText",
   "file": "apps/rider/lib/shared/widgets/cards/bw_score_card_v2.dart",
   "usage_count": 0,
   "notes": "V2 전용 — 직접 import가 확인되지 않는다. delta가 음수여도 배지 배경·글자색은 badgeNormalBg/Text를 사용한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(0)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 0개다. 앱 lib 내 직접 import 없음(테스트만 있거나 사용처 없음).",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-score-card-v2 \"><div class=\"pv-rider-stage \" style=\"height:116px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-card\"><div class=\"pv-rider-row pv-rider-score-row\"><span class=\"pv-rider-gauge\" style=\"width:72px;height:72px\"><svg width=\"72\" height=\"72\" viewBox=\"0 0 72 72\" aria-hidden=\"true\"><circle cx=\"36\" cy=\"36\" r=\"33\" fill=\"none\" stroke=\"#E5EBF5\" stroke-width=\"6\"/><path d=\"M36.000 3.000 A33 33 0 0 1 50.381 6.298\" fill=\"none\" stroke=\"#2d71d5\" stroke-width=\"6\"/><path d=\"M50.363 6.289 A33 33 0 0 1 61.875 15.518\" fill=\"none\" stroke=\"#2c6fd2\" stroke-width=\"6\"/><path d=\"M61.862 15.502 A33 33 0 0 1 68.210 28.821\" fill=\"none\" stroke=\"#2a6dcf\" stroke-width=\"6\"/><path d=\"M68.205 28.801 A33 33 0 0 1 68.123 43.555\" fill=\"none\" stroke=\"#296bcc\" stroke-width=\"6\"/><path d=\"M68.128 43.535 A33 33 0 0 1 61.633 56.783\" fill=\"none\" stroke=\"#2869ca\" stroke-width=\"6\"/><path d=\"M61.646 56.767 A33 33 0 0 1 50.032 65.868\" fill=\"none\" stroke=\"#2667c7\" stroke-width=\"6\"/><path d=\"M50.051 65.859 A33 33 0 0 1 35.634 68.998\" fill=\"none\" stroke=\"#2565c4\" stroke-width=\"6\"/><path d=\"M35.655 68.998 A33 33 0 0 1 21.309 65.550\" fill=\"none\" stroke=\"#2363c2\" stroke-width=\"6\"/><path d=\"M21.328 65.559 A33 33 0 0 1 9.913 56.210\" fill=\"none\" stroke=\"#2261bf\" stroke-width=\"6\"/><path d=\"M9.925 56.227 A33 33 0 0 1 3.717 42.842\" fill=\"none\" stroke=\"#205fbc\" stroke-width=\"6\"/><path d=\"M3.721 42.862 A33 33 0 0 1 3.957 28.109\" fill=\"none\" stroke=\"#1f5db9\" stroke-width=\"6\"/><path d=\"M3.952 28.129 A33 33 0 0 1 10.572 14.966\" fill=\"none\" stroke=\"#1e5bb7\" stroke-width=\"6\"/><circle cx=\"36\" cy=\"3\" r=\"3\" fill=\"#2e72d6\"/><circle cx=\"10.572\" cy=\"14.966\" r=\"3\" fill=\"#1d5ab5\"/></svg><span class=\"pv-rider-gauge-value\" style=\"font-size:24.48px\">86</span></span><div class=\"pv-rider-grow\"><strong style=\"font-size:18px;color:#16243A\">관리 우수</strong><p><span class=\"pv-rider-muted\">상위 15%</span></p><div style=\"margin-top:8px\"><div class=\"pv-rider-row \"><span class=\"pv-rider-muted\">지난달 <b>80 → 86</b></span><span class=\"pv-rider-delta\">▴ +6</span></div></div></div></div></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-row \"><span class=\"pv-rider-delta\">▴ +6</span><span class=\"pv-rider-delta\">▾ -3</span><span class=\"pv-rider-muted\">80 → 80</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-settings-tile-v2-bw-settings-card-v2",
   "platform": "rider",
   "name": "BwSettingsCardV2",
   "category": "카드",
   "purpose": "설정 위젯들을 둥근 테두리 안에 구분선과 함께 배치한다.",
   "variants": [
    "single-child",
    "multiple-children"
   ],
   "props": [
    {
     "name": "children",
     "type": "List<Widget>",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.trackLight",
    "AppSpacing.radiusLG"
   ],
   "sizes": "radius16, border AppColorsV2.border, clip antiAlias, divider height1; 자체 padding 미지정. 토큰 값: AppSpacing.radiusLG=16.0. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L20 BorderRadius.circular(AppSpacing.radiusLG) | L21 Border.all(color: AppColorsV2.border) | L19 color: AppColorsV2.bgWhite | L21 color: AppColorsV2.border | L29 height: 1 | L29 color: AppColorsV2.trackLight",
   "file": "apps/rider/lib/shared/widgets/lists/bw_settings_tile_v2.dart",
   "usage_count": 3,
   "notes": "V2 전용 — BwSettingsRowV2와 같은 파일에 정의되며 각 자식 사이에만 Divider를 삽입한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-settings-tile-v2-bw-settings-card-v2 \"><div class=\"pv-rider-settings-card\"><div class=\"pv-rider-settings-row \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 16h14l-2-3V9A5 5 0 0 0 7 9v4zm5 4h4M12 2v2\"/></svg><span class=\"pv-rider-grow\">알림 설정</span><svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 5 7 7-7 7\"/></svg></div><div class=\"pv-rider-settings-row \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 10v7m0-11v1\"/></svg><span class=\"pv-rider-grow\">앱 버전</span><span class=\"pv-rider-muted\">2.0.0</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-shop-card",
   "platform": "rider",
   "name": "BwShopCard",
   "category": "카드",
   "purpose": "정비소 정보와 인증·광고·서비스 태그·전화 버튼을 표시한다.",
   "variants": [
    "free",
    "basic",
    "flag",
    "featured",
    "buwang",
    "certified",
    "image",
    "call-action"
   ],
   "props": [
    {
     "name": "name",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "address",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "phone",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "rating",
     "type": "double? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "reviewCount",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "certVerified",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "serviceTags",
     "type": "List<String> (default: const [])",
     "required": false,
     "default": null
    },
    {
     "name": "onCall",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "isBuwang",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.statusInfoBg",
    "AppColors.statusWarning",
    "AppColors.textMuted",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusPill",
    "AppSpacing.sm",
    "AppSpacing.xxs",
    "AppTypography.bodyMD",
    "AppTypography.bodySM",
    "AppTypography.caption",
    "AppTypography.numericSmall",
    "AppTypography.overline"
   ],
   "sizes": "radius 16, 패딩 16; shadow alpha .06·blur 16·offset(0,4); 이름 16/w700; 전화 44×44·radius 12·shadow blur 8; BUWANG 배지 9/w800·H6/V2·radius 4. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusPill=9999.0; AppSpacing.sm=10.0; AppSpacing.xxs=4.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정); AppTypography.overline(size=10, weight=FontWeight.w700, height=1.5). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L80 BorderRadius.circular(AppSpacing.radiusLG) | L81 Border.all(color: AppColors.borderDefault) | L83 BoxShadow( color: AppColors.brandPrimary.withValues(alpha: 0.06), blurRadius: 16, offset: const Offset(0, 4), ) | L99 BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusLG), ) | L100 Radius.circular(AppSpacing.radiusLG) | L115 EdgeInsets.all(AppSpacing.lg) | L131 EdgeInsets.symmetric( horizontal: 6, vertical: 2, ) | L138 Color(0xFF42A5F5) | L139 Color(0xFF1E88E5) | L142 BorderRadius.circular(4) | L146 TextStyle( color: Colors.white, fontSize: 9, fontWeight: FontWeight.w800, letterSpacing: 0.5, ) | L165 AppTypography.bodyMD.copyWith( fontWeight: FontWeight.w700, fontSize: 16, color: AppColors.textPrimary, ) | L189 AppTypography.bodySM.copyWith( color: AppColors.textMuted, ) | L216 AppTypography.caption.copyWith( color: AppColors.textMuted, ) | L238 AppTypography.caption.copyWith( color: AppColors.textSecondary, height: 1.3, ) | L261 AppTypography.caption.copyWith( color: AppColors.textSecondary, ) | L278 BorderRadius.circular( AppSpacing.radiusMD, ) | L282 BoxShadow( color: AppColors.brandPrimary.withValues( alpha: 0.3, ), blurRadius: 8, offset: const Offset(0, 2), ) | L79 color: AppColors.bgWhite | L81 color: AppColors.borderDefault | L84 color: AppColors.brandPrimary.withValues(alpha: 0.06) | L85 blurRadius: 16 | L104 height: 120 | L105 width: double.infinity | L147 color: Colors.white | L148 fontSize: 9 | L149 fontWeight: FontWeight.w800 | L150 letterSpacing: 0.5 | L154 width: AppSpacing.xxs | L166 fontWeight: FontWeight.w700 | L167 fontSize: 16 | L168 color: AppColors.textPrimary | L183 height: AppSpacing.xxs | L190 color: AppColors.textMuted | L204 size: 14 | L205 color: AppColors.statusWarning | L239 color: AppColors.textSecondary | L240 height: 1.3 | L274 width: 44 | L275 height: 44 | L277 color: AppColors.brandPrimary | L283 color: AppColors.brandPrimary.withValues( alpha: 0.3, ) | L286 blurRadius: 8 | L296 size: AppSpacing.iconMD | L306 height: AppSpacing.md | L308 spacing: AppSpacing.xxs | L309 runSpacing: AppSpacing.xxs | L332 EdgeInsets.symmetric(horizontal: 7, vertical: 2) | L335 BorderRadius.circular(6) | L344 AppTypography.overline.copyWith( color: AppColors.brandPrimary, fontWeight: FontWeight.w800, letterSpacing: 0.5, ) | L334 color: AppColors.statusInfoBg | L340 size: 10 | L365 EdgeInsets.symmetric( horizontal: AppSpacing.sm, vertical: AppSpacing.xxs, ) | L371 BorderRadius.circular(AppSpacing.radiusPill) | L376 AppTypography.caption.copyWith(color: AppColors.textSecondary) | L370 color: AppColors.bgSecondary | L392 EdgeInsets.symmetric(horizontal: 6, vertical: 2) | L400 AppTypography.overline.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.w800, letterSpacing: 0.5, ) | L394 color: AppColors.textMuted.withValues(alpha: 0.14)",
   "file": "apps/rider/lib/shared/widgets/cards/bw_shop_card.dart",
   "usage_count": 11,
   "notes": "V1 전용 — adTier!=free이고 URL이 있을 때만 높이 120 이미지를 표시한다. 인증 배지는 isBuwang && certVerified일 때 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. props 10개 제한으로 남은 생성자 파라미터: imageUrl: String? (default: null); distanceKm: double? (default: null); adTier: String (default: 'free'); isFeatured: bool (default: false). usage_count는 정의 파일을 제외한 직접 import/export 파일 수(11)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 11개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-shop-card pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:151.51515%;transform:scale(0.66)\"><div class=\"pv-rider-card pv-rider-shop-v1\"><div class=\"pv-rider-row \"><span class=\"pv-rider-partner\">BUWANG</span><b style=\"font-size:16px\">부왕 모터스</b></div><div class=\"pv-rider-row\" style=\"margin-top:8px;color:#F59E0A\"><svg class=\"pv-rider-icon pv-rider-star-filled\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 2 3 6.5 7 1-5 5 1.2 7L12 18l-6.2 3.5 1.2-7-5-5 7-1z\"/></svg><b>4.9</b><span class=\"pv-rider-muted\">(128)</span><span class=\"pv-rider-chip pv-rider-tone-info \">부왕 인증</span></div><div style=\"margin-top:8px\"><span class=\"pv-rider-muted\">서울 마포구 월드컵로 12</span></div><div class=\"pv-rider-row pv-rider-between\" style=\"margin-top:12px\"><div class=\"pv-rider-wrap\"><span class=\"pv-rider-chip pv-rider-tone-neutral \">엔진오일</span><span class=\"pv-rider-chip pv-rider-tone-neutral \">타이어</span></div><span class=\"pv-rider-shop-call\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 3 4 4-2 3a16 16 0 0 0 7 7l3-2 4 4-3 3C9 22 2 15 2 6z\"/></svg></span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-shop-card-v2",
   "platform": "rider",
   "name": "BwShopCardV2",
   "category": "카드",
   "purpose": "정비소의 대표 사진·메타 정보·태그·전화·미니 지도·예약 동작을 표시한다.",
   "variants": [
    "free",
    "basic",
    "flag",
    "featured",
    "hasBadge",
    "certVerified",
    "visited",
    "external",
    "open",
    "map-preview",
    "photo-thumb",
    "meta-one-line",
    "reserve-action"
   ],
   "props": [
    {
     "name": "name",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "distanceKm",
     "type": "double? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "rating",
     "type": "double? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "isOpen",
     "type": "bool? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "certVerified",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "hasBadge",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "adTier",
     "type": "String (default: 'free')",
     "required": false,
     "default": null
    },
    {
     "name": "isFeatured",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "address",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "phone",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.badgeNormalText",
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.certTagBg",
    "AppColorsV2.certTagFg",
    "AppColorsV2.linkAccent",
    "AppColorsV2.starGold",
    "AppColorsV2.surface",
    "AppColorsV2.textMuted",
    "AppColorsV2.textSecondary",
    "AppColorsV2.visitTagBg",
    "AppColorsV2.visitTagFg",
    "AppSpacing.lg",
    "AppSpacing.radiusPill",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppSpacingV2.radius6",
    "AppSpacingV2.radiusCertBadge",
    "AppSpacingV2.radiusShopCard",
    "AppTypographyV2.badge",
    "AppTypographyV2.cardSubtitle",
    "AppTypographyV2.cardTitle"
   ],
   "sizes": "바깥 radius 18(잉크 radius6*3), 패딩 16; 제목 16/w800; 대표 사진 썸네일 56×56·radius 10(imageUrl 있을 때만, 실패 시 surface 바탕+storefront 20); 사진이 있으면 메타는 한 줄 말줄임(아이콘 13·구분점 ·); 전화 40×40·radius 10; 미니맵 height 110·radius 12·마커 30×30; 예약 버튼 높이 40·radius 14. 토큰 값: AppSpacing.lg=16.0; AppSpacing.radiusPill=9999.0; AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppSpacingV2.radius6=6.0; AppSpacingV2.radiusCertBadge=7.0; AppSpacingV2.radiusShopCard=14.0; AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4); AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L150 BorderRadius.circular(AppSpacingV2.radius6 * 3) | L152 EdgeInsets.all(AppSpacing.lg) | L155 BorderRadius.circular(18) | L156 Border.all(color: AppColorsV2.border) | L341 AppTypographyV2.cardTitle.copyWith(fontSize: 16) | L239 BorderRadius.circular(AppSpacingV2.radiusShopCard) | L244 AppTypographyV2.cardSubtitle.copyWith( fontWeight: FontWeight.w700, color: Colors.white, ) | L119 iconColor: AppColorsV2.starGold | L245 fontWeight: FontWeight.w700 | L123 color: AppColorsV2.badgeNormalText | L154 color: AppColorsV2.bgWhite | L156 color: AppColorsV2.border | L330 width: AppSpacing.xs | L341 fontSize: 16 | L349 size: 18 | L271 color: AppColorsV2.textMuted | L209 height: AppSpacing.xs | L194 spacing: 6 | L195 runSpacing: 4 | L227 height: AppSpacing.sm | L215 width: AppSpacing.sm | L229 width: double.infinity | L230 height: 40 | L234 backgroundColor: AppColorsV2.linkAccent | L235 foregroundColor: Colors.white | L236 elevation: 0 | L246 color: Colors.white | L269 EdgeInsets.symmetric(horizontal: 6, vertical: 2) | L272 BorderRadius.circular(4) | L276 AppTypographyV2.badge.copyWith( color: AppColorsV2.textSecondary, height: 1.2, ) | L271 color: AppColorsV2.textMuted.withValues(alpha: 0.14) | L277 color: AppColorsV2.textSecondary | L278 height: 1.2 | L431 size: 13 | L458 width: 2 | L470 width: 3 | L471 height: 3 | L494 EdgeInsets.symmetric(horizontal: 8, vertical: 3) | L497 BorderRadius.circular(AppSpacingV2.radiusCertBadge) | L508 AppTypographyV2.badge.copyWith(color: fg, height: 1.2) | L496 color: bg | L503 size: 12 | L503 color: fg | L525 EdgeInsets.symmetric(horizontal: 10, vertical: 4) | L528 BorderRadius.circular(AppSpacing.radiusPill) | L533 AppTypographyV2.cardSubtitle.copyWith( fontWeight: FontWeight.w500, fontSize: 11, color: AppColorsV2.textSecondary, ) | L527 color: AppColorsV2.surface | L534 fontWeight: FontWeight.w500 | L535 fontSize: 11 | L555 Color(0xFF42A5F5) | L555 Color(0xFF1E88E5) | L561 TextStyle( color: Colors.white, fontSize: 9, fontWeight: FontWeight.w800, letterSpacing: 0.5, ) | L554 gradient: const LinearGradient( colors: [Color(0xFF42A5F5), Color(0xFF1E88E5)], ) | L563 fontSize: 9 | L564 fontWeight: FontWeight.w800 | L565 letterSpacing: 0.5 | L589 AppTypographyV2.cardSubtitle.copyWith( fontSize: 12, color: AppColorsV2.textSecondary, ) | L582 size: 14 | L583 width: 5 | L590 fontSize: 12 | L609 BorderRadius.circular(10) | L608 color: AppColorsV2.linkAccent | L614 width: 40 | L640 BorderRadius.circular(12) | L674 Size(30, 30) | L637 height: 110 | L674 size: const Size(30, 30) | L709 AppTypographyV2.cardSubtitle.copyWith( fontSize: 12, color: AppColorsV2.textMuted, ) | L693 height: height | L705 size: 16 | L706 width: 6 | L387 static const double size = 56 | L392 BorderRadius.circular(10) — 썸네일 | L402 color: AppColorsV2.surface — 썸네일 실패 자리 | L405 size: 20, color: AppColorsV2.textMuted | L296 _metaStyle = AppTypographyV2.cardSubtitle.copyWith( fontWeight: FontWeight.w700, color: item.color, fontFeatures: const [FontFeature.tabularFigures()], ) | L431 color: item.iconColor ?? AppColorsV2.textMuted | L439 maxLines: 1 | L440 overflow: TextOverflow.ellipsis",
   "file": "apps/rider/lib/shared/widgets/cards/bw_shop_card_v2.dart",
   "usage_count": 10,
   "notes": "V2 전용 — hasBadge와 certVerified는 독립 조건이다. 서비스 태그는 최대 4개. 전화 행은 phone이 비어 있지 않고 onCall이 있을 때 표시한다. 웹 또는 지도 미준비면 placeholder를 표시한다. imageUrl(2026-09-19 #1650)이 있으면 이름 왼쪽에 56 정사각 썸네일을 걸고 이름·메타 두 줄과 세로 중앙을 맞춘다 — 없으면 정사각형 자체를 그리지 않고(빈 회색 네모 금지) 메타는 종전대로 Wrap 이다. 사진이 있을 때만 메타가 한 줄 고정(말줄임)이며, 못 불러온 사진은 자리를 접지 않는다. 대표 사진(가게)과 showMapPreview(위치)는 다른 축이라 함께 나온다. 생성자 공통 super.key: Key?는 주요 props에서 생략. props 10개 제한으로 남은 생성자 파라미터: latitude: double? (default: null); longitude: double? (default: null); showMapPreview: bool (default: false); imageUrl: String? (default: null); serviceTags: List<String> (default: const []); visitedLabel: String? (default: null); externalLabel: String? (default: null); showChevron: bool (default: true); onTap: VoidCallback? (default: null); onCall: VoidCallback? (default: null); onReserve: VoidCallback? (default: null); reserveLabel: String? (default: null). usage_count는 정의 파일을 제외한 직접 import/export 파일 수(10)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 9개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-shop-card-v2 \"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:172.41379%;transform:scale(0.58)\"><div class=\"pv-rider-card\" style=\"border-radius:18px\"><div class=\"pv-rider-row \"><span class=\"pv-rider-partner\">BUWANG</span><strong class=\"pv-rider-grow\" style=\"font-size:16px;color:#16243A\">부왕 모터스</strong><svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 5 7 7-7 7\"/></svg></div><div class=\"pv-rider-row\" style=\"margin-top:8px;font-size:12px;font-weight:700\"><svg class=\"pv-rider-icon \" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg>0.8 km · <span style=\"color:#E8A41C\">★</span> 4.9 · <span style=\"color:#2F9E44\">영업 중</span></div><div class=\"pv-rider-wrap\" style=\"margin-top:10px\"><span class=\"pv-rider-chip pv-rider-tone-info pv-rider-shop-tag\"><svg class=\"pv-rider-icon \" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 2 9 4v6c0 6-9 10-9 10S3 18 3 12V6z\"/><path d=\"m7 12 3 3 7-7\"/></svg>부왕 인증</span><span class=\"pv-rider-chip pv-rider-shop-tag\" style=\"background:#EEF2F8;color:#16365F\">방문한 정비소</span></div><div class=\"pv-rider-wrap\" style=\"margin:10px 0\"><span class=\"pv-rider-chip pv-rider-tone-neutral \">엔진오일</span><span class=\"pv-rider-chip pv-rider-tone-neutral \">타이어</span></div><span class=\"pv-rider-cta pv-rider-shop-reserve\">예약하기</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-stats-card",
   "platform": "rider",
   "name": "BwStatsCard",
   "category": "카드",
   "purpose": "브랜드 그라데이션 카드 중앙에 통계 이름과 값을 표시한다.",
   "variants": [
    "default"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "value",
     "type": "String",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.ctaGradient",
    "AppColors.textWhite",
    "AppSpacing.cardPadding",
    "AppSpacing.radiusLG",
    "AppSpacing.xxs",
    "AppTypography.bodySM",
    "AppTypography.numericDisplay"
   ],
   "sizes": "전체 폭, 패딩 20, radius 16, 레이블 13·흰색 alpha .8, 값 24/w700, gap 4. 토큰 값: AppSpacing.cardPadding=20.0; AppSpacing.radiusLG=16.0; AppSpacing.xxs=4.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.numericDisplay(size=20, weight=FontWeight.w700, height=1.4). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L24 EdgeInsets.all(AppSpacing.cardPadding) | L27 BorderRadius.circular(AppSpacing.radiusLG) | L33 AppTypography.bodySM.copyWith( color: AppColors.textWhite.withValues(alpha: 0.8), ) | L40 AppTypography.numericDisplay.copyWith( color: AppColors.textWhite, fontSize: 24, ) | L23 width: double.infinity | L26 gradient: AppColors.ctaGradient | L34 color: AppColors.textWhite.withValues(alpha: 0.8) | L37 height: AppSpacing.xxs | L41 color: AppColors.textWhite | L42 fontSize: 24",
   "file": "apps/rider/lib/shared/widgets/cards/bw_stats_card.dart",
   "usage_count": 2,
   "notes": "V1 전용 — 통계 값은 이미 형식화된 String을 받는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-stats-card pv-rider-v1\"><div class=\"pv-rider-stats\"><span>이번 달 총 주행거리</span><strong>1,240 km</strong></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-stat-tile",
   "platform": "rider",
   "name": "BwStatTile",
   "category": "카드",
   "purpose": "통계 이름·값·단위를 작은 테두리 카드에 표시한다.",
   "variants": [
    "unit",
    "accent"
   ],
   "props": [
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "value",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "unit",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "accent",
     "type": "Color? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.textLabel",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.radiusMD",
    "AppSpacing.xxs",
    "AppTypography.numericDisplay",
    "AppTypography.numericSmall",
    "AppTypography.overline"
   ],
   "sizes": "패딩 16, radius 12, border 1.25; 레이블 10·letterSpacing 1.2, 값 24/w800·height 1, 단위 12, gap 4. 토큰 값: AppSpacing.lg=16.0; AppSpacing.radiusMD=12.0; AppSpacing.xxs=4.0; AppTypography.numericDisplay(size=20, weight=FontWeight.w700, height=1.4); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정); AppTypography.overline(size=10, weight=FontWeight.w700, height=1.5). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L35 EdgeInsets.all(AppSpacing.lg) | L38 BorderRadius.circular(AppSpacing.radiusMD) | L39 Border.all(color: AppColors.borderDefault, width: 1.25) | L46 AppTypography.overline.copyWith( color: AppColors.textLabel, letterSpacing: 1.2, ) | L61 AppTypography.numericDisplay.copyWith( fontSize: 24, fontWeight: FontWeight.w800, height: 1, color: accent ?? AppColors.textPrimary, ) | L75 AppTypography.numericSmall.copyWith( color: AppColors.textSecondary, ) | L37 color: AppColors.bgWhite | L39 color: AppColors.borderDefault | L39 width: 1.25 | L47 color: AppColors.textLabel | L48 letterSpacing: 1.2 | L53 height: AppSpacing.xxs | L62 fontSize: 24 | L63 fontWeight: FontWeight.w800 | L64 height: 1 | L65 color: accent ?? AppColors.textPrimary | L72 width: AppSpacing.xxs | L76 color: AppColors.textSecondary",
   "file": "apps/rider/lib/shared/widgets/cards/bw_stat_tile.dart",
   "usage_count": 3,
   "notes": "V1 전용 — 값 색상은 accent가 없으면 textPrimary이며 한 줄 말줄임을 적용한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-stat-tile pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:74px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-row \"><div class=\"pv-rider-stat\"><span>누적 주행</span><div><strong>12,840</strong> <small>km</small></div></div><div class=\"pv-rider-stat\"><span>정비 횟수</span><div><strong style=\"color:#005792\">8</strong> <small>회</small></div></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-vehicle-card",
   "platform": "rider",
   "name": "BwVehicleCard",
   "category": "카드",
   "purpose": "차량 이름·번호판·주행거리·브랜드 이미지와 기본 차량 여부를 표시한다.",
   "variants": [
    "nickname",
    "primary",
    "logo",
    "brand-initial",
    "fallback-icon",
    "mileage"
   ],
   "props": [
    {
     "name": "plateNumber",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "manufacturer",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "model",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "nickname",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "mileage",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "brandColor",
     "type": "Color? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "logoUrl",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "isPrimary",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.chevronColor",
    "AppColors.statusInfoBg",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppColors.textWhite",
    "AppSpacing.iconLG",
    "AppSpacing.iconSM",
    "AppSpacing.lg",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusMD",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.bodySM",
    "AppTypography.caption",
    "AppTypography.displayLogo",
    "AppTypography.headingMD",
    "AppTypography.numericSmall"
   ],
   "sizes": "패딩 16, radius 16, border 1.25, 이미지 56×56·radius 12, 간격 14, 제목 16/w700, 기본 배지 H8/V2·radius 6, 이니셜 26. 토큰 값: AppSpacing.iconLG=24.0; AppSpacing.iconSM=18.0; AppSpacing.lg=16.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusMD=12.0; AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.displayLogo(size=20, weight=FontWeight.w900, height=1.4); AppTypography.headingMD(size=16, weight=FontWeight.w700, height=미지정); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L69 EdgeInsets.all(AppSpacing.lg) | L72 BorderRadius.circular(AppSpacing.radiusLG) | L73 Border.all(color: AppColors.borderDefault, width: 1.25) | L85 BorderRadius.circular(AppSpacing.radiusMD) | L96 EdgeInsets.all(AppSpacing.xs) | L130 EdgeInsets.symmetric( horizontal: AppSpacing.xs, vertical: 2, ) | L136 BorderRadius.circular(6) | L140 AppTypography.caption.copyWith( color: AppColors.brandPrimary, fontWeight: FontWeight.w700, ) | L153 AppTypography.bodySM.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L168 AppTypography.numericSmall.copyWith( color: AppColors.textMuted, ) | L214 AppTypography.displayLogo.copyWith( color: AppColors.textWhite, fontSize: 26, height: 1, letterSpacing: 0, ) | L71 color: AppColors.bgWhite | L73 color: AppColors.borderDefault | L73 width: 1.25 | L79 width: 56 | L80 height: 56 | L84 color: brandColor ?? AppColors.bgSurface | L110 width: 14 | L128 width: AppSpacing.xs | L135 color: AppColors.statusInfoBg | L141 color: AppColors.brandPrimary | L142 fontWeight: FontWeight.w700 | L150 height: 2 | L154 color: AppColors.textSecondary | L155 fontWeight: FontWeight.normal | L161 height: AppSpacing.xxs | L169 color: AppColors.textMuted | L176 width: AppSpacing.sm | L197 color: AppColors.chevronColor | L198 size: AppSpacing.iconSM | L215 color: AppColors.textWhite | L216 fontSize: 26 | L217 height: 1 | L218 letterSpacing: 0 | L224 size: AppSpacing.iconLG",
   "file": "apps/rider/lib/shared/widgets/cards/bw_vehicle_card.dart",
   "usage_count": 4,
   "notes": "V1 전용 — 제목 우선순위는 nickname → manufacturer/model → 번호판이다. 제목에 번호판을 사용하면 메타 줄 번호판 중복을 생략한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(4)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-vehicle-card pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:86px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-card\" style=\"border-width:1.25px\"><div class=\"pv-rider-row \"><span class=\"pv-rider-vehicle-initial\">H</span><div class=\"pv-rider-grow\"><div class=\"pv-rider-row \"><b style=\"font-size:16px\">내 PCX</b><span class=\"pv-rider-vehicle-primary\">기본</span></div><p><span class=\"pv-rider-muted\">PCX125 · 1가2 3456</span></p><p style=\"margin-top:4px;font-size:12px;color:#005792;font-weight:700\">12,840 km</p></div><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 5 7 7-7 7\"/></svg></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-vehicle-card-v2",
   "platform": "rider",
   "name": "BwVehicleCardV2",
   "category": "카드",
   "purpose": "차량 사진 면과 정비 점수·소모품 상태 면을 뒤집어 보여주는 홈 카드다.",
   "variants": [
    "front",
    "back",
    "large-text",
    "urgent",
    "soon",
    "status-badge",
    "reservation-action",
    "sos-action",
    "quote-action",
    "consumable-warning"
   ],
   "props": [
    {
     "name": "score",
     "type": "double",
     "required": true,
     "default": null
    },
    {
     "name": "grade",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "subtitle",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "mileageLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onEditMileage",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "onReserve",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onViewReservation",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onDetail",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onSos",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.amberValue",
    "AppColorsV2.badgeNeedReplaceBg",
    "AppColorsV2.badgeNeedReplaceText",
    "AppColorsV2.badgeNormalBg",
    "AppColorsV2.badgeNormalText",
    "AppColorsV2.badgeSoonBg",
    "AppColorsV2.badgeSoonText",
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.certTagBg",
    "AppColorsV2.dangerStrong",
    "AppColorsV2.headingNavy",
    "AppColorsV2.linkAccent",
    "AppColorsV2.navActivePillBg",
    "AppColorsV2.sosBg",
    "AppColorsV2.sosBorder",
    "AppColorsV2.sosDangerEnd",
    "AppColorsV2.surface",
    "AppColorsV2.textSecondary",
    "AppColorsV2.trackLight",
    "AppColorsV2.vehicleCardGradient",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusPill",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppSpacingV2.radiusCertBadge",
    "AppSpacingV2.radiusFlipInner",
    "AppSpacingV2.radiusFrame",
    "AppTypographyV2.badge",
    "AppTypographyV2.body",
    "AppTypographyV2.bodyNumeric",
    "AppTypographyV2.cardSubtitle",
    "AppTypographyV2.cardTitle",
    "AppTypographyV2.eyebrow",
    "AppTypographyV2.heroNumber"
   ],
   "sizes": "높이 460/큰 글자 540, frame radius 24; 앞면 이미지 높이 300·padding H32/V28, 아래 padding 16; 뒷면 padding 12; 게이지 52·stroke 6 및 34/48·stroke 4; CTA 50/큰 글자 최소104, SOS/견적 40; 막대 minHeight 7; 상세 치수는 아래 행별 코드 규격. 토큰 값: AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusPill=9999.0; AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppSpacingV2.radiusCertBadge=7.0; AppSpacingV2.radiusFlipInner=13.0; AppSpacingV2.radiusFrame=24.0; AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1); AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45); AppTypographyV2.bodyNumeric(size=14, weight=FontWeight.w800, height=미지정); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4); AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25); AppTypographyV2.eyebrow(size=11, weight=FontWeight.w800, height=1.4); AppTypographyV2.heroNumber(size=32, weight=FontWeight.w800, height=1). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L184 BorderRadius.circular(AppSpacingV2.radiusFrame) | L185 Border.all(color: AppColorsV2.border) | L183 gradient: AppColorsV2.vehicleCardGradient | L185 color: AppColorsV2.border | L219 EdgeInsets.symmetric( horizontal: 32, vertical: 28, ) | L243 BorderRadius.circular(11) | L247 AppTypographyV2.cardTitle.copyWith(fontSize: 16) | L256 EdgeInsets.symmetric( horizontal: 11, vertical: 6, ) | L262 BorderRadius.circular( AppSpacing.radiusPill, ) | L271 AppTypographyV2.badge.copyWith( fontSize: 11, color: AppColorsV2.linkAccent, ) | L305 EdgeInsets.all(AppSpacing.lg) | L324 AppTypographyV2.cardTitle.copyWith( fontSize: 20, letterSpacing: -0.5, ) | L357 AppTypographyV2.cardTitle.copyWith( fontSize: 15, ) | L391 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w700, color: AppColorsV2.linkAccent, ) | L426 TextStyle(fontSize: 14, height: 1) | L448 Color(0xFFC7E3F8) | L508 EdgeInsets.fromLTRB(8, 24, 8, 0) | L198 height: BwVehicleCardV2.heightFor(context) | L200 color: AppColorsV2.bgWhite | L209 height: 300 | L210 width: double.infinity | L238 width: 36 | L239 height: 36 | L242 color: Colors.white.withValues(alpha: 0.85) | L247 fontSize: 16 | L261 color: Colors.white.withValues(alpha: 0.9) | L272 fontSize: 11 | L273 color: AppColorsV2.linkAccent | L276 width: 4 | L279 size: 13 | L325 fontSize: 20 | L326 letterSpacing: -0.5 | L348 height: 2 | L358 fontSize: 15 | L363 width: AppSpacing.sm | L392 fontWeight: FontWeight.w700 | L398 size: 16 | L426 fontSize: 14 | L426 height: 1 | L479 size: 18 | L510 scale: 1.12 | L517 size: 96 | L518 color: AppColorsV2.headingNavy.withValues(alpha: 0.3) | L536 EdgeInsets.all(AppSpacing.md) | L546 height: AppSpacing.sm | L569 height: 50 | L592 BorderRadius.circular(AppSpacing.radiusLG) | L598 EdgeInsets.only(top: largeText ? 48 : 0) | L618 AppTypographyV2.heroNumber.copyWith( fontSize: 20, ) | L627 AppTypographyV2.cardTitle.copyWith( fontSize: 16, ) | L658 EdgeInsets.symmetric(horizontal: 10, vertical: 5) | L661 BorderRadius.circular(AppSpacing.radiusPill) | L604 size: 52 | L606 strokeWidth: 6 | L609 width: AppSpacing.md | L640 height: 3 | L672 width: 3 | L742 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w800, ) | L727 backgroundColor: tileBg | L728 iconColor: accent | L743 fontWeight: FontWeight.w800 | L766 minHeight: 7 | L767 backgroundColor: AppColorsV2.trackLight | L772 height: AppSpacing.md | L773 color: AppColorsV2.trackLight | L832 TextStyle(fontWeight: FontWeight.w700) | L837 TextStyle(fontWeight: FontWeight.w800) | L841 AppTypographyV2.cardSubtitle.copyWith( fontFeatures: const [FontFeature.tabularFigures()], ) | L848 EdgeInsets.symmetric(horizontal: 6, vertical: 1) | L863 AppTypographyV2.badge.copyWith( color: AppColorsV2.badgeNormalText, ) | L822 spacing: AppSpacing.xs | L823 runSpacing: AppSpacing.xxs | L846 width: AppSpacing.xs | L850 color: AppColorsV2.badgeNormalBg | L858 size: 10 | L859 color: AppColorsV2.badgeNormalText | L902 BorderRadius.circular(12) | L916 Border.all( color: AppColorsV2.bgWhite, width: 1.5, ) | L934 AppTypographyV2.badge.copyWith( fontSize: 10, fontWeight: FontWeight.w600, color: AppColorsV2.textSecondary, ) | L891 width: 42 | L892 height: 42 | L901 color: bg | L904 size: 19 | L904 color: fg | L910 width: 16 | L911 height: 16 | L918 width: 1.5 | L923 size: 9 | L931 height: 5 | L935 fontSize: 10 | L936 fontWeight: FontWeight.w600 | L937 color: AppColorsV2.textSecondary | L958 EdgeInsets.symmetric(horizontal: 9, vertical: 3) | L963 AppTypographyV2.badge.copyWith(color: fg) | L976 EdgeInsets.symmetric(horizontal: 12, vertical: 10) | L979 BorderRadius.circular(AppSpacingV2.radiusFlipInner) | L1002 AppTypographyV2.cardSubtitle.copyWith( fontWeight: FontWeight.w600, color: AppColorsV2.headingNavy, ) | L985 width: 26 | L986 height: 26 | L989 color: AppColorsV2.navActivePillBg | L994 size: 14 | L998 width: 8 | L1004 color: AppColorsV2.headingNavy | L1025 EdgeInsets.fromLTRB(8, 6, 14, 6) | L1047 AppTypographyV2.heroNumber.copyWith(fontSize: 16) | L1051 AppTypographyV2.badge.copyWith( fontSize: 8, fontWeight: FontWeight.w700, color: AppColorsV2.textSecondary, ) | L1059 AppTypographyV2.badge.copyWith( fontSize: 12, color: AppColorsV2.headingNavy, ) | L1027 color: Colors.white.withValues(alpha: 0.92) | L1035 size: largeText ? 48 : 34 | L1037 strokeWidth: 4 | L1052 fontSize: 8 | L1060 fontSize: 12 | L1079 EdgeInsets.symmetric(horizontal: 8, vertical: 4) | L1083 BorderRadius.circular(AppSpacingV2.radiusCertBadge) | L1085 BoxShadow( color: Color(0x14000000), blurRadius: 6, offset: Offset(0, 2), ) | L1086 Color(0x14000000) | L1094 AppTypographyV2.badge.copyWith(color: AppColorsV2.badgeSoonText) | L1082 color: Colors.white | L1086 color: Color(0x14000000) | L1087 blurRadius: 6 | L1094 color: AppColorsV2.badgeSoonText | L1113 EdgeInsets.symmetric(horizontal: 12, vertical: 7) | L1125 AppTypographyV2.bodyNumeric.copyWith(fontSize: 15) | L1132 AppTypographyV2.badge.copyWith( fontSize: 9, fontWeight: FontWeight.w700, color: AppColorsV2.textSecondary, ) | L1115 color: AppColorsV2.surface | L1133 fontSize: 9 | L1209 Border.all(color: border) | L1218 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w700, fontSize: 13.5, color: fg, ) | L1205 height: 40 | L1209 color: border | L1215 width: 6 | L1220 fontSize: 13.5",
   "file": "apps/rider/lib/shared/widgets/cards/bw_vehicle_card_v2.dart",
   "usage_count": 4,
   "notes": "V2 전용 — 탭 시 500ms Y축 회전. textScale>1.3이면 큰 글자 레이아웃을 쓴다. 화면 폭<430이면 상세 링크를 축약한다. 소모품 사용률은 clamp한다. brandColor는 생성자에 있으나 렌더링에서 참조하지 않는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. props 10개 제한으로 남은 생성자 파라미터: onQuote: VoidCallback? (default: null); titleSuffix: String? (default: null); plate: String? (default: null); yearLabel: String? (default: null); ownerLine: String? (default: null); emblem: String? (default: null); imageUrl: String? (default: null); catalogImageUrl: String? (default: null); modelName: String? (default: null); displacementCc: int? (default: null); brandColor: Color? (default: null); statusBadgeLabel: String? (default: null); percentileLabel: String? (default: null); prevScore: int? (default: null); delta: int? (default: null); dueName: String? (default: null); dueStatusLabel: String? (default: null); dueRemainLabel: String? (default: null); dueFillUsed: double? (default: null); dueIsUrgent: bool (default: false); tip: String? (default: null); consumableOk: bool (default: true). usage_count는 정의 파일을 제외한 직접 import/export 파일 수(4)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-vehicle-card-v2 \"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:384.61538%;transform:scale(0.26)\"><div class=\"pv-rider-vehicle-v2\"><div class=\"pv-rider-vehicle-front\"><div class=\"pv-rider-front-image\"><svg class=\"pv-rider-scooter\" viewBox=\"0 0 240 140\" fill=\"none\" aria-label=\"PCX125 스쿠터 실루엣\" role=\"img\"><ellipse cx=\"120\" cy=\"124\" rx=\"99\" ry=\"5\" fill=\"#16243A\" opacity=\".09\"/><circle cx=\"52\" cy=\"105\" r=\"23\" fill=\"#16243A\"/><circle cx=\"192\" cy=\"105\" r=\"23\" fill=\"#16243A\"/><circle cx=\"52\" cy=\"105\" r=\"12\" fill=\"#94A3B8\"/><circle cx=\"192\" cy=\"105\" r=\"12\" fill=\"#94A3B8\"/><path d=\"M52 104 91 67h27l-10 30h54l20-46-16-16 6-8 21 14-5 24 21 23-8 10-24-18-12 31H70Z\" fill=\"#475569\"/><path d=\"M26 76c7-13 29-24 60-23h30l-10 22-38 13-40-2Z\" fill=\"#1F62C9\"/><path d=\"m111 96 9-34 17-9 20-25 16 8-12 42-19 23Z\" fill=\"#1F62C9\"/><path d=\"M47 53h67v9H39Z\" fill=\"#16243A\"/><path d=\"m158 28 9-16h15l-10 19Z\" fill=\"#16243A\" opacity=\".7\"/><path d=\"m178 27 12-7m-1-1 12 1\" stroke=\"#16243A\" stroke-width=\"4\" stroke-linecap=\"round\"/><path d=\"m28 76-7 4 5 7h10m121-34 10 3-6 12\" fill=\"#E2E9F4\"/></svg><span class=\"pv-rider-plate\">1가2 3456</span></div><div class=\"pv-rider-front-body\"><div class=\"pv-rider-row pv-rider-between\"><strong style=\"font-size:20px\">내 PCX</strong><span class=\"pv-rider-gauge\" style=\"width:52px;height:52px\"><svg width=\"52\" height=\"52\" viewBox=\"0 0 52 52\" aria-hidden=\"true\"><circle cx=\"26\" cy=\"26\" r=\"23\" fill=\"none\" stroke=\"#E5EBF5\" stroke-width=\"6\"/><path d=\"M26.000 3.000 A23 23 0 0 1 36.023 5.299\" fill=\"none\" stroke=\"#2d71d5\" stroke-width=\"6\"/><path d=\"M36.010 5.293 A23 23 0 0 1 44.034 11.725\" fill=\"none\" stroke=\"#2c6fd2\" stroke-width=\"6\"/><path d=\"M44.025 11.714 A23 23 0 0 1 48.449 20.997\" fill=\"none\" stroke=\"#2a6dcf\" stroke-width=\"6\"/><path d=\"M48.446 20.983 A23 23 0 0 1 48.389 31.267\" fill=\"none\" stroke=\"#296bcc\" stroke-width=\"6\"/><path d=\"M48.392 31.253 A23 23 0 0 1 43.865 40.486\" fill=\"none\" stroke=\"#2869ca\" stroke-width=\"6\"/><path d=\"M43.874 40.475 A23 23 0 0 1 35.779 46.817\" fill=\"none\" stroke=\"#2667c7\" stroke-width=\"6\"/><path d=\"M35.792 46.811 A23 23 0 0 1 25.744 48.999\" fill=\"none\" stroke=\"#2565c4\" stroke-width=\"6\"/><path d=\"M25.758 48.999 A23 23 0 0 1 15.760 46.595\" fill=\"none\" stroke=\"#2363c2\" stroke-width=\"6\"/><path d=\"M15.773 46.601 A23 23 0 0 1 7.817 40.085\" fill=\"none\" stroke=\"#2261bf\" stroke-width=\"6\"/><path d=\"M7.826 40.096 A23 23 0 0 1 3.499 30.767\" fill=\"none\" stroke=\"#205fbc\" stroke-width=\"6\"/><path d=\"M3.502 30.781 A23 23 0 0 1 3.668 20.498\" fill=\"none\" stroke=\"#1f5db9\" stroke-width=\"6\"/><path d=\"M3.664 20.512 A23 23 0 0 1 8.279 11.338\" fill=\"none\" stroke=\"#1e5bb7\" stroke-width=\"6\"/><circle cx=\"26\" cy=\"3\" r=\"3\" fill=\"#2e72d6\"/><circle cx=\"8.279\" cy=\"11.338\" r=\"3\" fill=\"#1d5ab5\"/></svg><span class=\"pv-rider-gauge-value\" style=\"font-size:17.68px\">86</span></span></div><div><span class=\"pv-rider-muted\">HONDA · PCX125</span></div><div class=\"pv-rider-row pv-rider-between\" style=\"margin-top:10px\"><b>12,840 <small>km</small></b><span class=\"pv-rider-link\">주행거리 수정</span></div><div class=\"pv-rider-row \"><span class=\"pv-rider-vehicle-sos\"><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 17V11a7 7 0 0 1 14 0v6M3 17h18v4H3zM12 1v2M1 6l2 2m20-2-2 2\"/></svg>SOS</span><span class=\"pv-rider-vehicle-quote\"><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 2h10l5 5v15H5zM14 2v6h6M8 12h9m-9 4h9\"/></svg>견적 요청</span></div></div></div><div class=\"pv-rider-vehicle-back\"><div class=\"pv-rider-card\"><div class=\"pv-rider-row \"><span class=\"pv-rider-gauge\" style=\"width:52px;height:52px\"><svg width=\"52\" height=\"52\" viewBox=\"0 0 52 52\" aria-hidden=\"true\"><circle cx=\"26\" cy=\"26\" r=\"23\" fill=\"none\" stroke=\"#E5EBF5\" stroke-width=\"6\"/><path d=\"M26.000 3.000 A23 23 0 0 1 36.023 5.299\" fill=\"none\" stroke=\"#2d71d5\" stroke-width=\"6\"/><path d=\"M36.010 5.293 A23 23 0 0 1 44.034 11.725\" fill=\"none\" stroke=\"#2c6fd2\" stroke-width=\"6\"/><path d=\"M44.025 11.714 A23 23 0 0 1 48.449 20.997\" fill=\"none\" stroke=\"#2a6dcf\" stroke-width=\"6\"/><path d=\"M48.446 20.983 A23 23 0 0 1 48.389 31.267\" fill=\"none\" stroke=\"#296bcc\" stroke-width=\"6\"/><path d=\"M48.392 31.253 A23 23 0 0 1 43.865 40.486\" fill=\"none\" stroke=\"#2869ca\" stroke-width=\"6\"/><path d=\"M43.874 40.475 A23 23 0 0 1 35.779 46.817\" fill=\"none\" stroke=\"#2667c7\" stroke-width=\"6\"/><path d=\"M35.792 46.811 A23 23 0 0 1 25.744 48.999\" fill=\"none\" stroke=\"#2565c4\" stroke-width=\"6\"/><path d=\"M25.758 48.999 A23 23 0 0 1 15.760 46.595\" fill=\"none\" stroke=\"#2363c2\" stroke-width=\"6\"/><path d=\"M15.773 46.601 A23 23 0 0 1 7.817 40.085\" fill=\"none\" stroke=\"#2261bf\" stroke-width=\"6\"/><path d=\"M7.826 40.096 A23 23 0 0 1 3.499 30.767\" fill=\"none\" stroke=\"#205fbc\" stroke-width=\"6\"/><path d=\"M3.502 30.781 A23 23 0 0 1 3.668 20.498\" fill=\"none\" stroke=\"#1f5db9\" stroke-width=\"6\"/><path d=\"M3.664 20.512 A23 23 0 0 1 8.279 11.338\" fill=\"none\" stroke=\"#1e5bb7\" stroke-width=\"6\"/><circle cx=\"26\" cy=\"3\" r=\"3\" fill=\"#2e72d6\"/><circle cx=\"8.279\" cy=\"11.338\" r=\"3\" fill=\"#1d5ab5\"/></svg><span class=\"pv-rider-gauge-value\" style=\"font-size:17.68px\">86</span></span><div><strong style=\"font-size:20px\">관리 우수</strong><p><span class=\"pv-rider-muted\">상위 15% · 지난달 80점</span></p></div></div></div><div class=\"pv-rider-card\" style=\"margin-top:10px\"><strong>소모품 상태</strong><div class=\"pv-rider-wrap\" style=\"margin:10px 0\"><span class=\"pv-rider-chip pv-rider-tone-error \">교체 필요 1</span><span class=\"pv-rider-chip pv-rider-tone-warning \">교체 임박 1</span></div><p><span class=\"pv-rider-muted\">엔진오일 · 120km 초과</span></p><div class=\"pv-rider-track\" style=\"margin:8px 0 14px\"><span style=\"width:100%;background:#E03131\"></span></div><p><span class=\"pv-rider-muted\">타이어 · 800km 남음</span></p><div class=\"pv-rider-track\" style=\"margin-top:8px\"><span style=\"width:80%;background:#E8870B\"></span></div></div><div class=\"pv-rider-card\" style=\"padding:10px 12px;border-radius:13px;margin-top:10px;font-size:12px\">지금 점검하고 안전하게 달려요.</div><span class=\"pv-rider-cta \">정비 예약하기</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-model-checklist-card-v2",
   "platform": "rider",
   "name": "ModelChecklistCardV2",
   "category": "카드",
   "purpose": "기종별 필수 점검 시트로 들어가는 옅은 파랑 진입 카드를 표시한다.",
   "variants": [
    "default",
    "체크리스트 없음(SizedBox.shrink)",
    "topSpacing"
   ],
   "props": [
    {
     "name": "category",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "displacement",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "model",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "topSpacing",
     "type": "double (default: 0)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.headingNavy",
    "AppColorsV2.linkAccent",
    "AppColorsV2.navActivePillBg",
    "AppColorsV2.paleCardBorder",
    "AppColorsV2.textMuted",
    "AppColorsV2.textSecondary",
    "AppSpacing.md",
    "AppSpacing.xxs",
    "AppSpacingV2.radiusFlipInner",
    "AppTypographyV2.body",
    "AppTypographyV2.cardSubtitle"
   ],
   "sizes": "패딩 12, radius 13, 배경 #E7EEFB, 보더 #C9DBF7, 좌측 아이콘 22, 우측 셰브런 20, 제목 body 14/w700(1줄 말줄임), 부제 cardSubtitle 12/w500. 토큰 값: AppSpacing.md=12.0; AppSpacing.xxs=4.0; AppSpacingV2.radiusFlipInner=13.0; AppColorsV2.navActivePillBg=#E7EEFB; AppColorsV2.paleCardBorder=#C9DBF7; AppColorsV2.linkAccent=#1F62C9; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L45 BorderRadius.circular(AppSpacingV2.radiusFlipInner) | L56 EdgeInsets.all(AppSpacing.md) | L58 color: AppColorsV2.navActivePillBg | L59 Border.all(color: AppColorsV2.paleCardBorder) | L66 size: 22 | L67 color: AppColorsV2.linkAccent | L69 SizedBox(width: AppSpacing.md) | L77 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w700, color: AppColorsV2.headingNavy, ) | L84 SizedBox(height: AppSpacing.xxs) | L87 AppTypographyV2.cardSubtitle.copyWith( color: AppColorsV2.textSecondary, ) | L96 size: 20 | L97 color: AppColorsV2.textMuted",
   "file": "apps/rider/lib/features/vehicle/presentation/model_checklist_card_v2.dart",
   "usage_count": 2,
   "notes": "V2 전용 — 기종에 맞는 체크리스트(checklistFor)가 없으면 SizedBox.shrink 로 아무것도 그리지 않는다. 제목의 이름은 model 이 비면 체크리스트 kindLabel 로 대체한다. 탭하면 showModelChecklistSheet(useV2Theme: true) 를 연다. 테스트 앵커 Key(model-checklist-card-v2). 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다. 담기는 근거: 차량 상세 V2·차량 등록 플로우 V2 2곳에서 같은 카드를 쓴다(정의 파일 주석도 공통으로 사용하는 카드로 못박는다).",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "rider-bw-mileage-chart",
   "platform": "rider",
   "name": "BwMileageChart",
   "category": "차트",
   "purpose": "누적 주행거리 추세와 처음·마지막 날짜를 선형 차트로 표시한다.",
   "variants": [
    "empty",
    "single-point",
    "multiple-points",
    "flat-range"
   ],
   "props": [
    {
     "name": "points",
     "type": "List<BwMileagePoint>",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String (default: '주행거리 추세')",
     "required": false,
     "default": null
    },
    {
     "name": "height",
     "type": "double (default: 140)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderLight",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppSpacing.cardPadding",
    "AppSpacing.lg",
    "AppSpacing.radiusMD",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppSpacing.xxs",
    "AppTypography.bodyMD",
    "AppTypography.caption",
    "AppTypography.headingMD",
    "AppTypography.numericSmall"
   ],
   "sizes": "plot 기본 높이 140, 카드 padding 20·radius 12, plot 상하 pad 6, 선 stroke 2, 일반 점 radius 3·마지막 점 radius 4, 채움 alpha .18→0. 토큰 값: AppSpacing.cardPadding=20.0; AppSpacing.lg=16.0; AppSpacing.radiusMD=12.0; AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppSpacing.xxl=24.0; AppSpacing.xxs=4.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.headingMD(size=16, weight=FontWeight.w700, height=미지정); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L58 EdgeInsets.all(AppSpacing.cardPadding) | L61 BorderRadius.circular(AppSpacing.radiusMD) | L62 Border.all(color: AppColors.borderLight) | L87 AppTypography.numericSmall.copyWith( color: AppColors.textMuted, ) | L93 AppTypography.numericSmall.copyWith( color: AppColors.brandPrimary, ) | L57 width: double.infinity | L60 color: AppColors.bgWhite | L62 color: AppColors.borderLight | L68 height: AppSpacing.lg | L73 height: height | L81 height: AppSpacing.sm | L88 color: AppColors.textMuted | L94 color: AppColors.brandPrimary | L117 EdgeInsets.symmetric(vertical: AppSpacing.xxl) | L124 AppTypography.bodyMD.copyWith( color: AppColors.textMuted, fontWeight: FontWeight.normal, ) | L120 size: 32 | L121 height: AppSpacing.xs | L126 fontWeight: FontWeight.normal | L129 height: AppSpacing.xxs | L192 ..color = AppColors.brandPrimary | L193 ..strokeWidth = 2 | L202 ..color = AppColors.bgWhite",
   "file": "apps/rider/lib/shared/widgets/charts/bw_mileage_chart.dart",
   "usage_count": 2,
   "notes": "V1 전용 — 입력 순서를 그대로 사용하며 내부 날짜 정렬은 없다. 값이 모두 같으면 높이 중앙에 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-mileage-chart pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:181.81818%;transform:scale(0.55)\"><div class=\"pv-rider-card\" style=\"padding:20px;border-radius:12px\"><b>주행거리 추세</b><div class=\"pv-rider-mileage-plot\"><svg viewBox=\"0 0 320 140\" preserveAspectRatio=\"none\" width=\"100%\" height=\"140\" aria-label=\"9월 1일부터 8일까지 주행거리 증가\" role=\"img\"><path d=\"M0 120H320M0 70H320M0 20H320\" stroke=\"#E2E8F0\" stroke-dasharray=\"3 4\"/><path d=\"M4 120 76 99 154 78 230 44 316 6V134H4Z\" fill=\"#005792\" opacity=\".10\"/><path d=\"M4 120 76 99 154 78 230 44 316 6\" fill=\"none\" stroke=\"#005792\" stroke-width=\"2\"/><g fill=\"#005792\"><circle cx=\"4\" cy=\"120\" r=\"3\"/><circle cx=\"76\" cy=\"99\" r=\"3\"/><circle cx=\"154\" cy=\"78\" r=\"3\"/><circle cx=\"230\" cy=\"44\" r=\"3\"/><circle cx=\"316\" cy=\"6\" r=\"4\"/></g></svg></div><div class=\"pv-rider-row pv-rider-between\"><span class=\"pv-rider-muted\">09.01</span><span class=\"pv-rider-muted\">09.08</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-score-gauge",
   "platform": "rider",
   "name": "ScoreGauge",
   "category": "차트",
   "purpose": "0~100 정비 점수를 그라데이션 도넛과 선택적 숫자로 표시한다.",
   "variants": [
    "showValue",
    "hideValue",
    "subLabel",
    "zero-score"
   ],
   "props": [
    {
     "name": "score",
     "type": "double",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "double (default: 56)",
     "required": false,
     "default": null
    },
    {
     "name": "strokeWidth",
     "type": "double (default: 6)",
     "required": false,
     "default": null
    },
    {
     "name": "showValue",
     "type": "bool (default: true)",
     "required": false,
     "default": null
    },
    {
     "name": "subLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.ctaGradientEnd",
    "AppColorsV2.ctaGradientStart",
    "AppColorsV2.gaugeTrack",
    "AppColorsV2.textSecondary",
    "AppTypographyV2.badge",
    "AppTypographyV2.heroNumber"
   ],
   "sizes": "기본 size 56×56·strokeWidth 6, 숫자 fontSize=size*.34·height1, subLabel=size*.11; radius=(size.shortestSide-strokeWidth)/2, 시작 각도 -pi/2. 토큰 값: AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1); AppTypographyV2.heroNumber(size=32, weight=FontWeight.w800, height=1). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L48 AppTypographyV2.heroNumber.copyWith( fontSize: size * 0.34, letterSpacing: -0.5, height: 1, ) | L57 AppTypographyV2.badge.copyWith( fontSize: size * 0.11, color: AppColorsV2.textSecondary, letterSpacing: 0.5, ) | L34 width: size | L35 height: size | L49 fontSize: size * 0.34 | L50 letterSpacing: -0.5 | L51 height: 1 | L58 fontSize: size * 0.11 | L59 color: AppColorsV2.textSecondary | L60 letterSpacing: 0.5 | L85 ..strokeWidth = strokeWidth | L87 ..color = AppColorsV2.gaugeTrack",
   "file": "apps/rider/lib/shared/widgets/charts/score_gauge.dart",
   "usage_count": 4,
   "notes": "V2 전용 — 파일명에는 V2가 없지만 V2 색상·타이포만 import한다. 원호는 score를 clamp하고 중앙 숫자는 원래 score.round()를 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(4)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-score-gauge \"><div class=\"pv-rider-row \"><span class=\"pv-rider-gauge\" style=\"width:56px;height:56px\"><svg width=\"56\" height=\"56\" viewBox=\"0 0 56 56\" aria-hidden=\"true\"><circle cx=\"28\" cy=\"28\" r=\"25\" fill=\"none\" stroke=\"#E5EBF5\" stroke-width=\"6\"/><path d=\"M28.000 3.000 A25 25 0 0 1 38.895 5.499\" fill=\"none\" stroke=\"#2d71d5\" stroke-width=\"6\"/><path d=\"M38.881 5.492 A25 25 0 0 1 47.602 12.484\" fill=\"none\" stroke=\"#2c6fd2\" stroke-width=\"6\"/><path d=\"M47.592 12.471 A25 25 0 0 1 52.401 22.562\" fill=\"none\" stroke=\"#2a6dcf\" stroke-width=\"6\"/><path d=\"M52.398 22.547 A25 25 0 0 1 52.336 33.724\" fill=\"none\" stroke=\"#296bcc\" stroke-width=\"6\"/><path d=\"M52.339 33.709 A25 25 0 0 1 47.418 43.746\" fill=\"none\" stroke=\"#2869ca\" stroke-width=\"6\"/><path d=\"M47.428 43.733 A25 25 0 0 1 38.630 50.628\" fill=\"none\" stroke=\"#2667c7\" stroke-width=\"6\"/><path d=\"M38.644 50.621 A25 25 0 0 1 27.722 52.998\" fill=\"none\" stroke=\"#2565c4\" stroke-width=\"6\"/><path d=\"M27.738 52.999 A25 25 0 0 1 16.869 50.385\" fill=\"none\" stroke=\"#2363c2\" stroke-width=\"6\"/><path d=\"M16.883 50.392 A25 25 0 0 1 8.236 43.310\" fill=\"none\" stroke=\"#2261bf\" stroke-width=\"6\"/><path d=\"M8.246 43.322 A25 25 0 0 1 3.543 33.181\" fill=\"none\" stroke=\"#205fbc\" stroke-width=\"6\"/><path d=\"M3.546 33.197 A25 25 0 0 1 3.726 22.020\" fill=\"none\" stroke=\"#1f5db9\" stroke-width=\"6\"/><path d=\"M3.722 22.036 A25 25 0 0 1 8.738 12.063\" fill=\"none\" stroke=\"#1e5bb7\" stroke-width=\"6\"/><circle cx=\"28\" cy=\"3\" r=\"3\" fill=\"#2e72d6\"/><circle cx=\"8.738\" cy=\"12.063\" r=\"3\" fill=\"#1d5ab5\"/></svg><span class=\"pv-rider-gauge-value\" style=\"font-size:19.040000000000003px\">86<span style=\"font-size:6.16px\">점수</span></span></span><span class=\"pv-rider-gauge\" style=\"width:56px;height:56px\"><svg width=\"56\" height=\"56\" viewBox=\"0 0 56 56\" aria-hidden=\"true\"><circle cx=\"28\" cy=\"28\" r=\"25\" fill=\"none\" stroke=\"#E5EBF5\" stroke-width=\"6\"/><path d=\"M28.000 3.000 A25 25 0 0 1 33.852 3.695\" fill=\"none\" stroke=\"#2e71d5\" stroke-width=\"6\"/><path d=\"M33.836 3.691 A25 25 0 0 1 39.364 5.732\" fill=\"none\" stroke=\"#2d70d4\" stroke-width=\"6\"/><path d=\"M39.350 5.725 A25 25 0 0 1 44.249 9.001\" fill=\"none\" stroke=\"#2c6fd2\" stroke-width=\"6\"/><path d=\"M44.237 8.991 A25 25 0 0 1 48.235 13.319\" fill=\"none\" stroke=\"#2b6ed1\" stroke-width=\"6\"/><path d=\"M48.226 13.306 A25 25 0 0 1 51.104 18.449\" fill=\"none\" stroke=\"#2b6dd0\" stroke-width=\"6\"/><path d=\"M51.098 18.434 A25 25 0 0 1 52.695 24.107\" fill=\"none\" stroke=\"#2a6cce\" stroke-width=\"6\"/><path d=\"M52.693 24.091 A25 25 0 0 1 52.922 29.979\" fill=\"none\" stroke=\"#296bcd\" stroke-width=\"6\"/><path d=\"M52.923 29.964 A25 25 0 0 1 51.771 35.743\" fill=\"none\" stroke=\"#286acb\" stroke-width=\"6\"/><path d=\"M51.776 35.728 A25 25 0 0 1 49.306 41.078\" fill=\"none\" stroke=\"#2869ca\" stroke-width=\"6\"/><path d=\"M49.314 41.065 A25 25 0 0 1 45.664 45.691\" fill=\"none\" stroke=\"#2768c8\" stroke-width=\"6\"/><path d=\"M45.675 45.680 A25 25 0 0 1 41.046 49.326\" fill=\"none\" stroke=\"#2667c7\" stroke-width=\"6\"/><path d=\"M41.059 49.318 A25 25 0 0 1 35.722 51.778\" fill=\"none\" stroke=\"#2566c6\" stroke-width=\"6\"/><circle cx=\"28\" cy=\"3\" r=\"3\" fill=\"#2e72d6\"/><circle cx=\"35.722\" cy=\"51.778\" r=\"3\" fill=\"#2565c5\"/></svg></span><span class=\"pv-rider-gauge\" style=\"width:56px;height:56px\"><svg width=\"56\" height=\"56\" viewBox=\"0 0 56 56\" aria-hidden=\"true\"><circle cx=\"28\" cy=\"28\" r=\"25\" fill=\"none\" stroke=\"#E5EBF5\" stroke-width=\"6\"/></svg><span class=\"pv-rider-gauge-value\" style=\"font-size:19.040000000000003px\">0</span></span></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-row pv-rider-between\"><span class=\"pv-rider-muted\">정비 점수</span><span class=\"pv-rider-muted\">값 숨김</span><span class=\"pv-rider-muted\">0점</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-back-header",
   "platform": "rider",
   "name": "BwBackHeader",
   "category": "내비",
   "purpose": "뒤로가기 동작·제목·선택적 우측 위젯을 표시한다.",
   "variants": [
    "trailing",
    "onBack"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "trailing",
     "type": "Widget? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onBack",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderLight",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.sm",
    "AppTypography.headingLG"
   ],
   "sizes": "바깥 padding H16/V12, 뒤로가기 내부 V10, 아이콘 20·gap 10, 제목 headingLG 18/w700·height1.56, 하단 borderLight. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.sm=10.0; AppTypography.headingLG(size=18, weight=FontWeight.w700, height=1.56). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L22 EdgeInsets.symmetric( vertical: AppSpacing.md, horizontal: AppSpacing.lg, ) | L28 BorderSide(color: AppColors.borderLight) | L38 EdgeInsets.symmetric(vertical: AppSpacing.sm) | L21 width: double.infinity | L27 color: AppColors.bgWhite | L28 color: AppColors.borderLight | L44 size: AppSpacing.iconMD | L46 width: AppSpacing.sm",
   "file": "apps/rider/lib/shared/widgets/headers/back_header.dart",
   "usage_count": 38,
   "notes": "V1 전용 — onBack이 null이면 자동 Navigator 동작을 연결하지 않는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(38)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 38개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-back-header pv-rider-v1\"><div class=\"pv-rider-back-v1\"><div class=\"pv-rider-row \"><span style=\"padding:10px 0\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m15 5-7 7 7 7\"/></svg></span><b class=\"pv-rider-grow\">정비 예약</b><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 10v7m0-11v1\"/></svg></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-back-header-v2",
   "platform": "rider",
   "name": "BwBackHeaderV2",
   "category": "내비",
   "purpose": "V2 배경과 제목 스타일로 뒤로가기 헤더를 표시한다.",
   "variants": [
    "trailing",
    "custom-back",
    "default-maybePop"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onBack",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "trailing",
     "type": "Widget? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.headingNavy",
    "AppColorsV2.surface",
    "AppSpacing.sm",
    "AppSpacing.xs",
    "AppTypographyV2.cardTitle"
   ],
   "sizes": "padding H10/V8, chevron 28, 제목 18/w800·height1.25. 토큰 값: AppSpacing.sm=10.0; AppSpacing.xs=8.0; AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L26 EdgeInsets.symmetric( horizontal: AppSpacing.sm, vertical: AppSpacing.xs, ) | L41 AppTypographyV2.cardTitle.copyWith(fontSize: 18) | L25 color: AppColorsV2.surface | L34 size: 28 | L35 color: AppColorsV2.headingNavy | L41 fontSize: 18",
   "file": "apps/rider/lib/shared/widgets/headers/bw_back_header_v2.dart",
   "usage_count": 37,
   "notes": "V2 전용 — V2 토큰 구현이며 riding_timeline 등 비버전 경로에서도 재사용한다. onBack 생략 시 Navigator.maybePop을 호출한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(37)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 37개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-back-header-v2 \"><div class=\"pv-rider-back-v2\"><div class=\"pv-rider-row \"><svg class=\"pv-rider-icon \" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m15 5-7 7 7 7\"/></svg><strong class=\"pv-rider-grow\">내 차량 관리</strong><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 10v7m0-11v1\"/></svg></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bottom-nav-bar",
   "platform": "rider",
   "name": "BwBottomNavBar",
   "category": "내비",
   "purpose": "홈·정비·정비소·MY 네 개 탭과 현재 선택 상태를 표시한다.",
   "variants": [
    "active",
    "inactive",
    "large-text"
   ],
   "props": [
    {
     "name": "currentIndex",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "ValueChanged<int>",
     "required": true,
     "default": null
    },
    {
     "name": "itemKeys",
     "type": "List<Key?>? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgWhite",
    "AppColors.borderLight",
    "AppColors.brandPrimaryDark",
    "AppColors.textMuted",
    "AppSpacing.iconMD",
    "AppSpacing.md",
    "AppSpacing.navHeight",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusXL",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypography.displayNavActive",
    "AppTypography.displayNavInactive"
   ],
   "sizes": "height80, textScale>1.3이면112; 상단 radius20·border1; 아이콘20·gap4; active pill radius16·H12/V8(큰 글자 H8/V4); label10/w700·최대1/2줄. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.md=12.0; AppSpacing.navHeight=80.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusXL=20.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypography.displayNavActive(size=10, weight=FontWeight.w700, height=1.5); AppTypography.displayNavInactive(size=10, weight=FontWeight.w700, height=1.5). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L37 BorderSide(color: AppColors.borderLight, width: 1) | L38 BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusXL), ) | L39 Radius.circular(AppSpacing.radiusXL) | L99 EdgeInsets.symmetric( vertical: largeText ? AppSpacing.xxs : AppSpacing.xs, horizontal: largeText ? AppSpacing.xs : AppSpacing.md, ) | L105 BorderRadius.circular(AppSpacing.radiusLG) | L36 color: AppColors.bgWhite | L37 color: AppColors.borderLight | L37 width: 1 | L47 height: AppSpacing.navHeight + (largeText ? 32 : 0) | L83 size: AppSpacing.iconMD | L83 color: color | L84 height: AppSpacing.xxs | L104 color: AppColors.bgSecondary",
   "file": "apps/rider/lib/shared/widgets/navigation/bottom_nav_bar.dart",
   "usage_count": 3,
   "notes": "V1 전용 — app_router.dart가 V1/V2 내비를 분기한다. SafeArea의 아래 여백은 기기에 따라 추가된다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bottom-nav-bar pv-rider-v1\"><div class=\"pv-rider-nav pv-rider-nav-v1\"><span class=\"pv-rider-nav-item pv-rider-nav-active\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m2 11 10-9 10 9M5 9v12h14V9M9 21v-8h6v8\"/></svg><span>홈</span></span><span class=\"pv-rider-nav-item \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"17\" r=\"4\"/><circle cx=\"19\" cy=\"17\" r=\"4\"/><path d=\"M5 17h9l-4-9H5m7-4h4l3 13M8 8l-3 9\"/></svg><span>정비</span></span><span class=\"pv-rider-nav-item \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg><span>정비소</span></span><span class=\"pv-rider-nav-item \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg><span>MY</span></span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-bottom-nav-bar-v2",
   "platform": "rider",
   "name": "BwBottomNavBarV2",
   "category": "내비",
   "purpose": "홈·차량관리·정비소·MY 네 개 V2 탭을 표시한다.",
   "variants": [
    "active",
    "inactive"
   ],
   "props": [
    {
     "name": "currentIndex",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "ValueChanged<int>",
     "required": true,
     "default": null
    },
    {
     "name": "itemKeys",
     "type": "List<Key?>? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.linkAccent",
    "AppColorsV2.navActivePillBg",
    "AppColorsV2.textMuted",
    "AppSpacing.iconMD",
    "AppSpacing.navHeight",
    "AppSpacing.radiusLG",
    "AppSpacing.radiusXL",
    "AppSpacing.xs",
    "AppSpacing.xxs",
    "AppTypographyV2.badge"
   ],
   "sizes": "height80, 상단 radius20·border1, 아이콘20·gap4, active pill H14/V8·radius16; label10/w700·letterSpacing1·height1.2. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.navHeight=80.0; AppSpacing.radiusLG=16.0; AppSpacing.radiusXL=20.0; AppSpacing.xs=8.0; AppSpacing.xxs=4.0; AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L40 BorderSide(color: AppColorsV2.border, width: 1) | L41 BorderRadius.vertical( top: Radius.circular(AppSpacing.radiusXL), ) | L42 Radius.circular(AppSpacing.radiusXL) | L82 AppTypographyV2.badge.copyWith( fontSize: 10, fontWeight: FontWeight.w700, letterSpacing: 1, color: color, height: 1.2, ) | L96 EdgeInsets.symmetric( vertical: AppSpacing.xs, horizontal: 14, ) | L102 BorderRadius.circular(AppSpacing.radiusLG) | L39 color: AppColorsV2.bgWhite | L40 color: AppColorsV2.border | L40 width: 1 | L48 height: AppSpacing.navHeight | L78 size: AppSpacing.iconMD | L78 color: color | L79 height: AppSpacing.xxs | L83 fontSize: 10 | L84 fontWeight: FontWeight.w700 | L85 letterSpacing: 1 | L87 height: 1.2 | L101 color: AppColorsV2.navActivePillBg",
   "file": "apps/rider/lib/shared/widgets/navigation/bw_bottom_nav_bar_v2.dart",
   "usage_count": 2,
   "notes": "V2 전용 — app_router.dart에서 V2 분기 내비로 사용한다. SafeArea 아래 여백은 별도이며 V1 내비의 큰 글자 높이 분기는 없다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-bottom-nav-bar-v2 \"><div class=\"pv-rider-nav pv-rider-nav-v2\"><span class=\"pv-rider-nav-item pv-rider-nav-active\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m2 11 10-9 10 9M5 9v12h14V9M9 21v-8h6v8\"/></svg><span>홈</span></span><span class=\"pv-rider-nav-item \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"17\" r=\"4\"/><circle cx=\"19\" cy=\"17\" r=\"4\"/><path d=\"M5 17h9l-4-9H5m7-4h4l3 13M8 8l-3 9\"/></svg><span>차량관리</span></span><span class=\"pv-rider-nav-item \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg><span>정비소</span></span><span class=\"pv-rider-nav-item \"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M4 22v-3a8 8 0 0 1 16 0v3\"/></svg><span>MY</span></span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-global-header",
   "platform": "rider",
   "name": "BwGlobalHeader",
   "category": "내비",
   "purpose": "부왕 로고와 미읽음 알림 배지가 있는 상단 헤더다.",
   "variants": [
    "unread",
    "99-plus"
   ],
   "props": [
    {
     "name": "unreadCount",
     "type": "int (default: 0)",
     "required": false,
     "default": null
    },
    {
     "name": "onNotificationTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "notificationButtonKey",
     "type": "Key? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.bgWhite",
    "AppColors.borderLight",
    "AppColors.statusError",
    "AppColors.textSecondary",
    "AppColors.textWhite",
    "AppSpacing.iconLG",
    "AppSpacing.lg",
    "AppSpacing.radiusPill",
    "AppSpacing.radiusSM",
    "AppSpacing.screenPadding",
    "AppSpacing.sm",
    "AppTypography.caption",
    "AppTypography.displayLogo"
   ],
   "sizes": "padding H24/V16; 로고 34×34·radius8, 알림 버튼 40×40·radius10·아이콘24; 배지 min18×18·H5·border1.5·top/right -4; 워드마크20/w900. 토큰 값: AppSpacing.iconLG=24.0; AppSpacing.lg=16.0; AppSpacing.radiusPill=9999.0; AppSpacing.radiusSM=10.0; AppSpacing.screenPadding=24.0; AppSpacing.sm=10.0; AppTypography.caption(size=11, weight=FontWeight.normal, height=미지정); AppTypography.displayLogo(size=20, weight=FontWeight.w900, height=1.4). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L29 EdgeInsets.symmetric( horizontal: AppSpacing.screenPadding, vertical: AppSpacing.lg, ) | L40 BorderRadius.circular(8) | L63 BorderRadius.circular(AppSpacing.radiusSM) | L64 Border.all(color: AppColors.borderLight) | L33 color: AppColors.bgSecondary | L43 width: 34 | L44 height: 34 | L47 width: AppSpacing.sm | L59 width: 40 | L60 height: 40 | L62 color: AppColors.bgWhite | L64 color: AppColors.borderLight | L68 size: AppSpacing.iconLG | L69 color: AppColors.textSecondary | L96 BoxConstraints(minWidth: 18, minHeight: 18) | L97 EdgeInsets.symmetric(horizontal: 5) | L100 BorderRadius.circular(AppSpacing.radiusPill) | L101 Border.all(color: AppColors.bgSecondary, width: 1.5) | L107 AppTypography.caption.copyWith( color: AppColors.textWhite, fontWeight: FontWeight.w700, height: 1, ) | L96 minWidth: 18 | L96 minHeight: 18 | L99 color: AppColors.statusError | L101 width: 1.5 | L108 color: AppColors.textWhite | L109 fontWeight: FontWeight.w700 | L110 height: 1",
   "file": "apps/rider/lib/shared/widgets/headers/bw_global_header.dart",
   "usage_count": 6,
   "notes": "V1 전용 — 알림 수가 양수일 때 배지를 표시하고 99를 넘으면 99+로 제한한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(6)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 6개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-global-header pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:100px\"><div class=\"pv-rider-scaled\" style=\"width:117.64706%;transform:scale(0.85)\"><div class=\"pv-rider-global-v1\"><div class=\"pv-rider-row \"><span class=\"pv-rider-logo-mark\">B</span><strong class=\"pv-rider-grow\">BUWANG</strong><span class=\"pv-rider-header-bell\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 16h14l-2-3V9A5 5 0 0 0 7 9v4zm5 4h4M12 2v2\"/></svg><span class=\"pv-rider-header-count\">3</span></span></div></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-row \"><span class=\"pv-rider-muted\">알림 100개 이상</span><span class=\"pv-rider-header-count pv-rider-static-count\">99+</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-global-header-v2",
   "platform": "rider",
   "name": "BwGlobalHeaderV2",
   "category": "내비",
   "purpose": "인사말·부왕 워드마크·SOS와 알림 버튼을 표시하는 헤더다.",
   "variants": [
    "greeting",
    "sos",
    "unread",
    "99-plus"
   ],
   "props": [
    {
     "name": "greeting",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "unreadCount",
     "type": "int (default: 0)",
     "required": false,
     "default": null
    },
    {
     "name": "onNotificationTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onSosTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "notificationButtonKey",
     "type": "Key? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.brandNavy",
    "AppColorsV2.headingNavy",
    "AppColorsV2.linkAccent",
    "AppColorsV2.sosBg",
    "AppColorsV2.sosBorder",
    "AppColorsV2.sosRed",
    "AppColorsV2.surface",
    "AppSpacing.lg",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusPill",
    "AppSpacing.screenPadding",
    "AppSpacing.sm",
    "AppTypographyV2.badge",
    "AppTypographyV2.cardSubtitle"
   ],
   "sizes": "padding H24/V16, 워드마크 Pretendard21/w900·letterSpacing-1, 인사13/w600; 버튼42×42·radius12·아이콘21; 배지 min16×16·H4·border2·top/right -3·font9. 토큰 값: AppSpacing.lg=16.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusPill=9999.0; AppSpacing.screenPadding=24.0; AppSpacing.sm=10.0; AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L31 EdgeInsets.symmetric( horizontal: AppSpacing.screenPadding, vertical: AppSpacing.lg, ) | L46 EdgeInsets.only(bottom: 2) | L49 AppTypographyV2.cardSubtitle.copyWith( fontSize: 13, fontWeight: FontWeight.w600, ) | L59 TextStyle( fontFamily: 'Pretendard', fontSize: 21, fontWeight: FontWeight.w900, letterSpacing: -1, color: AppColorsV2.brandNavy, ) | L35 color: AppColorsV2.surface | L50 fontSize: 13 | L51 fontWeight: FontWeight.w600 | L60 fontFamily: 'Pretendard' | L61 fontSize: 21 | L62 fontWeight: FontWeight.w900 | L63 letterSpacing: -1 | L64 color: AppColorsV2.brandNavy | L72 width: AppSpacing.sm | L78 iconColor: AppColorsV2.sosRed | L82 width: 9 | L89 iconColor: AppColorsV2.headingNavy | L135 BorderRadius.circular(AppSpacing.radiusMD) | L136 Border.all(color: border) | L131 width: 42 | L132 height: 42 | L134 color: background | L136 color: border | L138 size: 21 | L138 color: iconColor | L162 BoxConstraints(minWidth: 16, minHeight: 16) | L163 EdgeInsets.symmetric(horizontal: 4) | L166 BorderRadius.circular(AppSpacing.radiusPill) | L167 Border.all(color: AppColorsV2.surface, width: 2) | L173 AppTypographyV2.badge.copyWith( fontSize: 9, color: AppColorsV2.bgWhite, ) | L162 minWidth: 16 | L162 minHeight: 16 | L165 color: AppColorsV2.linkAccent | L167 width: 2 | L174 fontSize: 9 | L175 color: AppColorsV2.bgWhite",
   "file": "apps/rider/lib/shared/widgets/headers/bw_global_header_v2.dart",
   "usage_count": 1,
   "notes": "V2 전용 — onSosTap이 있을 때만 SOS 버튼을 표시한다. 알림 badge는 V2 linkAccent 색상이다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-global-header-v2 \"><div class=\"pv-rider-stage \" style=\"height:75px\"><div class=\"pv-rider-scaled\" style=\"width:131.57895%;transform:scale(0.76)\"><div class=\"pv-rider-global-v2\"><div class=\"pv-rider-row \"><div class=\"pv-rider-grow\"><span style=\"font-size:13px;font-weight:600;color:#5C6B82\">민수님, 안녕하세요</span><br><strong>BUWANG</strong></div><span class=\"pv-rider-header-sos\"><svg class=\"pv-rider-icon \" width=\"21\" height=\"21\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 17V11a7 7 0 0 1 14 0v6M3 17h18v4H3zM12 1v2M1 6l2 2m20-2-2 2\"/></svg></span><span class=\"pv-rider-header-bell\"><svg class=\"pv-rider-icon \" width=\"21\" height=\"21\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 16h14l-2-3V9A5 5 0 0 0 7 9v4zm5 4h4M12 2v2\"/></svg><span class=\"pv-rider-header-count\">3</span></span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-settings-tile-v2-bw-settings-row-v2",
   "platform": "rider",
   "name": "BwSettingsRowV2",
   "category": "내비",
   "purpose": "설정 아이콘·이름·우측 위젯 또는 화살표를 한 행으로 표시한다.",
   "variants": [
    "default",
    "destructive",
    "trailing",
    "chevron"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "trailing",
     "type": "Widget? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "showChevron",
     "type": "bool (default: true)",
     "required": false,
     "default": null
    },
    {
     "name": "isDestructive",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.headingNavy",
    "AppColorsV2.sosRed",
    "AppColorsV2.textMuted",
    "AppColorsV2.textSecondary",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppTypographyV2.body"
   ],
   "sizes": "padding H16/V15, 아이콘20, gap14, 텍스트14/w600, chevron18. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L70 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: 15, ) | L81 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w600, color: fg, ) | L76 size: AppSpacing.iconMD | L76 color: iconColor | L77 width: 14 | L82 fontWeight: FontWeight.w600 | L83 color: fg | L92 size: 18 | L93 color: AppColorsV2.textMuted",
   "file": "apps/rider/lib/shared/widgets/lists/bw_settings_tile_v2.dart",
   "usage_count": 3,
   "notes": "V2 전용 — trailing이 showChevron보다 우선한다. isDestructive이면 빨간 글자·아이콘을 쓰고 기본 화살표를 숨긴다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-settings-tile-v2-bw-settings-row-v2 \"><div class=\"pv-rider-settings-row\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 16h14l-2-3V9A5 5 0 0 0 7 9v4zm5 4h4M12 2v2\"/></svg><span class=\"pv-rider-grow\">알림 설정</span><svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 5 7 7-7 7\"/></svg></div><div class=\"pv-rider-settings-row pv-rider-settings-danger\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M10 3H3v18h7m-3-9h15m-5-5 5 5-5 5\"/></svg><span class=\"pv-rider-grow\">로그아웃</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-empty-state",
   "platform": "rider",
   "name": "BwEmptyState",
   "category": "피드백",
   "purpose": "빈 목록의 아이콘·제목·설명과 선택적 기본 동작을 표시한다.",
   "variants": [
    "description",
    "primary-action"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "primaryActionLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onPrimaryAction",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.textMuted",
    "AppColors.textSecondary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.lg",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppTypography.bodyMD",
    "AppTypography.headingMD"
   ],
   "sizes": "padding24, 아이콘 원72×72·아이콘48, gap16/8/20; 제목16·설명14·최대2줄; CTA minHeight48/minWidth160, shape는 현재 테마 상속. 토큰 값: AppSpacing.btnHeightLG=48.0; AppSpacing.lg=16.0; AppSpacing.xl=20.0; AppSpacing.xs=8.0; AppSpacing.xxl=24.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.headingMD(size=16, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L39 EdgeInsets.symmetric( horizontal: AppSpacing.xxl, vertical: AppSpacing.xxl, ) | L74 AppTypography.bodyMD.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L88 BoxConstraints( minHeight: AppSpacing.btnHeightLG, minWidth: 160, ) | L49 width: 72 | L50 height: 72 | L52 color: AppColors.bgSurface | L56 size: 48 | L56 color: AppColors.textMuted | L58 height: AppSpacing.lg | L71 height: AppSpacing.xs | L75 color: AppColors.textSecondary | L76 fontWeight: FontWeight.normal | L86 height: AppSpacing.xl | L89 minHeight: AppSpacing.btnHeightLG | L90 minWidth: 160",
   "file": "apps/rider/lib/shared/widgets/states/bw_empty_state.dart",
   "usage_count": 7,
   "notes": "V1 전용 — primaryActionLabel과 onPrimaryAction이 모두 있을 때만 동작 버튼을 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(7)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 7개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-empty-state pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:217.39130%;transform:scale(0.46)\"><div class=\"pv-rider-state pv-rider-state-v1\"><span class=\"pv-rider-circle \" style=\"width:72px;height:72px\"><svg class=\"pv-rider-icon \" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"M7 2v6m10-6v6M3 10h18m-13 5h2m4 0h2\"/></svg></span><strong>아직 예약 내역이 없어요</strong><p><span class=\"pv-rider-muted\">가까운 정비소에서 첫 정비를 예약해 보세요.</span></p><span class=\"pv-rider-cta \">정비소 찾기</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-empty-state-v2",
   "platform": "rider",
   "name": "BwEmptyStateV2",
   "category": "피드백",
   "purpose": "V2 빈 상태 안내와 선택적 CTA를 일반 또는 작은 간격으로 표시한다.",
   "variants": [
    "compact",
    "regular",
    "description",
    "cta"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "ctaLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onCta",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "compact",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.linkAccent",
    "AppColorsV2.navActivePillBg",
    "AppSpacing.lg",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxxl",
    "AppTypographyV2.cardSubtitle",
    "AppTypographyV2.cardTitle"
   ],
   "sizes": "padding H16·V20(compact)/32, 원64×64·아이콘30, 제목16/w800·보조12, CTA width200/height46·radius14. 토큰 값: AppSpacing.lg=16.0; AppSpacing.xl=20.0; AppSpacing.xs=8.0; AppSpacing.xxxl=32.0; AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4); AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L35 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: compact ? AppSpacing.xl : AppSpacing.xxxl, ) | L56 AppTypographyV2.cardTitle.copyWith(fontSize: 16) | L43 width: 64 | L44 height: 64 | L46 color: AppColorsV2.navActivePillBg | L50 size: 30 | L50 color: AppColorsV2.linkAccent | L52 height: AppSpacing.lg | L56 fontSize: 16 | L59 height: AppSpacing.xs | L69 width: 200 | L73 height: 46",
   "file": "apps/rider/lib/shared/widgets/states/bw_empty_state_v2.dart",
   "usage_count": 9,
   "notes": "V2 전용 — ctaLabel과 onCta가 모두 있어야 CTA를 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(9)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 9개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-empty-state-v2 \"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:217.39130%;transform:scale(0.46)\"><div class=\"pv-rider-state pv-rider-state-v2\"><span class=\"pv-rider-circle \" style=\"width:64px;height:64px\"><svg class=\"pv-rider-icon \" width=\"30\" height=\"30\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"M7 2v6m10-6v6M3 10h18m-13 5h2m4 0h2\"/></svg></span><strong>아직 예약 내역이 없어요</strong><p><span class=\"pv-rider-muted\">가까운 정비소에서 첫 정비를 예약해 보세요.</span></p><span class=\"pv-rider-cta \">정비소 찾기</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-error-state",
   "platform": "rider",
   "name": "BwErrorState",
   "category": "피드백",
   "purpose": "오류 설명과 재시도·선택적 보조 동작을 표시한다.",
   "variants": [
    "description",
    "retry",
    "secondary-action"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData (default: Icons.error_outline)",
     "required": false,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onRetry",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "retryLabel",
     "type": "String (default: '다시 시도')",
     "required": false,
     "default": null
    },
    {
     "name": "secondaryLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "secondaryAction",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.statusError",
    "AppColors.textSecondary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.lg",
    "AppSpacing.sm",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppTypography.bodyMD",
    "AppTypography.headingMD"
   ],
   "sizes": "padding24, 오류 아이콘48, gap16/8/20/10; 제목16·최대2줄/본문14·최대3줄; CTA maxWidth240·부모 minHeight48·자식 실제높이52. 토큰 값: AppSpacing.btnHeightLG=48.0; AppSpacing.lg=16.0; AppSpacing.sm=10.0; AppSpacing.xl=20.0; AppSpacing.xs=8.0; AppSpacing.xxl=24.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.headingMD(size=16, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L49 EdgeInsets.symmetric( horizontal: AppSpacing.xxl, vertical: AppSpacing.xxl, ) | L75 AppTypography.bodyMD.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L88 BoxConstraints( minHeight: AppSpacing.btnHeightLG, maxWidth: 240, ) | L105 AppTypography.bodyMD.copyWith( color: AppColors.brandPrimary, fontWeight: FontWeight.w700, ) | L58 size: 48 | L58 color: AppColors.statusError | L59 height: AppSpacing.lg | L72 height: AppSpacing.xs | L76 color: AppColors.textSecondary | L77 fontWeight: FontWeight.normal | L86 height: AppSpacing.xl | L89 minHeight: AppSpacing.btnHeightLG | L90 maxWidth: 240 | L100 height: AppSpacing.sm | L106 color: AppColors.brandPrimary | L107 fontWeight: FontWeight.w700",
   "file": "apps/rider/lib/shared/widgets/states/bw_error_state.dart",
   "usage_count": 29,
   "notes": "공용 — V1 토큰과 PrimaryCtaButton을 쓰며 V1/V2 화면에서 모두 import한다. secondaryLabel/secondaryAction은 함께 지정하거나 함께 생략하도록 assert한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(29)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 29개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-error-state pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:208.33333%;transform:scale(0.48)\"><div class=\"pv-rider-state pv-rider-error-state\"><svg class=\"pv-rider-icon \" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 3 10 18H2zM12 9v5m0 3v1\"/></svg><strong>정비소를 불러오지 못했어요</strong><p><span class=\"pv-rider-muted\">연결 상태를 확인하고 다시 시도해 주세요.</span></p><span class=\"pv-rider-cta \">다시 시도</span><span class=\"pv-rider-link\" style=\"margin-top:10px\">홈으로 돌아가기</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-info-banner",
   "platform": "rider",
   "name": "BwInfoBanner",
   "category": "피드백",
   "purpose": "아이콘과 제목·설명·선택적 동작으로 안내 상태를 표시한다.",
   "variants": [
    "info",
    "success",
    "warning",
    "error",
    "description",
    "action"
   ],
   "props": [
    {
     "name": "variant",
     "type": "BwInfoBannerVariant (default: BwInfoBannerVariant.info)",
     "required": false,
     "default": null
    },
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "actionLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onAction",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.statusError",
    "AppColors.statusErrorBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppColors.textPrimary",
    "AppColors.textSecondary",
    "AppSpacing.md",
    "AppSpacing.radiusMD"
   ],
   "sizes": "패딩 L16/T12/R8/B12, radius 12, 아이콘 24, 제목 14/w700, 설명 13/w400, 액션 48×48·13/w700. 토큰 값: AppSpacing.md=12.0; AppSpacing.radiusMD=12.0. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L33 Color(0xFFE6F0F8) | L64 Border.all(color: p.border.withValues(alpha: 0.2)) | L65 BorderRadius.circular(AppSpacing.radiusMD) | L67 EdgeInsets.fromLTRB(16, 12, 8, 12) | L80 TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ) | L90 TextStyle( fontSize: 13, fontWeight: FontWeight.w400, color: AppColors.textSecondary, ) | L110 TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: p.fg, ) | L63 color: p.bg | L64 color: p.border.withValues(alpha: 0.2) | L71 size: 24 | L71 color: p.fg | L72 width: AppSpacing.md | L81 fontSize: 14 | L82 fontWeight: FontWeight.w700 | L83 color: AppColors.textPrimary | L87 height: 2 | L91 fontSize: 13 | L92 fontWeight: FontWeight.w400 | L93 color: AppColors.textSecondary | L105 width: 48 | L106 height: 48",
   "file": "apps/rider/lib/shared/widgets/banners/bw_info_banner.dart",
   "usage_count": 3,
   "notes": "V1 전용 — actionLabel과 onAction이 모두 있어야 동작 영역이 표시된다. info 배경은 토큰이 아닌 Color(0xFFE6F0F8)이다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-info-banner pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:112px\"><div class=\"pv-rider-scaled\" style=\"width:133.33333%;transform:scale(0.75)\"><div class=\"pv-rider-banner pv-rider-tone-info\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 10v7m0-11v1\"/></svg><div class=\"pv-rider-grow\"><b>주행거리를 업데이트해 주세요</b><p>정확한 교체 시기를 알려드려요.</p></div><span class=\"pv-rider-banner-action\">수정</span></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-row \"><span class=\"pv-rider-chip pv-rider-tone-success \">저장 완료</span><span class=\"pv-rider-chip pv-rider-tone-warning \">점검 필요</span><span class=\"pv-rider-chip pv-rider-tone-error \">연결 오류</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-loading-state",
   "platform": "rider",
   "name": "BwLoadingState",
   "category": "피드백",
   "purpose": "콘텐츠 영역 중앙에 로딩 스피너와 선택적 메시지를 표시한다.",
   "variants": [
    "spinner-only",
    "message"
   ],
   "props": [
    {
     "name": "message",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppSpacing.lg",
    "AppSpacing.xxl",
    "AppTypography.bodySM"
   ],
   "sizes": "padding24, spinner stroke3, 메시지 위 gap16, bodySM13/w700. 토큰 값: AppSpacing.lg=16.0; AppSpacing.xxl=24.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L16 EdgeInsets.all(AppSpacing.xxl) | L29 AppTypography.bodySM.copyWith( color: AppColors.textMuted, ) | L22 color: AppColors.brandPrimary | L23 strokeWidth: 3 | L26 height: AppSpacing.lg | L30 color: AppColors.textMuted",
   "file": "apps/rider/lib/shared/widgets/states/bw_loading_state.dart",
   "usage_count": 1,
   "notes": "V1 전용 — V1 토큰 구현이며 marketplace_home_screen에서 import한다. 자체 비동기 작업은 수행하지 않는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-loading-state pv-rider-v1\"><div class=\"pv-rider-loading-state\"><span class=\"pv-rider-spinner\"></span><b>정비소를 찾고 있어요</b></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-sos-step-indicator",
   "platform": "rider",
   "name": "BwSosStepIndicator",
   "category": "피드백",
   "purpose": "증상·위치·매칭·추적·완료의 SOS 다섯 단계 진행을 표시한다.",
   "variants": [
    "reached",
    "upcoming"
   ],
   "props": [
    {
     "name": "activeStep",
     "type": "int",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppSpacing.md",
    "AppSpacing.radiusPill",
    "AppSpacing.screenPadding",
    "AppSpacing.xxs",
    "AppTypography.displayNavInactive"
   ],
   "sizes": "padding L24/T12/R24/B0, 셀 H3, 막대 높이4·pill, label 위 gap4; label10/w700·letterSpacing .4. 토큰 값: AppSpacing.md=12.0; AppSpacing.radiusPill=9999.0; AppSpacing.screenPadding=24.0; AppSpacing.xxs=4.0; AppTypography.displayNavInactive(size=10, weight=FontWeight.w700, height=1.5). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L29 EdgeInsets.fromLTRB( AppSpacing.screenPadding, AppSpacing.md, AppSpacing.screenPadding, 0, ) | L40 EdgeInsets.symmetric(horizontal: 3) | L49 BorderRadius.circular( AppSpacing.radiusPill, ) | L60 AppTypography.displayNavInactive.copyWith( color: active ? AppColors.brandPrimary : AppColors.textMuted, letterSpacing: 0.4, ) | L27 width: double.infinity | L28 color: AppColors.bgWhite | L44 height: 4 | L46 color: active ? AppColors.brandPrimary : AppColors.borderDefault | L54 height: AppSpacing.xxs | L61 color: active ? AppColors.brandPrimary : AppColors.textMuted | L64 letterSpacing: 0.4",
   "file": "apps/rider/lib/shared/widgets/progress/bw_sos_step_indicator.dart",
   "usage_count": 9,
   "notes": "V1 전용 — activeStep은 0 기반이며 i<=activeStep까지 활성 색상이다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(9)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 9개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-sos-step-indicator pv-rider-v1\"><div class=\"pv-rider-sos-progress\"><span class=\"pv-rider-reached\"><i></i>증상</span><span class=\"pv-rider-reached\"><i></i>위치</span><span class=\"pv-rider-reached\"><i></i>매칭</span><span class=\"\"><i></i>추적</span><span class=\"\"><i></i>완료</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-sos-step-indicator-v2",
   "platform": "rider",
   "name": "BwSosStepIndicatorV2",
   "category": "피드백",
   "purpose": "요청·매칭·출동·완료의 네 단계를 연결된 원으로 표시한다.",
   "variants": [
    "done",
    "active",
    "upcoming"
   ],
   "props": [
    {
     "name": "activeStep",
     "type": "int",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.border",
    "AppColorsV2.linkAccent",
    "AppColorsV2.textMuted",
    "AppColorsV2.trackLight",
    "AppSpacing.xxs",
    "AppTypographyV2.badge"
   ],
   "sizes": "원28×28, 완료 아이콘16, 번호12/w800, 레이블11/w800·gap4; 연결선 높이2·top padding13. 토큰 값: AppSpacing.xxs=4.0; AppTypographyV2.badge(size=10, weight=FontWeight.w800, height=1). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L33 EdgeInsets.only(top: 13) | L35 height: 2 | L36 color: i < activeStep ? AppColorsV2.linkAccent : AppColorsV2.border | L73 Border.all( color: filled ? AppColorsV2.linkAccent : AppColorsV2.border, ) | L82 AppTypographyV2.badge.copyWith( fontSize: 12, color: active ? Colors.white : AppColorsV2.textMuted, ) | L91 AppTypographyV2.badge.copyWith( fontSize: 11, color: filled ? AppColorsV2.linkAccent : AppColorsV2.textMuted, ) | L68 width: 28 | L69 height: 28 | L71 color: filled ? AppColorsV2.linkAccent : AppColorsV2.trackLight | L74 color: filled ? AppColorsV2.linkAccent : AppColorsV2.border | L79 size: 16 | L79 color: Colors.white | L83 fontSize: 12 | L84 color: active ? Colors.white : AppColorsV2.textMuted | L88 height: AppSpacing.xxs | L92 fontSize: 11 | L93 color: filled ? AppColorsV2.linkAccent : AppColorsV2.textMuted",
   "file": "apps/rider/lib/shared/widgets/progress/bw_sos_step_indicator_v2.dart",
   "usage_count": 2,
   "notes": "V2 전용 — activeStep은 0 기반. 완료 원은 체크 아이콘, 활성·예정 원은 단계 번호를 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-sos-step-indicator-v2 \"><div class=\"pv-rider-sos-steps\"><span class=\"pv-rider-sos-step pv-rider-step-done\"><b><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg></b><span>요청</span></span><i class=\"pv-rider-step-done\"></i><span class=\"pv-rider-sos-step pv-rider-step-active\"><b>2</b><span>매칭</span></span><i class=\"\"></i><span class=\"pv-rider-sos-step \"><b>3</b><span>출동</span></span><i class=\"\"></i><span class=\"pv-rider-sos-step \"><b>4</b><span>완료</span></span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-sos-waiting-pulse",
   "platform": "rider",
   "name": "BwSosWaitingPulse",
   "category": "피드백",
   "purpose": "SOS 정비사 매칭 대기 애니메이션과 경과 시간을 표시한다.",
   "variants": [
    "waiting"
   ],
   "props": [
    {
     "name": "elapsed",
     "type": "String",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.ctaGradient",
    "AppColors.textSecondary",
    "AppColors.textWhite",
    "AppSpacing.xxs",
    "AppSpacing.xxxl",
    "AppTypography.bodyMD",
    "AppTypography.headingXL",
    "AppTypography.numericBody"
   ],
   "sizes": "영역 160×160, 중심 80×80, 링 scale 0.5+v*0.5·border 2·alpha 0.35*(1-v); 아이콘 32, 아래 gap 32/4; shadow blur 12·offset(0,4). 토큰 값: AppSpacing.xxs=4.0; AppSpacing.xxxl=32.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.headingXL(size=20, weight=FontWeight.w700, height=1.4); AppTypography.numericBody(size=14, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L73 Border.all( color: AppColors.brandPrimary.withValues( alpha: 0.35 * (1 - v), ), width: 2, ) | L92 BoxShadow( color: AppColors.brandPrimary.withValues(alpha: 0.30), blurRadius: 12, offset: const Offset(0, 4), ) | L114 AppTypography.bodyMD.copyWith( color: AppColors.textSecondary, fontWeight: FontWeight.normal, ) | L122 AppTypography.numericBody.copyWith( color: AppColors.brandPrimary, ) | L55 width: 160 | L56 height: 160 | L67 scale: 0.5 + v * 0.5 | L74 color: AppColors.brandPrimary.withValues( alpha: 0.35 * (1 - v), ) | L77 width: 2 | L86 width: 80 | L87 height: 80 | L89 gradient: AppColors.ctaGradient | L93 color: AppColors.brandPrimary.withValues(alpha: 0.30) | L94 blurRadius: 12 | L102 size: 32 | L103 color: AppColors.textWhite | L109 height: AppSpacing.xxxl | L111 height: AppSpacing.xxs | L115 color: AppColors.textSecondary | L116 fontWeight: FontWeight.normal | L123 color: AppColors.brandPrimary",
   "file": "apps/rider/lib/shared/widgets/bw_sos_waiting_pulse.dart",
   "usage_count": 3,
   "notes": "V1 전용 — 2200ms 무한 반복 애니메이션; 링 위상은 [0.0,1/3,2/3]이고 elapsed는 외부 문자열이다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-sos-waiting-pulse pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:192.30769%;transform:scale(0.52)\"><div class=\"pv-rider-waiting\"><div class=\"pv-rider-pulse\"><i></i><i></i><i></i><span><svg class=\"pv-rider-icon \" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M14 4a6 6 0 0 0-7 7L2 16a3 3 0 0 0 4 4l5-5a6 6 0 0 0 9-7l-4 4-4-4 4-4z\"/></svg></span></div><b>가까운 정비사를 찾고 있어요</b><span class=\"pv-rider-muted\">평균 1~3분 소요 · 경과 00:42</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-step-progress-bar",
   "platform": "rider",
   "name": "BwStepProgressBar",
   "category": "피드백",
   "purpose": "현재 단계와 전체 단계 수를 얇은 선형 진행률로 표시한다.",
   "variants": [
    "empty-total",
    "in-progress",
    "complete"
   ],
   "props": [
    {
     "name": "current",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "total",
     "type": "int",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.borderDefault",
    "AppColors.brandPrimary"
   ],
   "sizes": "minHeight4, radius4; brandPrimary fill/borderDefault track. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L22 BorderRadius.circular(4) | L25 backgroundColor: AppColors.borderDefault | L26 color: AppColors.brandPrimary | L27 minHeight: 4",
   "file": "apps/rider/lib/shared/widgets/progress/bw_step_progress_bar.dart",
   "usage_count": 3,
   "notes": "V1 전용 — current는 0 기반. total<=0이면 0, 그 외 (current+1)/total을 0~1로 clamp한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-step-progress-bar pv-rider-v1\"><div class=\"pv-rider-step-bar\"><span style=\"width:60%\"></span></div><div class=\"pv-rider-mini\"><span class=\"pv-rider-muted\">내 차량 등록 · 3 / 5 단계</span></div><div class=\"pv-rider-step-bar\" style=\"margin-top:16px\"><span style=\"width:100%\"></span></div><div class=\"pv-rider-mini\"><span class=\"pv-rider-muted\">등록 완료 · 5 / 5 단계</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-toast-message",
   "platform": "rider",
   "name": "BwToastMessage",
   "category": "피드백",
   "purpose": "성공·오류·경고 메시지를 키보드 위 슬라이드 토스트로 표시한다.",
   "variants": [
    "success",
    "error",
    "warning",
    "comingSoon"
   ],
   "props": [],
   "tokens": [
    "AppColors.statusError",
    "AppColors.statusErrorBg",
    "AppColors.statusSuccess",
    "AppColors.statusSuccessBg",
    "AppColors.statusWarning",
    "AppColors.statusWarningBg",
    "AppSpacing.iconMD",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.xl",
    "AppTypography.bodyMD"
   ],
   "sizes": "bottom=100+keyboardInset, left/right16, padding H20/V14, radius12, 아이콘20·gap12, 본문14/w600; slide300ms, 자동닫기2초, failsafe3500ms. 토큰 값: AppSpacing.iconMD=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppSpacing.xl=20.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L57 color: color | L182 EdgeInsets.symmetric( vertical: 14, horizontal: AppSpacing.xl, ) | L188 BorderRadius.circular(AppSpacing.radiusMD) | L189 Border.all(color: widget.border) | L198 AppTypography.bodyMD.copyWith( fontWeight: FontWeight.w600, color: widget.color, ) | L180 color: Colors.transparent | L187 color: widget.bg | L189 color: widget.border | L193 size: AppSpacing.iconMD | L193 color: widget.color | L194 width: AppSpacing.md | L199 fontWeight: FontWeight.w600",
   "file": "apps/rider/lib/shared/widgets/overlays/toast_message.dart",
   "usage_count": 25,
   "notes": "공용 — V1 토큰을 쓰지만 V1/V2에서 공유한다. static show(BuildContext context,{required String message,ToastVariant variant=ToastVariant.success}), comingSoon(context,feature)를 사용한다. 기존 토스트를 교체한다. 2026-09-11(#1564)부터 본문을 sentenceLines(message)로 감싸 한국어 문장 단위로 줄바꿈한다 — 기대 문자열을 비교하는 테스트는 개행을 포함해야 한다. 공개 생성자 없음; private BwToastMessage._()만 정의하므로 props=[]. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(25)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 25개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-toast-message pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:100px\"><div class=\"pv-rider-scaled\" style=\"width:133.33333%;transform:scale(0.75)\"><div class=\"pv-rider-toast pv-rider-tone-success\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg>주행거리를 저장했어요</div><div class=\"pv-rider-row\" style=\"margin-top:8px\"><div class=\"pv-rider-toast pv-rider-tone-error\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 10v7m0-11v1\"/></svg>저장 실패</div><div class=\"pv-rider-toast pv-rider-tone-warning\"><svg class=\"pv-rider-icon \" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 3 10 18H2zM12 9v5m0 3v1\"/></svg>점검 필요</div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-vehicle-gate-v2",
   "platform": "rider",
   "name": "BwVehicleGateV2",
   "category": "피드백",
   "purpose": "차량이 필요한 화면에서 등록 안내와 차량 등록 CTA를 표시한다.",
   "variants": [
    "regular",
    "large-text"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onRegister",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "icon",
     "type": "IconData (default: Icons.two_wheeler)",
     "required": false,
     "default": null
    },
    {
     "name": "anchorKey",
     "type": "Key? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.linkAccent",
    "AppColorsV2.navActivePillBg",
    "AppColorsV2.textMuted",
    "AppSpacing.lg",
    "AppSpacing.sm",
    "AppSpacing.xl",
    "AppTypographyV2.cardSubtitle",
    "AppTypographyV2.cardTitle"
   ],
   "sizes": "padding20, 아이콘 원80×80·아이콘38, 제목20, 설명13·height1.5; CTA width220/textScale>1.3이면 전체 폭·기본높이52; 안내 아이콘14. 토큰 값: AppSpacing.lg=16.0; AppSpacing.sm=10.0; AppSpacing.xl=20.0; AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4); AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L44 EdgeInsets.all(AppSpacing.xl) | L62 AppTypographyV2.cardTitle.copyWith(fontSize: 20) | L68 AppTypographyV2.cardSubtitle.copyWith( fontSize: 13, height: 1.5, ) | L96 AppTypographyV2.cardSubtitle.copyWith( fontSize: 12, color: AppColorsV2.textMuted, ) | L49 width: 80 | L50 height: 80 | L52 color: AppColorsV2.navActivePillBg | L56 size: 38 | L56 color: AppColorsV2.linkAccent | L58 height: AppSpacing.lg | L62 fontSize: 20 | L64 height: AppSpacing.sm | L69 fontSize: 13 | L70 height: 1.5 | L73 height: AppSpacing.xl | L75 width: largeText ? double.infinity : 220 | L90 size: 14 | L91 color: AppColorsV2.textMuted | L93 width: 5 | L97 fontSize: 12",
   "file": "apps/rider/lib/shared/widgets/states/bw_vehicle_gate_v2.dart",
   "usage_count": 1,
   "notes": "V2 전용 — 차량 등록 상태 판정은 호출부 책임이다. 전체를 SingleChildScrollView로 감싼다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-vehicle-gate-v2 \"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:270.27027%;transform:scale(0.37)\"><div class=\"pv-rider-state pv-rider-vehicle-gate\"><span class=\"pv-rider-circle \" style=\"width:80px;height:80px\"><svg class=\"pv-rider-icon \" width=\"38\" height=\"38\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"17\" r=\"4\"/><circle cx=\"19\" cy=\"17\" r=\"4\"/><path d=\"M5 17h9l-4-9H5m7-4h4l3 13M8 8l-3 9\"/></svg></span><strong>내 차량을 등록해 주세요</strong><p>차량을 등록하면 정비 예약과<br>소모품 관리를 시작할 수 있어요.</p><span class=\"pv-rider-cta \">내 차량 등록하기</span><span class=\"pv-rider-row pv-rider-muted\" style=\"margin-top:12px;gap:5px\"><svg class=\"pv-rider-icon \" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 10v7m0-11v1\"/></svg>차량 등록은 약 1분이면 끝나요</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-sub-progress",
   "platform": "rider",
   "name": "SubProgress",
   "category": "피드백",
   "purpose": "차량 등록 4단계(안내·촬영·확인·완료) 진행을 점·선 스테퍼로 표시한다.",
   "variants": [
    "intro",
    "capture",
    "result",
    "done"
   ],
   "props": [
    {
     "name": "stage",
     "type": "VehicleSubStage",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted"
   ],
   "sizes": "세로 패딩 8, 노드 원 18(번호 10/w700 tabular·지난 단계는 체크 10), 노드-라벨 gap 6, 라벨 11/w700, 연결선 height 1·좌우 margin 4. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L21 EdgeInsets.symmetric(vertical: 8) | L34 height: 1 | L35 EdgeInsets.symmetric(horizontal: 4) | L36 color: i < currentIdx ? AppColors.brandPrimary : AppColors.borderDefault | L67 width: 18 | L68 height: 18 | L70 color: isActive ? AppColors.brandPrimary : AppColors.borderDefault | L75 Icon(Icons.check, size: 10, color: Colors.white) | L81 fontSize: 10 | L82 fontWeight: FontWeight.w700 | L89 SizedBox(width: 6) | L93 fontSize: 11 | L95 color: isActive ? AppColors.brandPrimary : AppColors.textMuted",
   "file": "apps/rider/lib/features/vehicle/presentation/registration/sub_progress.dart",
   "usage_count": 3,
   "notes": "V1 토큰 — 단계 라벨은 안내·촬영·확인·완료 고정이고 stage 까지가 active, stage 이전은 체크로 바뀐다. jsx SSOT screens-vehicle.jsx SubProgress. shared/widgets/progress 의 BwStepProgressBar·BwReservationStepper 와 같은 스테퍼 계열이지만 차량 등록 서브플로 전용으로 따로 있다(중복 신호 — 새 스테퍼를 만들기 전에 셋을 먼저 본다). 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다. 담기는 근거: 등록 안내·OCR 결과·완료 3화면이 상단에서 공유한다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "rider-confirm-dialog",
   "platform": "rider",
   "name": "BwConfirmDialog",
   "category": "오버레이",
   "purpose": "취소·확인과 파괴적 동작 변형을 제공하는 확인 대화상자다.",
   "variants": [
    "default",
    "destructive",
    "description"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "description",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "confirmLabel",
     "type": "String (default: '확인')",
     "required": false,
     "default": null
    },
    {
     "name": "cancelLabel",
     "type": "String (default: '취소')",
     "required": false,
     "default": null
    },
    {
     "name": "onConfirm",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onCancel",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "destructive",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSecondary",
    "AppColors.brandPrimary",
    "AppColors.statusError",
    "AppColors.statusErrorBg",
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.ctaGradient",
    "AppColorsV2.textSecondary",
    "AppSpacing.btnHeightLG",
    "AppSpacing.iconLG",
    "AppSpacing.md",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppSpacing.xxl",
    "AppSpacingV2.radiusShopCard",
    "AppTypographyV2.body",
    "AppTypographyV2.cardTitle"
   ],
   "sizes": "padding24, radius14; 아이콘 원56×56·아이콘24; gap20/8, 버튼 높이48·radius14·사이12; 취소 border1.5, 제목18·본문14. 토큰 값: AppSpacing.btnHeightLG=48.0; AppSpacing.iconLG=24.0; AppSpacing.md=12.0; AppSpacing.xl=20.0; AppSpacing.xs=8.0; AppSpacing.xxl=24.0; AppSpacingV2.radiusShopCard=14.0; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45); AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L83 EdgeInsets.all(AppSpacing.xxl) | L86 BorderRadius.circular(AppSpacingV2.radiusShopCard) | L114 AppTypographyV2.cardTitle.copyWith(fontSize: 18) | L121 AppTypographyV2.body.copyWith( color: AppColorsV2.textSecondary, ) | L140 BorderRadius.circular( AppSpacingV2.radiusShopCard, ) | L143 Border.all( color: AppColorsV2.border, width: 1.5, ) | L151 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w700, color: AppColorsV2.textSecondary, ) | L177 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w800, color: Colors.white, ) | L81 backgroundColor: Colors.transparent | L85 color: AppColorsV2.bgWhite | L93 width: 56 | L94 height: 56 | L96 color: destructive ? AppColors.statusErrorBg : AppColors.bgSecondary | L103 size: AppSpacing.iconLG | L104 color: destructive ? AppColors.statusError : AppColors.brandPrimary | L109 height: AppSpacing.xl | L114 fontSize: 18 | L118 height: AppSpacing.xs | L122 color: AppColorsV2.textSecondary | L137 height: AppSpacing.btnHeightLG | L144 color: AppColorsV2.border | L145 width: 1.5 | L152 fontWeight: FontWeight.w700 | L159 width: AppSpacing.md | L167 color: destructive ? AppColors.statusError : null | L168 gradient: destructive ? null : AppColorsV2.ctaGradient | L178 fontWeight: FontWeight.w800 | L179 color: Colors.white",
   "file": "apps/rider/lib/shared/widgets/overlays/confirm_dialog.dart",
   "usage_count": 18,
   "notes": "공용 — V1/V2 화면에서 공유하며 V1 아이콘 색과 V2 표면·타이포를 혼용한다. show는 Future<void>, ask는 Future<bool>이다. 콜백을 주면 기본 pop을 대체하므로 닫기는 호출부 책임이다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(18)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 18개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-confirm-dialog \"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:227.27273%;transform:scale(0.44)\"><div class=\"pv-rider-dialog\"><span class=\"pv-rider-circle pv-rider-confirm-icon\" style=\"width:56px;height:56px\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m12 3 10 18H2zM12 9v5m0 3v1\"/></svg></span><strong>예약을 취소할까요?</strong><p>취소한 예약은 복구할 수 없어요.</p><div class=\"pv-rider-row\" style=\"gap:12px;margin-top:20px\"><span class=\"pv-rider-cta pv-rider-dialog-cancel\">돌아가기</span><span class=\"pv-rider-cta pv-rider-dialog-danger\">예약 취소</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-loading-overlay",
   "platform": "rider",
   "name": "BwLoadingOverlay",
   "category": "오버레이",
   "purpose": "중복 표시를 막는 차단형 로딩 대화상자를 연다.",
   "variants": [
    "hidden",
    "showing",
    "custom-message"
   ],
   "props": [],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.brandPrimary",
    "AppColors.shadowBlue",
    "AppColors.textMuted",
    "AppSpacing.lg",
    "AppSpacing.radiusLG",
    "AppTypography.bodySM"
   ],
   "sizes": "160×160, radius16, shadowBlue·blur24·offset(0,8), spinner stroke3, gap16, 메시지13/w500. 토큰 값: AppSpacing.lg=16.0; AppSpacing.radiusLG=16.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L24 BorderRadius.circular(AppSpacing.radiusLG) | L26 BoxShadow( color: AppColors.shadowBlue, blurRadius: 24, offset: Offset(0, 8), ) | L43 AppTypography.bodySM.copyWith( fontWeight: FontWeight.w500, color: AppColors.textMuted, ) | L20 width: 160 | L21 height: 160 | L23 color: AppColors.bgWhite | L27 color: AppColors.shadowBlue | L28 blurRadius: 24 | L37 color: AppColors.brandPrimary | L38 strokeWidth: 3 | L40 height: AppSpacing.lg | L44 fontWeight: FontWeight.w500 | L45 color: AppColors.textMuted",
   "file": "apps/rider/lib/shared/widgets/overlays/loading_overlay.dart",
   "usage_count": 0,
   "notes": "V1 전용 — V1 토큰 구현이며 직접 import는 없다. static show(BuildContext context,{String? message})와 hide(BuildContext context)로 사용한다. barrierDismissible=false이다. 공개 생성자 없음; private BwLoadingOverlay._()만 정의하므로 props=[]. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(0)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 0개다. 앱 lib 내 직접 import 없음(테스트만 있거나 사용처 없음).",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-loading-overlay-preview pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:117px\"><div class=\"pv-rider-scaled\" style=\"width:153.84615%;transform:scale(0.65)\"><div class=\"pv-rider-overlay-demo\"><div class=\"pv-rider-overlay-underlay\">내 차량 · 정비 예약</div><div class=\"pv-rider-loading-overlay\"><span class=\"pv-rider-spinner\"></span><span>예약을 확인하고 있어요</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-ocr-capture-frame",
   "platform": "rider",
   "name": "BwOcrCaptureFrame",
   "category": "오버레이",
   "purpose": "카메라 위에 문서 정렬 프레임·모서리·안내 문구를 표시한다.",
   "variants": [
    "aligned",
    "unaligned"
   ],
   "props": [
    {
     "name": "hint",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "aligned",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "width",
     "type": "double (default: 260)",
     "required": false,
     "default": null
    },
    {
     "name": "height",
     "type": "double (default: 150)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.statusSuccess",
    "AppColors.textPrimary",
    "AppColors.textWhite",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusPill",
    "AppSpacing.xs",
    "AppTypography.bodySM"
   ],
   "sizes": "기본 프레임260×150·radius12·border3, 모서리20×20·border4, 힌트 gap16·padding H12/V8·pill·아이콘14·font13. 토큰 값: AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusPill=9999.0; AppSpacing.xs=8.0; AppTypography.bodySM(size=13, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L52 BorderRadius.circular(AppSpacing.radiusMD) | L53 Border.all(color: frameColor, width: 3) | L68 EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.xs, ) | L76 BorderRadius.circular(AppSpacing.radiusPill) | L89 AppTypography.bodySM.copyWith( color: AppColors.textWhite, ) | L105 BorderSide(color: color, width: 4) | L44 width: width | L45 height: height | L53 color: frameColor | L53 width: 3 | L65 height: AppSpacing.lg | L73 color: aligned ? AppColors.statusSuccess : AppColors.textPrimary.withValues(alpha: 0.7) | L83 size: 14 | L84 color: AppColors.textWhite | L86 width: AppSpacing.xs | L105 color: color | L105 width: 4 | L110 width: side | L111 height: side",
   "file": "apps/rider/lib/shared/widgets/ocr/bw_ocr_capture_frame.dart",
   "usage_count": 1,
   "notes": "V1 전용 — 카메라 기능 자체는 없고 정렬 여부를 외부에서 받는다. 앱 lib 내 직접 import는 없다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다. 앱 lib 내 직접 import 없음(테스트만 있거나 사용처 없음).",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-ocr-capture-frame pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:118px\"><div class=\"pv-rider-scaled\" style=\"width:200.00000%;transform:scale(0.5)\"><div class=\"pv-rider-capture\"><div class=\"pv-rider-capture-frame\"><i></i><i></i><i></i><i></i><span class=\"pv-rider-capture-document\">이륜자동차 사용신고필증<br><span>차량 번호　1가2 3456</span></span></div><span class=\"pv-rider-capture-hint\"><svg class=\"pv-rider-icon \" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg>문서가 잘 맞춰졌어요</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-prompt-dialog",
   "platform": "rider",
   "name": "BwPromptDialog",
   "category": "오버레이",
   "purpose": "설명 또는 사용자 위젯과 기본·보조 동작을 세로로 표시한다.",
   "variants": [
    "body",
    "bodyWidget",
    "icon",
    "dismissible"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "primaryLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "secondaryLabel",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "body",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "bodyWidget",
     "type": "Widget? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "icon",
     "type": "IconData? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.bgWhite",
    "AppColorsV2.linkAccent",
    "AppColorsV2.textMuted",
    "AppColorsV2.textSecondary",
    "AppSpacingV2.radiusShopCard",
    "AppTypographyV2.body",
    "AppTypographyV2.cardTitle"
   ],
   "sizes": "radius14, 제목18/w800, 본문14/w500·height1.45; 아이콘 gap8, 본문→CTA gap20, CTA 기본52·radius14, 보조 버튼 gap4. 토큰 값: AppSpacingV2.radiusShopCard=14.0; AppTypographyV2.body(size=14, weight=FontWeight.w500, height=1.45); AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L69 AppTypographyV2.cardTitle.copyWith(fontSize: 18) | L74 BorderRadius.circular(AppSpacingV2.radiusShopCard) | L105 AppTypographyV2.body.copyWith( fontWeight: FontWeight.w700, color: AppColorsV2.textMuted, ) | L69 fontSize: 18 | L72 backgroundColor: AppColorsV2.bgWhite | L80 color: AppColorsV2.linkAccent | L81 width: 8 | L93 color: AppColorsV2.textSecondary | L95 height: 20 | L100 height: 4 | L106 fontWeight: FontWeight.w700 | L107 color: AppColorsV2.textMuted",
   "file": "apps/rider/lib/shared/widgets/overlays/bw_prompt_dialog.dart",
   "usage_count": 8,
   "notes": "공용 — V2 토큰 기반이지만 V1 signup_screen/notification_screen과 V2 화면에서 모두 import한다. bodyWidget이 body보다 우선한다. static show는 Future<bool?>이며 기본 barrierDismissible=true, primary=true/secondary=false/닫기=null이다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(8)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 8개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-prompt-dialog \"><div class=\"pv-rider-stage \" style=\"height:120px\"><div class=\"pv-rider-scaled\" style=\"width:192.30769%;transform:scale(0.52)\"><div class=\"pv-rider-prompt\"><div class=\"pv-rider-row\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 16h14l-2-3V9A5 5 0 0 0 7 9v4zm5 4h4M12 2v2\"/></svg><strong>예약 알림을 받아보세요</strong></div><p>예약 확정과 정비 완료 소식을<br>빠르게 알려드릴게요.</p><span class=\"pv-rider-cta \">알림 받기</span><span class=\"pv-rider-prompt-secondary\">나중에 할게요</span></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-mileage-bottom-sheet-v2",
   "platform": "rider",
   "name": "MileageBottomSheetV2",
   "category": "오버레이",
   "purpose": "주행거리를 직접 입력해 갱신하는 V2 바텀시트를 표시한다.",
   "variants": [
    "기본",
    "현재 주행거리 표기",
    "서브헤더(title)",
    "검증 오류",
    "저장 중"
   ],
   "props": [
    {
     "name": "vehicleId",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "currentMileage",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "title",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColorsV2.bgWhite",
    "AppColorsV2.border",
    "AppColorsV2.linkAccent",
    "AppColorsV2.sosRed",
    "AppColorsV2.surface",
    "AppColorsV2.textMuted",
    "AppColorsV2.textSecondary",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppSpacing.radiusPill",
    "AppSpacing.screenPadding",
    "AppSpacing.xl",
    "AppSpacing.xs",
    "AppTypographyV2.body",
    "AppTypographyV2.cardSubtitle",
    "AppTypographyV2.cardTitle",
    "AppTypographyV2.eyebrow",
    "AppTypographyV2.heroNumber"
   ],
   "sizes": "시트 상단 radius 24, 패딩 H24/T12/B20(SafeArea), 자체 핸들 44×5·radius pill·아래 여백 16; 제목 cardTitle 17/w800, 부제 12/w500, 라벨 eyebrow 11/w800/ls1.2; 숫자 필드 heroNumber 26/w800 tabular·내부 패딩 16·radius 12·보더 1(오류 시 sosRed)·포커스 linkAccent 1.5·suffix KM 14/w700; 저장 CTA 높이 54. 토큰 값: AppSpacing.screenPadding=24.0; AppSpacing.xl=20.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.xs=8.0; AppSpacing.radiusMD=12.0; AppSpacing.radiusPill=9999.0; AppTypographyV2.cardTitle(size=17, weight=FontWeight.w800, height=1.25); AppTypographyV2.cardSubtitle(size=12, weight=FontWeight.w500, height=1.4); AppTypographyV2.eyebrow(size=11, weight=FontWeight.w800, height=1.4); AppTypographyV2.heroNumber(size=32, weight=FontWeight.w800, height=1) — 여기서는 fontSize 26 으로 덮어쓴다. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L130 EdgeInsets.only(bottom: viewInsets) | L133 color: AppColorsV2.bgWhite | L134 BorderRadius.vertical(top: Radius.circular(24)) | L140 EdgeInsets.fromLTRB( AppSpacing.screenPadding, AppSpacing.md, AppSpacing.screenPadding, AppSpacing.xl, ) | L153 width: 44 | L154 height: 5 | L155 EdgeInsets.only(bottom: AppSpacing.lg) | L157 color: AppColorsV2.border | L158 BorderRadius.circular(AppSpacing.radiusPill) | L163 AppTypographyV2.cardTitle | L178 AppTypographyV2.eyebrow | L195 height: 54 | L219 AppTypographyV2.heroNumber.copyWith(fontSize: 26) | L231 fillColor: AppColorsV2.surface | L233 EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.lg, ) | L237 BorderRadius.circular(AppSpacing.radiusMD) | L247 color: AppColorsV2.linkAccent | L248 width: 1.5",
   "file": "apps/rider/lib/features/vehicle/presentation/v2/mileage_bottom_sheet_v2.dart",
   "usage_count": 2,
   "notes": "V2 전용 · 라이더 시트 규약의 정본 — 호출부는 showModalBottomSheet(showDragHandle: false, backgroundColor: Colors.transparent, isScrollControlled: true) 로 띄우고, 시트가 자기 흰 컨테이너와 핸들(44×5) 하나를 그린다(핸들이 스크림 위로 뜨는 것을 막는다). RiderV2Theme 밖 Navigator 라 테마 의존 위젯 대신 토큰을 직접 쓴다. 키보드는 viewInsets 로 밀어 올린다. 입력은 ThousandsSeparatorInputFormatter(maxDigits: 7)·상한 999,999 이고 현재값 미만 금지 로컬 가드는 없다 — 실제 하한은 서버가 400 detail 로 돌려준다. CycleEditBottomSheetV2 가 같은 규약을 주석으로 참조한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다. 담기는 근거: 홈 V2·차량 상세 V2 2곳에서 같은 시트를 띄운다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "rider-bw-auto-fit-label",
   "platform": "rider",
   "name": "BwAutoFitLabel",
   "category": "레이아웃",
   "purpose": "주어진 글자 크기 목록에서 영역에 맞는 크기와 줄 수를 선택한다.",
   "variants": [
    "fitted",
    "smallest-size",
    "reduced-lines",
    "ellipsis",
    "bold-text"
   ],
   "props": [
    {
     "name": "text",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "style",
     "type": "TextStyle",
     "required": true,
     "default": null
    },
    {
     "name": "sizes",
     "type": "List<double>",
     "required": true,
     "default": null
    },
    {
     "name": "maxLines",
     "type": "int (default: 3)",
     "required": false,
     "default": null
    },
    {
     "name": "textAlign",
     "type": "TextAlign (default: TextAlign.center)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "크기 토큰 없음; fontSize는 sizes에서 선택, 기본 maxLines3·TextAlign.center; 실패하면 sizes.last와 더 적은 줄 수를 사용하며 최종 ellipsis. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L72 TextStyle(fontWeight: FontWeight.bold) | L72 fontWeight: FontWeight.bold | L103 fontSize: size | L111 maxWidth: maxWidth | L150 fontSize: chosen",
   "file": "apps/rider/lib/shared/widgets/text/bw_auto_fit_label.dart",
   "usage_count": 3,
   "notes": "공용 — 테마 독립 위젯이고 확인된 화면 import는 V2이다. sizes는 비어 있지 않은 내림차순이어야 한다. TextPainter로 스케일·locale·굵은 글씨·높이 제약을 반영한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-auto-fit-label\"><div class=\"pv-rider-stage \" style=\"height:114px\"><div class=\"pv-rider-scaled\" style=\"width:133.33333%;transform:scale(0.75)\"><div class=\"pv-rider-autofit\"><div><strong>정비소<br>찾아보기</strong><span>넓은 영역</span></div><div><b>가까운 정비소<br>찾아보기</b><span>작은 글자로 맞춤</span></div><div><b class=\"pv-rider-ellipsis\">내 차량의 모든 정비 내역</b><span>말줄임</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-quick-action-grid",
   "platform": "rider",
   "name": "BwQuickActionGrid",
   "category": "레이아웃",
   "purpose": "빠른 실행 항목을 두 열의 높이 맞춤 타일로 배치한다.",
   "variants": [
    "two-column",
    "odd-last-row",
    "enabled",
    "disabled"
   ],
   "props": [
    {
     "name": "items",
     "type": "List<BwQuickAction>",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgWhite",
    "AppColors.borderDefault",
    "AppColors.brandPrimary",
    "AppColors.textMuted",
    "AppSpacing.iconSM",
    "AppSpacing.lg",
    "AppSpacing.md",
    "AppSpacing.radiusMD",
    "AppTypography.bodyMD",
    "AppTypography.bodyXS"
   ],
   "sizes": "2열, 가로/세로 gap 12, minHeight 100, 패딩 16, radius 12, border 1.25, 아이콘 원 36×36·아이콘 18; 제목 14·설명 12. 토큰 값: AppSpacing.iconSM=18.0; AppSpacing.lg=16.0; AppSpacing.md=12.0; AppSpacing.radiusMD=12.0; AppTypography.bodyMD(size=14, weight=FontWeight.w700, height=미지정); AppTypography.bodyXS(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L73 BoxConstraints(minHeight: 100) | L75 EdgeInsets.all(AppSpacing.lg) | L78 BorderRadius.circular(AppSpacing.radiusMD) | L79 Border.all(color: AppColors.borderDefault, width: 1.25) | L109 AppTypography.bodyXS.copyWith( fontWeight: FontWeight.w400, color: AppColors.textMuted, ) | L47 height: AppSpacing.md | L55 width: AppSpacing.md | L73 minHeight: 100 | L77 color: AppColors.bgWhite | L79 color: AppColors.borderDefault | L79 width: 1.25 | L85 width: 36 | L86 height: 36 | L89 color: AppColors.brandPrimary.withValues(alpha: 0.10) | L94 size: AppSpacing.iconSM | L95 color: AppColors.brandPrimary | L106 height: 2 | L110 fontWeight: FontWeight.w400 | L111 color: AppColors.textMuted | L123 opacity: 0.45",
   "file": "apps/rider/lib/shared/widgets/cards/bw_quick_action_grid.dart",
   "usage_count": 3,
   "notes": "V1 전용 — enabled=false는 Opacity(0.45)만 적용하며 GestureDetector.onTap은 여전히 연결되어 있다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-quick-action-grid pv-rider-v1\"><div class=\"pv-rider-stage \" style=\"height:112px\"><div class=\"pv-rider-scaled\" style=\"width:125.00000%;transform:scale(0.8)\"><div class=\"pv-rider-quick-grid\"><div><span class=\"pv-rider-circle \" style=\"width:36px;height:36px\"><svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg></span><b>정비소 찾기</b><span>내 주변 정비소</span></div><div style=\"opacity:.45\"><span class=\"pv-rider-circle \" style=\"width:36px;height:36px\"><svg class=\"pv-rider-icon \" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 2h10l5 5v15H5zM14 2v6h6M8 12h9m-9 4h9\"/></svg></span><b>견적 요청</b><span>준비 중이에요</span></div></div></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-reservation-stepper",
   "platform": "rider",
   "name": "BwReservationStepper",
   "category": "레이아웃",
   "purpose": "번호 원과 제목 아래에 예약 단계 콘텐츠를 배치한다.",
   "variants": [
    "step"
   ],
   "props": [
    {
     "name": "step",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "child",
     "type": "Widget",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textWhite",
    "AppSpacing.md",
    "AppSpacing.xs",
    "AppTypography.headingMD",
    "AppTypography.numericSmall"
   ],
   "sizes": "번호 원24×24·텍스트12, 제목16, 가로 gap8, 콘텐츠 위 gap12. 토큰 값: AppSpacing.md=12.0; AppSpacing.xs=8.0; AppTypography.headingMD(size=16, weight=FontWeight.w700, height=미지정); AppTypography.numericSmall(size=12, weight=FontWeight.w700, height=미지정). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L39 AppTypography.numericSmall.copyWith( color: AppColors.textWhite, ) | L30 width: 24 | L31 height: 24 | L33 color: AppColors.brandPrimary | L40 color: AppColors.textWhite | L44 width: AppSpacing.xs | L48 height: AppSpacing.md",
   "file": "apps/rider/lib/shared/widgets/progress/bw_reservation_stepper.dart",
   "usage_count": 2,
   "notes": "V1 전용 — 단계 값 검증이나 완료 상태 분기 없이 전달받은 step을 표시한다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-reservation-stepper pv-rider-v1\"><div class=\"pv-rider-row \"><span class=\"pv-rider-reservation-number\">2</span><b style=\"font-size:16px\">정비 항목 선택</b></div><div class=\"pv-rider-sample-frame\" style=\"margin-top:12px\"><div class=\"pv-rider-row \"><span class=\"pv-rider-chip pv-rider-tone-info \">엔진오일 교체</span><span class=\"pv-rider-chip pv-rider-tone-neutral \">타이어</span></div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-delivery-components-delivery-section-header",
   "platform": "rider",
   "name": "DeliverySectionHeader",
   "category": "레이아웃",
   "purpose": "섹션 제목을 브랜드색 세로 알약 + 제목 + 우측 텍스트 액션으로 표시한다.",
   "variants": [
    "기본",
    "액션 있음"
   ],
   "props": [
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "actionLabel",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "onAction",
     "type": "VoidCallback? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppSpacing.md",
    "AppSpacing.radiusPill",
    "AppTypography.headingXL"
   ],
   "sizes": "알약 6×24·radius 9999, gap 12, 제목 headingXL 20/w700/h1.4, 액션 14/w700 brandPrimary. 토큰 값: AppSpacing.md=12.0; AppSpacing.radiusPill=9999.0; AppTypography.headingXL(size=20, weight=FontWeight.w700, height=1.4). 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L148 width: 6 | L149 height: 24 | L151 color: AppColors.brandPrimary | L152 BorderRadius.circular(AppSpacing.radiusPill) | L155 SizedBox(width: AppSpacing.md) | L156 AppTypography.headingXL | L161 HitTestBehavior.opaque | L164 kr(size: 14, color: AppColors.brandPrimary)",
   "file": "apps/rider/lib/features/delivery_ledger/presentation/widgets/delivery_components.dart",
   "usage_count": 5,
   "notes": "V1 토큰 — actionLabel 이 null 이면 우측 액션 영역을 그리지 않는다(onAction 만 주면 표시되지 않는다). 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count 는 파일 단위 값이다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다. 담기는 근거: 배달 장부·순수익 대시보드·정산 3곳에서 같은 섹션 규약을 쓴다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "rider-rider-text-scale-scope",
   "platform": "rider",
   "name": "RiderTextScaleScope",
   "category": "레이아웃",
   "purpose": "기기 접근성 글자 배율과 앱의 기본·크게 모드를 합성해 하위 화면 전체에 적용한다.",
   "variants": [
    "standard",
    "large",
    "device-scale-composed"
   ],
   "props": [
    {
     "name": "mode",
     "type": "RiderTextScaleMode",
     "required": true,
     "default": null
    },
    {
     "name": "child",
     "type": "Widget",
     "required": true,
     "default": null
    }
   ],
   "tokens": [
    "RiderTextScaleMode.standard",
    "RiderTextScaleMode.large"
   ],
   "sizes": "standard 1.0배, large 1.2배. 유효 배율은 기기 배율과 곱하며 상한을 두지 않는다.",
   "file": "apps/rider/lib/shared/theme/app_text_scale.dart",
   "usage_count": 1,
   "notes": "V1·V2 공용 — MaterialApp.builder에서 전역 적용한다. 시스템의 비선형 TextScaler 곡선을 보존한 채 앱 모드 배율을 합성한다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-text-scale-scope\"><div class=\"pv-rider-scale-compare\"><div><span>기본 · 1.0×</span><b>정비 내역을 확인해요</b></div><div class=\"pv-rider-scale-large\"><span>크게 · 1.2×</span><b>정비 내역을 확인해요</b></div></div><p class=\"pv-rider-muted\">기기 글자 배율 × 앱 모드 배율</p></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-brand-model-cascade-bw-cascade-option",
   "platform": "rider",
   "name": "BwCascadeOption",
   "category": "기타",
   "purpose": "제조사·모델 선택에 사용하는 ID·라벨·배기량 정보를 전달한다.",
   "variants": [
    "displacement"
   ],
   "props": [
    {
     "name": "id",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "displacement",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "시각 규격 없음(데이터 클래스).",
   "file": "apps/rider/lib/shared/widgets/forms/bw_brand_model_cascade.dart",
   "usage_count": 1,
   "notes": "V1 전용 — BwBrandModelCascade용 데이터 클래스. 앱 lib 내 직접 import는 없고 테스트 import만 확인된다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다. 앱 lib 내 직접 import 없음(테스트만 있거나 사용처 없음).",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-brand-model-cascade-bw-cascade-option pv-rider-v1\"><div class=\"pv-rider-data-diagram\"><strong>차량 선택 데이터</strong><dl><div><dt>id</dt><dd>125</dd></div><div><dt>label</dt><dd>PCX125</dd></div><div><dt>displacement</dt><dd>125 cc</dd></div></dl><div class=\"pv-rider-data-target\">↓ 제조사 · 모델 선택 필드</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-mileage-chart-bw-mileage-point",
   "platform": "rider",
   "name": "BwMileagePoint",
   "category": "기타",
   "purpose": "주행거리 차트의 날짜와 누적 주행거리를 전달한다.",
   "variants": [
    "default"
   ],
   "props": [
    {
     "name": "date",
     "type": "DateTime",
     "required": true,
     "default": null
    },
    {
     "name": "mileage",
     "type": "int",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "시각 규격 없음(데이터 클래스).",
   "file": "apps/rider/lib/shared/widgets/charts/bw_mileage_chart.dart",
   "usage_count": 2,
   "notes": "V1 전용 — BwMileageChart용 데이터 클래스다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-mileage-chart-bw-mileage-point pv-rider-v1\"><div class=\"pv-rider-data-diagram\"><strong>주행거리 기록</strong><dl><div><dt>date</dt><dd>2026.09.08</dd></div><div><dt>mileage</dt><dd>12,840 km</dd></div></dl><div class=\"pv-rider-data-target\">↓ 주행거리 차트의 한 점</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-ocr-result-editor-bw-ocr-field",
   "platform": "rider",
   "name": "BwOcrField",
   "category": "기타",
   "purpose": "OCR 항목의 키·라벨·값·신뢰도와 입력 형식을 전달한다.",
   "variants": [
    "high",
    "low",
    "mono",
    "keyboardType"
   ],
   "props": [
    {
     "name": "key",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "value",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "confidence",
     "type": "BwOcrConfidence (default: BwOcrConfidence.high)",
     "required": false,
     "default": null
    },
    {
     "name": "mono",
     "type": "bool (default: false)",
     "required": false,
     "default": null
    },
    {
     "name": "keyboardType",
     "type": "TextInputType? (default: null)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "시각 규격 없음(데이터 클래스).",
   "file": "apps/rider/lib/shared/widgets/ocr/bw_ocr_result_editor.dart",
   "usage_count": 1,
   "notes": "V1 전용 — 위젯 key가 아닌 String 데이터 key를 생성자로 받는다. 자체 렌더링은 없다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(1)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 1개다. 앱 lib 내 직접 import 없음(테스트만 있거나 사용처 없음).",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-ocr-result-editor-bw-ocr-field pv-rider-v1\"><div class=\"pv-rider-data-diagram\"><strong>문서 인식 항목</strong><dl><div><dt>label</dt><dd>차량 번호</dd></div><div><dt>value</dt><dd>1가2 3456</dd></div><div><dt>confidence</dt><dd><span class=\"pv-rider-chip pv-rider-tone-warning \">확인 필요</span></dd></div></dl><div class=\"pv-rider-data-target\">↓ OCR 결과 수정 필드</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-quick-action-grid-bw-quick-action",
   "platform": "rider",
   "name": "BwQuickAction",
   "category": "기타",
   "purpose": "빠른 실행 타일의 아이콘·제목·설명·동작·활성 상태를 전달한다.",
   "variants": [
    "enabled",
    "disabled"
   ],
   "props": [
    {
     "name": "icon",
     "type": "IconData",
     "required": true,
     "default": null
    },
    {
     "name": "title",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "onTap",
     "type": "VoidCallback",
     "required": true,
     "default": null
    },
    {
     "name": "subtitle",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "enabled",
     "type": "bool (default: true)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "시각 규격 없음(데이터 클래스).",
   "file": "apps/rider/lib/shared/widgets/cards/bw_quick_action_grid.dart",
   "usage_count": 3,
   "notes": "V1 전용 — BwQuickActionGrid용 데이터 클래스이며 자체 렌더링이 없다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-quick-action-grid-bw-quick-action pv-rider-v1\"><div class=\"pv-rider-data-diagram\"><strong>빠른 실행 항목</strong><dl><div><dt>icon</dt><dd><svg class=\"pv-rider-icon \" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg></dd></div><div><dt>title</dt><dd>정비소 찾기</dd></div><div><dt>enabled</dt><dd><span class=\"pv-rider-chip pv-rider-tone-info \">활성</span></dd></div></dl><div class=\"pv-rider-data-target\">↓ 두 열 빠른 실행 타일</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-terms-agreement-list-bw-terms-item",
   "platform": "rider",
   "name": "BwTermsItem",
   "category": "기타",
   "purpose": "동의 항목의 키·제목·약관 본문·체크 상태를 전달한다.",
   "variants": [
    "checked",
    "unchecked"
   ],
   "props": [
    {
     "name": "key",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "label",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "content",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "isChecked",
     "type": "bool",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "시각 규격 없음(데이터 클래스).",
   "file": "apps/rider/lib/shared/widgets/progress/bw_terms_agreement_list.dart",
   "usage_count": 2,
   "notes": "V1 전용 — String key를 갖는 데이터 클래스이며 상태 변경은 외부에서 수행한다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(2)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 2개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-terms-agreement-list-bw-terms-item pv-rider-v1\"><div class=\"pv-rider-data-diagram\"><strong>약관 동의 항목</strong><dl><div><dt>key</dt><dd>service_terms</dd></div><div><dt>label</dt><dd>서비스 이용약관</dd></div><div><dt>isChecked</dt><dd><span class=\"pv-rider-chip pv-rider-tone-info \"><svg class=\"pv-rider-icon \" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"/></svg>동의 완료</span></dd></div></dl><div class=\"pv-rider-data-target\">↓ 약관 전체 · 개별 동의</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-bw-tracking-map",
   "platform": "rider",
   "name": "BwTrackingMap",
   "category": "기타",
   "purpose": "라이더·정비사·정비소 위치를 네이버 지도에 표시한다.",
   "variants": [
    "map-ready",
    "map-unavailable",
    "rider",
    "mechanic",
    "buwang-workshop",
    "external-workshop"
   ],
   "props": [
    {
     "name": "riderPosition",
     "type": "LatLng? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "mechanicPosition",
     "type": "LatLng? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "workshops",
     "type": "List<ShopMarker> (default: const [])",
     "required": false,
     "default": null
    },
    {
     "name": "fallbackCenter",
     "type": "LatLng (default: const LatLng(36.35, 127.39))",
     "required": false,
     "default": null
    },
    {
     "name": "initialZoom",
     "type": "double (default: 14.0)",
     "required": false,
     "default": null
    },
    {
     "name": "riderColor",
     "type": "Color (default: AppColors.statusDanger)",
     "required": false,
     "default": null
    },
    {
     "name": "riderIcon",
     "type": "IconData (default: Icons.two_wheeler_outlined)",
     "required": false,
     "default": null
    },
    {
     "name": "mechanicColor",
     "type": "Color (default: AppColors.brandPrimary)",
     "required": false,
     "default": null
    },
    {
     "name": "mechanicIcon",
     "type": "IconData (default: Icons.build_outlined)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.bgSurface",
    "AppColors.brandPrimary",
    "AppColors.statusDanger",
    "AppColors.textMuted"
   ],
   "sizes": "NMarker 기본 size 24×32, initialZoom 14.0; 정비소 색 Color(0xFFFFC93C)/Color(0xFF9E9E9E); 지도 전체 크기는 부모 제약을 따른다. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L83 Color(0xFFFFC93C) | L84 Color(0xFF9E9E9E) | L88 Size(24, 32) | L132 size: _markerSize | L184 color: AppColors.bgSurface | L186 color: AppColors.textMuted",
   "file": "apps/rider/lib/shared/widgets/bw_tracking_map.dart",
   "usage_count": 5,
   "notes": "공용 — V1/V2 SOS 추적 화면에서 모두 import한다. riderIcon/mechanicIcon은 주석상 deprecated이며 실제 NMarker에는 적용하지 않는다. 중심은 mechanicPosition → riderPosition → fallbackCenter 순이다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-tracking-map \"><div class=\"pv-rider-map\"><svg viewBox=\"0 0 240 110\" width=\"100%\" height=\"110\" preserveAspectRatio=\"none\" aria-hidden=\"true\"><rect width=\"240\" height=\"110\" fill=\"#F1F5F9\"/><path d=\"M0 30H240M0 84H240M42 0V110M124 0V110M208 0V110\" stroke=\"#FFFFFF\" stroke-width=\"12\"/><path d=\"M-10 106 255 0\" stroke=\"#FFFFFF\" stroke-width=\"15\"/><path d=\"M124 27v30L76 77\" fill=\"none\" stroke=\"#005792\" stroke-width=\"2\" stroke-dasharray=\"4 3\"/><path d=\"M157 75h42v26h-42zM51 0h55v22H51Z\" fill=\"#E7F6EC\"/></svg><span class=\"pv-rider-map-pin pv-rider-map-rider\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg></span><span class=\"pv-rider-map-pin pv-rider-map-mechanic\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg></span><span class=\"pv-rider-map-pin pv-rider-map-shop\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg></span><span class=\"pv-rider-map-pin pv-rider-map-external\"><svg class=\"pv-rider-icon \" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0Z\"/><circle cx=\"12\" cy=\"10\" r=\"2.5\"/></svg></span><span class=\"pv-rider-map-legend\">내 위치 · 정비사 · 정비소</span></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-maintenance-icons",
   "platform": "rider",
   "name": "MaintenanceIcons",
   "category": "기타",
   "purpose": "정비 카테고리 이름을 아이콘 자산·Lucide 라인 아이콘으로 바꾸는 SSOT 와 원형·그리드·타일 표시 헬퍼를 제공한다.",
   "variants": [
    "categoryImage(자산 우선)",
    "circleIcon(V1 원형 버블)",
    "gridIcon(V2 3x3 그리드)",
    "tileIcon(V2 d12 타일)",
    "fallback(LucideIcons.wrench)"
   ],
   "props": [
    {
     "name": "categoryName",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "size",
     "type": "double (default: 44 categoryImage · 40 circleIcon · AppSpacingV2.iconGrid gridIcon)",
     "required": false,
     "default": null
    },
    {
     "name": "iconSize",
     "type": "double (default: 20 circleIcon · AppSpacingV2.iconMD tileIcon)",
     "required": false,
     "default": null
    },
    {
     "name": "selected",
     "type": "bool",
     "required": true,
     "default": null
    },
    {
     "name": "box",
     "type": "double (default: AppSpacingV2.tileIconBox tileIcon)",
     "required": false,
     "default": null
    },
    {
     "name": "backgroundColor",
     "type": "Color? (default: brandPrimary 10% circleIcon · AppColorsV2.navActivePillBg tileIcon)",
     "required": false,
     "default": null
    },
    {
     "name": "iconColor",
     "type": "Color? (default: AppColors.brandPrimary circleIcon · AppColorsV2.linkAccent tileIcon)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [
    "AppColors.brandPrimary",
    "AppColors.textSecondary",
    "AppColorsV2.linkAccent",
    "AppColorsV2.navActivePillBg",
    "AppColorsV2.textSecondary",
    "AppSpacingV2.iconGrid",
    "AppSpacingV2.iconMD",
    "AppSpacingV2.radiusIconTile",
    "AppSpacingV2.tileIconBox"
   ],
   "sizes": "categoryImage 44 정사각(PNG 자산, 실패 시 Lucide 아이콘); circleIcon 원 40·아이콘 20·배경 brandPrimary 10%; gridIcon 아이콘 26(자산이면 44); tileIcon 38×38·radius 11·아이콘 20·배경 #E7EEFB. 토큰 값: AppSpacingV2.iconGrid=26.0; AppSpacingV2.iconMD=20.0; AppSpacingV2.tileIconBox=38.0; AppSpacingV2.radiusIconTile=11.0; AppColorsV2.navActivePillBg=#E7EEFB; AppColorsV2.linkAccent=#1F62C9; AppColorsV2.textSecondary=#5C6B82; AppColors.brandPrimary=#005792. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스): L66 fallback = LucideIcons.wrench | L81 double size = 44 | L161 double size = 40 | L162 double iconSize = 20 | L170 backgroundColor ?? AppColors.brandPrimary.withValues(alpha: 0.1) | L177 iconColor ?? AppColors.brandPrimary | L196 double size = AppSpacingV2.iconGrid | L206 color: selected ? AppColorsV2.linkAccent : AppColorsV2.textSecondary | L219 double box = AppSpacingV2.tileIconBox | L220 double iconSize = AppSpacingV2.iconMD | L221 Color backgroundColor = AppColorsV2.navActivePillBg | L222 Color iconColor = AppColorsV2.linkAccent | L230 BorderRadius.circular(AppSpacingV2.radiusIconTile)",
   "file": "apps/rider/lib/shared/icons/maintenance_icons.dart",
   "usage_count": 17,
   "notes": "공용(V1·V2) 아이콘 SSOT — 정비사앱 apps/mobile/lib/shared/icons/maintenance_icons.dart 와 같은 매핑을 쓴다. 조회는 exact 맵 → 부분일치 추론(_substringIcon) → fallback 순이고, 화면에 로컬 매핑을 다시 만들지 말라고 코드 주석이 못박는다(V2 화면 4곳의 복붙 매핑을 흡수한 결과). 선택 표현은 V1(circleIcon)이 브랜드색 고정, V2(gridIcon)는 linkAccent/textSecondary 로 아이콘 색만 바꾸고 배경·보더는 호출부 타일이 담당한다. 정적 클래스라 생성자 props 가 없고 위 props 는 헬퍼 파라미터다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(17)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 17개다. 담기는 근거: 12개 화면과 shared/widgets 3개가 이 SSOT 를 부른다.",
   "style_source": null,
   "preview_html": null,
   "preview_variant": null
  },
  {
   "id": "rider-bw-tracking-map-shop-marker",
   "platform": "rider",
   "name": "ShopMarker",
   "category": "기타",
   "purpose": "지도에 표시할 정비소 ID·이름·위치·부왕 여부를 전달한다.",
   "variants": [
    "isBuwang=true",
    "isBuwang=false"
   ],
   "props": [
    {
     "name": "id",
     "type": "int",
     "required": true,
     "default": null
    },
    {
     "name": "name",
     "type": "String",
     "required": true,
     "default": null
    },
    {
     "name": "position",
     "type": "LatLng",
     "required": true,
     "default": null
    },
    {
     "name": "isBuwang",
     "type": "bool",
     "required": true,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "시각 규격 없음(데이터 클래스).",
   "file": "apps/rider/lib/shared/widgets/bw_tracking_map.dart",
   "usage_count": 5,
   "notes": "공용 — 위젯이 아닌 데이터 클래스이며 BwTrackingMap과 SOS provider에서 V1/V2가 공유한다. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(5)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 4개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-bw-tracking-map-shop-marker \"><div class=\"pv-rider-data-diagram\"><strong>지도 정비소 마커</strong><dl><div><dt>name</dt><dd>부왕 모터스</dd></div><div><dt>position</dt><dd>37.556, 126.923</dd></div><div><dt>isBuwang</dt><dd><span style=\"color:#FFC93C\">●</span> 부왕　<span style=\"color:#9E9E9E\">●</span> 외부</dd></div></dl><div class=\"pv-rider-data-target\">↓ SOS 추적 지도</div></div></div>",
   "preview_variant": null
  },
  {
   "id": "rider-vehicle-image",
   "platform": "rider",
   "name": "VehicleImage",
   "category": "기타",
   "purpose": "차량 사진에서 카탈로그·차종 실루엣·호출부 대체 이미지로 순차 대체한다.",
   "variants": [
    "photo",
    "catalog",
    "scooter",
    "manual",
    "underbone",
    "electric",
    "placeholder",
    "loading"
   ],
   "props": [
    {
     "name": "photoUrl",
     "type": "String?",
     "required": true,
     "default": null
    },
    {
     "name": "catalogImageUrl",
     "type": "String?",
     "required": true,
     "default": null
    },
    {
     "name": "placeholderBuilder",
     "type": "WidgetBuilder",
     "required": true,
     "default": null
    },
    {
     "name": "category",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "model",
     "type": "String? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "displacement",
     "type": "int? (default: null)",
     "required": false,
     "default": null
    },
    {
     "name": "fit",
     "type": "BoxFit (default: BoxFit.cover)",
     "required": false,
     "default": null
    }
   ],
   "tokens": [],
   "sizes": "고정 크기·패딩·색 토큰 없음; 네트워크 fit 기본 BoxFit.cover(변경 가능), 실루엣 BoxFit.contain, 로딩 시 SizedBox.expand. 코드 규격(해당 파일 행 번호; 본체와 참조 보조 클래스):",
   "file": "apps/rider/lib/shared/widgets/vehicle/vehicle_image.dart",
   "usage_count": 3,
   "notes": "공용 — V1 차량 상세와 V2 차량 카드가 공유한다. URL 오류 시 다음 단계로 이동하고 VehicleKind.unknown이면 실루엣을 추측하지 않는다. 생성자 공통 super.key: Key?는 주요 props에서 생략. usage_count는 정의 파일을 제외한 직접 import/export 파일 수(3)이며, 그중 주석·문자열을 제외한 심볼 참조 파일은 3개다.",
   "style_source": null,
   "preview_html": "<div class=\"pv-rider-preview pv-rider-vehicle-image \"><div class=\"pv-rider-vehicle-image\"><svg class=\"pv-rider-scooter\" viewBox=\"0 0 240 140\" fill=\"none\" aria-label=\"PCX125 스쿠터 실루엣\" role=\"img\"><ellipse cx=\"120\" cy=\"124\" rx=\"99\" ry=\"5\" fill=\"#16243A\" opacity=\".09\"/><circle cx=\"52\" cy=\"105\" r=\"23\" fill=\"#16243A\"/><circle cx=\"192\" cy=\"105\" r=\"23\" fill=\"#16243A\"/><circle cx=\"52\" cy=\"105\" r=\"12\" fill=\"#94A3B8\"/><circle cx=\"192\" cy=\"105\" r=\"12\" fill=\"#94A3B8\"/><path d=\"M52 104 91 67h27l-10 30h54l20-46-16-16 6-8 21 14-5 24 21 23-8 10-24-18-12 31H70Z\" fill=\"#475569\"/><path d=\"M26 76c7-13 29-24 60-23h30l-10 22-38 13-40-2Z\" fill=\"#1F62C9\"/><path d=\"m111 96 9-34 17-9 20-25 16 8-12 42-19 23Z\" fill=\"#1F62C9\"/><path d=\"M47 53h67v9H39Z\" fill=\"#16243A\"/><path d=\"m158 28 9-16h15l-10 19Z\" fill=\"#16243A\" opacity=\".7\"/><path d=\"m178 27 12-7m-1-1 12 1\" stroke=\"#16243A\" stroke-width=\"4\" stroke-linecap=\"round\"/><path d=\"m28 76-7 4 5 7h10m121-34 10 3-6 12\" fill=\"#E2E9F4\"/></svg></div><div class=\"pv-rider-mini\"><div class=\"pv-rider-wrap\"><span class=\"pv-rider-chip pv-rider-tone-neutral \">스쿠터</span><span class=\"pv-rider-chip pv-rider-tone-neutral \">이미지 없음</span><span class=\"pv-rider-chip pv-rider-tone-neutral \">로딩</span></div></div></div>",
   "preview_variant": null
  }
 ]
}
