{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "section-title",
  "type": "registry:ui",
  "title": "Section Title",
  "description": "콘텐츠 한 덩어리에 이름을 붙이고, 그 덩어리에만 해당하는 설명이나 버튼을 오른쪽에 두는 제목 줄입니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/lib-bem",
    "@atom/lib-cn",
    "@atom/lib-slot"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/section-title.tsx",
      "content": "\"use client\";\n\nimport { type HTMLAttributes, type ReactNode, type Ref, useId } from \"react\";\nimport { mod } from \"../lib/bem\";\nimport { cn } from \"../lib/cn\";\nimport { Slot } from \"../lib/slot\";\n\n/* -------------------------------------------------------------------------- */\n/*  Section Title (components/section-title.md,                               */\n/*                 styles/atom/elements/section-title.css)                    */\n/*    <SectionTitle                                                           */\n/*      title=\"항목 목록\" count=\"(1/20)\" variant=\"underline\"                  */\n/*      actions={<Button size=\"xsmall\" variant=\"neutralOutline\">EXCEL</Button>} */\n/*    />                                                                      */\n/*    <SectionTitle as=\"h3\" size=\"small\" layout=\"stack\"                       */\n/*      title=\"수용 변경 내역\" description=\"최근 신설·변경된 정보입니다\" />     */\n/*  레이아웃 엘리먼트 — 제목 단계(as)와 size 는 서로 연동되지 않습니다.          */\n/* -------------------------------------------------------------------------- */\n\nexport type SectionTitleSize = \"small\" | \"medium\";\nexport type SectionTitleVariant = \"plain\" | \"underline\";\nexport type SectionTitleLayout = \"inline\" | \"stack\";\nexport type SectionTitleAs = \"h2\" | \"h3\" | \"h4\";\n\nconst B = \"atom-section-title\";\n\nexport type SectionTitleProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"children\"> & {\n  ref?: Ref<HTMLDivElement>;\n  /** 덩어리의 이름입니다. */\n  title: string;\n  /** `__heading` 이 렌더할 태그입니다. 문서 구조가 정합니다. */\n  as?: SectionTitleAs;\n  /** 제목 앞의 아이콘입니다. 장식으로 노출됩니다. */\n  icon?: ReactNode;\n  /** `\"(1/20)\"` 처럼 이미 조립된 문자열입니다. */\n  count?: string;\n  /** (구현 추가) Count 의 뜻이 글자만으로 불분명할 때 읽힐 이름입니다. 예: \"20건 중 1건\" */\n  countLabel?: string;\n  /** 보충 한 줄입니다. */\n  description?: ReactNode;\n  /** 그 덩어리에 작용하는 버튼들입니다. */\n  actions?: ReactNode;\n  /** Description 을 같은 줄에 둘지 아래로 내릴지입니다. */\n  layout?: SectionTitleLayout;\n  /** 행 아래 경계선입니다. */\n  variant?: SectionTitleVariant;\n  /** 제목과 보조 글자의 크기입니다. */\n  size?: SectionTitleSize;\n};\n\nfunction SectionTitleRoot({\n  ref,\n  title,\n  as: Heading = \"h2\",\n  icon,\n  count,\n  countLabel,\n  description,\n  actions,\n  layout = \"inline\",\n  variant = \"plain\",\n  size = \"medium\",\n  className,\n  ...rest\n}: SectionTitleProps) {\n  const descriptionId = useId();\n  const hasDescription = description !== undefined && description !== null && description !== false;\n  return (\n    <div\n      {...rest}\n      ref={ref}\n      className={cn(\n        B,\n        mod(B, \"size\", size, \"medium\"),\n        mod(B, \"variant\", variant, \"plain\"),\n        mod(B, \"layout\", layout, \"inline\"),\n        className,\n      )}\n    >\n      <Heading className={`${B}__heading`} aria-describedby={hasDescription ? descriptionId : undefined}>\n        {icon !== undefined && icon !== null && (\n          <Slot aria-hidden=\"true\" className={`${B}__icon`}>\n            {icon}\n          </Slot>\n        )}\n        <span className={`${B}__title`}>{title}</span>\n        {count !== undefined && (\n          <span className={`${B}__count`} aria-label={countLabel}>\n            {count}\n          </span>\n        )}\n      </Heading>\n      {hasDescription && (\n        <p id={descriptionId} className={`${B}__description`}>\n          {description}\n        </p>\n      )}\n      {actions !== undefined && actions !== null && <div className={`${B}__actions`}>{actions}</div>}\n    </div>\n  );\n}\n\nexport const SectionTitle = Object.assign(SectionTitleRoot, {});\n",
      "type": "registry:ui",
      "target": "components/ui/section-title.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/button.css",
      "content": "/* ============================================================================\n   Element — Button\n   ----------------------------------------------------------------------------\n   atom-button\n     --size_{xsmall|small|medium|large}      medium is the default\n     --variant_{brand-solid|neutral-solid|neutral-weak|critical-solid\n                |brand-outline|neutral-outline|ghost}\n     --layout_icon-only\n     --width_full\n   states: :hover  [data-pressed]  [disabled]  [data-loading]\n\n   Loaded by both the card preview and the detail page, so\n   this file is the only definition of the component.\n   ========================================================================== */\n\n.atom-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--atom-x2);\n  height: 47px;\n  padding-inline: var(--atom-x5);\n  border-radius: var(--atom-radius-r2);\n  font-size: 15px;\n  font-weight: 600;\n  line-height: 1;\n  white-space: nowrap;\n  user-select: none;\n  transition: filter var(--atom-duration-fast) ease,\n    background-color var(--atom-duration-fast) ease;\n}\n\n.atom-button__icon {\n  width: 18px;\n  height: 18px;\n  flex-shrink: 0;\n}\n\n.atom-button__label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* --- variants ------------------------------------------------------------- */\n.atom-button--variant_neutral-solid {\n  background: var(--atom-bg-neutral-solid);\n  color: var(--atom-fg-on-solid);\n}\n\n.atom-button--variant_brand-solid {\n  background: var(--atom-primary-600);\n  color: #fff;\n}\n\n.atom-button--variant_critical-solid {\n  background: var(--atom-bg-critical-solid);\n  color: #fff;\n}\n\n.atom-button--variant_neutral-weak {\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-button--variant_brand-outline {\n  background: transparent;\n  color: var(--atom-primary-600);\n  box-shadow: inset 0 0 0 1px currentColor;\n}\n\n.atom-button--variant_neutral-outline {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n  box-shadow: inset 0 0 0 1px var(--atom-border-default);\n}\n\n.atom-button--variant_ghost {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-button--size_xsmall {\n  height: 32px;\n  gap: var(--atom-x1);\n  padding-inline: var(--atom-x3);\n  border-radius: var(--atom-radius-full);\n  font-size: 13px;\n}\n\n.atom-button--size_xsmall .atom-button__icon {\n  width: 14px;\n  height: 14px;\n}\n\n.atom-button--size_small {\n  height: 40px;\n  padding-inline: var(--atom-x4);\n  font-size: 14px;\n}\n\n.atom-button--size_small .atom-button__icon {\n  width: 16px;\n  height: 16px;\n}\n\n.atom-button--size_large {\n  height: 54px;\n  padding-inline: var(--atom-x6);\n  border-radius: var(--atom-radius-r3);\n  font-size: 16px;\n}\n\n.atom-button--size_large .atom-button__icon {\n  width: 20px;\n  height: 20px;\n}\n\n/* --- layout -------------------------------------------------------------- */\n.atom-button--layout_icon-only {\n  width: 47px;\n  padding-inline: 0;\n}\n\n.atom-button--layout_icon-only.atom-button--size_xsmall {\n  width: 32px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_small {\n  width: 40px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_large {\n  width: 54px;\n}\n\n.atom-button--width_full {\n  width: 100%;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-button:hover:not([disabled]):not([data-loading]) {\n  filter: brightness(0.94);\n}\n\n.atom-button--variant_ghost:hover:not([disabled]),\n.atom-button--variant_neutral-outline:hover:not([disabled]),\n.atom-button--variant_brand-outline:hover:not([disabled]) {\n  filter: none;\n  background: var(--atom-bg-neutral-weak);\n}\n\n.atom-button[data-pressed] {\n  filter: brightness(0.88);\n}\n\n.atom-button[disabled] {\n  opacity: 0.35;\n  cursor: not-allowed;\n  filter: none;\n}\n\n.atom-button[data-loading] {\n  cursor: progress;\n}\n\n.atom-button[data-loading] .atom-button__label {\n  opacity: 0.45;\n}\n\n.atom-button__spinner {\n  width: 16px;\n  height: 16px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-full);\n  border: 2px solid currentColor;\n  border-top-color: transparent;\n  animation: atom-button-spin 700ms linear infinite;\n}\n\n@keyframes atom-button-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/button.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/grid.css",
      "content": "/* ============================================================================\n   Element — Grid (Data Table)\n   ----------------------------------------------------------------------------\n   A paged data table. The header stays put while the body scrolls, columns can\n   be sorted, moved, resized, pinned and filtered, and the footer reports the\n   row count, the pager and the page size in one line.\n\n   atom-grid\n     --size_{small|medium}                   medium is the default\n     [data-state=\"ready|loading|empty\"]\n     __scroll  __table  __thead  __tbody\n     __th  __th-inner  __th-label  __th-sort  __th-filter  __th-grip\n     __th-resize\n     __tr  __td  __cell-check\n     __tree-cell  __tree-caret  __tree-count    the hierarchy column\n     __check                                 header + row checkbox\n     __filter-popover  __filter-group  __filter-title  __filter-row\n     __filter-list  __filter-divider  __filter-actions\n     __overlay  __overlay-text  __spinner  __empty  __empty-text\n     __footer  __count  __count-value  __page-size\n\n   The footer carries all three status controls in one row —\n   총 건수 · Pagination · Page size, left to right. The middle one is the\n   Pagination element (styles/elements/pagination.css), not a part of this file.\n\n   Column modifiers live on both __th and __td so a pinned column stays lined\n   up through the body:\n     --pinned          sticky to the inline start edge\n     --sorted_asc  --sorted_desc\n     --align_{end|center}\n     --check           the row-selection column\n\n   __td--tree is the exception: it sits on the body cell only. The header of a\n   tree column needs no indent, and a modifier with no rule behind it is a\n   promise the stylesheet does not keep.\n\n   Tree rows are flat <tr>s, not nested ones — a table cannot nest and stay a\n   table. Depth arrives as an inline custom property on the row, the way\n   tree.css does it, because a `--depth_1 … --depth_9` ladder would cap the\n   hierarchy at whatever number this file stopped at. Collapsed descendants are\n   not hidden with CSS; the row model stops emitting them.\n\n   The checkbox repeats the Checkbox visual rather than importing it, for the\n   same reason Multi Select does: a card preview loads one stylesheet.\n   ========================================================================== */\n\n.atom-grid {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-width: 0;\n  font-size: 14px;\n  color: var(--atom-fg-neutral);\n}\n\n/* --- scroll container ---------------------------------------------------- */\n/* Vertical scroll lives here rather than on the page so the sticky header has\n   something to stick to; horizontal scroll appears on its own once the columns\n   outgrow the box.\n\n   `flex: 1 1 auto` with an auto basis means the box sizes to its content when\n   the Grid has no height of its own, and absorbs the leftover space when it\n   does — so `height: 300px` on .atom-grid is enough to pin the whole thing. */\n.atom-grid__scroll {\n  position: relative;\n  flex: 1 1 auto;\n  min-height: 0;\n  overflow: auto;\n  max-height: 420px;\n  border-radius: var(--atom-radius-r2);\n  box-shadow: inset 0 0 0 1px var(--atom-border-subtle);\n}\n\n.atom-grid__table {\n  width: 100%;\n  border-collapse: separate;\n  border-spacing: 0;\n  table-layout: fixed;\n}\n\n/* --- header -------------------------------------------------------------- */\n.atom-grid__thead .atom-grid__th {\n  position: sticky;\n  top: 0;\n  z-index: 2;\n  height: 44px;\n  padding: 0;\n  background: var(--atom-bg-neutral-weak);\n  border-bottom: 1px solid var(--atom-border-default);\n  font-size: 13px;\n  font-weight: 600;\n  color: var(--atom-fg-neutral);\n  text-align: left;\n  white-space: nowrap;\n}\n\n.atom-grid__th-inner {\n  position: relative;\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  height: 100%;\n  padding-inline: var(--atom-x3);\n}\n\n.atom-grid__th-label {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* --- sort (C) — 내림차 → 오름차 → 기본값 --------------------------------- */\n.atom-grid__th-sort {\n  display: inline-flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 1px;\n  width: 14px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-subtle);\n  cursor: pointer;\n}\n\n.atom-grid__th-sort svg {\n  width: 8px;\n  height: 5px;\n  display: block;\n}\n\n.atom-grid__th--sorted_desc .atom-grid__th-sort,\n.atom-grid__th--sorted_asc .atom-grid__th-sort {\n  color: var(--atom-fg-neutral);\n}\n\n/* Only the active direction stays solid — a sorted column that highlights both\n   arrows tells the user nothing about which way it went. */\n.atom-grid__th--sorted_desc .atom-grid__th-sort svg:first-child,\n.atom-grid__th--sorted_asc .atom-grid__th-sort svg:last-child {\n  opacity: 0.25;\n}\n\n/* --- filter (F) ---------------------------------------------------------- */\n.atom-grid__th-filter {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 20px;\n  height: 20px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-r1);\n  color: var(--atom-fg-neutral-muted);\n  cursor: pointer;\n  transition: background-color var(--atom-duration-fast) ease,\n    color var(--atom-duration-fast) ease;\n}\n\n.atom-grid__th-filter svg {\n  width: 13px;\n  height: 13px;\n}\n\n.atom-grid__th-filter:hover {\n  background: var(--atom-bg-layer);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-grid__th-filter[data-active] {\n  color: var(--atom-fg-brand);\n}\n\n.atom-grid__th-filter[aria-expanded=\"true\"] {\n  background: var(--atom-bg-layer);\n  color: var(--atom-fg-neutral);\n}\n\n/* --- column filter popover (F) ------------------------------------------- */\n/* Anchored to .atom-grid, not to the header cell: __scroll clips its own\n   overflow, so a popover parented inside a cell would be cut off the moment it\n   grew taller than the header. Production code positions it against the cell's\n   rect and closes it on focusout or Esc. */\n.atom-grid__filter-popover {\n  position: absolute;\n  z-index: 5;\n  display: flex;\n  flex-direction: column;\n  gap: var(--atom-x3);\n  width: 280px;\n  padding: var(--atom-x4);\n  border-radius: var(--atom-radius-r3);\n  background: var(--atom-bg-layer-raised);\n  box-shadow: var(--atom-shadow-s3), inset 0 0 0 1px var(--atom-border-subtle);\n  font-size: 13px;\n  text-align: left;\n  font-weight: 400;\n}\n\n.atom-grid__filter-popover[data-state=\"closed\"] {\n  display: none;\n}\n\n.atom-grid__filter-group {\n  display: flex;\n  flex-direction: column;\n  gap: var(--atom-x2);\n}\n\n.atom-grid__filter-title {\n  font-size: 11px;\n  font-weight: 600;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n.atom-grid__filter-row {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n}\n\n/* Both children default to width:100%, so without explicit flex the two of\n   them ask for more than the row has and shrink together — squeezing the mode\n   picker until its label breaks mid-word. The mode has a fixed job and a fixed\n   width; the search box takes what is left. */\n.atom-grid__filter-row .atom-select {\n  flex: 0 0 96px;\n}\n\n.atom-grid__filter-row .atom-text-input {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n/* The value list scrolls on its own so the popover keeps a predictable height\n   no matter how many distinct values the column holds. */\n.atom-grid__filter-list {\n  display: flex;\n  flex-direction: column;\n  max-height: 84px;\n  overflow-y: auto;\n}\n\n.atom-grid__filter-divider {\n  height: 1px;\n  background: var(--atom-border-subtle);\n}\n\n.atom-grid__filter-actions {\n  display: flex;\n  justify-content: flex-end;\n  gap: var(--atom-x2);\n}\n\n/* --- move (B) ------------------------------------------------------------ */\n.atom-grid__th-grip {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 14px;\n  height: 20px;\n  flex-shrink: 0;\n  margin-inline-start: -4px;\n  color: var(--atom-fg-neutral-subtle);\n  cursor: grab;\n  opacity: 0;\n  transition: opacity var(--atom-duration-fast) ease;\n}\n\n.atom-grid__th-grip svg {\n  width: 10px;\n  height: 14px;\n}\n\n.atom-grid__th:hover .atom-grid__th-grip {\n  opacity: 1;\n}\n\n.atom-grid__th[data-dragging] {\n  background: var(--atom-bg-layer-raised);\n}\n\n.atom-grid__th[data-drop-target] {\n  box-shadow: inset 2px 0 0 var(--atom-fg-brand);\n}\n\n/* --- resize (D) ---------------------------------------------------------- */\n/* The handle is wider than the line it draws — a 1px hit area is unusable with\n   a mouse and impossible with a trackpad. */\n.atom-grid__th-resize {\n  position: absolute;\n  inset-block: 0;\n  inset-inline-end: -4px;\n  width: 9px;\n  cursor: col-resize;\n  z-index: 1;\n}\n\n.atom-grid__th-resize::after {\n  content: \"\";\n  position: absolute;\n  inset-block: 10px;\n  inset-inline-start: 4px;\n  width: 1px;\n  background: var(--atom-border-default);\n}\n\n.atom-grid__th-resize:hover::after,\n.atom-grid__th-resize[data-dragging]::after {\n  inset-block: 0;\n  width: 2px;\n  background: var(--atom-fg-brand);\n}\n\n/* --- body ---------------------------------------------------------------- */\n.atom-grid__td {\n  height: 44px;\n  padding-inline: var(--atom-x3);\n  border-bottom: 1px solid var(--atom-border-subtle);\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  background: var(--atom-bg-layer);\n}\n\n.atom-grid__tr:last-child .atom-grid__td {\n  border-bottom: 0;\n}\n\n.atom-grid__tr:hover .atom-grid__td {\n  background: var(--atom-bg-nav-hover);\n}\n\n.atom-grid__tr[aria-selected=\"true\"] .atom-grid__td {\n  background: var(--atom-bg-informative-weak);\n}\n\n.atom-grid__th--align_end,\n.atom-grid__td--align_end {\n  text-align: right;\n}\n\n.atom-grid__th--align_end .atom-grid__th-inner {\n  flex-direction: row-reverse;\n}\n\n.atom-grid__th--align_center,\n.atom-grid__td--align_center {\n  text-align: center;\n}\n\n/* Numbers are compared down the column, so they line up digit by digit. */\n.atom-grid__td--align_end {\n  font-variant-numeric: tabular-nums;\n}\n\n/* --- pinned (E) ---------------------------------------------------------- */\n.atom-grid__th--pinned,\n.atom-grid__td--pinned {\n  position: sticky;\n  inset-inline-start: 0;\n  z-index: 1;\n}\n\n.atom-grid__thead .atom-grid__th--pinned {\n  z-index: 3;\n}\n\n/* The row washes have to stay opaque on a pinned cell.\n   `--atom-bg-nav-hover` is 4% brand — a tint, not a fill. Behind a normal cell\n   that lands on a static surface and reads as intended, but a pinned cell is\n   the only thing standing between the reader and the columns sliding\n   underneath it: replacing its opaque `--atom-bg-layer` with a 4% wash lets\n   the scrolled text through and the two sets of text collide. So the surface\n   stays the background *colour* and the wash is painted as an image over it.\n\n   Selection outranks hover here the same way it does on a plain cell, and it\n   needs no layering — `--atom-bg-informative-weak` is already opaque. */\n.atom-grid__tr:hover .atom-grid__td--pinned {\n  background-color: var(--atom-bg-layer);\n  background-image: linear-gradient(\n    var(--atom-bg-nav-hover),\n    var(--atom-bg-nav-hover)\n  );\n}\n\n.atom-grid__tr[aria-selected=\"true\"] .atom-grid__td--pinned,\n.atom-grid__tr[aria-selected=\"true\"]:hover .atom-grid__td--pinned {\n  background-color: var(--atom-bg-informative-weak);\n  background-image: none;\n}\n\n/* The shadow only appears once the grid is actually scrolled sideways —\n   a permanent seam reads as a border the user cannot explain. */\n.atom-grid[data-scrolled-x] .atom-grid__th--pinned::after,\n.atom-grid[data-scrolled-x] .atom-grid__td--pinned::after {\n  content: \"\";\n  position: absolute;\n  inset-block: 0;\n  inset-inline-end: 0;\n  width: 6px;\n  transform: translateX(100%);\n  background: linear-gradient(\n    to right,\n    rgb(0 0 0 / 0.08),\n    rgb(0 0 0 / 0)\n  );\n  pointer-events: none;\n}\n\n/* --- row checkbox (A) ---------------------------------------------------- */\n.atom-grid__th--check,\n.atom-grid__td--check {\n  width: 44px;\n  padding-inline: 0;\n  text-align: center;\n}\n\n.atom-grid__th--check .atom-grid__th-inner {\n  justify-content: center;\n  padding-inline: 0;\n}\n\n.atom-grid__check {\n  position: relative;\n  display: inline-block;\n  width: 18px;\n  height: 18px;\n  vertical-align: middle;\n  border-radius: var(--atom-radius-r1);\n  background: var(--atom-bg-layer);\n  box-shadow: inset 0 0 0 1.5px var(--atom-border-default);\n  cursor: pointer;\n  transition: background-color var(--atom-duration-fast) ease,\n    box-shadow var(--atom-duration-fast) ease;\n}\n\n.atom-grid__check svg,\n.atom-grid__check i {\n  position: absolute;\n  inset: 0;\n  margin: auto;\n  color: var(--atom-fg-on-solid);\n  opacity: 0;\n}\n\n.atom-grid__check svg {\n  width: 12px;\n  height: 12px;\n}\n\n.atom-grid__check i {\n  width: 9px;\n  height: 2px;\n  border-radius: 1px;\n  background: var(--atom-fg-on-solid);\n}\n\n.atom-grid__check[data-state=\"checked\"],\n.atom-grid__check[data-state=\"indeterminate\"] {\n  background: var(--atom-bg-neutral-solid);\n  box-shadow: inset 0 0 0 1.5px var(--atom-bg-neutral-solid);\n}\n\n.atom-grid__check[data-state=\"checked\"] svg,\n.atom-grid__check[data-state=\"indeterminate\"] i {\n  opacity: 1;\n}\n\n/* --- loading (5) --------------------------------------------------------- */\n.atom-grid__overlay {\n  position: absolute;\n  inset: 0;\n  z-index: 4;\n  display: none;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: var(--atom-x3);\n  background: var(--atom-bg-scrim);\n  border-radius: var(--atom-radius-r2);\n}\n\n.atom-grid[data-state=\"loading\"] .atom-grid__overlay {\n  display: flex;\n}\n\n.atom-grid__overlay-text {\n  font-size: 13px;\n  color: var(--atom-fg-neutral);\n}\n\n.atom-grid__spinner {\n  width: 28px;\n  height: 28px;\n  border-radius: var(--atom-radius-full);\n  border: 3px solid var(--atom-border-default);\n  border-top-color: var(--atom-fg-brand);\n  animation: atom-grid-spin var(--atom-duration-slow) linear infinite;\n}\n\n@keyframes atom-grid-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .atom-grid__spinner {\n    animation-duration: 2.4s;\n  }\n}\n\n/* --- empty (6) ----------------------------------------------------------- */\n.atom-grid__empty {\n  display: none;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: var(--atom-x2);\n  min-height: 180px;\n  padding: var(--atom-x8) var(--atom-x4);\n  background: var(--atom-bg-layer);\n}\n\n.atom-grid[data-state=\"empty\"] .atom-grid__empty {\n  display: flex;\n}\n\n.atom-grid[data-state=\"empty\"] .atom-grid__tbody {\n  display: none;\n}\n\n.atom-grid__empty-text {\n  font-size: 14px;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n/* --- footer — 총 건수 (1) + pager (2) + page size (3) --------------------- */\n/* One row, three slots: count left, pager centred, page size right. Only the\n   two ends are styled here — the pager in the middle is its own element, and\n   what this file still owns about it is placement. See the rule below. */\n.atom-grid__footer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--atom-x4);\n  padding-top: var(--atom-x3);\n}\n\n.atom-grid__count {\n  flex-shrink: 0;\n  font-size: 13px;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* The count is the one number a user checks against their own expectation, so\n   it stays legible while the label around it recedes. */\n.atom-grid__count-value {\n  font-weight: 600;\n  font-variant-numeric: tabular-nums;\n  color: var(--atom-fg-neutral);\n}\n\n.atom-grid__page-size {\n  flex-shrink: 0;\n  width: 96px;\n}\n\n/* The pager is Pagination — styles/elements/pagination.css. The block moved\n   out of this file once a list that is not a table needed the same control,\n   and it deliberately carries no margin of its own, so where it sits is the\n   caller's business. Here it is centred against the Grid rather than against\n   the gap between its neighbours: the count growing a digit must not shift the\n   page numbers out from under a hand about to press the same button twice. */\n.atom-grid__footer .atom-pagination {\n  margin-inline: auto;\n}\n\n/* --- tree ----------------------------------------------------------------\n   One column carries the hierarchy; every other column stays a plain cell.\n\n   The indent is padding on the cell, not a margin on its contents, so the row\n   fill — hover, selection, the zebra of the border — still runs the full width\n   of the column. Content pushed in by a margin leaves the fill starting at the\n   indent, and the rows read as a set of disconnected boxes.\n\n   20px per level is tree.css's `--atom-tree-indent`. The two controls sit side\n   by side in the same screens, and a hierarchy that steps 20px in a Tree and\n   something else in a Grid reads as two different depths. */\n.atom-grid {\n  --atom-grid-tree-indent: 20px;\n}\n\n/* The fallback is not decoration: a virtualised body renders rows into a flat\n   container that may not have .atom-grid above it, and a row with no depth\n   still has to draw at level 0. */\n.atom-grid__td--tree {\n  padding-inline-start: calc(\n    var(--atom-x3) + var(--atom-grid-tree-depth, 0) *\n      var(--atom-grid-tree-indent, 20px)\n  );\n}\n\n/* The cell is a flex row so the caret keeps its box while the label truncates.\n   __td already sets `overflow: hidden`, so the ellipsis lands on the label. */\n.atom-grid__tree-cell {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  min-width: 0;\n}\n\n/* A leaf keeps the slot and drops the glyph — the same trade tree.css makes.\n   Removing the box would pull every leaf 16px left of its siblings, and the\n   indent is the only thing saying which parent a row belongs to. */\n.atom-grid__tree-caret {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 16px;\n  height: 16px;\n  flex-shrink: 0;\n  border: 0;\n  padding: 0;\n  background: none;\n  color: var(--atom-fg-neutral-muted);\n  cursor: pointer;\n  transition: transform var(--atom-duration-fast) var(--atom-ease-out);\n}\n\n.atom-grid__tree-caret svg {\n  display: block;\n  width: 14px;\n  height: 14px;\n}\n\n.atom-grid__tree-caret--empty {\n  pointer-events: none;\n  visibility: hidden;\n}\n\n.atom-grid__tree-caret:focus-visible {\n  outline: 2px solid var(--atom-fg-brand);\n  outline-offset: 1px;\n  border-radius: var(--atom-radius-r1);\n}\n\n.atom-grid__tr[aria-expanded=\"true\"] .atom-grid__tree-caret {\n  transform: rotate(90deg);\n}\n\n/* The label owns the truncation, not the cell: an ellipsis on __td would clip\n   the count along with the name. */\n.atom-grid__tree-label {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n/* Muted and a step down, because it is a fact about the row rather than the\n   row's name. `tabular-nums` so the number stops shifting as branches open. */\n.atom-grid__tree-count {\n  flex-shrink: 0;\n  font-variant-numeric: tabular-nums;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-grid--size_small {\n  /* One step narrower with the rows, so the staircase keeps the same slope\n     against a 36px row that it has against a 44px one. */\n  --atom-grid-tree-indent: 16px;\n\n  font-size: 13px;\n}\n\n.atom-grid--size_small .atom-grid__thead .atom-grid__th {\n  height: 36px;\n  font-size: 12px;\n}\n\n.atom-grid--size_small .atom-grid__td {\n  height: 36px;\n}\n\n.atom-grid--size_small .atom-grid__th--check,\n.atom-grid--size_small .atom-grid__td--check {\n  width: 36px;\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/grid.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/section-title.css",
      "content": "/* ============================================================================\n   Element — Section Title\n   ----------------------------------------------------------------------------\n   The line that names a block of content and carries whatever belongs to that\n   block alone: a note about what the block holds, a count, or the buttons that\n   act on it. One page usually has several — the AS-IS screen this was measured\n   from stacks five of them, one per table.\n\n   It is a layout, not a heading style. The heading element still owns its own\n   level (`h2` under the page title, `h3` under that); this element owns the row\n   the heading sits in and the slots to its right.\n\n   Grid, not float. The AS-IS pattern right-aligns its slot with\n   `float: right`, which only worked because a `body div.main` rule turned it on\n   and an `overflow: hidden` on the wrapper cleaned up after it. Three columns\n   with the first at `1fr` puts the same content in the same place without\n   either, and keeps description and actions from landing on top of each other\n   when a section has both.\n\n   atom-section-title\n     --size_{small|medium}                medium is the default\n     --variant_{plain|underline}          plain is the default\n     --layout_{inline|stack}              inline is the default\n     __heading      the title row: icon + title + count. Put h2/h3 here\n     __icon         optional leading icon\n     __title        the name of the block\n     __count        optional \"(3/20)\" — tabular so it stops jittering\n     __description  optional supporting line\n     __actions      optional row of util buttons\n   ========================================================================== */\n\n/* Columns 2 and 3 are `auto`, so they collapse to nothing when their slots are\n   absent and a title-only instance still spans the row. Column 1 at `1fr` is\n   what pushes the rest to the far edge. */\n.atom-section-title {\n  display: grid;\n  grid-template-columns: minmax(0, 1fr) auto auto;\n  align-items: center;\n  column-gap: var(--atom-x5);\n  row-gap: var(--atom-x2);\n}\n\n/* --- heading -------------------------------------------------------------- */\n/* `margin: 0` because this is an h2/h3 and the browser's own margin would fight\n   the grid's row-gap.\n\n   18px / 1.5 / -0.01em is `--atom-t7` from Foundations — Typography, and 500 is\n   what that document calls the system's defining choice: headings are Medium,\n   not Bold, because Hangul closes up its counters at heavier weights. Hierarchy\n   comes from size here, not from weight. 600 belongs to button and form labels.\n   Do not reach for it to make a title louder. */\n.atom-section-title__heading {\n  grid-area: 1 / 1;\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  min-width: 0;\n  margin: 0;\n  font-size: 18px;\n  line-height: 1.5;\n  font-weight: 500;\n  letter-spacing: -0.01em;\n  color: var(--atom-fg-neutral);\n}\n\n/* Optional, and decorative when present: the title text already names the block,\n   so the icon carries `aria-hidden` and adds nothing a reader needs. Sized to\n   18px / 16px, the same pair `__icon` takes in button.css and autocomplete.css,\n   so an icon in a section title matches one in the buttons beside it. */\n.atom-section-title__icon {\n  width: 18px;\n  height: 18px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* A long name truncates rather than pushing the count and the buttons off the\n   row. The full text stays in the DOM for the screen reader. */\n.atom-section-title__title {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n/* Meta, so 400 and `--atom-fg-neutral-muted` (6.99:1) — 13px is `--atom-t3`,\n   the scale's step for supporting text. Size and colour separate it from the\n   title; weight is not needed and 500 here would blur the two together.\n\n   Tabular figures: the count sits next to a name and changes as the user pages\n   through, and proportional digits make the whole title shift sideways. */\n.atom-section-title__count {\n  flex-shrink: 0;\n  font-size: 13px;\n  font-weight: 400;\n  font-variant-numeric: tabular-nums;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* --- description ---------------------------------------------------------- */\n/* Muted and a step down, because it explains the block rather than naming it.\n   At the same weight and size as the title it reads as a second title.\n   13px `--atom-t3` / 1.5, `--atom-fg-neutral-muted` at 6.99:1 — over the 4.5:1\n   body-text floor, so this is readable rather than decorative.\n   `--atom-fg-neutral-subtle` is 2.57:1 and never carries text. */\n.atom-section-title__description {\n  grid-area: 1 / 2;\n  font-size: 13px;\n  line-height: 1.5;\n  color: var(--atom-fg-neutral-muted);\n  word-break: keep-all;\n}\n\n/* --- actions -------------------------------------------------------------- */\n.atom-section-title__actions {\n  grid-area: 1 / 3;\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  flex-wrap: wrap;\n  justify-content: flex-end;\n}\n\n/* --- layout --------------------------------------------------------------- */\n/* `inline` keeps the description on the title's row, at the far edge. That is\n   the AS-IS arrangement and it survives migration unchanged, but on a wide\n   table the two ends of the row can sit 1000px apart.\n   `stack` drops the description under the title, where a reader finds it\n   without crossing the row. Actions stay where they were. */\n.atom-section-title--layout_stack .atom-section-title__description {\n  grid-area: 2 / 1;\n  justify-self: start;\n}\n\n/* --- variant -------------------------------------------------------------- */\n/* The rule is a divider, not an underline of the text: it spans the whole row\n   so the title and its buttons read as one band sitting on the content below.\n   1px on a token, where the AS-IS drew 2px of #c6c6c7.\n\n   `--atom-border-default` is 1.36:1 on white, which Foundations — Color flags as\n   short of WCAG 1.4.11's 3:1. That rule governs boundaries that identify a UI\n   component; this line separates content and carries no state, so the shortfall\n   is not a barrier. The heading is what names the section, not the rule. */\n.atom-section-title--variant_underline {\n  padding-bottom: var(--atom-x3);\n  border-bottom: 1px solid var(--atom-border-default);\n}\n\n/* --- sizes ---------------------------------------------------------------- */\n/* Two steps, matching the two the AS-IS ended up with: 18px (`--atom-t7`) for\n   the block a page is about, 15px (`--atom-t5`) for a block inside it. Anything\n   smaller stops reading as a heading next to 15px body text.\n\n   Line-height stays 1.5 at both sizes. `--atom-t5` proposes 1.7, but that is the\n   prose figure; Foundations puts headings at 1.45-1.5 and this is a heading at a\n   body size, not a paragraph. Letter-spacing goes back to normal because the\n   scale only tightens from 18px up. */\n.atom-section-title--size_small .atom-section-title__heading {\n  font-size: 15px;\n  letter-spacing: normal;\n}\n\n.atom-section-title--size_small .atom-section-title__icon {\n  width: 16px;\n  height: 16px;\n}\n\n.atom-section-title--size_small .atom-section-title__count,\n.atom-section-title--size_small .atom-section-title__description {\n  font-size: 12px;\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/section-title.css"
    }
  ],
  "categories": [
    "display"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "component",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "SectionTitle"
    ],
    "docs": "poc/design-system/components/section-title.md"
  }
}
