{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "signup-flow",
  "title": "Signup Flow",
  "description": "remocn demo composition \"Signup Flow\" — installs the full Remotion composition. Generated with AI from the prompt in demos/signup-flow/prompt.md.",
  "dependencies": [
    "@remotion/google-fonts",
    "culori",
    "remotion"
  ],
  "registryDependencies": [
    "https://remocn.dev/r/blur-in.json",
    "https://remocn.dev/r/button.json",
    "https://remocn.dev/r/remocn-ui.json",
    "https://remocn.dev/r/spinner.json"
  ],
  "files": [
    {
      "path": "src/components/remocn/cursor.tsx",
      "content": "\"use client\";\n\nimport { type RemocnTheme, useRemocnTheme } from \"@/lib/remocn-ui\";\n\nexport type CursorVariant = \"arrow\" | \"pointer\";\n\nexport interface CursorStyle {\n  x: number;\n  y: number;\n  scale: number;\n  rippleOpacity: number;\n  rippleScale: number;\n  pressScale?: number;\n}\n\nexport interface CursorProps {\n  style?: CursorStyle;\n  variant?: CursorVariant;\n  size?: number;\n  theme?: Partial<RemocnTheme>;\n  mode?: \"light\" | \"dark\";\n  rippleColor?: string;\n  className?: string;\n}\n\nconst REST: CursorStyle = {\n  x: 0,\n  y: 0,\n  scale: 1,\n  rippleOpacity: 0,\n  rippleScale: 0,\n};\n\nconst ARROW_PATH =\n  \"M1 1 L1 18.5 L5.6 14.4 L8.7 21.6 L11.6 20.4 L8.5 13.2 L14.5 13.2 Z\";\nconst POINTER_PATH =\n  \"M8 2.2 C8 1.3 8.7 0.7 9.5 0.7 C10.3 0.7 11 1.3 11 2.2 L11 9 \" +\n  \"C11 9 11.3 8.4 12.2 8.4 C13 8.4 13.4 9 13.4 9.6 \" +\n  \"C13.4 9.6 13.9 9.1 14.7 9.1 C15.5 9.1 15.9 9.7 15.9 10.3 \" +\n  \"C15.9 10.3 16.4 9.9 17.1 9.9 C17.9 9.9 18.3 10.5 18.3 11.2 \" +\n  \"L18.3 16.8 C18.3 19.8 16.3 22.3 12.8 22.3 L11.4 22.3 \" +\n  \"C9 22.3 7.9 21.2 6.6 19.2 L4 15.2 C3.5 14.4 3.7 13.4 4.5 12.9 \" +\n  \"C5.1 12.5 5.9 12.6 6.4 13.2 L8 15 Z\";\n\nexport function Cursor({\n  style,\n  variant = \"arrow\",\n  size = 28,\n  theme: themeOverride,\n  mode,\n  rippleColor,\n  className,\n}: CursorProps) {\n  const theme = useRemocnTheme(themeOverride, mode);\n  const v = style ?? REST;\n\n  const press = v.pressScale ?? 1;\n  const pressedScale = v.scale * (0.9 + 0.1 * press);\n\n  const ring = rippleColor ?? theme.primary;\n  const fill = theme.foreground;\n  const stroke = theme.background;\n  const rippleSize = size * 1.8;\n\n  return (\n    <div\n      className={className}\n      style={{\n        position: \"absolute\",\n        left: 0,\n        top: 0,\n        transform: `translate(${v.x}px, ${v.y}px)`,\n        pointerEvents: \"none\",\n      }}\n    >\n      {}\n      <div\n        style={{\n          position: \"absolute\",\n          left: -rippleSize / 2,\n          top: -rippleSize / 2,\n          width: rippleSize,\n          height: rippleSize,\n          borderRadius: \"50%\",\n          border: `2px solid ${ring}`,\n          opacity: v.rippleOpacity,\n          transform: `scale(${v.rippleScale})`,\n          transformOrigin: \"center\",\n        }}\n      />\n      {}\n      <svg\n        width={size}\n        height={size}\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: 0,\n          transform: `scale(${pressedScale})`,\n          transformOrigin: \"0 0\",\n          filter: \"drop-shadow(0 1px 2px rgba(0,0,0,0.3))\",\n          overflow: \"visible\",\n        }}\n      >\n        <path\n          d={variant === \"pointer\" ? POINTER_PATH : ARROW_PATH}\n          fill={fill}\n          stroke={stroke}\n          strokeWidth={1.4}\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/cursor.tsx"
    },
    {
      "path": "src/components/remocn/field.tsx",
      "content": "\"use client\";\n\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { type RemocnTheme, useRemocnTheme } from \"@/lib/remocn-ui\";\n\nexport interface FieldGroupProps {\n  children: ReactNode;\n  gap?: number;\n  style?: CSSProperties;\n}\n\nexport function FieldGroup({ children, gap = 16, style }: FieldGroupProps) {\n  return (\n    <div style={{ display: \"flex\", flexDirection: \"column\", gap, ...style }}>\n      {children}\n    </div>\n  );\n}\n\nexport interface FieldProps {\n  children: ReactNode;\n  gap?: number;\n  style?: CSSProperties;\n}\n\nexport function Field({ children, gap = 6, style }: FieldProps) {\n  return (\n    <div style={{ display: \"flex\", flexDirection: \"column\", gap, ...style }}>\n      {children}\n    </div>\n  );\n}\n\nexport interface FieldLabelProps {\n  children: ReactNode;\n  mode?: \"light\" | \"dark\";\n  theme?: Partial<RemocnTheme>;\n  style?: CSSProperties;\n}\n\nexport function FieldLabel({ children, mode, theme, style }: FieldLabelProps) {\n  const t = useRemocnTheme(theme, mode);\n  return (\n    <div\n      style={{\n        fontSize: 13,\n        lineHeight: \"18px\",\n        fontWeight: 500,\n        letterSpacing: \"-0.01em\",\n        color: t.foreground,\n        ...style,\n      }}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport interface FieldDescriptionProps {\n  children: ReactNode;\n  align?: \"start\" | \"center\";\n  mode?: \"light\" | \"dark\";\n  theme?: Partial<RemocnTheme>;\n  style?: CSSProperties;\n}\n\nexport function FieldDescription({\n  children,\n  align = \"start\",\n  mode,\n  theme,\n  style,\n}: FieldDescriptionProps) {\n  const t = useRemocnTheme(theme, mode);\n  return (\n    <div\n      style={{\n        fontSize: 12,\n        lineHeight: \"16px\",\n        color: t.mutedForeground,\n        textAlign: align === \"center\" ? \"center\" : \"left\",\n        ...style,\n      }}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport interface FieldControlProps {\n  children: ReactNode;\n  height?: number;\n  style?: CSSProperties;\n}\n\nexport function FieldControl({ children, height = 40, style }: FieldControlProps) {\n  return (\n    <div style={{ position: \"relative\", height, ...style }}>{children}</div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/field.tsx"
    },
    {
      "path": "src/components/remocn/input.tsx",
      "content": "\"use client\";\n\nimport { mixOklch, type RemocnTheme, useRemocnTheme } from \"@/lib/remocn-ui\";\n\nexport type InputState =\n  | \"idle\"\n  | \"hover\"\n  | \"active\"\n  | \"typing\"\n  | \"blur\"\n  | \"invalid\";\n\ntype InputSize = \"sm\" | \"default\" | \"lg\";\n\nexport interface InputProps {\n  state?: InputState;\n  style?: InputStyle;\n  placeholder?: string;\n  value?: string;\n  size?: InputSize;\n  theme?: Partial<RemocnTheme>;\n  primary?: string;\n  mode?: \"light\" | \"dark\";\n  fullWidth?: boolean;\n  className?: string;\n}\n\nconst FIELD_WIDTH = 320;\n\nconst SIZE_STYLES: Record<\n  InputSize,\n  { height: number; padding: number; fontSize: number }\n> = {\n  sm: { height: 36, padding: 12, fontSize: 13 },\n  default: { height: 40, padding: 14, fontSize: 15 },\n  lg: { height: 48, padding: 16, fontSize: 17 },\n};\n\nexport interface InputStyle {\n  borderColor: string;\n  ringColor: string;\n  ringWidth: number;\n  background: string;\n  caretOpacity: number;\n  valueReveal: number;\n  placeholderOpacity: number;\n}\n\nexport interface InputStyleContext {\n  idleBorder: string;\n  hoverBorder: string;\n  activeBorder: string;\n  invalidBorder: string;\n  ring: string;\n  invalidRing: string;\n  background: string;\n  hoverBackground: string;\n  foreground: string;\n  mutedForeground: string;\n}\n\nexport function inputStyleContext(theme: RemocnTheme): InputStyleContext {\n  return {\n    idleBorder: theme.input,\n    hoverBorder: mixOklch(theme.input, theme.foreground, 0.18),\n    activeBorder: theme.ring,\n    invalidBorder: theme.destructive,\n    ring: mixOklch(theme.background, theme.ring, 0.5),\n    invalidRing: mixOklch(theme.background, theme.destructive, 0.4),\n    background: theme.background,\n    hoverBackground: mixOklch(theme.background, theme.muted, 0.4),\n    foreground: theme.foreground,\n    mutedForeground: theme.mutedForeground,\n  };\n}\n\nexport function inputStyle(\n  state: InputState,\n  ctx: InputStyleContext,\n): InputStyle {\n  switch (state) {\n    case \"hover\":\n      return {\n        borderColor: ctx.hoverBorder,\n        ringColor: ctx.ring,\n        ringWidth: 0,\n        background: ctx.hoverBackground,\n        caretOpacity: 0,\n        valueReveal: 0,\n        placeholderOpacity: 1,\n      };\n    case \"active\":\n      return {\n        borderColor: ctx.activeBorder,\n        ringColor: ctx.ring,\n        ringWidth: 3,\n        background: ctx.background,\n        caretOpacity: 1,\n        valueReveal: 0,\n        placeholderOpacity: 1,\n      };\n    case \"typing\":\n      return {\n        borderColor: ctx.activeBorder,\n        ringColor: ctx.ring,\n        ringWidth: 3,\n        background: ctx.background,\n        caretOpacity: 1,\n        valueReveal: 1,\n        placeholderOpacity: 0,\n      };\n    case \"blur\":\n      return {\n        borderColor: ctx.idleBorder,\n        ringColor: ctx.ring,\n        ringWidth: 0,\n        background: ctx.background,\n        caretOpacity: 0,\n        valueReveal: 1,\n        placeholderOpacity: 0,\n      };\n    case \"invalid\":\n      return {\n        borderColor: ctx.invalidBorder,\n        ringColor: ctx.invalidRing,\n        ringWidth: 3,\n        background: ctx.background,\n        caretOpacity: 0,\n        valueReveal: 1,\n        placeholderOpacity: 0,\n      };\n    default:\n      return {\n        borderColor: ctx.idleBorder,\n        ringColor: ctx.ring,\n        ringWidth: 0,\n        background: ctx.background,\n        caretOpacity: 0,\n        valueReveal: 0,\n        placeholderOpacity: 1,\n      };\n  }\n}\n\nexport function Input({\n  state = \"idle\",\n  style,\n  placeholder = \"you@example.com\",\n  value = \"remotion@remocn.dev\",\n  size = \"default\",\n  theme: themeOverride,\n  primary,\n  mode,\n  fullWidth = false,\n  className,\n}: InputProps) {\n  const theme = useRemocnTheme(\n    { ...themeOverride, ...(primary ? { primary } : {}) },\n    mode,\n  );\n\n  const sizeStyle = SIZE_STYLES[size];\n  const ctx = inputStyleContext(theme);\n  const v = style ?? inputStyle(state, ctx);\n  const revealed = value.slice(0, Math.round(value.length * v.valueReveal));\n\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        background: theme.background,\n        fontFamily:\n          \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\",\n      }}\n    >\n      <div\n        className={className}\n        style={{\n          position: \"relative\",\n          display: \"flex\",\n          alignItems: \"center\",\n          width: fullWidth ? \"100%\" : FIELD_WIDTH,\n          height: sizeStyle.height,\n          padding: `0 ${sizeStyle.padding}px`,\n          fontSize: sizeStyle.fontSize,\n          letterSpacing: \"-0.01em\",\n          background: v.background,\n          border: `1px solid ${v.borderColor}`,\n          borderRadius: theme.radius,\n          boxShadow: `0 0 0 ${v.ringWidth}px ${v.ringColor}`,\n        }}\n      >\n        {}\n        <span\n          style={{\n            position: \"absolute\",\n            left: sizeStyle.padding,\n            color: ctx.mutedForeground,\n            opacity: v.valueReveal > 0 ? 0 : v.placeholderOpacity,\n            pointerEvents: \"none\",\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {placeholder}\n        </span>\n        {}\n        <div style={{ display: \"flex\", alignItems: \"center\", minWidth: 0 }}>\n          <span style={{ whiteSpace: \"nowrap\", color: ctx.foreground }}>\n            {revealed}\n          </span>\n          <span\n            style={{\n              flexShrink: 0,\n              width: 2,\n              height: Math.round(sizeStyle.fontSize * 1.1),\n              borderRadius: 1,\n              background: ctx.foreground,\n              opacity: v.caretOpacity,\n              marginLeft: revealed.length > 0 ? 4 : 0,\n            }}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/input.tsx"
    },
    {
      "path": "src/components/remocn/signup-flow.tsx",
      "content": "\"use client\";\n\nimport { type RemocnTheme, useRemocnTheme } from \"@/lib/remocn-ui\";\nimport { Cursor } from \"@/components/remocn/cursor\";\nimport { useCursorPath } from \"@/components/remocn/use-cursor-path\";\nimport { Input } from \"@/components/remocn/input\";\nimport { useInputTransition } from \"@/components/remocn/use-input-transition\";\nimport { Button } from \"@/components/remocn/button\";\nimport { useButtonTransition } from \"@/components/remocn/use-button-transition\";\nimport { Toast } from \"@/components/remocn/toast\";\nimport { useToastTransition } from \"@/components/remocn/use-toast-transition\";\nimport { BlurIn } from \"@/components/remocn/blur-in\";\nimport { useBlurInTransition } from \"@/components/remocn/use-blur-in-transition\";\nimport {\n  Field,\n  FieldControl,\n  FieldDescription,\n  FieldGroup,\n  FieldLabel,\n} from \"@/components/remocn/field\";\n\nexport interface SignupFlowProps {\n  title?: string;\n  description?: string;\n  fullName?: string;\n  email?: string;\n  password?: string;\n  createLabel?: string;\n  googleLabel?: string;\n  signinText?: string;\n  toastTitle?: string;\n  mode?: \"light\" | \"dark\";\n  theme?: Partial<RemocnTheme>;\n}\n\nconst STAGE_W = 1280;\nconst CARD_W = 376;\nconst CARD_TOP = 48;\nconst CARD_LEFT = (STAGE_W - CARD_W) / 2;\nconst CENTER_X = STAGE_W / 2;\n\nconst NAME_Y = 216;\nconst EMAIL_Y = 296;\nconst PASS_Y = 398;\nconst CONFIRM_Y = 500;\nconst CREATE_Y = 564;\n\nexport function SignupFlow({\n  title = \"Create an account\",\n  description = \"Enter your information below to create your account\",\n  fullName = \"John Doe\",\n  email = \"m@example.com\",\n  password = \"••••••••\",\n  createLabel = \"Create account\",\n  toastTitle = \"Account created\",\n  mode = \"light\",\n  theme,\n}: SignupFlowProps) {\n  const resolved = useRemocnTheme(theme, mode);\n  const opts = { mode, theme };\n\n  const cardEnter = useBlurInTransition(\n    [{ at: 0, state: \"revealed\", duration: 18 }],\n    { distance: 0 },\n  );\n  const enterHeader = useBlurInTransition([\n    { at: 18, state: \"revealed\", duration: 16 },\n  ]);\n  const enterName = useBlurInTransition([\n    { at: 24, state: \"revealed\", duration: 16 },\n  ]);\n  const enterEmail = useBlurInTransition([\n    { at: 30, state: \"revealed\", duration: 16 },\n  ]);\n  const enterPass = useBlurInTransition([\n    { at: 36, state: \"revealed\", duration: 16 },\n  ]);\n  const enterConfirm = useBlurInTransition([\n    { at: 42, state: \"revealed\", duration: 16 },\n  ]);\n  const enterButton = useBlurInTransition([\n    { at: 48, state: \"revealed\", duration: 16 },\n  ]);\n\n  const DEMO = 48;\n\n  const cursorStyle = useCursorPath([\n    { at: 0, x: 160, y: 120 },\n    { at: 18 + DEMO, x: CENTER_X, y: NAME_Y, duration: 18, click: true },\n    { at: 52 + DEMO, x: CENTER_X, y: EMAIL_Y, duration: 30, click: true },\n    { at: 96 + DEMO, x: CENTER_X, y: PASS_Y, duration: 40, click: true },\n    { at: 134 + DEMO, x: CENTER_X, y: CONFIRM_Y, duration: 32, click: true },\n    { at: 176 + DEMO, x: CENTER_X, y: CREATE_Y, duration: 38, click: true },\n  ]);\n\n  const nameStyle = useInputTransition(\n    [\n      { at: 18 + DEMO, state: \"active\", duration: 6 },\n      { at: 20 + DEMO, state: \"typing\", duration: 20 },\n      { at: 52 + DEMO, state: \"blur\", duration: 8 },\n    ],\n    opts,\n  );\n  const emailStyle = useInputTransition(\n    [\n      { at: 52 + DEMO, state: \"active\", duration: 6 },\n      { at: 54 + DEMO, state: \"typing\", duration: 28 },\n      { at: 96 + DEMO, state: \"blur\", duration: 8 },\n    ],\n    opts,\n  );\n  const passStyle = useInputTransition(\n    [\n      { at: 96 + DEMO, state: \"active\", duration: 6 },\n      { at: 98 + DEMO, state: \"typing\", duration: 22 },\n      { at: 134 + DEMO, state: \"blur\", duration: 8 },\n    ],\n    opts,\n  );\n  const confirmStyle = useInputTransition(\n    [\n      { at: 134 + DEMO, state: \"active\", duration: 6 },\n      { at: 136 + DEMO, state: \"typing\", duration: 22 },\n      { at: 176 + DEMO, state: \"blur\", duration: 8 },\n    ],\n    opts,\n  );\n\n  const buttonStyle = useButtonTransition(\n    [\n      { at: 176 + DEMO, state: \"hover\", duration: 8 },\n      { at: 186 + DEMO, state: \"press\", duration: 6 },\n      { at: 192 + DEMO, state: \"loading\", duration: 6 },\n      { at: 234 + DEMO, state: \"success\", duration: 16 },\n    ],\n    opts,\n  );\n\n  const toastStyle = useToastTransition(\n    [\n      { at: 234 + DEMO, state: \"visible\", duration: 14 },\n      { at: 300 + DEMO, state: \"hidden\", duration: 14 },\n    ],\n    { mode },\n  );\n\n  return (\n    <div\n      style={{\n        position: \"relative\",\n        width: \"100%\",\n        height: \"100%\",\n        background: resolved.muted,\n        fontFamily:\n          \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\",\n      }}\n    >\n      <div\n        style={{\n          position: \"absolute\",\n          left: CARD_LEFT,\n          top: CARD_TOP,\n          width: CARD_W,\n          height: 580,\n          boxSizing: \"border-box\",\n          padding: 28,\n          display: \"flex\",\n          flexDirection: \"column\",\n          gap: 24,\n          background: resolved.background,\n          border: `1px solid ${resolved.border}`,\n          borderRadius: 14,\n          boxShadow:\n            \"0 10px 30px -12px rgba(0,0,0,0.22), 0 2px 8px -3px rgba(0,0,0,0.10)\",\n          opacity: cardEnter.opacity,\n          filter: cardEnter.blur > 0 ? `blur(${cardEnter.blur}px)` : \"none\",\n        }}\n      >\n        <BlurIn display=\"block\" style={enterHeader}>\n          <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4 }}>\n            <div\n              style={{\n                fontSize: 22,\n                lineHeight: \"28px\",\n                fontWeight: 600,\n                letterSpacing: \"-0.02em\",\n                color: resolved.cardForeground,\n              }}\n            >\n              {title}\n            </div>\n            <div\n              style={{\n                fontSize: 14,\n                lineHeight: \"20px\",\n                color: resolved.mutedForeground,\n              }}\n            >\n              {description}\n            </div>\n          </div>\n        </BlurIn>\n\n        <FieldGroup gap={16}>\n          <BlurIn display=\"block\" style={enterName}>\n            <Field>\n              <FieldLabel mode={mode} theme={theme}>\n                Full Name\n              </FieldLabel>\n              <FieldControl>\n                <Input\n                  placeholder={fullName}\n                  value={fullName}\n                  style={nameStyle}\n                  mode={mode}\n                  theme={theme}\n                />\n              </FieldControl>\n            </Field>\n          </BlurIn>\n\n          <BlurIn display=\"block\" style={enterEmail}>\n            <Field>\n              <FieldLabel mode={mode} theme={theme}>\n                Email\n              </FieldLabel>\n              <FieldControl>\n                <Input\n                  placeholder={email}\n                  value={email}\n                  style={emailStyle}\n                  mode={mode}\n                  theme={theme}\n                />\n              </FieldControl>\n              <FieldDescription mode={mode} theme={theme}>\n                We'll use this to contact you.\n              </FieldDescription>\n            </Field>\n          </BlurIn>\n\n          <BlurIn display=\"block\" style={enterPass}>\n            <Field>\n              <FieldLabel mode={mode} theme={theme}>\n                Password\n              </FieldLabel>\n              <FieldControl>\n                <Input\n                  placeholder={password}\n                  value={password}\n                  style={passStyle}\n                  mode={mode}\n                  theme={theme}\n                />\n              </FieldControl>\n              <FieldDescription mode={mode} theme={theme}>\n                Must be at least 8 characters long.\n              </FieldDescription>\n            </Field>\n          </BlurIn>\n\n          <BlurIn display=\"block\" style={enterConfirm}>\n            <Field>\n              <FieldLabel mode={mode} theme={theme}>\n                Confirm Password\n              </FieldLabel>\n              <FieldControl>\n                <Input\n                  placeholder={password}\n                  value={password}\n                  style={confirmStyle}\n                  mode={mode}\n                  theme={theme}\n                />\n              </FieldControl>\n            </Field>\n          </BlurIn>\n        </FieldGroup>\n\n        <BlurIn display=\"block\" style={enterButton}>\n          <Field gap={10}>\n            <FieldControl>\n              <Button\n                label={createLabel}\n                style={buttonStyle}\n                theme={theme}\n              />\n            </FieldControl>\n          </Field>\n        </BlurIn>\n      </div>\n\n      <div style={{ position: \"absolute\", right: 32, bottom: 32 }}>\n        <Toast\n          title={toastTitle}\n          variant=\"success\"\n          style={toastStyle}\n          mode={mode}\n          theme={theme}\n        />\n      </div>\n\n      <Cursor style={cursorStyle} variant=\"pointer\" mode={mode} theme={theme} />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/signup-flow.tsx"
    },
    {
      "path": "src/components/remocn/toast.tsx",
      "content": "\"use client\";\n\nimport { type RemocnTheme, useRemocnTheme } from \"@/lib/remocn-ui\";\n\nexport type ToastState = \"hidden\" | \"visible\";\n\nexport type ToastVariant = \"default\" | \"success\" | \"error\";\n\nexport interface ToastProps {\n  state?: ToastState;\n  style?: ToastStyle;\n  title: string;\n  description?: string;\n  variant?: ToastVariant;\n  theme?: Partial<RemocnTheme>;\n  mode?: \"light\" | \"dark\";\n  className?: string;\n}\n\nconst TOAST_WIDTH = 356;\n\nexport interface ToastStyle {\n  opacity: number;\n  translateY: number;\n  scale: number;\n}\n\nexport interface ToastStyleContext {\n  iconColor: string;\n}\n\nexport function toastStyleContext(\n  variant: ToastVariant,\n  theme: RemocnTheme,\n): ToastStyleContext {\n  if (variant === \"success\") return { iconColor: \"oklch(0.6 0.17 150)\" };\n  if (variant === \"error\") return { iconColor: theme.destructive };\n  return { iconColor: theme.mutedForeground };\n}\n\nexport function toastStyle(state: ToastState): ToastStyle {\n  switch (state) {\n    case \"visible\":\n      return { opacity: 1, translateY: 0, scale: 1 };\n    default:\n      return { opacity: 0, translateY: 16, scale: 0.97 };\n  }\n}\n\nfunction ToastIcon({\n  variant,\n  color,\n}: {\n  variant: ToastVariant;\n  color: string;\n}) {\n  if (variant === \"success\") {\n    return (\n      <svg width={18} height={18} viewBox=\"0 0 24 24\" fill=\"none\">\n        <circle cx=\"12\" cy=\"12\" r=\"9\" stroke={color} strokeWidth=\"2\" />\n        <path\n          d=\"M8 12.5l2.6 2.6L16 9\"\n          stroke={color}\n          strokeWidth=\"2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n    );\n  }\n  if (variant === \"error\") {\n    return (\n      <svg width={18} height={18} viewBox=\"0 0 24 24\" fill=\"none\">\n        <circle cx=\"12\" cy=\"12\" r=\"9\" stroke={color} strokeWidth=\"2\" />\n        <path\n          d=\"M12 7.5v5\"\n          stroke={color}\n          strokeWidth=\"2\"\n          strokeLinecap=\"round\"\n        />\n        <circle cx=\"12\" cy=\"16\" r=\"1.1\" fill={color} />\n      </svg>\n    );\n  }\n  return (\n    <svg width={18} height={18} viewBox=\"0 0 24 24\" fill=\"none\">\n      <circle cx=\"12\" cy=\"12\" r=\"9\" stroke={color} strokeWidth=\"2\" />\n      <path d=\"M12 11v5\" stroke={color} strokeWidth=\"2\" strokeLinecap=\"round\" />\n      <circle cx=\"12\" cy=\"8\" r=\"1.1\" fill={color} />\n    </svg>\n  );\n}\n\nexport function Toast({\n  state = \"hidden\",\n  style,\n  title,\n  description,\n  variant = \"default\",\n  theme: themeOverride,\n  mode,\n  className,\n}: ToastProps) {\n  const theme = useRemocnTheme(themeOverride, mode);\n  const ctx = toastStyleContext(variant, theme);\n  const v = style ?? toastStyle(state);\n\n  return (\n    <div\n      className={className}\n      style={{\n        display: \"flex\",\n\n        alignItems: description ? \"flex-start\" : \"center\",\n        gap: 12,\n        width: TOAST_WIDTH,\n        padding: \"16px\",\n        background: theme.popover,\n        color: theme.popoverForeground,\n        border: `1px solid ${theme.border}`,\n        borderRadius: theme.radius,\n        boxShadow:\n          \"0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)\",\n        opacity: v.opacity,\n        transform: `translateY(${v.translateY}px) scale(${v.scale})`,\n        transformOrigin: \"bottom center\",\n        fontFamily:\n          \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\",\n      }}\n    >\n      <span\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          marginTop: description ? 1 : 0,\n        }}\n      >\n        <ToastIcon variant={variant} color={ctx.iconColor} />\n      </span>\n      <div\n        style={{\n          display: \"flex\",\n          flexDirection: \"column\",\n          gap: 4,\n          minWidth: 0,\n        }}\n      >\n        <span\n          style={{\n            fontSize: 14,\n            fontWeight: 500,\n            lineHeight: 1.3,\n            letterSpacing: \"-0.01em\",\n          }}\n        >\n          {title}\n        </span>\n        {description !== undefined && (\n          <span\n            style={{\n              fontSize: 13,\n              lineHeight: 1.4,\n              color: theme.mutedForeground,\n            }}\n          >\n            {description}\n          </span>\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/toast.tsx"
    },
    {
      "path": "src/components/remocn/use-cursor-path.ts",
      "content": "\"use client\";\n\nimport { useCurrentFrame } from \"remotion\";\nimport {\n  clamp01,\n  type EasingName,\n  easings,\n} from \"@/lib/remocn-ui\";\nimport type { CursorStyle } from \"@/components/remocn/cursor\";\n\nexport interface CursorWaypoint {\n  at: number;\n  x: number;\n  y: number;\n  duration?: number;\n  click?: boolean;\n  press?: boolean;\n  easing?: EasingName;\n}\n\nexport const DEFAULT_DURATION = 24;\n\nexport const CLICK_FRAMES = 16;\n\nexport const PRESS_FRAMES = 8;\n\nexport interface CursorPathOptions {\n  speed?: number;\n  defaultDuration?: number;\n}\n\nfunction lerp(a: number, b: number, t: number): number {\n  return a + (b - a) * t;\n}\n\nexport function ripplePhase(framesSinceClick: number): {\n  rippleOpacity: number;\n  rippleScale: number;\n} {\n  if (framesSinceClick < 0 || framesSinceClick >= CLICK_FRAMES) {\n    return { rippleOpacity: 0, rippleScale: 0 };\n  }\n  const p = clamp01(framesSinceClick / CLICK_FRAMES);\n  return {\n    rippleOpacity: 0.5 * (1 - p),\n    rippleScale: 2.5 * easings.out(p),\n  };\n}\n\nexport function clickPress(framesSinceClick: number): number {\n  if (framesSinceClick < 0 || framesSinceClick >= PRESS_FRAMES) return 1;\n  const half = PRESS_FRAMES / 2;\n  const p =\n    framesSinceClick < half\n      ? framesSinceClick / half\n      : 1 - (framesSinceClick - half) / half;\n  return 1 - clamp01(p);\n}\n\nexport function useCursorPath(\n  waypoints: CursorWaypoint[],\n  opts: CursorPathOptions = {},\n): CursorStyle {\n  const { speed = 1 } = opts;\n  const raw = useCurrentFrame() * speed;\n  return cursorPathAt(waypoints, raw, opts);\n}\n\nexport function cursorPathAt(\n  waypoints: CursorWaypoint[],\n  raw: number,\n  opts: CursorPathOptions = {},\n): CursorStyle {\n  const { defaultDuration = DEFAULT_DURATION } = opts;\n\n  if (waypoints.length === 0) {\n    return { x: 0, y: 0, scale: 1, rippleOpacity: 0, rippleScale: 0 };\n  }\n\n  const first = waypoints[0];\n\n  if (raw <= first.at) {\n    return {\n      x: first.x,\n      y: first.y,\n      scale: 1,\n      rippleOpacity: 0,\n      rippleScale: 0,\n      pressScale: 1,\n    };\n  }\n\n  let toIndex = waypoints.length - 1;\n  for (let i = 1; i < waypoints.length; i++) {\n    if (waypoints[i].at > raw) {\n      toIndex = i;\n      break;\n    }\n  }\n  const pastLast = raw >= waypoints[waypoints.length - 1].at;\n  const to = pastLast ? waypoints[waypoints.length - 1] : waypoints[toIndex];\n  const from = pastLast\n    ? waypoints[waypoints.length - 1]\n    : waypoints[toIndex - 1];\n\n  const dur = to.duration ?? defaultDuration;\n  const ease = easings[to.easing ?? \"inOut\"];\n  const start = to.at - dur;\n  const t = pastLast || dur <= 0 ? 1 : ease(clamp01((raw - start) / dur));\n  const x = lerp(from.x, to.x, t);\n  const y = lerp(from.y, to.y, t);\n\n  let lastClickAt = -Infinity;\n  for (const wp of waypoints) {\n    if (wp.click && wp.at <= raw && wp.at > lastClickAt) lastClickAt = wp.at;\n  }\n  const sinceClick = lastClickAt === -Infinity ? -1 : raw - lastClickAt;\n  const ripple = ripplePhase(sinceClick);\n  const clickDip = clickPress(sinceClick);\n\n  const holdWp = pastLast ? waypoints[waypoints.length - 1] : from;\n  const heldPress = holdWp.press ? 0 : 1;\n\n  const pressScale = Math.min(clickDip, heldPress);\n\n  return {\n    x,\n    y,\n    scale: 1,\n    rippleOpacity: ripple.rippleOpacity,\n    rippleScale: ripple.rippleScale,\n    pressScale,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/remocn/use-cursor-path.ts"
    },
    {
      "path": "src/components/remocn/use-input-transition.ts",
      "content": "\"use client\";\n\nimport {\n  easings,\n  mixOklch,\n  type RemocnTheme,\n  type Step,\n  useRemocnTheme,\n  useStateTransition,\n} from \"@/lib/remocn-ui\";\nimport {\n  inputStyle,\n  inputStyleContext,\n  type InputState,\n  type InputStyle,\n} from \"@/components/remocn/input\";\n\nexport const DEFAULT_DURATION = 8;\n\nexport function tweenInputStyle(\n  a: InputStyle,\n  b: InputStyle,\n  t: number,\n): InputStyle {\n  return {\n    ringWidth: a.ringWidth + (b.ringWidth - a.ringWidth) * t,\n    caretOpacity: a.caretOpacity + (b.caretOpacity - a.caretOpacity) * t,\n    valueReveal: a.valueReveal + (b.valueReveal - a.valueReveal) * t,\n    placeholderOpacity:\n      a.placeholderOpacity + (b.placeholderOpacity - a.placeholderOpacity) * t,\n    borderColor: mixOklch(a.borderColor, b.borderColor, t),\n    ringColor: mixOklch(a.ringColor, b.ringColor, t),\n    background: mixOklch(a.background, b.background, t),\n  };\n}\n\nexport interface InputTransitionOptions {\n  theme?: Partial<RemocnTheme>;\n  mode?: \"light\" | \"dark\";\n  primary?: string;\n  speed?: number;\n  defaultDuration?: number;\n}\n\nexport function useInputTransition(\n  steps: Step<InputState>[],\n  opts: InputTransitionOptions = {},\n): InputStyle {\n  const {\n    theme: themeOverride,\n    mode,\n    primary,\n    speed = 1,\n    defaultDuration = DEFAULT_DURATION,\n  } = opts;\n  const theme = useRemocnTheme(\n    { ...themeOverride, ...(primary ? { primary } : {}) },\n    mode,\n  );\n  const ctx = inputStyleContext(theme);\n  const { from, to, progress } = useStateTransition(\n    steps,\n    \"idle\",\n    speed,\n    defaultDuration,\n  );\n  const t = easings.out(progress);\n  return tweenInputStyle(inputStyle(from, ctx), inputStyle(to, ctx), t);\n}\n",
      "type": "registry:component",
      "target": "components/remocn/use-input-transition.ts"
    },
    {
      "path": "src/components/remocn/use-toast-transition.ts",
      "content": "\"use client\";\n\nimport {\n  easings,\n  type Step,\n  useStateTransition,\n} from \"@/lib/remocn-ui\";\nimport {\n  toastStyle,\n  type ToastState,\n  type ToastStyle,\n} from \"@/components/remocn/toast\";\n\nexport const DEFAULT_DURATION = 12;\n\nexport function tweenToastStyle(\n  a: ToastStyle,\n  b: ToastStyle,\n  t: number,\n): ToastStyle {\n  return {\n    opacity: a.opacity + (b.opacity - a.opacity) * t,\n    translateY: a.translateY + (b.translateY - a.translateY) * t,\n    scale: a.scale + (b.scale - a.scale) * t,\n  };\n}\n\nexport interface ToastTransitionOptions {\n  mode?: \"light\" | \"dark\";\n  speed?: number;\n  defaultDuration?: number;\n}\n\nexport function useToastTransition(\n  steps: Step<ToastState>[],\n  opts: ToastTransitionOptions = {},\n): ToastStyle {\n  const { speed = 1, defaultDuration = DEFAULT_DURATION } = opts;\n  const { from, to, progress } = useStateTransition(\n    steps,\n    \"hidden\",\n    speed,\n    defaultDuration,\n  );\n  const t = easings.out(progress);\n  return tweenToastStyle(toastStyle(from), toastStyle(to), t);\n}\n",
      "type": "registry:component",
      "target": "components/remocn/use-toast-transition.ts"
    },
    {
      "path": "src/demos/signup-flow/index.tsx",
      "content": "import React from \"react\";\nimport { loadFont } from \"@remotion/google-fonts/Inter\";\nimport { RemocnUIProvider } from \"@/lib/remocn-ui\";\nimport { SignupFlow } from \"@/components/remocn/signup-flow\";\n\n// Capture Inter's font family and expose it as `--font-geist-sans`, the CSS\n// variable the remocn components reference for their `fontFamily`. Without\n// this binding the variable is undefined inside the Remotion render and the\n// components fall back to a system font.\nconst { fontFamily } = loadFont(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"700\"],\n});\n\nexport const SignupFlowDemo: React.FC = () => (\n  <RemocnUIProvider>\n    <div\n      style={\n        {\n          width: \"100%\",\n          height: \"100%\",\n          \"--font-geist-sans\": fontFamily,\n        } as React.CSSProperties\n      }\n    >\n      <SignupFlow />\n    </div>\n  </RemocnUIProvider>\n);\n",
      "type": "registry:component",
      "target": "demos/signup-flow/index.tsx"
    },
    {
      "path": "src/demos/signup-flow/prompt.md",
      "content": "<!-- TODO(draft): placeholder written by AI — replace with the real prompt used to generate this video -->\n\nPut together a short animated demo of a signup form flow, built entirely from remocn primitives — inputs, buttons, transitions between steps, that kind of thing. I just want to see a clean multi-step signup (something like email, then password, then a confirmation state) animate smoothly from one step to the next, nothing fancy around it, just the form itself as the whole video.\n",
      "type": "registry:file",
      "target": "demos/signup-flow/prompt.md"
    }
  ],
  "docs": "Register the composition in your Remotion Root:\n\n  import { SignupFlowDemo } from \"@/demos/signup-flow\";\n  <Composition id=\"signup-flow\" component={SignupFlowDemo} durationInFrames={230} fps={30} width={1280} height={720} />\n\nRequires Tailwind v4 wired into Remotion (@remotion/tailwind-v4).\nRender locally: npx remotion render signup-flow out/signup-flow.mp4",
  "type": "registry:block"
}