{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"concierge-chat","type":"registry:block","title":"concierge chat","description":"\"add 50\" — a styling/product advisor that streams answers, knows the catalog, and hands off to checkout.","author":"Motion Menu","categories":["role-product","ai","realtime"],"dependencies":[],"registryDependencies":["@motionmenu/motion-menu-runtime"],"meta":{"number":"50","slug":"concierge-chat","framework":"react","docs":"https://motion-menu-two.vercel.app/p/concierge-chat.md"},"files":[{"path":"motion-menu/concierge-chat.html","type":"registry:file","target":"motion-menu/concierge-chat.html","content":"<article class=\"card ai-card\">\n        <header><span class=\"num\">50</span><h3>Concierge chat</h3><div class=\"tags\"><i>Claude API</i><i>streaming</i></div><a class=\"format-action\" data-format-link data-pattern-slug=\"concierge-chat\" href=\"/r/concierge-chat.json?framework=react\" title=\"Open the React registry output\">React ↗</a></header>\n        <div class=\"demo\" style=\"align-items:flex-start; padding:1.2rem 0;\">\n          <span class=\"ai-badge\">simulation</span>\n          <div class=\"chat-demo\" id=\"chat-demo\">\n            <div class=\"msg user\" id=\"chat-u\"></div>\n            <div class=\"msg bot\" id=\"chat-b\"></div>\n          </div>\n        </div>\n        <p class=\"order\">Say: <b>\"add 50\"</b> — a styling/product advisor that streams answers, knows the catalog, and hands off to checkout.</p>\n      </article>"},{"path":"motion-menu/concierge-chat.css","type":"registry:file","target":"motion-menu/concierge-chat.css","content":".card {\n    border: 1px solid var(--line); border-radius: 14px; background: var(--card);\n    overflow: hidden; display: flex; flex-direction: column;\n  }\n\n.card > header { display: flex; align-items: baseline; gap: .8rem; padding: 1.1rem 1.2rem .9rem; border-bottom: 1px solid var(--line); }\n\n.card .num { font-family: var(--mono); color: var(--acc); font-size: 1.05rem; }\n\n.card h3 { font-size: 1.02rem; font-weight: 600; letter-spacing: -0.01em; flex: 1; }\n\n.tags { display: flex; gap: .35rem; }\n\n.tags i {\n    font: 400 .62rem/1 var(--mono); font-style: normal; color: var(--dim);\n    border: 1px solid var(--line); border-radius: 99px; padding: .28em .6em; white-space: nowrap;\n  }\n\n.demo {\n    min-height: 250px; position: relative; display: flex; align-items: center; justify-content: center;\n    background: var(--bg2); overflow: hidden; flex: 1;\n  }\n\n.order { padding: .85rem 1.2rem; font-family: var(--mono); font-size: .72rem; color: var(--dim); border-top: 1px solid var(--line); }\n\n.order b { color: var(--txt); font-weight: 500; }\n\n.demo .btn.corner { position: absolute; top: 12px; right: 12px; padding: .55em 1em; font-size: .68rem; z-index: 5; }\n\n/* ---------- Section H · AI layer ---------- */\n  .ai-card { border-color: rgba(122,92,255,.4) !important; }\n\n.ai-card .num { color: var(--acc2) !important; }\n\n.ai-badge { position: absolute; top: 12px; left: 14px; font: 400 .6rem var(--mono); color: var(--acc2);\n    border: 1px solid rgba(122,92,255,.4); border-radius: 99px; padding: .3em .7em; z-index: 4; }\n\n.chat-demo { width: 84%; display: flex; flex-direction: column; gap: .55rem; font-size: .78rem; }\n\n.chat-demo .msg { padding: .6rem .85rem; border-radius: 12px; max-width: 85%; line-height: 1.45; min-height: 1em; }\n\n.chat-demo .user { align-self: flex-end; background: rgba(255,255,255,.08); border-bottom-right-radius: 3px; }\n\n.chat-demo .bot { align-self: flex-start; background: rgba(122,92,255,.16); border: 1px solid rgba(122,92,255,.3); border-bottom-left-radius: 3px; }\n\n.scrim.bot { bottom: 0; height: 34%; }\n\n.scrim.bot .s-l1 { backdrop-filter: blur(2px); -webkit-mask-image: linear-gradient(transparent 30%, #000 70%); mask-image: linear-gradient(transparent 30%, #000 70%); }\n\n.scrim.bot .s-l2 { backdrop-filter: blur(6px); -webkit-mask-image: linear-gradient(transparent 55%, #000 90%); mask-image: linear-gradient(transparent 55%, #000 90%); }\n\n.scrim.bot .s-l3 { backdrop-filter: blur(14px); -webkit-mask-image: linear-gradient(transparent 75%, #000 100%); mask-image: linear-gradient(transparent 75%, #000 100%); }\n\n.demo:has(.hascheck input:checked) .hasbtn { border-color: var(--acc); color: var(--acc); pointer-events: auto; opacity: 1; }"},{"path":"components/motion-menu/concierge-chat.tsx","type":"registry:component","content":"// @ts-nocheck — generated wrapper; inlined pattern JS is imperative DOM code, not authored TS.\n\"use client\";\n// concierge chat — Motion Menu\n// Calls the Motion Menu shared runtime (webglDemo / canvas2dDemo / art helpers).\n// Install `motion-menu-runtime` and mount it once at the app root, or the effect\n// below throws ReferenceError on first paint.\nimport { useEffect, useRef } from \"react\";\n\nconst MARKUP = \"<article class=\\\"card ai-card\\\">\\n        <header><span class=\\\"num\\\">50</span><h3>Concierge chat</h3><div class=\\\"tags\\\"><i>Claude API</i><i>streaming</i></div><a class=\\\"format-action\\\" data-format-link data-pattern-slug=\\\"concierge-chat\\\" href=\\\"/r/concierge-chat.json?framework=react\\\" title=\\\"Open the React registry output\\\">React \\u2197</a></header>\\n        <div class=\\\"demo\\\" style=\\\"align-items:flex-start; padding:1.2rem 0;\\\">\\n          <span class=\\\"ai-badge\\\">simulation</span>\\n          <div class=\\\"chat-demo\\\" id=\\\"chat-demo\\\">\\n            <div class=\\\"msg user\\\" id=\\\"chat-u\\\"></div>\\n            <div class=\\\"msg bot\\\" id=\\\"chat-b\\\"></div>\\n          </div>\\n        </div>\\n        <p class=\\\"order\\\">Say: <b>\\\"add 50\\\"</b> \\u2014 a styling/product advisor that streams answers, knows the catalog, and hands off to checkout.</p>\\n      </article>\";\n\nexport function ConciergeChat() {\n  const hostRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const host = hostRef.current;\n    if (!host) return;\n    const $ = (s: string) => host.querySelector(s) as HTMLElement | null;\n    let disposed = false;\n    const cleanup: Array<() => void> = [];\n\n    (async () => {\n      if (disposed) return;\n      try {\n        /* ---- 50 · concierge chat ---- */\n        (async function chatLoop() {\n          const u = document.getElementById('chat-u');\n          const b = document.getElementById('chat-b');\n          const exchanges = [\n            ['Anniversary gift, she wears silver, under $150?', 'Three picks: the Luna cuff, the Signet chain, or the Vesper studs — the cuff ships fastest. Want it wrapped?'],\n            ['Do these hoops go with a September wedding look?', 'Yes — pair the Aster hoops with something matte, not glossy. Here are two sets guests bought together.'],\n            ['I need to fit out a 6-desk office by March.', 'Doable. I can draft a costed plan from the catalog — sit-stand desks, task chairs, acoustics — in about a minute.'],\n          ];\n          let i = 0;\n          while (true) {\n            const [q, a] = exchanges[i % exchanges.length];\n            b.textContent = ''; u.textContent = '';\n            await typeInto(u, q, 24);\n            await wait(500);\n            await typeInto(b, a, 14);\n            await wait(2600);\n            i++;\n          }\n        })();\n      } catch (err: any) {\n        console.warn(\"[motion-menu] concierge-chat failed to start:\", err);\n      }\n    })();\n\n    return () => {\n      disposed = true;\n      cleanup.forEach((fn) => { try { fn(); } catch (_) {} });\n    };\n  }, []);\n\n  return <div ref={hostRef} dangerouslySetInnerHTML={{ __html: MARKUP }} />;\n}\n\nexport default ConciergeChat;\n"}]}