{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "keyboard-cat",
  "type": "registry:component",
  "title": "Keyboard Cat",
  "description": "Loading art with a cat loafing on a laptop: its paws knead the keys so they light up while code types out and the progress bar fills, then it stretches and hops off when the job is done.",
  "categories": [
    "illustration",
    "Characters"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/keyboard-cat.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport {\n  type AnimationPlaybackControls,\n  animate,\n  motion,\n  useInView,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  useTransform,\n} from \"motion/react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type KeyboardCatHue = \"orange\" | \"amber\" | \"rose\" | \"violet\" | \"sky\" | \"teal\" | \"emerald\" | \"indigo\";\n\nconst hues: Record<KeyboardCatHue, string> = {\n  orange:\n    \"[--hue-hi:var(--color-orange-300)] [--hue:var(--color-orange-400)] [--hue-deep:var(--color-orange-600)] [--soft:var(--color-orange-100)] [--ink:var(--color-orange-950)]\",\n  amber:\n    \"[--hue-hi:var(--color-amber-300)] [--hue:var(--color-amber-400)] [--hue-deep:var(--color-amber-600)] [--soft:var(--color-amber-100)] [--ink:var(--color-amber-950)]\",\n  rose: \"[--hue-hi:var(--color-rose-300)] [--hue:var(--color-rose-400)] [--hue-deep:var(--color-rose-600)] [--soft:var(--color-rose-100)] [--ink:var(--color-rose-950)]\",\n  violet:\n    \"[--hue-hi:var(--color-violet-300)] [--hue:var(--color-violet-400)] [--hue-deep:var(--color-violet-600)] [--soft:var(--color-violet-100)] [--ink:var(--color-violet-950)]\",\n  sky: \"[--hue-hi:var(--color-sky-300)] [--hue:var(--color-sky-400)] [--hue-deep:var(--color-sky-600)] [--soft:var(--color-sky-100)] [--ink:var(--color-sky-950)]\",\n  teal: \"[--hue-hi:var(--color-teal-300)] [--hue:var(--color-teal-400)] [--hue-deep:var(--color-teal-600)] [--soft:var(--color-teal-100)] [--ink:var(--color-teal-950)]\",\n  emerald:\n    \"[--hue-hi:var(--color-emerald-300)] [--hue:var(--color-emerald-400)] [--hue-deep:var(--color-emerald-600)] [--soft:var(--color-emerald-100)] [--ink:var(--color-emerald-950)]\",\n  indigo:\n    \"[--hue-hi:var(--color-indigo-300)] [--hue:var(--color-indigo-400)] [--hue-deep:var(--color-indigo-600)] [--soft:var(--color-indigo-100)] [--ink:var(--color-indigo-950)]\",\n};\n\nexport interface KeyboardCatProps {\n  name?: string;\n  hue?: KeyboardCatHue;\n  progress?: number;\n  label?: string;\n  doneLabel?: string;\n  doneDescription?: string;\n  onDone?: () => void;\n  className?: string;\n}\n\ntype Phase = \"working\" | \"waking\" | \"gone\" | \"returning\";\ntype Key = { x: number; y: number; w: number; h: number };\n\nconst VB = { x: 40, y: 14, w: 320, h: 214 };\nconst GAP = 3;\n\nfunction deckEdges(y: number) {\n  const t = (y - 150) / 56;\n  return { left: 110 - 32 * t + 8, right: 290 + 32 * t - 8 };\n}\n\nfunction row(y: number, h: number, n: number): Key[] {\n  const { left, right } = deckEdges(y + h / 2);\n  const w = (right - left - GAP * (n - 1)) / n;\n  return Array.from({ length: n }, (_, i) => ({ x: left + i * (w + GAP), y, w, h }));\n}\n\nfunction spaceRow(y: number, h: number): Key[] {\n  const { left, right } = deckEdges(y + h / 2);\n  const bar = (right - left) * 0.4;\n  const side = (right - left - bar - GAP * 2) / 2;\n  const w = (side - GAP * 2) / 3;\n  const keys: Key[] = [];\n  for (let i = 0; i < 3; i++) keys.push({ x: left + i * (w + GAP), y, w, h });\n  keys.push({ x: left + side + GAP, y, w: bar, h });\n  for (let i = 0; i < 3; i++) keys.push({ x: left + side + bar + GAP * 2 + i * (w + GAP), y, w, h });\n  return keys;\n}\n\nconst KEYS: Key[] = [...row(155, 9, 12), ...row(167, 9, 12), ...row(179, 9, 11), ...spaceRow(191, 8)];\n\nfunction nearest(x: number, y: number) {\n  let best = 0;\n  let dist = Number.POSITIVE_INFINITY;\n  KEYS.forEach((k, i) => {\n    const d = Math.hypot(k.x + k.w / 2 - x, k.y + k.h / 2 - y);\n    if (d < dist) {\n      dist = d;\n      best = i;\n    }\n  });\n  return best;\n}\n\nconst PAWS = [\n  { x: 166, y: 194 },\n  { x: 192, y: 195 },\n];\nconst PAW_KEYS = PAWS.map((p) => nearest(p.x, p.y));\nconst OPEN_KEYS = KEYS.map((k, i) => ({ k, i }))\n  .filter(({ k }) => k.x + k.w < 146 || k.x > 306)\n  .map(({ i }) => i);\n\nconst LINES = [\n  { w: 72, kw: 20 },\n  { w: 104, kw: 26 },\n  { w: 58, kw: 16 },\n  { w: 92, kw: 22 },\n  { w: 44, kw: 14 },\n];\n\nfunction useCalmMotion() {\n  const prefers = useReducedMotion();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  return mounted && !!prefers;\n}\n\nexport function KeyboardCat({\n  name = \"Mochi\",\n  hue = \"orange\",\n  progress,\n  label = \"Installing dependencies\",\n  doneLabel = \"All installed\",\n  doneDescription = \"1,284 packages, no warnings.\",\n  onDone,\n  className,\n}: KeyboardCatProps) {\n  const reduce = useCalmMotion();\n  const root = useRef<HTMLDivElement>(null);\n  const inView = useInView(root);\n  const live = inView && !reduce;\n  const simulated = progress === undefined;\n  const [simPct, setSimPct] = useState(0);\n  const pct = Math.round(Math.min(100, Math.max(0, simulated ? simPct : progress)));\n  const done = pct >= 100;\n  const [phase, setPhase] = useState<Phase>(\"working\");\n  const [tick, setTick] = useState({ beat: 0, lit: [] as number[] });\n  const [peek, setPeek] = useState<0 | 1 | null>(null);\n  const [twitch, setTwitch] = useState({ ear: 0, n: 0 });\n\n  const bar = useMotionValue(0);\n  const barSmooth = useSpring(bar, { stiffness: 140, damping: 24 });\n  const barWidth = useTransform(barSmooth, (v) => Math.max(0, v) * 132);\n\n  const catX = useMotionValue(0);\n  const catY = useMotionValue(0);\n  const catRotate = useMotionValue(0);\n  const catOpacity = useMotionValue(1);\n  const stretchX = useMotionValue(1);\n  const stretchY = useMotionValue(1);\n\n  const onDoneRef = useRef(onDone);\n  useEffect(() => {\n    onDoneRef.current = onDone;\n  });\n\n  const lastPct = useRef(pct);\n  useEffect(() => {\n    if (pct < lastPct.current) barSmooth.jump(pct / 100);\n    bar.set(pct / 100);\n    lastPct.current = pct;\n  }, [pct, bar, barSmooth]);\n\n  useEffect(() => {\n    if (!simulated || done || phase !== \"working\" || !inView) return;\n    const timer = setInterval(() => {\n      setSimPct((p) => {\n        if (Math.random() < 0.12) return p;\n        return Math.min(100, p + 0.6 + Math.random() * 2.6);\n      });\n    }, 140);\n    return () => clearInterval(timer);\n  }, [simulated, done, phase, inView]);\n\n  useEffect(() => {\n    if (phase !== \"working\" || !inView || done) {\n      setTick((t) => (t.lit.length ? { beat: t.beat, lit: [] } : t));\n      return;\n    }\n    const timer = setInterval(() => {\n      setTick((t) => {\n        const beat = t.beat + 1;\n        const lit = [PAW_KEYS[beat % 2]];\n        if (Math.random() < 0.55) lit.push(OPEN_KEYS[Math.floor(Math.random() * OPEN_KEYS.length)]);\n        return { beat, lit };\n      });\n    }, 320);\n    return () => clearInterval(timer);\n  }, [phase, inView, done]);\n\n  useEffect(() => {\n    if (!live || phase !== \"working\") return;\n    let timer: ReturnType<typeof setTimeout>;\n    let peekTimer: ReturnType<typeof setTimeout>;\n    const schedule = () => {\n      timer = setTimeout(\n        () => {\n          if (Math.random() < 0.3) {\n            setPeek(Math.random() < 0.5 ? 0 : 1);\n            peekTimer = setTimeout(() => setPeek(null), 1100);\n          } else {\n            setTwitch((t) => ({ ear: Math.random() < 0.5 ? 0 : 1, n: t.n + 1 }));\n          }\n          schedule();\n        },\n        1800 + Math.random() * 2600,\n      );\n    };\n    schedule();\n    return () => {\n      clearTimeout(timer);\n      clearTimeout(peekTimer);\n      setPeek(null);\n    };\n  }, [live, phase]);\n\n  const phaseRef = useRef<Phase>(\"working\");\n  const doneRef = useRef(done);\n  const runId = useRef(0);\n  const running = useRef<AnimationPlaybackControls[]>([]);\n  const timers = useRef(new Set<ReturnType<typeof setTimeout>>());\n\n  useEffect(() => {\n    const pending = timers.current;\n    return () => {\n      runId.current++;\n      for (const t of pending) clearTimeout(t);\n      for (const c of running.current) c.stop();\n    };\n  }, []);\n\n  const stage = (next: Phase) => {\n    phaseRef.current = next;\n    setPhase(next);\n  };\n\n  const wait = (ms: number) =>\n    new Promise<void>((resolve) => {\n      const t = setTimeout(() => {\n        timers.current.delete(t);\n        resolve();\n      }, ms);\n      timers.current.add(t);\n    });\n\n  const run = (c: AnimationPlaybackControls) => {\n    running.current.push(c);\n    return c;\n  };\n\n  const leave = async () => {\n    const id = ++runId.current;\n    const alive = () => runId.current === id;\n    running.current = [];\n    stage(\"waking\");\n    await wait(reduce ? 200 : 420);\n    if (!alive()) return;\n    if (reduce) {\n      await run(animate(catOpacity, 0, { duration: 0.4 }));\n    } else {\n      run(animate(catRotate, -5, { duration: 0.45, ease: [0.65, 0, 0.35, 1] }));\n      run(animate(stretchX, 1.14, { duration: 0.45, ease: [0.65, 0, 0.35, 1] }));\n      await run(animate(stretchY, 0.86, { duration: 0.45, ease: [0.65, 0, 0.35, 1] }));\n      if (!alive()) return;\n      await wait(260);\n      if (!alive()) return;\n      run(animate(catRotate, 0, { duration: 0.28, ease: [0.23, 1, 0.32, 1] }));\n      run(animate(stretchX, 1, { duration: 0.28, ease: [0.23, 1, 0.32, 1] }));\n      await run(animate(stretchY, 1, { duration: 0.28, ease: [0.23, 1, 0.32, 1] }));\n      if (!alive()) return;\n      run(animate(catRotate, [0, -10, 4], { duration: 0.6, ease: \"easeInOut\" }));\n      run(animate(catY, [0, -48, -30, 40], { duration: 0.6, ease: \"easeIn\", times: [0, 0.4, 0.62, 1] }));\n      await run(animate(catX, [0, 90, 170, 290], { duration: 0.6, ease: \"linear\" }));\n    }\n    if (!alive()) return;\n    stage(\"gone\");\n    onDoneRef.current?.();\n    if (!doneRef.current) comeBack();\n  };\n\n  const comeBack = async () => {\n    const id = ++runId.current;\n    const alive = () => runId.current === id;\n    running.current = [];\n    stage(\"returning\");\n    if (reduce) {\n      catX.set(0);\n      catY.set(0);\n      catRotate.set(0);\n      await run(animate(catOpacity, 1, { duration: 0.4 }));\n    } else {\n      catOpacity.set(1);\n      run(animate(catRotate, [6, -6, 0], { duration: 0.6, ease: \"easeOut\" }));\n      run(animate(catY, [40, -46, 0], { duration: 0.6, ease: \"easeOut\", times: [0, 0.5, 1] }));\n      await run(animate(catX, [290, 120, 0], { duration: 0.6, ease: [0.23, 1, 0.32, 1] }));\n      if (!alive()) return;\n      run(animate(stretchY, [1, 0.9, 1], { duration: 0.32, ease: [0.23, 1, 0.32, 1] }));\n      await run(animate(stretchX, [1, 1.06, 1], { duration: 0.32, ease: [0.23, 1, 0.32, 1] }));\n      if (!alive()) return;\n      await wait(450);\n    }\n    if (!alive()) return;\n    stage(\"working\");\n    if (doneRef.current) leave();\n  };\n\n  useEffect(() => {\n    doneRef.current = done;\n    if (done && phaseRef.current === \"working\") leave();\n    if (!done && phaseRef.current === \"gone\") comeBack();\n  }, [done]);\n\n  useEffect(() => {\n    if (!simulated || phase !== \"gone\" || !inView) return;\n    const timer = setTimeout(() => setSimPct(0), 2600);\n    return () => clearTimeout(timer);\n  }, [simulated, phase, inView]);\n\n  const replay = () => {\n    if (phase === \"gone\" && simulated) {\n      setSimPct(0);\n      return;\n    }\n    if (phase === \"working\") setTwitch((t) => ({ ear: t.n % 2 === 0 ? 0 : 1, n: t.n + 1 }));\n  };\n\n  const awake = phase !== \"working\";\n  const kneading = live && phase === \"working\" && !done;\n  const revealed = (i: number) => Math.min(1, Math.max(0, (pct / 100) * (LINES.length + 0.6) - i));\n  const caretLine = Math.min(LINES.length - 1, Math.floor((pct / 100) * (LINES.length + 0.6)));\n  const caretX = 134 + LINES[caretLine].w * revealed(caretLine) + 2;\n  const finished = phase === \"gone\" || (phase === \"waking\" && done);\n\n  return (\n    <div\n      ref={root}\n      className={cn(\n        \"relative w-full max-w-[420px] overflow-hidden rounded-2xl border bg-card p-4 shadow-xs/5 sm:p-6\",\n        hues[hue],\n        className,\n      )}\n    >\n      <div\n        aria-hidden\n        className=\"dot-grid pointer-events-none absolute inset-0 opacity-50 [mask-image:radial-gradient(closest-side,black,transparent)]\"\n      />\n      <button\n        aria-label={finished && simulated ? \"Run it again\" : `${name} is curled up on the keyboard. Give ${name} a scratch`}\n        className=\"relative mx-auto block aspect-[160/107] w-full max-w-[380px] cursor-pointer rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n        onClick={replay}\n        type=\"button\"\n      >\n        <svg aria-hidden className=\"absolute inset-0 size-full overflow-visible\" viewBox={`${VB.x} ${VB.y} ${VB.w} ${VB.h}`}>\n          <motion.ellipse\n            className=\"fill-foreground/8\"\n            cx={200}\n            cy={219}\n            rx={130}\n            ry={4}\n          />\n          <rect className=\"fill-card stroke-foreground/25\" height={122} rx={10} strokeWidth={1.5} width={168} x={116} y={22} />\n          <rect className=\"fill-foreground/[0.05]\" height={104} rx={5} width={152} x={124} y={30} />\n          <g className=\"fill-foreground/15\">\n            <circle cx={133} cy={38} r={2} />\n            <circle cx={140} cy={38} r={2} />\n            <circle cx={147} cy={38} r={2} />\n          </g>\n\n          <motion.g animate={{ opacity: finished ? 0 : 1 }} initial={false} transition={{ duration: 0.25 }}>\n            {LINES.map((line, i) => {\n              const f = revealed(i);\n              const w = line.w * f;\n              return (\n                <g key={line.w}>\n                  <rect className=\"fill-foreground/[0.07]\" height={5} rx={2.5} width={line.w} x={134} y={50 + i * 11} />\n                  {w > 0 && <rect className=\"fill-foreground/25\" height={5} rx={2.5} width={Math.max(5, w)} x={134} y={50 + i * 11} />}\n                  {w > 0 && (\n                    <rect className=\"fill-(--hue)\" height={5} rx={2.5} width={Math.max(5, Math.min(w, line.kw))} x={134} y={50 + i * 11} />\n                  )}\n                </g>\n              );\n            })}\n            <motion.rect\n              animate={live ? { opacity: [1, 1, 0, 0] } : { opacity: 1 }}\n              className=\"fill-(--hue-deep) dark:fill-(--hue-hi)\"\n              height={8}\n              rx={1}\n              transition={live ? { duration: 1, ease: \"linear\", repeat: Number.POSITIVE_INFINITY, times: [0, 0.5, 0.5, 1] } : { duration: 0 }}\n              width={2}\n              x={caretX}\n              y={48.5 + caretLine * 11}\n            />\n            <rect className=\"fill-foreground/10\" height={4} rx={2} width={132} x={134} y={110} />\n            <motion.rect className=\"fill-(--hue)\" height={4} rx={2} style={{ width: barWidth }} x={134} y={110} />\n          </motion.g>\n\n          <motion.g\n            animate={finished ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.92 }}\n            initial={false}\n            style={{ originX: \"200px\", originY: \"70px\", transformBox: \"view-box\" }}\n            transition={{ duration: reduce ? 0.2 : 0.3, ease: [0.23, 1, 0.32, 1], delay: finished ? 0.1 : 0 }}\n          >\n            <circle className=\"fill-success\" cx={200} cy={70} r={17} />\n            <motion.path\n              animate={{ pathLength: finished ? 1 : 0 }}\n              className=\"fill-none stroke-white\"\n              d=\"M192 70.5 l5.5 5.5 l10 -11\"\n              initial={false}\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={3.5}\n              transition={{ duration: reduce ? 0 : 0.35, ease: \"easeOut\", delay: finished ? 0.25 : 0 }}\n            />\n            <rect className=\"fill-foreground/15\" height={5} rx={2.5} width={64} x={168} y={100} />\n          </motion.g>\n\n          <rect className=\"fill-foreground/15\" height={6} rx={3} width={180} x={110} y={144} />\n          <path className=\"fill-card stroke-foreground/25\" d=\"M110 150 H290 L322 206 H78 Z\" strokeLinejoin=\"round\" strokeWidth={1.5} />\n          <path\n            className=\"fill-card stroke-foreground/25\"\n            d=\"M78 206 H322 v4 a4 4 0 0 1 -4 4 H82 a4 4 0 0 1 -4 -4 Z\"\n            strokeLinejoin=\"round\"\n            strokeWidth={1.5}\n          />\n\n          {KEYS.map((k, i) => {\n            const on = tick.lit.includes(i);\n            return (\n              <g key={`${k.x.toFixed(1)}-${k.y}`}>\n                <rect className=\"fill-foreground/[0.07]\" height={k.h} rx={2} width={k.w} x={k.x} y={k.y} />\n                <motion.rect\n                  animate={{ opacity: on ? 1 : 0 }}\n                  className=\"fill-(--hue-hi)\"\n                  height={k.h + 4}\n                  initial={false}\n                  rx={3.5}\n                  transition={{ duration: on ? 0.06 : 0.5, ease: \"easeOut\" }}\n                  width={k.w + 4}\n                  x={k.x - 2}\n                  y={k.y - 2}\n                />\n              </g>\n            );\n          })}\n\n          <motion.g style={{ opacity: catOpacity, rotate: catRotate, x: catX, y: catY, originX: \"236px\", originY: \"196px\", transformBox: \"view-box\" }}>\n            <motion.g style={{ originX: \"292px\", originY: \"198px\", scaleX: stretchX, scaleY: stretchY, transformBox: \"view-box\" }}>\n              <motion.g\n                animate={live && !awake ? { rotate: [0, 5, -3, 0] } : { rotate: 0 }}\n                style={{ originX: \"282px\", originY: \"184px\", transformBox: \"view-box\" }}\n                transition={live && !awake ? { duration: 3.2, ease: \"easeInOut\", repeat: Number.POSITIVE_INFINITY } : { duration: 0.3 }}\n              >\n                <path className=\"fill-none stroke-(--hue)\" d=\"M280 184 C 306 192 302 212 270 211 C 256 210.5 246 208 238 205\" strokeLinecap=\"round\" strokeWidth={12} />\n                <motion.path\n                  animate={live && !awake ? { rotate: [0, -10, 4, 0] } : { rotate: 0 }}\n                  className=\"fill-none stroke-(--soft)\"\n                  d=\"M250 208.5 C 245 207.5 241 206 238 205\"\n                  strokeLinecap=\"round\"\n                  strokeWidth={12}\n                  style={{ originX: \"252px\", originY: \"209px\", transformBox: \"view-box\" }}\n                  transition={live && !awake ? { duration: 1.6, ease: \"easeInOut\", repeat: Number.POSITIVE_INFINITY, repeatDelay: 0.8 } : { duration: 0.3 }}\n                />\n              </motion.g>\n\n              <motion.g\n                animate={live && !awake ? { scaleY: [1, 1.035, 1] } : { scaleY: 1 }}\n                style={{ originX: \"232px\", originY: \"198px\", transformBox: \"view-box\" }}\n                transition={live && !awake ? { duration: 3, ease: \"easeInOut\", repeat: Number.POSITIVE_INFINITY } : { duration: 0.3 }}\n              >\n                <ellipse className=\"fill-(--hue)\" cx={236} cy={174} rx={56} ry={26} />\n                <ellipse className=\"fill-(--hue)\" cx={268} cy={182} rx={24} ry={18} />\n                <path className=\"fill-none stroke-white/30\" d=\"M214 154 q16 -8 34 -6\" strokeLinecap=\"round\" strokeWidth={4} />\n                <g className=\"fill-none stroke-(--hue-deep)\" strokeLinecap=\"round\" strokeWidth={4}>\n                  <path d=\"M232 149 q3 7 0 13\" />\n                  <path d=\"M247 150 q3 7 0 12\" />\n                  <path d=\"M262 154 q2.5 6 0 11\" />\n                </g>\n              </motion.g>\n\n              <motion.g\n                animate={awake ? { y: -6, rotate: -4 } : { y: 0, rotate: 0 }}\n                style={{ originX: \"180px\", originY: \"184px\", transformBox: \"view-box\" }}\n                transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 320, damping: 22 }}\n              >\n                {[0, 1].map((ear) => (\n                  <motion.g\n                    animate={\n                      awake\n                        ? { rotate: 0, scaleY: 1.08 }\n                        : twitch.n > 0 && twitch.ear === ear && !reduce\n                          ? { rotate: [0, ear ? 16 : -16, 0, ear ? 9 : -9, 0], scaleY: 1 }\n                          : { rotate: 0, scaleY: 1 }\n                    }\n                    key={`${ear}-${twitch.ear === ear ? twitch.n : 0}`}\n                    style={{\n                      originX: ear ? \"194px\" : \"166px\",\n                      originY: \"150px\",\n                      transformBox: \"view-box\",\n                    }}\n                    transition={{ duration: 0.42, ease: \"easeOut\" }}\n                  >\n                    <path\n                      className=\"fill-(--hue) stroke-(--hue)\"\n                      d={ear ? \"M184 146 L202 124 L206 152 Z\" : \"M154 152 L158 124 L176 146 Z\"}\n                      strokeLinejoin=\"round\"\n                      strokeWidth={5}\n                    />\n                    <path className=\"fill-rose-300\" d={ear ? \"M190 145 L200 132 L202 148 Z\" : \"M159 148 L161 132 L171 145 Z\"} />\n                  </motion.g>\n                ))}\n                <ellipse className=\"fill-(--hue)\" cx={180} cy={164} rx={31} ry={25} />\n                <ellipse className=\"fill-(--soft)\" cx={180} cy={176} rx={14} ry={9} />\n                <g className=\"fill-rose-400/45\">\n                  <ellipse cx={162} cy={172} rx={5} ry={3} />\n                  <ellipse cx={198} cy={172} rx={5} ry={3} />\n                </g>\n                {[0, 1].map((eye) => {\n                  const cx = eye ? 190 : 170;\n                  const open = awake || peek === eye;\n                  return (\n                    <g key={eye}>\n                      <motion.g animate={{ opacity: open ? 0 : 1 }} initial={false} transition={{ duration: 0.12 }}>\n                        <path className=\"fill-none stroke-(--ink)\" d={`M${cx - 6} 163 q6 5 12 0`} strokeLinecap=\"round\" strokeWidth={2.6} />\n                      </motion.g>\n                      <motion.g\n                        animate={{ opacity: open ? 1 : 0, scaleY: open ? 1 : 0.3 }}\n                        initial={false}\n                        style={{ originX: `${cx}px`, originY: \"164px\", transformBox: \"view-box\" }}\n                        transition={{ duration: 0.14, ease: \"easeOut\" }}\n                      >\n                        <ellipse className=\"fill-(--ink)\" cx={cx} cy={164} rx={3.8} ry={4.8} />\n                        <circle className=\"fill-white\" cx={cx - 1.3} cy={162.2} r={1.4} />\n                      </motion.g>\n                    </g>\n                  );\n                })}\n                <path className=\"fill-rose-400\" d=\"M177.5 172 h5 l-2.5 3 Z\" strokeLinejoin=\"round\" />\n                <path className=\"fill-none stroke-(--ink)\" d=\"M180 175 q-2 3.2 -5 1.6 M180 175 q2 3.2 5 1.6\" strokeLinecap=\"round\" strokeWidth={1.3} />\n                <g className=\"stroke-(--ink)/30\" strokeLinecap=\"round\" strokeWidth={1.1}>\n                  <line x1={166} x2={146} y1={176} y2={173} />\n                  <line x1={166} x2={147} y1={179} y2={181} />\n                  <line x1={194} x2={214} y1={176} y2={173} />\n                  <line x1={194} x2={213} y1={179} y2={181} />\n                </g>\n              </motion.g>\n\n              {PAWS.map((paw, i) => (\n                <motion.g\n                  animate={{ y: kneading && tick.beat % 2 === i ? 2.5 : 0 }}\n                  initial={false}\n                  key={paw.x}\n                  transition={{ duration: 0.16, ease: \"easeOut\" }}\n                >\n                  <ellipse className=\"fill-(--soft)\" cx={paw.x} cy={paw.y} rx={9} ry={5.5} />\n                  <path className=\"fill-none stroke-(--hue)/70\" d={`M${paw.x - 3} ${paw.y + 1} v3 M${paw.x + 3} ${paw.y + 1} v3`} strokeLinecap=\"round\" strokeWidth={1.4} />\n                </motion.g>\n              ))}\n            </motion.g>\n          </motion.g>\n        </svg>\n      </button>\n\n      <div className=\"relative mt-3 text-center\" aria-live=\"polite\">\n        <p className=\"text-balance font-medium text-foreground text-sm\">{finished ? doneLabel : label}</p>\n        <p className=\"mx-auto mt-1 max-w-[34ch] text-pretty text-muted-foreground text-xs\">\n          {finished ? (\n            doneDescription\n          ) : (\n            <>\n              <span className=\"font-medium text-foreground tabular-nums\">{pct}%</span> · {name} is keeping the keys warm\n            </>\n          )}\n        </p>\n      </div>\n    </div>\n  );\n}\n"
    }
  ]
}