{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "introducing",
  "title": "Introducing",
  "description": "'Introducing' settles then lifts into an eyebrow as the remocn wordmark lands with a tagline.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/introducing/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadGeist } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/InstrumentSerif\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport type React from \"react\";\nimport { Fragment, useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  interpolateColors,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"500\", \"600\"],\n  subsets: [\"latin\"],\n});\nconst serif = loadSerif(\"italic\", { weights: [\"400\"], subsets: [\"latin\"] });\nconst geist = loadGeist(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\n/**\n * Beat sheet (30fps, speed 1):\n * 0–18    \"Introducing\" settles in alone at the centre (tracking closes, blur clears).\n * 18–40   it lifts into the eyebrow slot while the name rises out of a mask,\n *         tracking wide and closing in to tight display spacing.\n * 30–74   a beam of light (gradients.sh \"beam\") passes through the name: ahead\n *         of it the letters are dim, behind it they are lit. The ground glows\n *         where the beam is.\n * 52–78   the tagline rises word by word.\n * 78–120  poster hold.\n */\n\nexport interface IntroducingProps {\n  /** Small line before the name lands. */\n  eyebrow?: string;\n  /** Product name. \"remocn\" with no `logo` renders the real remocn.dev wordmark. */\n  name?: string;\n  /** Mark set before the name, sized to the name's cap height. */\n  logo?: React.ReactNode;\n  /** One line under the lockup. */\n  tagline?: string;\n  /** Colour of the light passing through the name. */\n  accent?: string;\n  /** Stage fill. \"transparent\" drops the stage and its glow. */\n  background?: string;\n  /** Playback multiplier. */\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\n/** gradients.sh \"beam\": bg #02030a, [#f4f7ff, #8fb4ff, #3d5bff, #1a1660]. */\nconst BEAM = [\"#f4f7ff\", \"#8fb4ff\", \"#3d5bff\", \"#1a1660\"] as const;\n\nconst PALETTES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F4F3F0\",\n    dim: \"#323238\",\n    muted: \"#8E8D94\",\n    beamEdge: BEAM[1],\n    rim: \"rgba(157,180,255,0.62)\",\n    rimOuter: \"rgba(61,91,255,0.22)\",\n    haze: \"rgba(26,22,96,0.55)\",\n    halo: \"rgba(61,91,255,0.16)\",\n    column: \"rgba(143,180,255,0.16)\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#111113\",\n    dim: \"#D9D6CE\",\n    muted: \"#6E6C67\",\n    beamEdge: \"#7d8fff\",\n    rim: \"rgba(61,91,255,0.42)\",\n    rimOuter: \"rgba(143,180,255,0.22)\",\n    haze: \"rgba(143,180,255,0.16)\",\n    halo: \"rgba(61,91,255,0.07)\",\n    column: \"rgba(61,91,255,0.08)\",\n    grain: 0.04,\n  },\n} as const;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_SWEEP = Easing.bezier(0.45, 0, 0.25, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\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\n/** R mark width / height. The site sets it 0.76em tall. */\nconst R_RATIO = 124.06 / 134.26;\nconst R_W = 0.76 * R_RATIO;\nconst R_GAP = 0.04;\n\n/** Manrope advance widths (per 1000 em) for ASCII 32–126, weights 500 and 600. */\nconst MANROPE_500 = [\n  200, 329, 417, 924, 602, 901, 657, 229, 435, 435, 446, 575, 278, 420, 270,\n  402, 626, 406, 578, 562, 596, 580, 621, 512, 591, 621, 314, 318, 622, 750,\n  622, 534, 907, 646, 619, 723, 681, 575, 510, 714, 697, 249, 476, 612, 518,\n  846, 696, 732, 616, 732, 640, 630, 600, 708, 616, 949, 622, 574, 630, 412,\n  402, 412, 673, 660, 506, 562, 600, 561, 600, 590, 362, 600, 604, 249, 263,\n  510, 249, 865, 604, 600, 600, 600, 374, 530, 412, 604, 523, 779, 536, 538,\n  531, 430, 269, 430, 680,\n];\nconst MANROPE_600_EMOCN = (596 + 882 + 610 + 568 + 615) / 1000;\n/** Geist 500 advances (per 1000 em), used to estimate how the tagline wraps. */\nconst GEIST_500 = [\n  243, 230, 349, 513, 632, 853, 668, 174, 329, 329, 420, 563, 186, 432, 186,\n  457, 677, 374, 614, 600, 629, 629, 610, 507, 629, 613, 227, 227, 547, 544,\n  547, 517, 901, 678, 681, 704, 701, 610, 596, 692, 705, 281, 592, 652, 584,\n  883, 738, 739, 653, 739, 673, 642, 558, 696, 688, 956, 633, 586, 557, 342,\n  439, 342, 438, 532, 257, 556, 591, 549, 591, 566, 413, 591, 581, 256, 274,\n  565, 283, 852, 581, 562, 592, 591, 394, 535, 414, 587, 548, 806, 559, 540,\n  499, 405, 274, 406, 522,\n];\n\nfunction textEm(text: string, table: number[]) {\n  let w = 0;\n  for (const ch of text) {\n    const c = ch.charCodeAt(0);\n    w += c >= 32 && c <= 126 ? table[c - 32] : 600;\n  }\n  return w / 1000;\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity, pointerEvents: \"none\" }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={7}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\n/**\n * Horizontal fill for the lit sweep, in percent of the element it fills.\n * Lit (fg) behind the beam, the beam itself, dim ahead of it.\n */\nfunction sweepStops(\n  centre: number,\n  half: number,\n  fg: string,\n  edge: string,\n  core: string,\n  dim: string,\n) {\n  return [\n    [fg, centre - half],\n    [edge, centre - half * 0.45],\n    [core, centre],\n    [edge, centre + half * 0.35],\n    [dim, centre + half],\n  ] as const;\n}\n\nexport function Introducing({\n  eyebrow = \"Introducing\",\n  name = \"remocn\",\n  logo,\n  tagline = \"Launch-video scenes for Remotion. Copy, paste, render.\",\n  accent = \"#3B5BFD\",\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: IntroducingProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width, height } = useVideoConfig();\n  const p = PALETTES[theme];\n  const gid = `b${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n  const portrait = height > width * 1.1;\n  const s = portrait\n    ? (width / 1280) * 1.5\n    : Math.min(width / 1280, height / 720);\n\n  const wordmark = logo === undefined && name.trim().toLowerCase() === \"remocn\";\n\n  // ---- lockup metrics (em) ------------------------------------------------\n  const chars = wordmark ? 5 : [...name].length;\n  const LS_END = -0.035;\n  const logoEm = wordmark ? R_W + R_GAP : logo ? 0.84 + 0.2 : 0;\n  const textBase = wordmark ? MANROPE_600_EMOCN : textEm(name, MANROPE_500);\n  const maxW = width * (portrait ? 0.86 : 0.8);\n  const endEm = logoEm + textBase + chars * LS_END;\n  const nameFs = Math.min((portrait ? 150 : 176) * s, maxW / endEm);\n  // open tracking only as far as the frame allows\n  const lsStart = Math.max(\n    LS_END,\n    Math.min(0.12, (maxW / nameFs - logoEm - textBase) / chars),\n  );\n\n  // ---- timing --------------------------------------------------------------\n  const eyeIn = interpolate(frame, [0, 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const lift = interpolate(frame, [12, 32], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const rise = interpolate(frame, [24, 48], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const track = interpolate(frame, [24, 68], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const sweep = interpolate(frame, [34, 78], [0, 1], {\n    ...CLAMP,\n    easing: EASE_SWEEP,\n  });\n  const settle = interpolate(frame, [0, 100], [1.035, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  const ls = lsStart + (LS_END - lsStart) * track;\n  const lockupEm = logoEm + textBase + chars * ls;\n  const lockupW = lockupEm * nameFs;\n\n  // ---- vertical layout -----------------------------------------------------\n  const eyeFsEnd = 40 * s;\n  const eyeFsStart = 58 * s;\n  const tagFs = (portrait ? 34 : 30) * s;\n  const tagMax = Math.min(width * 0.78, 980 * s);\n  const tagLines = tagline\n    ? Math.max(\n        1,\n        Math.ceil((textEm(tagline, GEIST_500) * 0.97 * tagFs) / tagMax),\n      )\n    : 0;\n  const tagH = tagLines * tagFs * 1.3;\n  const nameH = nameFs * 1.0;\n  const gapTop = nameFs * 0.2;\n  const gapBottom = nameFs * 0.24;\n  const groupH = eyeFsEnd + gapTop + nameH + (tagline ? gapBottom + tagH : 0);\n  const top = height / 2 - groupH / 2;\n  const eyeYEnd = top + eyeFsEnd / 2;\n  const nameY = top + eyeFsEnd + gapTop + nameH / 2;\n  const tagTop = nameY + nameH / 2 + gapBottom;\n\n  const eyeY = interpolate(lift, [0, 1], [height / 2, eyeYEnd]);\n  const eyeFs = interpolate(lift, [0, 1], [eyeFsStart, eyeFsEnd]);\n\n  // ---- the beam ------------------------------------------------------------\n  // P = beam centre, em from the lockup's left edge.\n  const half = 0.9;\n  const P = interpolate(sweep, [0, 1], [-half * 1.2, lockupEm + half * 1.2]);\n  const beamX = width / 2 - lockupW / 2 + P * nameFs;\n  const sweeping = sweep > 0 && sweep < 1;\n  const textStart = logoEm;\n  const textEmW = Math.max(0.01, lockupEm - logoEm);\n  const toPct = (em: number) => ((em - textStart) / textEmW) * 100;\n  const fillFor = (from: number, w: number) => {\n    const stops = sweepStops(\n      ((P - from) / w) * 100,\n      (half / w) * 100,\n      p.fg,\n      p.beamEdge,\n      accent,\n      p.dim,\n    );\n    return stops;\n  };\n  const textStops = sweepStops(\n    toPct(P),\n    (half / textEmW) * 100,\n    p.fg,\n    p.beamEdge,\n    accent,\n    p.dim,\n  );\n  const textFill =\n    sweep >= 1\n      ? p.fg\n      : `linear-gradient(90deg, ${textStops.map(([c, o]) => `${c} ${o.toFixed(2)}%`).join(\", \")})`;\n  const rStops = fillFor(0, R_W);\n\n  // ---- ground --------------------------------------------------------------\n  const transparent = background === \"transparent\";\n  const glow = interpolate(frame, [26, 80], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  // a horizon of light rises into the bottom of the frame as the name lights up\n  const horizonY =\n    height * interpolate(glow, [0, 1], [1.95, 0.875]) + height * 0.78;\n  const column = sweeping ? Math.sin(sweep * Math.PI) : 0;\n\n  const words = tagline.split(/\\s+/).filter(Boolean);\n\n  return (\n    <AbsoluteFill\n      style={{\n        background: transparent ? \"transparent\" : (background ?? p.bg),\n        overflow: \"hidden\",\n      }}\n    >\n      {!transparent && background === undefined ? (\n        <>\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              opacity: glow,\n              background: [\n                `radial-gradient(${width * 1.05}px ${height * 0.78}px at 50% ${horizonY}px, transparent 0%, transparent 84%, ${p.rimOuter} 88.5%, ${p.rim} 90.5%, ${p.rimOuter} 93%, transparent 100%)`,\n                `radial-gradient(${width * 1.05}px ${height * 0.78}px at 50% ${horizonY}px, ${p.haze} 0%, transparent 92%)`,\n                `radial-gradient(${width * 0.7}px ${height * 0.5}px at 50% ${horizonY - height * 0.78}px, ${p.halo} 0%, transparent 100%)`,\n              ].join(\", \"),\n            }}\n          />\n          <div\n            style={{\n              position: \"absolute\",\n              left: beamX - nameFs * 1.6,\n              width: nameFs * 3.2,\n              top: 0,\n              bottom: 0,\n              opacity: column,\n              background: `radial-gradient(50% 30% at 50% ${(nameY / height) * 100}%, ${p.column} 0%, transparent 100%)`,\n            }}\n          />\n          <Grain opacity={p.grain} />\n        </>\n      ) : null}\n\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          transform: `scale(${settle})`,\n        }}\n      >\n        {/* eyebrow */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            right: 0,\n            top: eyeY,\n            transform: \"translateY(-50%)\",\n            display: \"flex\",\n            justifyContent: \"center\",\n          }}\n        >\n          <span\n            style={{\n              fontFamily: serif.fontFamily,\n              fontStyle: \"italic\",\n              fontWeight: 400,\n              fontSize: eyeFs,\n              lineHeight: 1,\n              color: interpolateColors(lift, [0, 1], [p.fg, p.muted]),\n              opacity: eyeIn,\n              letterSpacing: `${interpolate(eyeIn, [0, 1], [0.14, -0.005])}em`,\n              filter: `blur(${(1 - eyeIn) * 8 * s}px)`,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {eyebrow}\n          </span>\n        </div>\n\n        {/* name, rising out of a mask */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            right: 0,\n            // the mask's lower edge sits just under the baseline, so the\n            // letters climb out of a line rather than fade up\n            top: nameY - nameFs * 0.8,\n            height: nameFs * 1.44,\n            paddingTop: nameFs * 0.3,\n            overflow: \"hidden\",\n            display: \"flex\",\n            justifyContent: \"center\",\n            alignItems: \"flex-start\",\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"baseline\",\n              height: nameFs,\n              transform: `translateY(${(1 - rise) * nameFs * 1.02}px)`,\n              fontFamily: manrope.fontFamily,\n              fontWeight: wordmark ? 600 : 500,\n              fontSize: nameFs,\n              lineHeight: 1,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {wordmark ? (\n              <svg\n                viewBox=\"0 0 124.06 134.26\"\n                style={{ height: \"0.76em\", width: `${R_W}em`, flexShrink: 0 }}\n                aria-hidden\n              >\n                <defs>\n                  <linearGradient id={gid} x1=\"0\" x2=\"1\" y1=\"0\" y2=\"0\">\n                    {sweep >= 1 ? (\n                      <stop offset=\"0\" stopColor={p.fg} />\n                    ) : (\n                      rStops.map(([c, o]) => (\n                        <stop key={`${c}${o}`} offset={o / 100} stopColor={c} />\n                      ))\n                    )}\n                  </linearGradient>\n                </defs>\n                <path fill={`url(#${gid})`} d={REMOCN_R_PATH} />\n              </svg>\n            ) : logo ? (\n              <div\n                style={{\n                  height: \"0.84em\",\n                  width: \"0.84em\",\n                  marginRight: \"0.2em\",\n                  alignSelf: \"center\",\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  justifyContent: \"center\",\n                  opacity: interpolate(sweep, [0, 0.25], [0.35, 1], CLAMP),\n                }}\n              >\n                {logo}\n              </div>\n            ) : null}\n            <span\n              style={{\n                marginLeft: wordmark ? `${R_GAP}em` : 0,\n                letterSpacing: `${ls}em`,\n                marginRight: `${-ls}em`,\n                backgroundImage: textFill.startsWith(\"linear\")\n                  ? textFill\n                  : undefined,\n                WebkitBackgroundClip: \"text\",\n                backgroundClip: \"text\",\n                color: textFill.startsWith(\"linear\") ? \"transparent\" : textFill,\n                paddingBottom: \"0.08em\",\n              }}\n            >\n              {wordmark ? \"emocn\" : name}\n            </span>\n          </div>\n        </div>\n\n        {/* tagline */}\n        {tagline ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: (width - tagMax) / 2,\n              width: tagMax,\n              top: tagTop,\n              textAlign: \"center\",\n              textWrap: \"balance\",\n              fontFamily: geist.fontFamily,\n              fontWeight: 400,\n              fontSize: tagFs,\n              lineHeight: 1.3,\n              letterSpacing: \"-0.012em\",\n              color: p.muted,\n            }}\n          >\n            {words.map((w, i) => {\n              const t = interpolate(frame, [56 + i * 2, 74 + i * 2], [0, 1], {\n                ...CLAMP,\n                easing: EASE_OUT,\n              });\n              return (\n                <Fragment key={i}>\n                  {i > 0 ? \" \" : null}\n                  <span\n                    style={{\n                      display: \"inline-block\",\n                      opacity: t,\n                      transform: `translateY(${(1 - t) * 0.45}em)`,\n                      filter: `blur(${(1 - t) * 6 * s}px)`,\n                    }}\n                  >\n                    {w}\n                  </span>\n                </Fragment>\n              );\n            })}\n          </div>\n        ) : null}\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/introducing.tsx"
    }
  ],
  "type": "registry:component"
}
