{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "exploded-view",
  "title": "Exploded View",
  "description": "An isometric stack of slabs springs apart with leader lines and labels, holds, then clicks back together.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/exploded-view/index.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type ExplodedViewColors = {\n  background: string;\n  top: string;\n  left: string;\n  right: string;\n  highlightTop: string;\n  highlightLeft: string;\n  highlightRight: string;\n  line: string;\n  label: string;\n};\n\nexport type ExplodedViewProps = {\n  /** Number of slabs, 2 to 6. */\n  layers?: number;\n  /** One label per slab, top slab first. */\n  labels?: string[];\n  /** Index of the red slab, counted from the top. */\n  highlight?: number;\n  colors?: Partial<ExplodedViewColors>;\n  /** Background color. Overrides `colors.background`. */\n  backgroundColor?: string;\n  /** Label text color. Overrides `colors.label`. */\n  labelColor?: string;\n  speed?: number;\n};\n\nexport const explodedViewDefaults = {\n  layers: 4,\n  labels: [\"Interface\", \"Engine\", \"Storage\", \"Hardware\"],\n  highlight: 1,\n  colors: {\n    background: \"#111111\",\n    top: \"#F4F4F4\",\n    left: \"#A6A6A6\",\n    right: \"#747474\",\n    highlightTop: \"#E5322D\",\n    highlightLeft: \"#B5231F\",\n    highlightRight: \"#8A1A17\",\n    line: \"#FFFFFF\",\n    label: \"#FFFFFF\",\n  } satisfies ExplodedViewColors,\n  speed: 1,\n};\n\nconst FONT = \"Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\";\nconst OPEN = 3;\nconst CLOSE = 38;\nconst COS30 = Math.cos(Math.PI / 6);\nconst clamp = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\nexport function ExplodedView({\n  layers = explodedViewDefaults.layers,\n  labels = explodedViewDefaults.labels,\n  highlight = explodedViewDefaults.highlight,\n  colors: colorOverrides,\n  backgroundColor,\n  labelColor,\n  speed = explodedViewDefaults.speed,\n}: ExplodedViewProps) {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const baseColors = { ...explodedViewDefaults.colors, ...colorOverrides };\n  const colors = {\n    ...baseColors,\n    background: backgroundColor ?? baseColors.background,\n    label: labelColor ?? baseColors.label,\n  };\n  const n = Math.round(Math.min(6, Math.max(2, layers)));\n  const t = (frame * speed * 30) / fps;\n\n  const side = height * 0.36;\n  const thick = height * 0.036;\n  const gap = (height * 0.27) / (n - 1);\n  const cx = width * 0.4;\n  const cy = height * 0.5;\n\n  const iso = (X: number, Y: number, Z: number) =>\n    [(X - Y) * COS30, (X + Y) * 0.5 - Z] as const;\n  const pts = (list: (readonly [number, number])[]) =>\n    list.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(\" \");\n\n  const close = spring({\n    frame: t - CLOSE,\n    fps: 30,\n    config: { damping: 26, stiffness: 320, mass: 0.8 },\n  });\n  // Top slab leaves first; bottom one barely waits.\n  const spread = Array.from({ length: n }, (_, i) => {\n    const fromTop = n - 1 - i;\n    const open = spring({\n      frame: t - OPEN - fromTop * 1.5,\n      fps: 30,\n      config: { damping: 12, stiffness: 110 },\n    });\n    return Math.max(0, open * (1 - close));\n  });\n  const offsets: number[] = [];\n  let z = 0;\n  for (let i = 0; i < n; i++) {\n    offsets.push(z);\n    z += thick + (i < n - 1 ? gap * Math.max(spread[i + 1], 0) : 0);\n  }\n  const total = z;\n  // The click: a short downward knock once the slabs meet again.\n  const knock =\n    interpolate(t, [CLOSE + 5, CLOSE + 7, CLOSE + 12], [0, 1, 0], {\n      ...clamp,\n      easing: Easing.out(Easing.quad),\n    }) *\n    height *\n    0.008;\n  const baseY = cy + side * 0.02 + total / 2 + knock;\n  const labelOut = interpolate(t, [CLOSE - 5, CLOSE], [1, 0], clamp);\n  const labelX = width * 0.66;\n  const fontSize = height * 0.042;\n\n  const slabs: React.ReactNode[] = [];\n  const leaders: React.ReactNode[] = [];\n  for (let i = 0; i < n; i++) {\n    const fromTop = n - 1 - i;\n    const hot = fromTop === highlight;\n    const z0 = offsets[i];\n    const z1 = z0 + thick;\n    const h = side / 2;\n    const top = [\n      iso(-h, -h, z1),\n      iso(h, -h, z1),\n      iso(h, h, z1),\n      iso(-h, h, z1),\n    ];\n    const left = [iso(-h, h, z1), iso(h, h, z1), iso(h, h, z0), iso(-h, h, z0)];\n    const right = [\n      iso(h, -h, z1),\n      iso(h, h, z1),\n      iso(h, h, z0),\n      iso(h, -h, z0),\n    ];\n    const inset = h * 0.78;\n    const inner = [\n      iso(-inset, -inset, z1),\n      iso(inset, -inset, z1),\n      iso(inset, inset, z1),\n      iso(-inset, inset, z1),\n    ];\n    const ink = hot ? \"#FFFFFF\" : \"#111111\";\n    slabs.push(\n      <g key={i}>\n        <polygon\n          points={pts(left)}\n          fill={hot ? colors.highlightLeft : colors.left}\n        />\n        <polygon\n          points={pts(right)}\n          fill={hot ? colors.highlightRight : colors.right}\n        />\n        <polygon\n          points={pts(top)}\n          fill={hot ? colors.highlightTop : colors.top}\n        />\n        <polygon\n          points={pts(inner)}\n          fill=\"none\"\n          stroke={ink}\n          strokeOpacity={0.18}\n          strokeWidth={height * 0.002}\n        />\n        {[-0.4, 0, 0.4].map((k) => {\n          const a = iso(-inset * 0.7, k * inset, z1);\n          const b = iso(inset * (fromTop % 2 ? 0.2 : 0.7), k * inset, z1);\n          return (\n            <line\n              key={k}\n              x1={a[0]}\n              y1={a[1]}\n              x2={b[0]}\n              y2={b[1]}\n              stroke={ink}\n              strokeOpacity={0.22}\n              strokeWidth={height * 0.006}\n              strokeLinecap=\"round\"\n            />\n          );\n        })}\n      </g>,\n    );\n\n    const label = labels[fromTop];\n    if (!label) continue;\n    const anchor = iso(h, -h, (z0 + z1) / 2);\n    const draw = interpolate(\n      t,\n      [14 + fromTop * 2.5, 24 + fromTop * 2.5],\n      [0, 1],\n      {\n        ...clamp,\n        easing: Easing.inOut(Easing.cubic),\n      },\n    );\n    const show =\n      interpolate(t, [19 + fromTop * 2.5, 27 + fromTop * 2.5], [0, 1], {\n        ...clamp,\n        easing: Easing.out(Easing.cubic),\n      }) * labelOut;\n    const ax = cx + anchor[0] + height * 0.012;\n    const ay = baseY + anchor[1];\n    const length = (labelX - ax) * draw * labelOut;\n    leaders.push(\n      <g key={`l${i}`}>\n        <circle\n          cx={ax}\n          cy={ay}\n          r={height * 0.006}\n          fill={colors.line}\n          opacity={Math.min(1, draw * 3) * labelOut}\n        />\n        <line\n          x1={ax}\n          y1={ay}\n          x2={ax + length}\n          y2={ay}\n          stroke={colors.line}\n          strokeOpacity={0.6}\n          strokeWidth={Math.max(1, height * 0.0022)}\n        />\n        <g\n          opacity={show}\n          transform={`translate(${labelX + height * 0.02 + (1 - show) * height * 0.03} ${ay})`}\n        >\n          <text\n            y={-fontSize * 0.62}\n            fill={hot ? colors.highlightTop : colors.label}\n            fillOpacity={hot ? 1 : 0.5}\n            fontFamily={FONT}\n            fontSize={fontSize * 0.5}\n            fontWeight={600}\n            letterSpacing=\"0.12em\"\n          >\n            {`0${fromTop + 1}`}\n          </text>\n          <text\n            y={fontSize * 0.42}\n            fill={colors.label}\n            fontFamily={FONT}\n            fontSize={fontSize}\n            fontWeight={700}\n            letterSpacing=\"-0.02em\"\n          >\n            {label}\n          </text>\n        </g>\n      </g>,\n    );\n  }\n\n  return (\n    <AbsoluteFill\n      style={{ backgroundColor: colors.background, overflow: \"hidden\" }}\n    >\n      <svg\n        width={width}\n        height={height}\n        viewBox={`0 0 ${width} ${height}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <ellipse\n          cx={cx}\n          cy={baseY + side * 0.5 + height * 0.035}\n          rx={side * COS30 * 1.05}\n          ry={side * 0.16}\n          fill=\"#000000\"\n          opacity={0.5}\n        />\n        <g transform={`translate(${cx} ${baseY})`}>{slabs}</g>\n        {leaders}\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/exploded-view.tsx"
    }
  ],
  "type": "registry:component"
}
