{
  "name": "media-sequence",
  "type": "registry:block",
  "description": "Edited run of shots pushed on one after another, with a chapter strip that fills through the item playing",
  "dependencies": [
    "remotion",
    "@remotion/google-fonts",
    "@remotion/transitions"
  ],
  "registryDependencies": [
    "code-syntax",
    "layout",
    "media-utils",
    "motion-tokens",
    "media-frame",
    "transition-fade",
    "transition-slide"
  ],
  "files": [
    {
      "path": "registry/bases/default/scenes/media-sequence/index.tsx",
      "type": "registry:block",
      "content": "import { loadFont } from \"@remotion/google-fonts/Inter\";\nimport { TransitionSeries } from \"@remotion/transitions\";\nimport React from \"react\";\nimport {\n  AbsoluteFill,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport { CODE_THEMES } from \"@/remotion/lib/code-syntax\";\nimport { getSafeAreaPadding } from \"@/remotion/lib/layout\";\nimport { EASING } from \"@/remotion/lib/motion-tokens\";\nimport { resolveMediaWindows, type MediaItem } from \"@/remotion/lib/media-utils\";\nimport { transitionFade } from \"@/remotion/primitives/transition-fade\";\nimport { transitionSlide } from \"@/remotion/primitives/transition-slide\";\nimport { MediaFrame } from \"@/remotion/scenes/media-frame\";\n\nconst { fontFamily } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\", \"600\"],\n  subsets: [\"latin\"],\n});\n\nexport type MediaSequenceProps = {\n  items: MediaItem[];\n  /** Length of an item that does not set its own `durationInFrames`. */\n  defaultDurationInFrames?: number;\n  transitionDurationInFrames?: number;\n  /** `slide` pushes each item on from the right; `fade` dissolves. */\n  transition?: \"slide\" | \"fade\";\n  /** Chapter strip along the foot, showing which item is playing. */\n  showProgress?: boolean;\n  /** Aspect the frames are cut to. */\n  aspect?: number;\n  backgroundColor?: string;\n  accentColor?: string;\n  theme?: \"dark\" | \"light\";\n};\n\nconst clamp = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n/**\n * An edited run of shots rather than a slideshow: each item is pushed on by the\n * next, and a chapter strip along the foot fills through the item that is\n * playing and stays filled behind it — so the viewer can see where they are in\n * the sequence and how much is left.\n */\nexport const MediaSequence: React.FC<MediaSequenceProps> = ({\n  items,\n  defaultDurationInFrames = 78,\n  transitionDurationInFrames = 14,\n  transition = \"slide\",\n  showProgress = true,\n  aspect = 16 / 9,\n  backgroundColor,\n  accentColor = \"#E8B86D\",\n  theme = \"dark\",\n}) => {\n  const frame = useCurrentFrame();\n  const { width, height } = useVideoConfig();\n  const palette = CODE_THEMES[theme];\n  const safe = getSafeAreaPadding({ width, height });\n  const u = Math.min(width / 1280, height / 720);\n\n  const windows = resolveMediaWindows(items, defaultDurationInFrames);\n  const moveFade = transitionFade({\n    durationInFrames: transitionDurationInFrames,\n  });\n  const moveSlide = transitionSlide({\n    durationInFrames: transitionDurationInFrames,\n    direction: \"from-right\",\n  });\n\n  // A TransitionSeries overlaps neighbours, so an item's real start is its\n  // cumulative offset minus the transitions already consumed before it.\n  const startOf = (index: number) =>\n    windows\n      .slice(0, index)\n      .reduce((total, window) => total + window.durationInFrames, 0) -\n    index * transitionDurationInFrames;\n\n  const stripW = width - safe.paddingLeft - safe.paddingRight;\n  const segmentGap = 8 * u;\n  const segmentW =\n    (stripW - segmentGap * Math.max(windows.length - 1, 0)) /\n    Math.max(windows.length, 1);\n\n  return (\n    <AbsoluteFill\n      style={{ backgroundColor: backgroundColor ?? palette.page, fontFamily }}\n    >\n      <TransitionSeries>\n        {windows.map((item, index) => (\n          <React.Fragment key={`${item.src}-${index}`}>\n            <TransitionSeries.Sequence durationInFrames={item.durationInFrames}>\n              <MediaFrame\n                src={item.src}\n                title={item.title}\n                caption={item.caption}\n                fit={item.fit ?? \"contain\"}\n                aspect={aspect}\n                accentColor={accentColor}\n                theme={theme}\n                backgroundColor={item.backgroundColor ?? backgroundColor}\n              />\n            </TransitionSeries.Sequence>\n            {index < windows.length - 1 ? (\n              // Split rather than spreading one union: the two presentations\n              // carry different prop shapes, and a single spread would force\n              // `TransitionSeries.Transition` to pick one of them.\n              transition === \"fade\" ? (\n                <TransitionSeries.Transition {...moveFade} />\n              ) : (\n                <TransitionSeries.Transition {...moveSlide} />\n              )\n            ) : null}\n          </React.Fragment>\n        ))}\n      </TransitionSeries>\n\n      {showProgress && windows.length > 1 ? (\n        <div\n          style={{\n            position: \"absolute\",\n            left: safe.paddingLeft,\n            right: safe.paddingRight,\n            bottom: safe.paddingBottom * 0.4,\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: segmentGap,\n          }}\n        >\n          {windows.map((item, index) => {\n            const start = startOf(index);\n            const fill = interpolate(\n              frame,\n              [start, start + item.durationInFrames],\n              [0, 1],\n              { easing: EASING.editorial, ...clamp },\n            );\n            return (\n              <div\n                key={`chapter-${item.src}-${index}`}\n                style={{\n                  width: segmentW,\n                  height: 4 * u,\n                  borderRadius: 999,\n                  background: palette.band,\n                  overflow: \"hidden\",\n                }}\n              >\n                <div\n                  style={{\n                    width: `${fill * 100}%`,\n                    height: \"100%\",\n                    background: accentColor,\n                    opacity: fill > 0 && fill < 1 ? 1 : 0.55,\n                  }}\n                />\n              </div>\n            );\n          })}\n        </div>\n      ) : null}\n    </AbsoluteFill>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "blocks",
    "drive": "media",
    "tier": "advanced"
  }
}