{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "feather-settle",
  "title": "Feather Settle",
  "description": "\"Your launch, landing softly.\" settles in as if falling like a feather on a taupe ground.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/feather-settle/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Lexend\";\nimport { useEffect, 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 lexend = loadFont(\"normal\", {\n  weights: [\"300\"],\n  subsets: [\"latin\"],\n});\n\nexport interface FeatherSettleProps {\n  text?: string;\n  highlight?: string;\n  stagger?: number;\n  fall?: number;\n  sway?: number;\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: { bg: \"#3A2F25\", ink: \"#EFE6D8\", accent: \"#9DBF9E\" },\n  light: { bg: \"#EFE6D8\", ink: \"#3A2F25\", accent: \"#5E8B60\" },\n} as const;\n\nconst FIRST = 4;\nconst CYCLES = 1.55;\nconst TILT = 7;\n\nconst DRIFT = Easing.bezier(0.3, 0.05, 0.25, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\ninterface Metrics {\n  words: number[];\n  space: number;\n  baseline: number;\n}\n\nfunction measure(words: string[]): Metrics {\n  const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n  if (!ctx) {\n    return {\n      words: words.map((w) => w.length * 0.55),\n      space: 0.26,\n      baseline: 0.8,\n    };\n  }\n  ctx.font = `300 100px \"${lexend.fontFamily}\"`;\n  const fm = ctx.measureText(\"H\");\n  const asc = fm.fontBoundingBoxAscent / 100;\n  const desc = fm.fontBoundingBoxDescent / 100;\n  return {\n    words: words.map((w) => ctx.measureText(w).width / 100),\n    space: ctx.measureText(\" \").width / 100,\n    baseline: (1 - (asc + desc)) / 2 + asc,\n  };\n}\n\nfunction useMetrics(words: string[]) {\n  const key = words.join(\"\\u0001\");\n  const [state, setState] = useState<{ key: string; m: Metrics } | null>(null);\n  const [handle] = useState(() => delayRender(\"feather-settle: measuring\"));\n  const released = useRef(false);\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      lexend.waitUntilDone(),\n      document.fonts.load(`300 100px \"${lexend.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        setState({ key, m: measure(key.split(\"\\u0001\")) });\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.m : null;\n}\n\nfunction hash(n: number) {\n  const s = Math.sin(n * 127.1 + 311.7) * 43758.5453;\n  return s - Math.floor(s);\n}\n\nexport function FeatherSettle({\n  text = \"Your launch, landing softly.\",\n  highlight = \"softly\",\n  stagger = 13,\n  fall = 68,\n  sway = 1,\n  accent,\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: FeatherSettleProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const spot = accent ?? c.accent;\n  const stage =\n    background === \"transparent\" ? \"transparent\" : (background ?? c.bg);\n  const words = useMemo(() => text.split(/\\s+/).filter(Boolean), [text]);\n  const m = useMetrics(words);\n\n  if (!m) {\n    return (\n      <div style={{ position: \"absolute\", inset: 0, background: stage }} />\n    );\n  }\n\n  const portrait = H > W * 1.1;\n  const margin = portrait ? W * 0.08 : W * 0.1;\n  const room = W - margin * 2;\n  const lineEm =\n    m.words.reduce((a, w) => a + w, 0) +\n    m.space * Math.max(0, words.length - 1);\n  const F = Math.min(\n    room / Math.max(0.5, lineEm),\n    H * (portrait ? 0.06 : 0.12),\n  );\n  const lineW = lineEm * F;\n  const baseY = H * (portrait ? 0.78 : 0.82);\n  const x0 = (W - lineW) / 2;\n  const hl = highlight\n    .trim()\n    .toLowerCase()\n    .replace(/[^a-z0-9]/g, \"\");\n\n  let x = x0;\n  const placed = words.map((w, i) => {\n    const left = x;\n    x += m.words[i] * F + m.space * F;\n    return { w, left, width: m.words[i] * F };\n  });\n\n  return (\n    <AbsoluteFill style={{ background: stage, overflow: \"hidden\" }}>\n      {placed.map((p, i) => {\n        const start = FIRST + i * stagger;\n        const k = interpolate(t, [start, start + fall], [0, 1], CLAMP);\n        const drop = DRIFT(k);\n        const height = H * (portrait ? 0.5 : 0.6) * (0.9 + hash(i) * 0.2);\n        const y = -height * (1 - drop);\n        const amp = F * 0.85 * sway * (1 - k) ** 1.6;\n        const phase = hash(i + 7) * Math.PI * 2;\n        const dir = i % 2 === 0 ? 1 : -1;\n        const angle = Math.PI * 2 * CYCLES * k + phase;\n        const dx = dir * amp * Math.sin(angle);\n        const tilt = dir * TILT * sway * (1 - k) ** 1.6 * Math.cos(angle);\n        const seen = interpolate(k, [0, 0.22], [0, 1], {\n          ...CLAMP,\n          easing: EASE_OUT,\n        });\n        const lit =\n          hl.length > 0 && p.w.toLowerCase().replace(/[^a-z0-9]/g, \"\") === hl;\n        return (\n          <div\n            key={i}\n            style={{\n              position: \"absolute\",\n              left: p.left,\n              top: baseY - m.baseline * F,\n              width: p.width,\n              height: F,\n              lineHeight: `${F}px`,\n              fontFamily: lexend.fontFamily,\n              fontWeight: 300,\n              fontSize: F,\n              letterSpacing: \"-0.01em\",\n              whiteSpace: \"pre\",\n              color: lit ? spot : c.ink,\n              opacity: seen,\n              transformOrigin: \"50% 0%\",\n              transform: `translate(${dx.toFixed(2)}px, ${y.toFixed(2)}px) rotate(${tilt.toFixed(3)}deg)`,\n            }}\n          >\n            {p.w}\n          </div>\n        );\n      })}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/feather-settle.tsx"
    }
  ],
  "type": "registry:component"
}
