{
  "name": "claude-chat",
  "type": "registry:block",
  "description": "Claude composer card with typed prompt and waveform-to-send morph",
  "dependencies": [
    "remotion",
    "@remotion/google-fonts"
  ],
  "registryDependencies": [
    "ai-composer-utils"
  ],
  "files": [
    {
      "path": "registry/bases/default/scenes/claude-chat/index.tsx",
      "type": "registry:block",
      "content": "import { loadFont } from \"@remotion/google-fonts/Inter\";\nimport { AbsoluteFill, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport {\n  AI_TYPING_START,\n  Caret,\n  fadeUpAt,\n  introBounceIn,\n  morphProgressAt,\n  stageScale,\n  useTypewriter,\n} from \"@/remotion/lib/ai-composer-utils\";\n\nconst { fontFamily } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\", \"600\", \"700\"],\n  subsets: [\"latin\"],\n});\n\nexport type ClaudeChatProps = {\n  placeholder?: string;\n  prompt?: string;\n  modelName?: string;\n  modelTier?: string;\n  accentColor?: string;\n  theme?: \"light\" | \"dark\";\n  speed?: number;\n};\n\ntype Theme = {\n  page: string;\n  cardBg: string;\n  cardBorder: string;\n  fg: string;\n  fgMuted: string;\n  placeholder: string;\n  iconBtnBorder: string;\n};\n\nexport const THEMES: Record<\"light\" | \"dark\", Theme> = {\n  light: {\n    page: \"#F5F4EF\",\n    cardBg: \"#FFFFFF\",\n    cardBorder: \"#E8E5DD\",\n    fg: \"#1F1E1D\",\n    fgMuted: \"#73726C\",\n    placeholder: \"#A3A097\",\n    iconBtnBorder: \"#E0DDD4\",\n  },\n  dark: {\n    page: \"#262624\",\n    cardBg: \"#1F1E1D\",\n    cardBorder: \"#3A3936\",\n    fg: \"#F0EEE6\",\n    fgMuted: \"#9B9892\",\n    placeholder: \"#73726C\",\n    iconBtnBorder: \"#3A3936\",\n  },\n};\n\nconst TYPING_CPS = 22;\n\nfunction ChevronDown({ size, color }: { size: number; color: string }) {\n  return (\n    <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n      <title>Expand</title>\n      <path\n        d=\"M6 9l6 6 6-6\"\n        stroke={color}\n        strokeWidth={2}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction PlusIcon({ size, color }: { size: number; color: string }) {\n  return (\n    <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n      <title>Add</title>\n      <path\n        d=\"M12 5v14M5 12h14\"\n        stroke={color}\n        strokeWidth={2}\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction MicIcon({ size, color }: { size: number; color: string }) {\n  return (\n    <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n      <title>Voice input</title>\n      <rect\n        x={9}\n        y={3}\n        width={6}\n        height={11}\n        rx={3}\n        stroke={color}\n        strokeWidth={1.8}\n      />\n      <path\n        d=\"M5.5 11a6.5 6.5 0 0013 0M12 17.5V21M9 21h6\"\n        stroke={color}\n        strokeWidth={1.8}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction WaveformIcon({ size, color }: { size: number; color: string }) {\n  const bars = [\n    { x: 4, h: 8 },\n    { x: 9, h: 16 },\n    { x: 14, h: 12 },\n    { x: 19, h: 20 },\n  ];\n  return (\n    <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n      <title>Voice</title>\n      {bars.map((bar) => (\n        <rect\n          key={bar.x}\n          x={bar.x - 1}\n          y={(24 - bar.h) / 2}\n          width={2.4}\n          height={bar.h}\n          rx={1.2}\n          fill={color}\n        />\n      ))}\n    </svg>\n  );\n}\n\nfunction SendIcon({ size }: { size: number }) {\n  return (\n    <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n      <title>Send</title>\n      <path\n        d=\"M12 19V5M12 5l-6 6M12 5l6 6\"\n        stroke=\"#FFFFFF\"\n        strokeWidth={2.2}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction IconButton({\n  size,\n  border,\n  children,\n}: {\n  size: number;\n  border: string;\n  children: React.ReactNode;\n}) {\n  return (\n    <div\n      style={{\n        width: size,\n        height: size,\n        borderRadius: \"100%\",\n        border: `1px solid ${border}`,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        flexShrink: 0,\n      }}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport const ClaudeChat: React.FC<ClaudeChatProps> = ({\n  placeholder = \"Try: draft an email · summarize a doc · plan your week\",\n  prompt = \"Draft a launch tweet for our new release\",\n  modelName = \"Opus 4.8\",\n  modelTier = \"Max\",\n  accentColor = \"#D97757\",\n  theme = \"light\",\n  speed = 1,\n}) => {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const t = THEMES[theme];\n\n  const refW = 1280;\n  const scale = stageScale(width, height);\n\n  const tw = useTypewriter(prompt, {\n    cps: TYPING_CPS,\n    speed,\n    startFrame: AI_TYPING_START,\n  });\n  const showText = tw.count > 0;\n  const morph = morphProgressAt(frame, { fps, speed });\n\n  const intro = introBounceIn(frame * speed, fps);\n  const cardFade = fadeUpAt(frame * speed, [6, 22]);\n\n  const cardWidth = 860;\n  const cardLeft = (refW - cardWidth) / 2;\n  const iconBtnSize = 36;\n  const morphSize = 40;\n\n  return (\n    <AbsoluteFill style={{ background: t.page, fontFamily }}>\n      <div\n        style={{\n          position: \"absolute\",\n          left: \"50%\",\n          top: \"50%\",\n          width: refW,\n          height: 720,\n          transform: `translate(-50%, -50%) scale(${scale})`,\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            left: cardLeft,\n            top: 300,\n            width: cardWidth,\n            background: t.cardBg,\n            border: `1px solid ${t.cardBorder}`,\n            borderRadius: 24,\n            boxShadow: \"0 8px 30px -12px rgba(31,30,29,0.12)\",\n            opacity: cardFade.opacity,\n            transform: `translateY(${cardFade.translateY + intro.translateY}px) scale(${intro.scale})`,\n            transformOrigin: \"center top\",\n          }}\n        >\n          <div\n            style={{\n              padding: \"26px 28px\",\n              minHeight: 58,\n              fontSize: 21,\n              lineHeight: 1.3,\n              display: \"flex\",\n              alignItems: \"center\",\n            }}\n          >\n            {showText ? (\n              <span style={{ color: t.fg }}>\n                {tw.text}\n                <Caret\n                  color={t.fg}\n                  blink={!tw.typing}\n                  speed={speed}\n                  height={24}\n                  radius={0}\n                  marginLeft={1}\n                  style={{\n                    verticalAlign: \"text-bottom\",\n                    transform: \"translateY(3px)\",\n                  }}\n                />\n              </span>\n            ) : (\n              <span\n                style={{\n                  color: t.placeholder,\n                  display: \"inline-flex\",\n                  alignItems: \"center\",\n                }}\n              >\n                <Caret\n                  color={t.fg}\n                  blink={!tw.typing}\n                  speed={speed}\n                  height={24}\n                  radius={0}\n                  marginLeft={1}\n                  style={{\n                    verticalAlign: \"text-bottom\",\n                    transform: \"translateY(3px)\",\n                  }}\n                />\n                <span style={{ marginLeft: 2 }}>{placeholder}</span>\n              </span>\n            )}\n          </div>\n\n          <div\n            style={{\n              padding: \"14px 18px\",\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              alignItems: \"center\",\n            }}\n          >\n            <IconButton size={iconBtnSize} border={t.iconBtnBorder}>\n              <PlusIcon size={20} color={t.fg} />\n            </IconButton>\n\n            <div style={{ display: \"flex\", alignItems: \"center\", gap: 14 }}>\n              <div style={{ display: \"flex\", alignItems: \"center\", gap: 7 }}>\n                <span\n                  style={{ fontSize: 18, fontWeight: 500, color: t.fg }}\n                >\n                  {modelName}\n                </span>\n                <span\n                  style={{ fontSize: 18, fontWeight: 400, color: t.fgMuted }}\n                >\n                  {modelTier}\n                </span>\n                <ChevronDown size={16} color={t.fgMuted} />\n              </div>\n\n              <IconButton size={iconBtnSize} border={t.iconBtnBorder}>\n                <MicIcon size={20} color={t.fg} />\n              </IconButton>\n\n              <div\n                style={{\n                  position: \"relative\",\n                  width: morphSize,\n                  height: morphSize,\n                  flexShrink: 0,\n                }}\n              >\n                <div\n                  style={{\n                    position: \"absolute\",\n                    inset: 0,\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    justifyContent: \"center\",\n                    borderRadius: \"100%\",\n                    border: `1px solid ${t.iconBtnBorder}`,\n                    opacity: 1 - morph,\n                    transform: `scale(${1 - 0.1 * morph})`,\n                  }}\n                >\n                  <WaveformIcon size={22} color={t.fg} />\n                </div>\n                <div\n                  style={{\n                    position: \"absolute\",\n                    inset: 0,\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    justifyContent: \"center\",\n                    borderRadius: 10,\n                    background: accentColor,\n                    opacity: morph,\n                    transform: `scale(${0.8 + 0.2 * morph})`,\n                  }}\n                >\n                  <SendIcon size={22} />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "blocks",
    "drive": "time",
    "tier": "core",
    "tags": [
      "ai"
    ]
  }
}