{
  "slug": "morph-search",
  "name": "Morph Search",
  "description": "Adaptive morphing search bar with cycling placeholder, spring physics, and intent card morphs.",
  "summary": "An ultra-fluid adaptive search bar inspired by Shapeshift. Features a pill-shaped input with continuous cycling placeholders, spring-driven opening and closing morphs, and interactive contextual card transformations for polls, events, checklists, timers, expense splits, and instant search queries.",
  "category": "search",
  "tags": [
    "search",
    "input",
    "morph",
    "shapeshift",
    "spring",
    "motion",
    "card",
    "intent"
  ],
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion"
  ],
  "version": "1.0.0",
  "createdDate": "2026-09-23",
  "updatedDate": "2026-09-23",
  "interactive": true,
  "supportsColor": false,
  "highlights": [
    "Smooth layout morphing from closed pill to expanded command card with spring physics",
    "Dynamic cycling placeholder with smooth vertical crossfade and blur transition",
    "Contextual intent morphs: interactive polls, event schedules, task checklists, timers, and bill splits",
    "Keyboard shortcuts: '/' and Cmd/Ctrl+K open the search, Enter submits, Escape clears or closes",
    "Typography-first design with no icons or emojis"
  ],
  "anatomy": [
    "<MorphSearch> (Main interactive container with spring layout morph)",
    "<input> (Search and prompt text input)",
    "Cycling placeholder (Animated crossfading prompt suggestions)",
    "Poll card section (Voting options with percentage bars)",
    "Event card section (Schedule preview with time and attendee tags)",
    "Checklist card section (Tasks with completion toggles)",
    "Timer card section (Focus countdown with controls)",
    "Split card section (Bill split breakdown per person)"
  ],
  "physics": {
    "engine": "Motion Spring Solver",
    "description": "Spring transition on the container layout (stiffness 440, damping 38, mass 0.8), close to critically damped, so it settles without visible overshoot.",
    "parameters": [
      {
        "label": "Stiffness",
        "value": "440"
      },
      {
        "label": "Damping",
        "value": "38"
      },
      {
        "label": "Mass",
        "value": "0.8"
      },
      {
        "label": "Corner Radius",
        "value": "28px (constant)"
      }
    ]
  },
  "accessibility": {
    "role": "textbox",
    "aria": "The input has aria-label \"Search or enter prompt\" and the Clear button is labelled. The open and close shortcut buttons have no accessible label.",
    "reducedMotion": "With prefers-reduced-motion, placeholder cycling stops and the spring layout animation becomes a 0.14s tween; placeholder and panel entrances drop the blur and slide offsets.",
    "keyboard": [
      {
        "key": "/ or Cmd/Ctrl + K",
        "description": "Open and focus the search (ignored while typing in another field)."
      },
      {
        "key": "Enter",
        "description": "Submit and save the current entry."
      },
      {
        "key": "Escape",
        "description": "Clear the input; if it is already empty, close the search."
      }
    ]
  },
  "guidelines": {
    "recommended": [
      "Primary search bar and copilot entry points",
      "Interactive command palettes and multi-intent query prompts",
      "Landing page search demonstrations and productivity tools"
    ],
    "bestPractices": [
      "Provide an onSubmit callback to handle confirmed user prompts",
      "Pass placeholder to replace the built-in cycling examples with your own text",
      "Use subtle backdrop-blur panels for elevated contrast"
    ]
  },
  "props": [
    {
      "name": "value",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Controlled search query string."
    },
    {
      "name": "defaultValue",
      "type": "string",
      "defaultValue": "\"\"",
      "description": "Initial uncontrolled query string."
    },
    {
      "name": "onChange",
      "type": "(value: string) => void",
      "defaultValue": "undefined",
      "description": "Callback fired whenever the input text changes."
    },
    {
      "name": "onSubmit",
      "type": "(value: string, intent?: string) => void",
      "defaultValue": "undefined",
      "description": "Callback fired when Enter is pressed or an action is submitted."
    },
    {
      "name": "onClear",
      "type": "() => void",
      "defaultValue": "undefined",
      "description": "Callback fired when query is cleared."
    },
    {
      "name": "placeholder",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Custom placeholder text to override cycling placeholders."
    },
    {
      "name": "className",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Optional custom CSS class name for the wrapper."
    },
    {
      "name": "autoFocus",
      "type": "boolean",
      "defaultValue": "false",
      "description": "Whether the input should auto-focus on initial mount."
    }
  ],
  "usage": "\"use client\";\n\nimport { MorphSearch } from \"@/components/ui/morph-search\";\n\nexport function MorphSearchDemo() {\n  return <MorphSearch onSubmit={(value) => console.log(value)} />;\n}",
  "files": [
    {
      "name": "morph-search.tsx",
      "path": "registry/ui/morph-search.tsx",
      "code": "\"use client\";\n\nimport React, {\n  useCallback,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n  type Transition,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface MorphSearchProps {\n  value?: string;\n  defaultValue?: string;\n  onChange?: (value: string) => void;\n  onSubmit?: (value: string, intent?: string) => void;\n  onClear?: () => void;\n  placeholder?: string;\n  className?: string;\n  autoFocus?: boolean;\n}\n\ntype IntentType = \"poll\" | \"event\" | \"todo\" | \"timer\" | \"split\" | \"search\";\n\ninterface PollData {\n  title: string;\n  options: string[];\n}\n\ninterface EventData {\n  title: string;\n  time: string;\n  attendee: string;\n}\n\ninterface TodoData {\n  items: string[];\n}\n\ninterface TimerData {\n  seconds: number;\n  label: string;\n}\n\ninterface SplitData {\n  total: number;\n  people: number;\n  perPerson: number;\n}\n\ninterface DetectedIntent {\n  type: IntentType;\n  poll?: PollData;\n  event?: EventData;\n  todo?: TodoData;\n  timer?: TimerData;\n  split?: SplitData;\n}\n\ninterface SavedEntry {\n  id: number;\n  intent: IntentType;\n  text: string;\n  summary: string;\n  timestamp: number;\n}\n\nconst PLACEHOLDERS = [\n  \"pizza or burgers for friday?\",\n  \"dinner with priya friday 8pm\",\n  \"buy milk, eggs, bread and coffee\",\n  \"25 min focus\",\n  \"split 2400 between 3\",\n  \"explore components and hooks\",\n];\n\nconst springMorph: Transition = {\n  type: \"spring\",\n  stiffness: 440,\n  damping: 38,\n  mass: 0.8,\n};\n\nconst fadeTween: Transition = {\n  duration: 0.14,\n  ease: [0.23, 1, 0.32, 1],\n};\n\nfunction parseQuery(text: string): DetectedIntent {\n  const clean = text.trim();\n  const lower = clean.toLowerCase();\n\n  if (!clean) {\n    return { type: \"search\" };\n  }\n\n  if (\n    lower.includes(\" or \") ||\n    lower.includes(\" vs \") ||\n    (lower.endsWith(\"?\") &&\n      (lower.includes(\"pizza\") || lower.includes(\"burger\")))\n  ) {\n    const rawNoQ = clean.replace(/\\?+$/, \"\");\n    const parts = rawNoQ.split(/\\s+(?:or|vs\\.?)\\s+/i).map((s) => s.trim());\n    if (parts.length >= 2) {\n      const opts = parts.map((p) => {\n        const words = p.split(/\\s+/);\n        return words.length > 3 ? words.slice(-2).join(\" \") : p;\n      });\n      return {\n        type: \"poll\",\n        poll: {\n          title: clean.endsWith(\"?\") ? clean : `${clean}?`,\n          options: opts.slice(0, 4),\n        },\n      };\n    }\n  }\n\n  if (\n    /^(?:split|divide|share)\\b/i.test(lower) ||\n    (/\\bbetween\\s+\\d+/i.test(lower) && /\\d+/.test(lower))\n  ) {\n    const numbers = lower.match(/\\d+(?:\\.\\d+)?/g);\n    if (numbers && numbers.length >= 2) {\n      const total = parseFloat(numbers[0]);\n      const people = parseInt(numbers[1], 10) || 1;\n      const perPerson = Math.round((total / Math.max(1, people)) * 100) / 100;\n      return {\n        type: \"split\",\n        split: { total, people, perPerson },\n      };\n    }\n  }\n\n  if (\n    /\\b(?:timer|focus|break|pomodoro)\\b/i.test(lower) ||\n    /^\\d+\\s*(?:m|min|mins|minutes|s|sec|seconds|h|hr|hrs|hours)\\b/i.test(lower)\n  ) {\n    let secs = 25 * 60;\n    const match = lower.match(\n      /(\\d+)\\s*(m|min|mins|minutes|s|sec|seconds|h|hr|hrs|hours)?/i,\n    );\n    if (match) {\n      const num = parseInt(match[1], 10);\n      const unit = match[2]?.charAt(0) || \"m\";\n      if (unit === \"h\") secs = num * 3600;\n      else if (unit === \"s\") secs = num;\n      else secs = num * 60;\n    }\n    const label = lower\n      .replace(\n        /\\d+\\s*(?:m|min|mins|minutes|s|sec|seconds|h|hr|hrs|hours)?/i,\n        \"\",\n      )\n      .replace(/timer|for/gi, \"\")\n      .trim();\n    return {\n      type: \"timer\",\n      timer: {\n        seconds: Math.max(10, secs),\n        label: label\n          ? label.charAt(0).toUpperCase() + label.slice(1)\n          : \"Focus Session\",\n      },\n    };\n  }\n\n  if (\n    /^(?:buy|get|todo|tasks|groceries|shopping list)\\b/i.test(lower) ||\n    (clean.includes(\",\") && (clean.includes(\"and\") || clean.includes(\"&\")))\n  ) {\n    const sanitized = clean\n      .replace(/^(?:buy|get|todo|tasks|groceries|shopping list):?\\s*/i, \"\")\n      .replace(/\\band\\b/gi, \",\");\n    const rawItems = sanitized\n      .split(/[,;\\n]/)\n      .map((i) => i.trim())\n      .filter((i) => i.length > 0);\n    if (rawItems.length >= 2) {\n      return {\n        type: \"todo\",\n        todo: {\n          items: rawItems.map(\n            (item) => item.charAt(0).toUpperCase() + item.slice(1),\n          ),\n        },\n      };\n    }\n  }\n\n  if (\n    /\\b(?:dinner|lunch|breakfast|coffee|meeting|sync|call|party|interview|session)\\b/i.test(\n      lower,\n    ) ||\n    /\\b(?:today|tomorrow|tonight|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\b/i.test(\n      lower,\n    )\n  ) {\n    const withMatch = clean.match(/\\bwith\\s+([A-Za-z]+)/i);\n    const timeMatch = clean.match(\n      /\\b(?:at\\s+)?(\\d{1,2}(?::\\d{2})?\\s*(?:am|pm)?|\\d{1,2}\\s*(?:am|pm)|noon|midnight)\\b/i,\n    );\n    const dayMatch = clean.match(\n      /\\b(today|tomorrow|tonight|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\b/i,\n    );\n    const timeStr = [\n      dayMatch\n        ? dayMatch[1].charAt(0).toUpperCase() + dayMatch[1].slice(1)\n        : \"Upcoming\",\n      timeMatch ? timeMatch[1].toUpperCase() : \"8:00 PM\",\n    ].join(\", \");\n\n    return {\n      type: \"event\",\n      event: {\n        title: clean.charAt(0).toUpperCase() + clean.slice(1),\n        time: timeStr,\n        attendee: withMatch\n          ? withMatch[1].charAt(0).toUpperCase() + withMatch[1].slice(1)\n          : \"Team\",\n      },\n    };\n  }\n\n  return { type: \"search\" };\n}\n\nfunction formatTimer(secs: number): string {\n  const m = Math.floor(secs / 60);\n  const s = secs % 60;\n  return `${String(m).padStart(2, \"0\")}:${String(s).padStart(2, \"0\")}`;\n}\n\nexport function MorphSearch({\n  value: controlledValue,\n  defaultValue = \"\",\n  onChange,\n  onSubmit,\n  onClear,\n  placeholder,\n  className,\n  autoFocus = false,\n}: MorphSearchProps) {\n  const isControlled = controlledValue !== undefined;\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const query = isControlled ? controlledValue : internalValue;\n\n  const [isOpen, setIsOpen] = useState(false);\n  const [placeholderIndex, setPlaceholderIndex] = useState(0);\n  const [activeCategory, setActiveCategory] = useState<string>(\"all\");\n  const [savedItems, setSavedItems] = useState<SavedEntry[]>([]);\n\n  const [pollVotes, setPollVotes] = useState<Record<number, number>>({});\n  const [completedTodos, setCompletedTodos] = useState<Record<number, boolean>>(\n    {},\n  );\n  const [timerRunning, setTimerRunning] = useState(false);\n  const [timerTimeLeft, setTimerTimeLeft] = useState<number | null>(null);\n\n  const containerRef = useRef<HTMLDivElement>(null);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const reduceMotion = useReducedMotion();\n  const inputId = useId();\n\n  const detected = useMemo(() => parseQuery(query), [query]);\n\n  useEffect(() => {\n    if (reduceMotion) return;\n    const interval = setInterval(() => {\n      setPlaceholderIndex((prev) => (prev + 1) % PLACEHOLDERS.length);\n    }, 2800);\n    return () => clearInterval(interval);\n  }, [reduceMotion]);\n\n  const [prevQuery, setPrevQuery] = useState(query);\n  if (query !== prevQuery) {\n    setPrevQuery(query);\n    setPollVotes({});\n    setCompletedTodos({});\n    setTimerRunning(false);\n    setTimerTimeLeft(\n      detected.type === \"timer\" && detected.timer\n        ? detected.timer.seconds\n        : null,\n    );\n  }\n\n  useEffect(() => {\n    if (!timerRunning || timerTimeLeft === null || timerTimeLeft <= 0) return;\n    const interval = setInterval(() => {\n      setTimerTimeLeft((prev) => {\n        if (prev === null || prev <= 1) {\n          setTimerRunning(false);\n          return 0;\n        }\n        return prev - 1;\n      });\n    }, 1000);\n    return () => clearInterval(interval);\n  }, [timerRunning, timerTimeLeft]);\n\n  const updateQuery = useCallback(\n    (newVal: string) => {\n      if (!isControlled) {\n        setInternalValue(newVal);\n      }\n      onChange?.(newVal);\n    },\n    [isControlled, onChange],\n  );\n\n  const handleOpen = useCallback(() => {\n    setIsOpen(true);\n    requestAnimationFrame(() => {\n      inputRef.current?.focus();\n    });\n  }, []);\n\n  const handleClose = useCallback(() => {\n    setIsOpen(false);\n    inputRef.current?.blur();\n  }, []);\n\n  const closeToTrigger = useCallback(() => {\n    handleClose();\n    triggerRef.current?.focus();\n  }, [handleClose]);\n\n  const handleClear = useCallback(() => {\n    updateQuery(\"\");\n    onClear?.();\n    inputRef.current?.focus();\n  }, [updateQuery, onClear]);\n\n  const saveCurrentItem = useCallback(() => {\n    const trimmed = query.trim();\n    if (!trimmed) return;\n\n    let summary = trimmed;\n    if (detected.type === \"poll\" && detected.poll) {\n      summary = detected.poll.title;\n    } else if (detected.type === \"event\" && detected.event) {\n      summary = `${detected.event.title} · ${detected.event.time}`;\n    } else if (detected.type === \"todo\" && detected.todo) {\n      summary = detected.todo.items.join(\", \");\n    } else if (detected.type === \"timer\" && detected.timer) {\n      summary = `${detected.timer.label} · ${formatTimer(detected.timer.seconds)}`;\n    } else if (detected.type === \"split\" && detected.split) {\n      summary = `₹${detected.split.total} among ${detected.split.people} (₹${detected.split.perPerson}/person)`;\n    }\n\n    const newItem: SavedEntry = {\n      id: Date.now() + Math.random(),\n      intent: detected.type,\n      text: trimmed,\n      summary,\n      timestamp: Date.now(),\n    };\n\n    setSavedItems((prev) => [newItem, ...prev.slice(0, 8)]);\n    onSubmit?.(trimmed, detected.type);\n    updateQuery(\"\");\n    setIsOpen(false);\n  }, [query, detected, onSubmit, updateQuery]);\n\n  const reopenItem = (item: SavedEntry) => {\n    updateQuery(item.text);\n    setIsOpen(true);\n    requestAnimationFrame(() => {\n      inputRef.current?.focus();\n    });\n  };\n\n  const removeItem = (id: number) => {\n    setSavedItems((prev) => prev.filter((item) => item.id !== id));\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === \"Escape\") {\n      e.preventDefault();\n      if (query) {\n        handleClear();\n      } else {\n        closeToTrigger();\n      }\n    } else if (e.key === \"Enter\" && !e.nativeEvent.isComposing) {\n      e.preventDefault();\n      saveCurrentItem();\n    }\n  };\n\n  useEffect(() => {\n    const handleGlobalKeyDown = (e: KeyboardEvent) => {\n      const activeEl = document.activeElement;\n      const isInput =\n        activeEl instanceof HTMLInputElement ||\n        activeEl instanceof HTMLTextAreaElement ||\n        activeEl?.getAttribute(\"contenteditable\") === \"true\";\n\n      if (\n        (e.key === \"/\" || (e.key === \"k\" && (e.metaKey || e.ctrlKey))) &&\n        !isInput\n      ) {\n        e.preventDefault();\n        handleOpen();\n      }\n    };\n    window.addEventListener(\"keydown\", handleGlobalKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleGlobalKeyDown);\n  }, [handleOpen]);\n\n  useEffect(() => {\n    const handleClickOutside = (e: MouseEvent) => {\n      if (\n        containerRef.current &&\n        !containerRef.current.contains(e.target as Node)\n      ) {\n        handleClose();\n      }\n    };\n    if (isOpen) {\n      document.addEventListener(\"mousedown\", handleClickOutside);\n    }\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, [isOpen, handleClose]);\n\n  const voteOnPoll = (index: number) => {\n    setPollVotes((prev) => ({\n      ...prev,\n      [index]: (prev[index] || 0) + 1,\n    }));\n  };\n\n  const toggleTodo = (index: number) => {\n    setCompletedTodos((prev) => ({\n      ...prev,\n      [index]: !prev[index],\n    }));\n  };\n\n  const sampleChips = [\n    { label: \"All\", id: \"all\" },\n    { label: \"Poll\", id: \"poll\", sample: \"pizza or burgers for friday?\" },\n    { label: \"Event\", id: \"event\", sample: \"dinner with priya friday 8pm\" },\n    { label: \"Todo\", id: \"todo\", sample: \"buy milk, eggs, bread and coffee\" },\n    { label: \"Timer\", id: \"timer\", sample: \"25 min focus\" },\n    { label: \"Split\", id: \"split\", sample: \"split 2400 between 3\" },\n  ];\n\n  const searchResults = useMemo(() => {\n    if (detected.type !== \"search\" || !query.trim()) return [];\n    const pool = [\n      {\n        title: \"Navigation Components\",\n        category: \"Components\",\n        desc: \"Gooey navbar and fluid floating tab selectors\",\n      },\n      {\n        title: \"Action Controls\",\n        category: \"Inputs\",\n        desc: \"Tactile buttons, liquid toggles, and switches\",\n      },\n      {\n        title: \"Micro Physics Engines\",\n        category: \"Motion\",\n        desc: \"Spring solvers, collision dynamics, and kinetic easing\",\n      },\n      {\n        title: \"Theming & Palette Matrix\",\n        category: \"Tokens\",\n        desc: \"Adaptive dark mode palettes and high contrast curves\",\n      },\n    ];\n    return pool.filter(\n      (item) =>\n        item.title.toLowerCase().includes(query.toLowerCase()) ||\n        item.desc.toLowerCase().includes(query.toLowerCase()) ||\n        item.category.toLowerCase().includes(query.toLowerCase()),\n    );\n  }, [detected.type, query]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\"relative w-full max-w-140 mx-auto select-none\", className)}\n    >\n      <motion.div\n        layout\n        transition={reduceMotion ? fadeTween : springMorph}\n        style={{ borderRadius: 28 }}\n        className={cn(\n          \"relative overflow-hidden border border-zinc-200/80 bg-white/95 backdrop-blur-2xl dark:border-white/10 dark:bg-[#0d0d12]/95\",\n          isOpen\n            ? \"border-zinc-300 shadow-[0_24px_64px_rgba(0,0,0,0.08),0_0_0_1px_rgba(0,0,0,0.04)] dark:border-white/20 dark:shadow-[0_24px_64px_rgba(0,0,0,0.7),0_0_0_1px_rgba(255,255,255,0.06)]\"\n            : \"hover:border-zinc-300 shadow-[0_8px_32px_rgba(0,0,0,0.06),0_1px_1px_rgba(0,0,0,0.02)] dark:hover:border-white/15 dark:shadow-[0_8px_32px_rgba(0,0,0,0.36),0_1px_1px_rgba(255,255,255,0.04)]\",\n        )}\n      >\n        <motion.div\n          layout=\"position\"\n          transition={reduceMotion ? fadeTween : springMorph}\n          className=\"relative flex h-14.5 items-center px-6 gap-3\"\n        >\n          <div className=\"relative flex-1 flex items-center h-full min-w-0\">\n            <input\n              id={inputId}\n              ref={inputRef}\n              autoFocus={autoFocus}\n              autoComplete=\"off\"\n              spellCheck={false}\n              value={query}\n              onFocus={handleOpen}\n              onChange={(e) => updateQuery(e.target.value)}\n              onKeyDown={handleKeyDown}\n              className=\"w-full bg-transparent text-[17px] font-normal tracking-[-0.01em] text-zinc-900 caret-zinc-900 dark:text-white dark:caret-white outline-none placeholder:text-transparent\"\n              aria-label=\"Search or enter prompt\"\n            />\n\n            {!query && (\n              <span className=\"pointer-events-none absolute inset-0 flex items-center overflow-hidden\">\n                <AnimatePresence mode=\"popLayout\" initial={false}>\n                  <motion.span\n                    key={placeholderIndex}\n                    initial={\n                      reduceMotion\n                        ? { opacity: 0 }\n                        : { opacity: 0, y: 8, filter: \"blur(4px)\" }\n                    }\n                    animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n                    exit={\n                      reduceMotion\n                        ? { opacity: 0 }\n                        : { opacity: 0, y: -8, filter: \"blur(4px)\" }\n                    }\n                    transition={fadeTween}\n                    className=\"truncate text-[17px] font-normal tracking-[-0.01em] text-zinc-400 dark:text-zinc-400\"\n                  >\n                    {placeholder ?? PLACEHOLDERS[placeholderIndex]}\n                  </motion.span>\n                </AnimatePresence>\n              </span>\n            )}\n          </div>\n\n          <div className=\"flex items-center gap-2 shrink-0\">\n            {query && (\n              <button\n                type=\"button\"\n                onClick={handleClear}\n                className=\"text-xs text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white px-1.5 py-0.5 rounded transition-colors cursor-pointer\"\n                aria-label=\"Clear input\"\n              >\n                Clear\n              </button>\n            )}\n\n            <button\n              ref={triggerRef}\n              type=\"button\"\n              onClick={isOpen ? closeToTrigger : handleOpen}\n              aria-label={isOpen ? \"Close search\" : \"Open search\"}\n              aria-expanded={isOpen}\n              className=\"relative flex items-center rounded-full border border-zinc-200 bg-zinc-100/80 px-2 py-0.5 text-[11px] font-mono text-zinc-500 hover:text-zinc-900 hover:border-zinc-300 dark:border-white/10 dark:bg-white/5 dark:text-zinc-400 dark:hover:text-white dark:hover:border-white/20 transition-colors cursor-pointer after:absolute after:-inset-x-2.5 after:-inset-y-3 outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 dark:focus-visible:ring-white/40\"\n            >\n              <span>{isOpen ? \"ESC\" : \"/\"}</span>\n            </button>\n          </div>\n        </motion.div>\n\n        <AnimatePresence mode=\"popLayout\" initial={false}>\n          {isOpen && (\n            <motion.div\n              key=\"content-body\"\n              initial={\n                reduceMotion\n                  ? { opacity: 0 }\n                  : { opacity: 0, filter: \"blur(4px)\" }\n              }\n              animate={{ opacity: 1, filter: \"blur(0px)\" }}\n              exit={{\n                opacity: 0,\n                filter: \"blur(4px)\",\n                transition: { duration: 0.12, ease: [0.23, 1, 0.32, 1] },\n              }}\n              transition={\n                reduceMotion ? fadeTween : { ...springMorph, delay: 0.02 }\n              }\n              className=\"w-full overflow-hidden border-t border-zinc-200/80 dark:border-white/8\"\n            >\n              <div className=\"p-5 flex flex-col gap-4\">\n                {detected.type === \"poll\" && detected.poll && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Poll\n                      </span>\n                      <span>Select an option to vote</span>\n                    </div>\n\n                    <h3 className=\"text-[15px] font-medium text-zinc-900 dark:text-white\">\n                      {detected.poll.title}\n                    </h3>\n\n                    <div className=\"flex flex-col gap-2\">\n                      {detected.poll.options.map((opt, idx) => {\n                        const totalVotes = Object.values(pollVotes).reduce(\n                          (a, b) => a + b,\n                          0,\n                        );\n                        const votes = pollVotes[idx] || 0;\n                        const pct =\n                          totalVotes > 0\n                            ? Math.round((votes / totalVotes) * 100)\n                            : 0;\n\n                        return (\n                          <button\n                            key={idx}\n                            type=\"button\"\n                            onClick={() => voteOnPoll(idx)}\n                            className=\"group relative flex items-center justify-between overflow-hidden rounded-lg border border-zinc-200/80 bg-zinc-100/60 px-3.5 py-2.5 text-left text-xs transition-colors hover:border-zinc-300 hover:bg-zinc-100 dark:border-white/8 dark:bg-white/4 dark:hover:border-white/15 dark:hover:bg-white/7 cursor-pointer\"\n                          >\n                            <motion.div\n                              className=\"absolute inset-y-0 left-0 bg-zinc-900/10 dark:bg-white/10\"\n                              initial={{ width: \"0%\" }}\n                              animate={{ width: `${pct}%` }}\n                              transition={springMorph}\n                            />\n                            <span className=\"relative z-10 font-medium text-zinc-800 group-hover:text-zinc-950 dark:text-zinc-200 dark:group-hover:text-white\">\n                              {opt}\n                            </span>\n                            <span className=\"relative z-10 font-mono text-zinc-500 dark:text-zinc-400 tabular-nums\">\n                              {pct}%\n                            </span>\n                          </button>\n                        );\n                      })}\n                    </div>\n\n                    <div className=\"flex items-center justify-between pt-1\">\n                      <span className=\"text-[11px] text-zinc-500 dark:text-zinc-400\">\n                        {Object.values(pollVotes).reduce((a, b) => a + b, 0)}{\" \"}\n                        total votes\n                      </span>\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Poll</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"event\" && detected.event && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Event\n                      </span>\n                      <span>Scheduled Meeting</span>\n                    </div>\n\n                    <h3 className=\"text-[15px] font-medium text-zinc-900 dark:text-white\">\n                      {detected.event.title}\n                    </h3>\n\n                    <div className=\"flex flex-wrap items-center gap-2\">\n                      <div className=\"rounded-md border border-zinc-200 bg-zinc-100 px-2.5 py-1 text-xs text-zinc-700 dark:border-white/8 dark:bg-white/4 dark:text-zinc-300\">\n                        <span>{detected.event.time}</span>\n                      </div>\n                      <div className=\"rounded-md border border-zinc-200 bg-zinc-100 px-2.5 py-1 text-xs text-zinc-700 dark:border-white/8 dark:bg-white/4 dark:text-zinc-300\">\n                        <span>{detected.event.attendee}</span>\n                      </div>\n                    </div>\n\n                    <div className=\"flex justify-end pt-1\">\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Event</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"todo\" && detected.todo && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Checklist\n                      </span>\n                      <span>\n                        {Object.values(completedTodos).filter(Boolean).length}/\n                        {detected.todo.items.length} completed\n                      </span>\n                    </div>\n\n                    <div className=\"flex flex-col gap-1.5\">\n                      {detected.todo.items.map((item, idx) => {\n                        const done = Boolean(completedTodos[idx]);\n                        return (\n                          <button\n                            key={idx}\n                            type=\"button\"\n                            onClick={() => toggleTodo(idx)}\n                            className=\"flex items-center gap-2.5 rounded-lg border border-zinc-200/60 bg-zinc-100/40 px-3 py-2 text-left text-xs transition-colors hover:bg-zinc-100/80 dark:border-white/6 dark:bg-white/3 dark:hover:bg-white/6 cursor-pointer\"\n                          >\n                            <span\n                              className={cn(\n                                \"flex size-3.5 shrink-0 rounded-sm border transition-colors\",\n                                done\n                                  ? \"border-zinc-900 bg-zinc-900 dark:border-white dark:bg-white\"\n                                  : \"border-zinc-300 dark:border-white/30\",\n                              )}\n                            />\n                            <span\n                              className={cn(\n                                \"font-normal transition-all\",\n                                done\n                                  ? \"text-zinc-400 line-through dark:text-zinc-500\"\n                                  : \"text-zinc-800 dark:text-zinc-200\",\n                              )}\n                            >\n                              {item}\n                            </span>\n                          </button>\n                        );\n                      })}\n                    </div>\n\n                    <div className=\"flex justify-end pt-1\">\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Tasks</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"timer\" && detected.timer && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Timer\n                      </span>\n                      <span>{detected.timer.label}</span>\n                    </div>\n\n                    <div className=\"flex items-center justify-between py-2\">\n                      <span className=\"font-mono text-3xl font-medium tracking-tight text-zinc-900 dark:text-white\">\n                        {formatTimer(timerTimeLeft ?? detected.timer.seconds)}\n                      </span>\n\n                      <div className=\"flex items-center gap-2\">\n                        <button\n                          type=\"button\"\n                          onClick={() => setTimerRunning((p) => !p)}\n                          className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                        >\n                          {timerRunning ? \"Pause\" : \"Start\"}\n                        </button>\n                        <button\n                          type=\"button\"\n                          onClick={() => {\n                            setTimerRunning(false);\n                            setTimerTimeLeft(detected.timer?.seconds ?? 1500);\n                          }}\n                          className=\"flex items-center rounded-full border border-zinc-200 bg-zinc-100 px-3 py-1 text-xs text-zinc-600 hover:text-zinc-900 dark:border-white/10 dark:bg-white/5 dark:text-zinc-400 dark:hover:text-white transition-colors cursor-pointer\"\n                        >\n                          Reset\n                        </button>\n                        <button\n                          type=\"button\"\n                          onClick={saveCurrentItem}\n                          className=\"flex items-center rounded-full border border-zinc-200 bg-zinc-100 px-3 py-1 text-xs text-zinc-700 hover:text-zinc-900 dark:border-white/10 dark:bg-white/5 dark:text-zinc-300 dark:hover:text-white transition-colors cursor-pointer\"\n                        >\n                          Save\n                        </button>\n                      </div>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"split\" && detected.split && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Split Bill\n                      </span>\n                      <span>\n                        {detected.split.total} among {detected.split.people}\n                      </span>\n                    </div>\n\n                    <div className=\"flex items-baseline justify-between py-1\">\n                      <span className=\"text-xs text-zinc-500 dark:text-zinc-400\">\n                        Each pays\n                      </span>\n                      <span className=\"font-mono text-2xl font-semibold text-zinc-900 dark:text-white\">\n                        ₹{detected.split.perPerson}\n                      </span>\n                    </div>\n\n                    <div className=\"grid grid-cols-3 gap-1.5 pt-1\">\n                      {(() => {\n                        const splitData = detected.split;\n                        return Array.from({\n                          length: Math.min(6, splitData.people),\n                        }).map((_, i) => (\n                          <div\n                            key={i}\n                            className=\"flex flex-col items-center justify-center rounded-lg border border-zinc-200/60 bg-zinc-100/40 py-2 text-center dark:border-white/6 dark:bg-white/3\"\n                          >\n                            <span className=\"text-[10px] text-zinc-500 dark:text-zinc-500\">\n                              Person {i + 1}\n                            </span>\n                            <span className=\"font-mono text-xs font-medium text-zinc-800 dark:text-zinc-200\">\n                              {splitData.perPerson}\n                            </span>\n                          </div>\n                        ));\n                      })()}\n                    </div>\n\n                    <div className=\"flex justify-end pt-1\">\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Split</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"search\" && query.trim() && (\n                  <div className=\"flex flex-col gap-1.5\">\n                    <div className=\"px-1 text-[11px] font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider\">\n                      Search Results\n                    </div>\n                    {searchResults.length > 0 ? (\n                      searchResults.map((item, idx) => (\n                        <button\n                          key={idx}\n                          type=\"button\"\n                          onClick={() => {\n                            updateQuery(item.title);\n                            saveCurrentItem();\n                          }}\n                          className=\"group flex items-center justify-between rounded-lg border border-transparent px-3 py-2 text-left text-xs transition-colors hover:border-zinc-200 hover:bg-zinc-100/70 dark:hover:border-white/8 dark:hover:bg-white/5 cursor-pointer\"\n                        >\n                          <div className=\"flex flex-col\">\n                            <span className=\"font-medium text-zinc-800 group-hover:text-zinc-950 dark:text-zinc-200 dark:group-hover:text-white\">\n                              {item.title}\n                            </span>\n                            <span className=\"text-[11px] text-zinc-500 dark:text-zinc-500\">\n                              {item.desc}\n                            </span>\n                          </div>\n                          <span className=\"rounded-full border border-zinc-200 bg-zinc-100 px-2 py-0.5 text-[10px] text-zinc-600 dark:border-white/8 dark:bg-white/4 dark:text-zinc-400\">\n                            {item.category}\n                          </span>\n                        </button>\n                      ))\n                    ) : (\n                      <div className=\"py-6 text-center text-xs text-zinc-500 dark:text-zinc-500\">\n                        No matches found for &quot;{query}&quot;\n                      </div>\n                    )}\n                  </div>\n                )}\n\n                {!query.trim() && (\n                  <div className=\"flex flex-col gap-3\">\n                    <div className=\"flex flex-wrap items-center gap-1.5\">\n                      {sampleChips.map((chip) => {\n                        const isSelected = activeCategory === chip.id;\n                        return (\n                          <button\n                            key={chip.id}\n                            type=\"button\"\n                            onClick={() => {\n                              setActiveCategory(chip.id);\n                              if (chip.sample) {\n                                updateQuery(chip.sample);\n                              }\n                            }}\n                            className={cn(\n                              \"flex items-center rounded-full border px-3 py-1 text-xs transition-colors cursor-pointer\",\n                              isSelected\n                                ? \"border-zinc-900 bg-zinc-900 text-white dark:border-white/25 dark:bg-white/10 dark:text-white\"\n                                : \"border-zinc-200 bg-zinc-100 text-zinc-600 hover:border-zinc-300 hover:text-zinc-900 dark:border-white/8 dark:bg-white/4 dark:text-zinc-400 dark:hover:border-white/15 dark:hover:text-zinc-200\",\n                            )}\n                          >\n                            <span>{chip.label}</span>\n                          </button>\n                        );\n                      })}\n                    </div>\n\n                    <div className=\"flex flex-col gap-1 pt-1\">\n                      <span className=\"px-1 text-[11px] font-medium text-zinc-500 dark:text-zinc-500 uppercase tracking-wider\">\n                        Suggested Prompts\n                      </span>\n                      {PLACEHOLDERS.slice(0, 4).map((p, i) => (\n                        <button\n                          key={i}\n                          type=\"button\"\n                          onClick={() => updateQuery(p)}\n                          className=\"flex items-center justify-between rounded-lg px-2.5 py-1.5 text-left text-xs text-zinc-700 hover:bg-zinc-100 hover:text-zinc-950 dark:text-zinc-300 dark:hover:bg-white/5 dark:hover:text-white transition-colors cursor-pointer\"\n                        >\n                          <span className=\"truncate\">{p}</span>\n                        </button>\n                      ))}\n                    </div>\n                  </div>\n                )}\n              </div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </motion.div>\n\n      <AnimatePresence initial={false}>\n        {savedItems.length > 0 && (\n          <motion.div\n            layout\n            initial={{ opacity: 0, y: 6 }}\n            animate={{ opacity: 1, y: 0 }}\n            exit={{ opacity: 0, y: 6 }}\n            transition={reduceMotion ? fadeTween : springMorph}\n            className=\"mt-3 flex flex-col gap-1 rounded-[22px] border border-zinc-200/80 bg-white/95 backdrop-blur-2xl p-2 shadow-[0_8px_32px_rgba(0,0,0,0.06)] dark:border-white/10 dark:bg-[#0d0d12]/95 dark:shadow-[0_8px_32px_rgba(0,0,0,0.36)]\"\n          >\n            <AnimatePresence initial={false}>\n              {savedItems.map((item) => (\n                <motion.div\n                  key={item.id}\n                  layout\n                  initial={{ opacity: 0, y: -4 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  exit={{ opacity: 0, scale: 0.96 }}\n                  transition={reduceMotion ? fadeTween : springMorph}\n                  className=\"group relative flex items-center justify-between rounded-xl px-3 py-2.5 transition-colors hover:bg-zinc-100/70 dark:hover:bg-white/5 cursor-pointer\"\n                  onClick={() => reopenItem(item)}\n                >\n                  <div className=\"flex items-center gap-2.5 min-w-0 flex-1 pr-3\">\n                    <span className=\"shrink-0 rounded-md border border-zinc-200 bg-zinc-100 px-2 py-0.5 text-[11px] font-medium uppercase tracking-wider text-zinc-600 dark:border-white/10 dark:bg-white/5 dark:text-zinc-400\">\n                      {item.intent}\n                    </span>\n                    <span className=\"truncate text-[14px] text-zinc-800 group-hover:text-zinc-950 dark:text-zinc-200 dark:group-hover:text-white\">\n                      {item.summary}\n                    </span>\n                  </div>\n                  <button\n                    type=\"button\"\n                    onClick={(e) => {\n                      e.stopPropagation();\n                      removeItem(item.id);\n                    }}\n                    className=\"shrink-0 rounded px-1.5 py-0.5 text-xs text-zinc-400 hover:text-zinc-800 dark:text-zinc-500 dark:hover:text-zinc-200 transition-colors cursor-pointer\"\n                  >\n                    Delete\n                  </button>\n                </motion.div>\n              ))}\n            </AnimatePresence>\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n}\n",
      "content": "\"use client\";\n\nimport React, {\n  useCallback,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n  type Transition,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface MorphSearchProps {\n  value?: string;\n  defaultValue?: string;\n  onChange?: (value: string) => void;\n  onSubmit?: (value: string, intent?: string) => void;\n  onClear?: () => void;\n  placeholder?: string;\n  className?: string;\n  autoFocus?: boolean;\n}\n\ntype IntentType = \"poll\" | \"event\" | \"todo\" | \"timer\" | \"split\" | \"search\";\n\ninterface PollData {\n  title: string;\n  options: string[];\n}\n\ninterface EventData {\n  title: string;\n  time: string;\n  attendee: string;\n}\n\ninterface TodoData {\n  items: string[];\n}\n\ninterface TimerData {\n  seconds: number;\n  label: string;\n}\n\ninterface SplitData {\n  total: number;\n  people: number;\n  perPerson: number;\n}\n\ninterface DetectedIntent {\n  type: IntentType;\n  poll?: PollData;\n  event?: EventData;\n  todo?: TodoData;\n  timer?: TimerData;\n  split?: SplitData;\n}\n\ninterface SavedEntry {\n  id: number;\n  intent: IntentType;\n  text: string;\n  summary: string;\n  timestamp: number;\n}\n\nconst PLACEHOLDERS = [\n  \"pizza or burgers for friday?\",\n  \"dinner with priya friday 8pm\",\n  \"buy milk, eggs, bread and coffee\",\n  \"25 min focus\",\n  \"split 2400 between 3\",\n  \"explore components and hooks\",\n];\n\nconst springMorph: Transition = {\n  type: \"spring\",\n  stiffness: 440,\n  damping: 38,\n  mass: 0.8,\n};\n\nconst fadeTween: Transition = {\n  duration: 0.14,\n  ease: [0.23, 1, 0.32, 1],\n};\n\nfunction parseQuery(text: string): DetectedIntent {\n  const clean = text.trim();\n  const lower = clean.toLowerCase();\n\n  if (!clean) {\n    return { type: \"search\" };\n  }\n\n  if (\n    lower.includes(\" or \") ||\n    lower.includes(\" vs \") ||\n    (lower.endsWith(\"?\") &&\n      (lower.includes(\"pizza\") || lower.includes(\"burger\")))\n  ) {\n    const rawNoQ = clean.replace(/\\?+$/, \"\");\n    const parts = rawNoQ.split(/\\s+(?:or|vs\\.?)\\s+/i).map((s) => s.trim());\n    if (parts.length >= 2) {\n      const opts = parts.map((p) => {\n        const words = p.split(/\\s+/);\n        return words.length > 3 ? words.slice(-2).join(\" \") : p;\n      });\n      return {\n        type: \"poll\",\n        poll: {\n          title: clean.endsWith(\"?\") ? clean : `${clean}?`,\n          options: opts.slice(0, 4),\n        },\n      };\n    }\n  }\n\n  if (\n    /^(?:split|divide|share)\\b/i.test(lower) ||\n    (/\\bbetween\\s+\\d+/i.test(lower) && /\\d+/.test(lower))\n  ) {\n    const numbers = lower.match(/\\d+(?:\\.\\d+)?/g);\n    if (numbers && numbers.length >= 2) {\n      const total = parseFloat(numbers[0]);\n      const people = parseInt(numbers[1], 10) || 1;\n      const perPerson = Math.round((total / Math.max(1, people)) * 100) / 100;\n      return {\n        type: \"split\",\n        split: { total, people, perPerson },\n      };\n    }\n  }\n\n  if (\n    /\\b(?:timer|focus|break|pomodoro)\\b/i.test(lower) ||\n    /^\\d+\\s*(?:m|min|mins|minutes|s|sec|seconds|h|hr|hrs|hours)\\b/i.test(lower)\n  ) {\n    let secs = 25 * 60;\n    const match = lower.match(\n      /(\\d+)\\s*(m|min|mins|minutes|s|sec|seconds|h|hr|hrs|hours)?/i,\n    );\n    if (match) {\n      const num = parseInt(match[1], 10);\n      const unit = match[2]?.charAt(0) || \"m\";\n      if (unit === \"h\") secs = num * 3600;\n      else if (unit === \"s\") secs = num;\n      else secs = num * 60;\n    }\n    const label = lower\n      .replace(\n        /\\d+\\s*(?:m|min|mins|minutes|s|sec|seconds|h|hr|hrs|hours)?/i,\n        \"\",\n      )\n      .replace(/timer|for/gi, \"\")\n      .trim();\n    return {\n      type: \"timer\",\n      timer: {\n        seconds: Math.max(10, secs),\n        label: label\n          ? label.charAt(0).toUpperCase() + label.slice(1)\n          : \"Focus Session\",\n      },\n    };\n  }\n\n  if (\n    /^(?:buy|get|todo|tasks|groceries|shopping list)\\b/i.test(lower) ||\n    (clean.includes(\",\") && (clean.includes(\"and\") || clean.includes(\"&\")))\n  ) {\n    const sanitized = clean\n      .replace(/^(?:buy|get|todo|tasks|groceries|shopping list):?\\s*/i, \"\")\n      .replace(/\\band\\b/gi, \",\");\n    const rawItems = sanitized\n      .split(/[,;\\n]/)\n      .map((i) => i.trim())\n      .filter((i) => i.length > 0);\n    if (rawItems.length >= 2) {\n      return {\n        type: \"todo\",\n        todo: {\n          items: rawItems.map(\n            (item) => item.charAt(0).toUpperCase() + item.slice(1),\n          ),\n        },\n      };\n    }\n  }\n\n  if (\n    /\\b(?:dinner|lunch|breakfast|coffee|meeting|sync|call|party|interview|session)\\b/i.test(\n      lower,\n    ) ||\n    /\\b(?:today|tomorrow|tonight|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\b/i.test(\n      lower,\n    )\n  ) {\n    const withMatch = clean.match(/\\bwith\\s+([A-Za-z]+)/i);\n    const timeMatch = clean.match(\n      /\\b(?:at\\s+)?(\\d{1,2}(?::\\d{2})?\\s*(?:am|pm)?|\\d{1,2}\\s*(?:am|pm)|noon|midnight)\\b/i,\n    );\n    const dayMatch = clean.match(\n      /\\b(today|tomorrow|tonight|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\b/i,\n    );\n    const timeStr = [\n      dayMatch\n        ? dayMatch[1].charAt(0).toUpperCase() + dayMatch[1].slice(1)\n        : \"Upcoming\",\n      timeMatch ? timeMatch[1].toUpperCase() : \"8:00 PM\",\n    ].join(\", \");\n\n    return {\n      type: \"event\",\n      event: {\n        title: clean.charAt(0).toUpperCase() + clean.slice(1),\n        time: timeStr,\n        attendee: withMatch\n          ? withMatch[1].charAt(0).toUpperCase() + withMatch[1].slice(1)\n          : \"Team\",\n      },\n    };\n  }\n\n  return { type: \"search\" };\n}\n\nfunction formatTimer(secs: number): string {\n  const m = Math.floor(secs / 60);\n  const s = secs % 60;\n  return `${String(m).padStart(2, \"0\")}:${String(s).padStart(2, \"0\")}`;\n}\n\nexport function MorphSearch({\n  value: controlledValue,\n  defaultValue = \"\",\n  onChange,\n  onSubmit,\n  onClear,\n  placeholder,\n  className,\n  autoFocus = false,\n}: MorphSearchProps) {\n  const isControlled = controlledValue !== undefined;\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const query = isControlled ? controlledValue : internalValue;\n\n  const [isOpen, setIsOpen] = useState(false);\n  const [placeholderIndex, setPlaceholderIndex] = useState(0);\n  const [activeCategory, setActiveCategory] = useState<string>(\"all\");\n  const [savedItems, setSavedItems] = useState<SavedEntry[]>([]);\n\n  const [pollVotes, setPollVotes] = useState<Record<number, number>>({});\n  const [completedTodos, setCompletedTodos] = useState<Record<number, boolean>>(\n    {},\n  );\n  const [timerRunning, setTimerRunning] = useState(false);\n  const [timerTimeLeft, setTimerTimeLeft] = useState<number | null>(null);\n\n  const containerRef = useRef<HTMLDivElement>(null);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const reduceMotion = useReducedMotion();\n  const inputId = useId();\n\n  const detected = useMemo(() => parseQuery(query), [query]);\n\n  useEffect(() => {\n    if (reduceMotion) return;\n    const interval = setInterval(() => {\n      setPlaceholderIndex((prev) => (prev + 1) % PLACEHOLDERS.length);\n    }, 2800);\n    return () => clearInterval(interval);\n  }, [reduceMotion]);\n\n  const [prevQuery, setPrevQuery] = useState(query);\n  if (query !== prevQuery) {\n    setPrevQuery(query);\n    setPollVotes({});\n    setCompletedTodos({});\n    setTimerRunning(false);\n    setTimerTimeLeft(\n      detected.type === \"timer\" && detected.timer\n        ? detected.timer.seconds\n        : null,\n    );\n  }\n\n  useEffect(() => {\n    if (!timerRunning || timerTimeLeft === null || timerTimeLeft <= 0) return;\n    const interval = setInterval(() => {\n      setTimerTimeLeft((prev) => {\n        if (prev === null || prev <= 1) {\n          setTimerRunning(false);\n          return 0;\n        }\n        return prev - 1;\n      });\n    }, 1000);\n    return () => clearInterval(interval);\n  }, [timerRunning, timerTimeLeft]);\n\n  const updateQuery = useCallback(\n    (newVal: string) => {\n      if (!isControlled) {\n        setInternalValue(newVal);\n      }\n      onChange?.(newVal);\n    },\n    [isControlled, onChange],\n  );\n\n  const handleOpen = useCallback(() => {\n    setIsOpen(true);\n    requestAnimationFrame(() => {\n      inputRef.current?.focus();\n    });\n  }, []);\n\n  const handleClose = useCallback(() => {\n    setIsOpen(false);\n    inputRef.current?.blur();\n  }, []);\n\n  const closeToTrigger = useCallback(() => {\n    handleClose();\n    triggerRef.current?.focus();\n  }, [handleClose]);\n\n  const handleClear = useCallback(() => {\n    updateQuery(\"\");\n    onClear?.();\n    inputRef.current?.focus();\n  }, [updateQuery, onClear]);\n\n  const saveCurrentItem = useCallback(() => {\n    const trimmed = query.trim();\n    if (!trimmed) return;\n\n    let summary = trimmed;\n    if (detected.type === \"poll\" && detected.poll) {\n      summary = detected.poll.title;\n    } else if (detected.type === \"event\" && detected.event) {\n      summary = `${detected.event.title} · ${detected.event.time}`;\n    } else if (detected.type === \"todo\" && detected.todo) {\n      summary = detected.todo.items.join(\", \");\n    } else if (detected.type === \"timer\" && detected.timer) {\n      summary = `${detected.timer.label} · ${formatTimer(detected.timer.seconds)}`;\n    } else if (detected.type === \"split\" && detected.split) {\n      summary = `₹${detected.split.total} among ${detected.split.people} (₹${detected.split.perPerson}/person)`;\n    }\n\n    const newItem: SavedEntry = {\n      id: Date.now() + Math.random(),\n      intent: detected.type,\n      text: trimmed,\n      summary,\n      timestamp: Date.now(),\n    };\n\n    setSavedItems((prev) => [newItem, ...prev.slice(0, 8)]);\n    onSubmit?.(trimmed, detected.type);\n    updateQuery(\"\");\n    setIsOpen(false);\n  }, [query, detected, onSubmit, updateQuery]);\n\n  const reopenItem = (item: SavedEntry) => {\n    updateQuery(item.text);\n    setIsOpen(true);\n    requestAnimationFrame(() => {\n      inputRef.current?.focus();\n    });\n  };\n\n  const removeItem = (id: number) => {\n    setSavedItems((prev) => prev.filter((item) => item.id !== id));\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === \"Escape\") {\n      e.preventDefault();\n      if (query) {\n        handleClear();\n      } else {\n        closeToTrigger();\n      }\n    } else if (e.key === \"Enter\" && !e.nativeEvent.isComposing) {\n      e.preventDefault();\n      saveCurrentItem();\n    }\n  };\n\n  useEffect(() => {\n    const handleGlobalKeyDown = (e: KeyboardEvent) => {\n      const activeEl = document.activeElement;\n      const isInput =\n        activeEl instanceof HTMLInputElement ||\n        activeEl instanceof HTMLTextAreaElement ||\n        activeEl?.getAttribute(\"contenteditable\") === \"true\";\n\n      if (\n        (e.key === \"/\" || (e.key === \"k\" && (e.metaKey || e.ctrlKey))) &&\n        !isInput\n      ) {\n        e.preventDefault();\n        handleOpen();\n      }\n    };\n    window.addEventListener(\"keydown\", handleGlobalKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleGlobalKeyDown);\n  }, [handleOpen]);\n\n  useEffect(() => {\n    const handleClickOutside = (e: MouseEvent) => {\n      if (\n        containerRef.current &&\n        !containerRef.current.contains(e.target as Node)\n      ) {\n        handleClose();\n      }\n    };\n    if (isOpen) {\n      document.addEventListener(\"mousedown\", handleClickOutside);\n    }\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, [isOpen, handleClose]);\n\n  const voteOnPoll = (index: number) => {\n    setPollVotes((prev) => ({\n      ...prev,\n      [index]: (prev[index] || 0) + 1,\n    }));\n  };\n\n  const toggleTodo = (index: number) => {\n    setCompletedTodos((prev) => ({\n      ...prev,\n      [index]: !prev[index],\n    }));\n  };\n\n  const sampleChips = [\n    { label: \"All\", id: \"all\" },\n    { label: \"Poll\", id: \"poll\", sample: \"pizza or burgers for friday?\" },\n    { label: \"Event\", id: \"event\", sample: \"dinner with priya friday 8pm\" },\n    { label: \"Todo\", id: \"todo\", sample: \"buy milk, eggs, bread and coffee\" },\n    { label: \"Timer\", id: \"timer\", sample: \"25 min focus\" },\n    { label: \"Split\", id: \"split\", sample: \"split 2400 between 3\" },\n  ];\n\n  const searchResults = useMemo(() => {\n    if (detected.type !== \"search\" || !query.trim()) return [];\n    const pool = [\n      {\n        title: \"Navigation Components\",\n        category: \"Components\",\n        desc: \"Gooey navbar and fluid floating tab selectors\",\n      },\n      {\n        title: \"Action Controls\",\n        category: \"Inputs\",\n        desc: \"Tactile buttons, liquid toggles, and switches\",\n      },\n      {\n        title: \"Micro Physics Engines\",\n        category: \"Motion\",\n        desc: \"Spring solvers, collision dynamics, and kinetic easing\",\n      },\n      {\n        title: \"Theming & Palette Matrix\",\n        category: \"Tokens\",\n        desc: \"Adaptive dark mode palettes and high contrast curves\",\n      },\n    ];\n    return pool.filter(\n      (item) =>\n        item.title.toLowerCase().includes(query.toLowerCase()) ||\n        item.desc.toLowerCase().includes(query.toLowerCase()) ||\n        item.category.toLowerCase().includes(query.toLowerCase()),\n    );\n  }, [detected.type, query]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\"relative w-full max-w-140 mx-auto select-none\", className)}\n    >\n      <motion.div\n        layout\n        transition={reduceMotion ? fadeTween : springMorph}\n        style={{ borderRadius: 28 }}\n        className={cn(\n          \"relative overflow-hidden border border-zinc-200/80 bg-white/95 backdrop-blur-2xl dark:border-white/10 dark:bg-[#0d0d12]/95\",\n          isOpen\n            ? \"border-zinc-300 shadow-[0_24px_64px_rgba(0,0,0,0.08),0_0_0_1px_rgba(0,0,0,0.04)] dark:border-white/20 dark:shadow-[0_24px_64px_rgba(0,0,0,0.7),0_0_0_1px_rgba(255,255,255,0.06)]\"\n            : \"hover:border-zinc-300 shadow-[0_8px_32px_rgba(0,0,0,0.06),0_1px_1px_rgba(0,0,0,0.02)] dark:hover:border-white/15 dark:shadow-[0_8px_32px_rgba(0,0,0,0.36),0_1px_1px_rgba(255,255,255,0.04)]\",\n        )}\n      >\n        <motion.div\n          layout=\"position\"\n          transition={reduceMotion ? fadeTween : springMorph}\n          className=\"relative flex h-14.5 items-center px-6 gap-3\"\n        >\n          <div className=\"relative flex-1 flex items-center h-full min-w-0\">\n            <input\n              id={inputId}\n              ref={inputRef}\n              autoFocus={autoFocus}\n              autoComplete=\"off\"\n              spellCheck={false}\n              value={query}\n              onFocus={handleOpen}\n              onChange={(e) => updateQuery(e.target.value)}\n              onKeyDown={handleKeyDown}\n              className=\"w-full bg-transparent text-[17px] font-normal tracking-[-0.01em] text-zinc-900 caret-zinc-900 dark:text-white dark:caret-white outline-none placeholder:text-transparent\"\n              aria-label=\"Search or enter prompt\"\n            />\n\n            {!query && (\n              <span className=\"pointer-events-none absolute inset-0 flex items-center overflow-hidden\">\n                <AnimatePresence mode=\"popLayout\" initial={false}>\n                  <motion.span\n                    key={placeholderIndex}\n                    initial={\n                      reduceMotion\n                        ? { opacity: 0 }\n                        : { opacity: 0, y: 8, filter: \"blur(4px)\" }\n                    }\n                    animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n                    exit={\n                      reduceMotion\n                        ? { opacity: 0 }\n                        : { opacity: 0, y: -8, filter: \"blur(4px)\" }\n                    }\n                    transition={fadeTween}\n                    className=\"truncate text-[17px] font-normal tracking-[-0.01em] text-zinc-400 dark:text-zinc-400\"\n                  >\n                    {placeholder ?? PLACEHOLDERS[placeholderIndex]}\n                  </motion.span>\n                </AnimatePresence>\n              </span>\n            )}\n          </div>\n\n          <div className=\"flex items-center gap-2 shrink-0\">\n            {query && (\n              <button\n                type=\"button\"\n                onClick={handleClear}\n                className=\"text-xs text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white px-1.5 py-0.5 rounded transition-colors cursor-pointer\"\n                aria-label=\"Clear input\"\n              >\n                Clear\n              </button>\n            )}\n\n            <button\n              ref={triggerRef}\n              type=\"button\"\n              onClick={isOpen ? closeToTrigger : handleOpen}\n              aria-label={isOpen ? \"Close search\" : \"Open search\"}\n              aria-expanded={isOpen}\n              className=\"relative flex items-center rounded-full border border-zinc-200 bg-zinc-100/80 px-2 py-0.5 text-[11px] font-mono text-zinc-500 hover:text-zinc-900 hover:border-zinc-300 dark:border-white/10 dark:bg-white/5 dark:text-zinc-400 dark:hover:text-white dark:hover:border-white/20 transition-colors cursor-pointer after:absolute after:-inset-x-2.5 after:-inset-y-3 outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 dark:focus-visible:ring-white/40\"\n            >\n              <span>{isOpen ? \"ESC\" : \"/\"}</span>\n            </button>\n          </div>\n        </motion.div>\n\n        <AnimatePresence mode=\"popLayout\" initial={false}>\n          {isOpen && (\n            <motion.div\n              key=\"content-body\"\n              initial={\n                reduceMotion\n                  ? { opacity: 0 }\n                  : { opacity: 0, filter: \"blur(4px)\" }\n              }\n              animate={{ opacity: 1, filter: \"blur(0px)\" }}\n              exit={{\n                opacity: 0,\n                filter: \"blur(4px)\",\n                transition: { duration: 0.12, ease: [0.23, 1, 0.32, 1] },\n              }}\n              transition={\n                reduceMotion ? fadeTween : { ...springMorph, delay: 0.02 }\n              }\n              className=\"w-full overflow-hidden border-t border-zinc-200/80 dark:border-white/8\"\n            >\n              <div className=\"p-5 flex flex-col gap-4\">\n                {detected.type === \"poll\" && detected.poll && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Poll\n                      </span>\n                      <span>Select an option to vote</span>\n                    </div>\n\n                    <h3 className=\"text-[15px] font-medium text-zinc-900 dark:text-white\">\n                      {detected.poll.title}\n                    </h3>\n\n                    <div className=\"flex flex-col gap-2\">\n                      {detected.poll.options.map((opt, idx) => {\n                        const totalVotes = Object.values(pollVotes).reduce(\n                          (a, b) => a + b,\n                          0,\n                        );\n                        const votes = pollVotes[idx] || 0;\n                        const pct =\n                          totalVotes > 0\n                            ? Math.round((votes / totalVotes) * 100)\n                            : 0;\n\n                        return (\n                          <button\n                            key={idx}\n                            type=\"button\"\n                            onClick={() => voteOnPoll(idx)}\n                            className=\"group relative flex items-center justify-between overflow-hidden rounded-lg border border-zinc-200/80 bg-zinc-100/60 px-3.5 py-2.5 text-left text-xs transition-colors hover:border-zinc-300 hover:bg-zinc-100 dark:border-white/8 dark:bg-white/4 dark:hover:border-white/15 dark:hover:bg-white/7 cursor-pointer\"\n                          >\n                            <motion.div\n                              className=\"absolute inset-y-0 left-0 bg-zinc-900/10 dark:bg-white/10\"\n                              initial={{ width: \"0%\" }}\n                              animate={{ width: `${pct}%` }}\n                              transition={springMorph}\n                            />\n                            <span className=\"relative z-10 font-medium text-zinc-800 group-hover:text-zinc-950 dark:text-zinc-200 dark:group-hover:text-white\">\n                              {opt}\n                            </span>\n                            <span className=\"relative z-10 font-mono text-zinc-500 dark:text-zinc-400 tabular-nums\">\n                              {pct}%\n                            </span>\n                          </button>\n                        );\n                      })}\n                    </div>\n\n                    <div className=\"flex items-center justify-between pt-1\">\n                      <span className=\"text-[11px] text-zinc-500 dark:text-zinc-400\">\n                        {Object.values(pollVotes).reduce((a, b) => a + b, 0)}{\" \"}\n                        total votes\n                      </span>\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Poll</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"event\" && detected.event && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Event\n                      </span>\n                      <span>Scheduled Meeting</span>\n                    </div>\n\n                    <h3 className=\"text-[15px] font-medium text-zinc-900 dark:text-white\">\n                      {detected.event.title}\n                    </h3>\n\n                    <div className=\"flex flex-wrap items-center gap-2\">\n                      <div className=\"rounded-md border border-zinc-200 bg-zinc-100 px-2.5 py-1 text-xs text-zinc-700 dark:border-white/8 dark:bg-white/4 dark:text-zinc-300\">\n                        <span>{detected.event.time}</span>\n                      </div>\n                      <div className=\"rounded-md border border-zinc-200 bg-zinc-100 px-2.5 py-1 text-xs text-zinc-700 dark:border-white/8 dark:bg-white/4 dark:text-zinc-300\">\n                        <span>{detected.event.attendee}</span>\n                      </div>\n                    </div>\n\n                    <div className=\"flex justify-end pt-1\">\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Event</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"todo\" && detected.todo && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Checklist\n                      </span>\n                      <span>\n                        {Object.values(completedTodos).filter(Boolean).length}/\n                        {detected.todo.items.length} completed\n                      </span>\n                    </div>\n\n                    <div className=\"flex flex-col gap-1.5\">\n                      {detected.todo.items.map((item, idx) => {\n                        const done = Boolean(completedTodos[idx]);\n                        return (\n                          <button\n                            key={idx}\n                            type=\"button\"\n                            onClick={() => toggleTodo(idx)}\n                            className=\"flex items-center gap-2.5 rounded-lg border border-zinc-200/60 bg-zinc-100/40 px-3 py-2 text-left text-xs transition-colors hover:bg-zinc-100/80 dark:border-white/6 dark:bg-white/3 dark:hover:bg-white/6 cursor-pointer\"\n                          >\n                            <span\n                              className={cn(\n                                \"flex size-3.5 shrink-0 rounded-sm border transition-colors\",\n                                done\n                                  ? \"border-zinc-900 bg-zinc-900 dark:border-white dark:bg-white\"\n                                  : \"border-zinc-300 dark:border-white/30\",\n                              )}\n                            />\n                            <span\n                              className={cn(\n                                \"font-normal transition-all\",\n                                done\n                                  ? \"text-zinc-400 line-through dark:text-zinc-500\"\n                                  : \"text-zinc-800 dark:text-zinc-200\",\n                              )}\n                            >\n                              {item}\n                            </span>\n                          </button>\n                        );\n                      })}\n                    </div>\n\n                    <div className=\"flex justify-end pt-1\">\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Tasks</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"timer\" && detected.timer && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Timer\n                      </span>\n                      <span>{detected.timer.label}</span>\n                    </div>\n\n                    <div className=\"flex items-center justify-between py-2\">\n                      <span className=\"font-mono text-3xl font-medium tracking-tight text-zinc-900 dark:text-white\">\n                        {formatTimer(timerTimeLeft ?? detected.timer.seconds)}\n                      </span>\n\n                      <div className=\"flex items-center gap-2\">\n                        <button\n                          type=\"button\"\n                          onClick={() => setTimerRunning((p) => !p)}\n                          className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                        >\n                          {timerRunning ? \"Pause\" : \"Start\"}\n                        </button>\n                        <button\n                          type=\"button\"\n                          onClick={() => {\n                            setTimerRunning(false);\n                            setTimerTimeLeft(detected.timer?.seconds ?? 1500);\n                          }}\n                          className=\"flex items-center rounded-full border border-zinc-200 bg-zinc-100 px-3 py-1 text-xs text-zinc-600 hover:text-zinc-900 dark:border-white/10 dark:bg-white/5 dark:text-zinc-400 dark:hover:text-white transition-colors cursor-pointer\"\n                        >\n                          Reset\n                        </button>\n                        <button\n                          type=\"button\"\n                          onClick={saveCurrentItem}\n                          className=\"flex items-center rounded-full border border-zinc-200 bg-zinc-100 px-3 py-1 text-xs text-zinc-700 hover:text-zinc-900 dark:border-white/10 dark:bg-white/5 dark:text-zinc-300 dark:hover:text-white transition-colors cursor-pointer\"\n                        >\n                          Save\n                        </button>\n                      </div>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"split\" && detected.split && (\n                  <div className=\"flex flex-col gap-3 rounded-xl border border-zinc-200/80 bg-zinc-50/60 p-4 dark:border-white/8 dark:bg-white/2\">\n                    <div className=\"flex items-center justify-between text-xs text-zinc-400\">\n                      <span className=\"font-medium text-zinc-700 dark:text-zinc-300\">\n                        Split Bill\n                      </span>\n                      <span>\n                        {detected.split.total} among {detected.split.people}\n                      </span>\n                    </div>\n\n                    <div className=\"flex items-baseline justify-between py-1\">\n                      <span className=\"text-xs text-zinc-500 dark:text-zinc-400\">\n                        Each pays\n                      </span>\n                      <span className=\"font-mono text-2xl font-semibold text-zinc-900 dark:text-white\">\n                        ₹{detected.split.perPerson}\n                      </span>\n                    </div>\n\n                    <div className=\"grid grid-cols-3 gap-1.5 pt-1\">\n                      {(() => {\n                        const splitData = detected.split;\n                        return Array.from({\n                          length: Math.min(6, splitData.people),\n                        }).map((_, i) => (\n                          <div\n                            key={i}\n                            className=\"flex flex-col items-center justify-center rounded-lg border border-zinc-200/60 bg-zinc-100/40 py-2 text-center dark:border-white/6 dark:bg-white/3\"\n                          >\n                            <span className=\"text-[10px] text-zinc-500 dark:text-zinc-500\">\n                              Person {i + 1}\n                            </span>\n                            <span className=\"font-mono text-xs font-medium text-zinc-800 dark:text-zinc-200\">\n                              {splitData.perPerson}\n                            </span>\n                          </div>\n                        ));\n                      })()}\n                    </div>\n\n                    <div className=\"flex justify-end pt-1\">\n                      <button\n                        type=\"button\"\n                        onClick={saveCurrentItem}\n                        className=\"flex items-center rounded-full bg-zinc-900 px-3 py-1 text-xs font-medium text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 transition-colors cursor-pointer\"\n                      >\n                        <span>Save Split</span>\n                      </button>\n                    </div>\n                  </div>\n                )}\n\n                {detected.type === \"search\" && query.trim() && (\n                  <div className=\"flex flex-col gap-1.5\">\n                    <div className=\"px-1 text-[11px] font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider\">\n                      Search Results\n                    </div>\n                    {searchResults.length > 0 ? (\n                      searchResults.map((item, idx) => (\n                        <button\n                          key={idx}\n                          type=\"button\"\n                          onClick={() => {\n                            updateQuery(item.title);\n                            saveCurrentItem();\n                          }}\n                          className=\"group flex items-center justify-between rounded-lg border border-transparent px-3 py-2 text-left text-xs transition-colors hover:border-zinc-200 hover:bg-zinc-100/70 dark:hover:border-white/8 dark:hover:bg-white/5 cursor-pointer\"\n                        >\n                          <div className=\"flex flex-col\">\n                            <span className=\"font-medium text-zinc-800 group-hover:text-zinc-950 dark:text-zinc-200 dark:group-hover:text-white\">\n                              {item.title}\n                            </span>\n                            <span className=\"text-[11px] text-zinc-500 dark:text-zinc-500\">\n                              {item.desc}\n                            </span>\n                          </div>\n                          <span className=\"rounded-full border border-zinc-200 bg-zinc-100 px-2 py-0.5 text-[10px] text-zinc-600 dark:border-white/8 dark:bg-white/4 dark:text-zinc-400\">\n                            {item.category}\n                          </span>\n                        </button>\n                      ))\n                    ) : (\n                      <div className=\"py-6 text-center text-xs text-zinc-500 dark:text-zinc-500\">\n                        No matches found for &quot;{query}&quot;\n                      </div>\n                    )}\n                  </div>\n                )}\n\n                {!query.trim() && (\n                  <div className=\"flex flex-col gap-3\">\n                    <div className=\"flex flex-wrap items-center gap-1.5\">\n                      {sampleChips.map((chip) => {\n                        const isSelected = activeCategory === chip.id;\n                        return (\n                          <button\n                            key={chip.id}\n                            type=\"button\"\n                            onClick={() => {\n                              setActiveCategory(chip.id);\n                              if (chip.sample) {\n                                updateQuery(chip.sample);\n                              }\n                            }}\n                            className={cn(\n                              \"flex items-center rounded-full border px-3 py-1 text-xs transition-colors cursor-pointer\",\n                              isSelected\n                                ? \"border-zinc-900 bg-zinc-900 text-white dark:border-white/25 dark:bg-white/10 dark:text-white\"\n                                : \"border-zinc-200 bg-zinc-100 text-zinc-600 hover:border-zinc-300 hover:text-zinc-900 dark:border-white/8 dark:bg-white/4 dark:text-zinc-400 dark:hover:border-white/15 dark:hover:text-zinc-200\",\n                            )}\n                          >\n                            <span>{chip.label}</span>\n                          </button>\n                        );\n                      })}\n                    </div>\n\n                    <div className=\"flex flex-col gap-1 pt-1\">\n                      <span className=\"px-1 text-[11px] font-medium text-zinc-500 dark:text-zinc-500 uppercase tracking-wider\">\n                        Suggested Prompts\n                      </span>\n                      {PLACEHOLDERS.slice(0, 4).map((p, i) => (\n                        <button\n                          key={i}\n                          type=\"button\"\n                          onClick={() => updateQuery(p)}\n                          className=\"flex items-center justify-between rounded-lg px-2.5 py-1.5 text-left text-xs text-zinc-700 hover:bg-zinc-100 hover:text-zinc-950 dark:text-zinc-300 dark:hover:bg-white/5 dark:hover:text-white transition-colors cursor-pointer\"\n                        >\n                          <span className=\"truncate\">{p}</span>\n                        </button>\n                      ))}\n                    </div>\n                  </div>\n                )}\n              </div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </motion.div>\n\n      <AnimatePresence initial={false}>\n        {savedItems.length > 0 && (\n          <motion.div\n            layout\n            initial={{ opacity: 0, y: 6 }}\n            animate={{ opacity: 1, y: 0 }}\n            exit={{ opacity: 0, y: 6 }}\n            transition={reduceMotion ? fadeTween : springMorph}\n            className=\"mt-3 flex flex-col gap-1 rounded-[22px] border border-zinc-200/80 bg-white/95 backdrop-blur-2xl p-2 shadow-[0_8px_32px_rgba(0,0,0,0.06)] dark:border-white/10 dark:bg-[#0d0d12]/95 dark:shadow-[0_8px_32px_rgba(0,0,0,0.36)]\"\n          >\n            <AnimatePresence initial={false}>\n              {savedItems.map((item) => (\n                <motion.div\n                  key={item.id}\n                  layout\n                  initial={{ opacity: 0, y: -4 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  exit={{ opacity: 0, scale: 0.96 }}\n                  transition={reduceMotion ? fadeTween : springMorph}\n                  className=\"group relative flex items-center justify-between rounded-xl px-3 py-2.5 transition-colors hover:bg-zinc-100/70 dark:hover:bg-white/5 cursor-pointer\"\n                  onClick={() => reopenItem(item)}\n                >\n                  <div className=\"flex items-center gap-2.5 min-w-0 flex-1 pr-3\">\n                    <span className=\"shrink-0 rounded-md border border-zinc-200 bg-zinc-100 px-2 py-0.5 text-[11px] font-medium uppercase tracking-wider text-zinc-600 dark:border-white/10 dark:bg-white/5 dark:text-zinc-400\">\n                      {item.intent}\n                    </span>\n                    <span className=\"truncate text-[14px] text-zinc-800 group-hover:text-zinc-950 dark:text-zinc-200 dark:group-hover:text-white\">\n                      {item.summary}\n                    </span>\n                  </div>\n                  <button\n                    type=\"button\"\n                    onClick={(e) => {\n                      e.stopPropagation();\n                      removeItem(item.id);\n                    }}\n                    className=\"shrink-0 rounded px-1.5 py-0.5 text-xs text-zinc-400 hover:text-zinc-800 dark:text-zinc-500 dark:hover:text-zinc-200 transition-colors cursor-pointer\"\n                  >\n                    Delete\n                  </button>\n                </motion.div>\n              ))}\n            </AnimatePresence>\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/morph-search.tsx"
    }
  ],
  "type": "registry:ui",
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "title": "Morph Search"
}
