{
  "name": "hero-loop",
  "type": "registry:block",
  "description": "12-second seamless-looping website hero composition",
  "dependencies": [
    "remotion",
    "@remotion/google-fonts"
  ],
  "registryDependencies": [
    "counter",
    "fade-in",
    "slide-up",
    "spring-in",
    "stagger-children",
    "typewriter",
    "layout",
    "motion-tokens",
    "timing"
  ],
  "composition": {
    "id": "HeroLoop",
    "component": "HeroLoop",
    "durationInFrames": 360,
    "fps": 30,
    "width": 1920,
    "height": 1080,
    "importPath": "@/compositions/hero-loop/index"
  },
  "files": [
    {
      "path": "registry/bases/default/compositions/hero-loop/index.tsx",
      "type": "registry:block",
      "content": "import { loadFont as loadBodyFont } from \"@remotion/google-fonts/IBMPlexSans\";\nimport { loadFont as loadMonoFont } from \"@remotion/google-fonts/JetBrainsMono\";\nimport { loadFont as loadDisplayFont } from \"@remotion/google-fonts/Newsreader\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Sequence,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport { Counter } from \"@/remotion/primitives/counter\";\nimport { FadeIn } from \"@/remotion/primitives/fade-in\";\nimport { SlideUp } from \"@/remotion/primitives/slide-up\";\nimport { SpringIn } from \"@/remotion/primitives/spring-in\";\nimport { StaggerChildren } from \"@/remotion/primitives/stagger-children\";\nimport { Typewriter } from \"@/remotion/primitives/typewriter\";\nimport { getSafeAreaPadding, scaleFont } from \"@/remotion/lib/layout\";\nimport { EASING } from \"@/remotion/lib/motion-tokens\";\nimport { enterProgress } from \"@/remotion/lib/timing\";\n\n/**\n * hero-loop — the landing-page monitor composition.\n *\n * Four beats over 360 frames (12s @ 30fps):\n *   title 0-50 · install 36-150 · catalog 136-258 · render 244-330 · title 316-360\n *\n * Every beat travels the same direction — it arrives from below and leaves\n * upward — so the overlapping handoffs read as one continuous roll instead of a\n * double exposure of two centred layouts.\n *\n * The title beat is a single element driven by the global frame, so its state\n * at the last frame is its state at frame 0 and the wrap is invisible. The only\n * moving thing that crosses the seam is the tally light, whose 30-frame period\n * divides 360 exactly. Everything else lives in a `<Sequence>` so off-screen\n * beats are unmounted.\n */\n\nconst { fontFamily: displayFamily } = loadDisplayFont(\"normal\", {\n  weights: [\"500\"],\n  subsets: [\"latin\"],\n});\n\nconst { fontFamily: bodyFamily } = loadBodyFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nconst { fontFamily: monoFamily } = loadMonoFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\n/** Edit Bay palette. No blue anywhere — phosphor amber is the only accent. */\nconst COLORS = {\n  stage: \"#050505\",\n  panel: \"#0d0c0b\",\n  panelRaised: \"#141210\",\n  hairline: \"rgba(236, 236, 236, 0.09)\",\n  hairlineStrong: \"rgba(236, 236, 236, 0.16)\",\n  ink: \"#ececec\",\n  muted: \"#8b857c\",\n  dim: \"#575149\",\n  phosphor: \"#e8b86d\",\n} as const;\n\n/** Lane stripe hues from the registry atlas — stripe only, never a surface. */\nconst LANE = {\n  atoms: \"oklch(0.55 0.06 252)\",\n  signals: \"oklch(0.55 0.06 285)\",\n  vectors: \"oklch(0.55 0.06 195)\",\n  spatial: \"oklch(0.55 0.06 155)\",\n  blocks: \"oklch(0.55 0.06 48)\",\n  cuts: \"oklch(0.55 0.06 25)\",\n  reels: \"oklch(0.55 0.06 330)\",\n} as const;\n\n/**\n * Handoff shape, shared by every beat.\n *\n * Opacity and travel run on different clocks on purpose. The fade is short and\n * front-loaded so an outgoing beat is a faint ghost within three frames, while\n * the longer travel keeps both layouts physically apart for the whole handoff.\n * Fading and moving on one curve is what turns an overlap into a double\n * exposure of two centred layouts.\n */\nconst BEAT_ENTER_FADE = 14;\nconst BEAT_ENTER_MOVE = 24;\nconst BEAT_EXIT_FADE = 12;\nconst BEAT_EXIT_MOVE = 18;\n/** The title carries more travel than a panel — it is the only full-bleed beat. */\nconst TITLE_ENTER_MOVE = 26;\n/** Frames between one beat starting to leave and the next arriving. */\nconst BEAT_OVERLAP = 4;\n\n/** A beat's Sequence has to outlive its fade so the travel can finish. */\nconst beat = (from: number, exitAt: number) =>\n  ({ from, exitAt, durationInFrames: exitAt + BEAT_EXIT_MOVE }) as const;\n\n/** Each beat is chained off the previous one so the schedule cannot drift. */\nconst TITLE_EXIT = 32;\nconst INSTALL = beat(TITLE_EXIT + BEAT_OVERLAP, 96);\nconst CATALOG = beat(INSTALL.from + INSTALL.exitAt + BEAT_OVERLAP, 104);\nconst RENDER = beat(CATALOG.from + CATALOG.exitAt + BEAT_OVERLAP, 68);\nconst TITLE_ENTER = RENDER.from + RENDER.exitAt + BEAT_OVERLAP;\n/** Frame where the title switches from \"leaving\" to \"returning\". */\nconst TITLE_PIVOT = 180;\n\nconst PREMOUNT = 10;\n\nconst TITLE_LINES = [\"Compositions you own,\", \"frame by frame.\"] as const;\n\nconst TITLE_SUB = \"Install with the CLI. Every frame lands in your repo.\";\n\nconst INSTALL_COMMAND = \"npx remotion-ui@latest add social-clip\";\n\nconst INSTALL_FILES = [\n  \"src/compositions/social-clip/index.tsx\",\n  \"src/remotion/scenes/caption-scene.tsx\",\n  \"src/remotion/primitives/typewriter.tsx\",\n  \"src/remotion/lib/timing.ts\",\n] as const;\n\n/**\n * The two facts the hero states about the registry. Both are written by\n * `pnpm registry:build` from registry.json + the atlas, so a rename or a new\n * component can never leave a stale number or a dead name on the landing page.\n * Edit the curated card order in scripts/build-registry.mts, not here.\n */\n// #region generated:registry-facts\nconst REGISTRY_COUNT = 109;\n\nconst CATALOG_ITEMS = [\n  { name: \"social-clip\", kind: \"composition\", stripe: LANE.reels },\n  { name: \"caption-scene\", kind: \"scene\", stripe: LANE.signals },\n  { name: \"typewriter\", kind: \"primitive\", stripe: LANE.atoms },\n  { name: \"audiogram-bars\", kind: \"primitive\", stripe: LANE.signals },\n  { name: \"lower-third\", kind: \"scene\", stripe: LANE.blocks },\n  { name: \"path-draw\", kind: \"primitive\", stripe: LANE.vectors },\n  { name: \"metric-ticker\", kind: \"scene\", stripe: LANE.signals },\n  { name: \"transition-wipe\", kind: \"primitive\", stripe: LANE.cuts },\n  { name: \"data-story\", kind: \"composition\", stripe: LANE.reels },\n  { name: \"karaoke-captions\", kind: \"primitive\", stripe: LANE.signals },\n  { name: \"code-reveal\", kind: \"scene\", stripe: LANE.blocks },\n  { name: \"logo-reveal\", kind: \"scene\", stripe: LANE.vectors },\n] as const;\n// #endregion generated:registry-facts\n\ntype Metrics = {\n  /** Scale a 1080p-reference size against the frame's shorter edge. */\n  s: (size: number) => number;\n  safe: ReturnType<typeof getSafeAreaPadding>;\n  contentWidth: number;\n  panelWidth: number;\n  gridWidth: number;\n  gridColumns: number;\n  gridGap: number;\n  cardHeight: number;\n  titleWidth: number;\n  /** Auto-fit so the longest headline never wraps in a narrow crop. */\n  headlineSize: number;\n  mono: number;\n  tick: number;\n};\n\n/** Longest title line, in ems, at Newsreader 500 — used to auto-fit the display size. */\nconst TITLE_LINE_EMS = 10.6;\n\nfunction useMetrics(): Metrics {\n  const { width, height } = useVideoConfig();\n\n  return useMemo(() => {\n    const basis = Math.min(width, height);\n    const s = (size: number) => scaleFont(size, basis);\n    const safe = getSafeAreaPadding({ width, height });\n    const inner = s(36);\n    const contentWidth = Math.max(\n      s(320),\n      width - safe.paddingLeft - safe.paddingRight - inner * 2,\n    );\n    const gridWidth = Math.min(contentWidth, s(1500));\n    const titleWidth = Math.min(contentWidth, s(1300));\n\n    return {\n      s,\n      safe,\n      contentWidth,\n      panelWidth: Math.min(contentWidth, s(1300)),\n      gridWidth,\n      gridColumns: gridWidth >= s(1000) ? 4 : 2,\n      gridGap: s(14),\n      cardHeight: s(94),\n      titleWidth,\n      headlineSize: Math.min(s(92), Math.floor(titleWidth / TITLE_LINE_EMS)),\n      mono: s(26),\n      tick: s(30),\n    };\n  }, [width, height]);\n}\n\nexport const HeroLoop: React.FC = () => {\n  const metrics = useMetrics();\n\n  return (\n    <AbsoluteFill\n      style={{\n        backgroundColor: COLORS.stage,\n        color: COLORS.ink,\n        fontFamily: bodyFamily,\n      }}\n    >\n      <StageChrome m={metrics} />\n      <TitleBeat m={metrics} />\n\n      <Sequence\n        from={INSTALL.from}\n        durationInFrames={INSTALL.durationInFrames}\n        premountFor={PREMOUNT}\n        name=\"install\"\n      >\n        <InstallBeat m={metrics} />\n      </Sequence>\n\n      <Sequence\n        from={CATALOG.from}\n        durationInFrames={CATALOG.durationInFrames}\n        premountFor={PREMOUNT}\n        name=\"catalog\"\n      >\n        <CatalogBeat m={metrics} />\n      </Sequence>\n\n      <Sequence\n        from={RENDER.from}\n        durationInFrames={RENDER.durationInFrames}\n        premountFor={PREMOUNT}\n        name=\"render\"\n      >\n        <RenderBeat m={metrics} />\n      </Sequence>\n    </AbsoluteFill>\n  );\n};\n\n/**\n * Safe-area corner ticks plus a two-label slate. Static apart from the tally\n * light, which is the one thing alive during the title hold.\n *\n * The tally blinks on a 30-frame period and 360 divides by 30, so the phase at\n * the wrap is the phase at frame 0 — the loop stays seamless even though the\n * frames either side of the seam are no longer pixel-identical.\n */\nconst TALLY_BLINK = 30;\n\nconst StageChrome: React.FC<{ m: Metrics }> = ({ m }) => {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const { safe, tick, s } = m;\n  const labelSize = s(17);\n  const tally = interpolate(\n    (frame % TALLY_BLINK) / TALLY_BLINK,\n    [0, 0.42, 0.5, 0.92, 1],\n    [1, 1, 0.22, 0.22, 1],\n    { easing: EASING.editorial },\n  );\n\n  return (\n    <AbsoluteFill>\n      <div\n        style={{\n          position: \"absolute\",\n          left: safe.paddingLeft,\n          right: safe.paddingRight,\n          top: safe.paddingTop,\n          bottom: safe.paddingBottom,\n        }}\n      >\n        <CornerTick size={tick} corner=\"tl\" />\n        <CornerTick size={tick} corner=\"tr\" />\n        <CornerTick size={tick} corner=\"bl\" />\n        <CornerTick size={tick} corner=\"br\" />\n      </div>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: safe.paddingLeft + tick + s(16),\n          top: safe.paddingTop - labelSize,\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: s(9),\n          fontFamily: monoFamily,\n          fontSize: labelSize,\n          letterSpacing: \"0.01em\",\n          color: COLORS.muted,\n        }}\n      >\n        <span\n          style={{\n            width: s(6),\n            height: s(6),\n            backgroundColor: COLORS.phosphor,\n            opacity: tally,\n          }}\n        />\n        remotion-ui\n      </div>\n\n      <div\n        style={{\n          position: \"absolute\",\n          right: safe.paddingRight + tick + s(16),\n          bottom: safe.paddingBottom - labelSize,\n          fontFamily: monoFamily,\n          fontSize: labelSize,\n          letterSpacing: \"0.01em\",\n          color: COLORS.dim,\n        }}\n      >\n        {width} × {height} · {fps} fps\n      </div>\n    </AbsoluteFill>\n  );\n};\n\nconst CornerTick: React.FC<{\n  size: number;\n  corner: \"tl\" | \"tr\" | \"bl\" | \"br\";\n}> = ({ size, corner }) => {\n  const isTop = corner === \"tl\" || corner === \"tr\";\n  const isLeft = corner === \"tl\" || corner === \"bl\";\n  const edge = `1px solid ${COLORS.hairlineStrong}`;\n\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        width: size,\n        height: size,\n        [isTop ? \"top\" : \"bottom\"]: 0,\n        [isLeft ? \"left\" : \"right\"]: 0,\n        [isTop ? \"borderTop\" : \"borderBottom\"]: edge,\n        [isLeft ? \"borderLeft\" : \"borderRight\"]: edge,\n      }}\n    />\n  );\n};\n\n/**\n * The loop anchor. Rendered on every frame from the global clock so the state\n * at frame 359 is the state at frame 0 — the wrap has nothing to hide.\n */\nconst TitleBeat: React.FC<{ m: Metrics }> = ({ m }) => {\n  const frame = useCurrentFrame();\n  const { s, titleWidth, headlineSize } = m;\n  const returning = frame >= TITLE_PIVOT;\n  const rise = s(130);\n\n  /**\n   * One direction of travel across the whole loop: lines leave upward and\n   * return from below, so the cycle reads as a continuous roll. Both branches\n   * resolve to opacity 1 / offset 0 outside their window, which is what makes\n   * frame 359 and frame 0 the same picture.\n   */\n  const lineStyle = (index: number): CSSProperties => {\n    if (returning) {\n      const at = TITLE_ENTER + index * 4;\n      const fade = enterProgress(frame, at, BEAT_ENTER_FADE);\n      const move = enterProgress(frame, at, TITLE_ENTER_MOVE);\n      return { opacity: fade, translate: `0px ${(1 - move) * rise}px` };\n    }\n\n    const at = TITLE_EXIT + index * 3;\n    const fade = enterProgress(frame, at, BEAT_EXIT_FADE);\n    const move = enterProgress(frame, at, BEAT_EXIT_MOVE, EASING.editorial);\n    return { opacity: 1 - fade, translate: `0px ${move * -rise}px` };\n  };\n\n  return (\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      }}\n    >\n      <div style={{ width: titleWidth, textAlign: \"center\" }}>\n        {TITLE_LINES.map((line, index) => (\n          <h1\n            key={line}\n            style={{\n              margin: 0,\n              fontFamily: displayFamily,\n              fontWeight: 500,\n              fontSize: headlineSize,\n              lineHeight: 1.12,\n              letterSpacing: \"-0.01em\",\n              color: index === 0 ? COLORS.ink : COLORS.phosphor,\n              ...lineStyle(index),\n            }}\n          >\n            {line}\n          </h1>\n        ))}\n        <p\n          style={{\n            margin: `${s(28)}px 0 0`,\n            fontSize: s(28),\n            lineHeight: 1.5,\n            color: COLORS.muted,\n            ...lineStyle(2),\n          }}\n        >\n          {TITLE_SUB}\n        </p>\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n/** Frames, local to the beat, over which the panel opens for its output. */\nconst INSTALL_OPEN = [34, 48] as const;\n\nconst InstallBeat: React.FC<{ m: Metrics }> = ({ m }) => {\n  const frame = useCurrentFrame();\n  const { s, panelWidth, mono } = m;\n  const rowHeight = Math.round(mono * 1.7);\n  const rowGap = s(8);\n  const filesHeight = rowHeight * INSTALL_FILES.length + rowGap * 3;\n  /**\n   * The output block owns its full height as one eased opening, timed to the\n   * last keystroke. Letting the rows size the panel themselves would re-centre\n   * the whole card four times; reserving the height from frame zero — the\n   * earlier fix — left a terminal that was a mostly-empty box for a second and\n   * a half while the command typed. One growth, once, at the moment output\n   * would actually arrive.\n   */\n  const open = interpolate(frame, INSTALL_OPEN, [0, 1], {\n    easing: EASING.editorial,\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n\n  return (\n    <Beat m={m} exitAt={INSTALL.exitAt}>\n      <div style={{ ...panelStyle, width: panelWidth, borderRadius: s(8) }}>\n        <PanelHeader m={m} label=\"terminal\" trailing=\"add\" />\n\n        <div\n          style={{\n            display: \"grid\",\n            gap: s(26),\n            padding: `${s(30)}px ${s(34)}px ${s(32)}px`,\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"baseline\",\n              gap: s(14),\n              height: rowHeight,\n            }}\n          >\n            <span\n              style={{\n                fontFamily: monoFamily,\n                fontSize: mono,\n                color: COLORS.dim,\n              }}\n            >\n              $\n            </span>\n            <Typewriter\n              text={INSTALL_COMMAND}\n              charFrames={1}\n              delayInFrames={6}\n              showCursor\n              cursorStyle=\"block\"\n              cursorColor={COLORS.phosphor}\n              cursorBlinkFrames={TALLY_BLINK}\n              style={{\n                fontFamily: monoFamily,\n                fontSize: mono,\n                fontWeight: 400,\n                color: COLORS.ink,\n                lineHeight: `${rowHeight}px`,\n              }}\n            />\n          </div>\n\n          <div\n            style={{\n              display: \"grid\",\n              gap: rowGap,\n              height: filesHeight * open,\n              overflow: \"hidden\",\n            }}\n          >\n            <StaggerChildren staggerInFrames={6} baseDelayInFrames={42}>\n              {INSTALL_FILES.map((file) => (\n                <SlideUp key={file} durationInFrames={18} distance={s(12)}>\n                  <div\n                    style={{\n                      display: \"flex\",\n                      gap: s(16),\n                      fontFamily: monoFamily,\n                      fontSize: mono,\n                      lineHeight: `${rowHeight}px`,\n                    }}\n                  >\n                    <span style={{ color: COLORS.phosphor }}>+</span>\n                    <span style={{ color: COLORS.muted }}>{file}</span>\n                  </div>\n                </SlideUp>\n              ))}\n            </StaggerChildren>\n          </div>\n\n          <FadeIn delayInFrames={70} durationInFrames={14}>\n            <p\n              style={{\n                margin: 0,\n                fontFamily: monoFamily,\n                fontSize: s(21),\n                color: COLORS.dim,\n              }}\n            >\n              4 files written · 0 runtime dependencies\n            </p>\n          </FadeIn>\n        </div>\n      </div>\n    </Beat>\n  );\n};\n\nconst CatalogBeat: React.FC<{ m: Metrics }> = ({ m }) => {\n  const { s, gridWidth, gridColumns, gridGap, cardHeight } = m;\n  const headingSize = s(56);\n  /** Cards arrive inside Sequences, so the track has to be laid out for the\n   *  full set from frame one — otherwise the block re-centres each new row. */\n  const rows = Math.ceil(CATALOG_ITEMS.length / gridColumns);\n\n  return (\n    <Beat m={m} exitAt={CATALOG.exitAt}>\n      <div style={{ width: gridWidth }}>\n        <SlideUp durationInFrames={22} distance={s(20)}>\n          {/* MotionWrapper shrink-wraps, so the heading carries the track\n                width itself to stay centred over the grid. */}\n          <h2\n            style={{\n              width: gridWidth,\n              margin: `0 0 ${s(36)}px`,\n              display: \"flex\",\n              alignItems: \"baseline\",\n              justifyContent: \"center\",\n              gap: s(16),\n              fontFamily: displayFamily,\n              fontWeight: 500,\n              fontSize: headingSize,\n              lineHeight: 1.1,\n              color: COLORS.ink,\n            }}\n          >\n            <Counter\n              from={REGISTRY_COUNT - 18}\n              to={REGISTRY_COUNT}\n              durationInFrames={34}\n              delayInFrames={2}\n              fontSize={headingSize}\n              color={COLORS.phosphor}\n              fontFamily={displayFamily}\n              style={{ fontWeight: 500 }}\n            />\n            components, one registry.\n          </h2>\n        </SlideUp>\n\n        <div\n          style={{\n            display: \"grid\",\n            gridTemplateColumns: `repeat(${gridColumns}, minmax(0, 1fr))`,\n            gridTemplateRows: `repeat(${rows}, ${cardHeight}px)`,\n            gap: gridGap,\n            height: rows * cardHeight + (rows - 1) * gridGap,\n          }}\n        >\n          <StaggerChildren staggerInFrames={3} baseDelayInFrames={18}>\n            {CATALOG_ITEMS.map((item) => (\n              <SpringIn key={item.name} durationInFrames={22}>\n                <ClipCard m={m} {...item} />\n              </SpringIn>\n            ))}\n          </StaggerChildren>\n        </div>\n      </div>\n    </Beat>\n  );\n};\n\nconst ClipCard: React.FC<{\n  m: Metrics;\n  name: string;\n  kind: string;\n  stripe: string;\n}> = ({ m, name, kind, stripe }) => {\n  const { s } = m;\n\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"center\",\n        width: \"100%\",\n        height: m.cardHeight,\n        boxSizing: \"border-box\",\n        borderRadius: s(6),\n        border: `1px solid ${COLORS.hairline}`,\n        backgroundColor: COLORS.panel,\n        overflow: \"hidden\",\n      }}\n    >\n      <span\n        style={{\n          width: s(4),\n          alignSelf: \"stretch\",\n          flex: \"0 0 auto\",\n          backgroundColor: stripe,\n        }}\n      />\n      <div\n        style={{\n          display: \"grid\",\n          gap: s(7),\n          padding: `${s(18)}px ${s(20)}px`,\n          minWidth: 0,\n        }}\n      >\n        <span\n          style={{\n            fontSize: s(22),\n            fontWeight: 500,\n            color: COLORS.ink,\n            whiteSpace: \"nowrap\",\n            overflow: \"hidden\",\n            textOverflow: \"ellipsis\",\n          }}\n        >\n          {name}\n        </span>\n        <span\n          style={{\n            fontFamily: monoFamily,\n            fontSize: s(16),\n            color: COLORS.muted,\n          }}\n        >\n          {kind}\n        </span>\n      </div>\n    </div>\n  );\n};\n\nconst RenderBeat: React.FC<{ m: Metrics }> = ({ m }) => {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const { s, panelWidth, mono } = m;\n\n  const progress = interpolate(frame, [4, 34], [0, 1], {\n    easing: EASING.editorial,\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n  const percent = Math.round(progress * 100);\n  const rowStyle: CSSProperties = {\n    display: \"flex\",\n    alignItems: \"baseline\",\n    justifyContent: \"space-between\",\n    gap: s(24),\n    fontFamily: monoFamily,\n    fontSize: mono,\n    lineHeight: 1.6,\n  };\n\n  return (\n    <Beat m={m} exitAt={RENDER.exitAt}>\n      <div style={{ ...panelStyle, width: panelWidth, borderRadius: s(8) }}>\n        <PanelHeader\n          m={m}\n          label=\"render queue\"\n          trailing={`h264 · ${fps} fps`}\n        />\n\n        <div\n          style={{\n            display: \"grid\",\n            gap: s(22),\n            padding: `${s(30)}px ${s(34)}px ${s(32)}px`,\n          }}\n        >\n          <div style={{ ...rowStyle, color: COLORS.dim }}>\n            <span>hero-loop</span>\n            <span>done</span>\n          </div>\n\n          <div style={{ display: \"grid\", gap: s(14) }}>\n            <div style={rowStyle}>\n              <span style={{ color: COLORS.ink }}>social-clip</span>\n              <span\n                style={{\n                  color: COLORS.phosphor,\n                  fontVariantNumeric: \"tabular-nums\",\n                }}\n              >\n                {percent}%\n              </span>\n            </div>\n\n            <div\n              style={{\n                height: s(4),\n                borderRadius: s(4),\n                overflow: \"hidden\",\n                backgroundColor: COLORS.hairline,\n              }}\n            >\n              <div\n                style={{\n                  width: `${progress * 100}%`,\n                  height: \"100%\",\n                  backgroundColor: COLORS.phosphor,\n                }}\n              />\n            </div>\n          </div>\n\n          <div style={{ height: Math.round(mono * 1.6) }}>\n            <FadeIn delayInFrames={36} durationInFrames={12}>\n              <div style={{ ...rowStyle, width: panelWidth - s(68) }}>\n                <span style={{ color: COLORS.muted }}>out/social-clip.mp4</span>\n                <span style={{ color: COLORS.dim, whiteSpace: \"nowrap\" }}>\n                  {width} × {height}\n                </span>\n              </div>\n            </FadeIn>\n          </div>\n        </div>\n      </div>\n    </Beat>\n  );\n};\n\nconst PanelHeader: React.FC<{\n  m: Metrics;\n  label: string;\n  trailing: string;\n}> = ({ m, label, trailing }) => {\n  const { s } = m;\n\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"space-between\",\n        padding: `${s(15)}px ${s(24)}px`,\n        borderBottom: `1px solid ${COLORS.hairline}`,\n        backgroundColor: COLORS.panelRaised,\n        fontFamily: monoFamily,\n        fontSize: s(17),\n        color: COLORS.dim,\n      }}\n    >\n      <span>{label}</span>\n      <span>{trailing}</span>\n    </div>\n  );\n};\n\n/**\n * Beat container. Owns the handoff so no beat ever cross-dissolves on top of\n * the next one at the same coordinates — the outgoing beat is already lifting\n * away while the incoming beat is still rising into place.\n */\nconst Beat: React.FC<{\n  m: Metrics;\n  exitAt: number;\n  children: ReactNode;\n}> = ({ m, exitAt, children }) => {\n  const frame = useCurrentFrame();\n  const lift = m.s(170);\n  const enterFade = enterProgress(frame, 0, BEAT_ENTER_FADE);\n  const enterMove = enterProgress(frame, 0, BEAT_ENTER_MOVE);\n  const exitFade = enterProgress(frame, exitAt, BEAT_EXIT_FADE);\n  const exitMove = enterProgress(\n    frame,\n    exitAt,\n    BEAT_EXIT_MOVE,\n    EASING.editorial,\n  );\n\n  return (\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        paddingLeft: m.safe.paddingLeft,\n        paddingRight: m.safe.paddingRight,\n        paddingTop: m.safe.paddingTop,\n        paddingBottom: m.safe.paddingBottom,\n        boxSizing: \"border-box\",\n        opacity: enterFade * (1 - exitFade),\n        translate: `0px ${(1 - enterMove) * lift - exitMove * lift}px`,\n      }}\n    >\n      {children}\n    </AbsoluteFill>\n  );\n};\n\n/** No drop shadow: on a #050505 stage it only reads as a grey ghost box while\n *  the panel springs in from zero opacity. */\nconst panelStyle: CSSProperties = {\n  overflow: \"hidden\",\n  border: `1px solid ${COLORS.hairline}`,\n  backgroundColor: COLORS.panel,\n};\n"
    }
  ],
  "atlas": {
    "lane": "reels",
    "drive": "time",
    "tier": "core",
    "tags": [
      "hero",
      "website"
    ]
  }
}