{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grid-layout",
  "title": "线条网格布局",
  "description": "带全宽横线、竖线容器、刻度尺等多种纹路分隔带和交点十字的响应式布局原语。",
  "dependencies": [
    "class-variance-authority"
  ],
  "files": [
    {
      "path": "src/registry/ui/grid-layout/grid-layout.tsx",
      "content": "import type * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\n// 像素约定：竖线是 GridContainer 的 border-x（位于内容区外 1px），\n// 上方横线是上一行的底线（同样在外 1px），本行底线由 screen-line-bottom 画在内侧最后 1px。\n// 十字为 11px 方框，线在第 5 px，所以用整数偏移让中心正好压在线条像素上，不用 translate(-50%)。\nconst gridCrosshairVariants = cva(\n  \"pointer-events-none absolute z-10 size-[11px] before:absolute before:inset-y-0 before:left-[5px] before:w-px before:bg-foreground/45 after:absolute after:inset-x-0 after:top-[5px] after:h-px after:bg-foreground/45\",\n  {\n    variants: {\n      /**\n       * 十字所在的角：top-left、top-right、bottom-left、bottom-right\n       * @default \"top-left\"\n       */\n      position: {\n        \"top-left\": \"-top-[6px] -left-[6px]\",\n        \"top-right\": \"-top-[6px] -right-[6px]\",\n        \"bottom-left\": \"-bottom-[5px] -left-[6px]\",\n        \"bottom-right\": \"-right-[6px] -bottom-[5px]\",\n      },\n      /**\n       * 十字的定位基准。line：底线由 screen-line-bottom 画在元素内侧（GridSection / GridSeparator）；border：元素自身有四边 border\n       * @default \"line\"\n       */\n      anchor: { line: \"\", border: \"\" },\n    },\n    compoundVariants: [\n      { anchor: \"border\", position: \"bottom-left\", className: \"-bottom-[6px]\" },\n      {\n        anchor: \"border\",\n        position: \"bottom-right\",\n        className: \"-bottom-[6px]\",\n      },\n    ],\n    defaultVariants: { position: \"top-left\", anchor: \"line\" },\n  },\n)\n\n// 纹理层：高 31px（不覆盖底线），左端在左竖线外 4096px，平铺原点因此正好落在竖线上\nconst gridSeparatorVariants = cva(\n  \"absolute top-0 bottom-px -left-[4097px] -right-[4096px]\",\n  {\n    variants: {\n      pattern: {\n        ruler: \"pattern-ruler\",\n        hatch: \"pattern-hatch\",\n        dots: \"pattern-dots\",\n        grid: \"pattern-grid\",\n        plus: \"pattern-plus\",\n        wave: \"pattern-wave\",\n        none: \"\",\n      },\n    },\n    defaultVariants: { pattern: \"dots\" },\n  },\n)\n\ntype GridSeparatorPattern = NonNullable<\n  VariantProps<typeof gridSeparatorVariants>[\"pattern\"]\n>\n\n/**\n * 交点十字：绘制在容器竖线与横线交点处的小「+」，纯装饰。\n * 放在带 `position: relative` 的行（GridSection、GridSeparator）内，由 position 指定四个角之一。\n */\nfunction GridCrosshair({\n  className,\n  position,\n  anchor,\n  ...props\n}: React.ComponentProps<\"span\"> & VariantProps<typeof gridCrosshairVariants>) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"grid-crosshair\"\n      data-position={position ?? \"top-left\"}\n      className={cn(gridCrosshairVariants({ position, anchor }), className)}\n      {...props}\n    />\n  )\n}\n\n/**\n * 页面主容器：固定宽度 + 左右竖线。\n * 宽度可通过 CSS 变量 --grid-width 覆盖（默认 48rem）。\n * 外宽多 1px，使左右两条竖线的间距正好等于 --grid-width（纹路周期都能整除它）。\n * 视口窄于容器时，两侧各留出 gutter：竖线向内缩，全宽横线仍延伸到屏幕边缘，形成「线框中的内容」。\n * gutter 是两侧之和，用 CSS 变量 --grid-gutter 覆盖（默认窄屏 2rem、sm 起 3rem；设为 0px 则竖线贴边）。\n */\nfunction GridContainer({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"grid-container\"\n      className={cn(\n        \"mx-auto w-[calc(100%-var(--grid-gutter,2rem))] max-w-[calc(var(--grid-width,48rem)+1px)] border-x border-edge sm:w-[calc(100%-var(--grid-gutter,3rem))]\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * 一行内容：底部一条横跨整个屏幕的分隔线。\n * 放在 GridContainer 内使用。\n */\nfunction GridSection({\n  className,\n  topLine = false,\n  crosshair = false,\n  children,\n  ...props\n}: React.ComponentProps<\"section\"> & {\n  /**\n   * 在行的顶部补一条全屏横线（默认只画底线）\n   * @default false\n   */\n  topLine?: boolean\n  /**\n   * 在底线与左右竖线的两个交点处绘制「+」\n   * @default false\n   */\n  crosshair?: boolean\n}) {\n  return (\n    <section\n      data-slot=\"grid-section\"\n      data-top-line={topLine}\n      className={cn(\n        \"screen-line-bottom\",\n        topLine && \"screen-line-top\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      {crosshair ? (\n        <>\n          <GridCrosshair position=\"bottom-left\" />\n          <GridCrosshair position=\"bottom-right\" />\n        </>\n      ) : null}\n    </section>\n  )\n}\n\n/**\n * 分隔带：全屏宽度的纹理 + 底部横线，用于主题切换处。\n * 默认是点阵纹路；需要时可用 crosshair 在交点绘制「+」。\n */\nfunction GridSeparator({\n  className,\n  pattern,\n  label,\n  crosshair,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof gridSeparatorVariants> & {\n    /**\n     * 纹路：dots、ruler、hatch、grid、plus、wave，none 为只留线条\n     * @default \"dots\"\n     */\n    pattern?: GridSeparatorPattern | null\n    /** 左侧的等宽小字编号（如 `02 / PROJECTS`），像标签一样贴在纹路上；可与任意纹路组合 */\n    label?: React.ReactNode\n    /**\n     * 是否在四个交点绘制十字\n     * @default false\n     */\n    crosshair?: boolean\n  }) {\n  const resolved = pattern ?? \"dots\"\n  const showCrosshair = crosshair ?? false\n  return (\n    <div\n      aria-hidden\n      data-slot=\"grid-separator\"\n      data-pattern={resolved}\n      className={cn(\"screen-line-bottom h-8 w-full\", className)}\n      {...props}\n    >\n      {resolved === \"ruler\" ? (\n        // 刻度尺带一层极淡的底色，让分隔带读作一条「带子」\n        <div className=\"absolute top-0 -right-[4096px] bottom-px -left-[4097px] bg-foreground/[0.025]\" />\n      ) : null}\n      <div\n        data-slot=\"grid-separator-pattern\"\n        className={gridSeparatorVariants({ pattern: resolved })}\n      />\n      {label ? (\n        <div\n          data-slot=\"grid-separator-label\"\n          className=\"absolute inset-y-0 left-4 flex items-center sm:left-6\"\n        >\n          <span className=\"bg-background px-1.5 font-mono text-[11px] leading-4 tracking-[0.08em] whitespace-nowrap text-muted-foreground\">\n            {label}\n          </span>\n        </div>\n      ) : null}\n      {showCrosshair ? (\n        <>\n          <GridCrosshair position=\"top-left\" />\n          <GridCrosshair position=\"top-right\" />\n          <GridCrosshair position=\"bottom-left\" />\n          <GridCrosshair position=\"bottom-right\" />\n        </>\n      ) : null}\n    </div>\n  )\n}\n\n/**\n * 分栏：栏与栏之间用虚线分隔，窄屏自动堆叠。\n */\nfunction GridColumns({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"grid-columns\"\n      className={cn(\n        \"grid grid-cols-1 divide-y divide-dashed divide-edge sm:grid-cols-2 sm:divide-x sm:divide-y-0\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  GridColumns,\n  GridContainer,\n  GridCrosshair,\n  GridSection,\n  GridSeparator,\n  gridCrosshairVariants,\n  gridSeparatorVariants,\n}\nexport type { GridSeparatorPattern }\n",
      "type": "registry:ui",
      "target": "@ui/grid-layout/grid-layout.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-edge": "var(--edge)"
    },
    "light": {
      "edge": "color-mix(in oklch, var(--foreground) 12%, transparent)"
    },
    "dark": {
      "edge": "color-mix(in oklch, var(--foreground) 18%, transparent)"
    }
  },
  "css": {
    "@utility screen-line-top": {
      "position": "relative",
      "&::before": {
        "content": "\"\"",
        "position": "absolute",
        "top": "0",
        "left": "-100vw",
        "width": "200vw",
        "height": "1px",
        "background-color": "var(--edge)",
        "pointer-events": "none"
      }
    },
    "@utility screen-line-bottom": {
      "position": "relative",
      "&::after": {
        "content": "\"\"",
        "position": "absolute",
        "bottom": "0",
        "left": "-100vw",
        "width": "200vw",
        "height": "1px",
        "background-color": "var(--edge)",
        "pointer-events": "none"
      }
    },
    "@utility pattern-ruler": {
      "background-color": "color-mix(in oklab, var(--foreground) 34%, transparent)",
      "mask-image": "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='31' shape-rendering='crispEdges'%3E%3Crect x='0' y='19' width='1' height='12'/%3E%3Crect x='0' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='8' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='8' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='16' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='16' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='24' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='24' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='32' y='24' width='1' height='7' fill-opacity='.8'/%3E%3Crect x='32' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='40' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='40' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='48' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='48' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='56' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='56' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='64' y='24' width='1' height='7' fill-opacity='.8'/%3E%3Crect x='64' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='72' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='72' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='80' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='80' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='88' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='88' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='96' y='24' width='1' height='7' fill-opacity='.8'/%3E%3Crect x='96' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='104' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='104' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='112' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='112' y='0' width='1' height='3' fill-opacity='.45'/%3E%3Crect x='120' y='27' width='1' height='4' fill-opacity='.55'/%3E%3Crect x='120' y='0' width='1' height='3' fill-opacity='.45'/%3E%3C/svg%3E\")",
      "mask-size": "128px 31px",
      "mask-repeat": "repeat-x",
      "mask-position": "0 0"
    },
    "@utility pattern-hatch": {
      "background-color": "color-mix(in oklab, var(--foreground) 34%, transparent)",
      "mask-image": "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M-1-1L9 9M7-1L9 1M-1 7L1 9' fill='none' stroke='black'/%3E%3C/svg%3E\")",
      "mask-size": "8px 8px",
      "mask-repeat": "repeat",
      "mask-position": "0 0"
    },
    "@utility pattern-dots": {
      "background-color": "color-mix(in oklab, var(--foreground) 34%, transparent)",
      "mask-image": "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='31' shape-rendering='crispEdges'%3E%3Crect x='4' y='7' width='1' height='1'/%3E%3Crect x='4' y='15' width='1' height='1'/%3E%3Crect x='4' y='23' width='1' height='1'/%3E%3C/svg%3E\")",
      "mask-size": "8px 31px",
      "mask-repeat": "repeat-x",
      "mask-position": "0 0"
    },
    "@utility pattern-grid": {
      "background-color": "color-mix(in oklab, var(--foreground) 14%, transparent)",
      "mask-image": "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='31' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='1' height='31'/%3E%3Crect x='1' y='7' width='7' height='1'/%3E%3Crect x='1' y='15' width='7' height='1'/%3E%3Crect x='1' y='23' width='7' height='1'/%3E%3C/svg%3E\")",
      "mask-size": "8px 31px",
      "mask-repeat": "repeat-x",
      "mask-position": "0 0"
    },
    "@utility pattern-plus": {
      "background-color": "color-mix(in oklab, var(--foreground) 34%, transparent)",
      "mask-image": "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='31' shape-rendering='crispEdges'%3E%3Crect x='6' y='7' width='5' height='1'/%3E%3Crect x='8' y='5' width='1' height='2'/%3E%3Crect x='8' y='8' width='1' height='2'/%3E%3Crect x='6' y='23' width='5' height='1'/%3E%3Crect x='8' y='21' width='1' height='2'/%3E%3Crect x='8' y='24' width='1' height='2'/%3E%3C/svg%3E\")",
      "mask-size": "16px 31px",
      "mask-repeat": "repeat-x",
      "mask-position": "0 0"
    },
    "@utility pattern-wave": {
      "background-color": "color-mix(in oklab, var(--foreground) 34%, transparent)",
      "mask-image": "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='31'%3E%3Cpath d='M0 15.5C5.33 10.5 10.67 10.5 16 15.5S26.67 20.5 32 15.5' fill='none' stroke='black'/%3E%3C/svg%3E\")",
      "mask-size": "32px 31px",
      "mask-repeat": "repeat-x",
      "mask-position": "0 0"
    },
    "@utility bg-hatch": {
      "background-image": "repeating-linear-gradient(\n    45deg,\n    var(--edge) 0 1px,\n    transparent 0 50%\n  )",
      "background-size": "8px 8px"
    },
    "@utility bg-dots": {
      "background-image": "conic-gradient(\n    at 1px 1px,\n    transparent 75%,\n    color-mix(in oklab, var(--foreground) 34%, transparent) 0\n  )",
      "background-size": "8px 8px",
      "background-position": "4px 4px"
    },
    "@utility bg-grid": {
      "background-image": "linear-gradient(var(--edge) 1px, transparent 1px),\n    linear-gradient(90deg, var(--edge) 1px, transparent 1px)",
      "background-size": "16px 16px"
    },
    "@layer base": {
      "html, body": {
        "overflow-x": "clip"
      }
    }
  },
  "type": "registry:ui"
}
