{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "quadrant-plot",
  "title": "Quadrant Plot",
  "description": "Competitors scatter on a quadrant plot and the Remocn marker lands in the best corner.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/quadrant-plot/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadGeist } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadGeistMono } from \"@remotion/google-fonts/GeistMono\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  interpolateColors,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadGeist(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: mono } = loadGeistMono(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: manrope } = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\n});\n\nexport interface QuadrantPoint {\n  label: string;\n  x: number;\n  y: number;\n}\n\nexport interface QuadrantPlotProps {\n  xLabels?: [string, string];\n  yLabels?: [string, string];\n  points?: QuadrantPoint[];\n  brand?: QuadrantPoint;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst DEFAULT_POINTS: QuadrantPoint[] = [\n  { label: \"Kitebase\", x: 0.71, y: 0.3 },\n  { label: \"Lumen\", x: 0.27, y: 0.71 },\n  { label: \"Fernway\", x: 0.2, y: 0.24 },\n  { label: \"Quarry\", x: 0.6, y: 0.4 },\n  { label: \"Orbitly\", x: 0.38, y: 0.58 },\n  { label: \"Halcyon\", x: 0.13, y: 0.42 },\n];\n\nconst DEFAULT_BRAND: QuadrantPoint = { label: \"remocn\", x: 0.74, y: 0.8 };\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_ARRIVE = Easing.bezier(0.2, 0.9, 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    axis: \"rgba(255,255,255,0.26)\",\n    mark: \"rgba(255,255,255,0.32)\",\n    grid: \"rgba(255,255,255,0.075)\",\n    dot: \"#9C9BA1\",\n    grain: 0.06,\n    hero: \"#F5F6FF\",\n    heroLabel: \"#F5F6FF\",\n    guide: \"rgba(245,246,255,0.55)\",\n    // gradients.sh \"mesh\": bg #0a0d24, [#ecdcf2, #8796ee, #2e45b7, #0e1645]\n    meshBg: \"#0a0d24\",\n    blobs: [\n      [\"#2e45b7\", 18, 6, 0.95],\n      [\"#ecdcf2\", -2, 112, 0.8],\n      [\"#8796ee\", 30, 84, 0.85],\n      [\"#2e45b7\", 72, 46, 1],\n      [\"#0e1645\", 102, 104, 1],\n      [\"#8796ee\", 104, -6, 0.45],\n    ],\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    axis: \"rgba(18,18,20,0.26)\",\n    mark: \"rgba(18,18,20,0.32)\",\n    grid: \"rgba(18,18,20,0.08)\",\n    dot: \"#8E8C86\",\n    grain: 0.045,\n    hero: \"#121214\",\n    heroLabel: \"#121214\",\n    guide: \"rgba(18,18,20,0.45)\",\n    meshBg: \"#EEF0FB\",\n    blobs: [\n      [\"#8796ee\", 0, 104, 0.95],\n      [\"#ecdcf2\", 36, 70, 1],\n      [\"#2e45b7\", 8, 40, 0.45],\n      [\"#8796ee\", 100, 0, 0.55],\n      [\"#ecdcf2\", 96, 96, 0.9],\n      [\"#2e45b7\", 104, 104, 0.35],\n    ],\n  },\n} as const;\n\nconst AXIS = [-8, 22] as const;\nconst DROP = 26;\nconst DROP_STAGGER = 4;\nconst ARRIVE = 62;\nconst ARRIVE_LEN = 30;\nconst LANDED = ARRIVE + ARRIVE_LEN - 8;\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={23}\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\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/** The remocn.dev logo: R mark (0.76em, on the baseline) + \"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,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\n        whiteSpace: \"nowrap\",\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\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\n\nexport function QuadrantPlot({\n  xLabels = [\"Slow\", \"Fast\"],\n  yLabels = [\"Hard\", \"Easy\"],\n  points = DEFAULT_POINTS,\n  brand = DEFAULT_BRAND,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: QuadrantPlotProps) {\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\n  const cx = width / 2;\n  const cy = height / 2;\n  const hw = width * 0.3;\n  const hh = height * 0.3;\n  const toX = (x: number) => cx + (clamp01(x) - 0.5) * 2 * hw;\n  const toY = (y: number) => cy - (clamp01(y) - 0.5) * 2 * hh;\n  const sw = Math.max(1, u);\n\n  const axisK = interpolate(t, AXIS, [0, 1], { ...CLAMP, easing: EASE_IO });\n  const markK = interpolate(t, [12, 30], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const labelK = (delay: number) =>\n    interpolate(t, [14 + delay, 32 + delay], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n\n  const arrive = interpolate(t, [ARRIVE, ARRIVE + ARRIVE_LEN], [0, 1], {\n    ...CLAMP,\n    easing: EASE_ARRIVE,\n  });\n  const bx = toX(brand.x);\n  const by = toY(brand.y);\n  const sx = cx - hw * 0.12;\n  const sy = cy + hh * 0.18;\n  const qx = bx + hw * 0.05;\n  const qy = sy - hh * 0.05;\n  const along = (k: number): [number, number] => {\n    const a = (1 - k) * (1 - k);\n    const b = 2 * (1 - k) * k;\n    const c = k * k;\n    return [a * sx + b * qx + c * bx, a * sy + b * qy + c * by];\n  };\n  const arriveAt = (time: number) =>\n    interpolate(time, [ARRIVE, ARRIVE + ARRIVE_LEN], [0, 1], {\n      ...CLAMP,\n      easing: EASE_ARRIVE,\n    });\n  const [dx, dy] = along(arrive);\n  const brandO = interpolate(t, [ARRIVE, ARRIVE + 5], [0, 1], CLAMP);\n  const brandS = interpolate(t, [ARRIVE, ARRIVE + 10], [0.6, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const ghosts = [1.5, 3, 4.5].map((lag, i) => {\n    const [gx, gy] = along(arriveAt(t - lag));\n    const dist = Math.hypot(gx - dx, gy - dy);\n    return {\n      gx,\n      gy,\n      o: Math.min(1, dist / (18 * u)) * [0.3, 0.16, 0.08][i] * brandO,\n    };\n  });\n\n  const ring = interpolate(t, [LANDED, LANDED + 26], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const dim = interpolate(t, [LANDED - 4, LANDED + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const wash = interpolate(t, [LANDED - 2, LANDED + 20], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const guide = interpolate(t, [LANDED + 4, LANDED + 22], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const brandLab = interpolate(t, [LANDED - 2, LANDED + 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  const push = interpolate(t, [0, 140], [0, 1], { ...CLAMP, easing: EASE_IO });\n  const zoom = 0.975 + 0.04 * push;\n  const drift = interpolate(t, [ARRIVE - 10, 140], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const camX = -(bx - cx) * 0.035 * drift;\n  const camY = -(by - cy) * 0.035 * drift;\n\n  const corner = 16 * u * markK;\n  const corners: Array<[number, number, number, number]> = [\n    [cx - hw, cy - hh, 1, 1],\n    [cx + hw, cy - hh, -1, 1],\n    [cx - hw, cy + hh, 1, -1],\n    [cx + hw, cy + hh, -1, -1],\n  ];\n\n  const gap = 0.25;\n  const gridDots: Array<[number, number]> = [];\n  for (let gx = 0; gx <= 1.0001; gx += gap / 2) {\n    for (let gy = 0; gy <= 1.0001; gy += gap / 2) {\n      if (Math.abs(gx - 0.5) < 0.01 || Math.abs(gy - 0.5) < 0.01) continue;\n      gridDots.push([toX(gx), toY(gy)]);\n    }\n  }\n\n  const endLabel = (\n    text: string,\n    x: number,\n    y: number,\n    align: \"left\" | \"right\" | \"top\" | \"bottom\",\n    k: number,\n    strong: boolean,\n  ) => {\n    const tx = align === \"left\" ? \"-100%\" : align === \"right\" ? \"0%\" : \"-50%\";\n    const ty = align === \"top\" ? \"-100%\" : align === \"bottom\" ? \"0%\" : \"-50%\";\n    const nudge = (1 - k) * 10 * u;\n    const ox = align === \"left\" ? nudge : align === \"right\" ? -nudge : 0;\n    const oy = align === \"top\" ? nudge : align === \"bottom\" ? -nudge : 0;\n    return (\n      <div\n        key={text + align}\n        style={{\n          position: \"absolute\",\n          left: x,\n          top: y,\n          transform: `translate(${tx}, ${ty}) translate(${ox}px, ${oy}px)`,\n          fontFamily: mono,\n          fontWeight: 400,\n          fontSize: 14 * u,\n          letterSpacing: \"0.12em\",\n          textTransform: \"uppercase\",\n          color: strong\n            ? interpolateColors(wash, [0, 1], [p.muted, p.fg])\n            : p.muted,\n          opacity: k,\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        {text}\n      </div>\n    );\n  };\n\n  const pad = 20 * u;\n  const reveal = 0.25 + 0.75 * wash;\n  const sway = interpolate(t, [LANDED - 4, 140], [0, 1], CLAMP);\n  const mesh = [\n    ...p.blobs.map(\n      ([c, x, y, a], i) =>\n        `radial-gradient(85% 95% at ${x + Math.sin(i * 1.7 + sway * 1.4) * 4}% ${y + Math.cos(i * 2.3 + sway * 1.2) * 4}%, color-mix(in oklab, ${c} ${Math.round(a * 100)}%, transparent) 0%, transparent 68%)`,\n    ),\n    `linear-gradient(${p.meshBg}, ${p.meshBg})`,\n  ].join(\", \");\n  const heroDot =\n    theme === \"light\"\n      ? accent\n      : interpolateColors(wash, [0, 1], [accent, p.hero]);\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          transform: `translate(${camX}px, ${camY}px) scale(${zoom})`,\n          transformOrigin: \"50% 50%\",\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            left: cx,\n            top: cy - hh,\n            width: hw,\n            height: hh,\n            background: mesh,\n            opacity: wash,\n            clipPath: `inset(${(1 - reveal) * 100}% ${(1 - reveal) * 100}% 0 0)`,\n          }}\n        />\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0 }}\n          aria-hidden\n        >\n          {gridDots.map(([gx, gy]) => (\n            <circle\n              key={`${gx.toFixed(1)}-${gy.toFixed(1)}`}\n              cx={gx}\n              cy={gy}\n              r={1.3 * u}\n              fill={p.grid}\n              opacity={markK}\n            />\n          ))}\n\n          <line\n            x1={cx - hw * axisK}\n            y1={cy}\n            x2={cx + hw * axisK}\n            y2={cy}\n            stroke={p.axis}\n            strokeWidth={sw}\n          />\n          <line\n            x1={cx}\n            y1={cy - hh * axisK}\n            x2={cx}\n            y2={cy + hh * axisK}\n            stroke={p.axis}\n            strokeWidth={sw}\n          />\n\n          {corners.map(([x, y, ix, iy]) => (\n            <path\n              key={`${x}-${y}`}\n              d={`M${x + ix * corner} ${y} L${x} ${y} L${x} ${y + iy * corner}`}\n              fill=\"none\"\n              stroke={p.mark}\n              strokeWidth={sw}\n              opacity={markK * (ix < 0 && iy > 0 ? 1 - wash : 1)}\n            />\n          ))}\n\n          {points.map((pt, i) => {\n            const start = DROP + i * DROP_STAGGER;\n            const s = spring({\n              frame: t - start,\n              fps,\n              config: { damping: 14, stiffness: 170, mass: 0.7 },\n            });\n            const o = interpolate(t, [start, start + 6], [0, 1], CLAMP);\n            const x = toX(pt.x);\n            const y = toY(pt.y) - (1 - s) * 34 * u;\n            return (\n              <circle\n                key={pt.label}\n                cx={x}\n                cy={y}\n                r={7 * u}\n                fill={p.dot}\n                opacity={o * (1 - 0.62 * dim)}\n              />\n            );\n          })}\n\n          <g opacity={guide * 0.9}>\n            <line\n              x1={bx}\n              y1={by}\n              x2={bx}\n              y2={by + (cy - by) * guide}\n              stroke={p.guide}\n              strokeWidth={sw}\n              strokeDasharray={`${3 * u} ${5 * u}`}\n            />\n            <line\n              x1={bx}\n              y1={by}\n              x2={bx + (cx - bx) * guide}\n              y2={by}\n              stroke={p.guide}\n              strokeWidth={sw}\n              strokeDasharray={`${3 * u} ${5 * u}`}\n            />\n          </g>\n\n          {ghosts.map((g, i) => (\n            <circle\n              key={i}\n              cx={g.gx}\n              cy={g.gy}\n              r={12 * u * brandS}\n              fill={accent}\n              opacity={t >= ARRIVE ? g.o : 0}\n            />\n          ))}\n          <circle\n            cx={dx}\n            cy={dy}\n            r={(12 + 36 * ring) * u}\n            fill=\"none\"\n            stroke={heroDot}\n            strokeWidth={1.5 * u}\n            opacity={t >= LANDED ? (1 - ring) * 0.7 : 0}\n          />\n          <circle\n            cx={dx}\n            cy={dy}\n            r={22 * u}\n            fill=\"none\"\n            stroke={heroDot}\n            strokeWidth={sw}\n            opacity={brandLab * 0.45}\n          />\n          <circle\n            cx={dx}\n            cy={dy}\n            r={12 * u * brandS}\n            fill={heroDot}\n            opacity={t >= ARRIVE ? brandO : 0}\n          />\n        </svg>\n\n        {points.map((pt, i) => {\n          const start = DROP + i * DROP_STAGGER + 5;\n          const k = interpolate(t, [start, start + 14], [0, 1], {\n            ...CLAMP,\n            easing: EASE_OUT,\n          });\n          return (\n            <div\n              key={pt.label}\n              style={{\n                position: \"absolute\",\n                left: toX(pt.x) + 15 * u,\n                top: toY(pt.y),\n                transform: `translate(${(1 - k) * -6 * u}px, -50%)`,\n                fontFamily: sans,\n                fontWeight: 400,\n                fontSize: 17 * u,\n                letterSpacing: \"-0.005em\",\n                color: p.muted,\n                opacity: k * (1 - 0.6 * dim),\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              {pt.label}\n            </div>\n          );\n        })}\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: bx + 36 * u,\n            top: by,\n            transform: `translate(${(1 - brandLab) * -10 * u}px, -50%)`,\n            opacity: brandLab,\n          }}\n        >\n          {brand.label.toLowerCase() === \"remocn\" ? (\n            <RemocnLogo fontSize={34 * u} color={p.heroLabel} />\n          ) : (\n            <div\n              style={{\n                fontFamily: sans,\n                fontWeight: 500,\n                fontSize: 32 * u,\n                letterSpacing: \"-0.025em\",\n                color: p.heroLabel,\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              {brand.label}\n            </div>\n          )}\n        </div>\n\n        {endLabel(xLabels[0], cx - hw - pad, cy, \"left\", labelK(0), false)}\n        {endLabel(xLabels[1], cx + hw + pad, cy, \"right\", labelK(2), true)}\n        {endLabel(yLabels[1], cx, cy - hh - pad, \"top\", labelK(4), true)}\n        {endLabel(yLabels[0], cx, cy + hh + pad, \"bottom\", labelK(6), false)}\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/quadrant-plot.tsx"
    }
  ],
  "type": "registry:component"
}
