{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "time-breakdown",
  "title": "Time Breakdown",
  "description": "A segmented time bar shrinks while 56% less time per video counts up.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/time-breakdown/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { loadFont } from \"@remotion/google-fonts/SchibstedGrotesk\";\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 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\n// gradients.sh \"aurora\": soft curtains over a near-black base, plus the\n// horizontal band. Lives only inside the freed-time panel.\nconst BRAND_TAIL = /remocn$/i;\n\nconst AURORA = [\"#A0F0C8\", \"#3FB5A3\", \"#5147B6\", \"#B25C9C\"];\n\nfunction auroraFill(base: string, drift: number) {\n  const n = AURORA.length;\n  const curtains = AURORA.map((c, i) => {\n    const x = 12 + (76 * i) / (n - 1) + Math.sin(drift + i * 1.9) * 4;\n    const h = 150 + Math.cos(drift * 0.8 + i * 1.3) * 20;\n    return `radial-gradient(34% ${h}% at ${x}% 100%, color-mix(in oklab, ${c} 80%, transparent) 0%, transparent 72%)`;\n  });\n  return [\n    ...curtains,\n    `linear-gradient(180deg, color-mix(in oklab, ${AURORA[0]} 18%, transparent) 0%, transparent 60%)`,\n    base,\n  ].join(\", \");\n}\n\nexport interface TimeSegment {\n  label: string;\n  value: number;\n}\n\nexport interface TimeBreakdownProps {\n  before?: TimeSegment[];\n  after?: TimeSegment[];\n  title?: string;\n  beforeLabel?: string;\n  afterLabel?: string;\n  caption?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst DEFAULT_BEFORE: TimeSegment[] = [\n  { label: \"Editing\", value: 62 },\n  { label: \"Rendering\", value: 24 },\n  { label: \"Export\", value: 14 },\n];\n\nconst DEFAULT_AFTER: TimeSegment[] = [\n  { label: \"Editing\", value: 6 },\n  { label: \"Rendering\", value: 24 },\n  { label: \"Export\", value: 14 },\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 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.3)\",\n    base: \"#4A494F\",\n    tones: [\"#77767C\", \"#4E4D53\", \"#36353A\", \"#2A292E\"],\n    stage: \"rgba(255,255,255,0.05)\",\n    aurora: \"#03050B\",\n    auroraEdge: \"rgba(255,255,255,0.14)\",\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.32)\",\n    base: \"#BDBBB4\",\n    tones: [\"#8E8C86\", \"#B3B1AA\", \"#CFCDC6\", \"#DEDCD6\"],\n    stage: \"rgba(255,255,255,0.7)\",\n    aurora: \"#FFFFFF\",\n    auroraEdge: \"rgba(18,18,20,0.10)\",\n    grain: 0.045,\n  },\n} as const;\n\nconst T_SPLIT = 22;\nconst T_SWITCH = 66;\nconst T_COLLAPSE = 74;\nconst COLLAPSE_LEN = 36;\nconst T_DONE = T_COLLAPSE + COLLAPSE_LEN;\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={23}\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  return (\n    <div\n      style={{\n        display: \"flex\",\n        whiteSpace: \"pre\",\n        fontFamily: sans,\n        fontWeight: 400,\n        fontSize: size,\n        lineHeight: 1,\n        letterSpacing: \"-0.045em\",\n        fontVariantNumeric: \"tabular-nums\",\n        color,\n      }}\n    >\n      {Array.from(text).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 TimeBreakdown({\n  before = DEFAULT_BEFORE,\n  after = DEFAULT_AFTER,\n  title = \"Where the time goes\",\n  beforeLabel = \"Before\",\n  afterLabel = \"With remocn\",\n  caption = \"less time per video\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: TimeBreakdownProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height } = useVideoConfig();\n  const u = Math.min(width / 1280, height / 720);\n  const th = THEMES[theme];\n  const clipId = `h${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n  const segs = before.slice(0, 5).map((b, i) => ({\n    label: b.label,\n    from: Math.max(0, b.value),\n    to: Math.max(0, after[i]?.value ?? b.value),\n  }));\n  const totalBefore = segs.reduce((s, x) => s + x.from, 0) || 1;\n  const totalAfter = segs.reduce((s, x) => s + x.to, 0);\n  const saved = Math.max(0, Math.round((1 - totalAfter / totalBefore) * 100));\n  const focus = segs.reduce(\n    (best, s, i) =>\n      s.from - s.to > segs[best].from - segs[best].to ? i : best,\n    0,\n  );\n\n  const margin = width * 0.085;\n  const x0 = margin;\n  const barW = width - margin * 2;\n  const barH = 84 * u;\n  const barTop = height * 0.52;\n  const radius = 12 * u;\n\n  const reveal = interpolate(t, [-4, 24], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const split = interpolate(t, [T_SPLIT, T_SPLIT + 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const gap = 6 * u * split;\n  const sw = interpolate(t, [T_SWITCH, T_SWITCH + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const tint = interpolate(t, [T_COLLAPSE - 4, T_COLLAPSE + 6], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const c = interpolate(t, [T_COLLAPSE, T_DONE], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  const usable = barW - gap * (segs.length - 1);\n  const unitW = usable / totalBefore;\n  const geom: { x: number; w: number; v: number }[] = [];\n  let cursor = x0;\n  segs.forEach((s) => {\n    const v = s.from + (s.to - s.from) * c;\n    const w = v * unitW;\n    geom.push({ x: cursor, w, v });\n    cursor += w + gap;\n  });\n  const usedEnd = cursor - gap;\n  const freedX = usedEnd + gap;\n  const freedW = Math.max(0, x0 + barW - freedX);\n  const freedIn = interpolate(t, [T_COLLAPSE + 6, T_COLLAPSE + 22], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const finalFreedX =\n    x0 + segs.reduce((s, x) => s + x.to * unitW, 0) + 6 * u * segs.length;\n\n  const labelEnds: number[] = [];\n  const labelX = geom.map((g, i) => {\n    const min = i > 0 ? labelEnds[i - 1] + 18 * u : g.x;\n    const x = Math.max(g.x, min);\n    // Schibsted Grotesk advances: ~0.53em lowercase at 18px, ~0.6em digits at 42px.\n    const nameW = segs[i].label.length * 0.53 * 18 * u;\n    const pctW =\n      (String(Math.round((g.v / totalBefore) * 100)).length + 1) * 0.6 * 42 * u;\n    labelEnds.push(x + Math.max(nameW, pctW));\n    return x;\n  });\n\n  const heroT = t - (T_DONE - 4);\n  const heroSize = Math.min(160 * u, width * 0.125);\n  const capIn = interpolate(heroT, [12, 32], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const bracketIn = interpolate(heroT, [-6, 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const titleIn = interpolate(t, [-6, 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const drift = interpolate(t, [0, T_DONE + 60], [1.025, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  const bracketY = barTop - 22 * u;\n  const auroraDrift = interpolate(\n    t,\n    [T_COLLAPSE, T_DONE + 80],\n    [0, 1.6],\n    CLAMP,\n  );\n  const tick = 8 * u;\n\n  return (\n    <AbsoluteFill style={{ background: th.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 70% 70% at 40% 45%, ${th.stage}, transparent 75%)`,\n        }}\n      />\n      <AbsoluteFill\n        style={{ transform: `scale(${drift})`, transformOrigin: \"50% 55%\" }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            left: x0,\n            top: height * 0.2,\n            fontFamily: sans,\n            fontSize: 22 * u,\n            fontWeight: 500,\n            color: th.muted,\n            whiteSpace: \"nowrap\",\n            opacity: titleIn,\n            transform: `translateY(${(1 - titleIn) * 8 * u}px)`,\n          }}\n        >\n          {title}\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: x0,\n            top: barTop - 70 * u,\n            width: barW * 0.45,\n            height: 40 * u,\n            opacity: titleIn,\n          }}\n        >\n          {[beforeLabel, afterLabel].map((lbl, i) => {\n            const a =\n              i === 0\n                ? interpolate(sw, [0, 0.6], [1, 0], CLAMP)\n                : interpolate(sw, [0.35, 1], [0, 1], CLAMP);\n            const y = i === 0 ? -sw * 14 * u : (1 - sw) * 14 * u;\n            return (\n              <div\n                key={lbl}\n                style={{\n                  position: \"absolute\",\n                  top: 0,\n                  left: 0,\n                  height: 40 * u,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  gap: 10 * u,\n                  opacity: a,\n                  transform: `translateY(${y}px)`,\n                  filter: a < 0.98 ? `blur(${(1 - a) * 4 * u}px)` : undefined,\n                  fontFamily: sans,\n                  fontSize: 30 * u,\n                  fontWeight: 400,\n                  letterSpacing: \"-0.025em\",\n                  color: th.fg,\n                  whiteSpace: \"nowrap\",\n                }}\n              >\n                {i === 1 && BRAND_TAIL.test(lbl) ? (\n                  <>\n                    {lbl.replace(BRAND_TAIL, \"\").trim()}\n                    <span style={{ marginLeft: 2 * u, marginTop: 2 * u }}>\n                      <RemocnLogo fontSize={31 * u} color={th.fg} />\n                    </span>\n                  </>\n                ) : (\n                  <>\n                    {i === 1 ? (\n                      <span\n                        style={{\n                          width: 10 * u,\n                          height: 10 * u,\n                          borderRadius: \"50%\",\n                          background: accent,\n                        }}\n                      />\n                    ) : null}\n                    {lbl}\n                  </>\n                )}\n              </div>\n            );\n          })}\n        </div>\n\n        {freedW > 1 ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: freedX,\n              top: barTop,\n              width: freedW,\n              height: barH,\n              borderRadius: radius,\n              overflow: \"hidden\",\n              opacity: freedIn,\n              background: auroraFill(th.aurora, auroraDrift),\n              boxShadow: `inset 0 0 0 ${Math.max(1, u)}px ${th.auroraEdge}`,\n            }}\n          />\n        ) : null}\n\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0, overflow: \"visible\" }}\n          aria-hidden\n        >\n          <defs>\n            <clipPath id={`${clipId}c`}>\n              <rect\n                x={x0}\n                y={barTop - 4 * u}\n                width={barW * reveal}\n                height={barH + 8 * u}\n              />\n            </clipPath>\n          </defs>\n\n          <g clipPath={`url(#${clipId}c)`}>\n            {geom.map((g, i) => {\n              const tone = th.tones[Math.min(i, th.tones.length - 1)];\n              const split3 = interpolateColors(split, [0, 1], [th.base, tone]);\n              const fill =\n                i === focus\n                  ? interpolateColors(tint, [0, 1], [split3, accent])\n                  : split3;\n              const first = i === 0;\n              const last = i === geom.length - 1;\n              const rl = first ? radius : radius * split * 0.35;\n              const rr = last ? radius : radius * split * 0.35;\n              const w = Math.max(g.w, 2 * u);\n              const rlc = Math.min(rl, w / 2);\n              const rrc = Math.min(rr, w / 2);\n              const x = g.x;\n              const y = barTop;\n              const h = barH;\n              const d = [\n                `M ${x + rlc} ${y}`,\n                `H ${x + w - rrc}`,\n                `Q ${x + w} ${y} ${x + w} ${y + rrc}`,\n                `V ${y + h - rrc}`,\n                `Q ${x + w} ${y + h} ${x + w - rrc} ${y + h}`,\n                `H ${x + rlc}`,\n                `Q ${x} ${y + h} ${x} ${y + h - rlc}`,\n                `V ${y + rlc}`,\n                `Q ${x} ${y} ${x + rlc} ${y}`,\n                \"Z\",\n              ].join(\" \");\n              return <path key={segs[i].label} d={d} fill={fill} />;\n            })}\n          </g>\n\n          {saved > 0 ? (\n            <g\n              opacity={bracketIn}\n              stroke={th.hair}\n              strokeWidth={Math.max(1, 1.25 * u)}\n            >\n              <line\n                x1={finalFreedX}\n                x2={finalFreedX + (x0 + barW - finalFreedX) * bracketIn}\n                y1={bracketY}\n                y2={bracketY}\n              />\n              <line\n                x1={finalFreedX}\n                x2={finalFreedX}\n                y1={bracketY - tick}\n                y2={bracketY + tick}\n              />\n              <line\n                x1={x0 + barW}\n                x2={x0 + barW}\n                y1={bracketY - tick}\n                y2={bracketY + tick}\n                opacity={interpolate(bracketIn, [0.85, 1], [0, 1], CLAMP)}\n              />\n            </g>\n          ) : null}\n        </svg>\n\n        {geom.map((g, i) => {\n          const a = interpolate(\n            t,\n            [T_SPLIT + 6 + i * 4, T_SPLIT + 26 + i * 4],\n            [0, 1],\n            {\n              ...CLAMP,\n              easing: EASE_OUT,\n            },\n          );\n          const countIn = interpolate(\n            t,\n            [T_SPLIT + 6 + i * 4, T_SPLIT + 34 + i * 4],\n            [0, 1],\n            { ...CLAMP, easing: Easing.bezier(0.22, 1, 0.36, 1) },\n          );\n          const isFocus = i === focus;\n          const pct = Math.round((g.v / totalBefore) * 100 * countIn);\n          return (\n            <div\n              key={segs[i].label}\n              style={{\n                position: \"absolute\",\n                left: labelX[i],\n                top: barTop + barH + 20 * u,\n                display: \"flex\",\n                flexDirection: \"column\",\n                gap: 2 * u,\n                opacity: a,\n                transform: `translateY(${(1 - a) * 10 * u}px)`,\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              <span\n                style={{\n                  fontFamily: sans,\n                  fontSize: 18 * u,\n                  fontWeight: 500,\n                  color: isFocus\n                    ? interpolateColors(tint, [0, 1], [th.muted, accent])\n                    : th.muted,\n                }}\n              >\n                {segs[i].label}\n              </span>\n              <span\n                style={{\n                  fontFamily: sans,\n                  fontSize: 42 * u,\n                  fontWeight: 400,\n                  letterSpacing: \"-0.035em\",\n                  fontVariantNumeric: \"tabular-nums\",\n                  color: th.fg,\n                }}\n              >\n                {pct}%\n              </span>\n            </div>\n          );\n        })}\n\n        {saved > 0 ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: finalFreedX - heroSize * 0.04,\n              top: bracketY - 22 * u,\n              transform: \"translateY(-100%)\",\n              display: \"flex\",\n              alignItems: \"baseline\",\n              gap: 18 * u,\n            }}\n          >\n            <Hero text={`${saved}%`} t={heroT} size={heroSize} color={th.fg} />\n            <span\n              style={{\n                fontFamily: sans,\n                fontSize: 32 * u,\n                fontWeight: 400,\n                letterSpacing: \"-0.02em\",\n                color: th.muted,\n                whiteSpace: \"nowrap\",\n                opacity: capIn,\n                transform: `translateY(${(1 - capIn) * 10 * u}px)`,\n              }}\n            >\n              {caption}\n            </span>\n          </div>\n        ) : null}\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 85% 85% at 50% 50%, transparent 60%, ${th.bg} 100%)`,\n          opacity: 0.55,\n        }}\n      />\n      <Grain opacity={th.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/time-breakdown.tsx"
    }
  ],
  "type": "registry:component"
}
