{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "area-compare",
  "title": "Area Compare",
  "description": "Two discs compare bundle sizes while 14x smaller settles in big type.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/area-compare/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/InstrumentSans\";\nimport { loadFont as loadDisplay } from \"@remotion/google-fonts/InstrumentSerif\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  interpolateColors,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: display } = loadDisplay(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nloadDisplay(\"italic\", { weights: [\"400\"], subsets: [\"latin\"] });\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"500\", \"600\"],\n  subsets: [\"latin\"],\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/** Full logo as on remocn.dev: R mark (0.76em tall, baseline-aligned) + \"emocn\" in Manrope 600, tracking-tight. */\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.fontFamily,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\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 isBrand = (label: string) => label.trim().toLowerCase() === \"remocn\";\n\n// gradients.sh \"mesh\" palette, laid flat: a diagonal ramp with two drifting\n// blobs, so the shape reads as a printed swatch rather than a lit sphere.\nconst MESH = [\"#ECDCF2\", \"#8796EE\", \"#2E45B7\", \"#0E1645\"];\n\nfunction meshFill(accent: string, drift: number) {\n  const ax = 18 + Math.sin(drift * 1.3) * 6;\n  const bx = 82 + Math.cos(drift * 1.1) * 6;\n  return [\n    `radial-gradient(70% 60% at ${ax}% 16%, color-mix(in oklab, ${MESH[0]} 55%, transparent) 0%, transparent 70%)`,\n    `radial-gradient(80% 70% at ${bx}% 92%, ${MESH[3]} 0%, transparent 72%)`,\n    `linear-gradient(${150 + drift * 6}deg, ${MESH[1]} 0%, ${accent} 44%, ${MESH[2]} 74%, ${MESH[3]} 100%)`,\n  ].join(\", \");\n}\n\nexport interface AreaCompareProps {\n  label?: string;\n  before?: string;\n  after?: string;\n  beforeValue?: number;\n  afterValue?: number;\n  unit?: string;\n  verdict?: string;\n  shape?: \"circle\" | \"square\";\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\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_SHRINK = Easing.bezier(0.7, 0, 0.22, 1.04);\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    faint: \"#5A595F\",\n    hair: \"rgba(255,255,255,0.28)\",\n    fillTop: \"#3A393F\",\n    fillBottom: \"#2A292E\",\n    onFill: \"#F3F2EF\",\n    stage: \"rgba(255,255,255,0.05)\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    faint: \"#A3A099\",\n    hair: \"rgba(18,18,20,0.3)\",\n    fillTop: \"#DEDCD6\",\n    fillBottom: \"#CFCDC6\",\n    onFill: \"#121214\",\n    stage: \"rgba(255,255,255,0.7)\",\n    grain: 0.045,\n  },\n} as const;\n\nconst T_IN = 0;\nconst T_SHRINK = 38;\nconst SHRINK_LEN = 40;\nconst T_LAND = T_SHRINK + SHRINK_LEN;\n\nfunction groupThousands(n: number) {\n  return String(Math.round(n)).replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n}\n\nfunction formatValue(v: number, unit: string) {\n  if (unit === \"KB\" && v >= 1000) return `${(v / 1000).toFixed(1)} MB`;\n  if (unit === \"MB\" && v >= 1000) return `${(v / 1000).toFixed(1)} GB`;\n  if (v < 10 && Math.abs(v - Math.round(v)) > 0.01) {\n    return `${v.toFixed(1)} ${unit}`.trim();\n  }\n  return `${groupThousands(v)} ${unit}`.trim();\n}\n\nfunction formatRatio(r: number) {\n  if (r >= 10) return `${Math.round(r)}×`;\n  const s = r.toFixed(1);\n  return `${s.endsWith(\".0\") ? s.slice(0, -2) : s}×`;\n}\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={41}\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\nfunction Hero({\n  text,\n  t,\n  size,\n  color,\n}: {\n  text: string;\n  t: number;\n  size: number;\n  color: string;\n}) {\n  const chars = Array.from(text);\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        whiteSpace: \"pre\",\n        fontFamily: display,\n        fontWeight: 400,\n        fontSize: size,\n        lineHeight: 1,\n        letterSpacing: \"-0.03em\",\n        fontVariantNumeric: \"tabular-nums\",\n        color,\n      }}\n    >\n      {chars.map((ch, i) => {\n        const p = interpolate(t, [i * 3, i * 3 + 22], [0, 1], {\n          ...CLAMP,\n          easing: EASE_OUT,\n        });\n        return (\n          <span\n            key={i}\n            style={{\n              display: \"inline-block\",\n              overflow: \"hidden\",\n              paddingBottom: size * 0.06,\n              marginBottom: -size * 0.06,\n            }}\n          >\n            <span\n              style={{\n                display: \"inline-block\",\n                transform: `translateY(${(1 - p) * 0.7 * size}px)`,\n                opacity: interpolate(p, [0, 0.4], [0, 1], CLAMP),\n              }}\n            >\n              {ch}\n            </span>\n          </span>\n        );\n      })}\n    </div>\n  );\n}\n\nexport function AreaCompare({\n  label = \"Bundle size\",\n  before = \"Before\",\n  after = \"remocn\",\n  beforeValue = 1200,\n  afterValue = 84,\n  unit = \"KB\",\n  verdict = \"smaller\",\n  shape = \"circle\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: AreaCompareProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height } = useVideoConfig();\n  const u = Math.min(width / 1280, height / 720);\n  const p = THEMES[theme];\n\n  const big = Math.max(beforeValue, afterValue, 0.0001);\n  const small = Math.max(Math.min(beforeValue, afterValue), 0.0001);\n  const ratio = big / small;\n  const square = shape === \"square\";\n\n  const R0 = height * (square ? 0.33 : 0.36);\n  const Rf = R0 * Math.sqrt(small / big);\n\n  const enter = interpolate(t, [T_IN - 8, T_IN + 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const s = interpolate(t, [T_SHRINK, T_LAND], [0, 1], {\n    ...CLAMP,\n    easing: EASE_SHRINK,\n  });\n  const sLin = interpolate(t, [T_SHRINK, T_LAND], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const r = R0 + (Rf - R0) * s;\n  const value = beforeValue + (afterValue - beforeValue) * sLin;\n\n  const glide = interpolate(t, [T_SHRINK + 6, T_LAND + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const margin = width * 0.085;\n  const gxEnd = margin + R0 + 14 * u;\n  const gx = width / 2 + (gxEnd - width / 2) * glide;\n  const gy = height / 2 + 6 * u;\n  const bottom = gy + R0;\n  const cy = bottom - r;\n\n  const ghostIn = interpolate(t, [T_SHRINK, T_SHRINK + 10], [0, 1], CLAMP);\n  const tagIn = interpolate(t, [T_SHRINK + 14, T_SHRINK + 30], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const tint = interpolate(s, [0.2, 0.62], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const meshDrift = interpolate(t, [0, T_LAND + 90], [0, 1.4], CLAMP);\n  const onFill = interpolateColors(\n    interpolate(tint, [0.38, 0.58], [0, 1], CLAMP),\n    [0, 1],\n    [p.onFill, \"#FFFFFF\"],\n  );\n\n  const valueSize = Math.max(30 * u, 104 * u * (r / R0));\n  const fitsInside = Rf * 2 >= 118 * u;\n  const insideAlpha = fitsInside\n    ? 1\n    : interpolate(s, [0.55, 0.8], [1, 0], CLAMP);\n  const eyebrowAlpha = interpolate(t, [T_SHRINK - 2, T_SHRINK + 10], [1, 0], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const afterTagIn = interpolate(t, [T_LAND - 4, T_LAND + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const outsideIn = fitsInside\n    ? 0\n    : interpolate(t, [T_LAND - 6, T_LAND + 12], [0, 1], {\n        ...CLAMP,\n        easing: EASE_OUT,\n      });\n\n  const heroT = t - (T_LAND + 2);\n  const heroSize = Math.min(250 * u, width * 0.19);\n  const textX = gxEnd + R0 + width * 0.07;\n  const textW = width - margin - textX;\n  const lineIn = interpolate(heroT, [10, 30], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const eyeIn = interpolate(heroT, [-4, 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const breathe = interpolate(t, [0, T_LAND + 90], [1.025, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const cornerR = (side: number) => side * 0.1;\n\n  const shapePath = (cx: number, ccy: number, rad: number) =>\n    square\n      ? {\n          x: cx - rad,\n          y: ccy - rad,\n          w: rad * 2,\n          h: rad * 2,\n          rx: cornerR(rad * 2),\n        }\n      : { x: cx - rad, y: ccy - rad, w: rad * 2, h: rad * 2, rx: rad };\n\n  const ghost = shapePath(gx, gy, R0);\n  const body = shapePath(gx, cy, r);\n  const enterScale = 0.96 + 0.04 * enter;\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 60% 70% at ${(gx / width) * 100}% 40%, ${p.stage}, transparent 75%)`,\n        }}\n      />\n      <AbsoluteFill\n        style={{\n          transform: `scale(${breathe})`,\n          transformOrigin: \"50% 50%\",\n        }}\n      >\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0, overflow: \"visible\" }}\n          aria-hidden\n        >\n          <rect\n            x={ghost.x}\n            y={ghost.y}\n            width={ghost.w}\n            height={ghost.h}\n            rx={ghost.rx}\n            fill=\"none\"\n            stroke={p.hair}\n            strokeWidth={Math.max(1, 1.25 * u)}\n            opacity={ghostIn}\n          />\n        </svg>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: body.x,\n            top: body.y,\n            width: body.w,\n            height: body.h,\n            borderRadius: body.rx,\n            overflow: \"hidden\",\n            opacity: enter,\n            transform: `scale(${enterScale})`,\n            transformOrigin: `${gx - body.x}px ${bottom - body.y}px`,\n            background: `linear-gradient(180deg, ${p.fillTop}, ${p.fillBottom})`,\n          }}\n        >\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              opacity: tint,\n              background: meshFill(accent, meshDrift),\n            }}\n          />\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: gx,\n            top: gy - R0 + 34 * u,\n            transform: \"translateX(-50%)\",\n            display: \"flex\",\n            alignItems: \"baseline\",\n            gap: 10 * u,\n            whiteSpace: \"nowrap\",\n            fontFamily: sans,\n            fontSize: 18 * u,\n            fontWeight: 500,\n            color: p.muted,\n            opacity: tagIn,\n          }}\n        >\n          <span>{before}</span>\n          <span style={{ color: p.faint }}>·</span>\n          <span\n            style={{\n              color: p.fg,\n              fontWeight: 500,\n              fontVariantNumeric: \"tabular-nums\",\n            }}\n          >\n            {formatValue(beforeValue, unit)}\n          </span>\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: gx,\n            top: cy,\n            transform: `translate(-50%, -50%) scale(${enterScale})`,\n            display: \"flex\",\n            flexDirection: \"column\",\n            alignItems: \"center\",\n            opacity: enter * insideAlpha,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          <span\n            style={{\n              fontFamily: sans,\n              fontSize: 22 * u,\n              fontWeight: 500,\n              color: p.muted,\n              opacity: eyebrowAlpha,\n              marginBottom: 6 * u * eyebrowAlpha,\n              height: 28 * u * eyebrowAlpha,\n              overflow: \"hidden\",\n            }}\n          >\n            {label}\n          </span>\n          <span\n            style={{\n              fontFamily: display,\n              fontWeight: 400,\n              fontSize: valueSize * 1.12,\n              lineHeight: 1,\n              letterSpacing: \"-0.02em\",\n              fontVariantNumeric: \"tabular-nums\",\n              color: onFill,\n            }}\n          >\n            {formatValue(value, unit)}\n          </span>\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: gx,\n            top: bottom - Rf * 2 - 14 * u,\n            transform: `translate(-50%, -100%) translateY(${(1 - afterTagIn) * 6 * u}px)`,\n            fontFamily: sans,\n            fontSize: 18 * u,\n            fontWeight: 500,\n            color: p.fg,\n            opacity: afterTagIn * (fitsInside ? 1 : 0),\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {isBrand(after) ? (\n            <RemocnLogo fontSize={22 * u} color={p.fg} />\n          ) : (\n            after\n          )}\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: gx + Rf + 16 * u,\n            top: bottom - Rf,\n            transform: `translateY(-50%) translateX(${(1 - outsideIn) * -6 * u}px)`,\n            display: \"flex\",\n            flexDirection: \"column\",\n            gap: 2 * u,\n            opacity: outsideIn,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          <span\n            style={{\n              fontFamily: display,\n              fontWeight: 400,\n              fontSize: 34 * u,\n              letterSpacing: \"-0.02em\",\n              fontVariantNumeric: \"tabular-nums\",\n              color: p.fg,\n            }}\n          >\n            {formatValue(afterValue, unit)}\n          </span>\n          {isBrand(after) ? (\n            <RemocnLogo fontSize={20 * u} color={p.fg} />\n          ) : (\n            <span\n              style={{\n                fontFamily: sans,\n                fontSize: 18 * u,\n                fontWeight: 500,\n                color: p.fg,\n              }}\n            >\n              {after}\n            </span>\n          )}\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: textX,\n            top: gy,\n            width: textW,\n            transform: \"translateY(-50%)\",\n            display: \"flex\",\n            flexDirection: \"column\",\n            alignItems: \"flex-start\",\n          }}\n        >\n          <span\n            style={{\n              fontFamily: sans,\n              fontSize: 22 * u,\n              fontWeight: 500,\n              color: p.muted,\n              opacity: eyeIn,\n              transform: `translateY(${(1 - eyeIn) * 8 * u}px)`,\n              marginBottom: 18 * u,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {label}\n          </span>\n          <div style={{ marginLeft: -heroSize * 0.05 }}>\n            <Hero\n              text={formatRatio(ratio)}\n              t={heroT}\n              size={heroSize}\n              color={p.fg}\n            />\n          </div>\n          <span\n            style={{\n              fontFamily: display,\n              fontStyle: \"italic\",\n              fontWeight: 400,\n              fontSize: 88 * u,\n              lineHeight: 1,\n              letterSpacing: \"-0.02em\",\n              color: p.muted,\n              opacity: lineIn,\n              transform: `translateY(${(1 - lineIn) * 14 * u}px)`,\n              filter:\n                lineIn < 0.98 ? `blur(${(1 - lineIn) * 6 * u}px)` : undefined,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {verdict}\n          </span>\n        </div>\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 85% 85% at 50% 50%, transparent 60%, ${p.bg} 100%)`,\n          opacity: 0.55,\n        }}\n      />\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/area-compare.tsx"
    }
  ],
  "type": "registry:component"
}
