{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bracket-index",
  "title": "Bracket Index",
  "description": "Indexed bracket labels step through words before the brackets close around the product name.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/bracket-index/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/InstrumentSerif\";\nimport { loadFont as loadMono } from \"@remotion/google-fonts/JetBrainsMono\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport type React from \"react\";\nimport { useEffect, useId, useMemo, 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: [\"500\", \"600\"],\n  subsets: [\"latin\"],\n});\nconst serif = loadSerif(\"italic\", { weights: [\"400\"], subsets: [\"latin\"] });\nconst mono = loadMono(\"normal\", { weights: [\"400\"], subsets: [\"latin\"] });\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\";\nconst R_W = 124.06;\nconst R_H = 134.26;\n// The remocn.dev logo: R mark 0.76em tall on the baseline, then \"emocn\" in\n// Manrope 600 at -0.025em. Manrope's baseline sits 0.883em into a 1em line box.\nconst LOGO_R = 0.76;\nconst LOGO_GAP = 0.04;\nconst LOGO_TRACK = -0.025;\nconst BASELINE = 0.883;\n\nexport interface BracketIndexProps {\n  /** Text set before the brackets; it steps aside for the product name. */\n  prefix?: string;\n  /** Words indexed inside the brackets, in order. */\n  words?: string[];\n  /** Final word the brackets close on. */\n  product?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IO = Easing.bezier(0.7, 0, 0.2, 1);\nconst EASE_SNAP = Easing.bezier(0.22, 1.12, 0.36, 1);\n\nconst WEIGHT = 500;\nconst TRACK = -0.03;\nconst SERIF_TRACK = -0.01;\nconst PAD = 0.2;\nconst GAP = 0.26;\n\nconst OPEN_AT = 6;\nconst OPEN_LEN = 16;\nconst HOLD = 22;\nconst SWAP = 16;\nconst ROLL = 8;\nconst RESIZE = 8;\nconst FINAL_SWAP = 20;\n\n// gradients.sh \"beam\" palette (#f4f7ff #8fb4ff #3d5bff #1a1660), with the\n// accent standing in for #3d5bff. Dark runs light→accent, light runs accent→deep.\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    rest: \"#77767C\",\n    beam: [\"#F4F7FF\", \"#8FB4FF\", \"\"],\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    rest: \"#8E8C86\",\n    beam: [\"\", \"#2B37C9\", \"#1A1660\"],\n  },\n} as const;\n\nfunction isLogo(word: string) {\n  return word.trim().toLowerCase() === \"remocn\";\n}\n\n/** Widths in em: [prefix (serif italic), ...items (Manrope, or the logo)]. */\nfunction useWidths(strings: string[]) {\n  const [widths, setWidths] = useState<number[] | null>(null);\n  const [handle] = useState(() => delayRender(\"bracket-index: measuring\"));\n  const released = useRef(false);\n  const key = strings.join(\"\\u0000\");\n\n  useEffect(() => {\n    let live = true;\n    const sans = `${WEIGHT} 100px \"${manrope.fontFamily}\"`;\n    const logo = `600 100px \"${manrope.fontFamily}\"`;\n    const italic = `italic 400 100px \"${serif.fontFamily}\"`;\n    Promise.all([\n      manrope.waitUntilDone(),\n      serif.waitUntilDone(),\n      document.fonts.load(sans),\n      document.fonts.load(logo),\n      document.fonts.load(italic),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n        const run = (s: string, font: string, track: number) => {\n          if (!ctx) return s.length * 0.55;\n          ctx.font = font;\n          return ctx.measureText(s).width / 100 + track * [...s].length;\n        };\n        setWidths(\n          key.split(\"\\u0000\").map((s, i) => {\n            if (i === 0) return run(s, italic, SERIF_TRACK);\n            if (isLogo(s)) {\n              return (\n                (LOGO_R * R_W) / R_H + LOGO_GAP + run(\"emocn\", logo, LOGO_TRACK)\n              );\n            }\n            return run(s, sans, TRACK);\n          }),\n        );\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [key, handle]);\n\n  return widths;\n}\n\nfunction pad2(n: number) {\n  return String(n).padStart(2, \"0\");\n}\n\nexport function BracketIndex({\n  prefix = \"Motion for\",\n  words = [\"launches\", \"changelogs\", \"demos\"],\n  product = \"remocn\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: BracketIndexProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width, height } = useVideoConfig();\n  const t = THEMES[theme];\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const beam = t.beam.map((c) => c || accent);\n\n  const items = useMemo(() => [...words, product], [words, product]);\n  const widths = useWidths(useMemo(() => [prefix, ...items], [prefix, items]));\n\n  // Swap i brings items[i] in. Swap 0 is the bracket opening.\n  const starts = useMemo(() => {\n    const s = [OPEN_AT];\n    for (let i = 1; i < items.length; i++) {\n      s.push(s[i - 1] + (i === 1 ? OPEN_LEN : SWAP) + HOLD);\n    }\n    return s;\n  }, [items.length]);\n\n  if (!widths)\n    return <div style={{ position: \"absolute\", inset: 0, background: t.bg }} />;\n\n  const prefixW = widths[0];\n  const itemW = widths.slice(1);\n  const last = items.length - 1;\n\n  const stroke = 0.022;\n  const boxOf = (slot: number) => 2 * stroke + 2 * PAD + slot;\n  const widest = Math.max(\n    ...itemW.map((w, i) => boxOf(w) + (i === last ? 0 : prefixW + GAP)),\n  );\n\n  // The brackets always hold the wider of the two words: a longer word\n  // gets room before it rolls in, a shorter one is closed on after.\n  const rollAt = (i: number) =>\n    i > 0 && itemW[i] > itemW[i - 1] ? starts[i] + RESIZE - 2 : starts[i];\n  let slot = 0;\n  for (let i = 0; i < items.length; i++) {\n    if (frame < starts[i]) break;\n    if (i === 0) {\n      slot =\n        itemW[0] *\n        interpolate(frame, [starts[0], starts[0] + OPEN_LEN], [0, 1], {\n          ...CLAMP,\n          easing: EASE_SNAP,\n        });\n      continue;\n    }\n    const from = itemW[i - 1];\n    const resizeAt = itemW[i] > from ? starts[i] : starts[i] + ROLL - 1;\n    const p = interpolate(frame, [resizeAt, resizeAt + RESIZE], [0, 1], {\n      ...CLAMP,\n      easing: EASE_IO,\n    });\n    slot = from + (itemW[i] - from) * p;\n  }\n\n  const finalP = interpolate(\n    frame,\n    [starts[last], starts[last] + FINAL_SWAP],\n    [0, 1],\n    { ...CLAMP, easing: EASE_IO },\n  );\n  const presence = 1 - finalP;\n  // The product name lands bigger than the indexed words: a poster frame.\n  const sizeA = Math.min(height * 0.15, (width * 0.84) / widest);\n  const sizeB = Math.min(height * 0.27, (width * 0.6) / boxOf(itemW[last]));\n  const size = sizeA + (sizeB - sizeA) * finalP;\n  const box = boxOf(slot);\n  const total = presence * (prefixW + GAP) + box;\n\n  const cx = width / 2;\n  const cy = height / 2;\n  const em = (v: number) => v * size;\n  const groupLeft = cx - em(total) / 2;\n  const bracketL = groupLeft + em(presence * (prefixW + GAP));\n  const bracketR = bracketL + em(box);\n  const slotCenter = (bracketL + bracketR) / 2;\n\n  const intro = interpolate(frame, [0, 10], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const half = em(0.8) * (0.7 + 0.3 * intro);\n  const arm = em(0.2) * intro;\n  const sw = Math.max(1.5, sizeA * stroke);\n  // Lowercase words sit below cap center; nudge the frame toward x-height.\n  const top = cy + em(0.04) - half;\n  const bottom = cy + em(0.04) + half;\n  const lineTop = cy - em(0.5);\n\n  const bracketPath = (x: number, dir: 1 | -1) => {\n    const xi = x + (dir * sw) / 2;\n    return `M ${xi + dir * arm} ${top + sw / 2} H ${xi} V ${bottom - sw / 2} H ${xi + dir * arm}`;\n  };\n\n  const prefixOpacity =\n    interpolate(frame, [0, 10], [0.15, 1], CLAMP) *\n    interpolate(presence, [0.45, 1], [0, 1], CLAMP);\n  const prefixRise = interpolate(frame, [0, 12], [em(0.14), 0], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  const textStyle: React.CSSProperties = {\n    position: \"absolute\",\n    top: 0,\n    fontFamily: manrope.fontFamily,\n    fontWeight: WEIGHT,\n    fontSize: size,\n    lineHeight: 1,\n    letterSpacing: `${TRACK}em`,\n    whiteSpace: \"nowrap\",\n    color: t.fg,\n    fontKerning: \"normal\",\n  };\n\n  // Words ride one strip through the slit, like an odometer: the outgoing\n  // word and the incoming one never share the window.\n  const roll = (i: number) =>\n    interpolate(frame, [rollAt(i), rollAt(i) + ROLL], [0, 1], {\n      ...CLAMP,\n      easing: EASE_IO,\n    });\n  const travel = 1.16;\n  // The gradient turns a touch over the shot; never more than that.\n  const drift = interpolate(frame, [0, 180], [0, 1], CLAMP);\n  const wordLayers = items.map((word, i) => {\n    const s = starts[i];\n    const enter =\n      i === 0\n        ? interpolate(frame, [s, s + OPEN_LEN], [0, 1], {\n            ...CLAMP,\n            easing: EASE_OUT,\n          })\n        : roll(i);\n    const exit = i < last ? roll(i + 1) : 0;\n    if (frame < s || exit >= 1) return null;\n    const y =\n      (i === 0 ? em(0.1) : em(travel)) * (1 - enter) - em(travel) * exit;\n    // Dim whichever word is leaving the slit so the stacked frames stay quiet.\n    const opacity =\n      (i === 0 ? 0.35 + 0.65 * enter : 0.25 + 0.75 * enter) *\n      (1 - Math.min(1, exit / 0.6));\n    if (isLogo(word)) {\n      const lw = em(itemW[i]);\n      const rh = em(LOGO_R);\n      const base = em(BASELINE);\n      return (\n        <svg\n          key={word + String(i)}\n          width={lw}\n          height={em(1.14)}\n          style={{\n            position: \"absolute\",\n            top: 0,\n            left: slotCenter,\n            overflow: \"visible\",\n            transform: `translate(-50%, ${y}px)`,\n            opacity,\n          }}\n          aria-hidden\n        >\n          <path\n            d={REMOCN_R_PATH}\n            fill={`url(#${uid}-logo)`}\n            transform={`translate(0 ${base - rh}) scale(${rh / R_H})`}\n          />\n          <text\n            x={(rh * R_W) / R_H + em(LOGO_GAP)}\n            y={base}\n            fill={`url(#${uid}-logo)`}\n            fontFamily={manrope.fontFamily}\n            fontWeight={600}\n            fontSize={size}\n            letterSpacing={`${LOGO_TRACK}em`}\n            style={{ fontKerning: \"normal\" }}\n          >\n            emocn\n          </text>\n          <defs>\n            <linearGradient\n              id={`${uid}-logo`}\n              gradientUnits=\"userSpaceOnUse\"\n              x1={lw * (0.05 + 0.1 * drift)}\n              y1={base - em(0.95)}\n              x2={lw * (0.95 - 0.1 * drift)}\n              y2={base + em(0.2)}\n            >\n              <stop offset=\"0\" stopColor={beam[0]} />\n              <stop offset=\"0.5\" stopColor={beam[1]} />\n              <stop offset=\"1\" stopColor={beam[2]} />\n            </linearGradient>\n          </defs>\n        </svg>\n      );\n    }\n    return (\n      <div\n        key={word + String(i)}\n        style={{\n          ...textStyle,\n          left: slotCenter,\n          transform: `translate(-50%, ${y}px)`,\n          opacity,\n        }}\n      >\n        {word}\n      </div>\n    );\n  });\n\n  const innerL = bracketL + sw + em(0.04);\n  const innerR = bracketR - sw - em(0.04);\n\n  // Counter: two-digit index that rolls on every swap.\n  const counterSize = Math.max(12, sizeA * 0.2);\n  const counterRows = items.map((_, i) => {\n    const s = starts[i];\n    const into =\n      i === 0\n        ? 1\n        : interpolate(frame, [s + 2, s + 12], [0, 1], {\n            ...CLAMP,\n            easing: EASE_OUT,\n          });\n    const out =\n      i < last\n        ? interpolate(frame, [starts[i + 1], starts[i + 1] + 8], [0, 1], {\n            ...CLAMP,\n            easing: EASE_IO,\n          })\n        : 0;\n    if ((i > 0 && frame < s + 2) || out >= 1) return null;\n    return (\n      <div\n        key={`c${String(i)}`}\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: 0,\n          transform: `translateY(${(1 - into) * counterSize * 1.1 - out * counterSize * 1.1}px)`,\n          opacity: into * (1 - out),\n        }}\n      >\n        {pad2(i + 1)}\n      </div>\n    );\n  });\n  const counterIn = interpolate(\n    frame,\n    [starts[0] + 8, starts[0] + 18],\n    [0, 1],\n    { ...CLAMP, easing: EASE_OUT },\n  );\n\n  return (\n    <AbsoluteFill style={{ background: t.bg, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          opacity: EASE_OUT(finalP),\n          background:\n            theme === \"dark\"\n              ? `radial-gradient(64% 62% at ${46 + 8 * drift}% 122%, color-mix(in oklab, ${beam[1]} 46%, transparent) 0%, color-mix(in oklab, ${beam[2]} 34%, transparent) 38%, color-mix(in oklab, #1A1660 26%, transparent) 62%, transparent 86%)`\n              : `radial-gradient(64% 62% at ${46 + 8 * drift}% 122%, color-mix(in oklab, #8FB4FF 58%, transparent) 0%, color-mix(in oklab, #8FB4FF 22%, transparent) 50%, transparent 84%)`,\n        }}\n      />\n      <div\n        style={{\n          ...textStyle,\n          fontFamily: serif.fontFamily,\n          fontStyle: \"italic\",\n          fontWeight: 400,\n          letterSpacing: `${SERIF_TRACK}em`,\n          left: bracketL - em(GAP + prefixW),\n          top: lineTop,\n          transform: `translateY(${prefixRise}px)`,\n          opacity: prefixOpacity,\n        }}\n      >\n        {prefix}\n      </div>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          width,\n          top: lineTop - em(0.02),\n          height: em(1.14),\n          overflow: \"hidden\",\n          clipPath: `inset(0 ${width - innerR}px 0 ${innerL}px)`,\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            top: em(0.02),\n            width,\n          }}\n        >\n          {wordLayers}\n        </div>\n      </div>\n\n      <svg\n        width={width}\n        height={height}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <defs>\n          <linearGradient\n            id={`${uid}-bracket`}\n            gradientUnits=\"userSpaceOnUse\"\n            x1={0}\n            y1={top}\n            x2={0}\n            y2={bottom}\n          >\n            <stop offset=\"0\" stopColor={beam[0]} />\n            <stop offset=\"0.55\" stopColor={beam[1]} />\n            <stop offset=\"1\" stopColor={beam[2]} />\n          </linearGradient>\n        </defs>\n        <g\n          fill=\"none\"\n          stroke={`url(#${uid}-bracket)`}\n          strokeWidth={sw}\n          strokeLinecap=\"butt\"\n          strokeLinejoin=\"miter\"\n          opacity={interpolate(intro, [0, 1], [0.2, 1])}\n        >\n          <path d={bracketPath(bracketL, 1)} />\n          <path d={bracketPath(bracketR, -1)} />\n        </g>\n      </svg>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: bracketL,\n          top: top - counterSize * 1.75,\n          height: counterSize * 1.2,\n          display: \"flex\",\n          alignItems: \"center\",\n          fontFamily: mono.fontFamily,\n          fontWeight: 400,\n          fontSize: counterSize,\n          lineHeight: 1,\n          letterSpacing: \"0.02em\",\n          fontVariantNumeric: \"tabular-nums\",\n          color: t.rest,\n          opacity: counterIn,\n          transform: `translateY(${(1 - counterIn) * counterSize * 0.5}px)`,\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        <span>[</span>\n        <span\n          style={{\n            position: \"relative\",\n            display: \"inline-block\",\n            width: \"1.2em\",\n            height: counterSize * 1.2,\n            overflow: \"hidden\",\n            top: counterSize * 0.1,\n          }}\n        >\n          {counterRows}\n        </span>\n        <span>]</span>\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/bracket-index.tsx"
    }
  ],
  "type": "registry:component"
}
