{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "prompt-chips",
  "type": "registry:block",
  "title": "Prompt Chips",
  "description": "Prompt suggestions drifting past in endless rows that ease to a stop on hover and glide when flicked. Click one and it springs along an arc into the input, where the full prompt types itself in.",
  "docs": "Prompt Chips was added to public/vividsites/prompt-chips.html. Paste its markup, <style> and <script> where you want it on the page (or open the file to see it alone). Customise it with the CSS variables at the top. Docs and live demo: https://vividsites.app/c/prompt-chips",
  "categories": [
    "ui",
    "ai-interfaces"
  ],
  "files": [
    {
      "path": "components/vividsites/prompt-chips.html",
      "type": "registry:file",
      "target": "public/vividsites/prompt-chips.html",
      "content": "<!--\nvs-prompt-chips: prompt suggestions that drift past in endless rows. Hover a row and it\n  eases to a stop; drag or flick it (it glides with momentum, then settles back to cruise).\n  Click a chip and a copy of it springs along an arc into the input, where the full prompt\n  types itself in and the field glows. Tab through chips: the row stops and scrolls the\n  focused chip into view.\n\n  Markup: .vs-prompt-chips > form.vs-prompt-chips-bar (auto-growing textarea + send; Enter sends) + .vs-prompt-chips-rows\n          > .vs-prompt-chips-row > button.vs-prompt-chips-chip\n  Row attributes:  data-dir=\"-1\" (left) | \"1\" (right) · data-speed=\"1.2\" (multiplier)\n  Chip attributes: data-prompt=\"the full prompt to type\" · data-icon=\"write|plan|learn|code|idea|trip|list|mail|chart\"\n  Root attributes: data-theme=\"dark\" (for dark pages; light is the default) · data-target=\"#yourInput\" (fill your own field instead)\n                   data-focus=\"auto|always|never\" (focus the field after filling; auto skips touch)\n  Events: \"vs-prompt-chips:pick\" {label, prompt} (cancelable), \"vs-prompt-chips:submit\" {prompt} (cancelable)\n  JS:     const pc = vsPromptChips.mount(el) · pc.pick(i) · pc.fill(text)\n  Speed is the --vs-prompt-chips-speed variable (px/s). Reduced motion: rows hold still (still draggable).\n-->\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500&display=swap\" rel=\"stylesheet\">\n<div class=\"vs-prompt-chips\">\n  <form class=\"vs-prompt-chips-bar\">\n    <textarea class=\"vs-prompt-chips-input\" rows=\"1\" placeholder=\"Ask anything…\" aria-label=\"Prompt\"></textarea>\n    <button class=\"vs-prompt-chips-send\" type=\"submit\" aria-label=\"Send\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 19V5M6 11l6-6 6 6\"/></svg></button>\n  </form>\n  <div class=\"vs-prompt-chips-rows\">\n    <div class=\"vs-prompt-chips-row\" data-dir=\"-1\">\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"write\" data-prompt=\"Draft a polite but firm reminder to my tenant that March rent is five days late.\">Draft a rent reminder</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"trip\" data-prompt=\"Plan three easy days of hut-to-hut walking in the Dolomites for two beginners.\">Plan 3 days in the Dolomites</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"learn\" data-prompt=\"Explain compound interest like I'm fourteen, with one worked example.\">Explain compound interest</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"list\" data-prompt=\"Turn these meeting notes into a checklist with owners and due dates.\">Turn notes into a checklist</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"idea\" data-prompt=\"Suggest ten names for a small ceramics studio that sells tableware by the sea.\">Name my ceramics studio</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"code\" data-prompt=\"Why would a test fail only in CI? Walk me through how you'd debug it.\">Debug a flaky test</button>\n    </div>\n    <div class=\"vs-prompt-chips-row\" data-dir=\"1\" data-speed=\".8\">\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"mail\" data-prompt=\"Rewrite this email so it sounds warm but still says no to the deadline.\">Soften this email</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"chart\" data-prompt=\"Compare two job offers on salary, commute and growth, and help me weigh them honestly.\">Compare two job offers</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"plan\" data-prompt=\"Build a weekly vegetarian meal plan for two, under 40 minutes a night.\">Build a weekly meal plan</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"write\" data-prompt=\"Write a two-minute wedding toast for my sister: funny first, then tender.\">Write a toast for my sister</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"learn\" data-prompt=\"Summarise this freelance contract and flag anything unusual, in plain words.\">Summarise a contract</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"trip\" data-prompt=\"Make a carry-on packing list for four days in Oslo in March.\">Pack for Oslo in March</button>\n    </div>\n    <div class=\"vs-prompt-chips-row\" data-dir=\"-1\" data-speed=\"1.15\">\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"idea\" data-prompt=\"Brainstorm a two-player board game about tides and fishing boats.\">Invent a board game</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"code\" data-prompt=\"Explain what this regular expression matches, piece by piece.\">Explain this regex</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"chart\" data-prompt=\"Look at a month of my sleep data and tell me what is actually changing.\">Read my sleep data</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"list\" data-prompt=\"Give me eight likely questions for a product design interview, with a tip for each.\">Prep for an interview</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"write\" data-prompt=\"Critique my cover letter like a tough but kind hiring manager.\">Critique my cover letter</button>\n      <button class=\"vs-prompt-chips-chip\" data-icon=\"mail\" data-prompt=\"Reply to my landlord asking to fix the boiler before the weekend, politely.\">Reply to my landlord</button>\n    </div>\n  </div>\n</div>\n\n<style>\n.vs-prompt-chips {\n  --vs-prompt-chips-accent: #c6f432;                 /* send button, caret, chip hover glyph */\n  --vs-prompt-chips-on-accent: #10200f;              /* icon on the accent */\n  --vs-prompt-chips-ink: #18201a;                    /* text */\n  --vs-prompt-chips-chip: #ffffff;                   /* chip fill */\n  --vs-prompt-chips-line: rgba(24,32,26,.1);         /* hairlines */\n  --vs-prompt-chips-field: #ffffff;                  /* the input */\n  --vs-prompt-chips-radius: 999px;                   /* chip radius */\n  --vs-prompt-chips-speed: 34;                       /* marquee px/s (read by the script) */\n  --vs-prompt-chips-fade: 12%;                       /* edge fade width */\n  --vs-prompt-chips-font: \"Space Grotesk\", ui-sans-serif, system-ui, sans-serif;\n\n  position: relative; display: grid; gap: 1.6rem; width: 100%;\n  color: var(--vs-prompt-chips-ink); font-family: var(--vs-prompt-chips-font); -webkit-font-smoothing: antialiased;\n}\n/* on a dark page: data-theme=\"dark\" */\n.vs-prompt-chips[data-theme=\"dark\"] {\n  --vs-prompt-chips-ink: #eef4e8; --vs-prompt-chips-chip: rgba(236,248,228,.06); --vs-prompt-chips-line: rgba(236,248,228,.12);\n  --vs-prompt-chips-field: rgba(236,248,228,.07);\n}\n.vs-prompt-chips, .vs-prompt-chips * { box-sizing: border-box; }\n\n/* ---- the input ---- */\n.vs-prompt-chips-bar {\n  position: relative; display: flex; align-items: flex-end; gap: .5rem; width: min(40rem, 100%); margin: 0 auto;\n  padding: .45rem .45rem .45rem 1.35rem; border-radius: 1.25rem; background: var(--vs-prompt-chips-field);\n  box-shadow: inset 0 0 0 .5px var(--vs-prompt-chips-line), inset 0 1px 0 rgba(255,255,255,.07), 0 18px 40px -18px rgba(0,0,0,.45), 0 2px 6px -2px rgba(0,0,0,.2);\n  -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3);\n  transition: box-shadow .35s cubic-bezier(.2,0,0,1);\n}\n.vs-prompt-chips:not([data-theme=\"dark\"]) .vs-prompt-chips-bar { box-shadow: inset 0 0 0 .5px var(--vs-prompt-chips-line), 0 18px 40px -20px rgba(20,40,20,.25), 0 2px 6px -2px rgba(20,40,20,.08); }\n.vs-prompt-chips-bar:focus-within { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vs-prompt-chips-accent) 55%, transparent), 0 0 0 4px color-mix(in srgb, var(--vs-prompt-chips-accent) 14%, transparent), 0 18px 40px -18px rgba(0,0,0,.45); }\n.vs-prompt-chips-bar.is-filled { animation: vs-prompt-chips-glow .9s cubic-bezier(.16,1,.3,1); }\n@keyframes vs-prompt-chips-glow { 0% { box-shadow: inset 0 0 0 1px var(--vs-prompt-chips-accent), 0 0 0 0 color-mix(in srgb, var(--vs-prompt-chips-accent) 45%, transparent); } 100% { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vs-prompt-chips-accent) 55%, transparent), 0 0 0 14px transparent; } }\n.vs-prompt-chips-input {\n  all: unset; box-sizing: border-box; display: block; flex: 1; min-width: 0; resize: none; overflow-y: auto; scrollbar-width: none;\n  min-height: 2.75rem; max-height: 7.2rem; padding: .66rem 0; white-space: pre-wrap; overflow-wrap: anywhere;\n  font: 400 1.0625rem/1.4 var(--vs-prompt-chips-font); letter-spacing: -.005em;\n  color: var(--vs-prompt-chips-ink); caret-color: var(--vs-prompt-chips-accent);\n}\n.vs-prompt-chips-input::-webkit-scrollbar { display: none; }\n.vs-prompt-chips-input::placeholder { color: color-mix(in srgb, var(--vs-prompt-chips-ink) 42%, transparent); }\n.vs-prompt-chips-send {\n  all: unset; flex: none; display: grid; place-items: center; width: 2.75rem; height: 2.75rem; border-radius: .9rem; cursor: pointer;\n  background: var(--vs-prompt-chips-accent); color: var(--vs-prompt-chips-on-accent);\n  box-shadow: inset 0 1px 0 rgba(255,255,255,.45), inset 0 -1px 0 rgba(0,0,0,.12), 0 6px 16px -6px color-mix(in srgb, var(--vs-prompt-chips-accent) 70%, transparent);\n  transition: transform .45s cubic-bezier(.3,1.5,.5,1), opacity .25s, filter .25s, box-shadow .25s;\n}\n.vs-prompt-chips-send svg { width: 1.15rem; height: 1.15rem; transition: transform .45s cubic-bezier(.3,1.5,.5,1); }\n.vs-prompt-chips-send:hover:not(:disabled) svg { transform: translateY(-2px); }\n.vs-prompt-chips-send:active:not(:disabled) { transform: scale(.92); }\n.vs-prompt-chips-send:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--vs-prompt-chips-ink) 90%, transparent); }\n.vs-prompt-chips-send:disabled { cursor: default; opacity: .3; filter: saturate(.2); box-shadow: none; transform: scale(.94); }\n\n/* ---- the rows ---- */\n.vs-prompt-chips-rows { display: grid; gap: .7rem; }\n.vs-prompt-chips-row {\n  position: relative; overflow: hidden; padding: .35rem 0; touch-action: pan-y; cursor: grab; user-select: none; -webkit-user-select: none;\n  -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--vs-prompt-chips-fade), #000 calc(100% - var(--vs-prompt-chips-fade)), transparent);\n          mask-image: linear-gradient(90deg, transparent, #000 var(--vs-prompt-chips-fade), #000 calc(100% - var(--vs-prompt-chips-fade)), transparent);\n}\n.vs-prompt-chips-row.is-dragging { cursor: grabbing; }\n.vs-prompt-chips-track { display: flex; gap: .6rem; width: max-content; will-change: transform; }\n.vs-prompt-chips-chip {\n  all: unset; box-sizing: border-box; flex: none; display: inline-flex; align-items: center; gap: .55rem; cursor: pointer;\n  height: 2.6rem; padding: 0 1.05rem 0 .85rem; border-radius: var(--vs-prompt-chips-radius); white-space: nowrap;\n  font: 400 .96875rem/1 var(--vs-prompt-chips-font); letter-spacing: -.005em; color: color-mix(in srgb, var(--vs-prompt-chips-ink) 86%, transparent);\n  background: var(--vs-prompt-chips-chip);\n  box-shadow: inset 0 0 0 .5px var(--vs-prompt-chips-line), inset 0 1px 0 rgba(255,255,255,.05);\n  transition: background-color .2s cubic-bezier(.2,0,0,1), color .2s, box-shadow .2s, transform .5s cubic-bezier(.3,1.6,.5,1);\n  -webkit-tap-highlight-color: transparent;\n}\n.vs-prompt-chips:not([data-theme=\"dark\"]) .vs-prompt-chips-chip { box-shadow: inset 0 0 0 .5px var(--vs-prompt-chips-line), 0 1px 2px rgba(20,40,20,.06); }\n.vs-prompt-chips-chip svg { width: 1rem; height: 1rem; flex: none; opacity: .6; transition: opacity .2s, color .2s, transform .5s cubic-bezier(.3,1.6,.5,1); }\n.vs-prompt-chips-chip:hover { color: var(--vs-prompt-chips-ink); background: color-mix(in srgb, var(--vs-prompt-chips-ink) 11%, transparent); transform: translateY(-2px); }\n.vs-prompt-chips:not([data-theme=\"dark\"]) .vs-prompt-chips-chip:hover { background: #fff; box-shadow: inset 0 0 0 .5px var(--vs-prompt-chips-line), 0 8px 18px -8px rgba(20,40,20,.25); }\n.vs-prompt-chips-chip:hover svg { opacity: 1; color: var(--vs-prompt-chips-accent); transform: rotate(-8deg) scale(1.08); }\n.vs-prompt-chips:not([data-theme=\"dark\"]) .vs-prompt-chips-chip:hover svg { color: color-mix(in srgb, var(--vs-prompt-chips-accent) 55%, #18201a); }\n.vs-prompt-chips-chip:active { transform: translateY(0) scale(.95); transition-duration: .2s, .2s, .2s, .12s; }\n.vs-prompt-chips-chip:focus-visible { box-shadow: inset 0 0 0 1.5px var(--vs-prompt-chips-accent); color: var(--vs-prompt-chips-ink); }\n.vs-prompt-chips-chip.is-picked { opacity: .35; }\n/* the flying copy */\n.vs-prompt-chips-ghost { position: fixed; left: 0; top: 0; z-index: 2147483000; pointer-events: none; margin: 0; will-change: transform, opacity; transition: none !important;\n  background: color-mix(in srgb, var(--vs-prompt-chips-accent) 22%, var(--vs-prompt-chips-chip)); color: var(--vs-prompt-chips-ink);\n  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vs-prompt-chips-accent) 60%, transparent), 0 14px 30px -10px rgba(0,0,0,.4); }\n\n@media (max-width: 600px) {\n  .vs-prompt-chips { gap: 1.2rem; }\n  .vs-prompt-chips-bar { padding-left: 1.05rem; }\n  .vs-prompt-chips-chip { height: 2.4rem; font-size: .9rem; }\n}\n@media (prefers-reduced-motion: reduce) {\n  .vs-prompt-chips-bar.is-filled { animation: none; }\n  .vs-prompt-chips *, .vs-prompt-chips-ghost { transition-duration: 0s !important; }\n}\n</style>\n\n<script>\n(() => {\n  if (window.vsPromptChips) return;\n  const reduce = matchMedia('(prefers-reduced-motion: reduce)');\n  const coarse = matchMedia('(pointer: coarse)');\n  const ICONS = {\n    write: '<path d=\"M4 20h4L19 9l-4-4L4 16z\"/><path d=\"M13.5 6.5l4 4\"/>',\n    plan: '<rect x=\"4\" y=\"5\" width=\"16\" height=\"15\" rx=\"3\"/><path d=\"M4 10h16M9 3v4M15 3v4\"/>',\n    learn: '<path d=\"M4 19V6a2 2 0 0 1 2-2h13v13H6a2 2 0 0 0-2 2zm0 0a2 2 0 0 0 2 2h13\"/>',\n    code: '<path d=\"M9 7l-5 5 5 5M15 7l5 5-5 5\"/>',\n    idea: '<path d=\"M9.5 18h5M10.5 21h3\"/><path d=\"M12 3a6 6 0 0 0-3.6 10.8c.8.6 1.1 1.4 1.1 2.2h5c0-.8.3-1.6 1.1-2.2A6 6 0 0 0 12 3z\"/>',\n    trip: '<circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M15.5 8.5l-2 5-5 2 2-5z\"/>',\n    list: '<path d=\"M10 6h10M10 12h10M10 18h10\"/><path d=\"M3.5 6l1.2 1.2L7 5M3.5 12l1.2 1.2L7 11M3.5 18l1.2 1.2L7 17\"/>',\n    mail: '<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"3\"/><path d=\"M4 7.5l8 5.5 8-5.5\"/>',\n    chart: '<path d=\"M5 20v-8M11 20V5M17 20v-5M3 20h18\"/>',\n  };\n  const svg = k => `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${ICONS[k] || ICONS.idea}</svg>`;\n\n  // one shared rAF ticker\n  const live = new Set(); let raf = 0, last = 0;\n  const tick = now => {\n    const dt = Math.min(.05, (now - last) / 1000 || .016); last = now;\n    for (const f of live) if (!f(dt)) live.delete(f);\n    raf = live.size ? requestAnimationFrame(tick) : 0;\n  };\n  const wake = f => { live.add(f); if (!raf) { last = performance.now(); raf = requestAnimationFrame(tick); } };\n\n  function mount(root, opts = {}) {\n    if (root.__vsPC) return root.__vsPC;\n    const form = root.querySelector('.vs-prompt-chips-bar');\n    const input = opts.input || (root.dataset.target && document.querySelector(root.dataset.target)) || root.querySelector('.vs-prompt-chips-input');\n    const send = root.querySelector('.vs-prompt-chips-send');\n    const speed = () => reduce.matches ? 0 : parseFloat(getComputedStyle(root).getPropertyValue('--vs-prompt-chips-speed')) || 0;\n    root.querySelectorAll('.vs-prompt-chips-chip').forEach(c => {\n      c.type = 'button';\n      if (c.dataset.icon && !c.querySelector('svg')) c.insertAdjacentHTML('afterbegin', svg(c.dataset.icon));\n    });\n    let visible = true, typing = 0;\n    const grow = () => { if (input.tagName !== 'TEXTAREA') return; input.style.height = 'auto'; input.style.height = input.scrollHeight + 'px'; };\n    const sync = () => { grow(); if (send) send.disabled = !input.value.trim(); };\n\n    // ---------- rows: an endless track per row, with drag + inertia ----------\n    const rows = [...root.querySelectorAll('.vs-prompt-chips-row')].map(row => {\n      const dir = (+row.dataset.dir || -1) < 0 ? -1 : 1, mul = +row.dataset.speed || 1;\n      const track = document.createElement('div'); track.className = 'vs-prompt-chips-track';\n      const originals = [...row.children]; originals.forEach(c => track.append(c)); row.append(track);\n      if (!row.hasAttribute('aria-label')) row.setAttribute('aria-label', 'Suggestions');\n      let clones = [], W = 1;\n      const R = { x: 0, v: dir * speed() * mul, hover: false, focus: false, drag: null, shift: 0 };\n      function layout() {\n        clones.forEach(c => c.remove()); clones = [];\n        const add = () => originals.forEach(o => {\n          const c = o.cloneNode(true); c.setAttribute('aria-hidden', 'true'); c.tabIndex = -1; track.append(c); clones.push(c);\n        });\n        add(); W = Math.max(1, clones[0].offsetLeft - originals[0].offsetLeft);\n        for (let n = 1; W * n < row.clientWidth + 2 && n < 12; n++) add();\n        R.x = ((R.x % W) - W) % W;\n      }\n      layout(); new ResizeObserver(() => layout()).observe(row);\n      const target = () => (R.hover || R.focus || typing) ? 0 : dir * speed() * mul;\n      R.step = dt => {\n        if (!R.drag) {\n          // momentum with linear drag toward the cruise speed (a flick glides, then settles)\n          R.v = target() + (R.v - target()) * Math.exp(-(R.hover ? 7 : 2.6) * dt);\n          R.x += R.v * dt;\n          if (R.shift) { const d = R.shift * (1 - Math.exp(-12 * dt)); R.x += d; R.shift -= d; if (Math.abs(R.shift) < .3) { R.x += R.shift; R.shift = 0; } }\n        }\n        R.x = ((R.x % W) - W) % W;\n        track.style.transform = `translate3d(${R.x.toFixed(2)}px,0,0)`;\n      };\n      row.addEventListener('pointerenter', e => { if (e.pointerType === 'mouse') R.hover = true; });\n      row.addEventListener('pointerleave', () => { R.hover = false; });\n      row.addEventListener('focusin', e => {\n        R.focus = true;\n        const rr = row.getBoundingClientRect(), cr = e.target.getBoundingClientRect(), pad = rr.width * .14;\n        if (cr.left < rr.left + pad) R.shift = rr.left + pad - cr.left;\n        else if (cr.right > rr.right - pad) R.shift = rr.right - pad - cr.right;\n      });\n      row.addEventListener('focusout', () => { R.focus = false; });\n      row.addEventListener('pointerdown', e => {\n        if (e.button > 0) return;\n        R.drag = { id: e.pointerId, x0: e.clientX, px: R.x, lx: e.clientX, t: performance.now(), v: 0, on: false };\n        R.v = 0;\n      });\n      row.addEventListener('pointermove', e => {\n        const d = R.drag; if (!d || d.id !== e.pointerId) return;\n        const dx = e.clientX - d.x0;\n        if (!d.on && Math.abs(dx) > 6) { d.on = true; row.classList.add('is-dragging'); try { row.setPointerCapture(e.pointerId); } catch (_) {} }\n        if (!d.on) return;\n        const now = performance.now(), dtm = Math.max(1, now - d.t);\n        d.v = d.v * .5 + ((e.clientX - d.lx) / (dtm / 1000)) * .5; d.t = now; d.lx = e.clientX;\n        R.x = d.px + dx;\n      });\n      const up = e => {\n        const d = R.drag; if (!d || d.id !== e.pointerId) return;\n        R.drag = null; row.classList.remove('is-dragging');\n        if (d.on) { R.v = Math.max(-2600, Math.min(2600, performance.now() - d.t > 80 ? 0 : d.v)); row.__vsPCdragged = true; setTimeout(() => row.__vsPCdragged = false, 0); }\n        if (e.pointerType !== 'mouse') R.hover = false;\n      };\n      row.addEventListener('pointerup', up); row.addEventListener('pointercancel', up);\n      row.addEventListener('click', e => {\n        const chip = e.target.closest('.vs-prompt-chips-chip');\n        if (!chip) return;\n        if (row.__vsPCdragged) { e.preventDefault(); e.stopPropagation(); return; }\n        pick(chip);\n      }, true);\n      return R;\n    });\n    const loop = dt => { rows.forEach(r => r.step(dt)); return visible; };\n    new IntersectionObserver(([e]) => { visible = e.isIntersecting; if (visible) wake(loop); }).observe(root);\n    wake(loop);\n\n    // ---------- pick: fly a copy of the chip into the input, then type the prompt ----------\n    function type(text) {\n      const my = ++typing, cps = Math.max(70, text.length / .85);\n      input.value = ''; sync();\n      if (reduce.matches) { input.value = text; done(); return; }\n      const t0 = performance.now();\n      const f = () => {\n        if (my !== typing) return false;\n        const n = Math.min(text.length, Math.ceil((performance.now() - t0) / 1000 * cps));\n        input.value = text.slice(0, n); sync();\n        if (n >= text.length) { done(); return false; }\n        return true;\n      };\n      wake(f);\n      function done() {\n        typing = 0; sync();\n        if (form) { form.classList.remove('is-filled'); void form.offsetWidth; form.classList.add('is-filled'); }\n        const mode = root.dataset.focus || 'auto';\n        if (mode === 'always' || (mode === 'auto' && !coarse.matches)) { input.focus({ preventScroll: true }); try { input.setSelectionRange(text.length, text.length); } catch (_) {} }\n      }\n    }\n    function pick(chip) {\n      const label = chip.textContent.trim(), prompt = chip.dataset.prompt || label;\n      const ev = new CustomEvent('vs-prompt-chips:pick', { bubbles: true, cancelable: true, detail: { label, prompt, chip } });\n      if (!root.dispatchEvent(ev)) return;\n      if (reduce.matches || !input.getBoundingClientRect().width) return type(prompt);\n      const cr = chip.getBoundingClientRect(), ir = input.getBoundingClientRect(), ic = chip.querySelector('svg');\n      const textOff = ic ? ic.getBoundingClientRect().right - cr.left + 8.8 : parseFloat(getComputedStyle(chip).paddingLeft);\n      const g = chip.cloneNode(true); g.removeAttribute('aria-hidden'); g.setAttribute('aria-hidden', 'true');\n      g.classList.add('vs-prompt-chips-ghost'); g.style.width = cr.width + 'px'; g.style.height = cr.height + 'px';\n      root.append(g);\n      const lh = parseFloat(getComputedStyle(input).lineHeight) || 24, pt = parseFloat(getComputedStyle(input).paddingTop) || 0;\n      const tx = ir.left - textOff, ty = ir.top + pt + lh / 2 - cr.height / 2;\n      const X = { x: cr.left, v: 0, g: tx }, Y = { x: cr.top, v: -420, g: ty };   // a small upward kick gives the path an arc\n      const dist0 = Math.hypot(tx - cr.left, ty - cr.top) || 1;\n      let typed = false, fade = 1;\n      const f = dt => {\n        const n = 4, h = dt / n;\n        for (let i = 0; i < n; i++) {\n          X.v += (-170 * (X.x - X.g) - 24 * X.v) * h; X.x += X.v * h;\n          Y.v += (-260 * (Y.x - Y.g) - 30 * Y.v) * h; Y.x += Y.v * h;\n        }\n        const d = Math.hypot(X.x - X.g, Y.x - Y.g);\n        if (!typed && d < dist0 * .16) { typed = true; type(prompt); }\n        if (typed) fade -= dt * 7.5;\n        const s = 1 - (1 - Math.max(0, fade)) * .06;\n        g.style.transform = `translate3d(${X.x.toFixed(1)}px,${Y.x.toFixed(1)}px,0) scale(${s.toFixed(3)})`;\n        g.style.opacity = Math.max(0, fade).toFixed(3);\n        if (fade <= 0) { g.remove(); return false; }\n        return true;\n      };\n      f(0); wake(f);\n    }\n\n    input.addEventListener('input', () => { typing = 0; sync(); });\n    input.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey && !e.isComposing && form) { e.preventDefault(); form.requestSubmit(); } });\n    if (form) form.addEventListener('submit', e => {\n      e.preventDefault();\n      const prompt = input.value.trim(); if (!prompt) return;\n      const ev = new CustomEvent('vs-prompt-chips:submit', { bubbles: true, cancelable: true, detail: { prompt } });\n      if (!root.dispatchEvent(ev)) return;\n      const clear = () => { input.value = ''; input.style.opacity = ''; input.style.transform = ''; sync(); };\n      if (reduce.matches || !input.animate) return clear();\n      input.animate([{ opacity: 1, transform: 'none' }, { opacity: 0, transform: 'translate3d(0,-10px,0)' }], { duration: 240, easing: 'cubic-bezier(.4,0,1,1)' }).finished.then(clear);\n    });\n    sync();\n    const api = { pick: i => { const c = root.querySelectorAll('.vs-prompt-chips-chip:not([aria-hidden])')[i]; c && pick(c); }, fill: type, get input() { return input; } };\n    root.__vsPC = api;\n    return api;\n  }\n  const init = () => document.querySelectorAll('.vs-prompt-chips').forEach(el => mount(el));\n  document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();\n  window.vsPromptChips = { mount };\n})();\n</script>\n"
    }
  ],
  "meta": {
    "category": "ui",
    "group": "AI interfaces",
    "tags": [
      "ai",
      "prompt",
      "suggestions",
      "chips",
      "marquee",
      "drag",
      "input"
    ],
    "tech": [
      "CSS",
      "JS"
    ],
    "deps": [],
    "source": null,
    "free": true,
    "accent": "#c6f432",
    "added": "2026-10-01",
    "demo": "https://vividsites.app/api/component/prompt-chips/index.html",
    "thumbnail": "https://vividsites.app/api/component/prompt-chips/thumb.jpg",
    "video": "https://vividsites.app/api/component/prompt-chips/thumb.mp4",
    "page": "https://vividsites.app/c/prompt-chips"
  }
}