{
  "name": "masked-slide-reveal",
  "type": "registry:ui",
  "description": "Words slide up through a horizontal mask",
  "dependencies": [
    "remotion"
  ],
  "registryDependencies": [
    "layout",
    "timing"
  ],
  "files": [
    {
      "path": "registry/bases/default/primitives/masked-slide-reveal.tsx",
      "type": "registry:ui",
      "content": "import { interpolate, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { scaleFont } from \"@/remotion/lib/layout\";\nimport { EASING_ENTER, staggerDelay } from \"@/remotion/lib/timing\";\n\nconst MASK_LINE_HEIGHT = 1.12;\n\nexport type MaskedSlideRevealProps = {\n  /** Single string — splits on newlines for line mode, or words when one line. */\n  text?: string;\n  /** Explicit lines to reveal; preferred for multi-line headlines. */\n  lines?: string[];\n  staggerInFrames?: number;\n  durationInFrames?: number;\n  delayInFrames?: number;\n  fontSize?: number;\n  color?: string;\n  fontWeight?: number;\n  fontFamily?: string;\n  textAlign?: \"left\" | \"center\" | \"right\";\n  lineGap?: number;\n};\n\nfunction resolveLines(text: string | undefined, lines?: string[]): string[] {\n  if (lines && lines.length > 0) {\n    return lines;\n  }\n\n  const content = text?.trim() ?? \"\";\n  if (!content) {\n    return [];\n  }\n\n  if (content.includes(\"\\n\")) {\n    return content.split(\"\\n\").map((line) => line.trim()).filter(Boolean);\n  }\n\n  return content.split(/\\s+/).filter(Boolean);\n}\n\nfunction isWordMode(text: string | undefined, lines?: string[]): boolean {\n  const content = text?.trim() ?? \"\";\n  return !lines?.length && Boolean(content) && !content.includes(\"\\n\");\n}\n\nexport const MaskedSlideReveal: React.FC<MaskedSlideRevealProps> = ({\n  text,\n  lines,\n  staggerInFrames = 6,\n  durationInFrames = 16,\n  delayInFrames = 0,\n  fontSize: fontSizeProp,\n  color = \"#f4f4f5\",\n  fontWeight = 600,\n  fontFamily,\n  textAlign = \"center\",\n  lineGap = 0.18,\n}) => {\n  const frame = useCurrentFrame();\n  const { width } = useVideoConfig();\n  const fontSize = fontSizeProp ?? scaleFont(72, width);\n  const items = resolveLines(text, lines);\n  const wordMode = isWordMode(text, lines);\n\n  if (items.length === 0) {\n    return null;\n  }\n\n  const typography = {\n    fontSize,\n    fontWeight,\n    color,\n    lineHeight: MASK_LINE_HEIGHT,\n    letterSpacing: \"-0.02em\",\n    ...(fontFamily ? { fontFamily } : {}),\n  };\n\n  const renderMaskedItem = (item: string, index: number) => {\n    const start = staggerDelay(index, staggerInFrames, delayInFrames);\n    const progress = interpolate(\n      frame,\n      [start, start + durationInFrames],\n      [0, 1],\n      {\n        easing: EASING_ENTER,\n        extrapolateLeft: \"clamp\",\n        extrapolateRight: \"clamp\",\n      },\n    );\n    const y = interpolate(progress, [0, 1], [108, 0]);\n\n    return (\n      <span\n        key={`${item}-${index}`}\n        style={{\n          display: wordMode ? \"inline-block\" : \"block\",\n          overflow: \"hidden\",\n          height: `${MASK_LINE_HEIGHT}em`,\n          lineHeight: MASK_LINE_HEIGHT,\n          maxWidth: wordMode ? undefined : \"100%\",\n        }}\n      >\n        <span\n          style={{\n            display: wordMode ? \"inline-block\" : \"block\",\n            lineHeight: MASK_LINE_HEIGHT,\n            translate: `0 ${y}%`,\n            whiteSpace: wordMode ? \"nowrap\" : \"pre-wrap\",\n          }}\n        >\n          {item}\n        </span>\n      </span>\n    );\n  };\n\n  if (wordMode) {\n    return (\n      <span\n        style={{\n          ...typography,\n          display: \"inline-flex\",\n          flexWrap: \"wrap\",\n          justifyContent:\n            textAlign === \"center\"\n              ? \"center\"\n              : textAlign === \"right\"\n                ? \"flex-end\"\n                : \"flex-start\",\n          gap: \"0.28em\",\n          textAlign,\n        }}\n      >\n        {items.map(renderMaskedItem)}\n      </span>\n    );\n  }\n\n  return (\n    <span\n      style={{\n        ...typography,\n        display: \"flex\",\n        flexDirection: \"column\",\n        alignItems:\n          textAlign === \"center\"\n            ? \"center\"\n            : textAlign === \"right\"\n              ? \"flex-end\"\n              : \"flex-start\",\n        gap: `${lineGap}em`,\n        textAlign,\n        width: \"100%\",\n      }}\n    >\n      {items.map(renderMaskedItem)}\n    </span>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "atoms",
    "drive": "time",
    "tier": "advanced",
    "tags": [
      "text"
    ]
  }
}