{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "high-score",
  "title": "High Score",
  "description": "A pixel arcade table inserts a new top entry, 'NEW HIGH SCORE', above four lower scores.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/high-score/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadGeist } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { loadFont as loadPixel } from \"@remotion/google-fonts/Silkscreen\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: pixel } = loadPixel(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: geist } = loadGeist(\"normal\", {\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 HighScoreEntry {\n  /** Three-letter initials. */\n  name: string;\n  score: number;\n}\n\nexport interface HighScoreProps {\n  /** The table before the new run, best first. Up to five rows are shown. */\n  rows?: HighScoreEntry[];\n  /** Initials typed in once the new score lands at the top. */\n  player?: string;\n  /** The new score. Should beat the current #1. */\n  playerScore?: number;\n  /** Small table title shown before the flash. */\n  title?: string;\n  /** The flash that replaces the title once the initials are in. */\n  flash?: string;\n  /** Small cursor block while the initials are typed. */\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\n// Beat sheet (30fps, speed 1):\n//   0–26   title and column labels in; five rows slide in from the right, 3f apart\n//  22–28   the player band wipes in under the table, \"___\" and a 0 score\n//  28–74   the score races up; each time it passes a row the band jumps a slot\n//          and the overtaken row is shoved down (44, 53, 61, 68, 74); the old\n//          #5 is shoved off the table and drops away\n//  74–86   at #1 the count decelerates to 999,990; 86 is the impact: band flash,\n//          small shake, the band opens to full bleed\n//  89–97   the initials blink in one letter at a time behind a block cursor\n// 102–118  \"NEW HIGH SCORE\" slams in with the dither gradient and flashes twice;\n//          rows 2–5 dim; the logo rises in; poster hold\nconst ROW_IN = 6;\nconst ROW_STAGGER = 3;\nconst BAND_IN = 22;\nconst RACE_AT = 28;\nconst PASS = [44, 53, 61, 68, 74];\nconst MOVE = 6;\nconst LAND = 86;\nconst LETTERS_AT = 89;\nconst LETTER_GAP = 4;\nconst FLASH_AT = 102;\nconst LOGO_AT = 118;\n\nconst SLAM = Easing.bezier(0.12, 0.9, 0.25, 1);\nconst OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst INOUT = Easing.bezier(0.65, 0, 0.25, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n// gradients.sh \"dither-field\": bg #050505, [#2b3dd8, #ff6a3d, #ffd9a8].\n// Used stepped (hard stops) so it reads as an 8-bit ramp.\nconst DITHER = [\"#2B3DD8\", \"#FF6A3D\", \"#FFD9A8\"] as const;\n\nconst THEMES = {\n  dark: {\n    stage: \"#08080A\",\n    ink: \"#F3F2EF\",\n    dim: \"#6E6D74\",\n    rule: \"#1E1E23\",\n    flashStops: [DITHER[2], DITHER[1], \"#8C7BFF\"],\n  },\n  light: {\n    stage: \"#F5F4F0\",\n    ink: \"#121214\",\n    dim: \"#8E8D86\",\n    rule: \"#E2E0D9\",\n    flashStops: [\"#F0782E\", \"#E2452A\", DITHER[0]],\n  },\n} as const;\n\nconst RANKS = [\"1ST\", \"2ND\", \"3RD\", \"4TH\", \"5TH\"];\n\nfunction ramp(t: number, from: number, len: number) {\n  return interpolate(t, [from, from + len], [0, 1], CLAMP);\n}\n\nfunction hexToRgb(h: string) {\n  const n = Number.parseInt(h.slice(1), 16);\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction mix(a: string, b: string, k: number) {\n  const A = hexToRgb(a);\n  const B = hexToRgb(b);\n  const c = A.map((v, i) => Math.round(v + (B[i] - v) * k));\n  return `rgb(${c[0]}, ${c[1]}, ${c[2]})`;\n}\n\n/** A stepped (hard-stop) ramp across `stops`, `steps` bands. */\nfunction steppedGradient(\n  angle: number,\n  stops: readonly string[],\n  steps: number,\n) {\n  const parts: string[] = [];\n  for (let i = 0; i < steps; i++) {\n    const k = i / (steps - 1);\n    const seg = Math.min(stops.length - 2, Math.floor(k * (stops.length - 1)));\n    const local = k * (stops.length - 1) - seg;\n    const col = mix(stops[seg], stops[seg + 1], local);\n    parts.push(\n      `${col} ${((i / steps) * 100).toFixed(2)}% ${(((i + 1) / steps) * 100).toFixed(2)}%`,\n    );\n  }\n  return `linear-gradient(${angle}deg, ${parts.join(\", \")})`;\n}\n\nfunction formatScore(n: number) {\n  return Math.max(0, Math.round(n)).toLocaleString(\"en-US\");\n}\n\n/** Decaying, frame-stepped shake. */\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 HighScore({\n  rows = [\n    { name: \"ACE\", score: 870450 },\n    { name: \"JFK\", score: 812300 },\n    { name: \"MAX\", score: 640120 },\n    { name: \"ZOE\", score: 455900 },\n    { name: \"BOB\", score: 310050 },\n  ],\n  player = \"RMC\",\n  playerScore = 999990,\n  title = \"HIGH SCORES\",\n  flash = \"NEW HIGH SCORE\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: HighScoreProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const u = Math.min(W / 1280, H / 720);\n\n  const table = rows.slice(0, 5);\n  const n = table.length;\n  // Overtake times in order: the last row is passed first, #1 last.\n  const passes = PASS.slice(PASS.length - n);\n  const passAt = (i: number) => passes[n - 1 - i];\n\n  // ── Layout ────────────────────────────────────────────────────────────────\n  const fs = 52 * u;\n  const cell = fs * 0.76;\n  const comma = fs * 0.4;\n  const rowH = 74 * u;\n  const tableW = 860 * u;\n  const x0 = (W - tableW) / 2;\n  const top = 0.27 * H;\n  const slotY = (pos: number) => top + (pos - 0.5) * rowH;\n  const nameX = x0 + 250 * u;\n\n  // ── The race ──────────────────────────────────────────────────────────────\n  // Score keyframes: 0 at the start, each row's score at its overtake frame,\n  // then a decelerating finish to the player's score.\n  const lastPass = passes[n - 1] ?? RACE_AT;\n  const keysT = [RACE_AT, ...passes];\n  const keysV = [0, ...[...table].reverse().map((r) => r.score)];\n  let score: number;\n  if (t <= lastPass) {\n    score = interpolate(t, keysT, keysV, CLAMP);\n  } else {\n    score = interpolate(\n      t,\n      [lastPass, LAND],\n      [keysV[keysV.length - 1], playerScore],\n      { ...CLAMP, easing: Easing.bezier(0.22, 1, 0.36, 1) },\n    );\n  }\n  score = Math.round(score / 10) * 10;\n\n  // Player slot: starts below the table (n + 1), one slot up per overtake.\n  const climbs = passes.map((p) => OUT(ramp(t, p, MOVE)));\n  const playerPos = n + 1 - climbs.reduce((a, b) => a + b, 0);\n  // Row i (0 = best) is shoved one slot down when the player passes it.\n  const rowPos = (i: number) => i + 1 + SLAM(ramp(t, passAt(i), MOVE));\n\n  const land = t >= LAND;\n  const sh = shake(t, LAND, 8, 7 * u);\n  const impact = land ? Math.max(0, 1 - (t - LAND) / 7) : 0;\n  const bleed = INOUT(ramp(t, LAND + 1, 10));\n  const dimRest = OUT(ramp(t, FLASH_AT + 2, 12));\n\n  // ── Title swap ────────────────────────────────────────────────────────────\n  const titleIn = OUT(ramp(t, 0, 10));\n  const titleOut = ramp(t, FLASH_AT - 3, 3);\n  const flashIn = SLAM(ramp(t, FLASH_AT, 6));\n  const fd = t - FLASH_AT;\n  const flashOn =\n    t >= FLASH_AT && !(fd >= 9 && fd < 12) && !(fd >= 15 && fd < 18);\n  const logoP = OUT(ramp(t, LOGO_AT, 10));\n  const push = interpolate(t, [FLASH_AT, FLASH_AT + 60], [1, 1.05], {\n    ...CLAMP,\n    easing: Easing.bezier(0.22, 1, 0.36, 1),\n  });\n\n  const pixelText = (\n    text: string,\n    color: string,\n    size = fs,\n    extra?: React.CSSProperties,\n  ) => (\n    <div style={{ display: \"flex\", ...extra }}>\n      {text.split(\"\").map((ch, k) => {\n        const isComma = ch === \",\";\n        return (\n          <div\n            key={k}\n            style={{\n              width: isComma ? (comma * size) / fs : (cell * size) / fs,\n              textAlign: \"center\",\n              fontFamily: pixel,\n              fontWeight: 400,\n              fontSize: size,\n              lineHeight: 1,\n              color,\n            }}\n          >\n            {ch}\n          </div>\n        );\n      })}\n    </div>\n  );\n\n  const scoreText = (value: string, color: string) =>\n    pixelText(value, color, fs, {\n      position: \"absolute\",\n      right: x0,\n      top: -fs / 2,\n    });\n\n  // Initials typed in after the landing, behind a blinking block cursor.\n  const typed = Math.max(\n    0,\n    Math.min(player.length, Math.floor((t - LETTERS_AT) / LETTER_GAP) + 1),\n  );\n  const cursorOn =\n    t >= LETTERS_AT - 6 && typed < player.length && Math.floor(t / 3) % 2 === 0;\n\n  return (\n    <AbsoluteFill style={{ background: c.stage, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          transform: `translate(${sh.x}px, ${sh.y}px) scale(${push})`,\n          transformOrigin: \"50% 40%\",\n        }}\n      >\n        {/* Title: the small table title, swapped for the flash. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            width: W,\n            top: 0.12 * H,\n            display: \"flex\",\n            justifyContent: \"center\",\n            opacity: titleIn * (1 - titleOut),\n            transform: `translateY(${(1 - titleIn) * 12 * u}px) scaleY(${1 - titleOut})`,\n          }}\n        >\n          {pixelText(title, c.ink, 30 * u, { letterSpacing: 0 })}\n        </div>\n        {t >= FLASH_AT ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: 0,\n              width: W,\n              top: 0.12 * H - 24 * u,\n              display: \"flex\",\n              justifyContent: \"center\",\n              transform: `scale(${interpolate(flashIn, [0, 1], [1.45, 1])})`,\n              opacity: flashOn ? ramp(t, FLASH_AT, 2) : 0,\n            }}\n          >\n            <div\n              style={{\n                fontFamily: pixel,\n                fontWeight: 400,\n                fontSize: 76 * u,\n                lineHeight: 1,\n                letterSpacing: \"0.02em\",\n                whiteSpace: \"nowrap\",\n                backgroundImage: steppedGradient(90, c.flashStops, 12),\n                WebkitBackgroundClip: \"text\",\n                backgroundClip: \"text\",\n                color: \"transparent\",\n              }}\n            >\n              {flash}\n            </div>\n          </div>\n        ) : null}\n\n        {/* Column labels. */}\n        <div\n          style={{\n            position: \"absolute\",\n            top: top - 42 * u,\n            left: x0,\n            width: tableW,\n            height: 20 * u,\n            fontFamily: geist,\n            fontWeight: 500,\n            fontSize: 14 * u,\n            letterSpacing: \"0.22em\",\n            color: c.dim,\n            opacity: OUT(ramp(t, 4, 8)) * (1 - 0.5 * dimRest),\n          }}\n        >\n          <span style={{ position: \"absolute\", left: 0 }}>RANK</span>\n          <span style={{ position: \"absolute\", left: nameX - x0 }}>NAME</span>\n          <span style={{ position: \"absolute\", right: 0 }}>SCORE</span>\n        </div>\n\n        {/* Player band: wipes in under the table and climbs. */}\n        {t >= BAND_IN ? (\n          <div\n            style={{\n              position: \"absolute\",\n              top: slotY(playerPos) - rowH / 2 + 4 * u,\n              height: rowH - 8 * u,\n              left: interpolate(bleed, [0, 1], [x0 - 26 * u, 0]),\n              right: interpolate(bleed, [0, 1], [x0 - 26 * u, 0]),\n              clipPath: `inset(0 ${(1 - OUT(ramp(t, BAND_IN, 7))) * 100}% 0 0)`,\n              backgroundImage: steppedGradient(\n                90,\n                [\"#F25A2E\", \"#C2456E\", DITHER[0]],\n                16,\n              ),\n              boxShadow: `inset 0 ${4 * u}px 0 rgba(255, 217, 168, 0.45), inset 0 ${-4 * u}px 0 rgba(10, 10, 30, 0.28)`,\n            }}\n          >\n            <div\n              style={{\n                position: \"absolute\",\n                inset: 0,\n                background: \"#FFFFFF\",\n                opacity: impact * 0.85,\n              }}\n            />\n          </div>\n        ) : null}\n\n        {/* Rank labels, fixed per slot; white where the band covers them. */}\n        {table.map((_, k) => {\n          const covered = t >= BAND_IN && Math.abs(playerPos - (k + 1)) < 0.5;\n          const p = OUT(ramp(t, ROW_IN + k * ROW_STAGGER, 8));\n          const dim = covered ? 0 : dimRest;\n          return (\n            <div\n              key={RANKS[k]}\n              style={{\n                position: \"absolute\",\n                left: x0,\n                top: slotY(k + 1) - fs / 2,\n                opacity: p * (1 - 0.6 * dim),\n                transform: `translateX(${(1 - p) * 60 * u}px)`,\n              }}\n            >\n              {pixelText(RANKS[k], covered ? \"#FFFFFF\" : c.ink)}\n            </div>\n          );\n        })}\n\n        {/* The other rows: initials + score, shoved down one by one. */}\n        {table.map((r, i) => {\n          const pos = rowPos(i);\n          const p = OUT(ramp(t, ROW_IN + i * ROW_STAGGER, 8));\n          const off = OUT(ramp(t, passAt(i) + MOVE, 8));\n          const gone = i === n - 1;\n          const kickD = t - passAt(i);\n          const kick =\n            kickD >= 0 && kickD < 8 ? (1 - kickD / 8) ** 2 * 14 * u : 0;\n          const y = slotY(pos) + (gone ? off * 30 * u : 0);\n          return (\n            <div\n              key={r.name}\n              style={{\n                position: \"absolute\",\n                left: 0,\n                width: W,\n                top: y,\n                height: 0,\n                opacity: p * (gone ? 1 - off : 1) * (1 - 0.6 * dimRest),\n                transform: `translateX(${(1 - p) * 60 * u + kick}px)`,\n              }}\n            >\n              {pixelText(r.name, c.ink, fs, {\n                position: \"absolute\",\n                left: nameX,\n                top: -fs / 2,\n              })}\n              {scoreText(formatScore(r.score), c.ink)}\n            </div>\n          );\n        })}\n\n        {/* Player row content. */}\n        {t >= BAND_IN ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: 0,\n              width: W,\n              top: slotY(playerPos),\n              height: 0,\n              opacity: ramp(t, BAND_IN + 3, 3),\n            }}\n          >\n            <div\n              style={{\n                position: \"absolute\",\n                left: nameX,\n                top: -fs / 2,\n                display: \"flex\",\n              }}\n            >\n              {player.split(\"\").map((ch, k) => {\n                const shown = k < typed;\n                const isCursor = !shown && k === typed && cursorOn;\n                return (\n                  <div\n                    key={k}\n                    style={{\n                      position: \"relative\",\n                      width: cell,\n                      height: fs,\n                      display: \"flex\",\n                      justifyContent: \"center\",\n                    }}\n                  >\n                    {isCursor ? (\n                      <div\n                        style={{\n                          position: \"absolute\",\n                          left: cell * 0.14,\n                          right: cell * 0.14,\n                          top: fs * 0.04,\n                          bottom: fs * 0.08,\n                          background: \"#FFFFFF\",\n                          boxShadow: `0 0 0 ${2 * u}px ${accent}`,\n                        }}\n                      />\n                    ) : null}\n                    <div\n                      style={{\n                        fontFamily: pixel,\n                        fontWeight: 400,\n                        fontSize: fs,\n                        lineHeight: 1,\n                        color: \"#FFFFFF\",\n                        opacity: shown ? 1 : isCursor ? 0 : 0.55,\n                        transform: shown\n                          ? `scale(${interpolate(ramp(t, LETTERS_AT + k * LETTER_GAP, 3), [0, 1], [1.35, 1])})`\n                          : undefined,\n                      }}\n                    >\n                      {shown ? ch : \"_\"}\n                    </div>\n                  </div>\n                );\n              })}\n            </div>\n            {scoreText(formatScore(score), \"#FFFFFF\")}\n          </div>\n        ) : null}\n      </div>\n\n      {/* Logo. */}\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          width: W,\n          top: 0.895 * H,\n          display: \"flex\",\n          justifyContent: \"center\",\n          opacity: logoP,\n          transform: `translateY(${(1 - logoP) * 16 * u}px)`,\n        }}\n      >\n        <RemocnLogo fontSize={30 * u} color={c.ink} />\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/high-score.tsx"
    }
  ],
  "type": "registry:component"
}
