{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chord-ring",
  "title": "Chord Ring",
  "description": "Chords link a ring of integration names around a glowing center that reads \"Connects to everything\".",
  "dependencies": ["remotion", "@remotion/google-fonts", "@remotion/paths"],
  "files": [
    {
      "path": "registry/remocn/chord-ring/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadSans } from \"@remotion/google-fonts/InstrumentSans\";\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/InstrumentSerif\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { evolvePath } from \"@remotion/paths\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadSans(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: serif } = loadSerif(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nloadSerif(\"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\n/** Full logo as on remocn.dev: R mark (0.76em tall, baseline-aligned) + \"emocn\" in Manrope 600. */\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\n/** gradients.sh \"mesh\" palette (bg + colours), used as the core disc. */\nconst MESH = {\n  bg: \"#0a0d24\",\n  colors: [\"#ecdcf2\", \"#8796ee\", \"#2e45b7\", \"#0e1645\"],\n} as const;\n\nfunction meshGround(drift: number, accent: string) {\n  const d = drift * 6;\n  const blob = (x: number, y: number, c: string, w = 85, h = 95) =>\n    `radial-gradient(${w}% ${h}% at ${x}% ${y}%, ${c} 0%, transparent 68%)`;\n  return [\n    blob(\n      70 - d,\n      100,\n      `color-mix(in oklab, ${MESH.colors[0]} 45%, transparent)`,\n      70,\n      45,\n    ),\n    blob(88, 70 + d, MESH.colors[1], 60, 70),\n    blob(16 + d, 74, `color-mix(in oklab, ${accent} 90%, transparent)`, 70, 70),\n    blob(30, 18 - d, MESH.colors[2], 85, 80),\n    blob(60, 30, MESH.colors[3], 70, 60),\n    MESH.bg,\n  ].join(\", \");\n}\n\nexport interface ChordRingProps {\n  segments?: string[];\n  brandIndex?: number;\n  caption?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst DEFAULT_SEGMENTS = [\n  \"remocn\",\n  \"Kitebase\",\n  \"Lumen\",\n  \"Fernway\",\n  \"Quarry\",\n  \"Orbitly\",\n  \"Halcyon\",\n  \"Tessel\",\n  \"Northwind\",\n];\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_SEG = Easing.bezier(0.55, 0, 0.25, 1);\nconst EASE_CHORD = Easing.bezier(0.45, 0, 0.25, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    seg: \"#5E5D63\",\n    chord: \"rgba(243,242,239,0.34)\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    seg: \"#B4B2AC\",\n    chord: \"rgba(18,18,20,0.32)\",\n    grain: 0.045,\n  },\n} as const;\n\nconst SEG_START = 2;\nconst SEG_STAGGER = 2;\nconst SEG_LEN = 12;\nconst CHORD_START = 26;\nconst CHORD_STAGGER = 2.2;\nconst CHORD_LEN = 16;\nconst TAU = Math.PI * 2;\n\nfunction pt(cx: number, cy: number, r: number, a: number) {\n  return { x: cx + r * Math.sin(a), y: cy - r * Math.cos(a) };\n}\n\nfunction arc(cx: number, cy: number, r: number, a0: number, a1: number) {\n  const s = pt(cx, cy, r, a0);\n  const e = pt(cx, cy, r, a1);\n  const large = a1 - a0 > Math.PI ? 1 : 0;\n  return `M ${s.x.toFixed(2)} ${s.y.toFixed(2)} A ${r} ${r} 0 ${large} 1 ${e.x.toFixed(2)} ${e.y.toFixed(2)}`;\n}\n\nfunction buildPairs(n: number, brand: number) {\n  const brandPairs: [number, number][] = [];\n  for (let i = 0; i < n; i++) if (i !== brand) brandPairs.push([brand, i]);\n  const seen = new Set<string>();\n  const others: [number, number][] = [];\n  for (const k of [3, Math.floor(n / 2)]) {\n    for (let i = 0; i < n; i++) {\n      const j = (i + k) % n;\n      if (i === brand || j === brand || i === j) continue;\n      const key = `${Math.min(i, j)}-${Math.max(i, j)}`;\n      if (seen.has(key)) continue;\n      seen.add(key);\n      others.push([i, j]);\n    }\n  }\n  const order: { a: number; b: number; brand: boolean }[] = [];\n  const len = Math.max(brandPairs.length, others.length);\n  for (let i = 0; i < len; i++) {\n    const o = others[(i * 5) % Math.max(1, others.length)];\n    if (i < others.length && o) order.push({ a: o[0], b: o[1], brand: false });\n    const bp = brandPairs[(i * 3) % Math.max(1, brandPairs.length)];\n    if (i < brandPairs.length && bp)\n      order.push({ a: bp[0], b: bp[1], brand: true });\n  }\n  const uniq = new Set<string>();\n  return order.filter((c) => {\n    const key = `${Math.min(c.a, c.b)}-${Math.max(c.a, c.b)}`;\n    if (uniq.has(key)) return false;\n    uniq.add(key);\n    return true;\n  });\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 }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={47}\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\nexport function ChordRing({\n  segments = DEFAULT_SEGMENTS,\n  brandIndex = 0,\n  caption = \"Connects to everything\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: ChordRingProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height, fps } = useVideoConfig();\n  const u = Math.min(width / 1280, height / 720);\n  const p = THEMES[theme];\n  const list = segments.slice(0, 12);\n  const n = list.length;\n  const brand = Math.min(Math.max(0, Math.round(brandIndex)), n - 1);\n\n  const cx = width / 2;\n  const cy = height / 2;\n  const R = height * 0.32;\n  const sw = 10 * u;\n  const Rin = R - sw / 2 - 9 * u;\n  const hole = Rin * 0.64;\n  const step = TAU / Math.max(1, n);\n  const gapA = (8 * u) / R;\n\n  const spinDeg = interpolate(t, [0, 90], [-10, 0], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const rot = step / 2 + (spinDeg * Math.PI) / 180;\n\n  const segs = list.map((label, i) => {\n    const a0 = rot + i * step + gapA / 2;\n    const a1 = rot + (i + 1) * step - gapA / 2;\n    const start = SEG_START + i * SEG_STAGGER;\n    return {\n      label,\n      i,\n      a0,\n      a1,\n      mid: (a0 + a1) / 2,\n      start,\n      end: start + SEG_LEN,\n    };\n  });\n\n  const pairs = buildPairs(n, brand);\n  const ends = new Map<string, number>();\n  for (let s = 0; s < n; s++) {\n    const mine = pairs\n      .map((c, ci) => ({ c, ci }))\n      .filter(({ c }) => c.a === s || c.b === s)\n      .map(({ c, ci }) => {\n        const other = c.a === s ? c.b : c.a;\n        const delta = (((other - s) % n) + n) % n;\n        return { ci, delta };\n      })\n      .sort((x, y) => y.delta - x.delta);\n    const seg = segs[s];\n    const inset = (seg.a1 - seg.a0) * 0.2;\n    mine.forEach(({ ci }, k) => {\n      const f = mine.length > 1 ? k / (mine.length - 1) : 0.5;\n      ends.set(\n        `${ci}-${s}`,\n        seg.a0 + inset + (seg.a1 - seg.a0 - 2 * inset) * f,\n      );\n    });\n  }\n\n  const chords = pairs.map((c, ci) => {\n    const th0 = ends.get(`${ci}-${c.a}`) ?? segs[c.a].mid;\n    const th1 = ends.get(`${ci}-${c.b}`) ?? segs[c.b].mid;\n    const P0 = pt(cx, cy, Rin, th0);\n    const P2 = pt(cx, cy, Rin, th1);\n    let dth = th1 - th0;\n    while (dth > Math.PI) dth -= TAU;\n    while (dth < -Math.PI) dth += TAU;\n    const m = th0 + dth / 2;\n    const half = Rin * Math.cos(Math.abs(dth) / 2);\n    const rmin = Math.max(hole, half * 0.95);\n    const rc = 2 * rmin - half;\n    const C = pt(cx, cy, rc, m);\n    const d = `M${P0.x.toFixed(2)} ${P0.y.toFixed(2)} Q${C.x.toFixed(2)} ${C.y.toFixed(2)} ${P2.x.toFixed(2)} ${P2.y.toFixed(2)}`;\n    const start = CHORD_START + ci * CHORD_STAGGER;\n    return { ...c, d, start, end: start + CHORD_LEN };\n  });\n\n  const lastChord = chords.length\n    ? chords[chords.length - 1].end\n    : CHORD_START + 10;\n  const HIT = Math.round(lastChord + 6);\n\n  const flip = spring({\n    frame: t - HIT,\n    fps,\n    config: { damping: 20, stiffness: 140, mass: 0.8 },\n  });\n  const dim = interpolate(t, [HIT, HIT + 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const capT = HIT + 8;\n  const land = spring({\n    frame: t - capT,\n    fps,\n    config: { damping: 18, stiffness: 150, mass: 0.8 },\n  });\n  const landO = interpolate(t, [capT, capT + 10], [0, 1], CLAMP);\n  const blur = interpolate(t, [capT, capT + 16], [1, 0], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const push = interpolate(t, [0, 160], [0.965, 1.005], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const stage = interpolate(t, [0, 8], [0.5, 1], CLAMP);\n  const hair = Math.max(1, 1.4 * u);\n  const disc = hole * 0.9;\n  const discS = spring({\n    frame: t - HIT,\n    fps,\n    config: { damping: 22, stiffness: 120, mass: 0.9 },\n  });\n  const discO = interpolate(t, [HIT, HIT + 10], [0, 1], CLAMP);\n  const drift = interpolate(t, [HIT, HIT + 90], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const words = caption.trim().split(/\\s+/);\n  const tail = words.length ? words[words.length - 1] : \"\";\n  const lead = words.slice(0, -1).join(\" \");\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          transform: `scale(${push})`,\n          transformOrigin: \"50% 50%\",\n          opacity: stage,\n        }}\n      >\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0 }}\n          aria-hidden\n        >\n          {chords.map((c) => {\n            if (t < c.start) return null;\n            const k = interpolate(t, [c.start, c.end], [0, 1], {\n              ...CLAMP,\n              easing: EASE_CHORD,\n            });\n            const evo = evolvePath(k, c.d);\n            return (\n              <path\n                key={`c${c.a}-${c.b}`}\n                d={c.d}\n                fill=\"none\"\n                stroke={p.chord}\n                strokeWidth={hair}\n                strokeLinecap=\"round\"\n                strokeDasharray={evo.strokeDasharray}\n                strokeDashoffset={evo.strokeDashoffset}\n                opacity={c.brand ? 1 - flip : 1 - 0.7 * dim}\n              />\n            );\n          })}\n          {flip > 0.001\n            ? chords\n                .filter((c) => c.brand)\n                .map((c) => (\n                  <path\n                    key={`a${c.a}-${c.b}`}\n                    d={c.d}\n                    fill=\"none\"\n                    stroke={accent}\n                    strokeWidth={hair * (1 + 1.1 * flip)}\n                    strokeLinecap=\"round\"\n                    opacity={Math.min(1, flip)}\n                  />\n                ))\n            : null}\n\n          {segs.map((s) => {\n            const k = interpolate(t, [s.start, s.end], [0, 1], {\n              ...CLAMP,\n              easing: EASE_SEG,\n            });\n            if (k <= 0) return null;\n            const a1 = s.a0 + (s.a1 - s.a0) * k;\n            const isBrand = s.i === brand;\n            return (\n              <g key={s.label}>\n                <path\n                  d={arc(cx, cy, R, s.a0, a1)}\n                  fill=\"none\"\n                  stroke={p.seg}\n                  strokeWidth={sw}\n                  opacity={isBrand ? 1 - flip : 1 - 0.35 * dim}\n                />\n                {isBrand && flip > 0.001 ? (\n                  <path\n                    d={arc(cx, cy, R, s.a0, a1)}\n                    fill=\"none\"\n                    stroke={accent}\n                    strokeWidth={sw * (1 + 0.4 * flip)}\n                    opacity={Math.min(1, flip)}\n                  />\n                ) : null}\n              </g>\n            );\n          })}\n        </svg>\n\n        {segs.map((s) => {\n          const a = interpolate(t, [s.end - 6, s.end + 10], [0, 1], {\n            ...CLAMP,\n            easing: EASE_OUT,\n          });\n          const r = R + sw / 2 + 18 * u + (1 - a) * -6 * u;\n          const q = pt(cx, cy, r, s.mid);\n          const sx = Math.sin(s.mid);\n          const cyv = -Math.cos(s.mid);\n          const vertical = Math.abs(sx) < 0.3;\n          const tx = vertical ? -50 : sx > 0 ? 0 : -100;\n          const ty = vertical ? (cyv < 0 ? -100 : 0) : -50;\n          const isBrand = s.i === brand;\n          return (\n            <div\n              key={s.label}\n              style={{\n                position: \"absolute\",\n                left: q.x,\n                top: q.y,\n                transform: `translate(${tx}%, ${ty}%)`,\n                opacity: a * (isBrand ? 1 : 1 - 0.5 * dim),\n                fontFamily: sans,\n                fontWeight: 400,\n                fontSize: 19 * u,\n                letterSpacing: \"-0.005em\",\n                color: p.fg,\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              {s.label.trim().toLowerCase() === \"remocn\" ? (\n                <RemocnLogo fontSize={22 * u} color={p.fg} />\n              ) : (\n                s.label\n              )}\n            </div>\n          );\n        })}\n\n        <AbsoluteFill\n          style={{ alignItems: \"center\", justifyContent: \"center\" }}\n        >\n          <div\n            style={{\n              position: \"absolute\",\n              left: cx - disc,\n              top: cy - disc,\n              width: disc * 2,\n              height: disc * 2,\n              borderRadius: \"50%\",\n              background: meshGround(drift, accent),\n              opacity: discO,\n              transform: `scale(${0.9 + 0.1 * discS})`,\n            }}\n          />\n          <div\n            style={{\n              width: disc * 1.6,\n              textAlign: \"center\",\n              fontFamily: serif,\n              fontWeight: 400,\n              fontSize: 44 * u,\n              lineHeight: 1.02,\n              letterSpacing: \"-0.015em\",\n              textWrap: \"balance\",\n              color: \"#FFFFFF\",\n              opacity: landO,\n              filter: blur > 0.01 ? `blur(${blur * 8 * u}px)` : undefined,\n              transform: `translateY(${(1 - land) * 14 * u}px)`,\n            }}\n          >\n            {lead}\n            {lead ? \" \" : null}\n            <span style={{ fontStyle: \"italic\" }}>{tail}</span>\n          </div>\n        </AbsoluteFill>\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 80% 80% at 50% 50%, transparent 62%, ${p.bg} 100%)`,\n          opacity: 0.6,\n        }}\n      />\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/chord-ring.tsx"
    }
  ],
  "type": "registry:component"
}
