{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "verbed",
  "title": "Verbed",
  "description": "A dictionary entry for \"remocn\" with senses, inflections and an example sentence.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/verbed/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Bitter\";\nimport type React from \"react\";\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 roman = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst italic = loadFont(\"italic\", { weights: [\"400\"], subsets: [\"latin\"] });\n\nexport interface VerbedProps {\n  headword?: string;\n  pronunciation?: string;\n  nounLabel?: string;\n  verbLabel?: string;\n  nounSense?: string;\n  verbSense?: string;\n  inflections?: string[];\n  example?: string;\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: { bg: \"#0F1B3D\", ink: \"#F4EFE6\", accent: \"#FF8A3D\" },\n  light: { bg: \"#F4EFE6\", ink: \"#0F1B3D\", accent: \"#C85A12\" },\n} as const;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst HEAD_TRACK = -0.035;\nconst BODY_TRACK = -0.005;\nconst LH = 1.32;\nconst MOTION_END = 168;\nconst DIM = 0.5;\nconst BACK = 0.74;\n\ntype Face = \"head\" | \"body\" | \"italic\";\n\nfunction fontOf(face: Face) {\n  if (face === \"head\") return `500 100px \"${roman.fontFamily}\"`;\n  if (face === \"italic\") return `italic 400 100px \"${italic.fontFamily}\"`;\n  return `400 100px \"${roman.fontFamily}\"`;\n}\n\nfunction trackOf(face: Face) {\n  return face === \"head\" ? HEAD_TRACK : BODY_TRACK;\n}\n\nfunction useFontsReady() {\n  const [ready, setReady] = useState(false);\n  const [handle] = useState(() => delayRender(\"verbed: fonts\"));\n  const released = useRef(false);\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      roman.waitUntilDone(),\n      italic.waitUntilDone(),\n      document.fonts.load(fontOf(\"head\")),\n      document.fonts.load(fontOf(\"body\")),\n      document.fonts.load(fontOf(\"italic\")),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (live) setReady(true);\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [handle]);\n  return ready;\n}\n\nfunction useMeasure() {\n  const ctx = useRef<CanvasRenderingContext2D | null>(null);\n  const cache = useRef(new Map<string, number>());\n  return (text: string, face: Face) => {\n    const key = `${face}\\u0001${text}`;\n    const hit = cache.current.get(key);\n    if (hit !== undefined) return hit;\n    if (!ctx.current)\n      ctx.current = document.createElement(\"canvas\").getContext(\"2d\");\n    let w = text.length * 0.5;\n    if (ctx.current) {\n      ctx.current.font = fontOf(face);\n      w = ctx.current.measureText(text).width / 100;\n    }\n    const em = w + trackOf(face) * Array.from(text).length;\n    cache.current.set(key, em);\n    return em;\n  };\n}\n\nfunction greedy(\n  words: string[],\n  wOf: (s: string) => number,\n  space: number,\n  max: number,\n) {\n  const lines: string[][] = [];\n  let line: string[] = [];\n  let x = 0;\n  for (const word of words) {\n    const w = wOf(word);\n    if (line.length > 0 && x + space + w > max) {\n      lines.push(line);\n      line = [word];\n      x = w;\n    } else {\n      x += (line.length > 0 ? space : 0) + w;\n      line.push(word);\n    }\n  }\n  if (line.length > 0) lines.push(line);\n  return lines;\n}\n\nfunction balanced(\n  words: string[],\n  wOf: (s: string) => number,\n  space: number,\n  max: number,\n) {\n  const base = greedy(words, wOf, space, max);\n  if (base.length <= 1) return base;\n  let best = base;\n  for (let k = 1; k <= 30; k++) {\n    const next = greedy(words, wOf, space, max * (1 - k * 0.015));\n    if (next.length !== base.length) break;\n    best = next;\n  }\n  return best;\n}\n\nfunction splitWords(s: string) {\n  return s.trim().split(/\\s+/).filter(Boolean);\n}\n\nfunction curly(s: string) {\n  const body = s\n    .trim()\n    .replace(/^[\"“”]+/, \"\")\n    .replace(/[\"“”]+$/, \"\");\n  return `“${body}”`;\n}\n\nexport function Verbed({\n  headword = \"remocn\",\n  pronunciation = \"/rem-uh-kun/\",\n  nounLabel = \"noun\",\n  verbLabel = \"verb\",\n  nounSense = \"a set of video scenes you copy into your repo and edit like any other code.\",\n  verbSense = \"to make the launch film in the same pull request as the feature.\",\n  inflections = [\"remocns\", \"remocned\", \"remocning\"],\n  example = \"We remocned the changelog before lunch.\",\n  accent,\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: VerbedProps) {\n  const frame = 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  const ready = useFontsReady();\n  const measure = useMeasure();\n\n  if (!ready) {\n    return (\n      <div style={{ position: \"absolute\", inset: 0, background: stage }} />\n    );\n  }\n\n  const tail = Math.max(30, T * 0.16);\n  const pace = Math.min(1.5, Math.max(0.5, (T - tail) / MOTION_END));\n  const t = frame / pace;\n\n  const portrait = H > W * 1.1;\n  const mx = W * (portrait ? 0.08 : 0.07);\n  const my = H * (portrait ? 0.1 : 0.085);\n  const blockW = W * (portrait ? 0.84 : 0.8);\n  const availH = H - my * 2;\n\n  const space = measure(\" \", \"body\");\n  const inflWords = inflections\n    .map((w) => w.trim())\n    .filter(Boolean)\n    .map((w, i, all) => (i < all.length - 1 ? `${w} ·` : w));\n  const exampleText = curly(example);\n  const nounWords = splitWords(nounSense);\n  const verbWords = splitWords(verbSense);\n  const exampleWords = splitWords(exampleText);\n  const stem = headword.trim().toLowerCase();\n\n  let Fh = portrait ? W * 0.27 : H * 0.26;\n  let Fd = portrait ? W * 0.078 : H * 0.066;\n  const layout = () => {\n    const Fl = Fd;\n    const Fp = Fd * 0.82;\n    const Fi = Fd * 0.78;\n    const numW = Fd * 1.5;\n    const textW = (blockW - numW) / Fd;\n    const headW = measure(headword, \"head\") * Fh;\n    const labelFull = `${nounLabel}, ${verbLabel}`;\n    const sideW =\n      Fh * 0.22 +\n      measure(pronunciation, \"body\") * Fp +\n      Fd * 0.5 +\n      measure(labelFull, \"italic\") * Fl;\n    const stacked = headW + sideW > W - mx * 2;\n    const nounLines = balanced(\n      nounWords,\n      (s) => measure(s, \"body\"),\n      space,\n      textW,\n    );\n    const verbLines = balanced(\n      verbWords,\n      (s) => measure(s, \"body\"),\n      space,\n      textW,\n    );\n    const exLines = balanced(\n      exampleWords,\n      (s) => measure(s, \"italic\"),\n      measure(\" \", \"italic\"),\n      textW,\n    );\n    const lineH = Fd * LH;\n    const headH = Fh * 1.08 + (stacked ? Fl * 1.5 : 0);\n    const gap = Fd * 0.9;\n    const nounH = nounLines.length * lineH;\n    const verbH = verbLines.length * lineH;\n    const inflLines = balanced(\n      inflWords,\n      (s) => measure(s, \"italic\"),\n      measure(\" \", \"italic\"),\n      (textW * Fd) / Fi,\n    );\n    const inflLineH = Fi * 1.3;\n    const inflH =\n      inflLines.length > 0 ? inflLines.length * inflLineH + Fi * 0.25 : 0;\n    const exH = exLines.length * lineH;\n    const total =\n      headH + gap + nounH * BACK + Fd * 0.6 + verbH + inflH + Fd * 0.2 + exH;\n    return {\n      Fl,\n      Fp,\n      Fi,\n      numW,\n      headW,\n      stacked,\n      nounLines,\n      verbLines,\n      exLines,\n      lineH,\n      headH,\n      gap,\n      nounH,\n      verbH,\n      inflH,\n      inflLines,\n      inflLineH,\n      total,\n    };\n  };\n  let L = layout();\n  for (let k = 0; k < 40 && (L.total > availH || L.headW > W - mx * 2); k++) {\n    Fh *= 0.96;\n    Fd *= 0.97;\n    L = layout();\n  }\n\n  const rise = (start: number, dur = 16) =>\n    interpolate(t, [start, start + dur], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n\n  const word = (\n    text: string,\n    key: string,\n    p: number,\n    size: number,\n    extra?: React.CSSProperties,\n  ) => (\n    <span\n      key={key}\n      style={{\n        display: \"inline-block\",\n        whiteSpace: \"pre\",\n        opacity: p,\n        transform: `translateY(${((1 - p) * size * 0.4).toFixed(2)}px)`,\n        ...extra,\n      }}\n    >\n      {text}\n    </span>\n  );\n\n  const TURN = 84;\n  const turn = interpolate(t, [TURN, TURN + 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const verbIn = rise(TURN + 4, 18);\n\n  const headP = rise(0, 20);\n  const pronP = rise(8, 16);\n  const labelP = rise(12, 16);\n\n  const headTop = my;\n  const sensesTop = headTop + L.headH + L.gap;\n  const verbTop = sensesTop + L.nounH * BACK + Fd * 0.6;\n  const inflTop = verbTop + L.verbH;\n  const exTop = inflTop + L.inflH + Fd * 0.2;\n\n  const nounStart = 20;\n  const verbStart = TURN + 12;\n  const inflStart = verbStart + verbWords.length * 2 + 10;\n  const exStart = inflStart + 14;\n\n  const sideRow = (\n    <>\n      <span\n        style={{\n          fontSize: L.Fp,\n          letterSpacing: `${BODY_TRACK}em`,\n          opacity: pronP * 0.58,\n          marginLeft: L.stacked ? 0 : Fh * 0.22,\n          marginRight: Fd * 0.5,\n          whiteSpace: \"pre\",\n        }}\n      >\n        {pronunciation}\n      </span>\n      <span\n        style={{\n          fontFamily: italic.fontFamily,\n          fontStyle: \"italic\",\n          fontWeight: 400,\n          fontSize: L.Fl,\n          letterSpacing: `${BODY_TRACK}em`,\n          whiteSpace: \"pre\",\n          opacity: labelP,\n          display: \"inline-flex\",\n          alignItems: \"baseline\",\n        }}\n      >\n        <span>{nounLabel}</span>\n        <span\n          style={{\n            display: \"inline-block\",\n            color: hi,\n            opacity: turn,\n            transform: `translateX(${((1 - turn) * Fd * 0.3).toFixed(2)}px)`,\n          }}\n        >\n          {`, ${verbLabel}`}\n        </span>\n      </span>\n    </>\n  );\n\n  const nounScale = 1 - (1 - BACK) * turn;\n  const nounShift = (L.numW * turn) / nounScale;\n  const nounAlpha = 1 - (1 - DIM) * turn;\n\n  const lines = (\n    rows: string[][],\n    top: number,\n    left: number,\n    start: number,\n    keyBase: string,\n    face: Face,\n    colorOf?: (w: string) => string,\n  ) => {\n    let n = 0;\n    return rows.map((row, li) => (\n      <div\n        key={`${keyBase}${li}`}\n        style={{\n          position: \"absolute\",\n          left,\n          top: top + li * L.lineH,\n          height: L.lineH,\n          lineHeight: `${L.lineH}px`,\n          whiteSpace: \"nowrap\",\n          fontSize: Fd,\n          letterSpacing: `${trackOf(face)}em`,\n          fontFamily: face === \"italic\" ? italic.fontFamily : roman.fontFamily,\n          fontStyle: face === \"italic\" ? \"italic\" : \"normal\",\n          fontWeight: 400,\n        }}\n      >\n        {row.map((w, wi) => {\n          const p = rise(start + n++ * 2, 14);\n          return word(\n            wi < row.length - 1 ? `${w} ` : w,\n            `${keyBase}${li}-${wi}`,\n            p,\n            Fd,\n            colorOf ? { color: colorOf(w) } : undefined,\n          );\n        })}\n      </div>\n    ));\n  };\n\n  const isForm = (w: string) =>\n    stem.length > 0 &&\n    w\n      .toLowerCase()\n      .replace(/^[“\"‘']+/, \"\")\n      .startsWith(stem);\n\n  const numStyle: React.CSSProperties = {\n    position: \"absolute\",\n    left: mx,\n    height: L.lineH,\n    lineHeight: `${L.lineH}px`,\n    fontSize: Fd,\n    fontWeight: 500,\n    whiteSpace: \"pre\",\n  };\n\n  return (\n    <AbsoluteFill\n      style={{\n        background: stage,\n        color: c.ink,\n        fontFamily: roman.fontFamily,\n        fontWeight: 400,\n        overflow: \"hidden\",\n      }}\n    >\n      <div\n        style={{\n          position: \"absolute\",\n          left: mx,\n          top: headTop,\n          display: \"flex\",\n          flexDirection: L.stacked ? \"column\" : \"row\",\n          alignItems: L.stacked ? \"flex-start\" : \"baseline\",\n          lineHeight: 1.08,\n        }}\n      >\n        <span\n          style={{\n            fontSize: Fh,\n            fontWeight: 500,\n            letterSpacing: `${HEAD_TRACK}em`,\n            whiteSpace: \"pre\",\n            opacity: headP,\n            transform: `translateY(${((1 - headP) * Fh * 0.18).toFixed(2)}px)`,\n          }}\n        >\n          {headword}\n        </span>\n        {L.stacked ? (\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"baseline\",\n              marginTop: Fd * 0.2,\n              lineHeight: 1.2,\n            }}\n          >\n            {sideRow}\n          </div>\n        ) : (\n          sideRow\n        )}\n      </div>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: mx,\n          top: sensesTop,\n          width: 0,\n          height: 0,\n          transform: `scale(${nounScale.toFixed(4)})`,\n          transformOrigin: \"0 0\",\n          opacity: nounAlpha,\n        }}\n      >\n        <div\n          style={{\n            ...numStyle,\n            left: 0,\n            top: 0,\n            opacity: turn,\n            transform: `translateX(${((1 - turn) * -Fd * 0.4).toFixed(2)}px)`,\n          }}\n        >\n          1.\n        </div>\n        {lines(L.nounLines, 0, nounShift, nounStart, \"n\", \"body\")}\n      </div>\n\n      <div\n        style={{\n          ...numStyle,\n          top: verbTop,\n          color: hi,\n          opacity: verbIn,\n          transform: `translateY(${((1 - verbIn) * Fd * 0.4).toFixed(2)}px)`,\n        }}\n      >\n        2.\n      </div>\n      {lines(L.verbLines, verbTop, mx + L.numW, verbStart, \"v\", \"body\")}\n\n      {L.inflLines.map((row, li) => (\n        <div\n          key={`i${li}`}\n          style={{\n            position: \"absolute\",\n            left: mx + L.numW,\n            top: inflTop + li * L.inflLineH,\n            height: L.inflLineH,\n            lineHeight: `${L.inflLineH}px`,\n            fontFamily: italic.fontFamily,\n            fontStyle: \"italic\",\n            fontSize: L.Fi,\n            letterSpacing: `${BODY_TRACK}em`,\n            whiteSpace: \"pre\",\n            opacity: rise(inflStart + li * 3, 16) * 0.62,\n          }}\n        >\n          {row.join(\" \")}\n        </div>\n      ))}\n\n      {lines(L.exLines, exTop, mx + L.numW, exStart, \"e\", \"italic\", (w) =>\n        isForm(w)\n          ? interpolateColors(rise(exStart + 12, 16), [0, 1], [c.ink, hi])\n          : c.ink,\n      )}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/verbed.tsx"
    }
  ],
  "type": "registry:component"
}
