{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"project-calculator","type":"registry:block","title":"project calculator","description":"\"intake = 128\" — dimensions in, costed plan out, live. The DAF brief as an instrument instead of a form.","author":"Motion Menu","categories":["role-pricing","procurement","b2b"],"dependencies":["gsap"],"registryDependencies":[],"meta":{"number":"128","slug":"project-calculator","framework":"react","docs":"https://motion-menu-two.vercel.app/p/project-calculator.md"},"files":[{"path":"motion-menu/project-calculator.html","type":"registry:file","target":"motion-menu/project-calculator.html","content":"<article class=\"card\">\n        <header><span class=\"num\">128</span><h3>Project calculator</h3><div class=\"tags\"><i>procurement</i><i>real</i></div><a class=\"format-action\" data-format-link data-pattern-slug=\"project-calculator\" href=\"/r/project-calculator.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=\"calc\">\n            <div class=\"c-row\"><label>space</label><select id=\"pc-space\" aria-label=\"pc-space\"><option>open office</option><option>meeting suite</option><option>reception</option></select></div>\n            <div class=\"c-row\"><label>people</label><input type=\"range\" id=\"pc-people\" min=\"2\" max=\"40\" value=\"8\" aria-label=\"pc-people\"><b id=\"pc-people-n\" style=\"color:var(--txt)\">8</b></div>\n            <div class=\"c-row\"><label>tier</label><div class=\"c-tiers\" id=\"pc-tiers\"><button class=\"on\">value</button><button>studio</button><button>flagship</button></div></div>\n            <div class=\"c-out\" id=\"pc-out\"></div>\n          </div>\n        </div>\n        <p class=\"order\">Say: <b>\"intake = 128\"</b> — dimensions in, costed plan out, live. The DAF brief as an instrument instead of a form.</p>\n      </article>"},{"path":"motion-menu/project-calculator.css","type":"registry:file","target":"motion-menu/project-calculator.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.ai-card .num { color: var(--acc2) !important; }\n\n.tabs button.on { color: #0a0a0e; }\n\n.car-dots i.on { background: var(--acc); transform: scale(1.3); }\n\n.chap-dots i.on { background: var(--acc); }\n\n.cfg-swatch.on { border-color: #fff; }\n\n.cfg-finish.on { border-color: var(--acc); color: var(--acc); }\n\n.vfx-btns button.on { border-color: var(--acc); color: var(--acc); }\n\n.sw-chip.on { border-color: #fff; transform: scale(1.08); }\n\n.calc { width: 90%; font: 400 .7rem var(--mono); display: flex; flex-direction: column; gap: .9rem; }\n\n.calc .c-row { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }\n\n.calc .c-row label { color: var(--dim); width: 82px; flex: none; }\n\n.calc select { background: var(--bg2); border: 1px solid var(--line); color: var(--txt); border-radius: 8px;\n    font: 400 .72rem var(--mono); padding: .5rem .7rem; }\n\n.calc input[type=range] { flex: 1; accent-color: var(--acc); }\n\n.calc .c-tiers { display: flex; gap: .4rem; }\n\n.calc .c-tiers button { font: 500 .62rem var(--mono); color: var(--dim); background: transparent;\n    border: 1px solid var(--line); border-radius: 99px; padding: .45em 1em; }\n\n.calc .c-tiers button.on { border-color: var(--acc); color: var(--acc); }\n\n.calc .c-out { border-top: 1px solid var(--line); padding-top: .8rem; color: var(--txt); line-height: 2; }\n\n.calc .c-out .c-total { font: 700 1.3rem var(--sans); }\n\n.calc .c-out .c-total small { font: 400 .62rem var(--mono); color: var(--dim); }\n\n.dims .d-btns button.on { border-color: var(--acc); color: var(--acc); }\n\n.samplereq .sr-step.on { display: block; }\n\n.samplereq .sr-picks button.on { border-color: #fff; }\n\n.volmx tr.on td { background: rgba(183,255,46,.08); color: var(--txt); }\n\n.intwall .iw-filters button.on { border-color: var(--acc); color: var(--acc); }\n\n.lc-stages button.on { border-color: var(--acc); color: var(--acc); }\n\n.install-flow .if-step.on { border-color: var(--acc); color: var(--txt); }\n\n.demo:has(.hascheck input:checked) .hasbtn { border-color: var(--acc); color: var(--acc); pointer-events: auto; opacity: 1; }\n\n.bs-days button.on, .bs-times button.on { border-color: var(--acc); color: var(--acc); }\n\n.gw-box.on { border-color: #fff; }\n\n.stone.on { color: var(--acc); }\n\n.hot-chip.on { opacity: 1; }\n\n.lot-chip.on { color: var(--acc); border-color: var(--acc); }\n\n.gx-veil.on { opacity: 1; }\n\n.fp-pal.on { border-color: var(--acc); color: var(--acc); }\n\n.tier-layer.on { opacity: 1; }\n\n.tier-btn.on { border-color: var(--acc); color: var(--acc); }"},{"path":"components/motion-menu/project-calculator.tsx","type":"registry:component","content":"// @ts-nocheck — generated wrapper; inlined pattern JS is imperative DOM code, not authored TS.\n\"use client\";\n// project calculator — Motion Menu\nimport { useEffect, useRef } from \"react\";\n\nconst MARKUP = \"<article class=\\\"card\\\">\\n        <header><span class=\\\"num\\\">128</span><h3>Project calculator</h3><div class=\\\"tags\\\"><i>procurement</i><i>real</i></div><a class=\\\"format-action\\\" data-format-link data-pattern-slug=\\\"project-calculator\\\" href=\\\"/r/project-calculator.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=\\\"calc\\\">\\n            <div class=\\\"c-row\\\"><label>space</label><select id=\\\"pc-space\\\" aria-label=\\\"pc-space\\\"><option>open office</option><option>meeting suite</option><option>reception</option></select></div>\\n            <div class=\\\"c-row\\\"><label>people</label><input type=\\\"range\\\" id=\\\"pc-people\\\" min=\\\"2\\\" max=\\\"40\\\" value=\\\"8\\\" aria-label=\\\"pc-people\\\"><b id=\\\"pc-people-n\\\" style=\\\"color:var(--txt)\\\">8</b></div>\\n            <div class=\\\"c-row\\\"><label>tier</label><div class=\\\"c-tiers\\\" id=\\\"pc-tiers\\\"><button class=\\\"on\\\">value</button><button>studio</button><button>flagship</button></div></div>\\n            <div class=\\\"c-out\\\" id=\\\"pc-out\\\"></div>\\n          </div>\\n        </div>\\n        <p class=\\\"order\\\">Say: <b>\\\"intake = 128\\\"</b> \\u2014 dimensions in, costed plan out, live. The DAF brief as an instrument instead of a form.</p>\\n      </article>\";\n\nexport function ProjectCalculator() {\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        /* ---- 128 · project calculator ---- */\n        {\n          const TIER_MULT = [1, 1.55, 2.4];\n          const TIER_LEAD = ['4–6 wk', '6–9 wk', '10–14 wk'];\n          let tier = 0;\n          const out = document.getElementById('pc-out');\n          const people = document.getElementById('pc-people');\n          const space = document.getElementById('pc-space');\n          const total = { v: 0 };\n          function recompute() {\n            const p = Number(people.value);\n            document.getElementById('pc-people-n').textContent = p;\n            const m = TIER_MULT[tier];\n            const lines = space.selectedIndex === 1\n              ? [['Meeting table', Math.ceil(p / 8), 1450], ['Task chairs', p, 340], ['AV credenza', 1, 960], ['Acoustic panels', Math.ceil(p / 3), 190]]\n              : space.selectedIndex === 2\n              ? [['Reception desk', 1, 2200], ['Lounge seating', Math.ceil(p / 2), 720], ['Planters', Math.ceil(p / 3), 140], ['Lighting', Math.ceil(p / 4), 380]]\n              : [['Sit-stand desks', p, 820], ['Task chairs', p, 340], ['Acoustic panels', Math.ceil(p / 4), 190], ['Storage', Math.ceil(p / 6), 960]];\n            const sum = Math.round(lines.reduce((s, [, q, u]) => s + q * u * m, 0));\n            out.innerHTML = lines.map(([n, q, u]) => `${q}× ${n} — $${Math.round(u * m).toLocaleString()}`).join('<br>')\n              + `<br><span class=\"c-total\">$<span id=\"pc-total\">${Math.round(total.v).toLocaleString()}</span> <small>±8% · lead ${TIER_LEAD[tier]}</small></span>`;\n            gsap.to(total, { v: sum, duration: 0.7, ease: 'power2.out',\n              onUpdate: () => { const el = document.getElementById('pc-total'); if (el) el.textContent = Math.round(total.v).toLocaleString(); } });\n          }\n          document.querySelectorAll('#pc-tiers button').forEach((b, i) => b.addEventListener('click', () => {\n            tier = i;\n            document.querySelectorAll('#pc-tiers button').forEach((x, j) => x.classList.toggle('on', j === i));\n            recompute();\n          }));\n          people.addEventListener('input', recompute);\n          space.addEventListener('change', recompute);\n          recompute();\n        }\n      } catch (err: any) {\n        console.warn(\"[motion-menu] project-calculator 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 ProjectCalculator;\n"}]}