{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "quadtree-mark",
  "title": "Quadtree Mark",
  "description": "The R mark is built from subdividing pixel squares that refine into a sharp logo.",
  "dependencies": ["remotion", "@remotion/google-fonts", "@remotion/paths"],
  "files": [
    {
      "path": "registry/remocn/quadtree-mark/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { getLength, getPointAtLength, getSubpaths } from \"@remotion/paths\";\nimport { useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\n});\n\nexport interface QuadtreeMarkProps {\n  /** SVG path(s) of the mark to subdivide. Defaults to the remocn \"R\". */\n  logo?: string | string[];\n  /** Text set after the mark. Defaults to \"emocn\", so the lockup reads \"Remocn\". */\n  name?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\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\nconst DEFAULT_MARK = [REMOCN_R_PATH];\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst SNAP = Easing.bezier(0.3, 1.45, 0.5, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n// gradients.sh \"beam\" palette; the third stop follows `accent`.\nconst BEAM = [\"#f4f7ff\", \"#8fb4ff\", \"#3d5bff\", \"#1a1660\"];\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    cell: \"#26262B\",\n    hair: \"rgba(255,255,255,0.12)\",\n    glow: \"rgba(61,91,255,0.13)\",\n    stops: [\n      [0, BEAM[0]],\n      [0.34, BEAM[1]],\n      [0.72, \"accent\"],\n      [1, \"#2a2f9e\"],\n    ],\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    cell: \"#DCDAD4\",\n    hair: \"rgba(18,18,20,0.14)\",\n    glow: \"rgba(255,255,255,0.8)\",\n    stops: [\n      [0, BEAM[1]],\n      [0.42, \"accent\"],\n      [1, BEAM[3]],\n    ],\n    grain: 0.045,\n  },\n} as const;\n\nconst DEPTH = 6;\nconst APPEAR = 10;\nconst SPLIT_STEPS = [14, 9, 7, 6, 5, 5];\nconst OPEN = 6;\nconst RESOLVE = 7;\nconst PAN_AT = 64;\nconst PAN_DUR = 30;\nconst WORD_AT = 74;\nconst MERGE_AT = 82;\nconst MERGE_DUR = 16;\nconst TRACKING = -0.025;\n\n// Manrope 600 advance widths (per 1000 em), lowercase. Measured with HarfBuzz.\nconst ADVANCE: Record<string, number> = {\n  a: 570,\n  b: 608,\n  c: 568,\n  d: 608,\n  e: 596,\n  f: 370,\n  g: 608,\n  h: 615,\n  i: 264,\n  j: 276,\n  k: 524,\n  l: 264,\n  m: 882,\n  n: 615,\n  o: 610,\n  p: 608,\n  q: 608,\n  r: 386,\n  s: 536,\n  t: 422,\n  u: 615,\n  v: 538,\n  w: 790,\n  x: 545,\n  y: 548,\n  z: 532,\n  \" \": 200,\n};\n\nfunction advanceOf(ch: string) {\n  const lower = ch.toLowerCase();\n  const base = ADVANCE[lower] ?? 610;\n  return (ch === lower ? base : base * 1.14) / 1000;\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\ntype Poly = [number, number][];\n\nfunction insideEvenOdd(px: number, py: number, polys: Poly[]) {\n  let inside = false;\n  for (const poly of polys) {\n    for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\n      const [xi, yi] = poly[i];\n      const [xj, yj] = poly[j];\n      if (\n        yi > py !== yj > py &&\n        px < ((xj - xi) * (py - yi)) / (yj - yi) + xi\n      ) {\n        inside = !inside;\n      }\n    }\n  }\n  return inside;\n}\n\nfunction flattenLogo(paths: string[]) {\n  const parts = paths.map((d) => {\n    const polys: Poly[] = [];\n    for (const sub of getSubpaths(d)) {\n      const len = getLength(sub);\n      if (len <= 0) continue;\n      const n = Math.max(24, Math.min(600, Math.ceil(len * 4)));\n      const poly: Poly = [];\n      for (let i = 0; i <= n; i++) {\n        const p = getPointAtLength(sub, (len * i) / n);\n        if (p) poly.push([p.x, p.y]);\n      }\n      polys.push(poly);\n    }\n    return polys;\n  });\n  let minX = Number.POSITIVE_INFINITY;\n  let minY = Number.POSITIVE_INFINITY;\n  let maxX = Number.NEGATIVE_INFINITY;\n  let maxY = Number.NEGATIVE_INFINITY;\n  for (const polys of parts) {\n    for (const poly of polys) {\n      for (const [x, y] of poly) {\n        minX = Math.min(minX, x);\n        minY = Math.min(minY, y);\n        maxX = Math.max(maxX, x);\n        maxY = Math.max(maxY, y);\n      }\n    }\n  }\n  return { parts, minX, minY, w: maxX - minX || 1, h: maxY - minY || 1 };\n}\n\ninterface QNode {\n  c: number;\n  r: number;\n  size: number;\n  depth: number;\n  state: 0 | 1 | 2;\n  birth: number;\n  split: number;\n  resolve: number;\n  quad: number;\n  kids: QNode[];\n}\n\nfunction buildTree(filled: Uint8Array, n: number) {\n  const sum = new Int32Array((n + 1) * (n + 1));\n  for (let r = 0; r < n; r++) {\n    for (let c = 0; c < n; c++) {\n      sum[(r + 1) * (n + 1) + c + 1] =\n        filled[r * n + c] +\n        sum[r * (n + 1) + c + 1] +\n        sum[(r + 1) * (n + 1) + c] -\n        sum[r * (n + 1) + c];\n    }\n  }\n  const count = (c: number, r: number, s: number) =>\n    sum[(r + s) * (n + 1) + c + s] -\n    sum[r * (n + 1) + c + s] -\n    sum[(r + s) * (n + 1) + c] +\n    sum[r * (n + 1) + c];\n\n  const levelAt: number[] = [APPEAR];\n  for (const step of SPLIT_STEPS)\n    levelAt.push(levelAt[levelAt.length - 1] + step);\n\n  let seq = 0;\n  const make = (\n    c: number,\n    r: number,\n    size: number,\n    depth: number,\n    birth: number,\n    quad: number,\n  ): QNode => {\n    seq += 1;\n    const k = count(c, r, size);\n    const state: 0 | 1 | 2 = k === 0 ? 0 : k === size * size ? 1 : 2;\n    const j = hash(c * 7.13 + r * 3.71 + depth * 19.3);\n    const node: QNode = {\n      c,\n      r,\n      size,\n      depth,\n      state,\n      birth,\n      split: Number.POSITIVE_INFINITY,\n      resolve: birth + 2 + j * 3,\n      quad,\n      kids: [],\n    };\n    if (state === 2 && size > 1) {\n      node.split = Math.max(levelAt[depth] + j * 3, birth + OPEN);\n      const h = size / 2;\n      node.kids = [\n        make(c, r, h, depth + 1, node.split, 0),\n        make(c + h, r, h, depth + 1, node.split, 1),\n        make(c, r + h, h, depth + 1, node.split, 2),\n        make(c + h, r + h, h, depth + 1, node.split, 3),\n      ];\n    }\n    return node;\n  };\n  const root = make(0, 0, n, 0, 0, -1);\n  return { root, count: seq };\n}\n\nexport function QuadtreeMark({\n  logo,\n  name = \"emocn\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: QuadtreeMarkProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const pal = THEMES[theme];\n  const isR = !logo;\n\n  const paths = useMemo(\n    () => (logo ? (Array.isArray(logo) ? logo : [logo]) : DEFAULT_MARK),\n    [logo],\n  );\n  const shape = useMemo(() => flattenLogo(paths), [paths]);\n  const N = 2 ** DEPTH;\n\n  const tree = useMemo(() => {\n    const side = Math.max(shape.w, shape.h);\n    const x0 = shape.minX - (side - shape.w) / 2;\n    const y0 = shape.minY - (side - shape.h) / 2;\n    const filled = new Uint8Array(N * N);\n    const S = 3;\n    for (let r = 0; r < N; r++) {\n      for (let c = 0; c < N; c++) {\n        let hits = 0;\n        for (let i = 0; i < S; i++) {\n          for (let j = 0; j < S; j++) {\n            const lx = x0 + ((c + (i + 0.5) / S) / N) * side;\n            const ly = y0 + ((r + (j + 0.5) / S) / N) * side;\n            if (shape.parts.some((polys) => insideEvenOdd(lx, ly, polys)))\n              hits++;\n          }\n        }\n        filled[r * N + c] = hits * 2 > S * S ? 1 : 0;\n      }\n    }\n    return { ...buildTree(filled, N), side };\n  }, [shape, N]);\n\n  const markH = H * 0.46;\n  const pxPerUnit = markH / shape.h;\n  const rootPx = tree.side * pxPerUnit;\n  const leaf = rootPx / N;\n  const markW = shape.w * pxPerUnit;\n  const ox = W / 2 - rootPx / 2;\n  const oy = H / 2 - rootPx / 2;\n  const markLeft = W / 2 - markW / 2;\n  const markTop = H / 2 - markH / 2;\n  const baseline = markTop + markH;\n\n  // remocn.dev lockup: the R is 0.76em tall and stands on the text baseline.\n  const fontSize = isR ? markH / 0.76 : markH * 0.62;\n  const letters = name.split(\"\");\n  const offsets: number[] = [];\n  let run = 0;\n  for (const ch of letters) {\n    offsets.push(run * fontSize);\n    run += advanceOf(ch) + TRACKING;\n  }\n  const wordW = letters.length ? (run - TRACKING) * fontSize : 0;\n  const wordGap = name ? (isR ? 0.04 * fontSize : markH * 0.26) : 0;\n  const groupW = markW + wordGap + wordW;\n  const fit = name ? Math.min(1, (W * 0.74) / groupW) : 1;\n\n  const panE = EASE_IO(\n    interpolate(t, [PAN_AT, PAN_AT + PAN_DUR], [0, 1], CLAMP),\n  );\n  const focusX = W / 2 + (markLeft + groupW / 2 - W / 2) * panE;\n  const sc = 1 + (fit - 1) * panE;\n  const appear = EASE_OUT(interpolate(t, [0, APPEAR + 4], [0, 1], CLAMP));\n  const cam = (1 - appear) * 0.05 + 1;\n\n  const merge = EASE_IO(\n    interpolate(t, [MERGE_AT, MERGE_AT + MERGE_DUR], [0, 1], CLAMP),\n  );\n  const gapStart = Math.max(1.4 * u, leaf * 0.26);\n  const hairline = (1.1 * u) / fit;\n  const gap = gapStart + (hairline - gapStart) * merge;\n\n  const drift = interpolate(t, [0, 130], [0, 1], CLAMP);\n  const angle = ((58 + 10 * drift) * Math.PI) / 180;\n  const gx = markLeft + markW / 2;\n  const gy = markTop + markH / 2;\n  const gLen = Math.max(markW, markH) * 0.62;\n  const gradId = `${uid}-beam`;\n  const clipId = `${uid}-r`;\n  const markTransform = `translate(${markLeft - shape.minX * pxPerUnit} ${markTop - shape.minY * pxPerUnit}) scale(${pxPerUnit})`;\n\n  const rects: React.ReactNode[] = [];\n  const finalRect = (n: QNode) => {\n    const x = ox + n.c * leaf;\n    const y = oy + n.r * leaf;\n    const s = n.size * leaf;\n    return { x: x + gap / 2, y: y + gap / 2, s: s - gap };\n  };\n\n  const visit = (n: QNode, parent: QNode | null) => {\n    if (t < n.birth) return;\n    if (n.kids.length && t >= n.split) {\n      for (const k of n.kids) visit(k, n);\n      return;\n    }\n    const fin = finalRect(n);\n    let { x, y, s } = fin;\n    if (parent) {\n      const pr = finalRect(parent);\n      const half = pr.s / 2;\n      const qx = pr.x + (n.quad % 2) * half;\n      const qy = pr.y + Math.floor(n.quad / 2) * half;\n      const p = SNAP(interpolate(t - n.birth, [0, OPEN], [0, 1], CLAMP));\n      const left = qx + (fin.x - qx) * p;\n      const right = qx + half + (fin.x + fin.s - qx - half) * p;\n      const top = qy + (fin.y - qy) * p;\n      x = left;\n      y = top;\n      s = right - left;\n    }\n    const key = `${n.depth}-${n.c}-${n.r}`;\n    const k = EASE_OUT(interpolate(t - n.resolve, [0, RESOLVE], [0, 1], CLAMP));\n    if (n.state === 0) {\n      if (k >= 1) return;\n      const ss = s * (1 - 0.18 * k);\n      rects.push(\n        <rect\n          key={key}\n          x={x + (s - ss) / 2}\n          y={y + (s - ss) / 2}\n          width={ss}\n          height={ss}\n          fill={pal.cell}\n          opacity={1 - k}\n        />,\n      );\n      return;\n    }\n    if (n.state === 1) {\n      rects.push(\n        <g key={key}>\n          {k < 1 ? (\n            <rect x={x} y={y} width={s} height={s} fill={pal.cell} />\n          ) : null}\n          <rect\n            x={x}\n            y={y}\n            width={s}\n            height={s}\n            fill={`url(#${gradId})`}\n            opacity={k}\n          />\n        </g>,\n      );\n      return;\n    }\n    rects.push(\n      <rect key={key} x={x} y={y} width={s} height={s} fill={pal.cell} />,\n    );\n  };\n  visit(tree.root, null);\n\n  let lattice = \"\";\n  if (merge > 0) {\n    const walk = (n: QNode) => {\n      if (n.kids.length) {\n        for (const k of n.kids) walk(k);\n        return;\n      }\n      const x = ox + n.c * leaf;\n      const y = oy + n.r * leaf;\n      const s = n.size * leaf;\n      lattice += `M${x.toFixed(2)} ${y.toFixed(2)}h${s.toFixed(2)}v${s.toFixed(2)}h${(-s).toFixed(2)}Z`;\n    };\n    walk(tree.root);\n  }\n\n  const frameIn = interpolate(t, [0, 8], [0, 1], CLAMP);\n  const frameOut = interpolate(t, [44, 62], [1, 0], CLAMP);\n  const guide = frameIn * frameOut;\n  const tick = 10 * u;\n  const corners = [\n    [ox - 8 * u, oy - 8 * u, 1, 1],\n    [ox + rootPx + 8 * u, oy - 8 * u, -1, 1],\n    [ox - 8 * u, oy + rootPx + 8 * u, 1, -1],\n    [ox + rootPx + 8 * u, oy + rootPx + 8 * u, -1, -1],\n  ];\n\n  const glowK = interpolate(t, [24, 80], [0, 1], { ...CLAMP, easing: EASE_IO });\n  const wordLeft = markLeft + markW + wordGap;\n\n  return (\n    <AbsoluteFill style={{ background: pal.bg, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: 0,\n          width: W,\n          height: H,\n          transformOrigin: \"0 0\",\n          transform: `translate(${W / 2}px, ${H / 2}px) scale(${sc * cam}) translate(${-focusX}px, ${-H / 2}px)`,\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            left: markLeft + markW / 2 - markH * 1.3,\n            top: H / 2 - markH * 1.1,\n            width: markH * 2.6,\n            height: markH * 2.2,\n            opacity: glowK,\n            background: `radial-gradient(closest-side, ${pal.glow}, transparent)`,\n          }}\n        />\n        <svg\n          width={W}\n          height={H}\n          viewBox={`0 0 ${W} ${H}`}\n          style={{ position: \"absolute\", inset: 0, overflow: \"visible\" }}\n          aria-hidden\n        >\n          <defs>\n            <linearGradient\n              id={gradId}\n              gradientUnits=\"userSpaceOnUse\"\n              x1={gx - Math.cos(angle) * gLen}\n              y1={gy - Math.sin(angle) * gLen}\n              x2={gx + Math.cos(angle) * gLen}\n              y2={gy + Math.sin(angle) * gLen}\n            >\n              {pal.stops.map(([o, c]) => (\n                <stop\n                  key={o}\n                  offset={o}\n                  stopColor={c === \"accent\" ? accent : c}\n                />\n              ))}\n            </linearGradient>\n            <clipPath id={clipId} clipPathUnits=\"userSpaceOnUse\">\n              {paths.map((d) => (\n                <path key={d} d={d} transform={markTransform} />\n              ))}\n            </clipPath>\n          </defs>\n          <g opacity={appear * (1 - merge)}>{rects}</g>\n          {merge > 0 ? (\n            <g clipPath={`url(#${clipId})`} opacity={merge}>\n              <rect\n                x={ox}\n                y={oy}\n                width={rootPx}\n                height={rootPx}\n                fill={`url(#${gradId})`}\n              />\n              <path\n                d={lattice}\n                fill=\"none\"\n                stroke={pal.bg}\n                strokeWidth={gap}\n                opacity={0.55 + 0.45 * (1 - merge)}\n              />\n            </g>\n          ) : null}\n          <g\n            opacity={guide}\n            stroke={pal.hair}\n            strokeWidth={1.25 * u}\n            fill=\"none\"\n          >\n            {corners.map(([cx, cy, dx, dy]) => (\n              <path\n                key={`${cx}-${cy}`}\n                d={`M${cx} ${cy + dy * tick}V${cy}H${cx + dx * tick}`}\n              />\n            ))}\n          </g>\n          {letters.map((ch, i) => {\n            const k = interpolate(t - WORD_AT - i * 2.5, [0, 14], [0, 1], {\n              ...CLAMP,\n              easing: EASE_OUT,\n            });\n            return (\n              <text\n                key={`${uid}-${i}`}\n                x={wordLeft + offsets[i]}\n                y={baseline}\n                opacity={k}\n                transform={`translate(${(1 - k) * 18 * u} 0)`}\n                fill={pal.fg}\n                style={{\n                  fontFamily: manrope.fontFamily,\n                  fontWeight: 600,\n                  fontSize,\n                  whiteSpace: \"pre\",\n                }}\n              >\n                {ch}\n              </text>\n            );\n          })}\n        </svg>\n      </div>\n      <svg\n        width=\"100%\"\n        height=\"100%\"\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          opacity: pal.grain,\n          pointerEvents: \"none\",\n        }}\n        aria-hidden\n      >\n        <filter id={`${uid}-grain`}>\n          <feTurbulence\n            type=\"fractalNoise\"\n            baseFrequency=\"0.9\"\n            numOctaves={2}\n            seed={13}\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(#${uid}-grain)`} />\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/quadtree-mark.tsx"
    }
  ],
  "type": "registry:component"
}
