{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "keycap-press",
  "title": "Keycap Press",
  "description": "Two 3D keycaps press Cmd and K under the line Everything, one shortcut away.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/gsh"],
  "files": [
    {
      "path": "registry/remocn/keycap-press/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/BricolageGrotesque\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport type { GradientParams } from \"@/lib/remocn/gsh/gradient-engine\";\nimport { GshGround } from \"@/lib/remocn/gsh/gsh-ground\";\nimport type { GshPresetId } from \"@/lib/remocn/gsh/presets\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"300\", \"400\"],\n  subsets: [\"latin\"],\n});\n\nexport interface KeycapKey {\n  label: string;\n  sub?: string;\n  width?: number;\n}\n\nexport interface KeycapPressProps {\n  keys?: (string | KeycapKey)[];\n  label?: string;\n  material?: \"dark\" | \"light\";\n  theme?: \"dark\" | \"light\";\n  accent?: string;\n  ground?: GshPresetId;\n  groundParams?: Partial<GradientParams>;\n  speed?: number;\n}\n\nconst DEFAULT_KEYS: KeycapKey[] = [\n  { label: \"⌘\", sub: \"command\", width: 1.25 },\n  { label: \"K\" },\n];\n\nconst LIGHT_GLASS: Partial<GradientParams> = {\n  colors: [\"#a9bbff\", \"#3b5bfd\", \"#1a2a9e\", \"#8fa6ff\"],\n};\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst CAMERA = Easing.bezier(0.87, 0, 0.13, 1);\nconst HEAVY = { mass: 1, damping: 26, stiffness: 120 };\nconst CARD = { mass: 0.8, damping: 18, stiffness: 160 };\nconst CHIP = { mass: 0.5, damping: 12, stiffness: 200 };\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst MATERIALS = {\n  dark: {\n    top: [0x36, 0x36, 0x3c],\n    sideTop: [0x2e, 0x2e, 0x34],\n    sideBottom: [0x10, 0x10, 0x13],\n    legend: \"#ECEBE7\",\n    sub: \"#8F8E95\",\n    rim: 0.3,\n    dish: 0.34,\n    sheen: 0.11,\n    front: 0.4,\n    left: 0.07,\n    right: 0.45,\n    shadow: \"rgba(0,0,0,0.95)\",\n  },\n  light: {\n    top: [0xf7, 0xf6, 0xf2],\n    sideTop: [0xe9, 0xe7, 0xe1],\n    sideBottom: [0xa9, 0xa5, 0x9b],\n    legend: \"#2A2A2E\",\n    sub: \"#8C8981\",\n    rim: 0.9,\n    dish: 0.07,\n    sheen: 0.5,\n    front: 0.14,\n    left: 0.35,\n    right: 0.16,\n    shadow: \"rgba(58,48,32,0.5)\",\n  },\n} as const;\n\nconst STAGES = {\n  dark: {\n    base: \"#04050A\",\n    fg: \"#F3F2EF\",\n    muted: \"rgba(243,242,239,0.5)\",\n    ripple: \"255,255,255\",\n    scrim: \"4,5,10\",\n    scrimA: 0.42,\n    glowA: 0.34,\n    glowBlend: \"screen\",\n  },\n  light: {\n    base: \"#EEF0F6\",\n    fg: \"#121214\",\n    muted: \"rgba(18,18,20,0.5)\",\n    ripple: \"96,88,74\",\n    scrim: \"244,244,247\",\n    scrimA: 0.4,\n    glowA: 0.16,\n    glowBlend: \"multiply\",\n  },\n} as const;\n\nconst SYMBOLS: Record<string, string> = {\n  \"⌘\": \"M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3\",\n  \"⌥\": \"M3 5h6l6 14h6M14 5h7\",\n  \"⇧\": \"M9 19v-6H5l7-8 7 8h-4v6z\",\n};\n\ntype Material = (typeof MATERIALS)[keyof typeof MATERIALS];\n\nfunction rgb(c: readonly number[]) {\n  return `rgb(${c[0]},${c[1]},${c[2]})`;\n}\n\nfunction mix(a: readonly number[], b: readonly number[], t: number) {\n  return a.map((v, i) => Math.round(v + (b[i] - v) * t));\n}\n\nfunction hexToRgb(hex: string) {\n  const h = hex.replace(\"#\", \"\");\n  const n = Number.parseInt(\n    h.length === 3\n      ? h\n          .split(\"\")\n          .map((c) => c + c)\n          .join(\"\")\n      : h,\n    16,\n  );\n  return `${(n >> 16) & 255},${(n >> 8) & 255},${n & 255}`;\n}\n\nconst SLICES = 18;\n\nfunction profile(tt: number) {\n  const fillet = Math.max(0, (tt - 0.72) / 0.28) ** 2;\n  return tt * 0.68 + fillet * 0.32;\n}\n\nfunction Keycap({\n  w,\n  d,\n  h,\n  keyDef,\n  m,\n  unit,\n}: {\n  w: number;\n  d: number;\n  h: number;\n  keyDef: KeycapKey;\n  m: Material;\n  unit: number;\n}) {\n  const side = unit * 0.13;\n  const front = unit * 0.17;\n  const back = unit * 0.08;\n  const r0 = unit * 0.1;\n  const r1 = unit * 0.085;\n  const slices: React.ReactNode[] = [];\n  for (let k = 0; k < SLICES; k++) {\n    const tt = k / (SLICES - 1);\n    const p = profile(tt);\n    const base = mix(m.sideBottom, m.sideTop, Math.min(1, tt * 1.15));\n    const rim = Math.max(0, (tt - 0.7) / 0.3);\n    slices.push(\n      <div\n        key={k}\n        style={{\n          position: \"absolute\",\n          left: side * p,\n          right: side * p,\n          top: back * p,\n          bottom: front * p,\n          borderRadius: r0 + (r1 - r0) * tt,\n          transform: `translateZ(${tt * h}px)`,\n          background: [\n            `linear-gradient(180deg, rgba(255,255,255,${m.rim * rim * 0.5}) 0%, transparent 40%, rgba(0,0,0,${m.front * (1 - rim * 0.5)}) 100%)`,\n            `linear-gradient(90deg, rgba(255,255,255,${m.left + rim * m.rim * 0.25}) 0%, transparent 30%, transparent 62%, rgba(0,0,0,${m.right}) 100%)`,\n            rgb(base),\n          ].join(\", \"),\n        }}\n      />,\n    );\n  }\n  const topInset = profile(1);\n  const legendSize = unit * (keyDef.label.length > 1 ? 0.2 : 0.36);\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        left: -w / 2,\n        top: -d / 2,\n        width: w,\n        height: d,\n        transformStyle: \"preserve-3d\",\n      }}\n    >\n      {slices}\n      <div\n        style={{\n          position: \"absolute\",\n          left: side * topInset,\n          right: side * topInset,\n          top: back * topInset,\n          bottom: front * topInset,\n          borderRadius: r1,\n          transform: `translateZ(${h + 0.5}px)`,\n          background: [\n            `radial-gradient(ellipse 72% 64% at 50% 54%, rgba(0,0,0,${m.dish * 0.5}) 0%, rgba(0,0,0,${m.dish * 0.18}) 55%, transparent 100%)`,\n            `linear-gradient(160deg, rgba(255,255,255,${m.sheen}) 0%, transparent 45%)`,\n            rgb(m.top),\n          ].join(\", \"),\n          boxShadow: `inset 0 ${unit * 0.012}px 0 rgba(255,255,255,${m.rim * 0.5}), inset 0 -${unit * 0.02}px ${unit * 0.03}px rgba(0,0,0,${m.dish * 0.5})`,\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            display: \"flex\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            fontFamily: sans,\n            fontWeight: 400,\n            fontSize: legendSize,\n            lineHeight: 1,\n            letterSpacing: \"-0.02em\",\n            color: m.legend,\n            paddingBottom: keyDef.sub ? unit * 0.06 : 0,\n          }}\n        >\n          {SYMBOLS[keyDef.label] ? (\n            <svg\n              width={legendSize * 0.86}\n              height={legendSize * 0.86}\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke={m.legend}\n              strokeWidth={1.7}\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              aria-hidden\n            >\n              <path d={SYMBOLS[keyDef.label]} />\n            </svg>\n          ) : (\n            keyDef.label\n          )}\n        </div>\n        {keyDef.sub ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: unit * 0.09,\n              bottom: unit * 0.07,\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: unit * 0.085,\n              letterSpacing: \"0.01em\",\n              color: m.sub,\n            }}\n          >\n            {keyDef.sub}\n          </div>\n        ) : null}\n      </div>\n    </div>\n  );\n}\n\nexport function KeycapPress({\n  keys = DEFAULT_KEYS,\n  label = \"Everything, one shortcut away\",\n  material = \"dark\",\n  theme,\n  accent = \"#3B5BFD\",\n  ground,\n  groundParams,\n  speed = 1,\n}: KeycapPressProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height, fps } = useVideoConfig();\n  const tall = height > width;\n  const u = tall ? width / 760 : Math.min(width / 1280, height / 720);\n  const m = MATERIALS[material];\n  const look = theme ?? material;\n  const stage = STAGES[look];\n  const resolvedGround: GshPresetId | undefined =\n    ground ??\n    (groundParams ? undefined : look === \"dark\" ? \"spotlight\" : \"glasswork\");\n  const acc = hexToRgb(accent);\n\n  const defs: KeycapKey[] = keys.map((k) =>\n    typeof k === \"string\" ? { label: k } : k,\n  );\n  const unit = 205 * u * (tall ? 1.22 : 1) * (defs.length > 2 ? 0.82 : 1);\n  const gap = unit * 0.1;\n  const widths = defs.map((k) => (k.width ?? 1) * unit);\n  const total = widths.reduce((s, x) => s + x, 0) + gap * (defs.length - 1);\n  const centers: number[] = [];\n  let cursor = -total / 2;\n  for (const w of widths) {\n    centers.push(cursor + w / 2);\n    cursor += w + gap;\n  }\n  const depth = unit;\n  const capH = unit * 0.44;\n  const travel = unit * 0.13;\n  const n = defs.length;\n  const pressGap = n > 2 ? 10 : 12;\n  const pressAt = defs.map((_, i) => 36 + i * pressGap);\n  const chord = pressAt[n - 1];\n  const releaseAt = defs.map((_, i) => chord + 22 + (n - 1 - i) * 4);\n  const settle = spring({\n    frame: t,\n    fps,\n    config: HEAVY,\n    durationInFrames: 54,\n  });\n  const lift = interpolate(t, [chord + 2, chord + 34], [0, 1], {\n    ...CLAMP,\n    easing: CAMERA,\n  });\n  const rotX = 50 - settle * 8 - lift * 3;\n  const rotZ = interpolate(t, [0, 135], [-9, -4], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const camScale = 1.04 - settle * 0.04 - lift * 0.06;\n  const kick = interpolate(t, [chord, chord + 3, chord + 14], [0, 1, 0], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const keysY =\n    height * ((tall ? 0.42 : 0.46) - lift * (tall ? 0.05 : 0.08)) +\n    kick * 2 * u;\n  const perspective = 1800 * u;\n\n  const keyState = defs.map((_, i) => {\n    const rise = spring({\n      frame: t - (-8 + i * 5),\n      fps,\n      config: CARD,\n    });\n    const down = Math.min(\n      1.06,\n      spring({\n        frame: t - pressAt[i],\n        fps,\n        config: { mass: 0.4, damping: 14, stiffness: 320 },\n      }),\n    );\n    const up = spring({\n      frame: t - releaseAt[i],\n      fps,\n      config: CHIP,\n    });\n    const press = down * (1 - up);\n    return { rise, press };\n  });\n\n  const camera = (children: React.ReactNode) => (\n    <AbsoluteFill\n      style={{\n        perspective,\n        perspectiveOrigin: `50% ${(keysY / height) * 100}%`,\n      }}\n    >\n      <div\n        style={{\n          position: \"absolute\",\n          left: width / 2,\n          top: keysY,\n          width: 0,\n          height: 0,\n          transformStyle: \"preserve-3d\",\n          transform: `scale(${camScale}) rotateX(${rotX}deg) rotateZ(${rotZ}deg)`,\n        }}\n      >\n        {children}\n      </div>\n    </AbsoluteFill>\n  );\n  const ripples: React.ReactNode[] = [];\n  defs.forEach((_, i) => {\n    const age = t - pressAt[i];\n    if (age < 0 || age > 34) return;\n    const p = interpolate(age, [0, 34], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    const size = widths[i] * (1.05 + p * 1.5);\n    const sizeY = depth * (1.05 + p * 1.5);\n    ripples.push(\n      <div\n        key={`r${i}`}\n        style={{\n          position: \"absolute\",\n          left: centers[i] - size / 2,\n          top: -sizeY / 2 + depth * 0.04,\n          width: size,\n          height: sizeY,\n          borderRadius: unit * (0.2 + p * 0.5),\n          border: `${unit * 0.035}px solid rgba(${stage.ripple},${(1 - p) * 0.12})`,\n          boxShadow: `0 0 ${unit * 0.1}px rgba(${acc},${(1 - p) * 0.22})`,\n          filter: `blur(${unit * (0.012 + p * 0.03)}px)`,\n        }}\n      />,\n    );\n  });\n  const chordAge = t - chord;\n  if (chordAge >= 0 && chordAge < 50) {\n    for (const [lag, strength] of [\n      [0, 1],\n      [7, 0.55],\n    ] as const) {\n      const a = chordAge - lag;\n      if (a < 0) continue;\n      const p = interpolate(a, [0, 44], [0, 1], { ...CLAMP, easing: EASE_OUT });\n      const sx = total * (1.1 + p * 1.6);\n      const sy = depth * (1.1 + p * 2.6);\n      ripples.push(\n        <div\n          key={`c${lag}`}\n          style={{\n            position: \"absolute\",\n            left: -sx / 2,\n            top: -sy / 2 + depth * 0.05,\n            width: sx,\n            height: sy,\n            borderRadius: \"50%\",\n            border: `${unit * 0.04}px solid rgba(${stage.ripple},${(1 - p) * 0.1 * strength})`,\n            boxShadow: `0 0 ${unit * 0.14}px rgba(${acc},${(1 - p) * 0.22 * strength}), inset 0 0 ${unit * 0.14}px rgba(${acc},${(1 - p) * 0.14 * strength})`,\n            filter: `blur(${unit * (0.015 + p * 0.035)}px)`,\n          }}\n        />,\n      );\n    }\n  }\n  const poolFlash = interpolate(chordAge, [0, 3, 30], [0, 1, 0], CLAMP);\n  const glowRest = interpolate(t, [6, 40], [0, 0.35], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const glowHold = interpolate(chordAge, [0, 4, 40], [0, 1, 0.7], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const glow = Math.max(glowRest, glowHold);\n  const glowSpread =\n    1 +\n    0.12 *\n      interpolate(chordAge, [0, 40], [0, 1], {\n        ...CLAMP,\n        easing: EASE_OUT,\n      });\n  const gx = 50 + Math.sin(t / 47) * 1.5;\n  const underglow = `radial-gradient(closest-side at ${gx}% 50%, rgba(${acc},${stage.glowA}) 0%, rgba(${acc},${stage.glowA * 0.45}) 55%, transparent 100%)`;\n\n  const floor = camera(\n    <>\n      <div\n        style={{\n          position: \"absolute\",\n          left: -total,\n          top: -depth * 1.4,\n          width: total * 2,\n          height: depth * 2.8,\n          borderRadius: \"50%\",\n          background: `radial-gradient(closest-side, rgba(${acc},${0.16 * poolFlash}), transparent)`,\n        }}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          left: -total * 0.95 * glowSpread,\n          top: -depth * 1.1 * glowSpread + depth * 0.05,\n          width: total * 1.9 * glowSpread,\n          height: depth * 2.2 * glowSpread,\n          background: underglow,\n          opacity: glow,\n          mixBlendMode: stage.glowBlend,\n          filter: `blur(${unit * 0.12}px)`,\n        }}\n      />\n      {ripples}\n      {defs.map((_, i) => {\n        const { rise, press } = keyState[i];\n        const w = widths[i];\n        const tight = press;\n        const spread = (1 - tight) * 0.5 + 0.5;\n        return (\n          <div key={`s${i}`}>\n            <div\n              style={{\n                position: \"absolute\",\n                left: centers[i] - w * 0.55 + unit * 0.04,\n                top: -depth * 0.5 + depth * 0.14,\n                width: w * 1.1,\n                height: depth * 1.1,\n                borderRadius: unit * 0.24,\n                background: m.shadow,\n                opacity: 0.55 * rise * (0.85 + tight * 0.15),\n                filter: `blur(${unit * (0.16 + spread * 0.12)}px)`,\n              }}\n            />\n            <div\n              style={{\n                position: \"absolute\",\n                left: centers[i] - w * 0.48,\n                top: -depth * 0.48 + depth * 0.03,\n                width: w * 0.96,\n                height: depth * 0.96,\n                borderRadius: unit * 0.12,\n                background: m.shadow,\n                opacity: (0.6 + tight * 0.3) * rise,\n                filter: `blur(${unit * (0.045 + (1 - tight) * 0.04)}px)`,\n              }}\n            />\n          </div>\n        );\n      })}\n    </>,\n  );\n\n  const caps = camera(\n    defs.map((k, i) => {\n      const { rise, press } = keyState[i];\n      const off = 1 - rise;\n      const z = unit * 0.035 - press * travel - off * unit * 0.6;\n      const y = off * unit * 2.6;\n      const tilt = off * -24;\n      return (\n        <div\n          key={i}\n          style={{\n            position: \"absolute\",\n            left: centers[i],\n            top: 0,\n            width: 0,\n            height: 0,\n            transformStyle: \"preserve-3d\",\n            transform: `translate3d(0, ${y}px, ${z}px) rotateX(${tilt}deg)`,\n          }}\n        >\n          <Keycap\n            w={widths[i] - unit * 0.02}\n            d={depth}\n            h={capH}\n            keyDef={k}\n            m={m}\n            unit={unit}\n          />\n        </div>\n      );\n    }),\n  );\n  const words = label.split(\" \");\n  const word = (i: number) =>\n    interpolate(t, [chord + 6 + i * 3, chord + 26 + i * 3], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n\n  return (\n    <AbsoluteFill style={{ background: stage.base, overflow: \"hidden\" }}>\n      <GshGround\n        preset={resolvedGround}\n        params={groundParams ?? (look === \"light\" ? LIGHT_GLASS : undefined)}\n        timeOffset={look === \"dark\" ? 2 : 5}\n        timeScale={0.45}\n      />\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(60% 55% at 50% ${tall ? 42 : 46}%, rgba(${stage.scrim},${stage.scrimA}) 0%, rgba(${stage.scrim},${stage.scrimA * 0.4}) 100%)`,\n        }}\n      />\n      {floor}\n      {caps}\n      <div\n        style={{\n          position: \"absolute\",\n          left: \"10%\",\n          right: \"10%\",\n          top: height * (tall ? 0.6 : 0.7),\n          textAlign: \"center\",\n          fontFamily: sans,\n          fontWeight: 300,\n          fontSize: (tall ? 72 : 60) * u,\n          lineHeight: 1.08,\n          letterSpacing: \"-0.04em\",\n          color: stage.fg,\n          textWrap: \"balance\",\n        }}\n      >\n        {words.map((w, i) => (\n          <span\n            key={i}\n            style={{\n              display: \"inline-block\",\n              whiteSpace: \"pre\",\n              opacity: word(i),\n              transform: `translateY(${(1 - word(i)) * 14 * u}px)`,\n              filter: `blur(${(1 - word(i)) * 5 * u}px)`,\n            }}\n          >\n            {i < words.length - 1 ? `${w} ` : w}\n          </span>\n        ))}\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/keycap-press.tsx"
    }
  ],
  "type": "registry:component"
}
