{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ko-count",
  "title": "Ko Count",
  "description": "A knockout count ends in a full-frame gradient slam, 'KNOCKOUT', with a serif subline.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/ko-count/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/BigShoulders\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/Newsreader\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: shoulders } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: serif } = loadSerif(\"italic\", {\n  weights: [\"400\"],\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 KoCountProps {\n  /** The full-frame word that slams in after the count. */\n  word?: string;\n  /** Serif italic line under the word. */\n  subline?: string;\n  /** Small label next to the count, bottom left. */\n  label?: string;\n  /** The count ticks. */\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\n// Beat sheet (internal clock = frame + 1, 30fps, speed 1):\n//   0–56   the count: 1 → 10, a hard cut on every number, gaps tightening\n//          8 → 4 frames. Every numeral is a different crop, scale and tilt and\n//          lands with a small slam; the camera kick grows with the count. 8 is\n//          an inverted frame, 10 fills with the \"fluted\" gradient\n//  64–69   hard cut: the word slams in from 1.6× with echoes\n//  69–71   impact: two frames of solid gradient ground with the word knocked\n//          out, then a decaying shake\n//  80–98   the serif subline wipes in, the logo rises; poster hold\nconst GAPS = [8, 8, 7, 7, 6, 6, 5, 5, 4];\nconst HOLD_TEN = 8;\nconst SLAM_LEN = 5;\nconst IMPACT_LEN = 2;\nconst SUB_DELAY = 11;\nconst LOGO_DELAY = 19;\n\nconst SLAM = Easing.bezier(0.12, 0.9, 0.25, 1);\nconst OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n// gradients.sh \"fluted\": bg #050507, [#1d2a8f, #2f7bff, #ff7a3d, #ffe2c4].\nconst FLUTED = [\"#1D2A8F\", \"#2F7BFF\", \"#FF7A3D\", \"#FFE2C4\"] as const;\n\nconst THEMES = {\n  dark: {\n    stage: \"#08080A\",\n    ink: \"#F3F2EF\",\n    dim: \"#86858C\",\n    off: \"#2A2A30\",\n    fill: (a: number) =>\n      `linear-gradient(${a}deg, ${FLUTED[1]} 0%, #6A63E6 32%, ${FLUTED[2]} 68%, ${FLUTED[3]} 100%)`,\n    ground: (a: number) =>\n      `linear-gradient(${a}deg, ${FLUTED[0]} 0%, ${FLUTED[1]} 35%, ${FLUTED[2]} 75%, ${FLUTED[3]} 100%)`,\n    impactInk: \"#08080A\",\n  },\n  light: {\n    stage: \"#F5F4F0\",\n    ink: \"#121214\",\n    dim: \"#7E7D77\",\n    off: \"#DAD8D1\",\n    fill: (a: number) =>\n      `linear-gradient(${a}deg, ${FLUTED[0]} 0%, ${FLUTED[1]} 38%, #C8552B 72%, ${FLUTED[2]} 100%)`,\n    ground: (a: number) =>\n      `linear-gradient(${a}deg, ${FLUTED[0]} 0%, ${FLUTED[1]} 40%, ${FLUTED[2]} 80%, ${FLUTED[3]} 100%)`,\n    impactInk: \"#FFFFFF\",\n  },\n} as const;\n\n// Big Shoulders 500: cap/figure height 0.8em, cap top 0.0855em below a\n// line-height-1 box. Figures advance ~0.35em, \"1\" a little less.\nconst CAP = 0.8;\nconst CAP_TOP = 0.0855;\nconst FIG = 0.36;\n\n// Each count is its own shot: size (× height), centre, tilt.\nconst SHOTS = [\n  { fs: 1.05, x: 0.5, y: 0.5, r: 0 },\n  { fs: 1.45, x: 0.33, y: 0.58, r: 0 },\n  { fs: 0.82, x: 0.7, y: 0.47, r: -3 },\n  { fs: 1.9, x: 0.56, y: 0.36, r: 0 },\n  { fs: 1.25, x: 0.3, y: 0.52, r: -5 },\n  { fs: 2.2, x: 0.64, y: 0.64, r: 4 },\n  { fs: 1.4, x: 0.48, y: 0.5, r: -7 },\n  { fs: 2.6, x: 0.4, y: 0.46, r: 6 },\n  { fs: 1.75, x: 0.6, y: 0.56, r: -9 },\n  { fs: 1.22, x: 0.5, y: 0.52, r: -6 },\n] as const;\n\nconst WORDS = [\n  \"one\",\n  \"two\",\n  \"three\",\n  \"four\",\n  \"five\",\n  \"six\",\n  \"seven\",\n  \"eight\",\n  \"nine\",\n  \"ten\",\n];\n\nfunction ramp(t: number, from: number, len: number) {\n  return interpolate(t, [from, from + len], [0, 1], CLAMP);\n}\n\n/** Decaying camera shake that starts at `at`, stepped per frame. */\nfunction shake(t: number, at: number, len: number, amp: number) {\n  const d = Math.floor(t - at);\n  if (d < 0 || d >= len) return { x: 0, y: 0 };\n  const k = (1 - d / len) ** 2;\n  return {\n    x: amp * k * Math.sin(d * 2.4 + 0.6),\n    y: amp * 0.6 * k * Math.cos(d * 3.1 + 0.3),\n  };\n}\n\nexport function KoCount({\n  word = \"Knockout\",\n  subline = \"The old workflow did not get up.\",\n  label = \"The count\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: KoCountProps) {\n  const t = useCurrentFrame() * speed + 1;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const u = Math.min(W / 1280, H / 720);\n\n  const countAt: number[] = [0];\n  for (const g of GAPS) countAt.push(countAt[countAt.length - 1] + g);\n  const koAt = countAt[9] + HOLD_TEN;\n  const hitAt = koAt + SLAM_LEN;\n  const subAt = hitAt + SUB_DELAY;\n  const logoAt = hitAt + LOGO_DELAY;\n\n  let idx = 0;\n  for (let i = 0; i < 10; i++) if (t >= countAt[i]) idx = i;\n  const counting = t < koAt;\n  const local = t - countAt[idx];\n\n  // ── Camera: a kick on every count, growing; a big one on the word ─────────\n  const kickAmp = (3 + idx * 1.4) * u;\n  const sk = counting ? shake(t, countAt[idx] + 2, 5, kickAmp) : { x: 0, y: 0 };\n  const sh = shake(t, hitAt, 14, 22 * u);\n  const camX = sk.x + sh.x;\n  const camY = sk.y + sh.y;\n\n  const shot = SHOTS[idx];\n  const numFs = shot.fs * H;\n  const numSlam = SLAM(ramp(local, 0, 3));\n  const numScale = (1.16 - 0.16 * numSlam) * (1 + 0.035 * ramp(local, 3, 10));\n  const inverted = counting && idx === 7;\n  const stage = inverted ? c.ink : c.stage;\n  const numInk = inverted ? c.stage : c.ink;\n  const isTen = idx === 9;\n  const digits = String(idx + 1);\n  const angle = 100 + 14 * Math.sin(t / 30);\n\n  // ── The word ──────────────────────────────────────────────────────────────\n  const letters = word.length;\n  const wordFs = Math.min((0.9 * W) / (letters * 0.45), 0.56 * H);\n  const wordCapH = CAP * wordFs;\n  const wordCy = 0.44 * H;\n  const wordScale = (d: number) =>\n    (1.6 - 0.6 * SLAM(ramp(d, koAt, SLAM_LEN))) *\n    (1 + 0.025 * ramp(d, hitAt, 60));\n  const impact = t >= hitAt && t < hitAt + IMPACT_LEN * speed;\n  const subP = OUT(ramp(t, subAt, 12));\n  const logoP = OUT(ramp(t, logoAt, 10));\n\n  const hud = (ink: string, dim: string) => (\n    <>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 48 * u,\n          bottom: 40 * u,\n          display: \"flex\",\n          alignItems: \"baseline\",\n          gap: 16 * u,\n        }}\n      >\n        <div\n          style={{\n            fontFamily: shoulders,\n            fontWeight: 500,\n            fontSize: 22 * u,\n            letterSpacing: \"0.26em\",\n            textTransform: \"uppercase\",\n            color: dim,\n          }}\n        >\n          {label}\n        </div>\n        <div\n          style={{\n            fontFamily: serif,\n            fontStyle: \"italic\",\n            fontWeight: 400,\n            fontSize: 40 * u,\n            letterSpacing: \"-0.01em\",\n            color: ink,\n          }}\n        >\n          {WORDS[idx]}\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          right: 48 * u,\n          bottom: 48 * u,\n          display: \"flex\",\n          gap: 7 * u,\n        }}\n      >\n        {WORDS.map((w, i) => (\n          <div\n            key={w}\n            style={{\n              width: 14 * u,\n              height: 30 * u,\n              transform: \"skewX(-18deg)\",\n              background:\n                i < idx\n                  ? dim\n                  : i === idx\n                    ? isTen\n                      ? c.fill(100)\n                      : accent\n                    : c.off,\n              opacity: i < idx && inverted ? 0.6 : 1,\n            }}\n          />\n        ))}\n      </div>\n    </>\n  );\n\n  return (\n    <AbsoluteFill style={{ background: c.stage, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          background: counting ? stage : impact ? c.ground(angle) : c.stage,\n        }}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          transform: `translate(${camX}px, ${camY}px)`,\n        }}\n      >\n        {counting ? (\n          <>\n            {/* The numeral, cropped by the frame. */}\n            <div\n              style={{\n                position: \"absolute\",\n                left: shot.x * W - W,\n                width: 2 * W,\n                top: shot.y * H - CAP_TOP * numFs - (CAP * numFs) / 2,\n                display: \"flex\",\n                justifyContent: \"center\",\n                transform: `rotate(${shot.r}deg) scale(${numScale})`,\n                transformOrigin: `50% ${CAP_TOP * numFs + (CAP * numFs) / 2}px`,\n              }}\n            >\n              <div\n                style={{\n                  fontFamily: shoulders,\n                  fontWeight: 500,\n                  fontSize: numFs,\n                  lineHeight: 1,\n                  letterSpacing: \"-0.03em\",\n                  fontVariantNumeric: \"tabular-nums\",\n                  whiteSpace: \"nowrap\",\n                  color: isTen ? \"transparent\" : numInk,\n                  backgroundImage: isTen ? c.fill(angle) : undefined,\n                  WebkitBackgroundClip: isTen ? \"text\" : undefined,\n                  backgroundClip: isTen ? \"text\" : undefined,\n                  width: digits.length * FIG * numFs * 1.1,\n                  textAlign: \"center\",\n                }}\n              >\n                {digits}\n              </div>\n            </div>\n            {hud(numInk, inverted ? c.off : c.dim)}\n          </>\n        ) : (\n          <>\n            {/* The word, with echoes trailing the slam. */}\n            {[4, 2, 0].map((lag) => {\n              const ghost = lag > 0;\n              if (ghost && t >= hitAt) return null;\n              const tt = t - lag;\n              const op = ramp(tt, koAt, 2) * (ghost ? 0.22 / lag : 1);\n              return (\n                <div\n                  key={lag}\n                  style={{\n                    position: \"absolute\",\n                    left: 0,\n                    width: W,\n                    top: wordCy - CAP_TOP * wordFs - wordCapH / 2,\n                    display: \"flex\",\n                    justifyContent: \"center\",\n                    transform: `scale(${wordScale(tt)})`,\n                    transformOrigin: `50% ${CAP_TOP * wordFs + wordCapH / 2}px`,\n                    opacity: op,\n                  }}\n                >\n                  <div\n                    style={{\n                      fontFamily: shoulders,\n                      fontWeight: 500,\n                      fontSize: wordFs,\n                      lineHeight: 1,\n                      letterSpacing: \"-0.01em\",\n                      textTransform: \"uppercase\",\n                      whiteSpace: \"nowrap\",\n                      color: impact || ghost ? c.impactInk : \"transparent\",\n                      backgroundImage:\n                        impact || ghost ? undefined : c.fill(angle),\n                      WebkitBackgroundClip:\n                        impact || ghost ? undefined : \"text\",\n                      backgroundClip: impact || ghost ? undefined : \"text\",\n                      ...(ghost ? { color: c.ink } : {}),\n                      paddingRight: 0.02 * wordFs,\n                    }}\n                  >\n                    {word}\n                  </div>\n                </div>\n              );\n            })}\n\n            {/* Subline wipes in from the left. */}\n            {subP > 0 ? (\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: 0,\n                  width: W,\n                  top: wordCy + wordCapH / 2 + 34 * u,\n                  display: \"flex\",\n                  justifyContent: \"center\",\n                  fontFamily: serif,\n                  fontStyle: \"italic\",\n                  fontWeight: 400,\n                  fontSize: 50 * u,\n                  lineHeight: 1.1,\n                  letterSpacing: \"-0.01em\",\n                  color: c.ink,\n                  clipPath: `inset(-20% ${100 - 100 * subP}% -20% 0)`,\n                  transform: `translateX(${(1 - subP) * -20 * u}px)`,\n                }}\n              >\n                {subline}\n              </div>\n            ) : null}\n\n            {logoP > 0 ? (\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: 0,\n                  width: W,\n                  bottom: 44 * u,\n                  display: \"flex\",\n                  justifyContent: \"center\",\n                  opacity: logoP,\n                  transform: `translateY(${(1 - logoP) * 14 * u}px)`,\n                }}\n              >\n                <RemocnLogo fontSize={34 * u} color={c.ink} />\n              </div>\n            ) : null}\n          </>\n        )}\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/ko-count.tsx"
    }
  ],
  "type": "registry:component"
}
