{
  "name": "frosted-glass-wipe",
  "type": "registry:ui",
  "description": "A pane of frosted glass crosses the frame and leaves the next scene behind it",
  "dependencies": [
    "remotion",
    "@remotion/transitions"
  ],
  "registryDependencies": [
    "transition-timing"
  ],
  "files": [
    {
      "path": "registry/bases/default/primitives/frosted-glass-wipe.tsx",
      "type": "registry:ui",
      "content": "import type { TransitionPresentation } from \"@remotion/transitions\";\nimport { useMemo } from \"react\";\nimport { AbsoluteFill, interpolate } from \"remotion\";\nimport {\n  resolveTransitionTiming,\n  transitionPhase,\n  type TransitionVariant,\n} from \"@/remotion/lib/transition-timing\";\n\nexport type FrostedGlassDirection =\n  | \"from-left\"\n  | \"from-right\"\n  | \"from-top\"\n  | \"from-bottom\";\n\nexport type FrostedGlassWipeProps = {\n  blur?: number;\n  /** Panel width as a share of the frame, 0→1. */\n  panelWidth?: number;\n  direction?: FrostedGlassDirection;\n  frostColor?: string;\n};\n\nconst AXIS: Record<\n  FrostedGlassDirection,\n  { vertical: boolean; gradient: string; near: \"left\" | \"right\" | \"top\" | \"bottom\" }\n> = {\n  \"from-left\": { vertical: false, gradient: \"to right\", near: \"left\" },\n  \"from-right\": { vertical: false, gradient: \"to left\", near: \"right\" },\n  \"from-top\": { vertical: true, gradient: \"to bottom\", near: \"top\" },\n  \"from-bottom\": { vertical: true, gradient: \"to top\", near: \"bottom\" },\n};\n\nconst FrostedGlassWipePresentation: React.FC<\n  React.ComponentProps<\n    NonNullable<TransitionPresentation<FrostedGlassWipeProps>[\"component\"]>\n  >\n> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n  passedProps: {\n    blur = 20,\n    panelWidth = 0.14,\n    direction = \"from-left\",\n    frostColor = \"rgba(255,255,255,0.12)\",\n  },\n}) => {\n  const phase = transitionPhase(presentationProgress, presentationDirection, {\n    lead: 1,\n  });\n  const { vertical, gradient, near } = AXIS[direction];\n  const swept = 1 - phase.displace;\n\n  const revealStyle = useMemo(() => {\n    // The glass panel covers the frame edge it is crossing, so the scene under\n    // it can be cut hard: the panel is what hides the seam. Only the arriving\n    // scene is clipped — the outgoing one holds whole beneath it.\n    if (!phase.isEntering) {\n      return undefined;\n    }\n\n    const hidden = `${(1 - swept) * 100}%`;\n\n    return {\n      clipPath: vertical\n        ? direction === \"from-top\"\n          ? `inset(0 0 ${hidden} 0)`\n          : `inset(${hidden} 0 0 0)`\n        : direction === \"from-left\"\n          ? `inset(0 ${hidden} 0 0)`\n          : `inset(0 0 0 ${hidden})`,\n    };\n  }, [direction, phase.isEntering, swept, vertical]);\n\n  const panelStyle = useMemo(() => {\n    const widthPct = panelWidth * 100;\n    // Rides the seam, and is parked off-frame at both ends so it never sits\n    // over a scene that is not moving.\n    const offset = swept * (100 + widthPct) - widthPct;\n    const glow = interpolate(swept, [0, 0.15, 0.85, 1], [0, 1, 1, 0], {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n    });\n    const edge = `1px solid rgba(255,255,255,${(0.22 * glow).toFixed(3)})`;\n\n    return {\n      position: \"absolute\" as const,\n      ...(vertical\n        ? {\n            left: 0,\n            right: 0,\n            top: `${offset}%`,\n            height: `${widthPct}%`,\n            [near === \"top\" ? \"borderBottom\" : \"borderTop\"]: edge,\n          }\n        : {\n            top: 0,\n            bottom: 0,\n            left: `${offset}%`,\n            width: `${widthPct}%`,\n            [near === \"left\" ? \"borderRight\" : \"borderLeft\"]: edge,\n          }),\n      backdropFilter: `blur(${blur}px)`,\n      WebkitBackdropFilter: `blur(${blur}px)`,\n      background: `linear-gradient(${gradient}, transparent, ${frostColor}, transparent)`,\n      boxShadow: `0 0 ${(28 * glow).toFixed(1)}px rgba(255,255,255,${(0.18 * glow).toFixed(3)})`,\n      opacity: glow,\n      pointerEvents: \"none\" as const,\n    };\n  }, [blur, frostColor, gradient, near, panelWidth, swept, vertical]);\n\n  // The exiting scene renders underneath without a panel of its own — one pane\n  // of glass crosses the frame, not two.\n  if (!phase.isEntering) {\n    return <AbsoluteFill>{children}</AbsoluteFill>;\n  }\n\n  return (\n    <AbsoluteFill>\n      <AbsoluteFill style={revealStyle}>{children}</AbsoluteFill>\n      <div style={panelStyle} />\n    </AbsoluteFill>\n  );\n};\n\n/** Progressive frosted glass panel sweep presentation for TransitionSeries */\nexport function frostedGlassWipe(\n  props: FrostedGlassWipeProps = {},\n): TransitionPresentation<FrostedGlassWipeProps> {\n  return {\n    component: FrostedGlassWipePresentation,\n    props,\n  };\n}\n\nexport type TransitionFrostedGlassWipeConfig = {\n  durationInFrames?: number;\n  blur?: number;\n  panelWidth?: number;\n  direction?: FrostedGlassDirection;\n  frostColor?: string;\n  variant?: TransitionVariant;\n};\n\n/** Frosted glass wipe transition config for use with TransitionSeries.Transition */\nexport function transitionFrostedGlassWipe({\n  durationInFrames = 24,\n  blur = 20,\n  panelWidth = 0.14,\n  direction = \"from-left\",\n  frostColor = \"rgba(255,255,255,0.12)\",\n  variant = \"editorial\",\n}: TransitionFrostedGlassWipeConfig = {}) {\n  return {\n    presentation: frostedGlassWipe({ blur, panelWidth, direction, frostColor }),\n    timing: resolveTransitionTiming({ durationInFrames, variant }),\n  };\n}\n\nexport function getTransitionFrostedGlassWipeDuration(\n  config: TransitionFrostedGlassWipeConfig = {},\n  fps: number,\n): number {\n  return transitionFrostedGlassWipe(config).timing.getDurationInFrames({ fps });\n}\n"
    }
  ],
  "atlas": {
    "lane": "cuts",
    "drive": "time",
    "tier": "advanced"
  }
}