{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "versus-split",
  "title": "Versus Split",
  "description": "A diagonal split frames remocn against the old way with stat rows for time, cost and rounds.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/versus-split/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/BarlowCondensed\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: condensed } = loadFont(\"normal\", {\n  weights: [\"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: condensedItalic } = loadFont(\"italic\", {\n  weights: [\"500\"],\n  subsets: [\"latin\"],\n});\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"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. */\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\nexport interface VersusStat {\n  /** Small caps label on the seam: \"Time to ship\". */\n  label: string;\n  /** remocn's value, left side. */\n  home: string;\n  /** The opponent's value, right side. */\n  away: string;\n}\n\nexport interface VersusSplitProps {\n  /** The opponent, set in condensed caps on the right half. The left half is always the remocn logo. */\n  away?: string;\n  /** Small label over the logo. */\n  homeLabel?: string;\n  /** Small label over the opponent. */\n  awayLabel?: string;\n  /** Tale-of-the-tape rows, both sides land at once. Three read best. */\n  stats?: VersusStat[];\n  /** The marker on the seam plates. */\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\n// Beat sheet (internal clock = frame + 2, 30fps, speed 1):\n//   0–10   the frame splits on a diagonal: both halves slam in from opposite\n//          sides and meet on the seam, which flashes on impact\n//  12–18   \"VS\" slams down onto the seam from 1.7× (kick on landing)\n//  26–48   stat rows land one by one, 7f apart: the seam plate opens, both\n//          values shoot in from the outer edges at the same time\n//  56–64   the remocn half lights up: a \"halo\" gradient wipes across it from\n//          the left edge; the opponent half and its numbers dim\n//  64–     fight-poster hold\nconst SPLIT_LEN = 10;\nconst VS_AT = 12;\nconst VS_LEN = 7;\nconst ROWS_AT = 26;\nconst ROW_GAP = 7;\nconst ROW_LEN = 9;\nconst LIGHT_DELAY = 10;\nconst LIGHT_LEN = 9;\n\nconst SLAM = Easing.bezier(0.22, 1, 0.36, 1);\nconst WIPE = Easing.bezier(0.55, 0, 0.25, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n// gradients.sh \"halo\": #fff4ec, #ffb36b, #e2457a, #3d4dff.\nconst HALO = [\"#FFF4EC\", \"#FFB36B\", \"#E2457A\", \"#3D4DFF\"] as const;\n\nconst THEMES = {\n  dark: {\n    stage: \"#0A0A0B\",\n    home: \"#1E1E23\",\n    away: \"#121215\",\n    awayOff: \"#0E0E10\",\n    ink: \"#F3F2EF\",\n    dim: \"#86858C\",\n    off: \"#47464D\",\n    plate: \"#0A0A0B\",\n    plateInk: \"#C9C8CE\",\n    seam: \"#FFFFFF\",\n  },\n  light: {\n    stage: \"#F5F4F0\",\n    home: \"#E6E4DE\",\n    away: \"#DDDBD4\",\n    awayOff: \"#ECEAE5\",\n    ink: \"#121214\",\n    dim: \"#7E7D77\",\n    off: \"#B9B7B0\",\n    plate: \"#121214\",\n    plateInk: \"#E9E8E3\",\n    seam: \"#121214\",\n  },\n} as const;\n\nfunction ramp(t: number, from: number, len: number) {\n  return interpolate(t, [from, from + len], [0, 1], CLAMP);\n}\n\n/** A short hit: up in 2 frames, eases back over 9. */\nfunction kick(d: number) {\n  if (d < 0) return 0;\n  if (d < 2) return d / 2;\n  const k = Math.max(0, 1 - (d - 2) / 9);\n  return k * k;\n}\n\nexport function VersusSplit({\n  away = \"The old way\",\n  homeLabel = \"Challenger\",\n  awayLabel = \"Title holder\",\n  stats = [\n    { label: \"Time to ship\", home: \"1 day\", away: \"3 weeks\" },\n    { label: \"Cost\", home: \"$0\", away: \"$12K\" },\n    { label: \"Revisions\", home: \"1 round\", away: \"7 rounds\" },\n  ],\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: VersusSplitProps) {\n  const t = useCurrentFrame() * speed + 2;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const u = Math.min(W / 1280, H / 720);\n\n  // The seam leans right at the top: x = W/2 + lean at y = 0, W/2 - lean at y = H.\n  const lean = 0.14 * H;\n  const seamX = (y: number) => W / 2 + lean * (1 - (2 * y) / H);\n\n  const split = SLAM(ramp(t, 0, SPLIT_LEN));\n  const leftDx = -(1 - split) * 0.75 * W;\n  const rightDx = (1 - split) * 0.75 * W;\n  const seamFlash =\n    t >= SPLIT_LEN - 1 ? Math.max(0, 1 - (t - SPLIT_LEN + 1) / 8) : 0;\n\n  const vs = SLAM(ramp(t, VS_AT, VS_LEN));\n  const vsScale = 1.7 - 0.7 * vs;\n  const vsOpacity = ramp(t, VS_AT, 3);\n\n  const rowsEnd = ROWS_AT + (stats.length - 1) * ROW_GAP + ROW_LEN;\n  const lightAt = rowsEnd + LIGHT_DELAY;\n  const light = WIPE(ramp(t, lightAt, LIGHT_LEN));\n  const dimP = ramp(t, lightAt + 3, 6);\n\n  const bump = Math.max(\n    kick(t - SPLIT_LEN + 1),\n    kick(t - VS_AT - 4),\n    0.6 * kick(t - lightAt - LIGHT_LEN + 2),\n  );\n  const camScale = 1 + 0.02 * bump;\n\n  // The light's front edge runs parallel to the seam, from off the left edge\n  // to just past the seam.\n  const edgeW = 14 * u;\n  const reach = W / 2 + lean + edgeW;\n  const frontAt = (y: number) => seamX(y) - reach + light * (reach + 30 * u);\n  const lit = (x: number, y: number) => light > 0 && frontAt(y) > x;\n\n  const leftPoly = `polygon(0 0, ${seamX(0)}px 0, ${seamX(H)}px ${H}px, 0 ${H}px)`;\n  const rightPoly = `polygon(${seamX(0)}px 0, ${W}px 0, ${W}px ${H}px, ${seamX(H)}px ${H}px)`;\n  const f0 = frontAt(0);\n  const fH = frontAt(H);\n  const lightPoly = `polygon(0 0, ${f0}px 0, ${fH}px ${H}px, 0 ${H}px)`;\n  const edgePoly = `polygon(${f0 - edgeW}px 0, ${f0}px 0, ${fH}px ${H}px, ${fH - edgeW}px ${H}px)`;\n\n  const drift = interpolate(t, [lightAt, lightAt + 60], [0, 8], CLAMP);\n  const haloGround = [\n    `radial-gradient(70% 55% at ${8 + drift}% 104%, ${HALO[1]}f0 0%, transparent 70%)`,\n    `radial-gradient(60% 60% at ${58 - drift}% 78%, ${HALO[2]}e6 0%, transparent 72%)`,\n    `radial-gradient(60% 50% at 10% 0%, #6E7BFFcc 0%, transparent 70%)`,\n    `linear-gradient(165deg, ${HALO[3]} 0%, #2A2FB8 55%, #3A1F7A 100%)`,\n  ].join(\", \");\n\n  // ── Names row ─────────────────────────────────────────────────────────────\n  const nameY = 0.175 * H;\n  const vsY = 0.375 * H;\n  const homeCx = 0.25 * W;\n  const awayCx = 0.755 * W;\n  const labelFs = 20 * u;\n  const homeNameInk = lit(homeCx, nameY) ? \"#FFFFFF\" : c.ink;\n  const homeLabelInk = lit(homeCx, nameY) ? \"#FFFFFFcc\" : c.dim;\n  const awayInk = dimP > 0.5 ? c.off : c.ink;\n\n  // ── Stat rows ─────────────────────────────────────────────────────────────\n  const rowTop = 0.575 * H;\n  const rowStep = Math.min(\n    0.12 * H,\n    (0.95 * H - rowTop) / Math.max(1, stats.length),\n  );\n  const valueFs = 78 * u;\n  const plateW = 210 * u;\n  const plateH = 42 * u;\n  const valueGap = plateW / 2 + 26 * u;\n\n  const label = (text: string, ink: string) => (\n    <div\n      style={{\n        fontFamily: condensed,\n        fontWeight: 500,\n        fontSize: labelFs,\n        letterSpacing: \"0.16em\",\n        textTransform: \"uppercase\",\n        color: ink,\n        lineHeight: 1,\n        marginBottom: 14 * u,\n      }}\n    >\n      {text}\n    </div>\n  );\n\n  return (\n    <AbsoluteFill style={{ background: c.stage, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          transform: `scale(${camScale})`,\n          transformOrigin: \"50% 50%\",\n        }}\n      >\n        {/* Left half: remocn. */}\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            transform: `translateX(${leftDx}px)`,\n          }}\n        >\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              clipPath: leftPoly,\n              background: c.home,\n            }}\n          >\n            {light > 0 ? (\n              <>\n                <div\n                  style={{\n                    position: \"absolute\",\n                    inset: 0,\n                    clipPath: lightPoly,\n                    background: haloGround,\n                  }}\n                />\n                <div\n                  style={{\n                    position: \"absolute\",\n                    inset: 0,\n                    clipPath: edgePoly,\n                    background: HALO[0],\n                    opacity: 1 - light,\n                  }}\n                />\n              </>\n            ) : null}\n          </div>\n          <div\n            style={{\n              position: \"absolute\",\n              left: homeCx - 0.24 * W,\n              width: 0.48 * W,\n              top: nameY - 60 * u,\n              display: \"flex\",\n              flexDirection: \"column\",\n              alignItems: \"center\",\n            }}\n          >\n            {label(homeLabel, homeLabelInk)}\n            <RemocnLogo fontSize={80 * u} color={homeNameInk} />\n          </div>\n        </div>\n\n        {/* Right half: the opponent. */}\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            transform: `translateX(${rightDx}px)`,\n          }}\n        >\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              clipPath: rightPoly,\n              background: dimP > 0 ? c.awayOff : c.away,\n            }}\n          />\n          <div\n            style={{\n              position: \"absolute\",\n              left: awayCx - 0.24 * W,\n              width: 0.48 * W,\n              top: nameY - 60 * u,\n              display: \"flex\",\n              flexDirection: \"column\",\n              alignItems: \"center\",\n            }}\n          >\n            {label(awayLabel, dimP > 0.5 ? c.off : c.dim)}\n            <div\n              style={{\n                fontFamily: condensed,\n                fontWeight: 500,\n                fontSize: 92 * u,\n                lineHeight: 0.84,\n                letterSpacing: \"0.005em\",\n                textTransform: \"uppercase\",\n                color: awayInk,\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              {away}\n            </div>\n          </div>\n        </div>\n\n        {/* Impact flash along the seam. */}\n        {seamFlash > 0 ? (\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              clipPath: `polygon(${seamX(0) - 5 * u}px 0, ${seamX(0) + 5 * u}px 0, ${seamX(H) + 5 * u}px ${H}px, ${seamX(H) - 5 * u}px ${H}px)`,\n              background: c.seam,\n              opacity: seamFlash,\n            }}\n          />\n        ) : null}\n\n        {/* Stat rows. */}\n        {stats.map((s, i) => {\n          const at = ROWS_AT + i * ROW_GAP;\n          const p = SLAM(ramp(t, at, ROW_LEN));\n          const op = ramp(t, at, 3);\n          const plate = SLAM(ramp(t, at, 5));\n          const y = rowTop + (i + 0.5) * rowStep;\n          const sx = seamX(y);\n          const homeX = sx - valueGap;\n          const awayX = sx + valueGap;\n          const homeInk = lit(homeX - 60 * u, y) ? \"#FFFFFF\" : c.ink;\n          const awayValInk = dimP > 0.5 ? c.off : c.ink;\n          return (\n            <div key={`${i}-${s.label}`}>\n              <div\n                style={{\n                  position: \"absolute\",\n                  right: W - homeX,\n                  top: y - valueFs / 2,\n                  height: valueFs,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  transform: `translateX(${-(1 - p) * 0.38 * W}px)`,\n                  opacity: op,\n                  fontFamily: condensed,\n                  fontWeight: 500,\n                  fontSize: valueFs,\n                  lineHeight: 1,\n                  letterSpacing: \"0.005em\",\n                  textTransform: \"uppercase\",\n                  color: homeInk,\n                  whiteSpace: \"nowrap\",\n                  paddingTop: 6 * u,\n                }}\n              >\n                {s.home}\n              </div>\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: awayX,\n                  top: y - valueFs / 2,\n                  height: valueFs,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  transform: `translateX(${(1 - p) * 0.38 * W}px)`,\n                  opacity: op,\n                  fontFamily: condensed,\n                  fontWeight: 500,\n                  fontSize: valueFs,\n                  lineHeight: 1,\n                  letterSpacing: \"0.005em\",\n                  textTransform: \"uppercase\",\n                  color: awayValInk,\n                  whiteSpace: \"nowrap\",\n                  paddingTop: 6 * u,\n                }}\n              >\n                {s.away}\n              </div>\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: sx - plateW / 2,\n                  top: y - plateH / 2,\n                  width: plateW,\n                  height: plateH,\n                  background: c.plate,\n                  clipPath: `inset(0 ${50 * (1 - plate)}% 0 ${50 * (1 - plate)}%)`,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  justifyContent: \"center\",\n                  gap: 10 * u,\n                  fontFamily: condensed,\n                  fontWeight: 500,\n                  fontSize: labelFs,\n                  letterSpacing: \"0.14em\",\n                  textTransform: \"uppercase\",\n                  color: c.plateInk,\n                }}\n              >\n                <div\n                  style={{\n                    width: 4 * u,\n                    height: 16 * u,\n                    background: accent,\n                  }}\n                />\n                <span style={{ paddingTop: 2 * u }}>{s.label}</span>\n              </div>\n            </div>\n          );\n        })}\n\n        {/* VS on the seam. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: seamX(vsY) - 200 * u,\n            width: 400 * u,\n            top: vsY - 120 * u,\n            height: 240 * u,\n            display: \"flex\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            transform: `scale(${vsScale})`,\n            opacity: vsOpacity,\n            fontFamily: condensedItalic,\n            fontStyle: \"italic\",\n            fontWeight: 500,\n            fontSize: 250 * u,\n            lineHeight: 1,\n            letterSpacing: \"-0.01em\",\n            color: theme === \"dark\" ? \"#FFFFFF\" : c.ink,\n            paddingTop: 12 * u,\n          }}\n        >\n          VS\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/versus-split.tsx"
    }
  ],
  "type": "registry:component"
}
