{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"browser-ai-search-transformers-js","type":"registry:block","title":"in-browser AI search (transformers.js)","description":"\"vibe search for real = 171\" — the actual model from 51's simulation, running in the page. Type a feeling, get ranked products with similarity scores.","author":"Motion Menu","categories":["role-type","heavy","frontier","transformers-js"],"dependencies":["gsap"],"registryDependencies":[],"meta":{"number":"171","slug":"browser-ai-search-transformers-js","framework":"react","docs":"https://motion-menu-two.vercel.app/p/browser-ai-search-transformers-js.md"},"files":[{"path":"motion-menu/browser-ai-search-transformers-js.html","type":"registry:file","target":"motion-menu/browser-ai-search-transformers-js.html","content":"<article class=\"card\" data-cdn-hosts=\"huggingface\">\n        <header><span class=\"num\">171</span><h3>In-browser AI search</h3><div class=\"tags\"><i>transformers.js</i><i>real</i></div><a class=\"format-action\" data-format-link data-pattern-slug=\"browser-ai-search-transformers-js\" href=\"/r/browser-ai-search-transformers-js.json?framework=react\" title=\"Open the React registry output\">React ↗</a></header>\n        <div class=\"demo\" style=\"padding:1.2rem 0; align-items:flex-start;\">\n          <div class=\"heavy\">\n            <button class=\"btn\" id=\"tf-load\">load the model (~25 MB, runs on your GPU)</button>\n            <div class=\"hv-status\" id=\"tf-status\">real neural embeddings — no server, no API cost</div>\n            <input type=\"text\" id=\"tf-query\" placeholder=\"then search by vibe: 'something quiet for daily wear'…\" disabled aria-label=\"then search by vibe:\">\n            <div class=\"hv-hits\" id=\"tf-hits\"></div>\n          </div>\n        </div>\n        <p class=\"order\">Say: <b>\"vibe search for real = 171\"</b> — the actual model from 51's simulation, running in the page. Type a feeling, get ranked products with similarity scores.</p>\n      </article>"},{"path":"motion-menu/browser-ai-search-transformers-js.css","type":"registry:file","target":"motion-menu/browser-ai-search-transformers-js.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.btn {\n    font: 500 .8rem/1 var(--mono); color: var(--txt); background: transparent;\n    border: 1px solid rgba(255,255,255,.25); border-radius: 99px; padding: .8em 1.6em;\n    transition: border-color .3s;\n  }\n\n.btn:hover { border-color: var(--acc); color: var(--acc); }\n\n.demo .btn.corner { position: absolute; top: 12px; right: 12px; padding: .55em 1em; font-size: .68rem; z-index: 5; }\n\n.ai-card .num { color: var(--acc2) !important; }\n\n.brief a.btn { text-decoration: none; text-align: center; }\n\n/* ---------- Section AA · heavy tech ---------- */\n  .heavy { width: 90%; font: 400 .7rem var(--mono); color: var(--txt); display: flex; flex-direction: column; gap: .7rem; }\n\n.heavy .hv-status { color: var(--dim); min-height: 1.4em; }\n\n.heavy .hv-status b { color: var(--acc); }\n\n.heavy input[type=text], .heavy textarea { background: rgba(255,255,255,.04); border: 1px solid var(--line); border-radius: 8px;\n    color: var(--txt); font: 400 .72rem var(--mono); padding: .6rem .8rem; width: 100%; }\n\n.heavy textarea { height: 58px; resize: none; }\n\n.heavy table { width: 100%; border-collapse: collapse; }\n\n.heavy td, .heavy th { padding: .4rem .5rem; border-bottom: 1px solid var(--line); text-align: left; color: var(--dim); }\n\n.heavy th { color: var(--txt); }\n\n.hv-hits { display: flex; flex-direction: column; gap: .4rem; }\n\n.hv-hit { border: 1px solid rgba(122,92,255,.35); border-radius: 8px; padding: .55rem .8rem; display: flex; justify-content: space-between; }\n\n.hv-hit b { color: var(--acc); font-weight: 500; }\n\n.demo:has(.hascheck input:checked) .hasbtn { border-color: var(--acc); color: var(--acc); pointer-events: auto; opacity: 1; }"},{"path":"components/motion-menu/browser-ai-search-transformers-js.tsx","type":"registry:component","content":"// @ts-nocheck — generated wrapper; inlined pattern JS is imperative DOM code, not authored TS.\n\"use client\";\n// in-browser AI search (transformers.js) — Motion Menu\nimport { useEffect, useRef } from \"react\";\n\nconst MARKUP = \"<article class=\\\"card\\\" data-cdn-hosts=\\\"huggingface\\\">\\n        <header><span class=\\\"num\\\">171</span><h3>In-browser AI search</h3><div class=\\\"tags\\\"><i>transformers.js</i><i>real</i></div><a class=\\\"format-action\\\" data-format-link data-pattern-slug=\\\"browser-ai-search-transformers-js\\\" href=\\\"/r/browser-ai-search-transformers-js.json?framework=react\\\" title=\\\"Open the React registry output\\\">React \\u2197</a></header>\\n        <div class=\\\"demo\\\" style=\\\"padding:1.2rem 0; align-items:flex-start;\\\">\\n          <div class=\\\"heavy\\\">\\n            <button class=\\\"btn\\\" id=\\\"tf-load\\\">load the model (~25 MB, runs on your GPU)</button>\\n            <div class=\\\"hv-status\\\" id=\\\"tf-status\\\">real neural embeddings \\u2014 no server, no API cost</div>\\n            <input type=\\\"text\\\" id=\\\"tf-query\\\" placeholder=\\\"then search by vibe: 'something quiet for daily wear'\\u2026\\\" disabled aria-label=\\\"then search by vibe:\\\">\\n            <div class=\\\"hv-hits\\\" id=\\\"tf-hits\\\"></div>\\n          </div>\\n        </div>\\n        <p class=\\\"order\\\">Say: <b>\\\"vibe search for real = 171\\\"</b> \\u2014 the actual model from 51's simulation, running in the page. Type a feeling, get ranked products with similarity scores.</p>\\n      </article>\";\n\nexport function BrowserAiSearchTransformersJs() {\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        /* ---- 171 · in-browser AI search (transformers.js) ---- */\n        {\n          const PRODUCTS = [\n            'Luna cuff — hammered silver, quiet everyday bracelet',\n            'Vesper statement cuff — bold heavy silver piece',\n            'Aster pendant — delicate romantic necklace',\n            'Pearl drop earrings — classic wedding jewelry',\n            'Onyx signet ring — dark heavy masculine ring',\n            'Slate cord bracelet — minimal matte texture band',\n            'Walnut sit-stand desk — warm wood office furniture',\n            'Task chair mesh — ergonomic office seating',\n            'Brass floor lamp — warm ambient living room light',\n            'Rust wool rug — soft earthy floor textile',\n          ];\n          const status = document.getElementById('tf-status');\n          const query = document.getElementById('tf-query');\n          const hits = document.getElementById('tf-hits');\n          let embedder = null, productVecs = null;\n          const cos = (a, b) => {\n            let dot = 0, na = 0, nb = 0;\n            for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; }\n            return dot / (Math.sqrt(na) * Math.sqrt(nb));\n          };\n          document.getElementById('tf-load').addEventListener('click', async function () {\n            this.disabled = true;\n            try {\n              status.innerHTML = 'loading <b>all-MiniLM-L6-v2</b>…';\n              const { pipeline } = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0');\n              embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {\n                device: 'wasm', dtype: 'q8',\n                progress_callback: (p) => { if (p.status === 'progress' && p.progress) status.innerHTML = `loading model… <b>${Math.round(p.progress)}%</b>`; },\n              });\n              status.textContent = 'embedding the catalog…';\n              productVecs = [];\n              for (const p of PRODUCTS) {\n                const out = await embedder(p, { pooling: 'mean', normalize: true });\n                productVecs.push(Array.from(out.data));\n              }\n              status.innerHTML = '<b>ready</b> — real neural search, running on your device';\n              query.disabled = false;\n              query.focus();\n            } catch (err) {\n              status.textContent = 'model load failed: ' + err.message.slice(0, 80);\n              this.disabled = false;\n            }\n          });\n          let deb = null;\n          query.addEventListener('input', () => {\n            clearTimeout(deb);\n            deb = setTimeout(async () => {\n              if (!embedder || !query.value.trim()) { hits.innerHTML = ''; return; }\n              const out = await embedder(query.value, { pooling: 'mean', normalize: true });\n              const qv = Array.from(out.data);\n              const ranked = PRODUCTS.map((p, i) => [p, cos(qv, productVecs[i])]).sort((a, b) => b[1] - a[1]).slice(0, 3);\n              hits.innerHTML = ranked.map(([p, s]) => `<div class=\"hv-hit\"><span>${p.split(' — ')[0]}</span><b>${(s * 100).toFixed(1)}%</b></div>`).join('');\n              gsap.from('.hv-hit', { opacity: 0, y: 8, duration: 0.35, stagger: 0.07 });\n            }, 350);\n          });\n        }\n      } catch (err: any) {\n        console.warn(\"[motion-menu] browser-ai-search-transformers-js 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 BrowserAiSearchTransformersJs;\n"}]}