{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "first-light",
  "title": "First Light",
  "description": "The remocn wordmark is swept by a band of light along a baseline, with a tagline below.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/first-light/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { loadFont as loadSpaceGrotesk } from \"@remotion/google-fonts/SpaceGrotesk\";\nimport { loadFont as loadSpaceMono } from \"@remotion/google-fonts/SpaceMono\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\n});\nconst grotesk = loadSpaceGrotesk(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nconst mono = loadSpaceMono(\"normal\", {\n  weights: [\"400\"],\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\nconst R_W = 124.06;\nconst R_H = 134.26;\nconst R_SCALE = 0.76;\nconst R_GAP = 0.04;\n\nexport interface FirstLightProps {\n  name?: string;\n  tagline?: string;\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: {\n    bg: \"#000000\",\n    ink: \"#FFFFFF\",\n    dim: \"rgba(255,255,255,0.32)\",\n  },\n  light: {\n    bg: \"#FFFFFF\",\n    ink: \"#000000\",\n    dim: \"rgba(0,0,0,0.3)\",\n  },\n} as const;\n\nconst PROBE_PX = 200;\n\nconst APPEAR_LEN = 12;\nconst CLIMB_AT = 10;\nconst CLIMB_LEN = 54;\nconst TAG_AT = 60;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_CLIMB = Easing.bezier(0.55, 0, 0.2, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\ninterface Metrics {\n  text: number;\n  cap: number;\n  desc: number;\n}\n\nfunction measure(\n  word: string,\n  family: string,\n  weight: number,\n  track: number,\n): Metrics {\n  const probe = document.createElement(\"span\");\n  Object.assign(probe.style, {\n    position: \"absolute\",\n    left: \"-99999px\",\n    top: \"0\",\n    visibility: \"hidden\",\n    whiteSpace: \"pre\",\n    fontFamily: `\"${family}\"`,\n    fontWeight: String(weight),\n    fontSize: `${PROBE_PX}px`,\n    letterSpacing: `${track}em`,\n    lineHeight: \"1\",\n  });\n  probe.textContent = word;\n  document.body.appendChild(probe);\n  const text = probe.getBoundingClientRect().width / PROBE_PX - track;\n  probe.remove();\n\n  let cap = 0.72;\n  let desc = 0.22;\n  const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n  if (ctx) {\n    ctx.font = `${weight} ${PROBE_PX}px \"${family}\"`;\n    cap = ctx.measureText(\"H\").actualBoundingBoxAscent / PROBE_PX;\n    desc = Math.max(\n      0,\n      ctx.measureText(word).actualBoundingBoxDescent / PROBE_PX,\n    );\n  }\n  return { text, cap, desc };\n}\n\nfunction useMetrics(word: string, brand: boolean) {\n  const key = `${word}\\u0001${brand ? 1 : 0}`;\n  const [state, setState] = useState<{ key: string; m: Metrics } | null>(null);\n  const [handle] = useState(() => delayRender(\"first-light: measuring\"));\n  const released = useRef(false);\n  useEffect(() => {\n    let live = true;\n    const [w, b] = key.split(\"\\u0001\");\n    const isBrand = b === \"1\";\n    const family = isBrand ? manrope.fontFamily : grotesk.fontFamily;\n    const weight = isBrand ? 600 : 400;\n    Promise.all([\n      manrope.waitUntilDone(),\n      grotesk.waitUntilDone(),\n      mono.waitUntilDone(),\n      document.fonts.load(`${weight} 100px \"${family}\"`),\n      document.fonts.load(`400 100px \"${mono.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        setState({\n          key,\n          m: measure(w, family, weight, isBrand ? -0.025 : -0.035),\n        });\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [key, handle]);\n  return state && state.key === key ? state.m : null;\n}\n\nexport function FirstLight({\n  name = \"remocn\",\n  tagline = \"Every frame catches the light.\",\n  accent,\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: FirstLightProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const lineInk = accent ?? c.ink;\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const isBrand = name.trim().toLowerCase() === \"remocn\";\n  const word = isBrand ? \"emocn\" : name;\n  const family = isBrand ? manrope.fontFamily : grotesk.fontFamily;\n  const weight = isBrand ? 600 : 400;\n  const track = isBrand ? -0.025 : -0.035;\n  const m = useMetrics(word, isBrand);\n\n  const transparent = background === \"transparent\";\n  const stage = transparent ? \"transparent\" : (background ?? c.bg);\n\n  if (!m) {\n    return (\n      <div style={{ position: \"absolute\", inset: 0, background: stage }} />\n    );\n  }\n\n  const portrait = H > W * 1.1;\n  const minDim = Math.min(W, H);\n  const markEm = isBrand ? (R_SCALE * R_W) / R_H + R_GAP : 0;\n  const nameEm = markEm + m.text;\n  const marginX = W * (portrait ? 0.05 : 0.045);\n  const F = Math.min(\n    (W - marginX * 2) / nameEm,\n    (H * (portrait ? 0.2 : 0.4)) / m.cap,\n  );\n  const nameW = nameEm * F;\n  const capH = m.cap * F;\n  const descH = m.desc * F;\n  const tagFs = Math.max(minDim * 0.024, Math.min(minDim * 0.032, 24));\n  const stroke = Math.max(1, minDim * 0.0018);\n\n  const baseFinal = H / 2 + capH / 2 - H * 0.02;\n  const sunLine0 = baseFinal + descH + F * 0.06;\n  const baseStart = sunLine0 + F * 0.06 + capH + H * (portrait ? 0.1 : 0.03);\n  const climb = baseStart - baseFinal;\n\n  const appear = interpolate(t, [0, APPEAR_LEN], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const k = interpolate(t, [CLIMB_AT, CLIMB_AT + CLIMB_LEN], [0, 1], {\n    ...CLAMP,\n    easing: EASE_CLIMB,\n  });\n  const dy = climb * (1 - k) + (1 - appear) * F * 0.06;\n\n  const set = interpolate(\n    t,\n    [CLIMB_AT + CLIMB_LEN - 14, CLIMB_AT + CLIMB_LEN + 22],\n    [0, 1],\n    { ...CLAMP, easing: Easing.bezier(0.45, 0, 0.55, 1) },\n  );\n  const sunLine = sunLine0 + set * F * 0.05;\n\n  const x0 = W / 2 - nameW / 2;\n  const markH = R_SCALE * F;\n  const markW = (markH * R_W) / R_H;\n  const textX = isBrand ? x0 + markW + R_GAP * F : W / 2;\n  const ring = Math.max(1.2, F * 0.006);\n\n  const nameShapes = (mode: \"outline\" | \"solid\") => (\n    <g\n      transform={`translate(0 ${dy.toFixed(3)})`}\n      filter={mode === \"outline\" ? `url(#${uid}ring)` : undefined}\n      fill={c.ink}\n    >\n      {isBrand ? (\n        <path\n          d={REMOCN_R_PATH}\n          transform={`translate(${x0.toFixed(3)} ${(baseFinal - markH).toFixed(3)}) scale(${(markH / R_H).toFixed(5)})`}\n        />\n      ) : null}\n      <text\n        x={textX}\n        y={baseFinal}\n        textAnchor={isBrand ? \"start\" : \"middle\"}\n        fontFamily={family}\n        fontWeight={weight}\n        fontSize={F}\n        letterSpacing={`${track}em`}\n      >\n        {word}\n      </text>\n    </g>\n  );\n\n  const lineIn = interpolate(t, [0, 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const tickCount = portrait ? 24 : 48;\n  const tickGap = W / tickCount;\n  const ticks = [];\n  for (let i = 1; i < tickCount; i++) {\n    const x = i * tickGap;\n    if (x > W * lineIn) break;\n    const major = i % 4 === 0;\n    ticks.push(\n      <line\n        key={i}\n        x1={x}\n        x2={x}\n        y1={sunLine}\n        y2={sunLine + (major ? tagFs * 0.7 : tagFs * 0.32)}\n        stroke={c.dim}\n        strokeWidth={stroke}\n      />,\n    );\n  }\n\n  const tagP = interpolate(t, [TAG_AT, TAG_AT + 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  return (\n    <AbsoluteFill style={{ background: stage, overflow: \"hidden\" }}>\n      <svg\n        width={W}\n        height={H}\n        viewBox={`0 0 ${W} ${H}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <defs>\n          <filter id={`${uid}ring`} x=\"-5%\" y=\"-30%\" width=\"110%\" height=\"160%\">\n            <feMorphology\n              in=\"SourceAlpha\"\n              operator=\"erode\"\n              radius={ring}\n              result=\"core\"\n            />\n            <feComposite in=\"SourceGraphic\" in2=\"core\" operator=\"out\" />\n          </filter>\n          <clipPath id={`${uid}lit`} clipPathUnits=\"userSpaceOnUse\">\n            <rect x={0} y={-H} width={W} height={H + sunLine} />\n          </clipPath>\n          <clipPath id={`${uid}dark`} clipPathUnits=\"userSpaceOnUse\">\n            <rect x={0} y={sunLine} width={W} height={H * 2} />\n          </clipPath>\n        </defs>\n        <g opacity={appear}>\n          <g clipPath={`url(#${uid}dark)`} opacity={0.55}>\n            {nameShapes(\"outline\")}\n          </g>\n          <g clipPath={`url(#${uid}lit)`}>{nameShapes(\"solid\")}</g>\n        </g>\n        <line\n          x1={0}\n          x2={W * lineIn}\n          y1={sunLine}\n          y2={sunLine}\n          stroke={lineInk}\n          strokeWidth={stroke}\n        />\n        {ticks}\n      </svg>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: marginX,\n          top: sunLine + tagFs * 1.6,\n          whiteSpace: \"pre\",\n          fontFamily: mono.fontFamily,\n          fontWeight: 400,\n          fontSize: tagFs,\n          lineHeight: 1,\n          color: c.ink,\n          opacity: tagP,\n        }}\n      >\n        {tagline}\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/first-light.tsx"
    }
  ],
  "type": "registry:component"
}
