{
  "name": "feature-list",
  "type": "registry:block",
  "description": "List being ticked off: each row arrives in turn, its rule draws across, and its check strokes in behind it",
  "dependencies": [
    "remotion",
    "@remotion/google-fonts"
  ],
  "registryDependencies": [
    "code-syntax",
    "layout",
    "motion-tokens"
  ],
  "files": [
    {
      "path": "registry/bases/default/scenes/feature-list/index.tsx",
      "type": "registry:block",
      "content": "import { loadFont } from \"@remotion/google-fonts/Inter\";\nimport { interpolate, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { CODE_THEMES } from \"@/remotion/lib/code-syntax\";\nimport { getSafeAreaPadding } from \"@/remotion/lib/layout\";\nimport { EASING } from \"@/remotion/lib/motion-tokens\";\n\nconst { fontFamily } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\", \"600\", \"700\"],\n  subsets: [\"latin\"],\n});\n\nexport type FeatureItem = {\n  label: string;\n  /** Second line under the label. */\n  detail?: string;\n};\n\nexport type FeatureListProps = {\n  title?: string;\n  /** Small label above the title. */\n  eyebrow?: string;\n  /** Rows, as plain strings or `{ label, detail }`. */\n  items?: (string | FeatureItem)[];\n  accentColor?: string;\n  backgroundColor?: string;\n  theme?: \"dark\" | \"light\";\n  /** Animation speed multiplier. */\n  speed?: number;\n};\n\n/** Beat plan in seconds. */\nconst T = {\n  eyebrow: 0,\n  title: 0.14,\n  /** First row lands here. */\n  rows: 0.55,\n  /** Added per row. */\n  rowStagger: 0.34,\n  /** How long after a row lands its check strokes in. */\n  checkAfter: 0.22,\n} as const;\n\nconst clamp = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst CheckGlyph: React.FC<{\n  size: number;\n  color: string;\n  progress: number;\n}> = ({ size, color, progress }) => (\n  <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n    <path\n      d=\"M4.8 12.6l4.9 4.9 9.5-10.4\"\n      stroke={color}\n      strokeWidth={2.6}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeDasharray={22}\n      strokeDashoffset={22 * (1 - progress)}\n    />\n  </svg>\n);\n\n/**\n * A list being ticked off rather than a stack of bullets fading in: each row\n * arrives in turn, its rule draws across, and its check strokes in behind it —\n * so the scene lands on a list that has visibly been worked through.\n */\nexport const FeatureList: React.FC<FeatureListProps> = ({\n  title,\n  eyebrow,\n  items = [],\n  accentColor = \"#E8B86D\",\n  backgroundColor,\n  theme = \"dark\",\n  speed = 1,\n}) => {\n  const frame = useCurrentFrame();\n  const { fps, width, height } = useVideoConfig();\n  const palette = CODE_THEMES[theme];\n  const safe = getSafeAreaPadding({ width, height });\n\n  const at = (seconds: number) => (seconds * fps) / speed;\n  const ease = (from: number, to: number, easing = EASING.enter) =>\n    interpolate(frame, [at(from), at(to)], [0, 1], { easing, ...clamp });\n\n  const stage = {\n    w: width - safe.paddingLeft - safe.paddingRight,\n    h: height - safe.paddingTop - safe.paddingBottom,\n  };\n  const portrait = height > width;\n  const u = portrait\n    ? Math.min(stage.w / 620, stage.h / 1120)\n    : Math.min(stage.w / 1120, stage.h / 620);\n\n  const rows = items\n    .slice(0, 5)\n    .map((item) => (typeof item === \"string\" ? { label: item } : item));\n  const eyebrowIn = eyebrow ? ease(T.eyebrow, T.eyebrow + 0.45) : 0;\n  const titleIn = title ? ease(T.title, T.title + 0.5) : 0;\n\n  return (\n    <div\n      style={{\n        width,\n        height,\n        background: backgroundColor ?? palette.page,\n        fontFamily,\n        position: \"relative\",\n        overflow: \"hidden\",\n      }}\n    >\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          background: `radial-gradient(ellipse 70% 55% at 24% 30%, ${accentColor}16, transparent 70%)`,\n        }}\n      />\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: safe.paddingLeft,\n          top: safe.paddingTop,\n          width: stage.w,\n          height: stage.h,\n          display: \"flex\",\n          flexDirection: \"column\",\n          justifyContent: \"center\",\n          gap: 30 * u,\n        }}\n      >\n        {eyebrow || title ? (\n          <div style={{ display: \"flex\", flexDirection: \"column\", gap: 10 * u }}>\n            {eyebrow ? (\n              <div\n                style={{\n                  color: accentColor,\n                  fontSize: 20 * u,\n                  fontWeight: 600,\n                  letterSpacing: \"0.12em\",\n                  textTransform: \"uppercase\",\n                  opacity: eyebrowIn,\n                  transform: `translateY(${(1 - eyebrowIn) * 10 * u}px)`,\n                }}\n              >\n                {eyebrow}\n              </div>\n            ) : null}\n            {title ? (\n              <div style={{ overflow: \"hidden\", paddingBottom: \"0.04em\" }}>\n                <h2\n                  style={{\n                    margin: 0,\n                    color: palette.fg,\n                    fontSize: 58 * u,\n                    fontWeight: 700,\n                    lineHeight: 1.06,\n                    letterSpacing: \"-0.02em\",\n                    transform: `translateY(${(1 - titleIn) * 100}%)`,\n                  }}\n                >\n                  {title}\n                </h2>\n              </div>\n            ) : null}\n          </div>\n        ) : null}\n\n        <div style={{ display: \"flex\", flexDirection: \"column\" }}>\n          {rows.map((row, index) => {\n            const start = T.rows + index * T.rowStagger;\n            const rowIn = ease(start, start + 0.45);\n            const rule = ease(start + 0.06, start + 0.5, EASING.editorial);\n            const check = ease(\n              start + T.checkAfter,\n              start + T.checkAfter + 0.34,\n            );\n\n            return (\n              <div key={`${row.label}-${index}`}>\n                {/* The rule draws across ahead of the row settling */}\n                <div\n                  style={{\n                    height: 1,\n                    background: palette.border,\n                    transformOrigin: \"left center\",\n                    transform: `scaleX(${rule})`,\n                  }}\n                />\n                <div\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    gap: 18 * u,\n                    padding: `${17 * u}px 0`,\n                    opacity: rowIn,\n                    transform: `translateX(${(1 - rowIn) * -18 * u}px)`,\n                  }}\n                >\n                  <div\n                    style={{\n                      width: 40 * u,\n                      height: 40 * u,\n                      flexShrink: 0,\n                      borderRadius: \"50%\",\n                      display: \"grid\",\n                      placeItems: \"center\",\n                      background: `${accentColor}1A`,\n                      border: `1px solid ${\n                        check > 0.1 ? `${accentColor}88` : palette.border\n                      }`,\n                    }}\n                  >\n                    <CheckGlyph\n                      size={22 * u}\n                      color={accentColor}\n                      progress={check}\n                    />\n                  </div>\n                  <div style={{ minWidth: 0 }}>\n                    <div\n                      style={{\n                        color: palette.fg,\n                        fontSize: 32 * u,\n                        fontWeight: 600,\n                        lineHeight: 1.2,\n                        letterSpacing: \"-0.01em\",\n                      }}\n                    >\n                      {row.label}\n                    </div>\n                    {row.detail ? (\n                      <div\n                        style={{\n                          marginTop: 5 * u,\n                          color: palette.faint,\n                          fontSize: 22 * u,\n                          lineHeight: 1.35,\n                        }}\n                      >\n                        {row.detail}\n                      </div>\n                    ) : null}\n                  </div>\n                </div>\n              </div>\n            );\n          })}\n          {/* Closing rule, so the list ends on a line rather than in mid-air */}\n          <div\n            style={{\n              height: 1,\n              background: palette.border,\n              transformOrigin: \"left center\",\n              transform: `scaleX(${ease(\n                T.rows + rows.length * T.rowStagger,\n                T.rows + rows.length * T.rowStagger + 0.45,\n                EASING.editorial,\n              )})`,\n            }}\n          />\n        </div>\n      </div>\n    </div>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "blocks",
    "drive": "time",
    "tier": "core"
  }
}