{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scene",
  "title": "Scene",
  "description": "Panel grammar layout container enforcing 12px (pair), 20px (scene), and 32px (change) gutters.",
  "registryDependencies": [
    "https://frame12.dev/r/frame12-styles.json"
  ],
  "files": [
    {
      "path": "ui/scene.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\n\n// Bundlers (Next.js, webpack, Vite) statically replace `process.env.NODE_ENV`. Declared locally so\n// consumers without @types/node (e.g. Vite's react-ts template) still type-check this file.\ndeclare const process: { env: { NODE_ENV?: string } };\n\nexport type SceneGutter = 'pair' | 'scene' | 'change';\nexport type SceneLayout = 'split' | 'stack' | 'equal' | 'three' | 'areas';\n\nexport const GUTTERS: Record<SceneGutter, number> = {\n  pair: 12,\n  scene: 20,\n  change: 32,\n};\n\nexport interface SceneProps extends React.HTMLAttributes<HTMLDivElement> {\n  gutter?: SceneGutter;\n  layout?: SceneLayout;\n  areas?: string[];\n  columns?: number;\n  breakpoint?: number | string;\n  children?: React.ReactNode;\n}\n\nexport interface SceneAreaProps extends React.HTMLAttributes<HTMLDivElement> {\n  area: string;\n  children?: React.ReactNode;\n}\n\nexport function resolveAreaProps(\n  area: string | undefined,\n  style: React.CSSProperties | undefined,\n): { 'data-area': string | undefined; style: React.CSSProperties | undefined } {\n  const safeArea = typeof area === 'string' && area.trim() ? area.trim() : undefined;\n  const mergedStyle: React.CSSProperties | undefined = safeArea\n    ? ({\n        '--f12-area': safeArea,\n        ...style,\n      } as React.CSSProperties)\n    : style;\n  return { 'data-area': safeArea, style: mergedStyle };\n}\n\nexport const SceneArea = React.forwardRef<HTMLDivElement, SceneAreaProps>(function SceneArea(\n  { area, className = '', style, children, ...rest },\n  ref,\n) {\n  const areaProps = resolveAreaProps(area, style);\n\n  return (\n    <div\n      ref={ref}\n      className={`f12-scene-area${className ? ` ${className}` : ''}`}\n      data-area={areaProps['data-area']}\n      style={areaProps.style}\n      {...rest}\n    >\n      {children}\n    </div>\n  );\n});\n\nexport const Scene = React.forwardRef<HTMLDivElement, SceneProps>(function Scene(\n  {\n    gutter = 'scene',\n    layout = 'split',\n    areas,\n    columns,\n    breakpoint = '860px',\n    className = '',\n    style,\n    children,\n    ...rest\n  },\n  ref,\n) {\n  const reactId = React.useId();\n  const warnedAreasRef = React.useRef<string | null>(null);\n  const warnedColsRef = React.useRef<string | null>(null);\n  const warnedBpRef = React.useRef<string | null>(null);\n  const safeGutter: SceneGutter =\n    gutter === 'pair' || gutter === 'scene' || gutter === 'change' ? gutter : 'scene';\n  const hasAreas = Array.isArray(areas) && areas.length > 0;\n  const safeLayout: SceneLayout = hasAreas\n    ? 'areas'\n    : layout === 'split' ||\n        layout === 'stack' ||\n        layout === 'equal' ||\n        layout === 'three' ||\n        layout === 'areas'\n      ? layout\n      : 'split';\n\n  const cleanedRows = hasAreas\n    ? areas.map((row) => row.trim().replace(/^[\"']+|[\"']+$/g, '').trim())\n    : [];\n  const rowTokenCounts = cleanedRows.map((row) => (row ? row.split(/\\s+/).length : 0));\n  const firstRowCount = rowTokenCounts[0] ?? 0;\n\n  const formattedAreas = hasAreas\n    ? cleanedRows.map((row) => `\"${row}\"`).join(' ')\n    : undefined;\n\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    hasAreas &&\n    rowTokenCounts.length > 1 &&\n    rowTokenCounts.some((count) => count !== firstRowCount) &&\n    warnedAreasRef.current !== formattedAreas\n  ) {\n    warnedAreasRef.current = formattedAreas ?? null;\n    console.warn(\n      `[FRAME/12 Scene] Mismatched column counts across areas rows (${rowTokenCounts.join(', ')}). Every row in areas should have ${firstRowCount} column token(s).`,\n    );\n  }\n\n  const explicitCols =\n    typeof columns === 'number' && Number.isFinite(columns) && columns > 0\n      ? Math.floor(columns)\n      : undefined;\n\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    hasAreas &&\n    explicitCols !== undefined &&\n    explicitCols !== firstRowCount &&\n    warnedColsRef.current !== `${explicitCols}:${firstRowCount}`\n  ) {\n    warnedColsRef.current = `${explicitCols}:${firstRowCount}`;\n    console.warn(\n      `[FRAME/12 Scene] Explicit columns (${explicitCols}) does not match the areas row token count (${firstRowCount}). Deriving ${firstRowCount} column(s) from areas.`,\n    );\n  }\n\n  const safeColumns = firstRowCount > 0 ? firstRowCount : (explicitCols ?? 12);\n\n  const hasExplicitBreakpoint =\n    breakpoint !== undefined &&\n    (typeof breakpoint !== 'string' || breakpoint.trim().length > 0);\n  const rawBp =\n    typeof breakpoint === 'number' && Number.isFinite(breakpoint) && breakpoint > 0\n      ? `${breakpoint}px`\n      : typeof breakpoint === 'string' && breakpoint.trim()\n        ? /^\\d+(\\.\\d+)?$/.test(breakpoint.trim())\n          ? `${breakpoint.trim()}px`\n          : breakpoint.trim()\n        : '860px';\n  const isValidBreakpoint =\n    !hasExplicitBreakpoint ||\n    (typeof breakpoint === 'number'\n      ? Number.isFinite(breakpoint) && breakpoint > 0\n      : /^\\d+(\\.\\d+)?(px|rem|em)$/i.test(rawBp));\n\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    hasExplicitBreakpoint &&\n    !isValidBreakpoint &&\n    warnedBpRef.current !== String(breakpoint)\n  ) {\n    warnedBpRef.current = String(breakpoint);\n    console.warn(\n      `[FRAME/12 Scene] Invalid breakpoint \"${String(breakpoint)}\". Expected a positive number or px/rem/em length (e.g. \"960px\", \"60rem\"). Falling back to 860px.`,\n    );\n  }\n\n  const normalizedBreakpoint = isValidBreakpoint ? rawBp : '860px';\n  const isCustomBreakpoint = normalizedBreakpoint !== '860px';\n  const bpSlug = normalizedBreakpoint.replace(/[^a-zA-Z0-9_-]/g, '-');\n  const bpScope = `bp-${bpSlug}-${reactId.replace(/[^a-zA-Z0-9_-]/g, '')}`;\n\n  const mergedStyle: React.CSSProperties | undefined = hasAreas\n    ? ({\n        '--f12-scene-cols': String(safeColumns),\n        '--f12-scene-areas': formattedAreas,\n        ...style,\n      } as React.CSSProperties)\n    : style;\n\n  const classes = `f12-scene f12-scene-${safeLayout} f12-scene-${safeGutter}${className ? ` ${className}` : ''}`;\n\n  return (\n    <div\n      ref={ref}\n      className={classes}\n      data-gutter={GUTTERS[safeGutter]}\n      data-layout={safeLayout}\n      data-columns={hasAreas ? safeColumns : undefined}\n      data-breakpoint={hasAreas ? normalizedBreakpoint : undefined}\n      data-scene-bp={hasAreas && isCustomBreakpoint ? bpScope : undefined}\n      style={mergedStyle}\n      {...rest}\n    >\n      {hasAreas && isCustomBreakpoint ? (\n        <style>{`@media (min-width: ${normalizedBreakpoint}) { .f12-scene.f12-scene-areas[data-scene-bp=\"${bpScope}\"] { grid-template-columns: repeat(var(--f12-scene-cols, 12), minmax(0, 1fr)); grid-template-areas: var(--f12-scene-areas); } .f12-scene.f12-scene-areas[data-scene-bp=\"${bpScope}\"] > [data-area] { grid-area: var(--f12-area); } } @container (max-width: 760px) { .f12-scene.f12-scene-areas[data-scene-bp=\"${bpScope}\"] { grid-template-columns: minmax(0, 1fr); grid-template-areas: none; } .f12-scene.f12-scene-areas[data-scene-bp=\"${bpScope}\"] > [data-area] { grid-area: auto; } }`}</style>\n      ) : null}\n      {children}\n    </div>\n  );\n});\n",
      "type": "registry:ui",
      "target": "components/frame12/scene.tsx"
    }
  ],
  "type": "registry:ui"
}