{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "exactly",
  "title": "Exactly",
  "description": "A yellow \"Exactly.\" lands first, then \"It's just code.\" completes the answer.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/exactly/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/HubotSans\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  interpolateColors,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst hubot = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface ExactlyRound {\n  objection: string;\n  reply: string;\n}\n\nexport interface ExactlyProps {\n  rounds?: ExactlyRound[];\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst DEFAULT_ROUNDS: ExactlyRound[] = [\n  { objection: \"There’s nothing to click.\", reply: \"Good.\" },\n  { objection: \"It’s just code.\", reply: \"Exactly.\" },\n];\n\nconst THEMES = {\n  dark: {\n    bg: \"#1C1C1E\",\n    ink: \"#FFFFFF\",\n    muted: \"#9E9EA3\",\n    accent: \"#FFE14D\",\n  },\n  light: {\n    bg: \"#FFFBEA\",\n    ink: \"#17150F\",\n    muted: \"#7A7363\",\n    accent: \"#9A7A00\",\n  },\n} as const;\n\nconst TRACK = -0.03;\nconst LH = 1.06;\nconst WORD = 3;\nconst RISE = 13;\nconst DROP = 14;\nconst REFLOW = 20;\nconst EXIT = 12;\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_IN = Easing.bezier(0.55, 0, 0.85, 0.35);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nfunction breakLines(ws: number[], space: number, maxEm: number) {\n  const n = ws.length;\n  if (n === 0) return [] as number[][];\n  let count = 1;\n  let x = 0;\n  for (let i = 0; i < n; i++) {\n    if (x > 0 && x + space + ws[i] > maxEm) {\n      count++;\n      x = ws[i];\n    } else {\n      x += (x > 0 ? space : 0) + ws[i];\n    }\n  }\n  if (count === 1) return [ws.map((_, i) => i)];\n  const lineW = (i: number, j: number) => {\n    let s = 0;\n    for (let k = i; k < j; k++) s += ws[k] + (k > i ? space : 0);\n    return s;\n  };\n  const avg = lineW(0, n) / count;\n  const INF = 1e12;\n  const dp = Array.from({ length: count + 1 }, () =>\n    new Array<number>(n + 1).fill(INF),\n  );\n  const back = Array.from({ length: count + 1 }, () =>\n    new Array<number>(n + 1).fill(0),\n  );\n  dp[0][0] = 0;\n  for (let l = 1; l <= count; l++) {\n    for (let j = 1; j <= n; j++) {\n      for (let i = l - 1; i < j; i++) {\n        if (dp[l - 1][i] >= INF) continue;\n        const lw = lineW(i, j);\n        if (lw > maxEm + 1e-6 && j - i > 1) continue;\n        const cost = (lw - avg) ** 2;\n        if (dp[l - 1][i] + cost < dp[l][j]) {\n          dp[l][j] = dp[l - 1][i] + cost;\n          back[l][j] = i;\n        }\n      }\n    }\n  }\n  if (dp[count][n] >= INF) return [ws.map((_, i) => i)];\n  const starts: number[] = [];\n  let j = n;\n  for (let l = count; l > 0; l--) {\n    const i = back[l][j];\n    starts.unshift(i);\n    j = i;\n  }\n  return starts.map((s, l) => {\n    const e = l + 1 < starts.length ? starts[l + 1] : n;\n    return Array.from({ length: e - s }, (_, k) => s + k);\n  });\n}\n\nfunction useWidths(specs: string[]) {\n  const key = specs.join(\"\\u0001\");\n  const [state, setState] = useState<{\n    key: string;\n    w: Record<string, number>;\n  } | null>(null);\n  const [handle] = useState(() => delayRender(\"exactly: measuring\"));\n  const released = useRef(false);\n  const ref = useRef(specs);\n  ref.current = specs;\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      hubot.waitUntilDone(),\n      document.fonts.load(`400 100px \"${hubot.fontFamily}\"`),\n      document.fonts.load(`500 100px \"${hubot.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n        const w: Record<string, number> = {};\n        for (const spec of ref.current) {\n          const cut = spec.indexOf(\"|\");\n          const weight = spec.slice(0, cut);\n          const text = spec.slice(cut + 1);\n          if (ctx) ctx.font = `${weight} 100px \"${hubot.fontFamily}\"`;\n          w[spec] = ctx ? ctx.measureText(text).width / 100 : text.length * 0.5;\n        }\n        setState({ key, w });\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.w : null;\n}\n\ninterface Placed {\n  x: number;\n  y: number;\n}\n\nfunction centred(ws: number[], space: number, maxEm: number) {\n  const lines = breakLines(ws, space, maxEm);\n  const placed: Placed[] = new Array(ws.length);\n  let widest = 0;\n  const lineWidths: number[] = [];\n  lines.forEach((idx, line) => {\n    let lw = 0;\n    idx.forEach((k, m) => {\n      lw += ws[k] + (m > 0 ? space : 0);\n    });\n    lineWidths.push(lw);\n    widest = Math.max(widest, lw);\n    let x = -lw / 2;\n    idx.forEach((k, m) => {\n      if (m > 0) x += space;\n      placed[k] = { x, y: line };\n      x += ws[k];\n    });\n  });\n  return { placed, lines: lines.length, widest, lineWidths };\n}\n\nexport function Exactly({\n  rounds = DEFAULT_ROUNDS,\n  accent,\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: ExactlyProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H, durationInFrames: T } = useVideoConfig();\n  const c = THEMES[theme];\n  const hi = accent ?? c.accent;\n  const stage =\n    background === \"transparent\" ? \"transparent\" : (background ?? c.bg);\n\n  const list = rounds.length > 0 ? rounds : DEFAULT_ROUNDS;\n  const objs = list.map((r) => r.objection.trim().split(/\\s+/));\n  const replies = list.map((r) => r.reply.trim());\n  const specs = Array.from(\n    new Set([\n      \"500| \",\n      \"500|“\",\n      \"500|”\",\n      ...objs.flat().map((s) => `500|${s}`),\n      ...replies.map((s) => `500|${s}`),\n    ]),\n  );\n  const w = useWidths(specs);\n\n  if (!w) {\n    return (\n      <div style={{ position: \"absolute\", inset: 0, background: stage }} />\n    );\n  }\n\n  const adv = (s: string) =>\n    (w[`500|${s}`] ?? 0) + TRACK * Array.from(s).length;\n  const space = (w[\"500| \"] ?? 0.25) + 0.04;\n  const openW = adv(\"“\");\n  const closeW = adv(\"”\");\n\n  const portrait = H > W * 1.1;\n  const maxW = W * 0.84;\n  const maxLines = portrait ? 5 : 2;\n  const maxH = H * (portrait ? 0.46 : 0.5);\n\n  const build = (F: number) =>\n    list.map((_, i) => {\n      const ows = objs[i].map(adv);\n      const rw = adv(replies[i]);\n      const full = maxW / F;\n      const inline = full - rw - space;\n      const own = inline < Math.max(...ows) * 1.15;\n      const a = centred(ows, space, own ? full - openW - closeW : inline);\n      const shift = own ? 0 : (rw + space) / 2;\n      const b = a.placed.map((p) => ({\n        x: p.y === 0 ? p.x + shift : p.x,\n        y: own ? p.y + 1 : p.y,\n      }));\n      const replyX = own ? -rw / 2 : -(a.lineWidths[0] + rw + space) / 2;\n      const bLines = own ? a.lines + 1 : a.lines;\n      const bWidest = own\n        ? Math.max(a.widest, rw)\n        : Math.max(a.widest, a.lineWidths[0] + rw + space);\n      return { a, b, own, replyX, bLines, bWidest };\n    });\n\n  let F = portrait ? W * 0.13 : H * 0.15;\n  let lays = build(F);\n  for (let k = 0; k < 80; k++) {\n    const ok = lays.every(\n      (l) =>\n        l.bLines <= maxLines &&\n        (l.a.widest + openW + closeW) * F <= maxW + 0.5 &&\n        l.bWidest * F <= maxW + 0.5 &&\n        l.bLines * LH * F <= maxH,\n    );\n    if (ok) break;\n    F *= 0.97;\n    lays = build(F);\n  }\n  const lineH = LH * F;\n\n  const startAt: number[] = [];\n  const dropAt: number[] = [];\n  let clock = 4;\n  list.forEach((_, i) => {\n    startAt.push(clock);\n    clock += objs[i].length * WORD + RISE + 16;\n    dropAt.push(clock);\n    clock += 6 + REFLOW;\n    clock += i < list.length - 1 ? 30 : 0;\n  });\n  const room = Math.max(1, T - Math.max(30, T * 0.2));\n  const k = clock > room ? room / clock : 1;\n\n  const textStyle = (size: number) => ({\n    position: \"absolute\" as const,\n    fontSize: size,\n    lineHeight: `${lineH}px`,\n    letterSpacing: `${TRACK}em`,\n    whiteSpace: \"pre\" as const,\n  });\n\n  return (\n    <AbsoluteFill\n      style={{\n        background: stage,\n        overflow: \"hidden\",\n        fontFamily: hubot.fontFamily,\n        fontWeight: 500,\n      }}\n    >\n      {list.map((_, i) => {\n        const last = i === list.length - 1;\n        const leaveAt = last ? 1e9 : startAt[i + 1] * k - 4;\n        const leave = interpolate(t, [leaveAt, leaveAt + EXIT], [0, 1], {\n          ...CLAMP,\n          easing: EASE_IN,\n        });\n        if (t < startAt[i] * k - 1 || leave >= 1) return null;\n        const { a, b, own, replyX, bLines } = lays[i];\n        const ayTop = H / 2 - (a.lines * lineH) / 2;\n        const byTop = H / 2 - (bLines * lineH) / 2;\n        const cx = W / 2;\n        const d0 = dropAt[i] * k;\n        const drop = interpolate(t, [d0, d0 + DROP * k], [0, 1], {\n          ...CLAMP,\n          easing: EASE_IN,\n        });\n        const r0 = (dropAt[i] + 6) * k;\n        const flow = interpolate(t, [r0, r0 + REFLOW * k], [0, 1], {\n          ...CLAMP,\n          easing: EASE_IO,\n        });\n        const words = objs[i];\n        const n = words.length;\n        const lagOf = (j: number) =>\n          Math.min(\n            1,\n            Math.max(0, flow * 1.15 - ((n - 1 - j) / Math.max(1, n)) * 0.15),\n          );\n        const posOf = (j: number) => {\n          const l = lagOf(j);\n          const pa = a.placed[j];\n          const pb = b[j];\n          return {\n            x: cx + (pa.x + (pb.x - pa.x) * l) * F,\n            y:\n              ayTop +\n              pa.y * lineH +\n              (byTop + pb.y * lineH - (ayTop + pa.y * lineH)) * l,\n          };\n        };\n        const inAt = (j: number) =>\n          interpolate(\n            t,\n            [(startAt[i] + j * WORD) * k, (startAt[i] + j * WORD + RISE) * k],\n            [0, 1],\n            { ...CLAMP, easing: EASE_OUT },\n          );\n        const first = posOf(0);\n        const lastPos = posOf(n - 1);\n        const replyIn = interpolate(flow, [0.35, 0.9], [0, 1], CLAMP);\n        const reply = own\n          ? {\n              x: cx + replyX * F,\n              y: byTop - (1 - replyIn) * F * 0.2,\n            }\n          : {\n              x: first.x - (space + adv(replies[i])) * F,\n              y: first.y,\n            };\n        const quote = (x: number, y: number, appear: number) => ({\n          ...textStyle(F),\n          left: x,\n          top: y,\n          color: c.muted,\n          opacity: appear * Math.max(0, 1 - drop * 1.6),\n          transform: `translateY(${((1 - appear) * F * 0.18 + drop * F * 0.45).toFixed(2)}px)`,\n        });\n        return (\n          <div\n            key={`r${i}`}\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              opacity: 1 - leave,\n              transform: `translateY(${(-leave * H * 0.06).toFixed(2)}px)`,\n            }}\n          >\n            <div style={quote(first.x - openW * F, first.y, inAt(0))}>“</div>\n            <div\n              style={quote(\n                lastPos.x + adv(words[n - 1]) * F,\n                lastPos.y,\n                inAt(n - 1),\n              )}\n            >\n              ”\n            </div>\n            <div\n              style={{\n                ...textStyle(F),\n                left: reply.x,\n                top: reply.y,\n                color: hi,\n                opacity: replyIn,\n              }}\n            >\n              {replies[i]}\n            </div>\n            {words.map((word, j) => {\n              const e = inAt(j);\n              const p = posOf(j);\n              return (\n                <div\n                  key={`w${j}`}\n                  style={{\n                    ...textStyle(F),\n                    left: p.x,\n                    top: p.y,\n                    color: interpolateColors(flow, [0, 1], [c.muted, c.ink]),\n                    opacity: e,\n                    transform: `translateY(${((1 - e) * F * 0.18).toFixed(2)}px)`,\n                  }}\n                >\n                  {word}\n                </div>\n              );\n            })}\n          </div>\n        );\n      })}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/exactly.tsx"
    }
  ],
  "type": "registry:component"
}
