{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "caret-mention-popover",
  "type": "registry:component",
  "title": "Caret-Anchored Mention Popover",
  "description": "An @-mention popover anchored to the text caret via a hidden mirror, gliding as you type.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://lab.moumen.dev/r/lab-theme.json"
  ],
  "files": [
    {
      "path": "registry/lab/caret-mention-popover/caret-mention-popover.tsx",
      "type": "registry:component",
      "target": "@components/lab/caret-mention-popover.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { MotionConfig, motion } from \"motion/react\";\n\n// Mention composer - a caret-anchored @mention popover.\n//\n// A textarea won't tell you where its caret is - the only API is selectionStart,\n// a character index, no x/y. So most popovers anchor to the FIELD (bottom-left,\n// like a dropdown), which feels detached from what you're typing. This one\n// anchors to the CARET, with three stacked layers sharing one typography class\n// so their metrics can never drift:\n//\n//   · input    - the real textarea (transparent background, visible text).\n//   · backdrop - behind it, same box, text painted TRANSPARENT; only the\n//     highlight boxes show through (inserted mentions as pills, the live @query\n//     as a lighter chip). The spans add zero advance (box-shadow fakes the\n//     inset), so the backdrop's glyph grid matches the textarea exactly.\n//   · mirror   - invisible. value.slice(0, caret) is re-typeset into it plus a\n//     marker <span>; the marker's offsetLeft/offsetTop IS the caret's x/y.\n//\n// The popover is positioned with `translate` (left/top stay 0), so following the\n// caret is one retargetable transition - it glides, and a fresh open snaps. It\n// clamps horizontally and flips above when the viewport runs out. Focus never\n// leaves the textarea: ↑↓/Enter/Tab drive the list via aria-activedescendant.\n//\n// Animation via motion/react (the pill fade + send-icon swap) plus CSS\n// transitions; honours prefers-reduced-motion. Requires the lab-theme tokens.\n// Fully Tailwind, no CSS files.\n\nconst EASE = [0.22, 1, 0.36, 1] as const;\nconst EASE_ICON = [0.2, 0, 0, 1] as const;\n\n// THE contract: input, backdrop and mirror all take this, so their metrics\n// (font, line height, wrap width, spacing) cannot drift apart.\nconst TEXT = \"font-[inherit] text-[0.9375rem] leading-6 tracking-normal whitespace-pre-wrap [overflow-wrap:break-word]\";\n\nexport interface Person {\n  name: string;\n  handle: string;\n}\n\nexport interface MentionState {\n  open: boolean;\n  query: string;\n  matches: number;\n  caret: { x: number; y: number } | null;\n  placement: string;\n  anchor: string;\n  mentions: number;\n  lastMention: string | null;\n}\n\ninterface Match {\n  person: Person;\n  score?: number;\n  nameRange: [number, number] | null;\n  handleRange: [number, number] | null;\n}\n\n// Word-prefix on the name beats handle-prefix beats substring.\nfunction rank(people: Person[], query: string): Match[] {\n  if (!query) return people.map((person) => ({ person, nameRange: null, handleRange: null }));\n  const needle = query.toLowerCase();\n  const scored: Match[] = [];\n  for (const person of people) {\n    const name = person.name.toLowerCase();\n    const handle = person.handle.toLowerCase();\n    let score: number | null = null;\n    let nameRange: [number, number] | null = null;\n    let handleRange: [number, number] | null = null;\n    let offset = 0;\n    for (const word of name.split(\" \")) {\n      if (word.startsWith(needle)) {\n        score = 3;\n        nameRange = [offset, offset + query.length];\n        break;\n      }\n      offset += word.length + 1;\n    }\n    if (score === null && handle.startsWith(needle)) {\n      score = 2;\n      handleRange = [0, query.length];\n    }\n    if (score === null) {\n      const at = name.indexOf(needle);\n      if (at !== -1) {\n        score = 1;\n        nameRange = [at, at + query.length];\n      }\n    }\n    if (score === null) {\n      const at = handle.indexOf(needle);\n      if (at !== -1) {\n        score = 0;\n        handleRange = [at, at + query.length];\n      }\n    }\n    if (score !== null) scored.push({ person, score, nameRange, handleRange });\n  }\n  scored.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));\n  return scored;\n}\n\nfunction findToken(text: string, caretIndex: number) {\n  const before = text.slice(0, caretIndex);\n  const match = /(?:^|[\\s([{])@([\\w-]*)$/.exec(before);\n  if (!match) return null;\n  return { start: caretIndex - match[1].length - 1, end: caretIndex, query: match[1] };\n}\n\nfunction buildSegments(text: string, names: string[], token: { start: number; end: number } | null, inspect: boolean) {\n  const marks: { start: number; end: number; kind: \"pill\" | \"tok\" }[] = [];\n  for (const name of names) {\n    const needle = `@${name}`;\n    let from = 0;\n    let at: number;\n    while ((at = text.indexOf(needle, from)) !== -1) {\n      marks.push({ start: at, end: at + needle.length, kind: \"pill\" });\n      from = at + needle.length;\n    }\n  }\n  if (token && token.end > token.start) marks.push({ start: token.start, end: token.end, kind: \"tok\" });\n  marks.sort((a, b) => a.start - b.start);\n  const out: ReactNode[] = [];\n  const seen: Record<string, number> = {};\n  let pos = 0;\n  for (const mark of marks) {\n    if (mark.start < pos) continue;\n    if (mark.start > pos) out.push(text.slice(pos, mark.start));\n    const content = text.slice(mark.start, mark.end);\n    const key = mark.kind === \"pill\" ? `pill-${content}-${(seen[content] = (seen[content] ?? 0) + 1)}` : `tok-${mark.start}`;\n    out.push(\n      <motion.span\n        key={key}\n        initial={{ opacity: 0 }}\n        animate={{ opacity: 1 }}\n        transition={{ duration: 0.2, ease: EASE }}\n        className={\n          mark.kind === \"pill\"\n            ? \"bg-foreground/10 rounded-[0.25rem] shadow-[0_0_0_1.5px_color-mix(in_oklab,var(--color-foreground)_10%,transparent)] [box-decoration-break:clone] [-webkit-box-decoration-break:clone]\"\n            : `bg-muted rounded-[0.25rem] shadow-[0_0_0_1.5px_var(--color-muted)] [box-decoration-break:clone] [-webkit-box-decoration-break:clone]${inspect ? \" outline outline-[1.5px] outline-dashed outline-[#f59e0b] outline-offset-[1.5px]\" : \"\"}`\n        }\n      >\n        {content}\n      </motion.span>,\n    );\n    pos = mark.end;\n  }\n  if (pos < text.length) out.push(text.slice(pos));\n  return out;\n}\n\nfunction initials(name: string) {\n  return name.split(\" \").map((w) => w[0]).slice(0, 2).join(\"\").toUpperCase();\n}\n\n// Underline (not bold) for the matched range - a weight change would reflow.\nfunction Highlight({ text, range }: { text: string; range: [number, number] | null }) {\n  if (!range) return <>{text}</>;\n  return (\n    <>\n      {text.slice(0, range[0])}\n      <span className=\"underline underline-offset-2 decoration-muted-foreground/70\">{text.slice(range[0], range[1])}</span>\n      {text.slice(range[1])}\n    </>\n  );\n}\n\nexport default function MentionComposer({\n  people = PEOPLE,\n  anchor = \"caret\",\n  inspect = false,\n  onStateChange,\n  onSubmit,\n}: {\n  people?: Person[];\n  anchor?: \"caret\" | \"field\";\n  inspect?: boolean;\n  onStateChange?: (state: MentionState) => void;\n  onSubmit?: (value: string, mentions: string[]) => void;\n}) {\n  const [value, setValue] = useState(\"Nice catch, let’s loop in \");\n  const [mentioned, setMentioned] = useState<string[]>([]);\n  const [open, setOpen] = useState(false);\n  const [token, setToken] = useState<{ start: number; end: number; query: string } | null>(null);\n  const [matches, setMatches] = useState<Match[]>(() => rank(people, \"\"));\n  const [active, setActive] = useState(0);\n  const [caret, setCaret] = useState<{ x: number; y: number; tx: number; ty: number; h: number } | null>(null);\n  const [placement, setPlacement] = useState(\"below\");\n  const [lastMention, setLastMention] = useState<string | null>(null);\n  const [sent, setSent] = useState(false);\n\n  const rootRef = useRef<HTMLDivElement>(null);\n  const fieldRef = useRef<HTMLDivElement>(null);\n  const inputRef = useRef<HTMLTextAreaElement>(null);\n  const mirrorRef = useRef<HTMLDivElement>(null);\n  const backdropInnerRef = useRef<HTMLDivElement>(null);\n  const popRef = useRef<HTMLDivElement>(null);\n  const popCardRef = useRef<HTMLDivElement>(null);\n  const clipRef = useRef<HTMLDivElement>(null);\n  const listRef = useRef<HTMLUListElement>(null);\n  const wasOpenRef = useRef(false);\n  const composingRef = useRef(false);\n  const dismissedRef = useRef<number | null>(null);\n  const pendingCaretRef = useRef<{ index: number; focus: boolean } | null>(null);\n  const sentTimerRef = useRef<number>(0);\n  const syncRef = useRef<(overrideIndex?: number) => void>(() => {});\n  const listboxId = useId();\n\n  const query = token?.query ?? \"\";\n\n  function measureCaret(caretIndex: number) {\n    const ta = inputRef.current;\n    const mirror = mirrorRef.current;\n    const field = fieldRef.current;\n    if (!ta || !mirror || !field) return;\n    mirror.style.width = `${ta.clientWidth}px`;\n    mirror.textContent = ta.value.slice(0, caretIndex);\n    const marker = document.createElement(\"span\");\n    marker.textContent = \"​\";\n    mirror.appendChild(marker);\n    const lineHeight = parseFloat(getComputedStyle(ta).lineHeight) || 24;\n    const tx = marker.offsetLeft - ta.scrollLeft;\n    const ty = marker.offsetTop - ta.scrollTop;\n    setCaret({ x: field.offsetLeft + tx, y: field.offsetTop + ty, tx, ty, h: lineHeight });\n  }\n\n  function sync(overrideIndex?: number) {\n    const ta = inputRef.current;\n    if (!ta || composingRef.current) return;\n    const caretIndex = overrideIndex ?? ta.selectionStart ?? ta.value.length;\n    const found = findToken(ta.value, caretIndex);\n    if (found) {\n      if (found.query !== (token?.query ?? null)) setActive(0);\n      const ranked = rank(people, found.query);\n      setToken(found);\n      setMatches(ranked);\n      setOpen(ranked.length > 0 && dismissedRef.current !== found.start);\n    } else {\n      dismissedRef.current = null;\n      setToken(null);\n      setOpen(false);\n    }\n    measureCaret(caretIndex);\n  }\n  syncRef.current = sync;\n\n  function autoGrow() {\n    const ta = inputRef.current;\n    if (!ta) return;\n    ta.style.height = \"auto\";\n    ta.style.height = `${ta.scrollHeight}px`;\n  }\n\n  useLayoutEffect(() => {\n    autoGrow();\n    const ta = inputRef.current;\n    if (!ta) return;\n    const pending = pendingCaretRef.current;\n    pendingCaretRef.current = null;\n    if (pending) {\n      if (pending.focus) {\n        ta.focus({ preventScroll: true });\n        ta.setSelectionRange(pending.index, pending.index);\n        sync();\n      } else {\n        sync(pending.index);\n      }\n    } else {\n      sync();\n    }\n    if (backdropInnerRef.current) backdropInnerRef.current.style.transform = `translateY(${-ta.scrollTop}px)`;\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [value]);\n\n  useLayoutEffect(() => {\n    const pop = popRef.current;\n    const card = popCardRef.current;\n    const wrapper = rootRef.current;\n    const field = fieldRef.current;\n    if (!pop || !wrapper || !field) return;\n    if (!open) {\n      wasOpenRef.current = false;\n      return;\n    }\n    const gap = 6;\n    const caretX = caret ? caret.x : field.offsetLeft;\n    const caretY = caret ? caret.y : field.offsetTop;\n    const lineH = caret ? caret.h : 24;\n    const anchorX = anchor === \"caret\" ? caretX : field.offsetLeft;\n    const belowY = anchor === \"caret\" ? caretY + lineH + gap : field.offsetTop + field.offsetHeight + gap;\n    const aboveAnchorY = anchor === \"caret\" ? caretY : field.offsetTop;\n    const popW = pop.offsetWidth;\n    const listH = listRef.current?.offsetHeight ?? 0;\n    const popH = listH + 8;\n    const left = Math.max(0, Math.min(anchorX, wrapper.clientWidth - popW));\n    const rect = wrapper.getBoundingClientRect();\n    const fitsBelow = rect.top + belowY + popH + 12 <= window.innerHeight;\n    const aboveY = aboveAnchorY - popH - gap;\n    const useAbove = !fitsBelow && rect.top + aboveY >= 8;\n    const top = useAbove ? aboveY : belowY;\n    setPlacement(useAbove ? \"above\" : \"below\");\n    card?.style.setProperty(\"--ox\", `${Math.max(12, Math.min(anchorX - left, popW - 12))}px`);\n    const clip = clipRef.current;\n    if (!wasOpenRef.current) {\n      pop.style.transition = \"none\";\n      if (clip) clip.style.transition = \"none\";\n      pop.style.translate = `${left}px ${top}px`;\n      if (clip) clip.style.height = `${listH}px`;\n      void pop.offsetWidth;\n      pop.style.transition = \"\";\n      if (clip) clip.style.transition = \"\";\n    } else {\n      pop.style.translate = `${left}px ${top}px`;\n      if (clip) clip.style.height = `${listH}px`;\n    }\n    wasOpenRef.current = true;\n  }, [open, caret, anchor, matches.length]);\n\n  useEffect(() => {\n    if (!open) return;\n    listRef.current?.children[active]?.scrollIntoView({ block: \"nearest\" });\n  }, [active, open]);\n\n  useEffect(() => {\n    if (!inspect) {\n      if (document.activeElement !== inputRef.current) setOpen(false);\n      return;\n    }\n    dismissedRef.current = null;\n    const current = inputRef.current?.value ?? \"\";\n    if (findToken(current, current.length)) {\n      sync(current.length);\n    } else {\n      const next = `${current}${current === \"\" || /\\s$/.test(current) ? \"\" : \" \"}@`;\n      pendingCaretRef.current = { index: next.length, focus: false };\n      setValue(next);\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [inspect]);\n\n  useEffect(() => {\n    const onResize = () => syncRef.current();\n    window.addEventListener(\"resize\", onResize);\n    return () => window.removeEventListener(\"resize\", onResize);\n  }, []);\n\n  useEffect(() => () => clearTimeout(sentTimerRef.current), []);\n\n  useEffect(() => {\n    onStateChange?.({\n      open,\n      query,\n      matches: matches.length,\n      caret: caret ? { x: Math.round(caret.tx), y: Math.round(caret.ty) } : null,\n      placement,\n      anchor,\n      mentions: mentioned.length,\n      lastMention,\n    });\n  }, [open, query, matches.length, caret, placement, anchor, mentioned, lastMention, onStateChange]);\n\n  function insertMention(person: Person) {\n    const ta = inputRef.current;\n    if (!ta || !token) return;\n    const mention = `@${person.name}`;\n    const next = `${value.slice(0, token.start)}${mention} ${value.slice(ta.selectionStart)}`;\n    pendingCaretRef.current = { index: token.start + mention.length + 1, focus: true };\n    setValue(next);\n    setMentioned((list) => (list.includes(person.name) ? list : [...list, person.name]));\n    setLastMention(person.name);\n    setToken(null);\n    setOpen(false);\n  }\n\n  function handleKeyDown(event: React.KeyboardEvent) {\n    if (!open) return;\n    if (event.key === \"ArrowDown\") {\n      event.preventDefault();\n      setActive((index) => (index + 1) % matches.length);\n    } else if (event.key === \"ArrowUp\") {\n      event.preventDefault();\n      setActive((index) => (index - 1 + matches.length) % matches.length);\n    } else if (event.key === \"Enter\" || event.key === \"Tab\") {\n      event.preventDefault();\n      insertMention(matches[active].person);\n    } else if (event.key === \"Escape\") {\n      event.preventDefault();\n      dismissedRef.current = token?.start ?? null;\n      setOpen(false);\n    }\n  }\n\n  function handleScroll() {\n    const ta = inputRef.current;\n    if (!ta) return;\n    if (backdropInnerRef.current) backdropInnerRef.current.style.transform = `translateY(${-ta.scrollTop}px)`;\n    if (token) measureCaret(ta.selectionStart ?? 0);\n  }\n\n  function handleSend() {\n    onSubmit?.(value, mentioned);\n    setSent(true);\n    clearTimeout(sentTimerRef.current);\n    sentTimerRef.current = window.setTimeout(() => setSent(false), 1400);\n    setValue(\"\");\n    setMentioned([]);\n    setToken(null);\n    setOpen(false);\n  }\n\n  const iconShown = { opacity: 1, scale: 1, filter: \"blur(0px)\" };\n  const iconHidden = { opacity: 0, scale: 0.25, filter: \"blur(4px)\" };\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <div ref={rootRef} className=\"relative w-full\" data-inspect={inspect ? \"true\" : \"false\"}>\n        <div\n          className={`bg-card rounded-2xl px-4 pt-3.5 pb-2.5 shadow-border [transition:box-shadow_200ms_ease] hover:shadow-border-hover focus-within:shadow-border-hover focus-within:outline focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-ring${\n            inspect ? \" !shadow-none outline outline-[1.5px] outline-dashed outline-[#3b82f6]\" : \"\"\n          }`}\n        >\n          <div className=\"relative\" ref={fieldRef}>\n            <div className={`${TEXT} absolute inset-0 overflow-hidden text-transparent pointer-events-none`} aria-hidden=\"true\">\n              <div ref={backdropInnerRef}>{buildSegments(value, mentioned, token, inspect)}</div>\n            </div>\n            <textarea\n              ref={inputRef}\n              className={`${TEXT} block relative z-[1] w-full min-h-12 max-h-[10.5rem] p-0 border-0 resize-none overflow-y-auto bg-transparent text-foreground caret-foreground outline-none placeholder:text-muted-foreground/70`}\n              value={value}\n              rows={2}\n              placeholder=\"Write a comment, @ to mention\"\n              aria-label=\"Comment\"\n              aria-autocomplete=\"list\"\n              aria-expanded={open}\n              aria-controls={listboxId}\n              aria-activedescendant={open ? `${listboxId}-${active}` : undefined}\n              spellCheck={false}\n              onChange={(event) => setValue(event.target.value)}\n              onKeyDown={handleKeyDown}\n              onKeyUp={() => sync()}\n              onClick={() => sync()}\n              onFocus={() => sync()}\n              onBlur={() => {\n                if (!inspect) setOpen(false);\n              }}\n              onScroll={handleScroll}\n              onCompositionStart={() => {\n                composingRef.current = true;\n              }}\n              onCompositionEnd={() => {\n                composingRef.current = false;\n                sync();\n              }}\n            />\n            <div className={`${TEXT} absolute top-0 left-0 invisible pointer-events-none`} ref={mirrorRef} aria-hidden=\"true\" />\n          </div>\n\n          <div className=\"flex items-center justify-between gap-3 mt-2\">\n            <span className=\"inline-flex items-center gap-1.5 text-xs text-muted-foreground/70\">\n              <kbd className=\"font-[inherit] text-[0.6875rem] font-semibold text-muted-foreground bg-muted px-[0.3125rem] rounded-[0.25rem] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.06)]\">@</kbd> to mention\n              {mentioned.length > 0 && <> · {mentioned.length} mentioned</>}\n            </span>\n            <button\n              type=\"button\"\n              className=\"grid place-items-center w-9 h-9 rounded-[0.625rem] text-muted-foreground [transition:scale_150ms_ease-out,background-color_200ms_ease,color_200ms_ease,opacity_200ms_ease] hover:enabled:bg-accent hover:enabled:text-foreground active:enabled:scale-[0.96] disabled:opacity-35 disabled:cursor-default focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n              disabled={!sent && value.trim() === \"\"}\n              onClick={handleSend}\n              aria-label=\"Send comment\"\n            >\n              <motion.span className=\"[grid-area:1/1] inline-flex [&>svg]:[translate:-1px_1px]\" initial={false} animate={sent ? iconHidden : iconShown} transition={{ duration: 0.3, ease: EASE_ICON }}>\n                <SendIcon />\n              </motion.span>\n              <motion.span className=\"[grid-area:1/1] inline-flex text-[#16a34a]\" initial={false} animate={sent ? iconShown : iconHidden} transition={{ duration: 0.3, ease: EASE_ICON }} aria-hidden=\"true\">\n                <CheckIcon />\n              </motion.span>\n            </button>\n          </div>\n        </div>\n\n        {/* Popover: always mounted; `translate` on the shell (imperative) anchors it. */}\n        <div\n          className=\"group/pop absolute top-0 left-0 w-[15rem] z-30 pointer-events-none [transition:translate_140ms_var(--ease-smooth-out)] data-[open=true]:pointer-events-auto\"\n          ref={popRef}\n          data-open={open ? \"true\" : \"false\"}\n          data-placement={placement}\n          aria-hidden={!open}\n        >\n          <div\n            className={`relative bg-popover rounded-xl p-1 shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_12px_32px_-8px_rgba(0,0,0,0.22)] [transform-origin:var(--ox,1rem)_top] opacity-0 scale-[0.96] -translate-y-1 blur-[2px] invisible [transition:opacity_140ms_var(--ease-smooth-out),scale_140ms_var(--ease-smooth-out),translate_140ms_var(--ease-smooth-out),filter_140ms_var(--ease-smooth-out),visibility_0s_linear_140ms] group-data-[placement=above]/pop:[transform-origin:var(--ox,1rem)_bottom] group-data-[placement=above]/pop:translate-y-1 group-data-[open=true]/pop:opacity-100 group-data-[open=true]/pop:scale-100 group-data-[open=true]/pop:translate-y-0 group-data-[open=true]/pop:blur-[0px] group-data-[open=true]/pop:visible group-data-[open=true]/pop:[transition:opacity_180ms_var(--ease-smooth-out),scale_180ms_var(--ease-smooth-out),translate_180ms_var(--ease-smooth-out),filter_180ms_var(--ease-smooth-out),visibility_0s]${\n              inspect ? \" outline outline-[1.5px] outline-dashed outline-[#ef4444]\" : \"\"\n            }`}\n            ref={popCardRef}\n          >\n            <div className=\"overflow-hidden [transition:height_160ms_var(--ease-smooth-out)]\" ref={clipRef}>\n              <ul role=\"listbox\" id={listboxId} aria-label=\"People to mention\" className=\"flex flex-col max-h-[15.5rem] overflow-y-auto\" ref={listRef}>\n                {matches.map((match, index) => (\n                  <li\n                    key={match.person.handle}\n                    id={`${listboxId}-${index}`}\n                    role=\"option\"\n                    aria-selected={index === active}\n                    className=\"flex items-center gap-2.5 min-h-10 px-2 py-1.5 rounded-lg cursor-pointer opacity-0 translate-y-1 [transition:background-color_90ms_ease,opacity_200ms_var(--ease-smooth-out),translate_200ms_var(--ease-smooth-out)] group-data-[open=true]/pop:opacity-100 group-data-[open=true]/pop:translate-y-0 group-data-[open=true]/pop:[transition-delay:0ms,calc(var(--i,0)*25ms),calc(var(--i,0)*25ms)] data-[active]:bg-accent\"\n                    style={{ \"--i\": Math.min(index, 5) } as React.CSSProperties}\n                    data-active={index === active ? \"true\" : undefined}\n                    onMouseDown={(event) => {\n                      event.preventDefault();\n                      insertMention(match.person);\n                    }}\n                    onMouseMove={() => setActive(index)}\n                  >\n                    <span className=\"flex-none grid place-items-center w-7 h-7 rounded-full bg-foreground/10 text-[0.625rem] font-semibold tracking-[0.02em] text-foreground/80\" aria-hidden=\"true\">\n                      {initials(match.person.name)}\n                    </span>\n                    <span className=\"flex flex-col min-w-0\">\n                      <span className=\"text-[0.8125rem] font-medium text-foreground whitespace-nowrap overflow-hidden text-ellipsis\">\n                        <Highlight text={match.person.name} range={match.nameRange} />\n                      </span>\n                      <span className=\"text-[0.6875rem] text-muted-foreground/70 whitespace-nowrap overflow-hidden text-ellipsis\">\n                        @<Highlight text={match.person.handle} range={match.handleRange} />\n                      </span>\n                    </span>\n                  </li>\n                ))}\n              </ul>\n            </div>\n            {inspect && <SpecLabel className=\"top-[calc(100%+0.4rem)] right-0 border-[#fecaca] text-[#dc2626]\">translate: caret + line · clamp ↔ · flip ↕</SpecLabel>}\n          </div>\n        </div>\n\n        {open && (\n          <span className=\"sr-only\" aria-live=\"polite\">\n            {matches.length} {matches.length === 1 ? \"person\" : \"people\"} found. Press up and down to navigate, Enter to mention.\n          </span>\n        )}\n\n        {inspect && (\n          <>\n            <SpecLabel className=\"-top-[1.7rem] left-0 border-[#bfdbfe] text-[#2563eb]\">hidden mirror re-typesets the value → marker = caret</SpecLabel>\n            {caret && (\n              <span className=\"absolute top-0 left-0 w-0.5 h-[var(--h,1.5rem)] bg-[#ef4444] z-[35] pointer-events-none\" style={{ translate: `${caret.x}px ${caret.y}px`, \"--h\": `${caret.h}px` } as React.CSSProperties}>\n                <SpecLabel className=\"top-0 left-1.5 border-[#fecaca] text-[#dc2626]\">caret · x {Math.round(caret.tx)} · y {Math.round(caret.ty)}</SpecLabel>\n              </span>\n            )}\n          </>\n        )}\n      </div>\n    </MotionConfig>\n  );\n}\n\nfunction SpecLabel({ className = \"\", children }: { className?: string; children: ReactNode }) {\n  return <span className={`absolute z-[36] whitespace-nowrap rounded-[0.25rem] border bg-white px-[0.3125rem] py-[0.0625rem] text-[0.625rem] font-medium leading-normal tracking-[0.01em] shadow-[0_1px_2px_rgba(0,0,0,0.08)] pointer-events-none ${className}`}>{children}</span>;\n}\n\nconst PEOPLE: Person[] = [\n  { name: \"Sarah Chen\", handle: \"sarahchen\" },\n  { name: \"Omar Farouk\", handle: \"omarfarouk\" },\n  { name: \"June Park\", handle: \"junepark\" },\n  { name: \"Maya Lindberg\", handle: \"mayalindberg\" },\n  { name: \"Tomás Rivera\", handle: \"tomasrivera\" },\n  { name: \"Ali Hassan\", handle: \"alihassan\" },\n  { name: \"Nadia Rahman\", handle: \"nadiarahman\" },\n  { name: \"Leo Okafor\", handle: \"leookafor\" },\n];\n\nfunction Svg({ children, size = 18 }: { children: ReactNode; size?: number }) {\n  return (\n    <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n      {children}\n    </svg>\n  );\n}\nfunction SendIcon() { return <Svg><path d=\"M22 2 11 13\" /><path d=\"M22 2 15 22l-4-9-9-4Z\" /></Svg>; }\nfunction CheckIcon() { return <Svg><path d=\"M20 6 9 17l-5-5\" /></Svg>; }\n"
    }
  ]
}
