{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stat-rail",
  "title": "Stat Rail",
  "description": "A mint \"4 minutes from install to first render\" stat sits beside a headline over scenery.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/gsh"],
  "files": [
    {
      "path": "registry/remocn/stat-rail/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/InstrumentSerif\";\nimport { loadFont } from \"@remotion/google-fonts/InterTight\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport {\n  AbsoluteFill,\n  Easing,\n  Img,\n  interpolate,\n  interpolateColors,\n  spring,\n  staticFile,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport type { GradientParams } from \"@/lib/remocn/gsh/gradient-engine\";\nimport { GshGround } from \"@/lib/remocn/gsh/gsh-ground\";\nimport type { GshPresetId } from \"@/lib/remocn/gsh/presets\";\n\nconst sans = loadFont(\"normal\", {\n  weights: [\"300\", \"400\"],\n  subsets: [\"latin\"],\n});\nconst serif = loadSerif(\"italic\", { weights: [\"400\"], subsets: [\"latin\"] });\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\n});\n\nconst REMOCN_R_PATH =\n  \"M 0.01 0.81 C 0.01 1.73, 0.36 2.79, 1.09 4.13 C 4.91 11.04, 13.45 17.16, 21.7 18.9 C 22.94 19.16, 23.18 19.16, 51.39 19.27 C 76.07 19.36, 80.02 19.4, 81.32 19.57 C 89.89 20.69, 96.2 24.68, 99.38 31.01 C 103.19 38.56, 102.53 50.1, 97.91 57.07 C 94.66 61.96, 89.68 64.99, 83.26 66 C 82.81 66.07, 70.18 66.83, 55.2 67.69 C 24.82 69.43, 27.03 69.23, 24.18 70.4 C 19.9 72.15, 14.84 75.7, 10.65 79.89 C 4.86 85.68, 1.3 91.91, 0.25 98.13 C 0.12 98.85, 0.08 103.13, 0.04 116.66 L 0 134.26 9.5 134.26 L 19 134.26 19.05 119.41 C 19.1 103.27, 19.08 103.82, 19.82 101.04 C 21.79 93.65, 27.86 88.75, 36.45 87.63 C 37.23 87.53, 39.41 87.5, 43.57 87.53 C 50.12 87.59, 50.75 87.65, 53.22 88.45 C 56.61 89.56, 59.67 91.86, 62.02 95.07 C 62.52 95.76, 69.35 103.85, 77.2 113.07 C 85.04 122.28, 91.63 130.04, 91.85 130.32 C 92.07 130.59, 92.5 131.34, 92.82 131.97 C 93.52 133.35, 94.11 133.93, 95.06 134.13 C 95.5 134.23, 98.97 134.26, 106.36 134.23 L 117.01 134.19 100.82 113.07 C 91.91 101.45, 84.52 91.78, 84.39 91.57 C 83.36 89.89, 83.66 87.53, 85.09 86 C 85.79 85.25, 86.36 84.94, 88.07 84.38 C 96.18 81.72, 104.15 76.62, 109.97 70.36 C 120.59 58.93, 124.06 44.32, 119.44 30.43 C 114.59 15.81, 101.93 4.02, 87.64 0.83 C 83.93 0.01, 88.09 0.08, 41.85 0.04 L 0.01 0 0.01 0.81 Z\";\n\nfunction RemocnLogo({ fontSize, color }: { fontSize: number; color: string }) {\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"baseline\",\n        color,\n        fontFamily: manrope.fontFamily,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\n      }}\n    >\n      <svg\n        viewBox=\"0 0 124.06 134.26\"\n        style={{ height: \"0.76em\", width: \"auto\" }}\n        aria-hidden\n      >\n        <path fill=\"currentColor\" d={REMOCN_R_PATH} />\n      </svg>\n      <span style={{ marginLeft: \"0.04em\" }}>emocn</span>\n    </div>\n  );\n}\n\nexport interface Stat {\n  value: string;\n  label: string;\n}\n\nexport interface StatRailProps {\n  stats?: Stat[];\n  image?: string;\n  tagline?: string;\n  theme?: \"dark\" | \"light\";\n  background?: string;\n  accent?: string;\n  card?: string;\n  faded?: string;\n  ground?: GshPresetId;\n  groundParams?: Partial<GradientParams>;\n}\n\nconst THEMES = {\n  dark: {\n    background: \"#030806\",\n    accent: \"#7cf2b9\",\n    card: \"#0d1b16\",\n    faded: \"rgba(214,240,226,0.11)\",\n    fadedLabel: \"rgba(214,240,226,0.2)\",\n    label: \"rgba(236,248,241,0.84)\",\n    payInk: \"#03130c\",\n    shadow: \"0,0,0\",\n    shadowAlpha: 0.5,\n    hairline: \"rgba(170,255,210,0.13)\",\n    highlight: \"rgba(255,255,255,0.07)\",\n    groundParams: {\n      kind: \"mist\",\n      background: \"#030806\",\n      colors: [\"#b8f5d6\", \"#3fb58f\", \"#0e4a3c\", \"#06140f\"],\n      seed: 5,\n    } as Partial<GradientParams>,\n    groundOffset: 3,\n  },\n  light: {\n    background: \"#f3efe8\",\n    accent: \"#5fe3a4\",\n    card: \"#0b1a14\",\n    faded: \"rgba(30,44,36,0.12)\",\n    fadedLabel: \"rgba(30,44,36,0.24)\",\n    label: \"rgba(240,250,244,0.86)\",\n    payInk: \"#03130c\",\n    shadow: \"60,40,30\",\n    shadowAlpha: 0.2,\n    hairline: \"rgba(255,255,255,0.08)\",\n    highlight: \"rgba(255,255,255,0.08)\",\n    groundParams: {\n      kind: \"mist\",\n      background: \"#f3efe8\",\n      colors: [\"#f6c9b4\", \"#ecb3cf\", \"#cfe8d6\", \"#f9e6c8\"],\n      seed: 5,\n      blur: 0.7,\n    } as Partial<GradientParams>,\n    groundOffset: 3,\n  },\n};\n\nconst DEFAULT_STATS: Stat[] = [\n  { value: \"120\", label: \"components ready to install\" },\n  { value: \"38\", label: \"motion design primitives\" },\n  { value: \"12\", label: \"launch templates, ready as-is\" },\n  { value: \"4\", label: \"minutes from install to first render\" },\n];\n\nconst BEATS = [\n  { name: \"tagline\", at: 0 },\n  { name: \"shrink\", at: 30 },\n  { name: \"railIn\", at: 36 },\n  { name: \"step0\", at: 42 },\n  { name: \"step1\", at: 66 },\n  { name: \"step2\", at: 84 },\n  { name: \"step3\", at: 96 },\n  { name: \"end\", at: 144 },\n] as const;\n\ntype BeatName = (typeof BEATS)[number][\"name\"];\nconst beat = (n: BeatName) => BEATS.find((b) => b.name === n)?.at ?? 0;\nconst STEPS: BeatName[] = [\"step0\", \"step1\", \"step2\", \"step3\"];\n\nconst HEAVY = { mass: 1, damping: 26, stiffness: 120 };\nconst CARD = { mass: 0.8, damping: 18, stiffness: 160 };\nconst CHIP = { mass: 0.5, damping: 12, stiffness: 200 };\nconst TYPE_IN = Easing.bezier(0.32, 0.72, 0, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nfunction Tagline({\n  text,\n  frame,\n  start,\n}: {\n  text: string;\n  frame: number;\n  start: number;\n}) {\n  const parts = text.split(\" \");\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        flexWrap: \"wrap\",\n        justifyContent: \"center\",\n        columnGap: \"0.24em\",\n      }}\n    >\n      {parts.map((raw, i) => {\n        const isAccent = raw.startsWith(\"*\") && raw.endsWith(\"*\");\n        const word = isAccent ? raw.slice(1, -1) : raw;\n        const p = interpolate(\n          frame,\n          [start + i * 2, start + i * 2 + 13],\n          [0, 1],\n          { ...CLAMP, easing: TYPE_IN },\n        );\n        return (\n          <span\n            key={`${raw}-${i}`}\n            style={{\n              display: \"inline-block\",\n              overflow: \"hidden\",\n              padding: \"0.05em 0.04em 0.16em\",\n              margin: \"-0.05em -0.04em -0.16em\",\n            }}\n          >\n            <span\n              style={{\n                display: \"inline-block\",\n                transform: `translateY(${(1 - p) * 105}%)`,\n                fontFamily: isAccent ? serif.fontFamily : undefined,\n                fontStyle: isAccent ? \"italic\" : undefined,\n                fontSize: isAccent ? \"1.12em\" : undefined,\n                lineHeight: isAccent ? 0.9 : undefined,\n                letterSpacing: isAccent ? \"-0.01em\" : undefined,\n              }}\n            >\n              {word}\n            </span>\n          </span>\n        );\n      })}\n    </div>\n  );\n}\n\nexport function StatRail({\n  stats = DEFAULT_STATS,\n  image,\n  tagline = \"The *future* of launch video is code\",\n  theme = \"dark\",\n  background,\n  accent,\n  card,\n  faded,\n  ground,\n  groundParams,\n}: StatRailProps) {\n  const frame = useCurrentFrame();\n  const { width: W, height: H, fps } = useVideoConfig();\n  const t = THEMES[theme];\n  const acc = accent ?? t.accent;\n  const cardBg = card ?? t.card;\n  const fadedInk = faded ?? t.faded;\n  const src = image ?? staticFile(\"reel/reel-2.jpg\");\n  const portrait = H > W;\n  const last = Math.max(0, stats.length - 1);\n  const sp = (at: number, config: Partial<typeof CARD>) =>\n    spring({ frame: frame - at, fps, config });\n  const shadow = (a: number) => `rgba(${t.shadow},${a * t.shadowAlpha})`;\n\n  const startRect = portrait\n    ? { x: W * 0.025, y: H * 0.014, w: W * 0.95, h: H * 0.972 }\n    : { x: W * 0.012, y: H * 0.022, w: W * 0.976, h: H * 0.956 };\n  const endRect = portrait\n    ? { x: W * 0.06, y: H * 0.035, w: W * 0.88, h: H * 0.43 }\n    : { x: W * 0.405, y: H * 0.06, w: W * 0.555, h: H * 0.88 };\n  const shrink = sp(beat(\"shrink\"), HEAVY);\n  const shrinkPrev = spring({\n    frame: frame - 1 - beat(\"shrink\"),\n    fps,\n    config: HEAVY,\n  });\n  const lerp = (a: number, b: number) => a + (b - a) * shrink;\n  const rect = {\n    x: lerp(startRect.x, endRect.x),\n    y: lerp(startRect.y, endRect.y),\n    w: lerp(startRect.w, endRect.w),\n    h: lerp(startRect.h, endRect.h),\n  };\n  const shrinkBlur = Math.min(5, Math.abs(shrink - shrinkPrev) * 50);\n  const contentScale = lerp(1, portrait ? 0.82 : 0.64);\n  const radius = portrait ? W * 0.032 : W * 0.016;\n  const imgScale = interpolate(frame, [0, beat(\"end\")], [1.14, 1.02], {\n    easing: Easing.bezier(0.65, 0, 0.35, 1),\n  });\n  const tagSize = portrait ? W * 0.1 : W * 0.062;\n  const logoSize = portrait ? W * 0.044 : W * 0.023;\n\n  const railX = portrait ? W * 0.07 : W * 0.06;\n  const railTop = portrait ? endRect.y + endRect.h + H * 0.012 : 0;\n  const railBottom = H;\n  const slotY = portrait ? railTop + (railBottom - railTop) * 0.56 : H * 0.5;\n  const pitch = portrait ? H * 0.205 : H * 0.235;\n  const numSize = portrait ? W * 0.18 : H * 0.165;\n  const labelSize = portrait ? W * 0.038 : H * 0.03;\n  const cardW = portrait ? W * 0.7 : W * 0.3;\n  const padY = portrait ? W * 0.034 : H * 0.034;\n  const padX = portrait ? W * 0.045 : H * 0.045;\n\n  const idx =\n    -1 +\n    STEPS.slice(0, last + 1).reduce((acc2, s) => acc2 + sp(beat(s), CARD), 0);\n  const pay = sp(beat(STEPS[Math.min(last, 3)] as BeatName) + 2, CHIP);\n  const payColor = interpolate(\n    frame,\n    [\n      beat(STEPS[Math.min(last, 3)] as BeatName) + 1,\n      beat(STEPS[Math.min(last, 3)] as BeatName) + 9,\n    ],\n    [0, 1],\n    { ...CLAMP, easing: TYPE_IN },\n  );\n  const push = interpolate(frame, [0, beat(\"end\")], [1, 1.03]);\n  const fadeMask = portrait\n    ? \"linear-gradient(180deg, transparent 0%, black 9%, black 90%, transparent 100%)\"\n    : \"linear-gradient(180deg, transparent 0%, black 14%, black 86%, transparent 100%)\";\n  const preset = ground;\n  const gParams =\n    groundParams ??\n    (ground\n      ? undefined\n      : { ...t.groundParams, ...(background ? { background } : {}) });\n\n  return (\n    <AbsoluteFill style={{ overflow: \"hidden\" }}>\n      <GshGround\n        preset={preset}\n        params={gParams}\n        timeOffset={t.groundOffset}\n        timeScale={0.5}\n      />\n      <AbsoluteFill style={{ transform: `scale(${push})` }}>\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            top: railTop,\n            width: portrait ? W : endRect.x,\n            height: railBottom - railTop,\n            overflow: \"hidden\",\n            maskImage: fadeMask,\n            WebkitMaskImage: fadeMask,\n          }}\n        >\n          {stats.map((s, i) => {\n            const enter = sp(beat(\"railIn\") + i * 3, CARD);\n            const y = slotY - railTop + (i - idx) * pitch;\n            const a = Math.max(0, 1 - Math.abs(idx - i));\n            const isPay = i === last;\n            const p = isPay ? payColor : 0;\n            const bg = interpolateColors(p, [0, 1], [cardBg, acc]);\n            const numColor = interpolateColors(\n              p,\n              [0, 1],\n              [interpolateColors(a, [0, 1], [fadedInk, acc]), t.payInk],\n            );\n            const labelColor = interpolateColors(\n              p,\n              [0, 1],\n              [\n                interpolateColors(a, [0, 1], [t.fadedLabel, t.label]),\n                \"rgba(3,19,12,0.78)\",\n              ],\n            );\n            const scale = 0.94 + 0.06 * a + (isPay ? (pay - 1) * 0.06 : 0);\n            return (\n              <div\n                key={`${s.value}-${i}`}\n                style={{\n                  position: \"absolute\",\n                  left: railX,\n                  top: y,\n                  transform: `translateY(-50%) translateY(${(1 - enter) * H * 0.08}px) scale(${scale})`,\n                  transformOrigin: \"0% 50%\",\n                  opacity: enter,\n                }}\n              >\n                <div\n                  style={{\n                    position: \"relative\",\n                    display: \"flex\",\n                    flexDirection: portrait ? \"column\" : \"row\",\n                    alignItems: portrait ? \"flex-start\" : \"center\",\n                    gap: portrait ? numSize * 0.08 : numSize * 0.2,\n                    width: cardW,\n                    boxSizing: \"border-box\",\n                    padding: portrait\n                      ? `${padY * 1.1}px ${padX}px ${padY * 1.3}px`\n                      : `${padY}px ${padX}px`,\n                    borderRadius: numSize * 0.2,\n                  }}\n                >\n                  <div\n                    style={{\n                      position: \"absolute\",\n                      inset: 0,\n                      borderRadius: \"inherit\",\n                      backgroundColor: bg,\n                      backgroundImage:\n                        p > 0\n                          ? `radial-gradient(90% 140% at 0% 0%, rgba(255,255,255,${0.35 * p}) 0%, transparent 60%)`\n                          : `radial-gradient(90% 140% at 0% 100%, color-mix(in oklab, ${acc} 16%, transparent) 0%, transparent 62%)`,\n                      boxShadow: `inset 0 1px 0 ${t.highlight}, inset 0 0 0 1px ${t.hairline}, 0 1px 2px ${shadow(0.6)}, 0 ${numSize * 0.12}px ${numSize * 0.3}px ${shadow(0.7)}, 0 ${numSize * 0.4}px ${numSize * 0.9}px ${shadow(0.5)}`,\n                      opacity: a,\n                      transform: `scale(${0.9 + 0.1 * a})`,\n                      transformOrigin: \"0% 50%\",\n                    }}\n                  />\n                  <div\n                    style={{\n                      position: \"relative\",\n                      fontFamily: sans.fontFamily,\n                      fontWeight: 300,\n                      fontSize: numSize,\n                      lineHeight: 0.86,\n                      letterSpacing: \"-0.055em\",\n                      color: numColor,\n                      fontVariantNumeric: \"tabular-nums\",\n                      paddingBottom: numSize * 0.04,\n                    }}\n                  >\n                    {s.value}\n                  </div>\n                  <div\n                    style={{\n                      position: \"relative\",\n                      fontFamily: sans.fontFamily,\n                      fontWeight: 400,\n                      fontSize: labelSize,\n                      lineHeight: 1.2,\n                      letterSpacing: \"-0.01em\",\n                      color: labelColor,\n                      flex: portrait ? undefined : 1,\n                      textWrap: \"balance\",\n                      maxWidth: portrait ? \"11em\" : undefined,\n                    }}\n                  >\n                    {s.label}\n                  </div>\n                </div>\n              </div>\n            );\n          })}\n        </div>\n        <div\n          style={{\n            position: \"absolute\",\n            left: rect.x,\n            top: rect.y,\n            width: rect.w,\n            height: rect.h,\n            borderRadius: radius,\n            overflow: \"hidden\",\n            boxShadow: `0 1px 2px ${shadow(0.5)}, 0 ${H * 0.02 * shrink}px ${H * 0.04 * shrink}px ${shadow(0.6)}, 0 ${H * 0.06 * shrink}px ${H * 0.12 * shrink}px ${shadow(0.6)}`,\n            filter:\n              shrinkBlur > 0.4 ? `blur(${shrinkBlur * 0.35}px)` : undefined,\n          }}\n        >\n          <Img\n            src={src}\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              width: \"100%\",\n              height: \"100%\",\n              objectFit: \"cover\",\n              transform: `scale(${imgScale})`,\n            }}\n          />\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              backgroundImage:\n                \"radial-gradient(70% 55% at 50% 46%, rgba(6,10,14,0.46) 0%, rgba(6,10,14,0) 75%), linear-gradient(180deg, rgba(6,10,14,0.18) 0%, rgba(6,10,14,0) 40%, rgba(6,10,14,0.4) 100%)\",\n            }}\n          />\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              borderRadius: radius,\n              boxShadow: \"inset 0 0 0 1px rgba(255,255,255,0.12)\",\n            }}\n          />\n          <div\n            style={{\n              position: \"absolute\",\n              left: \"50%\",\n              top: \"50%\",\n              width: startRect.w,\n              transform: `translate(-50%, -50%) scale(${contentScale})`,\n              display: \"flex\",\n              flexDirection: \"column\",\n              alignItems: \"center\",\n              gap: tagSize * 0.36,\n              color: \"#ffffff\",\n              fontFamily: sans.fontFamily,\n              fontWeight: 400,\n              fontSize: tagSize,\n              lineHeight: 1.02,\n              letterSpacing: \"-0.04em\",\n              textAlign: \"center\",\n            }}\n          >\n            <div\n              style={{\n                opacity: interpolate(frame, [0, 8], [0.2, 1], CLAMP),\n                transform: `translateY(${(1 - sp(-4, CARD)) * logoSize}px)`,\n              }}\n            >\n              <RemocnLogo fontSize={logoSize} color=\"#ffffff\" />\n            </div>\n            <div style={{ maxWidth: portrait ? \"8.6em\" : \"9.6em\" }}>\n              <Tagline text={tagline} frame={frame} start={-3} />\n            </div>\n          </div>\n        </div>\n      </AbsoluteFill>\n    </AbsoluteFill>\n  );\n}\n\nexport default StatRail;\n",
      "type": "registry:component",
      "target": "components/remocn/stat-rail.tsx"
    }
  ],
  "type": "registry:component"
}
